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/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
diff --git a/docs/code_style.md b/docs/code_style.md
index dfa475e..92a934b 100644
--- a/docs/code_style.md
+++ b/docs/code_style.md
@@ -204,7 +204,7 @@ doIt('a', array(
 
 ~~~
 if ($event === null) {
-	return new Event($this);
+	return new Event();
 } elseif ($event instanceof CoolEvent) {
 	return $event->instance();
 } else {
@@ -251,10 +251,8 @@ switch ($this->phpType) {
 ~~~
 <?php
 /**
- * Component class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 ~~~
diff --git a/framework/YiiBase.php b/framework/YiiBase.php
index d01648c..16e237d 100644
--- a/framework/YiiBase.php
+++ b/framework/YiiBase.php
@@ -1,14 +1,12 @@
 <?php
 /**
- * YiiBase class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @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;
 
 /**
@@ -63,9 +61,9 @@ class YiiBase
 	 */
 	public static $classPath = array();
 	/**
-	 * @var yii\base\Application the application instance
+	 * @var yii\console\Application|yii\web\Application the application instance
 	 */
-	public static $application;
+	public static $app;
 	/**
 	 * @var array registered path aliases
 	 * @see getAlias
@@ -96,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;
 
 	/**
@@ -125,8 +123,8 @@ class YiiBase
 	 *
 	 * To import a class or a directory, one can use either path alias or class name (can be namespaced):
 	 *
-	 *  - `@application/components/GoogleMap`: importing the `GoogleMap` class with a path alias;
-	 *  - `@application/components/*`: importing the whole `components` directory with a path alias;
+	 *  - `@app/components/GoogleMap`: importing the `GoogleMap` class with a path alias;
+	 *  - `@app/components/*`: importing the whole `components` directory with a path alias;
 	 *  - `GoogleMap`: importing the `GoogleMap` class with a class name. [[autoload()]] will be used
 	 *  when this class is used for the first time.
 	 *
@@ -161,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) {
@@ -193,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;
+		}
 	}
 
 	/**
@@ -238,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);
 		}
 	}
 
@@ -262,6 +262,7 @@ class YiiBase
 	 *
 	 * @param string $className class name
 	 * @return boolean whether the class has been loaded successfully
+	 * @throws Exception if the class file does not exist
 	 */
 	public static function autoload($className)
 	{
@@ -274,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';
 			}
 		}
@@ -297,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;
@@ -322,12 +323,12 @@ class YiiBase
 	 * the class. For example,
 	 *
 	 * - `\app\components\GoogleMap`: fully-qualified namespaced class.
-	 * - `@application/components/GoogleMap`: an alias
+	 * - `@app/components/GoogleMap`: an alias
 	 *
 	 * Below are some usage examples:
 	 *
 	 * ~~~
-	 * $object = \Yii::createObject('@application/components/GoogleMap');
+	 * $object = \Yii::createObject('@app/components/GoogleMap');
 	 * $object = \Yii::createObject(array(
 	 *     'class' => '\app\components\GoogleMap',
 	 *     'apiKey' => 'xyz',
@@ -507,9 +508,6 @@ class YiiBase
 	 * i.e., the message returned will be chosen from a few candidates according to the given
 	 * number value. This feature is mainly used to solve plural format issue in case
 	 * a message has different plural forms in some languages.
-	 * @param string $category message category. Please use only word letters. Note, category 'yii' is
-	 * reserved for Yii framework core code use. See {@link CPhpMessageSource} for
-	 * more interpretation about message category.
 	 * @param string $message the original message
 	 * @param array $params parameters to be applied to the message using <code>strtr</code>.
 	 * The first parameter can be a number without key.
@@ -517,62 +515,12 @@ class YiiBase
 	 * an appropriate message translation.
 	 * You can pass parameter for {@link CChoiceFormat::format}
 	 * or plural forms format without wrapping it with array.
-	 * @param string $source which message source application component to use.
-	 * Defaults to null, meaning using 'coreMessages' for messages belonging to
-	 * the 'yii' category and using 'messages' for the rest messages.
 	 * @param string $language the target language. If null (default), the {@link CApplication::getLanguage application language} will be used.
 	 * @return string the translated message
 	 * @see CMessageSource
 	 */
-	public static function t($category, $message, $params = array(), $source = null, $language = null)
+	public static function t($message, $params = array(), $language = null)
 	{
-		// todo;
-		return $params !== array() ? strtr($message, $params) : $message;
-		if (self::$application !== null)
-		{
-			if ($source === null)
-					{
-						$source = $category === 'yii' ? 'coreMessages' : 'messages';
-					}
-			if (($source = self::$application->getComponent($source)) !== null)
-					{
-						$message = $source->translate($category, $message, $language);
-					}
-		}
-		if ($params === array())
-				{
-					return $message;
-				}
-		if (!is_array($params))
-				{
-					$params = array($params);
-				}
-		if (isset($params[0])) // number choice
-		{
-			if (strpos($message, '|') !== false)
-			{
-				if (strpos($message, '#') === false)
-				{
-					$chunks = explode('|', $message);
-					$expressions = self::$application->getLocale($language)->getPluralRules();
-					if ($n = min(count($chunks), count($expressions)))
-					{
-						for ($i = 0; $i < $n; $i++)
-								{
-									$chunks[$i] = $expressions[$i] . '#' . $chunks[$i];
-								}
-
-						$message = implode('|', $chunks);
-					}
-				}
-				$message = CChoiceFormat::format($message, $params[0]);
-			}
-			if (!isset($params['{n}']))
-					{
-						$params['{n}'] = $params[0];
-					}
-			unset($params[0]);
-		}
-		return $params !== array() ? strtr($message, $params) : $message;
+		return Yii::$app->getI18N()->translate($message, $params, $language);
 	}
 }
diff --git a/framework/base/Action.php b/framework/base/Action.php
index 8d4ec5a..7142539 100644
--- a/framework/base/Action.php
+++ b/framework/base/Action.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * Action class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
@@ -56,6 +54,15 @@ class Action extends Component
 	}
 
 	/**
+	 * Returns the unique ID of this action among the whole application.
+	 * @return string the unique ID of this action among the whole application.
+	 */
+	public function getUniqueId()
+	{
+		return $this->controller->getUniqueId() . '/' . $this->id;
+	}
+
+	/**
 	 * Runs this action with the specified parameters.
 	 * This method is mainly invoked by the controller.
 	 * @param array $params the parameters to be bound to the action's run() method.
@@ -67,36 +74,7 @@ class Action extends Component
 		if (!method_exists($this, 'run')) {
 			throw new InvalidConfigException(get_class($this) . ' must define a "run()" method.');
 		}
-		$method = new \ReflectionMethod($this, 'run');
-		$args = $this->bindActionParams($method, $params);
-		return (int)$method->invokeArgs($this, $args);
-	}
-
-	/**
-	 * Binds the given parameters to the action method.
-	 * The returned array contains the parameters that need to be passed to the action method.
-	 * This method calls [[Controller::validateActionParams()]] to check if any exception
-	 * should be raised if there are missing or unknown parameters.
-	 * @param \ReflectionMethod $method the action method reflection object
-	 * @param array $params the supplied parameters
-	 * @return array the parameters that can be passed to the action method
-	 */
-	protected function bindActionParams($method, $params)
-	{
-		$args = array();
-		$missing = array();
-		foreach ($method->getParameters() as $param) {
-			$name = $param->getName();
-			if (array_key_exists($name, $params)) {
-				$args[] = $params[$name];
-				unset($params[$name]);
-			} elseif ($param->isDefaultValueAvailable()) {
-				$args[] = $param->getDefaultValue();
-			} else {
-				$missing[] = $name;
-			}
-		}
-		$this->controller->validateActionParams($this, $missing, $params);
-		return $args;
+		$args = $this->controller->bindActionParams($this, $params);
+		return (int)call_user_func_array(array($this, 'run'), $args);
 	}
 }
diff --git a/framework/base/ActionEvent.php b/framework/base/ActionEvent.php
index ee945a8..7c5a40c 100644
--- a/framework/base/ActionEvent.php
+++ b/framework/base/ActionEvent.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * ActionEvent class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
diff --git a/framework/base/ActionFilter.php b/framework/base/ActionFilter.php
new file mode 100644
index 0000000..1f82e5d
--- /dev/null
+++ b/framework/base/ActionFilter.php
@@ -0,0 +1,90 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\base;
+
+/**
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+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).
+	 */
+	public function events()
+	{
+		return array(
+			'beforeAction' => 'beforeFilter',
+			'afterAction' => 'afterFilter',
+		);
+	}
+
+	/**
+	 * @param ActionEvent $event
+	 * @return boolean
+	 */
+	public function beforeFilter($event)
+	{
+		if ($this->isActive($event->action)) {
+			$event->isValid = $this->beforeAction($event->action);
+		}
+		return $event->isValid;
+	}
+
+	/**
+	 * @param ActionEvent $event
+	 * @return boolean
+	 */
+	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/framework/base/Application.php b/framework/base/Application.php
index 40e8437..9be1939 100644
--- a/framework/base/Application.php
+++ b/framework/base/Application.php
@@ -1,16 +1,14 @@
 <?php
 /**
- * Application class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
 namespace yii\base;
 
 use Yii;
-use yii\util\FileHelper;
+use yii\helpers\FileHelper;
 
 /**
  * Application is the base class for all application classes.
@@ -30,10 +28,6 @@ use yii\util\FileHelper;
  *   persistence method. This application component is dynamically loaded when needed.</li>
  * <li>{@link getCache cache}: provides caching feature. This application component is
  *   disabled by default.</li>
- * <li>{@link getMessages messages}: provides the message source for translating
- *   application messages. This application component is dynamically loaded when needed.</li>
- * <li>{@link getCoreMessages coreMessages}: provides the message source for translating
- *   Yii framework messages. This application component is dynamically loaded when needed.</li>
  * </ul>
  *
  * Application will undergo the following life cycles when processing a user request:
@@ -57,29 +51,34 @@ class Application extends Module
 	const EVENT_BEFORE_REQUEST = 'beforeRequest';
 	const EVENT_AFTER_REQUEST = 'afterRequest';
 	/**
-	 * @var string the application name. Defaults to 'My Application'.
+	 * @var string the application name.
 	 */
 	public $name = 'My Application';
 	/**
-	 * @var string the version of this application. Defaults to '1.0'.
+	 * @var string the version of this application.
 	 */
 	public $version = '1.0';
 	/**
-	 * @var string the charset currently used for the application. Defaults to 'UTF-8'.
+	 * @var string the charset currently used for the application.
 	 */
 	public $charset = 'UTF-8';
 	/**
+	 * @var string the language that is meant to be used for end users.
+	 * @see sourceLanguage
+	 */
+	public $language = 'en_US';
+	/**
 	 * @var string the language that the application is written in. This mainly refers to
-	 * the language that the messages and view files are in. Defaults to 'en_us' (US English).
+	 * the language that the messages and view files are written in.
 	 * @see language
 	 */
-	public $sourceLanguage = 'en_us';
+	public $sourceLanguage = 'en_US';
 	/**
 	 * @var array IDs of the components that need to be loaded when the application starts.
 	 */
 	public $preload = array();
 	/**
-	 * @var Controller the currently active controller instance
+	 * @var \yii\web\Controller|\yii\console\Controller the currently active controller instance
 	 */
 	public $controller;
 	/**
@@ -93,7 +92,12 @@ class Application extends Module
 
 	private $_runtimePath;
 	private $_ended = false;
-	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.
@@ -104,11 +108,12 @@ class Application extends Module
 	 */
 	public function __construct($id, $basePath, $config = array())
 	{
-		Yii::$application = $this;
+		Yii::$app = $this;
 		$this->id = $id;
 		$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());
 		}
@@ -141,12 +146,64 @@ class Application extends Module
 			$this->_ended = true;
 			$this->afterRequest();
 		}
+
+		$this->handleFatalError();
+
 		if ($exit) {
 			exit($status);
 		}
 	}
 
 	/**
+	 * Handles fatal PHP errors
+	 */
+	public function handleFatalError()
+	{
+		if (YII_ENABLE_ERROR_HANDLER) {
+			$error = error_get_last();
+
+			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')) {
+					$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);
+				}
+
+				exit(1);
+			}
+		}
+	}
+
+	/**
 	 * Runs the application.
 	 * This is the main entrance of an application.
 	 * @return integer the exit status (0 means normal, non-zero values mean abnormal)
@@ -154,6 +211,10 @@ 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();
 		return $status;
@@ -213,29 +274,6 @@ class Application extends Module
 	}
 
 	/**
-	 * Returns the language that the end user is using.
-	 * @return string the language that the user is using (e.g. 'en_US', 'zh_CN').
-	 * Defaults to the value of [[sourceLanguage]].
-	 */
-	public function getLanguage()
-	{
-		return $this->_language === null ? $this->sourceLanguage : $this->_language;
-	}
-
-	/**
-	 * Specifies which language the end user is using.
-	 * This is the language that the application should use to display to end users.
-	 * By default, [[language]] and [[sourceLanguage]] are the same.
-	 * Do not set this property unless your application needs to support multiple languages.
-	 * @param string $language the user language (e.g. 'en_US', 'zh_CN').
-	 * If it is null, the [[sourceLanguage]] will be used.
-	 */
-	public function setLanguage($language)
-	{
-		$this->_language = $language;
-	}
-
-	/**
 	 * Returns the time zone used by this application.
 	 * This is a simple wrapper of PHP function date_default_timezone_get().
 	 * @return string the time zone used by this application.
@@ -257,14 +295,6 @@ class Application extends Module
 		date_default_timezone_set($value);
 	}
 
-	//	/**
-	//	 * Returns the security manager component.
-	//	 * @return SecurityManager the security manager application component.
-	//	 */
-	//	public function getSecurityManager()
-	//	{
-	//		return $this->getComponent('securityManager');
-	//	}
 	//
 	//	/**
 	//	 * Returns the locale instance.
@@ -295,23 +325,6 @@ class Application extends Module
 	//		return $this->getLocale()->getDateFormatter();
 	//	}
 	//
-	//	/**
-	//	 * Returns the core message translations component.
-	//	 * @return \yii\i18n\MessageSource the core message translations
-	//	 */
-	//	public function getCoreMessages()
-	//	{
-	//		return $this->getComponent('coreMessages');
-	//	}
-	//
-	//	/**
-	//	 * Returns the application message translations component.
-	//	 * @return \yii\i18n\MessageSource the application message translations
-	//	 */
-	//	public function getMessages()
-	//	{
-	//		return $this->getComponent('messages');
-	//	}
 
 	/**
 	 * Returns the database connection component.
@@ -332,15 +345,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.
 	 */
@@ -351,7 +355,7 @@ class Application extends Module
 
 	/**
 	 * Returns the request component.
-	 * @return Request the request component
+	 * @return \yii\web\Request|\yii\console\Request the request component
 	 */
 	public function getRequest()
 	{
@@ -359,12 +363,30 @@ 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 getView()
+	{
+		return $this->getComponent('view');
+	}
+
+	/**
+	 * 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
 	 */
-	public function getViewRenderer()
+	public function getI18N()
 	{
-		return $this->getComponent('viewRenderer');
+		return $this->getComponent('i18n');
 	}
 
 	/**
@@ -372,9 +394,7 @@ class Application extends Module
 	 */
 	public function registerDefaultAliases()
 	{
-		Yii::$aliases['@application'] = $this->getBasePath();
-		Yii::$aliases['@entry'] = dirname($_SERVER['SCRIPT_FILENAME']);
-		Yii::$aliases['@www'] = '';
+		Yii::$aliases['@app'] = $this->getBasePath();
 	}
 
 	/**
@@ -387,20 +407,15 @@ class Application extends Module
 			'errorHandler' => array(
 				'class' => 'yii\base\ErrorHandler',
 			),
-			'coreMessages' => array(
-				'class' => 'yii\i18n\PhpMessageSource',
-				'language' => 'en_us',
-				'basePath' => '@yii/messages',
-			),
-			'messages' => array(
-				'class' => 'yii\i18n\PhpMessageSource',
-			),
-			'securityManager' => array(
-				'class' => 'yii\base\SecurityManager',
+			'i18n' => array(
+				'class' => 'yii\i18n\I18N',
 			),
 			'urlManager' => array(
 				'class' => 'yii\web\UrlManager',
 			),
+			'view' => array(
+				'class' => 'yii\base\View',
+			),
 		));
 	}
 
@@ -413,12 +428,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;
 		}
 	}
 
@@ -447,11 +474,14 @@ 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";
 			$msg .= (string)$exception;
+			if (YII_DEBUG) {
+				echo $msg;
+			}
 			$msg .= "\n\$_SERVER = " . var_export($_SERVER, true);
 			error_log($msg);
 			exit(1);
@@ -464,7 +494,7 @@ class Application extends Module
 	 */
 	public function renderException($exception)
 	{
-		if ($exception instanceof Exception && ($exception->causedByUser || !YII_DEBUG)) {
+		if ($exception instanceof Exception && ($exception instanceof UserException || !YII_DEBUG)) {
 			$message = $exception->getName() . ': ' . $exception->getMessage();
 		} else {
 			$message = YII_DEBUG ? (string)$exception : 'Error: ' . $exception->getMessage();
diff --git a/framework/base/Behavior.php b/framework/base/Behavior.php
index 9155097..abe08bb 100644
--- a/framework/base/Behavior.php
+++ b/framework/base/Behavior.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * Behavior class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
diff --git a/framework/base/Component.php b/framework/base/Component.php
index c6c91eb..f1d549b 100644
--- a/framework/base/Component.php
+++ b/framework/base/Component.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * Component class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
@@ -60,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);
 	}
 
 	/**
@@ -107,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);
 		}
 	}
 
@@ -424,7 +422,10 @@ class Component extends \yii\base\Object
 		$this->ensureBehaviors();
 		if (isset($this->_e[$name]) && $this->_e[$name]->getCount()) {
 			if ($event === null) {
-				$event = new Event($this);
+				$event = new Event;
+			}
+			if ($event->sender === null) {
+				$event->sender = $this;
 			}
 			$event->handled = false;
 			$event->name = $name;
diff --git a/framework/base/Controller.php b/framework/base/Controller.php
index 804b339..ff6d8f7 100644
--- a/framework/base/Controller.php
+++ b/framework/base/Controller.php
@@ -1,23 +1,19 @@
 <?php
 /**
- * Controller class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
 namespace yii\base;
 
 use Yii;
-use yii\util\StringHelper;
+use yii\helpers\FileHelper;
+use yii\helpers\StringHelper;
 
 /**
  * Controller is the base class for classes containing controller logic.
  *
- * @property string $route the route (module ID, controller ID and action ID) of the current request.
- * @property string $uniqueId the controller ID that is prefixed with the module ID (if any).
- *
  * @author Qiang Xue <qiang.xue@gmail.com>
  * @since 2.0
  */
@@ -72,9 +68,9 @@ class Controller extends Component
 	 *
 	 * ~~~
 	 * return array(
-	 *     'action1' => '@application/components/Action1',
+	 *     'action1' => '@app/components/Action1',
 	 *     'action2' => array(
-	 *         'class' => '@application/components/Action2',
+	 *         'class' => '@app/components/Action2',
 	 *         'property1' => 'value1',
 	 *         'property2' => 'value2',
 	 *     ),
@@ -139,8 +135,50 @@ class Controller extends Component
 		} elseif ($pos > 0) {
 			return $this->module->runAction($route, $params);
 		} else {
-			return \Yii::$application->runAction(ltrim($route, '/'), $params);
+			return \Yii::$app->runAction(ltrim($route, '/'), $params);
+		}
+	}
+
+	/**
+	 * Binds the parameters to the action.
+	 * This method is invoked by [[Action]] when it begins to run with the given parameters.
+	 * This method will check the parameter names that the action requires and return
+	 * the provided parameters according to the requirement. If there is any missing parameter,
+	 * an exception will be thrown.
+	 * @param Action $action the action to be bound with parameters
+	 * @param array $params the parameters to be bound to the action
+	 * @return array the valid parameters that the action can run with.
+	 * @throws InvalidRequestException if there are missing parameters.
+	 */
+	public function bindActionParams($action, $params)
+	{
+		if ($action instanceof InlineAction) {
+			$method = new \ReflectionMethod($this, $action->actionMethod);
+		} else {
+			$method = new \ReflectionMethod($action, 'run');
+		}
+
+		$args = array();
+		$missing = array();
+		foreach ($method->getParameters() as $param) {
+			$name = $param->getName();
+			if (array_key_exists($name, $params)) {
+				$args[] = $params[$name];
+				unset($params[$name]);
+			} elseif ($param->isDefaultValueAvailable()) {
+				$args[] = $param->getDefaultValue();
+			} else {
+				$missing[] = $name;
+			}
+		}
+
+		if ($missing !== array()) {
+			throw new InvalidRequestException(Yii::t('yii|Missing required parameters: {params}', array(
+				'{params}' => implode(', ', $missing),
+			)));
 		}
+
+		return $args;
 	}
 
 	/**
@@ -250,34 +288,51 @@ class Controller extends Component
 	 */
 	public function getRoute()
 	{
-		return $this->action !== null ? $this->getUniqueId() . '/' . $this->action->id : $this->getUniqueId();
+		return $this->action !== null ? $this->action->getUniqueId() : $this->getUniqueId();
 	}
 
 	/**
 	 * 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);
+		$output = Yii::$app->getView()->render($view, $params, $this);
+		$layoutFile = $this->findLayoutFile();
+		if ($layoutFile !== false) {
+			return Yii::$app->getView()->renderFile($layoutFile, array('content' => $output), $this);
+		} else {
+			return $output;
+		}
 	}
 
+	/**
+	 * 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 Yii::$app->getView()->render($view, $params, $this);
 	}
 
-	public function createView()
+	/**
+	 * 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 new View($this);
+		return Yii::$app->getView()->renderFile($file, $params, $this);
 	}
 
 	/**
@@ -290,4 +345,63 @@ class Controller extends Component
 	{
 		return $this->module->getViewPath() . DIRECTORY_SEPARATOR . $this->id;
 	}
+
+	/**
+	 * 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()
+	{
+		$module = $this->module;
+		if (is_string($this->layout)) {
+			$view = $this->layout;
+		} elseif ($this->layout === null) {
+			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/Dictionary.php b/framework/base/Dictionary.php
index cc61886..52262cb 100644
--- a/framework/base/Dictionary.php
+++ b/framework/base/Dictionary.php
@@ -1,15 +1,13 @@
 <?php
 /**
- * Dictionary class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
 namespace yii\base;
 
-use yii\util\ArrayHelper;
+use yii\helpers\ArrayHelper;
 
 /**
  * Dictionary implements a collection that stores key-value pairs.
@@ -150,7 +148,7 @@ class Dictionary extends Object implements \IteratorAggregate, \ArrayAccess, \Co
 	 * Defaults to false, meaning all items in the dictionary will be cleared directly
 	 * without calling [[remove]].
 	 */
-	public function clear($safeClear = false)
+	public function removeAll($safeClear = false)
 	{
 		if ($safeClear) {
 			foreach (array_keys($this->_d) as $key) {
@@ -166,7 +164,7 @@ class Dictionary extends Object implements \IteratorAggregate, \ArrayAccess, \Co
 	 * @param mixed $key the key
 	 * @return boolean whether the dictionary contains an item with the specified key
 	 */
-	public function contains($key)
+	public function has($key)
 	{
 		return isset($this->_d[$key]) || array_key_exists($key, $this->_d);
 	}
@@ -184,13 +182,13 @@ class Dictionary extends Object implements \IteratorAggregate, \ArrayAccess, \Co
 	 * Copies iterable data into the dictionary.
 	 * Note, existing data in the dictionary will be cleared first.
 	 * @param mixed $data the data to be copied from, must be an array or an object implementing `Traversable`
-	 * @throws InvalidCallException if data is neither an array nor an iterator.
+	 * @throws InvalidParamException if data is neither an array nor an iterator.
 	 */
 	public function copyFrom($data)
 	{
 		if (is_array($data) || $data instanceof \Traversable) {
 			if ($this->_d !== array()) {
-				$this->clear();
+				$this->removeAll();
 			}
 			if ($data instanceof self) {
 				$data = $data->_d;
@@ -199,7 +197,7 @@ class Dictionary extends Object implements \IteratorAggregate, \ArrayAccess, \Co
 				$this->add($key, $value);
 			}
 		} else {
-			throw new InvalidCallException('Data must be either an array or an object implementing Traversable.');
+			throw new InvalidParamException('Data must be either an array or an object implementing Traversable.');
 		}
 	}
 
@@ -216,7 +214,7 @@ class Dictionary extends Object implements \IteratorAggregate, \ArrayAccess, \Co
 	 *
 	 * @param array|\Traversable $data the data to be merged with. It must be an array or object implementing Traversable
 	 * @param boolean $recursive whether the merging should be recursive.
-	 * @throws InvalidCallException if data is neither an array nor an object implementing `Traversable`.
+	 * @throws InvalidParamException if data is neither an array nor an object implementing `Traversable`.
 	 */
 	public function mergeWith($data, $recursive = true)
 	{
@@ -240,7 +238,7 @@ class Dictionary extends Object implements \IteratorAggregate, \ArrayAccess, \Co
 				}
 			}
 		} else {
-			throw new InvalidCallException('The data to be merged with must be an array or an object implementing Traversable.');
+			throw new InvalidParamException('The data to be merged with must be an array or an object implementing Traversable.');
 		}
 	}
 
@@ -254,7 +252,7 @@ class Dictionary extends Object implements \IteratorAggregate, \ArrayAccess, \Co
 	 */
 	public function offsetExists($offset)
 	{
-		return $this->contains($offset);
+		return $this->has($offset);
 	}
 
 	/**
diff --git a/framework/base/DictionaryIterator.php b/framework/base/DictionaryIterator.php
index 61f61cf..0d15bb0 100644
--- a/framework/base/DictionaryIterator.php
+++ b/framework/base/DictionaryIterator.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * DictionaryIterator class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
diff --git a/framework/base/ErrorException.php b/framework/base/ErrorException.php
new file mode 100644
index 0000000..465d839
--- /dev/null
+++ b/framework/base/ErrorException.php
@@ -0,0 +1,81 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\base;
+
+/**
+ * ErrorException represents a PHP error.
+ *
+ * @author Alexander Makarov <sam@rmcreative.ru>
+ * @since 2.0
+ */
+class ErrorException extends Exception
+{
+	protected $severity;
+
+	/**
+	 * Constructs the exception
+	 * @link http://php.net/manual/en/errorexception.construct.php
+	 * @param $message [optional]
+	 * @param $code [optional]
+	 * @param $severity [optional]
+	 * @param $filename [optional]
+	 * @param $lineno [optional]
+	 * @param $previous [optional]
+	 */
+	public function __construct($message = '', $code = 0, $severity = 1, $filename = __FILE__, $lineno = __LINE__, \Exception $previous = null)
+	{
+		parent::__construct($message, $code, $previous);
+		$this->severity = $severity;
+		$this->file = $filename;
+		$this->line = $lineno;
+	}
+
+	/**
+	 * Gets the exception severity
+	 * @link http://php.net/manual/en/errorexception.getseverity.php
+	 * @return int the severity level of the exception.
+	 */
+	final public function getSeverity()
+	{
+		return $this->severity;
+	}
+
+	/**
+	 * Returns if error is one of fatal type
+	 *
+	 * @param array $error error got from error_get_last()
+	 * @return bool if error is one of fatal type
+	 */
+	public static function isFatalErorr($error)
+	{
+		return isset($error['type']) && in_array($error['type'], 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/ErrorHandler.php b/framework/base/ErrorHandler.php
index 0b6bf97..f71b8c8 100644
--- a/framework/base/ErrorHandler.php
+++ b/framework/base/ErrorHandler.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * ErrorHandler class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
@@ -18,7 +16,7 @@ namespace yii\base;
  * @author Qiang Xue <qiang.xue@gmail.com>
  * @since 2.0
  */
-use yii\util\VarDumper;
+use yii\helpers\VarDumper;
 
 class ErrorHandler extends Component
 {
@@ -36,7 +34,7 @@ class ErrorHandler extends Component
 	public $discardExistingOutput = true;
 	/**
 	 * @var string the route (eg 'site/error') to the controller action that will be used to display external errors.
-	 * Inside the action, it can retrieve the error information by \Yii::$application->errorHandler->error.
+	 * Inside the action, it can retrieve the error information by \Yii::$app->errorHandler->error.
 	 * This property defaults to null, meaning ErrorHandler will handle the error display.
 	 */
 	public $errorAction;
@@ -71,27 +69,27 @@ class ErrorHandler extends Component
 	protected function render($exception)
 	{
 		if ($this->errorAction !== null) {
-			\Yii::$application->runAction($this->errorAction);
-		} elseif (\Yii::$application instanceof \yii\web\Application) {
+			\Yii::$app->runAction($this->errorAction);
+		} elseif (\Yii::$app instanceof \yii\web\Application) {
 			if (!headers_sent()) {
 				$errorCode = $exception instanceof HttpException ? $exception->statusCode : 500;
 				header("HTTP/1.0 $errorCode " . get_class($exception));
 			}
 			if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest') {
-				\Yii::$application->renderException($exception);
+				\Yii::$app->renderException($exception);
 			} else {
-				$view = new View($this);
-				if (!YII_DEBUG || $exception instanceof Exception && $exception->causedByUser) {
+				$view = new View;
+				if (!YII_DEBUG || $exception instanceof UserException) {
 					$viewName = $this->errorView;
 				} else {
 					$viewName = $this->exceptionView;
 				}
 				echo $view->render($viewName, array(
 					'exception' => $exception,
-				));
+				), $this);
 			}
 		} else {
-			\Yii::$application->renderException($exception);
+			\Yii::$app->renderException($exception);
 		}
 	}
 
@@ -239,7 +237,7 @@ class ErrorHandler extends Component
 
 	public function htmlEncode($text)
 	{
-		return htmlspecialchars($text, ENT_QUOTES, \Yii::$application->charset);
+		return htmlspecialchars($text, ENT_QUOTES, \Yii::$app->charset);
 	}
 
 	public function clearOutput()
@@ -255,15 +253,10 @@ class ErrorHandler extends Component
 	 */
 	public function renderAsHtml($exception)
 	{
-		$view = new View($this);
-		if (!YII_DEBUG || $exception instanceof Exception && $exception->causedByUser) {
-			$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(
 			'exception' => $exception,
-		));
+		), $this);
 	}
 }
diff --git a/framework/base/Event.php b/framework/base/Event.php
index 540e982..b86ed7c 100644
--- a/framework/base/Event.php
+++ b/framework/base/Event.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * Event class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
@@ -30,7 +28,8 @@ class Event extends \yii\base\Object
 	 */
 	public $name;
 	/**
-	 * @var object the sender of this event
+	 * @var object the sender of this event. If not set, this property will be
+	 * set as the object whose "trigger()" method is called.
 	 */
 	public $sender;
 	/**
@@ -40,21 +39,7 @@ class Event extends \yii\base\Object
 	 */
 	public $handled = false;
 	/**
-	 * @var mixed extra data associated with the event.
+	 * @var mixed extra custom data associated with the event.
 	 */
 	public $data;
-
-	/**
-	 * Constructor.
-	 *
-	 * @param mixed $sender sender of the event
-	 * @param mixed $data extra data associated with the event
-	 * @param array $config name-value pairs that will be used to initialize the object properties
-	 */
-	public function __construct($sender = null, $data = null, $config = array())
-	{
-		$this->sender = $sender;
-		$this->data = $data;
-		parent::__construct($config);
-	}
 }
diff --git a/framework/base/Exception.php b/framework/base/Exception.php
index ab681e2..9ee698b 100644
--- a/framework/base/Exception.php
+++ b/framework/base/Exception.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * Exception class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
@@ -18,16 +16,10 @@ namespace yii\base;
 class Exception extends \Exception
 {
 	/**
-	 * @var boolean whether this exception is caused by end user's mistake (e.g. wrong URL)
-	 */
-	public $causedByUser = false;
-
-	/**
 	 * @return string the user-friendly name of this exception
 	 */
 	public function getName()
 	{
-		return \Yii::t('yii', 'Exception');
+		return \Yii::t('yii|Exception');
 	}
-}
-
+}
\ No newline at end of file
diff --git a/framework/base/HttpException.php b/framework/base/HttpException.php
index d2de5bc..94a9a55 100644
--- a/framework/base/HttpException.php
+++ b/framework/base/HttpException.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * HttpException class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
@@ -19,16 +17,12 @@ namespace yii\base;
  * @author Qiang Xue <qiang.xue@gmail.com>
  * @since 2.0
  */
-class HttpException extends Exception
+class HttpException extends UserException
 {
 	/**
 	 * @var integer HTTP status code, such as 403, 404, 500, etc.
 	 */
 	public $statusCode;
-	/**
-	 * @var boolean whether this exception is caused by end user's mistake (e.g. wrong URL)
-	 */
-	public $causedByUser = true;
 
 	/**
 	 * Constructor.
@@ -41,4 +35,73 @@ class HttpException extends Exception
 		$this->statusCode = $status;
 		parent::__construct($message, $code);
 	}
+
+	/**
+	 * @return string the user-friendly name of this exception
+	 */
+	public function getName()
+	{
+		static $httpCodes = array(
+			100 => 'Continue',
+			101 => 'Switching Protocols',
+			102 => 'Processing',
+			118 => 'Connection timed out',
+			200 => 'OK',
+			201 => 'Created',
+			202 => 'Accepted',
+			203 => 'Non-Authoritative',
+			204 => 'No Content',
+			205 => 'Reset Content',
+			206 => 'Partial Content',
+			207 => 'Multi-Status',
+			210 => 'Content Different',
+			300 => 'Multiple Choices',
+			301 => 'Moved Permanently',
+			302 => 'Found',
+			303 => 'See Other',
+			304 => 'Not Modified',
+			305 => 'Use Proxy',
+			307 => 'Temporary Redirect',
+			310 => 'Too many Redirect',
+			400 => 'Bad Request',
+			401 => 'Unauthorized',
+			402 => 'Payment Required',
+			403 => 'Forbidden',
+			404 => 'Not Found',
+			405 => 'Method Not Allowed',
+			406 => 'Not Acceptable',
+			407 => 'Proxy Authentication Required',
+			408 => 'Request Time-out',
+			409 => 'Conflict',
+			410 => 'Gone',
+			411 => 'Length Required',
+			412 => 'Precondition Failed',
+			413 => 'Request Entity Too Large',
+			414 => 'Request-URI Too Long',
+			415 => 'Unsupported Media Type',
+			416 => 'Requested range unsatisfiable',
+			417 => 'Expectation failed',
+			418 => 'I’m a teapot',
+			422 => 'Unprocessable entity',
+			423 => 'Locked',
+			424 => 'Method failure',
+			425 => 'Unordered Collection',
+			426 => 'Upgrade Required',
+			449 => 'Retry With',
+			450 => 'Blocked by Windows Parental Controls',
+			500 => 'Internal Server Error',
+			501 => 'Not Implemented',
+			502 => 'Bad Gateway ou Proxy Error',
+			503 => 'Service Unavailable',
+			504 => 'Gateway Time-out',
+			505 => 'HTTP Version not supported',
+			507 => 'Insufficient storage',
+			509 => 'Bandwidth Limit Exceeded',
+		);
+
+		if(isset($httpCodes[$this->statusCode]))
+			return $httpCodes[$this->statusCode];
+		else
+			return \Yii::t('yii|Error');
+	}
 }
diff --git a/framework/base/InlineAction.php b/framework/base/InlineAction.php
index 4cd5413..c5afe28 100644
--- a/framework/base/InlineAction.php
+++ b/framework/base/InlineAction.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * InlineAction class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
@@ -45,8 +43,7 @@ class InlineAction extends Action
 	 */
 	public function runWithParams($params)
 	{
-		$method = new \ReflectionMethod($this->controller, $this->actionMethod);
-		$args = $this->bindActionParams($method, $params);
-		return (int)$method->invokeArgs($this->controller, $args);
+		$args = $this->controller->bindActionParams($this, $params);
+		return (int)call_user_func_array(array($this->controller, $this->actionMethod), $args);
 	}
 }
diff --git a/framework/base/InvalidCallException.php b/framework/base/InvalidCallException.php
index a1df021..9aefe14 100644
--- a/framework/base/InvalidCallException.php
+++ b/framework/base/InvalidCallException.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * InvalidCallException class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
@@ -15,14 +13,14 @@ namespace yii\base;
  * @author Qiang Xue <qiang.xue@gmail.com>
  * @since 2.0
  */
-class InvalidCallException extends \Exception
+class InvalidCallException extends Exception
 {
 	/**
 	 * @return string the user-friendly name of this exception
 	 */
 	public function getName()
 	{
-		return \Yii::t('yii', 'Invalid Call');
+		return \Yii::t('yii|Invalid Call');
 	}
 }
 
diff --git a/framework/base/InvalidConfigException.php b/framework/base/InvalidConfigException.php
index 3c100d1..389737c 100644
--- a/framework/base/InvalidConfigException.php
+++ b/framework/base/InvalidConfigException.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * InvalidConfigException class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
@@ -15,14 +13,14 @@ namespace yii\base;
  * @author Qiang Xue <qiang.xue@gmail.com>
  * @since 2.0
  */
-class InvalidConfigException extends \Exception
+class InvalidConfigException extends Exception
 {
 	/**
 	 * @return string the user-friendly name of this exception
 	 */
 	public function getName()
 	{
-		return \Yii::t('yii', 'Invalid Configuration');
+		return \Yii::t('yii|Invalid Configuration');
 	}
 }
 
diff --git a/framework/base/InvalidParamException.php b/framework/base/InvalidParamException.php
new file mode 100644
index 0000000..a8c96fd
--- /dev/null
+++ b/framework/base/InvalidParamException.php
@@ -0,0 +1,26 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\base;
+
+/**
+ * InvalidParamException represents an exception caused by invalid parameters passed to a method.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class InvalidParamException extends Exception
+{
+	/**
+	 * @return string the user-friendly name of this exception
+	 */
+	public function getName()
+	{
+		return \Yii::t('yii|Invalid Parameter');
+	}
+}
+
diff --git a/framework/base/InvalidRequestException.php b/framework/base/InvalidRequestException.php
index fd468a1..6663e29 100644
--- a/framework/base/InvalidRequestException.php
+++ b/framework/base/InvalidRequestException.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * InvalidRequestException class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
@@ -15,19 +13,14 @@ namespace yii\base;
  * @author Qiang Xue <qiang.xue@gmail.com>
  * @since 2.0
  */
-class InvalidRequestException extends \Exception
+class InvalidRequestException extends UserException
 {
 	/**
-	 * @var boolean whether this exception is caused by end user's mistake (e.g. wrong URL)
-	 */
-	public $causedByUser = true;
-
-	/**
 	 * @return string the user-friendly name of this exception
 	 */
 	public function getName()
 	{
-		return \Yii::t('yii', 'Invalid Request');
+		return \Yii::t('yii|Invalid Request');
 	}
 }
 
diff --git a/framework/base/InvalidRouteException.php b/framework/base/InvalidRouteException.php
index e20b2b7..6d2256e 100644
--- a/framework/base/InvalidRouteException.php
+++ b/framework/base/InvalidRouteException.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * InvalidRouteException class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
@@ -15,19 +13,14 @@ namespace yii\base;
  * @author Qiang Xue <qiang.xue@gmail.com>
  * @since 2.0
  */
-class InvalidRouteException extends \Exception
+class InvalidRouteException extends UserException
 {
 	/**
-	 * @var boolean whether this exception is caused by end user's mistake (e.g. wrong URL)
-	 */
-	public $causedByUser = true;
-
-	/**
 	 * @return string the user-friendly name of this exception
 	 */
 	public function getName()
 	{
-		return \Yii::t('yii', 'Invalid Route');
+		return \Yii::t('yii|Invalid Route');
 	}
 }
 
diff --git a/framework/base/Model.php b/framework/base/Model.php
index 30cbcfa..13e567d 100644
--- a/framework/base/Model.php
+++ b/framework/base/Model.php
@@ -1,15 +1,13 @@
 <?php
 /**
- * Model class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
 namespace yii\base;
 
-use yii\util\StringHelper;
+use yii\helpers\StringHelper;
 use yii\validators\Validator;
 use yii\validators\RequiredValidator;
 
@@ -260,7 +258,7 @@ class Model extends Component implements \IteratorAggregate, \ArrayAccess
 	 */
 	public function beforeValidate()
 	{
-		$event = new ModelEvent($this);
+		$event = new ModelEvent;
 		$this->trigger(self::EVENT_BEFORE_VALIDATE, $event);
 		return $event->isValid;
 	}
@@ -331,7 +329,7 @@ class Model extends Component implements \IteratorAggregate, \ArrayAccess
 		foreach ($this->rules() as $rule) {
 			if ($rule instanceof Validator) {
 				$validators->add($rule);
-			} elseif (isset($rule[0], $rule[1])) { // attributes, validator type
+			} elseif (is_array($rule) && isset($rule[0], $rule[1])) { // attributes, validator type
 				$validator = Validator::createValidator($rule[1], $this, $rule[0], array_slice($rule, 2));
 				$validators->add($validator);
 			} else {
@@ -422,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 $attributeErrors) {
+				if (isset($attributeErrors[0])) {
+					$errors[] = $attributeErrors[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;
 	}
@@ -443,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.
 	 */
@@ -543,7 +541,7 @@ class Model extends Component implements \IteratorAggregate, \ArrayAccess
 	public function onUnsafeAttribute($name, $value)
 	{
 		if (YII_DEBUG) {
-			\Yii::warning("Failed to set unsafe attribute '$name' in '" . get_class($this) . "'.");
+			\Yii::info("Failed to set unsafe attribute '$name' in '" . get_class($this) . "'.", __CLASS__);
 		}
 	}
 
@@ -658,13 +656,13 @@ class Model extends Component implements \IteratorAggregate, \ArrayAccess
 	}
 
 	/**
-	 * Unsets the element at the specified offset.
+	 * Sets the element value at the specified offset to null.
 	 * This method is required by the SPL interface `ArrayAccess`.
 	 * It is implicitly called when you use something like `unset($model[$offset])`.
 	 * @param mixed $offset the offset to unset element
 	 */
 	public function offsetUnset($offset)
 	{
-		unset($this->$offset);
+		$this->$offset = null;
 	}
 }
diff --git a/framework/base/ModelEvent.php b/framework/base/ModelEvent.php
index e7b6a2e..57e41f9 100644
--- a/framework/base/ModelEvent.php
+++ b/framework/base/ModelEvent.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * ModelEvent class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
diff --git a/framework/base/Module.php b/framework/base/Module.php
index dcb468c..6b82157 100644
--- a/framework/base/Module.php
+++ b/framework/base/Module.php
@@ -1,624 +1,623 @@
-<?php
-/**
- * Module class file.
- *
- * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
- * @license http://www.yiiframework.com/license/
- */
-
-namespace yii\base;
-
-use Yii;
-use yii\util\StringHelper;
-use yii\util\FileHelper;
-
-/**
- * Module is the base class for module and application classes.
- *
- * A module represents a sub-application which contains MVC elements by itself, such as
- * models, views, controllers, etc.
- *
- * A module may consist of [[modules|sub-modules]].
- *
- * [[components|Components]] may be registered with the module so that they are globally
- * accessible within the module.
- *
- * @property string $uniqueId An ID that uniquely identifies this module among all modules within
- * the current application.
- * @property string $basePath The root directory of the module. Defaults to the directory containing the module class.
- * @property string $controllerPath The directory containing the controller classes. Defaults to "[[basePath]]/controllers".
- * @property string $viewPath The directory containing the view files within this module. Defaults to "[[basePath]]/views".
- * @property string $layoutPath The directory containing the layout view files within this module. Defaults to "[[viewPath]]/layouts".
- * @property array $modules The configuration of the currently installed modules (module ID => configuration).
- * @property array $components The components (indexed by their IDs) registered within this module.
- * @property array $import List of aliases to be imported. This property is write-only.
- * @property array $aliases List of aliases to be defined. This property is write-only.
- *
- * @author Qiang Xue <qiang.xue@gmail.com>
- * @since 2.0
- */
-abstract class Module extends Component
-{
-	/**
-	 * @var array custom module parameters (name => value).
-	 */
-	public $params = array();
-	/**
-	 * @var array the IDs of the components that should be preloaded when this module is created.
-	 */
-	public $preload = array();
-	/**
-	 * @var string an ID that uniquely identifies this module among other modules which have the same [[module|parent]].
-	 */
-	public $id;
-	/**
-	 * @var Module the parent module of this module. Null if this module does not have a parent.
-	 */
-	public $module;
-	/**
-	 * @var string|boolean the layout that should be applied for views within this module. This refers to a view name
-	 * relative to [[layoutPath]]. If this is not set, it means the layout value of the [[module|parent module]]
-	 * will be taken. If this is false, layout will be disabled within this module.
-	 */
-	public $layout;
-	/**
-	 * @var array mapping from controller ID to controller configurations.
-	 * Each name-value pair specifies the configuration of a single controller.
-	 * A controller configuration can be either a string or an array.
-	 * If the former, the string should be the class name or path alias of the controller.
-	 * If the latter, the array must contain a 'class' element which specifies
-	 * the controller's class name or path alias, and the rest of the name-value pairs
-	 * in the array are used to initialize the corresponding controller properties. For example,
-	 *
-	 * ~~~
-	 * array(
-	 *   'account' => '@application/controllers/UserController',
-	 *   'article' => array(
-	 *      'class' => '@application/controllers/PostController',
-	 *      'pageTitle' => 'something new',
-	 *   ),
-	 * )
-	 * ~~~
-	 */
-	public $controllerMap = array();
-	/**
-	 * @var string the namespace that controller classes are in. Default is to use global namespace.
-	 */
-	public $controllerNamespace;
-	/**
-	 * @return string the default route of this module. Defaults to 'default'.
-	 * The route may consist of child module ID, controller ID, and/or action ID.
-	 * For example, `help`, `post/create`, `admin/post/create`.
-	 * If action ID is not given, it will take the default value as specified in
-	 * [[Controller::defaultAction]].
-	 */
-	public $defaultRoute = 'default';
-	/**
-	 * @var string the root directory of the module.
-	 */
-	private $_basePath;
-	/**
-	 * @var string the root directory that contains view files for this module
-	 */
-	private $_viewPath;
-	/**
-	 * @var string the root directory that contains layout view files for this module.
-	 */
-	private $_layoutPath;
-	/**
-	 * @var string the directory containing controller classes in the module.
-	 */
-	private $_controllerPath;
-	/**
-	 * @var array child modules of this module
-	 */
-	private $_modules = array();
-	/**
-	 * @var array components registered under this module
-	 */
-	private $_components = array();
-
-	/**
-	 * Constructor.
-	 * @param string $id the ID of this module
-	 * @param Module $parent the parent module (if any)
-	 * @param array $config name-value pairs that will be used to initialize the object properties
-	 */
-	public function __construct($id, $parent = null, $config = array())
-	{
-		$this->id = $id;
-		$this->module = $parent;
-		parent::__construct($config);
-	}
-
-	/**
-	 * Getter magic method.
-	 * This method is overridden to support accessing components
-	 * like reading module properties.
-	 * @param string $name component or property name
-	 * @return mixed the named property value
-	 */
-	public function __get($name)
-	{
-		if ($this->hasComponent($name)) {
-			return $this->getComponent($name);
-		} else {
-			return parent::__get($name);
-		}
-	}
-
-	/**
-	 * Checks if a property value is null.
-	 * This method overrides the parent implementation by checking
-	 * if the named component is loaded.
-	 * @param string $name the property name or the event name
-	 * @return boolean whether the property value is null
-	 */
-	public function __isset($name)
-	{
-		if ($this->hasComponent($name)) {
-			return $this->getComponent($name) !== null;
-		} else {
-			return parent::__isset($name);
-		}
-	}
-
-	/**
-	 * Initializes the module.
-	 * This method is called after the module is created and initialized with property values
-	 * given in configuration. The default implement will create a path alias using the module [[id]]
-	 * and then call [[preloadComponents()]] to load components that are declared in [[preload]].
-	 */
-	public function init()
-	{
-		Yii::setAlias('@' . $this->id, $this->getBasePath());
-		$this->preloadComponents();
-	}
-
-	/**
-	 * Returns an ID that uniquely identifies this module among all modules within the current application.
-	 * Note that if the module is an application, an empty string will be returned.
-	 * @return string the unique ID of the module.
-	 */
-	public function getUniqueId()
-	{
-		if ($this instanceof Application) {
-			return '';
-		} elseif ($this->module) {
-			return $this->module->getUniqueId() . '/' . $this->id;
-		} else {
-			return $this->id;
-		}
-	}
-
-	/**
-	 * Returns the root directory of the module.
-	 * It defaults to the directory containing the module class file.
-	 * @return string the root directory of the module.
-	 */
-	public function getBasePath()
-	{
-		if ($this->_basePath === null) {
-			$class = new \ReflectionClass($this);
-			$this->_basePath = dirname($class->getFileName());
-		}
-		return $this->_basePath;
-	}
-
-	/**
-	 * Sets the root directory of the module.
-	 * This method can only be invoked at the beginning of the constructor.
-	 * @param string $path the root directory of the module. This can be either a directory name or a path alias.
-	 * @throws Exception if the directory does not exist.
-	 */
-	public function setBasePath($path)
-	{
-		$this->_basePath = FileHelper::ensureDirectory($path);
-	}
-
-	/**
-	 * Returns the directory that contains the controller classes.
-	 * Defaults to "[[basePath]]/controllers".
-	 * @return string the directory that contains the controller classes.
-	 */
-	public function getControllerPath()
-	{
-		if ($this->_controllerPath !== null) {
-			return $this->_controllerPath;
-		} else {
-			return $this->_controllerPath = $this->getBasePath() . DIRECTORY_SEPARATOR . 'controllers';
-		}
-	}
-
-	/**
-	 * Sets the directory that contains the controller classes.
-	 * @param string $path the directory that contains the controller classes.
-	 * This can be either a directory name or a path alias.
-	 * @throws Exception if the directory is invalid
-	 */
-	public function setControllerPath($path)
-	{
-		$this->_controllerPath = FileHelper::ensureDirectory($path);
-	}
-
-	/**
-	 * Returns the directory that contains the view files for this module.
-	 * @return string the root directory of view files. Defaults to "[[basePath]]/view".
-	 */
-	public function getViewPath()
-	{
-		if ($this->_viewPath !== null) {
-			return $this->_viewPath;
-		} else {
-			return $this->_viewPath = $this->getBasePath() . DIRECTORY_SEPARATOR . 'views';
-		}
-	}
-
-	/**
-	 * Sets the directory that contains the view files.
-	 * @param string $path the root directory of view files.
-	 * @throws Exception if the directory is invalid
-	 */
-	public function setViewPath($path)
-	{
-		$this->_viewPath = FileHelper::ensureDirectory($path);
-	}
-
-	/**
-	 * Returns the directory that contains layout view files for this module.
-	 * @return string the root directory of layout files. Defaults to "[[viewPath]]/layouts".
-	 */
-	public function getLayoutPath()
-	{
-		if ($this->_layoutPath !== null) {
-			return $this->_layoutPath;
-		} else {
-			return $this->_layoutPath = $this->getViewPath() . DIRECTORY_SEPARATOR . 'layouts';
-		}
-	}
-
-	/**
-	 * Sets the directory that contains the layout files.
-	 * @param string $path the root directory of layout files.
-	 * @throws Exception if the directory is invalid
-	 */
-	public function setLayoutPath($path)
-	{
-		$this->_layoutPath = FileHelper::ensureDirectory($path);
-	}
-
-	/**
-	 * Imports the specified path aliases.
-	 * This method is provided so that you can import a set of path aliases when configuring a module.
-	 * The path aliases will be imported by calling [[Yii::import()]].
-	 * @param array $aliases list of path aliases to be imported
-	 */
-	public function setImport($aliases)
-	{
-		foreach ($aliases as $alias) {
-			Yii::import($alias);
-		}
-	}
-
-	/**
-	 * Defines path aliases.
-	 * This method calls [[Yii::setAlias()]] to register the path aliases.
-	 * This method is provided so that you can define path aliases when configuring a module.
-	 * @param array $aliases list of path aliases to be defined. The array keys are alias names
-	 * (must start with '@') and the array values are the corresponding paths or aliases.
-	 * For example,
-	 *
-	 * ~~~
-	 * array(
-	 *	'@models' => '@application/models', // an existing alias
-	 *	'@backend' => __DIR__ . '/../backend',  // a directory
-	 * )
-	 * ~~~
-	 */
-	public function setAliases($aliases)
-	{
-		foreach ($aliases as $name => $alias) {
-			Yii::setAlias($name, $alias);
-		}
-	}
-
-	/**
-	 * Checks whether the named module exists.
-	 * @param string $id module ID
-	 * @return boolean whether the named module exists. Both loaded and unloaded modules
-	 * are considered.
-	 */
-	public function hasModule($id)
-	{
-		return isset($this->_modules[$id]);
-	}
-
-	/**
-	 * Retrieves the named module.
-	 * @param string $id module ID (case-sensitive)
-	 * @param boolean $load whether to load the module if it is not yet loaded.
-	 * @return Module|null the module instance, null if the module
-	 * does not exist.
-	 * @see hasModule()
-	 */
-	public function getModule($id, $load = true)
-	{
-		if (isset($this->_modules[$id])) {
-			if ($this->_modules[$id] instanceof Module) {
-				return $this->_modules[$id];
-			} elseif ($load) {
-				Yii::trace("Loading module: $id", __CLASS__);
-				return $this->_modules[$id] = Yii::createObject($this->_modules[$id], $id, $this);
-			}
-		}
-		return null;
-	}
-
-	/**
-	 * Adds a sub-module to this module.
-	 * @param string $id module ID
-	 * @param Module|array|null $module the sub-module to be added to this module. This can
-	 * be one of the followings:
-	 *
-	 * - a [[Module]] object
-	 * - a configuration array: when [[getModule()]] is called initially, the array
-	 *   will be used to instantiate the sub-module
-	 * - null: the named sub-module will be removed from this module
-	 */
-	public function setModule($id, $module)
-	{
-		if ($module === null) {
-			unset($this->_modules[$id]);
-		} else {
-			$this->_modules[$id] = $module;
-		}
-	}
-
-	/**
-	 * Returns the sub-modules in this module.
-	 * @param boolean $loadedOnly whether to return the loaded sub-modules only. If this is set false,
-	 * then all sub-modules registered in this module will be returned, whether they are loaded or not.
-	 * Loaded modules will be returned as objects, while unloaded modules as configuration arrays.
-	 * @return array the modules (indexed by their IDs)
-	 */
-	public function getModules($loadedOnly = false)
-	{
-		if ($loadedOnly) {
-			$modules = array();
-			foreach ($this->_modules as $module) {
-				if ($module instanceof Module) {
-					$modules[] = $module;
-				}
-			}
-			return $modules;
-		} else {
-			return $this->_modules;
-		}
-	}
-
-	/**
-	 * Registers sub-modules in the current module.
-	 *
-	 * Each sub-module should be specified as a name-value pair, where
-	 * name refers to the ID of the module and value the module or a configuration
-	 * array that can be used to create the module. In the latter case, [[Yii::createObject()]]
-	 * will be used to create the module.
-	 *
-	 * If a new sub-module has the same ID as an existing one, the existing one will be overwritten silently.
-	 *
-	 * The following is an example for registering two sub-modules:
-	 *
-	 * ~~~
-	 * array(
-	 *     'comment' => array(
-	 *         'class' => 'app\modules\CommentModule',
-	 *         'connectionID' => 'db',
-	 *     ),
-	 *     'booking' => array(
-	 *         'class' => 'app\modules\BookingModule',
-	 *     ),
-	 * )
-	 * ~~~
-	 *
-	 * @param array $modules modules (id => module configuration or instances)
-	 */
-	public function setModules($modules)
-	{
-		foreach ($modules as $id => $module) {
-			$this->_modules[$id] = $module;
-		}
-	}
-
-	/**
-	 * Checks whether the named component exists.
-	 * @param string $id component ID
-	 * @return boolean whether the named component exists. Both loaded and unloaded components
-	 * are considered.
-	 */
-	public function hasComponent($id)
-	{
-		return isset($this->_components[$id]);
-	}
-
-	/**
-	 * Retrieves the named component.
-	 * @param string $id component ID (case-sensitive)
-	 * @param boolean $load whether to load the component if it is not yet loaded.
-	 * @return Component|null the component instance, null if the component does not exist.
-	 * @see hasComponent()
-	 */
-	public function getComponent($id, $load = true)
-	{
-		if (isset($this->_components[$id])) {
-			if ($this->_components[$id] instanceof Component) {
-				return $this->_components[$id];
-			} elseif ($load) {
-				Yii::trace("Loading component: $id", __CLASS__);
-				return $this->_components[$id] = Yii::createObject($this->_components[$id]);
-			}
-		}
-		return null;
-	}
-
-	/**
-	 * Registers a component with this module.
-	 * @param string $id component ID
-	 * @param Component|array|null $component the component to be registered with the module. This can
-	 * be one of the followings:
-	 *
-	 * - a [[Component]] object
-	 * - a configuration array: when [[getComponent()]] is called initially for this component, the array
-	 *   will be used to instantiate the component via [[Yii::createObject()]].
-	 * - null: the named component will be removed from the module
-	 */
-	public function setComponent($id, $component)
-	{
-		if ($component === null) {
-			unset($this->_components[$id]);
-		} else {
-			$this->_components[$id] = $component;
-		}
-	}
-
-	/**
-	 * Returns the registered components.
-	 * @param boolean $loadedOnly whether to return the loaded components only. If this is set false,
-	 * then all components specified in the configuration will be returned, whether they are loaded or not.
-	 * Loaded components will be returned as objects, while unloaded components as configuration arrays.
-	 * @return array the components (indexed by their IDs)
-	 */
-	public function getComponents($loadedOnly = false)
-	{
-		if ($loadedOnly) {
-			$components = array();
-			foreach ($this->_components as $component) {
-				if ($component instanceof Component) {
-					$components[] = $component;
-				}
-			}
-			return $components;
-		} else {
-			return $this->_components;
-		}
-	}
-
-	/**
-	 * Registers a set of components in this module.
-	 *
-	 * Each component should be specified as a name-value pair, where
-	 * name refers to the ID of the component and value the component or a configuration
-	 * array that can be used to create the component. In the latter case, [[Yii::createObject()]]
-	 * will be used to create the component.
-	 *
-	 * If a new component has the same ID as an existing one, the existing one will be overwritten silently.
-	 *
-	 * The following is an example for setting two components:
-	 *
-	 * ~~~
-	 * array(
-	 *     'db' => array(
-	 *         'class' => 'yii\db\Connection',
-	 *         'dsn' => 'sqlite:path/to/file.db',
-	 *     ),
-	 *     'cache' => array(
-	 *         'class' => 'yii\caching\DbCache',
-	 *         'connectionID' => 'db',
-	 *     ),
-	 * )
-	 * ~~~
-	 *
-	 * @param array $components components (id => component configuration or instance)
-	 */
-	public function setComponents($components)
-	{
-		foreach ($components as $id => $component) {
-			$this->_components[$id] = $component;
-		}
-	}
-
-	/**
-	 * Loads components that are declared in [[preload]].
-	 */
-	public function preloadComponents()
-	{
-		foreach ($this->preload as $id) {
-			$this->getComponent($id);
-		}
-	}
-
-	/**
-	 * Runs a controller action specified by a route.
-	 * This method parses the specified route and creates the corresponding child module(s), controller and action
-	 * instances. It then calls [[Controller::runAction()]] to run the action with the given parameters.
-	 * If the route is empty, the method will use [[defaultRoute]].
-	 * @param string $route the route that specifies the action.
-	 * @param array $params the parameters to be passed to the action
-	 * @return integer the status code returned by the action execution. 0 means normal, and other values mean abnormal.
-	 * @throws InvalidRouteException if the requested route cannot be resolved into an action successfully
-	 */
-	public function runAction($route, $params = array())
-	{
-		$result = $this->createController($route);
-		if (is_array($result)) {
-			/** @var $controller Controller */
-			list($controller, $actionID) = $result;
-			$oldController = Yii::$application->controller;
-			Yii::$application->controller = $controller;
-			$status = $controller->runAction($actionID, $params);
-			Yii::$application->controller = $oldController;
-			return $status;
-		} else {
-			throw new InvalidRouteException('Unable to resolve the request: ' . trim($this->getUniqueId() . '/' . $route, '/'));
-		}
-	}
-
-	/**
-	 * Creates a controller instance based on the controller ID.
-	 *
-	 * The controller is created within this module. The method first attempts to
-	 * create the controller based on the [[controllerMap]] of the module. If not available,
-	 * it will look for the controller class under the [[controllerPath]] and create an
-	 * instance of it.
-	 *
-	 * @param string $route the route consisting of module, controller and action IDs.
-	 * @return array|boolean if the controller is created successfully, it will be returned together
-	 * with the remainder of the route which represents the action ID. Otherwise false will be returned.
-	 */
-	public function createController($route)
-	{
-		if ($route === '') {
-			$route = $this->defaultRoute;
-		}
-		if (($pos = strpos($route, '/')) !== false) {
-			$id = substr($route, 0, $pos);
-			$route = substr($route, $pos + 1);
-		} else {
-			$id = $route;
-			$route = '';
-		}
-
-		$module = $this->getModule($id);
-		if ($module !== null) {
-			return $module->createController($route);
-		}
-
-		if (isset($this->controllerMap[$id])) {
-			$controller = Yii::createObject($this->controllerMap[$id], $id, $this);
-		} elseif (preg_match('/^[a-z0-9\\-_]+$/', $id)) {
-			$className = StringHelper::id2camel($id) . 'Controller';
-			$classFile = $this->controllerPath . DIRECTORY_SEPARATOR . $className . '.php';
-			if (is_file($classFile)) {
-				$className = $this->controllerNamespace . '\\' . $className;
-				if (!class_exists($className, false)) {
-					require($classFile);
-				}
-				if (class_exists($className, false) && is_subclass_of($className, '\yii\base\Controller')) {
-					$controller = new $className($id, $this);
-				}
-			}
-		}
-
-		return isset($controller) ? array($controller, $route) : false;
-	}
-}
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\base;
+
+use Yii;
+use yii\helpers\StringHelper;
+use yii\helpers\FileHelper;
+
+/**
+ * Module is the base class for module and application classes.
+ *
+ * A module represents a sub-application which contains MVC elements by itself, such as
+ * models, views, controllers, etc.
+ *
+ * A module may consist of [[modules|sub-modules]].
+ *
+ * [[components|Components]] may be registered with the module so that they are globally
+ * accessible within the module.
+ *
+ * @property string $uniqueId An ID that uniquely identifies this module among all modules within
+ * the current application.
+ * @property string $basePath The root directory of the module. Defaults to the directory containing the module class.
+ * @property string $controllerPath The directory containing the controller classes. Defaults to "[[basePath]]/controllers".
+ * @property string $viewPath The directory containing the view files within this module. Defaults to "[[basePath]]/views".
+ * @property string $layoutPath The directory containing the layout view files within this module. Defaults to "[[viewPath]]/layouts".
+ * @property array $modules The configuration of the currently installed modules (module ID => configuration).
+ * @property array $components The components (indexed by their IDs) registered within this module.
+ * @property array $import List of aliases to be imported. This property is write-only.
+ * @property array $aliases List of aliases to be defined. This property is write-only.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+abstract class Module extends Component
+{
+	/**
+	 * @var array custom module parameters (name => value).
+	 */
+	public $params = array();
+	/**
+	 * @var array the IDs of the components that should be preloaded when this module is created.
+	 */
+	public $preload = array();
+	/**
+	 * @var string an ID that uniquely identifies this module among other modules which have the same [[module|parent]].
+	 */
+	public $id;
+	/**
+	 * @var Module the parent module of this module. Null if this module does not have a parent.
+	 */
+	public $module;
+	/**
+	 * @var string|boolean the layout that should be applied for views within this module. This refers to a view name
+	 * relative to [[layoutPath]]. If this is not set, it means the layout value of the [[module|parent module]]
+	 * will be taken. If this is false, layout will be disabled within this module.
+	 */
+	public $layout;
+	/**
+	 * @var array mapping from controller ID to controller configurations.
+	 * Each name-value pair specifies the configuration of a single controller.
+	 * A controller configuration can be either a string or an array.
+	 * If the former, the string should be the class name or path alias of the controller.
+	 * If the latter, the array must contain a 'class' element which specifies
+	 * the controller's class name or path alias, and the rest of the name-value pairs
+	 * in the array are used to initialize the corresponding controller properties. For example,
+	 *
+	 * ~~~
+	 * array(
+	 *   'account' => '@app/controllers/UserController',
+	 *   'article' => array(
+	 *      'class' => '@app/controllers/PostController',
+	 *      'pageTitle' => 'something new',
+	 *   ),
+	 * )
+	 * ~~~
+	 */
+	public $controllerMap = array();
+	/**
+	 * @var string the namespace that controller classes are in. Default is to use global namespace.
+	 */
+	public $controllerNamespace;
+	/**
+	 * @return string the default route of this module. Defaults to 'default'.
+	 * The route may consist of child module ID, controller ID, and/or action ID.
+	 * For example, `help`, `post/create`, `admin/post/create`.
+	 * If action ID is not given, it will take the default value as specified in
+	 * [[Controller::defaultAction]].
+	 */
+	public $defaultRoute = 'default';
+	/**
+	 * @var string the root directory of the module.
+	 */
+	private $_basePath;
+	/**
+	 * @var string the root directory that contains view files for this module
+	 */
+	private $_viewPath;
+	/**
+	 * @var string the root directory that contains layout view files for this module.
+	 */
+	private $_layoutPath;
+	/**
+	 * @var string the directory containing controller classes in the module.
+	 */
+	private $_controllerPath;
+	/**
+	 * @var array child modules of this module
+	 */
+	private $_modules = array();
+	/**
+	 * @var array components registered under this module
+	 */
+	private $_components = array();
+
+	/**
+	 * Constructor.
+	 * @param string $id the ID of this module
+	 * @param Module $parent the parent module (if any)
+	 * @param array $config name-value pairs that will be used to initialize the object properties
+	 */
+	public function __construct($id, $parent = null, $config = array())
+	{
+		$this->id = $id;
+		$this->module = $parent;
+		parent::__construct($config);
+	}
+
+	/**
+	 * Getter magic method.
+	 * This method is overridden to support accessing components
+	 * like reading module properties.
+	 * @param string $name component or property name
+	 * @return mixed the named property value
+	 */
+	public function __get($name)
+	{
+		if ($this->hasComponent($name)) {
+			return $this->getComponent($name);
+		} else {
+			return parent::__get($name);
+		}
+	}
+
+	/**
+	 * Checks if a property value is null.
+	 * This method overrides the parent implementation by checking
+	 * if the named component is loaded.
+	 * @param string $name the property name or the event name
+	 * @return boolean whether the property value is null
+	 */
+	public function __isset($name)
+	{
+		if ($this->hasComponent($name)) {
+			return $this->getComponent($name) !== null;
+		} else {
+			return parent::__isset($name);
+		}
+	}
+
+	/**
+	 * Initializes the module.
+	 * This method is called after the module is created and initialized with property values
+	 * given in configuration. The default implement will create a path alias using the module [[id]]
+	 * and then call [[preloadComponents()]] to load components that are declared in [[preload]].
+	 */
+	public function init()
+	{
+		Yii::setAlias('@' . $this->id, $this->getBasePath());
+		$this->preloadComponents();
+	}
+
+	/**
+	 * Returns an ID that uniquely identifies this module among all modules within the current application.
+	 * Note that if the module is an application, an empty string will be returned.
+	 * @return string the unique ID of the module.
+	 */
+	public function getUniqueId()
+	{
+		if ($this instanceof Application) {
+			return '';
+		} elseif ($this->module) {
+			return $this->module->getUniqueId() . '/' . $this->id;
+		} else {
+			return $this->id;
+		}
+	}
+
+	/**
+	 * Returns the root directory of the module.
+	 * It defaults to the directory containing the module class file.
+	 * @return string the root directory of the module.
+	 */
+	public function getBasePath()
+	{
+		if ($this->_basePath === null) {
+			$class = new \ReflectionClass($this);
+			$this->_basePath = dirname($class->getFileName());
+		}
+		return $this->_basePath;
+	}
+
+	/**
+	 * Sets the root directory of the module.
+	 * This method can only be invoked at the beginning of the constructor.
+	 * @param string $path the root directory of the module. This can be either a directory name or a path alias.
+	 * @throws Exception if the directory does not exist.
+	 */
+	public function setBasePath($path)
+	{
+		$this->_basePath = FileHelper::ensureDirectory($path);
+	}
+
+	/**
+	 * Returns the directory that contains the controller classes.
+	 * Defaults to "[[basePath]]/controllers".
+	 * @return string the directory that contains the controller classes.
+	 */
+	public function getControllerPath()
+	{
+		if ($this->_controllerPath !== null) {
+			return $this->_controllerPath;
+		} else {
+			return $this->_controllerPath = $this->getBasePath() . DIRECTORY_SEPARATOR . 'controllers';
+		}
+	}
+
+	/**
+	 * Sets the directory that contains the controller classes.
+	 * @param string $path the directory that contains the controller classes.
+	 * This can be either a directory name or a path alias.
+	 * @throws Exception if the directory is invalid
+	 */
+	public function setControllerPath($path)
+	{
+		$this->_controllerPath = FileHelper::ensureDirectory($path);
+	}
+
+	/**
+	 * Returns the directory that contains the view files for this module.
+	 * @return string the root directory of view files. Defaults to "[[basePath]]/view".
+	 */
+	public function getViewPath()
+	{
+		if ($this->_viewPath !== null) {
+			return $this->_viewPath;
+		} else {
+			return $this->_viewPath = $this->getBasePath() . DIRECTORY_SEPARATOR . 'views';
+		}
+	}
+
+	/**
+	 * Sets the directory that contains the view files.
+	 * @param string $path the root directory of view files.
+	 * @throws Exception if the directory is invalid
+	 */
+	public function setViewPath($path)
+	{
+		$this->_viewPath = FileHelper::ensureDirectory($path);
+	}
+
+	/**
+	 * Returns the directory that contains layout view files for this module.
+	 * @return string the root directory of layout files. Defaults to "[[viewPath]]/layouts".
+	 */
+	public function getLayoutPath()
+	{
+		if ($this->_layoutPath !== null) {
+			return $this->_layoutPath;
+		} else {
+			return $this->_layoutPath = $this->getViewPath() . DIRECTORY_SEPARATOR . 'layouts';
+		}
+	}
+
+	/**
+	 * Sets the directory that contains the layout files.
+	 * @param string $path the root directory of layout files.
+	 * @throws Exception if the directory is invalid
+	 */
+	public function setLayoutPath($path)
+	{
+		$this->_layoutPath = FileHelper::ensureDirectory($path);
+	}
+
+	/**
+	 * Imports the specified path aliases.
+	 * This method is provided so that you can import a set of path aliases when configuring a module.
+	 * The path aliases will be imported by calling [[Yii::import()]].
+	 * @param array $aliases list of path aliases to be imported
+	 */
+	public function setImport($aliases)
+	{
+		foreach ($aliases as $alias) {
+			Yii::import($alias);
+		}
+	}
+
+	/**
+	 * Defines path aliases.
+	 * This method calls [[Yii::setAlias()]] to register the path aliases.
+	 * This method is provided so that you can define path aliases when configuring a module.
+	 * @param array $aliases list of path aliases to be defined. The array keys are alias names
+	 * (must start with '@') and the array values are the corresponding paths or aliases.
+	 * For example,
+	 *
+	 * ~~~
+	 * array(
+	 *	'@models' => '@app/models', // an existing alias
+	 *	'@backend' => __DIR__ . '/../backend',  // a directory
+	 * )
+	 * ~~~
+	 */
+	public function setAliases($aliases)
+	{
+		foreach ($aliases as $name => $alias) {
+			Yii::setAlias($name, $alias);
+		}
+	}
+
+	/**
+	 * Checks whether the named module exists.
+	 * @param string $id module ID
+	 * @return boolean whether the named module exists. Both loaded and unloaded modules
+	 * are considered.
+	 */
+	public function hasModule($id)
+	{
+		return isset($this->_modules[$id]);
+	}
+
+	/**
+	 * Retrieves the named module.
+	 * @param string $id module ID (case-sensitive)
+	 * @param boolean $load whether to load the module if it is not yet loaded.
+	 * @return Module|null the module instance, null if the module
+	 * does not exist.
+	 * @see hasModule()
+	 */
+	public function getModule($id, $load = true)
+	{
+		if (isset($this->_modules[$id])) {
+			if ($this->_modules[$id] instanceof Module) {
+				return $this->_modules[$id];
+			} elseif ($load) {
+				Yii::trace("Loading module: $id", __CLASS__);
+				return $this->_modules[$id] = Yii::createObject($this->_modules[$id], $id, $this);
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * Adds a sub-module to this module.
+	 * @param string $id module ID
+	 * @param Module|array|null $module the sub-module to be added to this module. This can
+	 * be one of the followings:
+	 *
+	 * - a [[Module]] object
+	 * - a configuration array: when [[getModule()]] is called initially, the array
+	 *   will be used to instantiate the sub-module
+	 * - null: the named sub-module will be removed from this module
+	 */
+	public function setModule($id, $module)
+	{
+		if ($module === null) {
+			unset($this->_modules[$id]);
+		} else {
+			$this->_modules[$id] = $module;
+		}
+	}
+
+	/**
+	 * Returns the sub-modules in this module.
+	 * @param boolean $loadedOnly whether to return the loaded sub-modules only. If this is set false,
+	 * then all sub-modules registered in this module will be returned, whether they are loaded or not.
+	 * Loaded modules will be returned as objects, while unloaded modules as configuration arrays.
+	 * @return array the modules (indexed by their IDs)
+	 */
+	public function getModules($loadedOnly = false)
+	{
+		if ($loadedOnly) {
+			$modules = array();
+			foreach ($this->_modules as $module) {
+				if ($module instanceof Module) {
+					$modules[] = $module;
+				}
+			}
+			return $modules;
+		} else {
+			return $this->_modules;
+		}
+	}
+
+	/**
+	 * Registers sub-modules in the current module.
+	 *
+	 * Each sub-module should be specified as a name-value pair, where
+	 * name refers to the ID of the module and value the module or a configuration
+	 * array that can be used to create the module. In the latter case, [[Yii::createObject()]]
+	 * will be used to create the module.
+	 *
+	 * If a new sub-module has the same ID as an existing one, the existing one will be overwritten silently.
+	 *
+	 * The following is an example for registering two sub-modules:
+	 *
+	 * ~~~
+	 * array(
+	 *     'comment' => array(
+	 *         'class' => 'app\modules\CommentModule',
+	 *         'db' => 'db',
+	 *     ),
+	 *     'booking' => array(
+	 *         'class' => 'app\modules\BookingModule',
+	 *     ),
+	 * )
+	 * ~~~
+	 *
+	 * @param array $modules modules (id => module configuration or instances)
+	 */
+	public function setModules($modules)
+	{
+		foreach ($modules as $id => $module) {
+			$this->_modules[$id] = $module;
+		}
+	}
+
+	/**
+	 * Checks whether the named component exists.
+	 * @param string $id component ID
+	 * @return boolean whether the named component exists. Both loaded and unloaded components
+	 * are considered.
+	 */
+	public function hasComponent($id)
+	{
+		return isset($this->_components[$id]);
+	}
+
+	/**
+	 * Retrieves the named component.
+	 * @param string $id component ID (case-sensitive)
+	 * @param boolean $load whether to load the component if it is not yet loaded.
+	 * @return Component|null the component instance, null if the component does not exist.
+	 * @see hasComponent()
+	 */
+	public function getComponent($id, $load = true)
+	{
+		if (isset($this->_components[$id])) {
+			if ($this->_components[$id] instanceof Component) {
+				return $this->_components[$id];
+			} elseif ($load) {
+				Yii::trace("Loading component: $id", __CLASS__);
+				return $this->_components[$id] = Yii::createObject($this->_components[$id]);
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * Registers a component with this module.
+	 * @param string $id component ID
+	 * @param Component|array|null $component the component to be registered with the module. This can
+	 * be one of the followings:
+	 *
+	 * - a [[Component]] object
+	 * - a configuration array: when [[getComponent()]] is called initially for this component, the array
+	 *   will be used to instantiate the component via [[Yii::createObject()]].
+	 * - null: the named component will be removed from the module
+	 */
+	public function setComponent($id, $component)
+	{
+		if ($component === null) {
+			unset($this->_components[$id]);
+		} else {
+			$this->_components[$id] = $component;
+		}
+	}
+
+	/**
+	 * Returns the registered components.
+	 * @param boolean $loadedOnly whether to return the loaded components only. If this is set false,
+	 * then all components specified in the configuration will be returned, whether they are loaded or not.
+	 * Loaded components will be returned as objects, while unloaded components as configuration arrays.
+	 * @return array the components (indexed by their IDs)
+	 */
+	public function getComponents($loadedOnly = false)
+	{
+		if ($loadedOnly) {
+			$components = array();
+			foreach ($this->_components as $component) {
+				if ($component instanceof Component) {
+					$components[] = $component;
+				}
+			}
+			return $components;
+		} else {
+			return $this->_components;
+		}
+	}
+
+	/**
+	 * Registers a set of components in this module.
+	 *
+	 * Each component should be specified as a name-value pair, where
+	 * name refers to the ID of the component and value the component or a configuration
+	 * array that can be used to create the component. In the latter case, [[Yii::createObject()]]
+	 * will be used to create the component.
+	 *
+	 * If a new component has the same ID as an existing one, the existing one will be overwritten silently.
+	 *
+	 * The following is an example for setting two components:
+	 *
+	 * ~~~
+	 * array(
+	 *     'db' => array(
+	 *         'class' => 'yii\db\Connection',
+	 *         'dsn' => 'sqlite:path/to/file.db',
+	 *     ),
+	 *     'cache' => array(
+	 *         'class' => 'yii\caching\DbCache',
+	 *         'db' => 'db',
+	 *     ),
+	 * )
+	 * ~~~
+	 *
+	 * @param array $components components (id => component configuration or instance)
+	 */
+	public function setComponents($components)
+	{
+		foreach ($components as $id => $component) {
+			$this->_components[$id] = $component;
+		}
+	}
+
+	/**
+	 * Loads components that are declared in [[preload]].
+	 */
+	public function preloadComponents()
+	{
+		foreach ($this->preload as $id) {
+			$this->getComponent($id);
+		}
+	}
+
+	/**
+	 * Runs a controller action specified by a route.
+	 * This method parses the specified route and creates the corresponding child module(s), controller and action
+	 * instances. It then calls [[Controller::runAction()]] to run the action with the given parameters.
+	 * If the route is empty, the method will use [[defaultRoute]].
+	 * @param string $route the route that specifies the action.
+	 * @param array $params the parameters to be passed to the action
+	 * @return integer the status code returned by the action execution. 0 means normal, and other values mean abnormal.
+	 * @throws InvalidRouteException if the requested route cannot be resolved into an action successfully
+	 */
+	public function runAction($route, $params = array())
+	{
+		$result = $this->createController($route);
+		if (is_array($result)) {
+			/** @var $controller Controller */
+			list($controller, $actionID) = $result;
+			$oldController = Yii::$app->controller;
+			Yii::$app->controller = $controller;
+			$status = $controller->runAction($actionID, $params);
+			Yii::$app->controller = $oldController;
+			return $status;
+		} else {
+			throw new InvalidRouteException('Unable to resolve the request "' . trim($this->getUniqueId() . '/' . $route, '/') . '".');
+		}
+	}
+
+	/**
+	 * Creates a controller instance based on the controller ID.
+	 *
+	 * The controller is created within this module. The method first attempts to
+	 * create the controller based on the [[controllerMap]] of the module. If not available,
+	 * it will look for the controller class under the [[controllerPath]] and create an
+	 * instance of it.
+	 *
+	 * @param string $route the route consisting of module, controller and action IDs.
+	 * @return array|boolean if the controller is created successfully, it will be returned together
+	 * with the remainder of the route which represents the action ID. Otherwise false will be returned.
+	 */
+	public function createController($route)
+	{
+		if ($route === '') {
+			$route = $this->defaultRoute;
+		}
+		if (($pos = strpos($route, '/')) !== false) {
+			$id = substr($route, 0, $pos);
+			$route = substr($route, $pos + 1);
+		} else {
+			$id = $route;
+			$route = '';
+		}
+
+		$module = $this->getModule($id);
+		if ($module !== null) {
+			return $module->createController($route);
+		}
+
+		if (isset($this->controllerMap[$id])) {
+			$controller = Yii::createObject($this->controllerMap[$id], $id, $this);
+		} elseif (preg_match('/^[a-z0-9\\-_]+$/', $id)) {
+			$className = StringHelper::id2camel($id) . 'Controller';
+
+			$classFile = $this->controllerPath . DIRECTORY_SEPARATOR . $className . '.php';
+			if (is_file($classFile)) {
+				$className = $this->controllerNamespace . '\\' . $className;
+				if (!class_exists($className, false)) {
+					require($classFile);
+				}
+				if (class_exists($className, false) && is_subclass_of($className, '\yii\base\Controller')) {
+					$controller = new $className($id, $this);
+				}
+			}
+		}
+
+		return isset($controller) ? array($controller, $route) : false;
+	}
+}
diff --git a/framework/base/NotSupportedException.php b/framework/base/NotSupportedException.php
index 56e7e36..2f08891 100644
--- a/framework/base/NotSupportedException.php
+++ b/framework/base/NotSupportedException.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * NotSupportedException class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
@@ -15,14 +13,14 @@ namespace yii\base;
  * @author Qiang Xue <qiang.xue@gmail.com>
  * @since 2.0
  */
-class NotSupportedException extends \Exception
+class NotSupportedException extends Exception
 {
 	/**
 	 * @return string the user-friendly name of this exception
 	 */
 	public function getName()
 	{
-		return \Yii::t('yii', 'Not Supported');
+		return \Yii::t('yii|Not Supported');
 	}
 }
 
diff --git a/framework/base/Object.php b/framework/base/Object.php
index a3425dc..3bd8378 100644
--- a/framework/base/Object.php
+++ b/framework/base/Object.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * Object class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
@@ -67,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);
 		}
 	}
 
@@ -88,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);
 		}
 	}
 
@@ -131,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/Request.php b/framework/base/Request.php
index 0dbc568..45556ab 100644
--- a/framework/base/Request.php
+++ b/framework/base/Request.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * Request class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
@@ -13,12 +11,18 @@ namespace yii\base;
  * @author Qiang Xue <qiang.xue@gmail.com>
  * @since 2.0
  */
-class Request extends Component
+abstract class Request extends Component
 {
 	private $_scriptFile;
 	private $_isConsoleRequest;
 
 	/**
+	 * Resolves the current request into a route and the associated parameters.
+	 * @return array the first element is the route, and the second is the associated parameters.
+	 */
+	abstract public function resolve();
+
+	/**
 	 * Returns a value indicating whether the current request is made via command line
 	 * @return boolean the value indicating whether the current request is made via console
 	 */
@@ -39,24 +43,35 @@ class Request extends Component
 	/**
 	 * Returns entry script file path.
 	 * @return string entry script file path (processed w/ realpath())
+	 * @throws InvalidConfigException if the entry script file path cannot be determined automatically.
 	 */
 	public function getScriptFile()
 	{
 		if ($this->_scriptFile === null) {
-			$this->_scriptFile = realpath($_SERVER['SCRIPT_FILENAME']);
+			if (isset($_SERVER['SCRIPT_FILENAME'])) {
+				$this->setScriptFile($_SERVER['SCRIPT_FILENAME']);
+			} else {
+				throw new InvalidConfigException('Unable to determine the entry script file path.');
+			}
 		}
 		return $this->_scriptFile;
 	}
 
 	/**
 	 * Sets the entry script file path.
-	 * This can be an absolute or relative file path, or a path alias.
-	 * Note that you normally do not have to set the script file path
-	 * as [[getScriptFile()]] can determine it based on `$_SERVER['SCRIPT_FILENAME']`.
-	 * @param string $value the entry script file
+	 * The entry script file path can normally be determined based on the `SCRIPT_FILENAME` SERVER variable.
+	 * However, for some server configurations, this may not be correct or feasible.
+	 * This setter is provided so that the entry script file path can be manually specified.
+	 * @param string $value the entry script file path. This can be either a file path or a path alias.
+	 * @throws InvalidConfigException if the provided entry script file path is invalid.
 	 */
 	public function setScriptFile($value)
 	{
-		$this->_scriptFile = realpath(\Yii::getAlias($value));
+		$scriptFile = realpath(\Yii::getAlias($value));
+		if ($scriptFile !== false && is_file($scriptFile)) {
+			$this->_scriptFile = $scriptFile;
+		} else {
+			throw new InvalidConfigException('Unable to determine the entry script file path.');
+		}
 	}
 }
diff --git a/framework/base/Response.php b/framework/base/Response.php
index 3ced584..a53fd61 100644
--- a/framework/base/Response.php
+++ b/framework/base/Response.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * Response class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
diff --git a/framework/base/SecurityManager.php b/framework/base/SecurityManager.php
deleted file mode 100644
index 441b908..0000000
--- a/framework/base/SecurityManager.php
+++ /dev/null
@@ -1,290 +0,0 @@
-<?php
-/**
- * SecurityManager class file.
- *
- * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
- * @license http://www.yiiframework.com/license/
- */
-
-namespace yii\base;
-
-/**
- * SecurityManager provides private keys, hashing and encryption functions.
- *
- * @author Qiang Xue <qiang.xue@gmail.com>
- * @since 2.0
- */
-class SecurityManager extends Component
-{
-	const STATE_VALIDATION_KEY = 'Yii.SecurityManager.validationkey';
-	const STATE_ENCRYPTION_KEY = 'Yii.SecurityManager.encryptionkey';
-
-	/**
-	 * @var string the name of the hashing algorithm to be used by {@link computeHMAC}.
-	 * See {@link http://php.net/manual/en/function.hash-algos.php hash-algos} for the list of possible
-	 * hash algorithms. Note that if you are using PHP 5.1.1 or below, you can only use 'sha1' or 'md5'.
-	 *
-	 * Defaults to 'sha1', meaning using SHA1 hash algorithm.
-	 */
-	public $hashAlgorithm = 'sha1';
-	/**
-	 * @var mixed the name of the crypt algorithm to be used by {@link encrypt} and {@link decrypt}.
-	 * This will be passed as the first parameter to {@link http://php.net/manual/en/function.mcrypt-module-open.php mcrypt_module_open}.
-	 *
-	 * This property can also be configured as an array. In this case, the array elements will be passed in order
-	 * as parameters to mcrypt_module_open. For example, <code>array('rijndael-256', '', 'ofb', '')</code>.
-	 *
-	 * Defaults to 'des', meaning using DES crypt algorithm.
-	 */
-	public $cryptAlgorithm = 'des';
-
-	private $_validationKey;
-	private $_encryptionKey;
-
-	/**
-	 * @return string a randomly generated private key
-	 */
-	protected function generateRandomKey()
-	{
-		return sprintf('%08x%08x%08x%08x', mt_rand(), mt_rand(), mt_rand(), mt_rand());
-	}
-
-	/**
-	 * @return string the private key used to generate HMAC.
-	 * If the key is not explicitly set, a random one is generated and returned.
-	 */
-	public function getValidationKey()
-	{
-		if ($this->_validationKey !== null) {
-			return $this->_validationKey;
-		} else {
-			if (($key = \Yii::$application->getGlobalState(self::STATE_VALIDATION_KEY)) !== null) {
-				$this->setValidationKey($key);
-			} else {
-				$key = $this->generateRandomKey();
-				$this->setValidationKey($key);
-				\Yii::$application->setGlobalState(self::STATE_VALIDATION_KEY, $key);
-			}
-			return $this->_validationKey;
-		}
-	}
-
-	/**
-	 * @param string $value the key used to generate HMAC
-	 * @throws CException if the key is empty
-	 */
-	public function setValidationKey($value)
-	{
-		if (!empty($value)) {
-			$this->_validationKey = $value;
-		} else {
-			throw new CException(Yii::t('yii', 'SecurityManager.validationKey cannot be empty.'));
-		}
-	}
-
-	/**
-	 * @return string the private key used to encrypt/decrypt data.
-	 * If the key is not explicitly set, a random one is generated and returned.
-	 */
-	public function getEncryptionKey()
-	{
-		if ($this->_encryptionKey !== null) {
-			return $this->_encryptionKey;
-		} else {
-			if (($key = \Yii::$application->getGlobalState(self::STATE_ENCRYPTION_KEY)) !== null) {
-				$this->setEncryptionKey($key);
-			} else {
-				$key = $this->generateRandomKey();
-				$this->setEncryptionKey($key);
-				\Yii::$application->setGlobalState(self::STATE_ENCRYPTION_KEY, $key);
-			}
-			return $this->_encryptionKey;
-		}
-	}
-
-	/**
-	 * @param string $value the key used to encrypt/decrypt data.
-	 * @throws CException if the key is empty
-	 */
-	public function setEncryptionKey($value)
-	{
-		if (!empty($value)) {
-			$this->_encryptionKey = $value;
-		} else {
-			throw new CException(Yii::t('yii', 'SecurityManager.encryptionKey cannot be empty.'));
-		}
-	}
-
-	/**
-	 * This method has been deprecated since version 1.1.3.
-	 * Please use {@link hashAlgorithm} instead.
-	 * @return string
-	 */
-	public function getValidation()
-	{
-		return $this->hashAlgorithm;
-	}
-
-	/**
-	 * This method has been deprecated since version 1.1.3.
-	 * Please use {@link hashAlgorithm} instead.
-	 * @param string $value -
-	 */
-	public function setValidation($value)
-	{
-		$this->hashAlgorithm = $value;
-	}
-
-	/**
-	 * Encrypts data.
-	 * @param string $data data to be encrypted.
-	 * @param string $key the decryption key. This defaults to null, meaning using {@link getEncryptionKey EncryptionKey}.
-	 * @return string the encrypted data
-	 * @throws CException if PHP Mcrypt extension is not loaded
-	 */
-	public function encrypt($data, $key = null)
-	{
-		$module = $this->openCryptModule();
-		$key = $this->substr($key === null ? md5($this->getEncryptionKey()) : $key, 0, mcrypt_enc_get_key_size($module));
-		srand();
-		$iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($module), MCRYPT_RAND);
-		mcrypt_generic_init($module, $key, $iv);
-		$encrypted = $iv . mcrypt_generic($module, $data);
-		mcrypt_generic_deinit($module);
-		mcrypt_module_close($module);
-		return $encrypted;
-	}
-
-	/**
-	 * Decrypts data
-	 * @param string $data data to be decrypted.
-	 * @param string $key the decryption key. This defaults to null, meaning using {@link getEncryptionKey EncryptionKey}.
-	 * @return string the decrypted data
-	 * @throws CException if PHP Mcrypt extension is not loaded
-	 */
-	public function decrypt($data, $key = null)
-	{
-		$module = $this->openCryptModule();
-		$key = $this->substr($key === null ? md5($this->getEncryptionKey()) : $key, 0, mcrypt_enc_get_key_size($module));
-		$ivSize = mcrypt_enc_get_iv_size($module);
-		$iv = $this->substr($data, 0, $ivSize);
-		mcrypt_generic_init($module, $key, $iv);
-		$decrypted = mdecrypt_generic($module, $this->substr($data, $ivSize, $this->strlen($data)));
-		mcrypt_generic_deinit($module);
-		mcrypt_module_close($module);
-		return rtrim($decrypted, "\0");
-	}
-
-	/**
-	 * Opens the mcrypt module with the configuration specified in {@link cryptAlgorithm}.
-	 * @return resource the mycrypt module handle.
-	 * @since 1.1.3
-	 */
-	protected function openCryptModule()
-	{
-		if (extension_loaded('mcrypt')) {
-			if (is_array($this->cryptAlgorithm)) {
-				$module = @call_user_func_array('mcrypt_module_open', $this->cryptAlgorithm);
-			} else {
-				$module = @mcrypt_module_open($this->cryptAlgorithm, '', MCRYPT_MODE_CBC, '');
-			}
-
-			if ($module === false) {
-				throw new CException(Yii::t('yii', 'Failed to initialize the mcrypt module.'));
-			}
-
-			return $module;
-		} else {
-			throw new CException(Yii::t('yii', 'SecurityManager requires PHP mcrypt extension to be loaded in order to use data encryption feature.'));
-		}
-	}
-
-	/**
-	 * Prefixes data with an HMAC.
-	 * @param string $data data to be hashed.
-	 * @param string $key the private key to be used for generating HMAC. Defaults to null, meaning using {@link validationKey}.
-	 * @return string data prefixed with HMAC
-	 */
-	public function hashData($data, $key = null)
-	{
-		return $this->computeHMAC($data, $key) . $data;
-	}
-
-	/**
-	 * Validates if data is tampered.
-	 * @param string $data data to be validated. The data must be previously
-	 * generated using {@link hashData()}.
-	 * @param string $key the private key to be used for generating HMAC. Defaults to null, meaning using {@link validationKey}.
-	 * @return string the real data with HMAC stripped off. False if the data
-	 * is tampered.
-	 */
-	public function validateData($data, $key = null)
-	{
-		$len = $this->strlen($this->computeHMAC('test'));
-		if ($this->strlen($data) >= $len) {
-			$hmac = $this->substr($data, 0, $len);
-			$data2 = $this->substr($data, $len, $this->strlen($data));
-			return $hmac === $this->computeHMAC($data2, $key) ? $data2 : false;
-		} else {
-			return false;
-		}
-	}
-
-	/**
-	 * Computes the HMAC for the data with {@link getValidationKey ValidationKey}.
-	 * @param string $data data to be generated HMAC
-	 * @param string $key the private key to be used for generating HMAC. Defaults to null, meaning using {@link validationKey}.
-	 * @return string the HMAC for the data
-	 */
-	protected function computeHMAC($data, $key = null)
-	{
-		if ($key === null) {
-			$key = $this->getValidationKey();
-		}
-
-		if (function_exists('hash_hmac')) {
-			return hash_hmac($this->hashAlgorithm, $data, $key);
-		}
-
-		if (!strcasecmp($this->hashAlgorithm, 'sha1')) {
-			$pack = 'H40';
-			$func = 'sha1';
-		} else {
-			$pack = 'H32';
-			$func = 'md5';
-		}
-		if ($this->strlen($key) > 64) {
-			$key = pack($pack, $func($key));
-		}
-		if ($this->strlen($key) < 64) {
-			$key = str_pad($key, 64, chr(0));
-		}
-		$key = $this->substr($key, 0, 64);
-		return $func((str_repeat(chr(0x5C), 64) ^ $key) . pack($pack, $func((str_repeat(chr(0x36), 64) ^ $key) . $data)));
-	}
-
-	/**
-	 * Returns the length of the given string.
-	 * If available uses the multibyte string function mb_strlen.
-	 * @param string $string the string being measured for length
-	 * @return int the length of the string
-	 */
-	private function strlen($string)
-	{
-		return function_exists('mb_strlen') ? mb_strlen($string, '8bit') : strlen($string);
-	}
-
-	/**
-	 * Returns the portion of string specified by the start and length parameters.
-	 * If available uses the multibyte string function mb_substr
-	 * @param string $string the input string. Must be one character or longer.
-	 * @param int $start the starting position
-	 * @param int $length the desired portion length
-	 * @return string the extracted part of string, or FALSE on failure or an empty string.
-	 */
-	private function substr($string, $start, $length)
-	{
-		return function_exists('mb_substr') ? mb_substr($string, $start, $length, '8bit') : substr($string, $start, $length);
-	}
-}
diff --git a/framework/base/Theme.php b/framework/base/Theme.php
index 03f8f55..88ecb0a 100644
--- a/framework/base/Theme.php
+++ b/framework/base/Theme.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * Theme class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
@@ -11,7 +9,7 @@ namespace yii\base;
 
 use Yii;
 use yii\base\InvalidConfigException;
-use yii\util\FileHelper;
+use yii\helpers\FileHelper;
 
 /**
  * Theme represents an application theme.
@@ -42,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;
 
@@ -58,14 +57,16 @@ class Theme extends Component
 		if (empty($this->pathMap)) {
 			if ($this->basePath !== null) {
 				$this->basePath = FileHelper::ensureDirectory($this->basePath);
-				$this->pathMap = array(Yii::$application->getBasePath() => $this->basePath);
+				$this->pathMap = array(Yii::$app->getBasePath() => $this->basePath);
 			} else {
 				throw new InvalidConfigException("Theme::basePath must be set.");
 			}
 		}
 		$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;
 	}
@@ -95,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/UnknownMethodException.php b/framework/base/UnknownMethodException.php
index 459f791..29bedca 100644
--- a/framework/base/UnknownMethodException.php
+++ b/framework/base/UnknownMethodException.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * UnknownMethodException class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
@@ -15,14 +13,14 @@ namespace yii\base;
  * @author Qiang Xue <qiang.xue@gmail.com>
  * @since 2.0
  */
-class UnknownMethodException extends \Exception
+class UnknownMethodException extends Exception
 {
 	/**
 	 * @return string the user-friendly name of this exception
 	 */
 	public function getName()
 	{
-		return \Yii::t('yii', 'Unknown Method');
+		return \Yii::t('yii|Unknown Method');
 	}
 }
 
diff --git a/framework/base/UnknownPropertyException.php b/framework/base/UnknownPropertyException.php
index de8de1c..5ec3814 100644
--- a/framework/base/UnknownPropertyException.php
+++ b/framework/base/UnknownPropertyException.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * UnknownPropertyException class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
@@ -15,14 +13,14 @@ namespace yii\base;
  * @author Qiang Xue <qiang.xue@gmail.com>
  * @since 2.0
  */
-class UnknownPropertyException extends \Exception
+class UnknownPropertyException extends Exception
 {
 	/**
 	 * @return string the user-friendly name of this exception
 	 */
 	public function getName()
 	{
-		return \Yii::t('yii', 'Unknown Property');
+		return \Yii::t('yii|Unknown Property');
 	}
 }
 
diff --git a/framework/base/UrlManager.php b/framework/base/UrlManager.php
deleted file mode 100644
index 3de8807..0000000
--- a/framework/base/UrlManager.php
+++ /dev/null
@@ -1,837 +0,0 @@
-<?php
-/**
- * UrlManager class file
- *
- * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
- * @license http://www.yiiframework.com/license/
- */
-
-namespace yii\base;
-
-use \yii\base\Component;
-
-/**
- * UrlManager manages the URLs of Yii applications.
- *
- * It provides URL construction ({@link createUrl()}) as well as parsing ({@link parseUrl()}) functionality.
- *
- * URLs managed via UrlManager can be in one of the following two formats,
- * by setting {@link setUrlFormat urlFormat} property:
- * <ul>
- * <li>'path' format: /path/to/EntryScript.php/name1/value1/name2/value2...</li>
- * <li>'get' format:  /path/to/EntryScript.php?name1=value1&name2=value2...</li>
- * </ul>
- *
- * When using 'path' format, UrlManager uses a set of {@link setRules rules} to:
- * <ul>
- * <li>parse the requested URL into a route ('ControllerID/ActionID') and GET parameters;</li>
- * <li>create URLs based on the given route and GET parameters.</li>
- * </ul>
- *
- * A rule consists of a route and a pattern. The latter is used by UrlManager to determine
- * which rule is used for parsing/creating URLs. A pattern is meant to match the path info
- * part of a URL. It may contain named parameters using the syntax '&lt;ParamName:RegExp&gt;'.
- *
- * When parsing a URL, a matching rule will extract the named parameters from the path info
- * and put them into the $_GET variable; when creating a URL, a matching rule will extract
- * the named parameters from $_GET and put them into the path info part of the created URL.
- *
- * If a pattern ends with '/*', it means additional GET parameters may be appended to the path
- * info part of the URL; otherwise, the GET parameters can only appear in the query string part.
- *
- * To specify URL rules, set the {@link setRules rules} property as an array of rules (pattern=>route).
- * For example,
- * <pre>
- * array(
- *     'articles'=>'article/list',
- *     'article/<id:\d+>/*'=>'article/read',
- * )
- * </pre>
- * Two rules are specified in the above:
- * <ul>
- * <li>The first rule says that if the user requests the URL '/path/to/index.php/articles',
- *   it should be treated as '/path/to/index.php/article/list'; and vice versa applies
- *   when constructing such a URL.</li>
- * <li>The second rule contains a named parameter 'id' which is specified using
- *   the &lt;ParamName:RegExp&gt; syntax. It says that if the user requests the URL
- *   '/path/to/index.php/article/13', it should be treated as '/path/to/index.php/article/read?id=13';
- *   and vice versa applies when constructing such a URL.</li>
- * </ul>
- *
- * The route part may contain references to named parameters defined in the pattern part.
- * This allows a rule to be applied to different routes based on matching criteria.
- * For example,
- * <pre>
- * array(
- *      '<_c:(post|comment)>/<id:\d+>/<_a:(create|update|delete)>'=>'<_c>/<_a>',
- *      '<_c:(post|comment)>/<id:\d+>'=>'<_c>/view',
- *      '<_c:(post|comment)>s/*'=>'<_c>/list',
- * )
- * </pre>
- * In the above, we use two named parameters '<_c>' and '<_a>' in the route part. The '<_c>'
- * parameter matches either 'post' or 'comment', while the '<_a>' parameter matches an action ID.
- *
- * Like normal rules, these rules can be used for both parsing and creating URLs.
- * For example, using the rules above, the URL '/index.php/post/123/create'
- * would be parsed as the route 'post/create' with GET parameter 'id' being 123.
- * And given the route 'post/list' and GET parameter 'page' being 2, we should get a URL
- * '/index.php/posts/page/2'.
- *
- * It is also possible to include hostname into the rules for parsing and creating URLs.
- * One may extract part of the hostname to be a GET parameter.
- * For example, the URL <code>http://admin.example.com/en/profile</code> may be parsed into GET parameters
- * <code>user=admin</code> and <code>lang=en</code>. On the other hand, rules with hostname may also be used to
- * create URLs with parameterized hostnames.
- *
- * In order to use parameterized hostnames, simply declare URL rules with host info, e.g.:
- * <pre>
- * array(
- *     'http://<user:\w+>.example.com/<lang:\w+>/profile' => 'user/profile',
- * )
- * </pre>
- *
- * If you want to customize URL generation and parsing you can write custom
- * URL rule classes and use them for one or several URL rules. For example,
- * <pre>
- * array(
- *   // a standard rule
- *   '<action:(login|logout)>' => 'site/<action>',
- *   // a custom rule using data in DB
- *   array(
- *     'class' => '\application\components\MyUrlRule',
- *     'connectionID' => 'db',
- *   ),
- * )
- * </pre>
- * Please note that the custom URL rule class should extend from {@link BaseUrlRule} and
- * implement the following two methods,
- * <ul>
- *    <li>{@link BaseUrlRule::createUrl()}</li>
- *    <li>{@link BaseUrlRule::parseUrl()}</li>
- * </ul>
- *
- * UrlManager is a default application component that may be accessed via
- * {@link \Yii::$application->urlManager}.
- *
- * @property string $baseUrl The base URL of the application (the part after host name and before query string).
- * If {@link showScriptName} is true, it will include the script name part.
- * Otherwise, it will not, and the ending slashes are stripped off.
- * @property string $urlFormat The URL format. Defaults to 'path'. Valid values include 'path' and 'get'.
- * Please refer to the guide for more details about the difference between these two formats.
- *
- * @author Qiang Xue <qiang.xue@gmail.com>
- * @since 2.0
- */
-class UrlManager extends Component
-{
-	const CACHE_KEY='Yii.UrlManager.rules';
-	const GET_FORMAT='get';
-	const PATH_FORMAT='path';
-
-	/**
-	 * @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.
-	 */
-	public $urlRuleClass='UrlRule';
-
-	private $_urlFormat=self::GET_FORMAT;
-	private $_rules=array();
-	private $_baseUrl;
-
-
-	/**
-	 * Initializes the application component.
-	 */
-	public function init()
-	{
-		parent::init();
-		$this->processRules();
-	}
-
-	/**
-	 * Processes the URL rules.
-	 */
-	protected function processRules()
-	{
-		if(empty($this->rules) || $this->getUrlFormat()===self::GET_FORMAT)
-			return;
-		if($this->cacheID!==false && ($cache=\Yii::$application->getComponent($this->cacheID))!==null)
-		{
-			$hash=md5(serialize($this->rules));
-			if(($data=$cache->get(self::CACHE_KEY))!==false && isset($data[1]) && $data[1]===$hash)
-			{
-				$this->_rules=$data[0];
-				return;
-			}
-		}
-		foreach($this->rules as $pattern=>$route)
-			$this->_rules[]=$this->createUrlRule($route,$pattern);
-		if(isset($cache))
-			$cache->set(self::CACHE_KEY,array($this->_rules,$hash));
-	}
-
-	/**
-	 * Adds new URL rules.
-	 * In order to make the new rules effective, this method must be called BEFORE
-	 * {@link CWebApplication::processRequest}.
-	 * @param array $rules new URL rules (pattern=>route).
-	 * @param boolean $append whether the new URL rules should be appended to the existing ones. If false,
-	 * they will be inserted at the beginning.
-	 */
-	public function addRules($rules, $append=true)
-	{
-		if ($append)
-		{
-			foreach($rules as $pattern=>$route)
-				$this->_rules[]=$this->createUrlRule($route,$pattern);
-		}
-		else
-		{
-			foreach($rules as $pattern=>$route)
-				array_unshift($this->_rules, $this->createUrlRule($route,$pattern));
-		}
-	}
-
-	/**
-	 * Creates a URL rule instance.
-	 * The default implementation returns a CUrlRule object.
-	 * @param mixed $route the route part of the rule. This could be a string or an array
-	 * @param string $pattern the pattern part of the rule
-	 * @return CUrlRule the URL rule instance
-	 */
-	protected function createUrlRule($route,$pattern)
-	{
-		if(is_array($route) && isset($route['class']))
-			return $route;
-		else
-			return new $this->urlRuleClass($route,$pattern);
-	}
-
-	/**
-	 * Constructs a URL.
-	 * @param string $route the controller and the action (e.g. article/read)
-	 * @param array $params list of GET parameters (name=>value). Both the name and value will be URL-encoded.
-	 * If the name is '#', the corresponding value will be treated as an anchor
-	 * and will be appended at the end of the URL.
-	 * @param string $ampersand the token separating name-value pairs in the URL. Defaults to '&'.
-	 * @return string the constructed URL
-	 */
-	public function createUrl($route,$params=array(),$ampersand='&')
-	{
-		unset($params[$this->routeVar]);
-		foreach($params as $i=>$param)
-			if($param===null)
-				$params[$i]='';
-
-		if(isset($params['#']))
-		{
-			$anchor='#'.$params['#'];
-			unset($params['#']);
-		}
-		else
-			$anchor='';
-		$route=trim($route,'/');
-		foreach($this->_rules as $i=>$rule)
-		{
-			if(is_array($rule))
-				$this->_rules[$i]=$rule=Yii::createComponent($rule);
-			if(($url=$rule->createUrl($this,$route,$params,$ampersand))!==false)
-			{
-				if($rule->hasHostInfo)
-					return $url==='' ? '/'.$anchor : $url.$anchor;
-				else
-					return $this->getBaseUrl().'/'.$url.$anchor;
-			}
-		}
-		return $this->createUrlDefault($route,$params,$ampersand).$anchor;
-	}
-
-	/**
-	 * Creates a URL based on default settings.
-	 * @param string $route the controller and the action (e.g. article/read)
-	 * @param array $params list of GET parameters
-	 * @param string $ampersand the token separating name-value pairs in the URL.
-	 * @return string the constructed URL
-	 */
-	protected function createUrlDefault($route,$params,$ampersand)
-	{
-		if($this->getUrlFormat()===self::PATH_FORMAT)
-		{
-			$url=rtrim($this->getBaseUrl().'/'.$route,'/');
-			if($this->appendParams)
-			{
-				$url=rtrim($url.'/'.$this->createPathInfo($params,'/','/'),'/');
-				return $route==='' ? $url : $url.$this->urlSuffix;
-			}
-			else
-			{
-				if($route!=='')
-					$url.=$this->urlSuffix;
-				$query=$this->createPathInfo($params,'=',$ampersand);
-				return $query==='' ? $url : $url.'?'.$query;
-			}
-		}
-		else
-		{
-			$url=$this->getBaseUrl();
-			if(!$this->showScriptName)
-				$url.='/';
-			if($route!=='')
-			{
-				$url.='?'.$this->routeVar.'='.$route;
-				if(($query=$this->createPathInfo($params,'=',$ampersand))!=='')
-					$url.=$ampersand.$query;
-			}
-			else if(($query=$this->createPathInfo($params,'=',$ampersand))!=='')
-				$url.='?'.$query;
-			return $url;
-		}
-	}
-
-	/**
-	 * Parses the user request.
-	 * @param HttpRequest $request the request application component
-	 * @return string the route (controllerID/actionID) and perhaps GET parameters in path format.
-	 */
-	public function parseUrl($request)
-	{
-		if($this->getUrlFormat()===self::PATH_FORMAT)
-		{
-			$rawPathInfo=$request->getPathInfo();
-			$pathInfo=$this->removeUrlSuffix($rawPathInfo,$this->urlSuffix);
-			foreach($this->_rules as $i=>$rule)
-			{
-				if(is_array($rule))
-					$this->_rules[$i]=$rule=Yii::createComponent($rule);
-				if(($r=$rule->parseUrl($this,$request,$pathInfo,$rawPathInfo))!==false)
-					return isset($_GET[$this->routeVar]) ? $_GET[$this->routeVar] : $r;
-			}
-			if($this->useStrictParsing)
-				throw new HttpException(404,Yii::t('yii','Unable to resolve the request "{route}".',
-					array('{route}'=>$pathInfo)));
-			else
-				return $pathInfo;
-		}
-		else if(isset($_GET[$this->routeVar]))
-			return $_GET[$this->routeVar];
-		else if(isset($_POST[$this->routeVar]))
-			return $_POST[$this->routeVar];
-		else
-			return '';
-	}
-
-	/**
-	 * Parses a path info into URL segments and saves them to $_GET and $_REQUEST.
-	 * @param string $pathInfo path info
-	 */
-	public function parsePathInfo($pathInfo)
-	{
-		if($pathInfo==='')
-			return;
-		$segs=explode('/',$pathInfo.'/');
-		$n=count($segs);
-		for($i=0;$i<$n-1;$i+=2)
-		{
-			$key=$segs[$i];
-			if($key==='') continue;
-			$value=$segs[$i+1];
-			if(($pos=strpos($key,'['))!==false && ($m=preg_match_all('/\[(.*?)\]/',$key,$matches))>0)
-			{
-				$name=substr($key,0,$pos);
-				for($j=$m-1;$j>=0;--$j)
-				{
-					if($matches[1][$j]==='')
-						$value=array($value);
-					else
-						$value=array($matches[1][$j]=>$value);
-				}
-				if(isset($_GET[$name]) && is_array($_GET[$name]))
-					$value=CMap::mergeArray($_GET[$name],$value);
-				$_REQUEST[$name]=$_GET[$name]=$value;
-			}
-			else
-				$_REQUEST[$key]=$_GET[$key]=$value;
-		}
-	}
-
-	/**
-	 * Creates a path info based on the given parameters.
-	 * @param array $params list of GET parameters
-	 * @param string $equal the separator between name and value
-	 * @param string $ampersand the separator between name-value pairs
-	 * @param string $key this is used internally.
-	 * @return string the created path info
-	 */
-	public function createPathInfo($params,$equal,$ampersand, $key=null)
-	{
-		$pairs = array();
-		foreach($params as $k => $v)
-		{
-			if ($key!==null)
-				$k = $key.'['.$k.']';
-
-			if (is_array($v))
-				$pairs[]=$this->createPathInfo($v,$equal,$ampersand, $k);
-			else
-				$pairs[]=urlencode($k).$equal.urlencode($v);
-		}
-		return implode($ampersand,$pairs);
-	}
-
-	/**
-	 * Removes the URL suffix from path info.
-	 * @param string $pathInfo path info part in the URL
-	 * @param string $urlSuffix the URL suffix to be removed
-	 * @return string path info with URL suffix removed.
-	 */
-	public function removeUrlSuffix($pathInfo,$urlSuffix)
-	{
-		if($urlSuffix!=='' && substr($pathInfo,-strlen($urlSuffix))===$urlSuffix)
-			return substr($pathInfo,0,-strlen($urlSuffix));
-		else
-			return $pathInfo;
-	}
-
-	/**
-	 * Returns the base URL of the application.
-	 * @return string the base URL of the application (the part after host name and before query string).
-	 * If {@link showScriptName} is true, it will include the script name part.
-	 * Otherwise, it will not, and the ending slashes are stripped off.
-	 */
-	public function getBaseUrl()
-	{
-		if($this->_baseUrl!==null)
-			return $this->_baseUrl;
-		else
-		{
-			if($this->showScriptName)
-				$this->_baseUrl=\Yii::$application->getRequest()->getScriptUrl();
-			else
-				$this->_baseUrl=\Yii::$application->getRequest()->getBaseUrl();
-			return $this->_baseUrl;
-		}
-	}
-
-	/**
-	 * Sets the base URL of the application (the part after host name and before query string).
-	 * This method is provided in case the {@link baseUrl} cannot be determined automatically.
-	 * The ending slashes should be stripped off. And you are also responsible to remove the script name
-	 * if you set {@link showScriptName} to be false.
-	 * @param string $value the base URL of the application
-	 */
-	public function setBaseUrl($value)
-	{
-		$this->_baseUrl=$value;
-	}
-
-	/**
-	 * Returns the URL format.
-	 * @return string the URL format. Defaults to 'path'. Valid values include 'path' and 'get'.
-	 * Please refer to the guide for more details about the difference between these two formats.
-	 */
-	public function getUrlFormat()
-	{
-		return $this->_urlFormat;
-	}
-
-	/**
-	 * Sets the URL format.
-	 * @param string $value the URL format. It must be either 'path' or 'get'.
-	 */
-	public function setUrlFormat($value)
-	{
-		if($value===self::PATH_FORMAT || $value===self::GET_FORMAT)
-			$this->_urlFormat=$value;
-		else
-			throw new CException(Yii::t('yii','CUrlManager.UrlFormat must be either "path" or "get".'));
-	}
-}
-
-
-/**
- * CBaseUrlRule is the base class for a URL rule class.
- *
- * Custom URL rule classes should extend from this class and implement two methods:
- * {@link createUrl} and {@link parseUrl}.
- *
- * @author Qiang Xue <qiang.xue@gmail.com>
- */
-abstract class CBaseUrlRule extends CComponent
-{
-	/**
-	 * @var boolean whether this rule will also parse the host info part. Defaults to false.
-	 */
-	public $hasHostInfo=false;
-	/**
-	 * Creates a URL based on this rule.
-	 * @param CUrlManager $manager the manager
-	 * @param string $route the route
-	 * @param array $params list of parameters (name=>value) associated with the route
-	 * @param string $ampersand the token separating name-value pairs in the URL.
-	 * @return mixed the constructed URL. False if this rule does not apply.
-	 */
-	abstract public function createUrl($manager,$route,$params,$ampersand);
-	/**
-	 * Parses a URL based on this rule.
-	 * @param UrlManager $manager the URL manager
-	 * @param HttpRequest $request the request object
-	 * @param string $pathInfo path info part of the URL (URL suffix is already removed based on {@link CUrlManager::urlSuffix})
-	 * @param string $rawPathInfo path info that contains the potential URL suffix
-	 * @return mixed the route that consists of the controller ID and action ID. False if this rule does not apply.
-	 */
-	abstract public function parseUrl($manager,$request,$pathInfo,$rawPathInfo);
-}
-
-/**
- * CUrlRule represents a URL formatting/parsing rule.
- *
- * It mainly consists of two parts: route and pattern. The former classifies
- * the rule so that it only applies to specific controller-action route.
- * The latter performs the actual formatting and parsing role. The pattern
- * may have a set of named parameters.
- *
- * @author Qiang Xue <qiang.xue@gmail.com>
- */
-class CUrlRule extends CBaseUrlRule
-{
-	/**
-	 * @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.
-	 */
-	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.
-	 */
-	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.
-	 */
-	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;
-
-	/**
-	 * Constructor.
-	 * @param string $route the route of the URL (controller/action)
-	 * @param string $pattern the pattern for matching the URL
-	 */
-	public function __construct($route,$pattern)
-	{
-		if(is_array($route))
-		{
-			foreach(array('urlSuffix', 'caseSensitive', 'defaultParams', 'matchValue', 'verb', 'parsingOnly') as $name)
-			{
-				if(isset($route[$name]))
-					$this->$name=$route[$name];
-			}
-			if(isset($route['pattern']))
-				$pattern=$route['pattern'];
-			$route=$route[0];
-		}
-		$this->route=trim($route,'/');
-
-		$tr2['/']=$tr['/']='\\/';
-
-		if(strpos($route,'<')!==false && preg_match_all('/<(\w+)>/',$route,$matches2))
-		{
-			foreach($matches2[1] as $name)
-				$this->references[$name]="<$name>";
-		}
-
-		$this->hasHostInfo=!strncasecmp($pattern,'http://',7) || !strncasecmp($pattern,'https://',8);
-
-		if($this->verb!==null)
-			$this->verb=preg_split('/[\s,]+/',strtoupper($this->verb),-1,PREG_SPLIT_NO_EMPTY);
-
-		if(preg_match_all('/<(\w+):?(.*?)?>/',$pattern,$matches))
-		{
-			$tokens=array_combine($matches[1],$matches[2]);
-			foreach($tokens as $name=>$value)
-			{
-				if($value==='')
-					$value='[^\/]+';
-				$tr["<$name>"]="(?P<$name>$value)";
-				if(isset($this->references[$name]))
-					$tr2["<$name>"]=$tr["<$name>"];
-				else
-					$this->params[$name]=$value;
-			}
-		}
-		$p=rtrim($pattern,'*');
-		$this->append=$p!==$pattern;
-		$p=trim($p,'/');
-		$this->template=preg_replace('/<(\w+):?.*?>/','<$1>',$p);
-		$this->pattern='/^'.strtr($this->template,$tr).'\/';
-		if($this->append)
-			$this->pattern.='/u';
-		else
-			$this->pattern.='$/u';
-
-		if($this->references!==array())
-			$this->routePattern='/^'.strtr($this->route,$tr2).'$/u';
-
-		if(YII_DEBUG && @preg_match($this->pattern,'test')===false)
-			throw new CException(Yii::t('yii','The URL pattern "{pattern}" for route "{route}" is not a valid regular expression.',
-				array('{route}'=>$route,'{pattern}'=>$pattern)));
-	}
-
-	/**
-	 * Creates a URL based on this rule.
-	 * @param CUrlManager $manager the manager
-	 * @param string $route the route
-	 * @param array $params list of parameters
-	 * @param string $ampersand the token separating name-value pairs in the URL.
-	 * @return mixed the constructed URL or false on error
-	 */
-	public function createUrl($manager,$route,$params,$ampersand)
-	{
-		if($this->parsingOnly)
-			return false;
-
-		if($manager->caseSensitive && $this->caseSensitive===null || $this->caseSensitive)
-			$case='';
-		else
-			$case='i';
-
-		$tr=array();
-		if($route!==$this->route)
-		{
-			if($this->routePattern!==null && preg_match($this->routePattern.$case,$route,$matches))
-			{
-				foreach($this->references as $key=>$name)
-					$tr[$name]=$matches[$key];
-			}
-			else
-				return false;
-		}
-
-		foreach($this->defaultParams as $key=>$value)
-		{
-			if(isset($params[$key]))
-			{
-				if($params[$key]==$value)
-					unset($params[$key]);
-				else
-					return false;
-			}
-		}
-
-		foreach($this->params as $key=>$value)
-			if(!isset($params[$key]))
-				return false;
-
-		if($manager->matchValue && $this->matchValue===null || $this->matchValue)
-		{
-			foreach($this->params as $key=>$value)
-			{
-				if(!preg_match('/\A'.$value.'\z/u'.$case,$params[$key]))
-					return false;
-			}
-		}
-
-		foreach($this->params as $key=>$value)
-		{
-			$tr["<$key>"]=urlencode($params[$key]);
-			unset($params[$key]);
-		}
-
-		$suffix=$this->urlSuffix===null ? $manager->urlSuffix : $this->urlSuffix;
-
-		$url=strtr($this->template,$tr);
-
-		if($this->hasHostInfo)
-		{
-			$hostInfo=\Yii::$application->getRequest()->getHostInfo();
-			if(stripos($url,$hostInfo)===0)
-				$url=substr($url,strlen($hostInfo));
-		}
-
-		if(empty($params))
-			return $url!=='' ? $url.$suffix : $url;
-
-		if($this->append)
-			$url.='/'.$manager->createPathInfo($params,'/','/').$suffix;
-		else
-		{
-			if($url!=='')
-				$url.=$suffix;
-			$url.='?'.$manager->createPathInfo($params,'=',$ampersand);
-		}
-
-		return $url;
-	}
-
-	/**
-	 * Parses a URL based on this rule.
-	 * @param UrlManager $manager the URL manager
-	 * @param HttpRequest $request the request object
-	 * @param string $pathInfo path info part of the URL
-	 * @param string $rawPathInfo path info that contains the potential URL suffix
-	 * @return mixed the route that consists of the controller ID and action ID or false on error
-	 */
-	public function parseUrl($manager,$request,$pathInfo,$rawPathInfo)
-	{
-		if($this->verb!==null && !in_array($request->getRequestType(), $this->verb, true))
-			return false;
-
-		if($manager->caseSensitive && $this->caseSensitive===null || $this->caseSensitive)
-			$case='';
-		else
-			$case='i';
-
-		if($this->urlSuffix!==null)
-			$pathInfo=$manager->removeUrlSuffix($rawPathInfo,$this->urlSuffix);
-
-		// URL suffix required, but not found in the requested URL
-		if($manager->useStrictParsing && $pathInfo===$rawPathInfo)
-		{
-			$urlSuffix=$this->urlSuffix===null ? $manager->urlSuffix : $this->urlSuffix;
-			if($urlSuffix!='' && $urlSuffix!=='/')
-				return false;
-		}
-
-		if($this->hasHostInfo)
-			$pathInfo=strtolower($request->getHostInfo()).rtrim('/'.$pathInfo,'/');
-
-		$pathInfo.='/';
-
-		if(preg_match($this->pattern.$case,$pathInfo,$matches))
-		{
-			foreach($this->defaultParams as $name=>$value)
-			{
-				if(!isset($_GET[$name]))
-					$_REQUEST[$name]=$_GET[$name]=$value;
-			}
-			$tr=array();
-			foreach($matches as $key=>$value)
-			{
-				if(isset($this->references[$key]))
-					$tr[$this->references[$key]]=$value;
-				else if(isset($this->params[$key]))
-					$_REQUEST[$key]=$_GET[$key]=$value;
-			}
-			if($pathInfo!==$matches[0]) // there're additional GET params
-				$manager->parsePathInfo(ltrim(substr($pathInfo,strlen($matches[0])),'/'));
-			if($this->routePattern!==null)
-				return strtr($this->route,$tr);
-			else
-				return $this->route;
-		}
-		else
-			return false;
-	}
-}
\ No newline at end of file
diff --git a/framework/base/UserException.php b/framework/base/UserException.php
new file mode 100644
index 0000000..01ca602
--- /dev/null
+++ b/framework/base/UserException.php
@@ -0,0 +1,19 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\base;
+
+/**
+ * UserException is the base class for exceptions that are meant to be shown to end users.
+ * Such exceptions are often caused by mistakes of end users.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class UserException extends Exception
+{
+}
diff --git a/framework/base/Vector.php b/framework/base/Vector.php
index c271ccc..7d43fdb 100644
--- a/framework/base/Vector.php
+++ b/framework/base/Vector.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * Vector class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
@@ -101,7 +99,7 @@ class Vector extends Object implements \IteratorAggregate, \ArrayAccess, \Counta
 	 * Returns the item at the specified index.
 	 * @param integer $index the index of the item
 	 * @return mixed the item at the index
-	 * @throws InvalidCallException if the index is out of range
+	 * @throws InvalidParamException if the index is out of range
 	 */
 	public function itemAt($index)
 	{
@@ -110,7 +108,7 @@ class Vector extends Object implements \IteratorAggregate, \ArrayAccess, \Counta
 		} elseif ($index >= 0 && $index < $this->_c) { // in case the value is null
 			return $this->_d[$index];
 		} else {
-			throw new InvalidCallException('Index out of range: ' . $index);
+			throw new InvalidParamException('Index out of range: ' . $index);
 		}
 	}
 
@@ -132,7 +130,7 @@ class Vector extends Object implements \IteratorAggregate, \ArrayAccess, \Counta
 	 * one step towards the end.
 	 * @param integer $index the specified position.
 	 * @param mixed $item new item to be inserted into the vector
-	 * @throws InvalidCallException if the index specified is out of range, or the vector is read-only.
+	 * @throws InvalidParamException if the index specified is out of range, or the vector is read-only.
 	 */
 	public function insertAt($index, $item)
 	{
@@ -142,7 +140,7 @@ class Vector extends Object implements \IteratorAggregate, \ArrayAccess, \Counta
 			array_splice($this->_d, $index, 0, array($item));
 			$this->_c++;
 		} else {
-			throw new InvalidCallException('Index out of range: ' . $index);
+			throw new InvalidParamException('Index out of range: ' . $index);
 		}
 	}
 
@@ -169,7 +167,7 @@ class Vector extends Object implements \IteratorAggregate, \ArrayAccess, \Counta
 	 * Removes an item at the specified position.
 	 * @param integer $index the index of the item to be removed.
 	 * @return mixed the removed item.
-	 * @throws InvalidCallException if the index is out of range, or the vector is read only.
+	 * @throws InvalidParamException if the index is out of range, or the vector is read only.
 	 */
 	public function removeAt($index)
 	{
@@ -183,7 +181,7 @@ class Vector extends Object implements \IteratorAggregate, \ArrayAccess, \Counta
 				return $item;
 			}
 		} else {
-			throw new InvalidCallException('Index out of range: ' . $index);
+			throw new InvalidParamException('Index out of range: ' . $index);
 		}
 	}
 
@@ -193,7 +191,7 @@ class Vector extends Object implements \IteratorAggregate, \ArrayAccess, \Counta
 	 * Defaults to false, meaning all items in the vector will be cleared directly
 	 * without calling [[removeAt]].
 	 */
-	public function clear($safeClear = false)
+	public function removeAll($safeClear = false)
 	{
 		if ($safeClear) {
 			for ($i = $this->_c - 1; $i >= 0; --$i) {
@@ -211,7 +209,7 @@ class Vector extends Object implements \IteratorAggregate, \ArrayAccess, \Counta
 	 * @param mixed $item the item
 	 * @return boolean whether the vector contains the item
 	 */
-	public function contains($item)
+	public function has($item)
 	{
 		return $this->indexOf($item) >= 0;
 	}
@@ -242,13 +240,13 @@ class Vector extends Object implements \IteratorAggregate, \ArrayAccess, \Counta
 	 * Copies iterable data into the vector.
 	 * Note, existing data in the vector will be cleared first.
 	 * @param mixed $data the data to be copied from, must be an array or an object implementing `Traversable`
-	 * @throws InvalidCallException if data is neither an array nor an object implementing `Traversable`.
+	 * @throws InvalidParamException if data is neither an array nor an object implementing `Traversable`.
 	 */
 	public function copyFrom($data)
 	{
 		if (is_array($data) || $data instanceof \Traversable) {
 			if ($this->_c > 0) {
-				$this->clear();
+				$this->removeAll();
 			}
 			if ($data instanceof self) {
 				$data = $data->_d;
@@ -257,7 +255,7 @@ class Vector extends Object implements \IteratorAggregate, \ArrayAccess, \Counta
 				$this->add($item);
 			}
 		} else {
-			throw new InvalidCallException('Data must be either an array or an object implementing Traversable.');
+			throw new InvalidParamException('Data must be either an array or an object implementing Traversable.');
 		}
 	}
 
@@ -265,7 +263,7 @@ class Vector extends Object implements \IteratorAggregate, \ArrayAccess, \Counta
 	 * Merges iterable data into the vector.
 	 * New items will be appended to the end of the existing items.
 	 * @param array|\Traversable $data the data to be merged with. It must be an array or object implementing Traversable
-	 * @throws InvalidCallException if data is neither an array nor an object implementing `Traversable`.
+	 * @throws InvalidParamException if data is neither an array nor an object implementing `Traversable`.
 	 */
 	public function mergeWith($data)
 	{
@@ -277,7 +275,7 @@ class Vector extends Object implements \IteratorAggregate, \ArrayAccess, \Counta
 				$this->add($item);
 			}
 		} else {
-			throw new InvalidCallException('The data to be merged with must be an array or an object implementing Traversable.');
+			throw new InvalidParamException('The data to be merged with must be an array or an object implementing Traversable.');
 		}
 	}
 
diff --git a/framework/base/VectorIterator.php b/framework/base/VectorIterator.php
index d1fefad..f83d42d 100644
--- a/framework/base/VectorIterator.php
+++ b/framework/base/VectorIterator.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * VectorIterator class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
diff --git a/framework/base/View.php b/framework/base/View.php
index 410e3c5..c7087c1 100644
--- a/framework/base/View.php
+++ b/framework/base/View.php
@@ -1,23 +1,21 @@
 <?php
 /**
- * View class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
 namespace yii\base;
 
 use Yii;
-use yii\util\FileHelper;
 use yii\base\Application;
+use yii\helpers\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 <qiang.xue@gmail.com>
  * @since 2.0
  */
@@ -26,134 +24,124 @@ 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;
+	public $context;
 	/**
-	 * @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.
+	 * @var mixed custom parameters that are shared among view templates.
 	 */
-	public $layout;
+	public $params;
 	/**
-	 * @var string the language that the view should be rendered in. If not set, it will use
-	 * the value of [[Application::language]].
+	 * @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 $language;
+	public $renderer;
 	/**
-	 * @var string the language that the original view is in. If not set, it will use
-	 * the value of [[Application::sourceLanguage]].
+	 * @var Theme|array the theme object or the configuration array for creating the theme.
+	 * If not set, it means theming is not enabled.
 	 */
-	public $sourceLanguage;
+	public $theme;
 	/**
-	 * @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.
+	 * @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 $enableI18N = true;
+	public $clips;
 	/**
-	 * @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 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 $enableTheme = true;
+	public $widgetStack = array();
 	/**
-	 * @var mixed custom parameters that are available in the view template
+	 * @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 $params;
-
+	public $cacheStack = array();
 	/**
-	 * @var Widget[] the widgets that are currently not ended
+	 * @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();
 
-	/**
-	 * 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);
-	}
-
-	/**
-	 * 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.
 	 *
-	 * 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.
+	 * 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.
 	 *
-	 * @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 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 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 InvalidCallException if the view file cannot be found
-	 * @see findViewFile()
+	 * @throws InvalidParamException if the view cannot be resolved or the view file does not exist.
+	 * @see renderFile
+	 * @see findViewFile
 	 */
-	public function renderPartial($view, $params = array())
+	public function render($view, $params = array(), $context = null)
 	{
-		$file = $this->findViewFile($view);
-		if ($file !== false) {
-			return $this->renderFile($file, $params);
-		} else {
-			throw new InvalidCallException("Unable to find the view file for view '$view'.");
-		}
+		$viewFile = $this->findViewFile($context, $view);
+		return $this->renderFile($viewFile, $params, $context);
 	}
 
 	/**
 	 * 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.
+	 * 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 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($file, $params = array())
+	public function renderFile($viewFile, $params = array(), $context = null)
 	{
-		$renderer = Yii::$application->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 {
-			return $this->renderPhpFile($file, $params);
+			throw new InvalidParamException("The view file does not exist: $viewFile");
 		}
+
+		$oldContext = $this->context;
+		if ($context !== null) {
+			$this->context = $context;
+		}
+
+		if ($this->renderer !== null) {
+			$output = $this->renderer->render($this, $viewFile, $params);
+		} else {
+			$output = $this->renderPhpFile($viewFile, $params);
+		}
+
+		$this->context = $oldContext;
+
+		return $output;
 	}
 
 	/**
@@ -163,6 +151,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
@@ -177,6 +167,95 @@ class View extends Component
 	}
 
 	/**
+	 * 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->cacheStack)) {
+			$n = count($this->dynamicPlaceholders);
+			$placeholder = "<![CDATA[YDP-$n]]>";
+			$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);
+	}
+
+	/**
+	 * 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 [[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 or the context cannot be
+	 * used to determine the actual view file corresponding to the specified view.
+	 */
+	protected function findViewFile($context, $view)
+	{
+		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->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 FileHelper::getExtension($file) === '' ? $file . '.php' : $file;
+	}
+
+	/**
 	 * Creates a widget.
 	 * This method will use [[Yii::createObject()]] to create the widget.
 	 * @param string $class the widget class name or path alias
@@ -186,7 +265,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);
 	}
 
 	/**
@@ -225,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;
 	}
 
@@ -235,260 +314,108 @@ 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()
 	{
-		$widget = array_pop($this->_widgetStack);
+		$widget = array_pop($this->widgetStack);
 		if ($widget instanceof Widget) {
 			$widget->run();
 			return $widget;
 		} else {
-			throw new Exception("Unmatched beginWidget() and endWidget() calls.");
+			throw new InvalidCallException("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. "@application/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
+	 * 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
+	 * @see \yii\widgets\Clip
 	 */
-	public function findViewFile($view)
+	public function beginClip($id, $renderInPlace = false)
 	{
-		if (FileHelper::getExtension($view) === '') {
-			$view .= '.php';
-		}
-		if (strncmp($view, '@', 1) === 0) {
-			// e.g. "@application/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::$application->getViewPath() . DIRECTORY_SEPARATOR . $view;
-			}
-		} elseif (strncmp($view, '//', 2) !== 0 && Yii::$application->controller !== null) {
-			// e.g. "/site/index"
-			$file = Yii::$application->controller->module->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
-		} else {
-			// e.g. "//layouts/main"
-			$file = Yii::$application->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
-		}
+		return $this->beginWidget('yii\widgets\Clip', array(
+			'id' => $id,
+			'renderInPlace' => $renderInPlace,
+			'view' => $this,
+		));
+	}
 
-		if (is_file($file)) {
-			if ($this->enableTheme && ($theme = Yii::$application->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");
-		}
+	/**
+	 * Ends recording a clip.
+	 */
+	public function endClip()
+	{
+		$this->endWidget();
 	}
 
 	/**
-	 * 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. "@application/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.
+	 * 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.
+	 * 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,
 	 *
-	 * And if [[enableI18N]] is true, the method will attempt to use a translated version of the layout file,
-	 * when available.
+	 * ~~~
+	 * if($this->beginCache($id)) {
+	 *     // ...generate content here
+	 *     $this->endCache();
+	 * }
+	 * ~~~
 	 *
-	 * @return string|boolean the layout file path, or false if layout is not needed.
-	 * @throws InvalidConfigException if the layout file cannot be found
+	 * @param string $id a unique ID identifying the fragment to be cached.
+	 * @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 findLayoutFile()
+	public function beginCache($id, $properties = array())
 	{
-		/** @var $module Module */
-		if (is_string($this->layout)) {
-			if (Yii::$application->controller) {
-				$module = Yii::$application->controller->module;
-			} else {
-				$module = Yii::$application;
-			}
-			$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)) {
+		$properties['id'] = $id;
+		$properties['view'] = $this;
+		/** @var $cache \yii\widgets\FragmentCache */
+		$cache = $this->beginWidget('yii\widgets\FragmentCache', $properties);
+		if ($cache->getCachedContent() !== false) {
+			$this->endCache();
 			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::$application->getLayoutPath() . DIRECTORY_SEPARATOR . $view;
 		} else {
-			$file = $module->getLayoutPath() . DIRECTORY_SEPARATOR . $view;
+			return true;
 		}
+	}
 
-		if (is_file($file)) {
-			if ($this->enableTheme && ($theme = Yii::$application->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");
-		}
+	/**
+	 * Ends fragment caching.
+	 */
+	public function endCache()
+	{
+		$this->endWidget();
 	}
 }
\ No newline at end of file
diff --git a/framework/base/ViewRenderer.php b/framework/base/ViewRenderer.php
index ecb216d..576bbe8 100644
--- a/framework/base/ViewRenderer.php
+++ b/framework/base/ViewRenderer.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * ViewRenderer class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
diff --git a/framework/base/Widget.php b/framework/base/Widget.php
index bdec634..24d0685 100644
--- a/framework/base/Widget.php
+++ b/framework/base/Widget.php
@@ -1,14 +1,15 @@
 <?php
 /**
- * Widget class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
 namespace yii\base;
 
+use Yii;
+use yii\helpers\FileHelper;
+
 /**
  * Widget is the base class for widgets.
  *
@@ -72,35 +73,26 @@ 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. `@application/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);
+		return Yii::$app->getView()->render($view, $params, $this);
 	}
 
 	/**
-	 * @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()->renderFile($file, $params, $this);
 	}
 
 	/**
diff --git a/framework/caching/ApcCache.php b/framework/caching/ApcCache.php
index b4df296..dd954cc 100644
--- a/framework/caching/ApcCache.php
+++ b/framework/caching/ApcCache.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * ApcCache class file
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
diff --git a/framework/caching/Cache.php b/framework/caching/Cache.php
index a35785c..70cf8cb 100644
--- a/framework/caching/Cache.php
+++ b/framework/caching/Cache.php
@@ -1,15 +1,14 @@
 <?php
 /**
- * Cache class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
 namespace yii\caching;
 
 use yii\base\Component;
+use yii\helpers\StringHelper;
 
 /**
  * Cache is the base class for cache classes supporting different cache storage implementation.
@@ -72,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:
 	 *
@@ -86,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(serialize($params));
+			return md5(json_encode($key));
 		}
 	}
 
diff --git a/framework/caching/ChainedDependency.php b/framework/caching/ChainedDependency.php
index 570715d..9c4e547 100644
--- a/framework/caching/ChainedDependency.php
+++ b/framework/caching/ChainedDependency.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * ChainedDependency class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
diff --git a/framework/caching/DbCache.php b/framework/caching/DbCache.php
index 4b84bfd..3952852 100644
--- a/framework/caching/DbCache.php
+++ b/framework/caching/DbCache.php
@@ -1,14 +1,13 @@
 <?php
 /**
- * DbCache class file
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
 namespace yii\caching;
 
+use Yii;
 use yii\base\InvalidConfigException;
 use yii\db\Connection;
 use yii\db\Query;
@@ -16,30 +15,20 @@ use yii\db\Query;
 /**
  * DbCache implements a cache application component by storing cached data in a database.
  *
- * DbCache stores cache data in a DB table whose name is specified via [[cacheTableName]].
- * For MySQL database, the table should be created beforehand as follows :
- *
- * ~~~
- * CREATE TABLE tbl_cache (
- *     id char(128) NOT NULL,
- *     expire int(11) DEFAULT NULL,
- *     data LONGBLOB,
- *     PRIMARY KEY (id),
- *     KEY expire (expire)
- * );
- * ~~~
- *
- * You should replace `LONGBLOB` as follows if you are using a different DBMS:
- *
- * - PostgreSQL: `BYTEA`
- * - SQLite, SQL server, Oracle: `BLOB`
- *
- * DbCache connects to the database via the DB connection specified in [[connectionID]]
- * which must refer to a valid DB application component.
+ * By default, DbCache stores session data in a DB table named 'tbl_cache'. This table
+ * must be pre-created. The table name can be changed by setting [[cacheTable]].
  *
  * Please refer to [[Cache]] for common cache operations that are supported by DbCache.
  *
- * @property Connection $db The DB connection instance.
+ * The following example shows how you can configure the application to use DbCache:
+ *
+ * ~~~
+ * 'cache' => array(
+ *     'class' => 'yii\caching\DbCache',
+ *     // 'db' => 'mydb',
+ *     // 'cacheTable' => 'my_cache',
+ * )
+ * ~~~
  *
  * @author Qiang Xue <qiang.xue@gmail.com>
  * @since 2.0
@@ -47,50 +36,56 @@ use yii\db\Query;
 class DbCache extends Cache
 {
 	/**
-	 * @var string the ID of the [[Connection|DB connection]] application component. Defaults to 'db'.
+	 * @var Connection|string the DB connection object or the application component ID of the DB connection.
+	 * After the DbCache object is created, if you want to change this property, you should only assign it
+	 * with a DB connection object.
 	 */
-	public $connectionID = 'db';
+	public $db = 'db';
 	/**
-	 * @var string name of the DB table to store cache content. Defaults to 'tbl_cache'.
-	 * The table must be created before using this cache component.
+	 * @var string name of the DB table to store cache content.
+	 * The table should be pre-created as follows:
+	 *
+	 * ~~~
+	 * CREATE TABLE tbl_cache (
+	 *     id char(128) NOT NULL PRIMARY KEY,
+	 *     expire int(11),
+	 *     data BLOB
+	 * );
+	 * ~~~
+	 *
+	 * where 'BLOB' refers to the BLOB-type of your preferred DBMS. Below are the BLOB type
+	 * that can be used for some popular DBMS:
+	 *
+	 * - MySQL: LONGBLOB
+	 * - PostgreSQL: BYTEA
+	 * - MSSQL: BLOB
+	 *
+	 * When using DbCache in a production server, we recommend you create a DB index for the 'expire'
+	 * column in the cache table to improve the performance.
 	 */
-	public $cacheTableName = 'tbl_cache';
+	public $cacheTable = 'tbl_cache';
 	/**
 	 * @var integer the probability (parts per million) that garbage collection (GC) should be performed
-	 * when storing a piece of data in the cache. Defaults to 10, meaning 0.001% chance.
+	 * when storing a piece of data in the cache. Defaults to 100, meaning 0.01% chance.
 	 * This number should be between 0 and 1000000. A value 0 meaning no GC will be performed at all.
 	 **/
 	public $gcProbability = 100;
-	/**
-	 * @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.
-	 */
-	public function getDb()
-	{
-		if ($this->_db === null) {
-			$db = \Yii::$application->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.");
-			}
-		}
-		return $this->_db;
-	}
 
 	/**
-	 * Sets the DB connection used by the cache component.
-	 * @param Connection $value the DB connection instance
+	 * Initializes the DbCache component.
+	 * This method will initialize the [[db]] property to make sure it refers to a valid DB connection.
+	 * @throws InvalidConfigException if [[db]] is invalid.
 	 */
-	public function setDb($value)
+	public function init()
 	{
-		$this->_db = $value;
+		parent::init();
+		if (is_string($this->db)) {
+			$this->db = Yii::$app->getComponent($this->db);
+		}
+		if (!$this->db instanceof Connection) {
+			throw new InvalidConfigException("DbCache::db must be either a DB connection instance or the application component ID of a DB connection.");
+		}
 	}
 
 	/**
@@ -103,17 +98,16 @@ class DbCache extends Cache
 	{
 		$query = new Query;
 		$query->select(array('data'))
-			->from($this->cacheTableName)
-			->where('id = :id AND (expire = 0 OR expire > :time)', array(':id' => $key, ':time' => time()));
-		$db = $this->getDb();
-		if ($db->enableQueryCache) {
+			->from($this->cacheTable)
+			->where('id = :id AND (expire = 0 OR expire >' . time() . ')', array(':id' => $key));
+		if ($this->db->enableQueryCache) {
 			// temporarily disable and re-enable query caching
-			$db->enableQueryCache = false;
-			$result = $query->createCommand($db)->queryScalar();
-			$db->enableQueryCache = true;
+			$this->db->enableQueryCache = false;
+			$result = $query->createCommand($this->db)->queryScalar();
+			$this->db->enableQueryCache = true;
 			return $result;
 		} else {
-			return $query->createCommand($db)->queryScalar();
+			return $query->createCommand($this->db)->queryScalar();
 		}
 	}
 
@@ -129,17 +123,16 @@ class DbCache extends Cache
 		}
 		$query = new Query;
 		$query->select(array('id', 'data'))
-			->from($this->cacheTableName)
+			->from($this->cacheTable)
 			->where(array('id' => $keys))
-			->andWhere("expire = 0 OR expire > " . time() . ")");
+			->andWhere('(expire = 0 OR expire > ' . time() . ')');
 
-		$db = $this->getDb();
-		if ($db->enableQueryCache) {
-			$db->enableQueryCache = false;
-			$rows = $query->createCommand($db)->queryAll();
-			$db->enableQueryCache = true;
+		if ($this->db->enableQueryCache) {
+			$this->db->enableQueryCache = false;
+			$rows = $query->createCommand($this->db)->queryAll();
+			$this->db->enableQueryCache = true;
 		} else {
-			$rows = $query->createCommand($db)->queryAll();
+			$rows = $query->createCommand($this->db)->queryAll();
 		}
 
 		$results = array();
@@ -163,13 +156,13 @@ class DbCache extends Cache
 	 */
 	protected function setValue($key, $value, $expire)
 	{
-		$command = $this->getDb()->createCommand();
-		$command->update($this->cacheTableName, array(
-			'expire' => $expire > 0 ? $expire + time() : 0,
-			'data' => array($value, \PDO::PARAM_LOB),
-		), array(
-			'id' => $key,
-		));;
+		$command = $this->db->createCommand()
+			->update($this->cacheTable, array(
+				'expire' => $expire > 0 ? $expire + time() : 0,
+				'data' => array($value, \PDO::PARAM_LOB),
+			), array(
+				'id' => $key,
+			));
 
 		if ($command->execute()) {
 			$this->gc();
@@ -198,14 +191,13 @@ class DbCache extends Cache
 			$expire = 0;
 		}
 
-		$command = $this->getDb()->createCommand();
-		$command->insert($this->cacheTableName, array(
-			'id' => $key,
-			'expire' => $expire,
-			'data' => array($value, \PDO::PARAM_LOB),
-		));
 		try {
-			$command->execute();
+			$this->db->createCommand()
+				->insert($this->cacheTable, array(
+					'id' => $key,
+					'expire' => $expire,
+					'data' => array($value, \PDO::PARAM_LOB),
+				))->execute();
 			return true;
 		} catch (\Exception $e) {
 			return false;
@@ -220,8 +212,9 @@ class DbCache extends Cache
 	 */
 	protected function deleteValue($key)
 	{
-		$command = $this->getDb()->createCommand();
-		$command->delete($this->cacheTableName, array('id' => $key))->execute();
+		$this->db->createCommand()
+			->delete($this->cacheTable, array('id' => $key))
+			->execute();
 		return true;
 	}
 
@@ -233,8 +226,9 @@ class DbCache extends Cache
 	public function gc($force = false)
 	{
 		if ($force || mt_rand(0, 1000000) < $this->gcProbability) {
-			$command = $this->getDb()->createCommand();
-			$command->delete($this->cacheTableName, 'expire > 0 AND expire < ' . time())->execute();
+			$this->db->createCommand()
+				->delete($this->cacheTable, 'expire > 0 AND expire < ' . time())
+				->execute();
 		}
 	}
 
@@ -245,8 +239,9 @@ class DbCache extends Cache
 	 */
 	protected function flushValues()
 	{
-		$command = $this->getDb()->createCommand();
-		$command->delete($this->cacheTableName)->execute();
+		$this->db->createCommand()
+			->delete($this->cacheTable)
+			->execute();
 		return true;
 	}
 }
diff --git a/framework/caching/DbDependency.php b/framework/caching/DbDependency.php
index 7ffdb4e..cbe0ae1 100644
--- a/framework/caching/DbDependency.php
+++ b/framework/caching/DbDependency.php
@@ -1,23 +1,21 @@
 <?php
 /**
- * DbDependency class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
 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 <qiang.xue@gmail.com>
  * @since 2.0
@@ -25,88 +23,52 @@ use yii\db\Query;
 class DbDependency extends Dependency
 {
 	/**
-	 * @var string the ID of the [[Connection|DB connection]] application component. Defaults to 'db'.
+	 * @var string the application component ID of the DB connection.
 	 */
-	public $connectionID = 'db';
+	public $db = '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);
 	}
 
 	/**
-	 * PHP sleep magic method.
-	 * This method ensures that the database instance is set null because it contains resource handles.
-	 * @return array
-	 */
-	public function __sleep()
-	{
-		$this->_db = null;
-		return array_keys((array)$this);
-	}
-
-	/**
 	 * Generates the data needed to determine if dependency has been changed.
 	 * This method returns the value of the global state.
 	 * @return mixed the data needed to determine if dependency has been changed.
 	 */
 	protected function generateDependencyData()
 	{
-		$db = $this->getDb();
-		/**
-		 * @var \yii\db\Command $command
-		 */
-		$command = $this->query->createCommand($db);
+		$db = Yii::$app->getComponent($this->db);
+		if (!$db instanceof Connection) {
+			throw new InvalidConfigException("DbDependency::db must be the application component ID of a DB connection.");
+		}
+
 		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;
 	}
-
-	/**
-	 * 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.
-	 */
-	public function getDb()
-	{
-		if ($this->_db === null) {
-			$db = \Yii::$application->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.");
-			}
-		}
-		return $this->_db;
-	}
-
-	/**
-	 * Sets the DB connection used by the cache component.
-	 * @param Connection $value the DB connection instance
-	 */
-	public function setDb($value)
-	{
-		$this->_db = $value;
-	}
 }
diff --git a/framework/caching/Dependency.php b/framework/caching/Dependency.php
index 2e66145..feb8c07 100644
--- a/framework/caching/Dependency.php
+++ b/framework/caching/Dependency.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * Dependency class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
diff --git a/framework/caching/DummyCache.php b/framework/caching/DummyCache.php
index f6e8a44..359fa7c 100644
--- a/framework/caching/DummyCache.php
+++ b/framework/caching/DummyCache.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * DummyCache class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
@@ -13,7 +11,7 @@ namespace yii\caching;
  * DummyCache is a placeholder cache component.
  *
  * DummyCache does not cache anything. It is provided so that one can always configure
- * a 'cache' application component and save the check of existence of `\Yii::$application->cache`.
+ * a 'cache' application component and save the check of existence of `\Yii::$app->cache`.
  * By replacing DummyCache with some other cache component, one can quickly switch from
  * non-caching mode to caching mode.
  *
diff --git a/framework/caching/ExpressionDependency.php b/framework/caching/ExpressionDependency.php
index 7ad7543..e13c962 100644
--- a/framework/caching/ExpressionDependency.php
+++ b/framework/caching/ExpressionDependency.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * ExpressionDependency class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
diff --git a/framework/caching/FileCache.php b/framework/caching/FileCache.php
index f97861f..e565cad 100644
--- a/framework/caching/FileCache.php
+++ b/framework/caching/FileCache.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * FileCache class file
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
@@ -28,7 +26,7 @@ class FileCache extends Cache
 	/**
 	 * @var string the directory to store cache files. You may use path alias here.
 	 */
-	public $cachePath = '@application/runtime/cache';
+	public $cachePath = '@app/runtime/cache';
 	/**
 	 * @var string cache file suffix. Defaults to '.bin'.
 	 */
@@ -54,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/caching/FileDependency.php b/framework/caching/FileDependency.php
index 89b356c..3797dde 100644
--- a/framework/caching/FileDependency.php
+++ b/framework/caching/FileDependency.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * FileDependency class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
diff --git a/framework/caching/MemCache.php b/framework/caching/MemCache.php
index 288c3ee..df07b8e 100644
--- a/framework/caching/MemCache.php
+++ b/framework/caching/MemCache.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * MemCache class file
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
diff --git a/framework/caching/MemCacheServer.php b/framework/caching/MemCacheServer.php
index 13e929e..105137e 100644
--- a/framework/caching/MemCacheServer.php
+++ b/framework/caching/MemCacheServer.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * MemCacheServer class file
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
diff --git a/framework/caching/WinCache.php b/framework/caching/WinCache.php
index 8bb6569..ee6b4a9 100644
--- a/framework/caching/WinCache.php
+++ b/framework/caching/WinCache.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * WinCache class file
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
diff --git a/framework/caching/XCache.php b/framework/caching/XCache.php
index af5501c..2108c4f 100644
--- a/framework/caching/XCache.php
+++ b/framework/caching/XCache.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * XCache class file
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
diff --git a/framework/caching/ZendDataCache.php b/framework/caching/ZendDataCache.php
index 8716a36..669733d 100644
--- a/framework/caching/ZendDataCache.php
+++ b/framework/caching/ZendDataCache.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * ZendDataCache class file
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
diff --git a/framework/console/Application.php b/framework/console/Application.php
index 237be05..574495b 100644
--- a/framework/console/Application.php
+++ b/framework/console/Application.php
@@ -3,13 +3,12 @@
  * Console Application class file.
  *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
 namespace yii\console;
 
-use yii\base\Exception;
 use yii\base\InvalidRouteException;
 
 /**
@@ -85,16 +84,17 @@ class Application extends \yii\base\Application
 	 * Processes the request.
 	 * The request is represented in terms of a controller route and action parameters.
 	 * @return integer the exit status of the controller action (0 means normal, non-zero values mean abnormal)
+	 * @throws Exception if the script is not running from the command line
 	 */
 	public function processRequest()
 	{
 		/** @var $request Request */
 		$request = $this->getRequest();
 		if ($request->getIsConsoleRequest()) {
-			return $this->runAction($request->route, $request->params);
+			list ($route, $params) = $request->resolve();
+			return $this->runAction($route, $params);
 		} else {
-			echo "Error: this script must be run from the command line.";
-			return 1;
+			throw new Exception(\Yii::t('yii|This script must be run from the command line.'));
 		}
 	}
 
@@ -106,14 +106,14 @@ class Application extends \yii\base\Application
 	 * @param string $route the route that specifies the action.
 	 * @param array $params the parameters to be passed to the action
 	 * @return integer the status code returned by the action execution. 0 means normal, and other values mean abnormal.
+	 * @throws Exception if the route is invalid
 	 */
 	public function runAction($route, $params = array())
 	{
 		try {
 			return parent::runAction($route, $params);
 		} catch (InvalidRouteException $e) {
-			echo "Error: unknown command \"$route\".\n";
-			return 1;
+			throw new Exception(\Yii::t('yii|Unknown command "{command}".', array('{command}' => $route)));
 		}
 	}
 
@@ -127,8 +127,8 @@ class Application extends \yii\base\Application
 			'message' => 'yii\console\controllers\MessageController',
 			'help' => 'yii\console\controllers\HelpController',
 			'migrate' => 'yii\console\controllers\MigrateController',
-			'shell' => 'yii\console\controllers\ShellController',
-			'create' => 'yii\console\controllers\CreateController',
+			'app' => 'yii\console\controllers\AppController',
+			'cache' => 'yii\console\controllers\CacheController',
 		);
 	}
 
@@ -148,9 +148,4 @@ class Application extends \yii\base\Application
 			),
 		));
 	}
-
-	public function usageError($message)
-	{
-
-	}
 }
diff --git a/framework/console/Controller.php b/framework/console/Controller.php
index 16968f2..9924822 100644
--- a/framework/console/Controller.php
+++ b/framework/console/Controller.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * Controller class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
@@ -11,7 +9,7 @@ namespace yii\console;
 
 use Yii;
 use yii\base\Action;
-use yii\base\InvalidRequestException;
+use yii\base\InlineAction;
 use yii\base\InvalidRouteException;
 
 /**
@@ -49,14 +47,11 @@ class Controller extends \yii\base\Controller
 	public function runAction($id, $params = array())
 	{
 		if ($params !== array()) {
-			$class = new \ReflectionClass($this);
+			$options = $this->globalOptions();
 			foreach ($params as $name => $value) {
-				if ($class->hasProperty($name)) {
-					$property = $class->getProperty($name);
-					if ($property->isPublic() && !$property->isStatic() && $property->getDeclaringClass()->getName() === get_class($this)) {
-						$this->$name = $value;
-						unset($params[$name]);
-					}
+				if (in_array($name, $options, true)) {
+					$this->$name = $value;
+					unset($params[$name]);
 				}
 			}
 		}
@@ -64,25 +59,60 @@ class Controller extends \yii\base\Controller
 	}
 
 	/**
-	 * Validates the parameter being bound to actions.
-	 * This method is invoked when parameters are being bound to the currently requested action.
-	 * Child classes may override this method to throw exceptions when there are missing and/or unknown parameters.
-	 * @param Action $action the currently requested action
-	 * @param array $missingParams the names of the missing parameters
-	 * @param array $unknownParams the unknown parameters (name=>value)
-	 * @throws InvalidRequestException if there are missing or unknown parameters
+	 * Binds the parameters to the action.
+	 * This method is invoked by [[Action]] when it begins to run with the given parameters.
+	 * This method will first bind the parameters with the [[globalOptions()|global options]]
+	 * available to the action. It then validates the given arguments.
+	 * @param Action $action the action to be bound with parameters
+	 * @param array $params the parameters to be bound to the action
+	 * @return array the valid parameters that the action can run with.
+	 * @throws Exception if there are unknown options or missing arguments
 	 */
-	public function validateActionParams($action, $missingParams, $unknownParams)
+	public function bindActionParams($action, $params)
 	{
-		if (!empty($missingParams)) {
-			throw new InvalidRequestException(Yii::t('yii', 'Missing required options: {params}', array(
-				'{params}' => implode(', ', $missingParams),
+		if ($params !== array()) {
+			$options = $this->globalOptions();
+			foreach ($params as $name => $value) {
+				if (in_array($name, $options, true)) {
+					$this->$name = $value;
+					unset($params[$name]);
+				}
+			}
+		}
+
+		$args = isset($params[Request::ANONYMOUS_PARAMS]) ? $params[Request::ANONYMOUS_PARAMS] : array();
+		unset($params[Request::ANONYMOUS_PARAMS]);
+		if ($params !== array()) {
+			throw new Exception(Yii::t('yii|Unknown options: {params}', array(
+				'{params}' => implode(', ', array_keys($params)),
 			)));
-		} elseif (!empty($unknownParams)) {
-			throw new InvalidRequestException(Yii::t('yii', 'Unknown options: {params}', array(
-				'{params}' => implode(', ', $unknownParams),
+		}
+
+		if ($action instanceof InlineAction) {
+			$method = new \ReflectionMethod($this, $action->actionMethod);
+		} else {
+			$method = new \ReflectionMethod($action, 'run');
+		}
+
+		$missing = array();
+		foreach ($method->getParameters() as $i => $param) {
+			$name = $param->getName();
+			if (!isset($args[$i])) {
+				if ($param->isDefaultValueAvailable()) {
+					$args[$i] = $param->getDefaultValue();
+				} else {
+					$missing[] = $name;
+				}
+			}
+		}
+
+		if ($missing !== array()) {
+			throw new Exception(Yii::t('yii|Missing required arguments: {params}', array(
+				'{params}' => implode(', ', $missing),
 			)));
 		}
+
+		return $args;
 	}
 
 	/**
@@ -103,12 +133,17 @@ class Controller extends \yii\base\Controller
 		}
 	}
 
-	public function usageError($message)
-	{
-		echo "\nError: $message\n";
-		Yii::$application->end(1);
-	}
-
+	/**
+	 * Returns the names of the global options for this command.
+	 * A global option requires the existence of a public member variable whose
+	 * name is the option name.
+	 * Child classes may override this method to specify possible global options.
+	 *
+	 * Note that the values setting via global options are not available
+	 * until [[beforeAction()]] is being called.
+	 *
+	 * @return array the names of the global options for this command.
+	 */
 	public function globalOptions()
 	{
 		return array();
diff --git a/framework/console/Exception.php b/framework/console/Exception.php
new file mode 100644
index 0000000..cb10c19
--- /dev/null
+++ b/framework/console/Exception.php
@@ -0,0 +1,28 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\console;
+
+use yii\base\UserException;
+
+/**
+ * Exception represents an exception caused by incorrect usage of a console command.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class Exception extends UserException
+{
+	/**
+	 * @return string the user-friendly name of this exception
+	 */
+	public function getName()
+	{
+		return \Yii::t('yii|Error');
+	}
+}
+
diff --git a/framework/console/Request.php b/framework/console/Request.php
index dbf80ba..ed477e9 100644
--- a/framework/console/Request.php
+++ b/framework/console/Request.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * Request class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
@@ -15,49 +13,39 @@ namespace yii\console;
  */
 class Request extends \yii\base\Request
 {
-	/**
-	 * @var string the controller route specified by this request. If this is an empty string,
-	 * it means the [[Application::defaultRoute|default route]] will be used.
-	 * Note that the value of this property may not be a correct route. The console application
-	 * will determine it is valid or not when it attempts to execute with this route.
-	 */
-	public $route;
-	/**
-	 * @var array
-	 */
-	public $params;
-
-	public function init()
-	{
-		parent::init();
-		$this->resolveRequest();
-	}
+	const ANONYMOUS_PARAMS = '-args';
 
 	public function getRawParams()
 	{
 		return isset($_SERVER['argv']) ? $_SERVER['argv'] : array();
 	}
 
-	protected function resolveRequest()
+	/**
+	 * Resolves the current request into a route and the associated parameters.
+	 * @return array the first element is the route, and the second is the associated parameters.
+	 */
+	public function resolve()
 	{
 		$rawParams = $this->getRawParams();
 		array_shift($rawParams);  // the 1st argument is the yiic script name
 
 		if (isset($rawParams[0])) {
-			$this->route = $rawParams[0];
+			$route = $rawParams[0];
 			array_shift($rawParams);
 		} else {
-			$this->route = '';
+			$route = '';
 		}
 
-		$this->params = array();
+		$params = array(self::ANONYMOUS_PARAMS => array());
 		foreach ($rawParams as $param) {
 			if (preg_match('/^--(\w+)(=(.*))?$/', $param, $matches)) {
 				$name = $matches[1];
-				$this->params[$name] = isset($matches[3]) ? $matches[3] : true;
+				$params[$name] = isset($matches[3]) ? $matches[3] : true;
 			} else {
-				$this->params['args'][] = $param;
+				$params[self::ANONYMOUS_PARAMS][] = $param;
 			}
 		}
+
+		return array($route, $params);
 	}
 }
diff --git a/framework/console/controllers/CreateController.php b/framework/console/controllers/AppController.php
similarity index 90%
rename from framework/console/controllers/CreateController.php
rename to framework/console/controllers/AppController.php
index 7bd7fd0..93ef5f5 100644
--- a/framework/console/controllers/CreateController.php
+++ b/framework/console/controllers/AppController.php
@@ -1,16 +1,14 @@
 <?php
 /**
- * CreateController class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
 namespace yii\console\controllers;
 
 use yii\console\Controller;
-use yii\util\FileHelper;
+use yii\helpers\FileHelper;
 use yii\base\Exception;
 
 /**
@@ -20,14 +18,14 @@ use yii\base\Exception;
  * @author Alexander Makarov <sam@rmcreative.ru>
  * @since 2.0
  */
-class CreateController extends Controller
+class AppController extends Controller
 {
 	private $_rootPath;
 	private $_config;
 
 	/**
 	 * @var string custom template path. If specified, templates will be
-	 * searched there additionally to `framework/console/create`.
+	 * searched there additionally to `framework/console/webapp`.
 	 */
 	public $templatesPath;
 
@@ -46,6 +44,16 @@ class CreateController extends Controller
 		}
 	}
 
+	public function globalOptions()
+	{
+		return array('templatesPath', 'type');
+	}
+
+	public function actionIndex()
+	{
+		$this->forward('help/index', array('-args' => array('app/create')));
+	}
+
 	/**
 	 * Generates Yii application at the path specified via appPath parameter.
 	 *
@@ -56,7 +64,7 @@ class CreateController extends Controller
 	 * @throws \yii\base\Exception if path specified is not valid
 	 * @return integer the exit status
 	 */
-	public function actionIndex($path)
+	public function actionCreate($path)
 	{
 		$path = strtr($path, '/\\', DIRECTORY_SEPARATOR);
 		if(strpos($path, DIRECTORY_SEPARATOR) === false) {
@@ -127,7 +135,7 @@ class CreateController extends Controller
 	 */
 	protected function getDefaultTemplatesPath()
 	{
-		return realpath(__DIR__.'/../create');
+		return realpath(__DIR__.'/../webapp');
 	}
 
 	/**
diff --git a/framework/console/controllers/CacheController.php b/framework/console/controllers/CacheController.php
new file mode 100644
index 0000000..6765f9b
--- /dev/null
+++ b/framework/console/controllers/CacheController.php
@@ -0,0 +1,47 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\console\controllers;
+
+use yii\console\Controller;
+use yii\console\Exception;
+use yii\caching\Cache;
+
+/**
+ * This command allows you to flush cache.
+ *
+ * @author Alexander Makarov <sam@rmcreative.ru>
+ * @since 2.0
+ */
+class CacheController extends Controller
+{
+	public function actionIndex()
+	{
+		$this->forward('help/index', array('-args' => array('cache/flush')));
+	}
+
+	/**
+	 * Flushes cache.
+	 * @param string $component Name of the cache application component to use.
+	 *
+	 * @throws \yii\console\Exception
+	 */
+	public function actionFlush($component = 'cache')
+	{
+		/** @var $cache Cache */
+		$cache = \Yii::$app->getComponent($component);
+		if(!$cache || !$cache instanceof Cache) {
+			throw new Exception('Application component "'.$component.'" is not defined or not a cache.');
+		}
+
+		if(!$cache->flush()) {
+			throw new Exception('Unable to flush cache.');
+		}
+
+		echo "\nDone.\n";
+	}
+}
diff --git a/framework/console/controllers/HelpController.php b/framework/console/controllers/HelpController.php
index 6e4b397..ea7e3d5 100644
--- a/framework/console/controllers/HelpController.php
+++ b/framework/console/controllers/HelpController.php
@@ -1,18 +1,19 @@
 <?php
 /**
- * HelpController class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
 namespace yii\console\controllers;
 
+use Yii;
 use yii\base\Application;
+use yii\console\Exception;
 use yii\base\InlineAction;
 use yii\console\Controller;
-use yii\util\StringHelper;
+use yii\console\Request;
+use yii\helpers\StringHelper;
 
 /**
  * This command provides help information about console commands.
@@ -44,30 +45,32 @@ class HelpController extends Controller
 	 * yiic help message  # display help info about "message"
 	 * ~~~
 	 *
-	 * @param array $args additional anonymous command line arguments.
-	 * You may provide a command name to display its detailed information.
+	 * @param string $command The name of the command to show help about.
+	 * If not provided, all available commands will be displayed.
 	 * @return integer the exit status
+	 * @throws Exception if the command for help is unknown
 	 */
-	public function actionIndex($args = array())
+	public function actionIndex($command = null)
 	{
-		if (empty($args)) {
-			$status = $this->getHelp();
-		} else {
-			$result = \Yii::$application->createController($args[0]);
+		if ($command !== null) {
+			$result = Yii::$app->createController($command);
 			if ($result === false) {
-				echo "Error: no help for unknown command \"{$args[0]}\".\n";
-				return 1;
+				throw new Exception(Yii::t('yii|No help for unknown command "{command}".', array(
+					'{command}' => $command,
+				)));
 			}
 
 			list($controller, $actionID) = $result;
 
-			if ($actionID === '') {
-				$status = $this->getControllerHelp($controller);
+			$actions = $this->getActions($controller);
+			if ($actionID !== '' || count($actions) === 1 && $actions[0] === $controller->defaultAction) {
+				$this->getActionHelp($controller, $actionID);
 			} else {
-				$status = $this->getActionHelp($controller, $actionID);
+				$this->getControllerHelp($controller);
 			}
+		} else {
+			$this->getHelp();
 		}
-		return $status;
 	}
 
 	/**
@@ -76,7 +79,7 @@ class HelpController extends Controller
 	 */
 	public function getCommands()
 	{
-		$commands = $this->getModuleCommands(\Yii::$application);
+		$commands = $this->getModuleCommands(Yii::$app);
 		sort($commands);
 		return array_unique($commands);
 	}
@@ -91,7 +94,6 @@ class HelpController extends Controller
 		$actions = array_keys($controller->actions());
 		$class = new \ReflectionClass($controller);
 		foreach ($class->getMethods() as $method) {
-			/** @var $method \ReflectionMethod */
 			$name = $method->getName();
 			if ($method->isPublic() && !$method->isStatic() && strpos($name, 'action') === 0 && $name !== 'actions') {
 				$actions[] = StringHelper::camel2id(substr($name, 6));
@@ -136,29 +138,25 @@ class HelpController extends Controller
 
 	/**
 	 * Displays all available commands.
-	 * @return integer the exit status
 	 */
 	protected function getHelp()
 	{
 		$commands = $this->getCommands();
 		if ($commands !== array()) {
-			echo "\nUsage: yiic <command-name> [...options...]\n\n";
 			echo "The following commands are available:\n\n";
 			foreach ($commands as $command) {
-				echo " * $command\n";
+				echo "* $command\n";
 			}
 			echo "\nTo see the help of each command, enter:\n";
-			echo "\n    yiic help <command-name>\n";
+			echo "\n  yiic help <command-name>\n\n";
 		} else {
 			echo "\nNo commands are found.\n";
 		}
-		return 0;
 	}
 
 	/**
 	 * Displays the overall information of the command.
 	 * @param Controller $controller the controller instance
-	 * @return integer the exit status
 	 */
 	protected function getControllerHelp($controller)
 	{
@@ -169,181 +167,255 @@ class HelpController extends Controller
 		}
 
 		if ($comment !== '') {
-			echo "\n" . $comment . "\n";
-		}
-
-		$options = $this->getGlobalOptions($class, $controller);
-		if ($options !== array()) {
-			echo "\nGLOBAL OPTIONS";
-			echo "\n--------------\n\n";
-			foreach ($options as $name => $description) {
-				echo " --$name";
-				if ($description != '') {
-					echo ": $description\n";
-				}
-				echo "\n";
-			}
+			echo "\nDESCRIPTION\n";
+			echo "\n" . $comment . "\n\n";
 		}
 
 		$actions = $this->getActions($controller);
 		if ($actions !== array()) {
-			echo "\nSUB-COMMANDS";
-			echo "\n------------\n\n";
+			echo "\nSUB-COMMANDS\n\n";
 			$prefix = $controller->getUniqueId();
 			foreach ($actions as $action) {
-				if ($controller->defaultAction === $action) {
-					echo " * $prefix (default)\n";
+				if ($action === $controller->defaultAction) {
+					echo "* $prefix/$action (default)";
 				} else {
-					echo " * $prefix/$action\n";
+					echo "* $prefix/$action";
 				}
+				$summary = $this->getActionSummary($controller, $action);
+				if ($summary !== '') {
+					echo ': ' . $summary;
+				}
+				echo "\n";
 			}
-			echo "\n";
+			echo "\n\nTo see the detailed information about individual sub-commands, enter:\n";
+			echo "\n  yiic help <sub-command>\n\n";
 		}
+	}
 
-		return 0;
+	/**
+	 * Returns the short summary of the action.
+	 * @param Controller $controller the controller instance
+	 * @param string $actionID action ID
+	 * @return string the summary about the action
+	 */
+	protected function getActionSummary($controller, $actionID)
+	{
+		$action = $controller->createAction($actionID);
+		if ($action === null) {
+			return '';
+		}
+		if ($action instanceof InlineAction) {
+			$reflection = new \ReflectionMethod($controller, $action->actionMethod);
+		} else {
+			$reflection = new \ReflectionClass($action);
+		}
+		$tags = $this->parseComment($reflection->getDocComment());
+		if ($tags['description'] !== '') {
+			$limit = 73 - strlen($action->getUniqueId());
+			if ($actionID === $controller->defaultAction) {
+				$limit -= 10;
+			}
+			if ($limit < 0) {
+				$limit = 50;
+			}
+			$description = $tags['description'];
+			if (($pos = strpos($tags['description'], "\n")) !== false) {
+				$description = substr($description, 0, $pos);
+			}
+			$text = substr($description, 0, $limit);
+			return strlen($description) > $limit ? $text . '...' : $text;
+		} else {
+			return '';
+		}
 	}
 
 	/**
 	 * Displays the detailed information of a command action.
 	 * @param Controller $controller the controller instance
 	 * @param string $actionID action ID
-	 * @return integer the exit status
+	 * @throws Exception if the action does not exist
 	 */
 	protected function getActionHelp($controller, $actionID)
 	{
 		$action = $controller->createAction($actionID);
 		if ($action === null) {
-			echo 'Error: no help for unknown sub-command "' . $controller->getUniqueId() . "/$actionID\".\n";
-			return 1;
+			throw new Exception(Yii::t('yii|No help for unknown sub-command "{command}".', array(
+				'{command}' => rtrim($controller->getUniqueId() . '/' . $actionID, '/'),
+			)));
 		}
 		if ($action instanceof InlineAction) {
-			$method = new \ReflectionMethod($controller, 'action' . $action->id);
+			$method = new \ReflectionMethod($controller, $action->actionMethod);
 		} else {
 			$method = new \ReflectionMethod($action, 'run');
 		}
-		$comment = strtr(trim(preg_replace('/^\s*\**( |\t)?/m', '', trim($method->getDocComment(), '/'))), "\r", '');
-		if (preg_match('/^\s*@\w+/m', $comment, $matches, PREG_OFFSET_CAPTURE)) {
-			$meta = substr($comment, $matches[0][1]);
-			$comment = trim(substr($comment, 0, $matches[0][1]));
+
+		$tags = $this->parseComment($method->getDocComment());
+		$options = $this->getOptionHelps($controller);
+
+		if ($tags['description'] !== '') {
+			echo "\nDESCRIPTION";
+			echo "\n\n" . $tags['description'] . "\n\n";
+		}
+
+		echo "\nUSAGE\n\n";
+		if ($action->id === $controller->defaultAction) {
+			echo 'yiic ' . $controller->getUniqueId();
 		} else {
-			$meta = '';
+			echo "yiic " . $action->getUniqueId();
+		}
+		list ($required, $optional) = $this->getArgHelps($method, isset($tags['param']) ? $tags['param'] : array());
+		if (!empty($required)) {
+			echo ' <' . implode('> <', array_keys($required)) . '>';
+		}
+		if (!empty($optional)) {
+			echo ' [' . implode('] [', array_keys($optional)) . ']';
 		}
+		if (!empty($options)) {
+			echo ' [...options...]';
+		}
+		echo "\n\n";
 
-		if ($comment !== '') {
-			echo "\n" . $comment . "\n";
+		if (!empty($required) || !empty($optional)) {
+			echo implode("\n\n", array_merge($required, $optional)) . "\n\n";
 		}
 
-		$options = $this->getOptions($method, $meta);
+		$options = $this->getOptionHelps($controller);
 		if ($options !== array()) {
-			echo "\nOPTIONS";
-			echo "\n-------\n\n";
-			foreach ($options as $name => $description) {
-				echo " --$name";
-				if ($description != '') {
-					echo ": $description\n";
-				}
-			}
-			echo "\n";
+			echo "\nOPTIONS\n\n";
+			echo implode("\n\n", $options) . "\n\n";
 		}
-
-		return 0;
 	}
 
 	/**
+	 * Returns the help information about arguments.
 	 * @param \ReflectionMethod $method
-	 * @param string $meta
-	 * @return array
+	 * @param string $tags the parsed comment block related with arguments
+	 * @return array the required and optional argument help information
 	 */
-	protected function getOptions($method, $meta)
+	protected function getArgHelps($method, $tags)
 	{
+		if (is_string($tags)) {
+			$tags = array($tags);
+		}
 		$params = $method->getParameters();
-		$tags = preg_split('/^\s*@/m', $meta, -1, PREG_SPLIT_NO_EMPTY);
-		$options = array();
-		$count = 0;
-		foreach ($tags as $tag) {
-			$parts = preg_split('/\s+/', trim($tag), 2);
-			if ($parts[0] === 'param' && isset($params[$count])) {
-				$param = $params[$count];
-				$comment = isset($parts[1]) ? $parts[1] : '';
-				if (preg_match('/^([^\s]+)\s+(\$\w+\s+)?(.*)/s', $comment, $matches)) {
-					$type = $matches[1];
-					$doc = $matches[3];
-				} else {
-					$type = $comment;
-					$doc = '';
-				}
-				$comment = $type === '' ? '' : ($type . ', ');
-				if ($param->isDefaultValueAvailable()) {
-					$value = $param->getDefaultValue();
-					if (!is_array($value)) {
-						$comment .= 'optional (defaults to ' . var_export($value, true) . ').';
-					} else {
-						$comment .= 'optional.';
-					}
-				} else {
-					$comment .= 'required.';
-				}
-				if (trim($doc) !== '') {
-					$comment .= "\n" . preg_replace("/^/m", "     ", $doc);
-				}
-				$options[$param->getName()] = $comment;
-				$count++;
+		$optional = $required = array();
+		foreach ($params as $i => $param) {
+			$name = $param->getName();
+			$tag = isset($tags[$i]) ? $tags[$i] : '';
+			if (preg_match('/^([^\s]+)\s+(\$\w+\s+)?(.*)/s', $tag, $matches)) {
+				$type = $matches[1];
+				$comment = $matches[3];
+			} else {
+				$type = null;
+				$comment = $tag;
 			}
-		}
-		if ($count < count($params)) {
-			for ($i = $count; $i < count($params); ++$i) {
-				$options[$params[$i]->getName()] = '';
+			if ($param->isDefaultValueAvailable()) {
+				$optional[$name] = $this->formatOptionHelp('* ' . $name, false, $type, $param->getDefaultValue(), $comment);
+			} else {
+				$required[$name] = $this->formatOptionHelp('* ' . $name, true, $type, null, $comment);
 			}
 		}
 
-		ksort($options);
-		return $options;
+		return array($required, $optional);
 	}
 
 	/**
-	 * @param \ReflectionClass $class
-	 * @param Controller $controller
-	 * @return array
+	 * Returns the help information about the options available for a console controller.
+	 * @param Controller $controller the console controller
+	 * @return array the help information about the options
 	 */
-	protected function getGlobalOptions($class, $controller)
+	protected function getOptionHelps($controller)
 	{
+		$optionNames = $controller->globalOptions();
+		if (empty($optionNames)) {
+			return array();
+		}
+
+		$class = new \ReflectionClass($controller);
 		$options = array();
 		foreach ($class->getProperties() as $property) {
-			if (!$property->isPublic() || $property->isStatic() || $property->getDeclaringClass()->getName() !== get_class($controller)) {
+			$name = $property->getName();
+			if (!in_array($name, $optionNames, true)) {
 				continue;
 			}
-			$name = $property->getName();
-			$comment = strtr(trim(preg_replace('/^\s*\**( |\t)?/m', '', trim($property->getDocComment(), '/'))), "\r", '');
-			if (preg_match('/^\s*@\w+/m', $comment, $matches, PREG_OFFSET_CAPTURE)) {
-				$meta = substr($comment, $matches[0][1]);
+			$defaultValue = $property->getValue($controller);
+			$tags = $this->parseComment($property->getDocComment());
+			if (isset($tags['var']) || isset($tags['property'])) {
+				$doc = isset($tags['var']) ? $tags['var'] : $tags['property'];
+				if (is_array($doc)) {
+					$doc = reset($doc);
+				}
+				if (preg_match('/^([^\s]+)(.*)/s', $doc, $matches)) {
+					$type = $matches[1];
+					$comment = $matches[2];
+				} else {
+					$type = null;
+					$comment = $doc;
+				}
+				$options[$name] = $this->formatOptionHelp('--' . $name, false, $type, $defaultValue, $comment);
 			} else {
-				$meta = '';
+				$options[$name] = $this->formatOptionHelp('--' . $name, false, null, $defaultValue, '');
 			}
-			$tags = preg_split('/^\s*@/m', $meta, -1, PREG_SPLIT_NO_EMPTY);
-			foreach ($tags as $tag) {
-				$parts = preg_split('/\s+/', trim($tag), 2);
-				$comment = isset($parts[1]) ? $parts[1] : '';
-				if ($parts[0] === 'var' || $parts[0] === 'property') {
-					if (preg_match('/^([^\s]+)(\s+.*)?/s', $comment, $matches)) {
-						$type = $matches[1];
-						$doc = trim($matches[2]);
-					} else {
-						$type = $comment;
-						$doc = '';
-					}
-					$comment = $type === '' ? '' : ($type);
-					if (trim($doc) !== '') {
-						$comment .= ', ' . preg_replace("/^/m", "", $doc);
-					}
-					$options[$name] = $comment;
-					break;
+		}
+		ksort($options);
+		return $options;
+	}
+
+	/**
+	 * Parses the comment block into tags.
+	 * @param string $comment the comment block
+	 * @return array the parsed tags
+	 */
+	protected function parseComment($comment)
+	{
+		$tags = array();
+		$comment = "@description \n" . strtr(trim(preg_replace('/^\s*\**( |\t)?/m', '', trim($comment, '/'))), "\r", '');
+		$parts = preg_split('/^\s*@/m', $comment, -1, PREG_SPLIT_NO_EMPTY);
+		foreach ($parts as $part) {
+			if (preg_match('/^(\w+)(.*)/ms', trim($part), $matches)) {
+				$name = $matches[1];
+				if (!isset($tags[$name])) {
+					$tags[$name] = trim($matches[2]);
+				} elseif (is_array($tags[$name])) {
+					$tags[$name][] = trim($matches[2]);
+				} else {
+					$tags[$name] = array($tags[$name], trim($matches[2]));
 				}
 			}
-			if (!isset($options[$name])) {
-				$options[$name] = '';
+		}
+		return $tags;
+	}
+
+	/**
+	 * Generates a well-formed string for an argument or option.
+	 * @param string $name the name of the argument or option
+	 * @param boolean $required whether the argument is required
+	 * @param string $type the type of the option or argument
+	 * @param mixed $defaultValue the default value of the option or argument
+	 * @param string $comment comment about the option or argument
+	 * @return string the formatted string for the argument or option
+	 */
+	protected function formatOptionHelp($name, $required, $type, $defaultValue, $comment)
+	{
+		$doc = '';
+		$comment = trim($comment);
+
+		if ($defaultValue !== null && !is_array($defaultValue)) {
+			if ($type === null) {
+				$type = gettype($defaultValue);
 			}
+			$doc = "$type (defaults to " . var_export($defaultValue, true) . ")";
+		} elseif (trim($type) !== '') {
+			$doc = $type;
 		}
-		ksort($options);
-		return $options;
+
+		if ($doc === '') {
+			$doc = $comment;
+		} elseif ($comment !== '') {
+			$doc .= "\n" . preg_replace("/^/m", "  ", $comment);
+		}
+
+		$name = $required ? "$name (required)" : $name;
+		return $doc === '' ? $name : "$name: $doc";
 	}
 }
\ No newline at end of file
diff --git a/framework/console/controllers/MessageController.php b/framework/console/controllers/MessageController.php
index 2e8ec81..e010b55 100644
--- a/framework/console/controllers/MessageController.php
+++ b/framework/console/controllers/MessageController.php
@@ -1,10 +1,8 @@
 <?php
 /**
- * MessageController class file.
- *
  * @author Qiang Xue <qiang.xue@gmail.com>
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
diff --git a/framework/console/controllers/MigrateController.php b/framework/console/controllers/MigrateController.php
index e104856..3f816f1 100644
--- a/framework/console/controllers/MigrateController.php
+++ b/framework/console/controllers/MigrateController.php
@@ -1,539 +1,630 @@
-<?php
-/**
- * MigrateController class file.
- *
- * @author Qiang Xue <qiang.xue@gmail.com>
- * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
- * @license http://www.yiiframework.com/license/
- */
-
-namespace yii\console\controllers;
-
-use Yii;
-use yii\console\Controller;
-
-/**
- * This command provides support for database migrations.
- *
- * The implementation of this command and other supporting classes referenced
- * the yii-dbmigrations extension ((https://github.com/pieterclaerhout/yii-dbmigrations),
- * authored by Pieter Claerhout.
- *
- * EXAMPLES
- *
- * - yiic migrate
- *   Applies ALL new migrations. This is equivalent to 'yiic migrate up'.
- *
- * - yiic migrate create create_user_table
- *    Creates a new migration named 'create_user_table'.
- *
- * - yiic migrate up 3
- *   Applies the next 3 new migrations.
- *
- * - yiic migrate down
- *   Reverts the last applied migration.
- *
- * - yiic migrate down 3
- *   Reverts the last 3 applied migrations.
- *
- * - yiic migrate to 101129_185401
- *   Migrates up or down to version 101129_185401.
- *
- * - yiic migrate mark 101129_185401
- *   Modifies the migration history up or down to version 101129_185401.
- *   No actual migration will be performed.
- *
- * - yiic migrate history
- * Shows all previously applied migration information.
- *
- * - yiic migrate history 10
- * Shows the last 10 applied migrations.
- *
- * - yiic migrate new
- * Shows all new migrations.
- *
- * - yiic migrate new 10
- * Shows the next 10 migrations that have not been applied.
- *
- * @author Qiang Xue <qiang.xue@gmail.com>
- * @since 2.0
- */
-class MigrateController extends Controller
-{
-	const BASE_MIGRATION = 'm000000_000000_base';
-
-	/**
-	 * @var string the directory that stores the migrations. This must be specified
-	 * in terms of a path alias, and the corresponding directory must exist.
-	 * Defaults to 'application.migrations' (meaning 'protected/migrations').
-	 */
-	public $migrationPath = '@application/migrations';
-	/**
-	 * @var string the name of the table for keeping applied migration information.
-	 * This table will be automatically created if not exists. Defaults to 'tbl_migration'.
-	 * The table structure is: (version varchar(255) primary key, apply_time integer)
-	 */
-	public $migrationTable = 'tbl_migration';
-	/**
-	 * @var string the application component ID that specifies the database connection for
-	 * storing migration information. Defaults to 'db'.
-	 */
-	public $connectionID = 'db';
-	/**
-	 * @var string the path of the template file for generating new migrations. This
-	 * must be specified in terms of a path alias (e.g. application.migrations.template).
-	 * If not set, an internal template will be used.
-	 */
-	public $templateFile;
-	/**
-	 * @var string the default command action. It defaults to 'up'.
-	 */
-	public $defaultAction = 'up';
-	/**
-	 * @var boolean whether to execute the migration in an interactive mode. Defaults to true.
-	 * Set this to false when performing migration in a cron job or background process.
-	 */
-	public $interactive = true;
-
-
-	public function beforeAction($action)
-	{
-		if (parent::beforeAction($action)) {
-			$path = Yii::getAlias($this->migrationPath);
-			if ($path === false || !is_dir($path)) {
-				echo 'Error: the migration directory does not exist "' . $this->migrationPath . "\"\n";
-				return false;
-			}
-			$this->migrationPath = $path;
-			$version = Yii::getVersion();
-			echo "\nYii Migration Tool v2.0 (based on Yii v{$version})\n\n";
-			return true;
-		} else {
-			return false;
-		}
-	}
-
-	/**
-	 * @param array $args
-	 */
-	public function actionUp($args)
-	{
-		if (($migrations = $this->getNewMigrations()) === array()) {
-			echo "No new migration found. Your system is up-to-date.\n";
-			Yii::$application->end();
-		}
-
-		$total = count($migrations);
-		$step = isset($args[0]) ? (int)$args[0] : 0;
-		if ($step > 0) {
-			$migrations = array_slice($migrations, 0, $step);
-		}
-
-		$n = count($migrations);
-		if ($n === $total) {
-			echo "Total $n new " . ($n === 1 ? 'migration' : 'migrations') . " to be applied:\n";
-		} else {
-			echo "Total $n out of $total new " . ($total === 1 ? 'migration' : 'migrations') . " to be applied:\n";
-		}
-
-		foreach ($migrations as $migration) {
-			echo "    $migration\n";
-		}
-		echo "\n";
-
-		if ($this->confirm('Apply the above ' . ($n === 1 ? 'migration' : 'migrations') . "?")) {
-			foreach ($migrations as $migration) {
-				if ($this->migrateUp($migration) === false) {
-					echo "\nMigration failed. All later migrations are canceled.\n";
-					return;
-				}
-			}
-			echo "\nMigrated up successfully.\n";
-		}
-	}
-
-	public function actionDown($args)
-	{
-		$step = isset($args[0]) ? (int)$args[0] : 1;
-		if ($step < 1) {
-			die("Error: The step parameter must be greater than 0.\n");
-		}
-
-		if (($migrations = $this->getMigrationHistory($step)) === array()) {
-			echo "No migration has been done before.\n";
-			return;
-		}
-		$migrations = array_keys($migrations);
-
-		$n = count($migrations);
-		echo "Total $n " . ($n === 1 ? 'migration' : 'migrations') . " to be reverted:\n";
-		foreach ($migrations as $migration) {
-			echo "    $migration\n";
-		}
-		echo "\n";
-
-		if ($this->confirm('Revert the above ' . ($n === 1 ? 'migration' : 'migrations') . "?")) {
-			foreach ($migrations as $migration) {
-				if ($this->migrateDown($migration) === false) {
-					echo "\nMigration failed. All later migrations are canceled.\n";
-					return;
-				}
-			}
-			echo "\nMigrated down successfully.\n";
-		}
-	}
-
-	public function actionRedo($args)
-	{
-		$step = isset($args[0]) ? (int)$args[0] : 1;
-		if ($step < 1) {
-			die("Error: The step parameter must be greater than 0.\n");
-		}
-
-		if (($migrations = $this->getMigrationHistory($step)) === array()) {
-			echo "No migration has been done before.\n";
-			return;
-		}
-		$migrations = array_keys($migrations);
-
-		$n = count($migrations);
-		echo "Total $n " . ($n === 1 ? 'migration' : 'migrations') . " to be redone:\n";
-		foreach ($migrations as $migration) {
-			echo "    $migration\n";
-		}
-		echo "\n";
-
-		if ($this->confirm('Redo the above ' . ($n === 1 ? 'migration' : 'migrations') . "?")) {
-			foreach ($migrations as $migration) {
-				if ($this->migrateDown($migration) === false) {
-					echo "\nMigration failed. All later migrations are canceled.\n";
-					return;
-				}
-			}
-			foreach (array_reverse($migrations) as $migration) {
-				if ($this->migrateUp($migration) === false) {
-					echo "\nMigration failed. All later migrations are canceled.\n";
-					return;
-				}
-			}
-			echo "\nMigration redone successfully.\n";
-		}
-	}
-
-	public function actionTo($args)
-	{
-		if (isset($args[0])) {
-			$version = $args[0];
-		} else {
-			$this->usageError('Please specify which version to migrate to.');
-		}
-
-		$originalVersion = $version;
-		if (preg_match('/^m?(\d{6}_\d{6})(_.*?)?$/', $version, $matches)) {
-			$version = 'm' . $matches[1];
-		} else {
-			die("Error: The version option must be either a timestamp (e.g. 101129_185401)\nor the full name of a migration (e.g. m101129_185401_create_user_table).\n");
-		}
-
-		// try migrate up
-		$migrations = $this->getNewMigrations();
-		foreach ($migrations as $i => $migration) {
-			if (strpos($migration, $version . '_') === 0) {
-				$this->actionUp(array($i + 1));
-				return;
-			}
-		}
-
-		// try migrate down
-		$migrations = array_keys($this->getMigrationHistory(-1));
-		foreach ($migrations as $i => $migration) {
-			if (strpos($migration, $version . '_') === 0) {
-				if ($i === 0) {
-					echo "Already at '$originalVersion'. Nothing needs to be done.\n";
-				} else {
-					$this->actionDown(array($i));
-				}
-				return;
-			}
-		}
-
-		die("Error: Unable to find the version '$originalVersion'.\n");
-	}
-
-	public function actionMark($args)
-	{
-		if (isset($args[0])) {
-			$version = $args[0];
-		} else {
-			$this->usageError('Please specify which version to mark to.');
-		}
-		$originalVersion = $version;
-		if (preg_match('/^m?(\d{6}_\d{6})(_.*?)?$/', $version, $matches)) {
-			$version = 'm' . $matches[1];
-		} else {
-			die("Error: The version option must be either a timestamp (e.g. 101129_185401)\nor the full name of a migration (e.g. m101129_185401_create_user_table).\n");
-		}
-
-		$db = $this->getDb();
-
-		// try mark up
-		$migrations = $this->getNewMigrations();
-		foreach ($migrations as $i => $migration) {
-			if (strpos($migration, $version . '_') === 0) {
-				if ($this->confirm("Set migration history at $originalVersion?")) {
-					$command = $db->createCommand();
-					for ($j = 0; $j <= $i; ++$j) {
-						$command->insert($this->migrationTable, array(
-							'version' => $migrations[$j],
-							'apply_time' => time(),
-						));
-					}
-					echo "The migration history is set at $originalVersion.\nNo actual migration was performed.\n";
-				}
-				return;
-			}
-		}
-
-		// try mark down
-		$migrations = array_keys($this->getMigrationHistory(-1));
-		foreach ($migrations as $i => $migration) {
-			if (strpos($migration, $version . '_') === 0) {
-				if ($i === 0) {
-					echo "Already at '$originalVersion'. Nothing needs to be done.\n";
-				} else {
-					if ($this->confirm("Set migration history at $originalVersion?")) {
-						$command = $db->createCommand();
-						for ($j = 0; $j < $i; ++$j) {
-							$command->delete($this->migrationTable, $db->quoteColumnName('version') . '=:version', array(':version' => $migrations[$j]));
-						}
-						echo "The migration history is set at $originalVersion.\nNo actual migration was performed.\n";
-					}
-				}
-				return;
-			}
-		}
-
-		die("Error: Unable to find the version '$originalVersion'.\n");
-	}
-
-	public function actionHistory($args)
-	{
-		$limit = isset($args[0]) ? (int)$args[0] : -1;
-		$migrations = $this->getMigrationHistory($limit);
-		if ($migrations === array()) {
-			echo "No migration has been done before.\n";
-		} else {
-			$n = count($migrations);
-			if ($limit > 0) {
-				echo "Showing the last $n applied " . ($n === 1 ? 'migration' : 'migrations') . ":\n";
-			} else {
-				echo "Total $n " . ($n === 1 ? 'migration has' : 'migrations have') . " been applied before:\n";
-			}
-			foreach ($migrations as $version => $time) {
-				echo "    (" . date('Y-m-d H:i:s', $time) . ') ' . $version . "\n";
-			}
-		}
-	}
-
-	public function actionNew($args)
-	{
-		$limit = isset($args[0]) ? (int)$args[0] : -1;
-		$migrations = $this->getNewMigrations();
-		if ($migrations === array()) {
-			echo "No new migrations found. Your system is up-to-date.\n";
-		} else {
-			$n = count($migrations);
-			if ($limit > 0 && $n > $limit) {
-				$migrations = array_slice($migrations, 0, $limit);
-				echo "Showing $limit out of $n new " . ($n === 1 ? 'migration' : 'migrations') . ":\n";
-			} else {
-				echo "Found $n new " . ($n === 1 ? 'migration' : 'migrations') . ":\n";
-			}
-
-			foreach ($migrations as $migration) {
-				echo "    " . $migration . "\n";
-			}
-		}
-	}
-
-	public function actionCreate($args)
-	{
-		if (isset($args[0])) {
-			$name = $args[0];
-		} else {
-			$this->usageError('Please provide the name of the new migration.');
-		}
-
-		if (!preg_match('/^\w+$/', $name)) {
-			die("Error: The name of the migration must contain letters, digits and/or underscore characters only.\n");
-		}
-
-		$name = 'm' . gmdate('ymd_His') . '_' . $name;
-		$content = strtr($this->getTemplate(), array('{ClassName}' => $name));
-		$file = $this->migrationPath . DIRECTORY_SEPARATOR . $name . '.php';
-
-		if ($this->confirm("Create new migration '$file'?")) {
-			file_put_contents($file, $content);
-			echo "New migration created successfully.\n";
-		}
-	}
-
-	protected function migrateUp($class)
-	{
-		if ($class === self::BASE_MIGRATION) {
-			return;
-		}
-
-		echo "*** applying $class\n";
-		$start = microtime(true);
-		$migration = $this->instantiateMigration($class);
-		if ($migration->up() !== false) {
-			$this->getDb()->createCommand()->insert($this->migrationTable, array(
-				'version' => $class,
-				'apply_time' => time(),
-			));
-			$time = microtime(true) - $start;
-			echo "*** applied $class (time: " . sprintf("%.3f", $time) . "s)\n\n";
-		} else {
-			$time = microtime(true) - $start;
-			echo "*** failed to apply $class (time: " . sprintf("%.3f", $time) . "s)\n\n";
-			return false;
-		}
-	}
-
-	protected function migrateDown($class)
-	{
-		if ($class === self::BASE_MIGRATION) {
-			return;
-		}
-
-		echo "*** reverting $class\n";
-		$start = microtime(true);
-		$migration = $this->instantiateMigration($class);
-		if ($migration->down() !== false) {
-			$db = $this->getDb();
-			$db->createCommand()->delete($this->migrationTable, $db->quoteColumnName('version') . '=:version', array(':version' => $class));
-			$time = microtime(true) - $start;
-			echo "*** reverted $class (time: " . sprintf("%.3f", $time) . "s)\n\n";
-		} else {
-			$time = microtime(true) - $start;
-			echo "*** failed to revert $class (time: " . sprintf("%.3f", $time) . "s)\n\n";
-			return false;
-		}
-	}
-
-	protected function instantiateMigration($class)
-	{
-		$file = $this->migrationPath . DIRECTORY_SEPARATOR . $class . '.php';
-		require_once($file);
-		$migration = new $class;
-		$migration->setDb($this->getDb());
-		return $migration;
-	}
-
-	/**
-	 * @var CDbConnection
-	 */
-	private $_db;
-
-	protected function getDb()
-	{
-		if ($this->_db !== null) {
-			return $this->_db;
-		} else {
-			if (($this->_db = Yii::$application->getComponent($this->connectionID)) instanceof CDbConnection) {
-				return $this->_db;
-			} else {
-				die("Error: CMigrationCommand.connectionID '{$this->connectionID}' is invalid. Please make sure it refers to the ID of a CDbConnection application component.\n");
-			}
-		}
-	}
-
-	protected function getMigrationHistory($limit)
-	{
-		$db = $this->getDb();
-		if ($db->schema->getTable($this->migrationTable) === null) {
-			$this->createMigrationHistoryTable();
-		}
-		return CHtml::listData($db->createCommand()
-			->select('version, apply_time')
-			->from($this->migrationTable)
-			->order('version DESC')
-			->limit($limit)
-			->queryAll(), 'version', 'apply_time');
-	}
-
-	protected function createMigrationHistoryTable()
-	{
-		$db = $this->getDb();
-		echo 'Creating migration history table "' . $this->migrationTable . '"...';
-		$db->createCommand()->createTable($this->migrationTable, array(
-			'version' => 'string NOT NULL PRIMARY KEY',
-			'apply_time' => 'integer',
-		));
-		$db->createCommand()->insert($this->migrationTable, array(
-			'version' => self::BASE_MIGRATION,
-			'apply_time' => time(),
-		));
-		echo "done.\n";
-	}
-
-	protected function getNewMigrations()
-	{
-		$applied = array();
-		foreach ($this->getMigrationHistory(-1) as $version => $time) {
-			$applied[substr($version, 1, 13)] = true;
-		}
-
-		$migrations = array();
-		$handle = opendir($this->migrationPath);
-		while (($file = readdir($handle)) !== false) {
-			if ($file === '.' || $file === '..') {
-				continue;
-			}
-			$path = $this->migrationPath . DIRECTORY_SEPARATOR . $file;
-			if (preg_match('/^(m(\d{6}_\d{6})_.*?)\.php$/', $file, $matches) && is_file($path) && !isset($applied[$matches[2]])) {
-				$migrations[] = $matches[1];
-			}
-		}
-		closedir($handle);
-		sort($migrations);
-		return $migrations;
-	}
-
-	protected function getTemplate()
-	{
-		if ($this->templateFile !== null) {
-			return file_get_contents(Yii::getPathOfAlias($this->templateFile) . '.php');
-		} else {
-			return <<<EOD
-<?php
-
-class {ClassName} extends CDbMigration
-{
-	public function up()
-	{
-	}
-
-	public function down()
-	{
-		echo "{ClassName} does not support migration down.\\n";
-		return false;
-	}
-
-	/*
-	// Use safeUp/safeDown to do migration with transaction
-	public function safeUp()
-	{
-	}
-
-	public function safeDown()
-	{
-	}
-	*/
-}
-EOD;
-		}
-	}
-}
+<?php
+/**
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\console\controllers;
+
+use Yii;
+use yii\console\Exception;
+use yii\console\Controller;
+use yii\db\Connection;
+use yii\db\Query;
+use yii\helpers\ArrayHelper;
+
+/**
+ * This command manages application migrations.
+ *
+ * A migration means a set of persistent changes to the application environment
+ * that is shared among different developers. For example, in an application
+ * backed by a database, a migration may refer to a set of changes to
+ * the database, such as creating a new table, adding a new table column.
+ *
+ * This command provides support for tracking the migration history, upgrading
+ * or downloading with migrations, and creating new migration skeletons.
+ *
+ * The migration history is stored in a database table named
+ * as [[migrationTable]]. The table will be automatically created the first time
+ * this command is executed, if it does not exist. You may also manually
+ * create it as follows:
+ *
+ * ~~~
+ * CREATE TABLE tbl_migration (
+ *     version varchar(255) PRIMARY KEY,
+ *     apply_time integer
+ * )
+ * ~~~
+ *
+ * Below are some common usages of this command:
+ *
+ * ~~~
+ * # creates a new migration named 'create_user_table'
+ * yiic migrate/create create_user_table
+ *
+ * # applies ALL new migrations
+ * yiic migrate
+ *
+ * # reverts the last applied migration
+ * yiic migrate/down
+ * ~~~
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class MigrateController extends Controller
+{
+	/**
+	 * The name of the dummy migration that marks the beginning of the whole migration history.
+	 */
+	const BASE_MIGRATION = 'm000000_000000_base';
+
+	/**
+	 * @var string the default command action.
+	 */
+	public $defaultAction = 'up';
+	/**
+	 * @var string the directory storing the migration classes. This can be either
+	 * a path alias or a directory.
+	 */
+	public $migrationPath = '@app/migrations';
+	/**
+	 * @var string the name of the table for keeping applied migration information.
+	 */
+	public $migrationTable = 'tbl_migration';
+	/**
+	 * @var string the template file for generating new migrations.
+	 * This can be either a path alias (e.g. "@app/migrations/template.php")
+	 * or a file path.
+	 */
+	public $templateFile = '@yii/views/migration.php';
+	/**
+	 * @var boolean whether to execute the migration in an interactive mode.
+	 */
+	public $interactive = true;
+	/**
+	 * @var Connection|string the DB connection object or the application
+	 * component ID of the DB connection.
+	 */
+	public $db = 'db';
+
+	/**
+	 * Returns the names of the global options for this command.
+	 * @return array the names of the global options for this command.
+	 */
+	public function globalOptions()
+	{
+		return array('migrationPath', 'migrationTable', 'db', 'templateFile', 'interactive');
+	}
+
+	/**
+	 * This method is invoked right before an action is to be executed (after all possible filters.)
+	 * It checks the existence of the [[migrationPath]].
+	 * @param \yii\base\Action $action the action to be executed.
+	 * @return boolean whether the action should continue to be executed.
+	 * @throws Exception if the migration directory does not exist.
+	 */
+	public function beforeAction($action)
+	{
+		if (parent::beforeAction($action)) {
+			$path = Yii::getAlias($this->migrationPath);
+			if (!is_dir($path)) {
+				throw new Exception("The migration directory \"{$this->migrationPath}\" does not exist.");
+			}
+			$this->migrationPath = $path;
+
+			if (is_string($this->db)) {
+				$this->db = Yii::$app->getComponent($this->db);
+			}
+			if (!$this->db instanceof Connection) {
+				throw new Exception("The 'db' option must refer to the application component ID of a DB connection.");
+			}
+
+			$version = Yii::getVersion();
+			echo "Yii Migration Tool (based on Yii v{$version})\n\n";
+			return true;
+		} else {
+			return false;
+		}
+	}
+
+	/**
+	 * Upgrades the application by applying new migrations.
+	 * For example,
+	 *
+	 * ~~~
+	 * yiic migrate     # apply all new migrations
+	 * yiic migrate 3   # apply the first 3 new migrations
+	 * ~~~
+	 *
+	 * @param integer $limit the number of new migrations to be applied. If 0, it means
+	 * applying all available new migrations.
+	 */
+	public function actionUp($limit = 0)
+	{
+		if (($migrations = $this->getNewMigrations()) === array()) {
+			echo "No new migration found. Your system is up-to-date.\n";
+			Yii::$app->end();
+		}
+
+		$total = count($migrations);
+		$limit = (int)$limit;
+		if ($limit > 0) {
+			$migrations = array_slice($migrations, 0, $limit);
+		}
+
+		$n = count($migrations);
+		if ($n === $total) {
+			echo "Total $n new " . ($n === 1 ? 'migration' : 'migrations') . " to be applied:\n";
+		} else {
+			echo "Total $n out of $total new " . ($total === 1 ? 'migration' : 'migrations') . " to be applied:\n";
+		}
+
+		foreach ($migrations as $migration) {
+			echo "    $migration\n";
+		}
+		echo "\n";
+
+		if ($this->confirm('Apply the above ' . ($n === 1 ? 'migration' : 'migrations') . "?")) {
+			foreach ($migrations as $migration) {
+				if (!$this->migrateUp($migration)) {
+					echo "\nMigration failed. The rest of the migrations are canceled.\n";
+					return;
+				}
+			}
+			echo "\nMigrated up successfully.\n";
+		}
+	}
+
+	/**
+	 * Downgrades the application by reverting old migrations.
+	 * For example,
+	 *
+	 * ~~~
+	 * yiic migrate/down     # revert the last migration
+	 * yiic migrate/down 3   # revert the last 3 migrations
+	 * ~~~
+	 *
+	 * @param integer $limit the number of migrations to be reverted. Defaults to 1,
+	 * meaning the last applied migration will be reverted.
+	 * @throws Exception if the number of the steps specified is less than 1.
+	 */
+	public function actionDown($limit = 1)
+	{
+		$limit = (int)$limit;
+		if ($limit < 1) {
+			throw new Exception("The step argument must be greater than 0.");
+		}
+
+		if (($migrations = $this->getMigrationHistory($limit)) === array()) {
+			echo "No migration has been done before.\n";
+			return;
+		}
+		$migrations = array_keys($migrations);
+
+		$n = count($migrations);
+		echo "Total $n " . ($n === 1 ? 'migration' : 'migrations') . " to be reverted:\n";
+		foreach ($migrations as $migration) {
+			echo "    $migration\n";
+		}
+		echo "\n";
+
+		if ($this->confirm('Revert the above ' . ($n === 1 ? 'migration' : 'migrations') . "?")) {
+			foreach ($migrations as $migration) {
+				if (!$this->migrateDown($migration)) {
+					echo "\nMigration failed. The rest of the migrations are canceled.\n";
+					return;
+				}
+			}
+			echo "\nMigrated down successfully.\n";
+		}
+	}
+
+	/**
+	 * Redoes the last few migrations.
+	 *
+	 * This command will first revert the specified migrations, and then apply
+	 * them again. For example,
+	 *
+	 * ~~~
+	 * yiic migrate/redo     # redo the last applied migration
+	 * yiic migrate/redo 3   # redo the last 3 applied migrations
+	 * ~~~
+	 *
+	 * @param integer $limit the number of migrations to be redone. Defaults to 1,
+	 * meaning the last applied migration will be redone.
+	 * @throws Exception if the number of the steps specified is less than 1.
+	 */
+	public function actionRedo($limit = 1)
+	{
+		$limit = (int)$limit;
+		if ($limit < 1) {
+			throw new Exception("The step argument must be greater than 0.");
+		}
+
+		if (($migrations = $this->getMigrationHistory($limit)) === array()) {
+			echo "No migration has been done before.\n";
+			return;
+		}
+		$migrations = array_keys($migrations);
+
+		$n = count($migrations);
+		echo "Total $n " . ($n === 1 ? 'migration' : 'migrations') . " to be redone:\n";
+		foreach ($migrations as $migration) {
+			echo "    $migration\n";
+		}
+		echo "\n";
+
+		if ($this->confirm('Redo the above ' . ($n === 1 ? 'migration' : 'migrations') . "?")) {
+			foreach ($migrations as $migration) {
+				if (!$this->migrateDown($migration)) {
+					echo "\nMigration failed. The rest of the migrations are canceled.\n";
+					return;
+				}
+			}
+			foreach (array_reverse($migrations) as $migration) {
+				if (!$this->migrateUp($migration)) {
+					echo "\nMigration failed. The rest of the migrations migrations are canceled.\n";
+					return;
+				}
+			}
+			echo "\nMigration redone successfully.\n";
+		}
+	}
+
+	/**
+	 * Upgrades or downgrades till the specified version.
+	 *
+	 * This command will first revert the specified migrations, and then apply
+	 * them again. For example,
+	 *
+	 * ~~~
+	 * yiic migrate/to 101129_185401                      # using timestamp
+	 * yiic migrate/to m101129_185401_create_user_table   # using full name
+	 * ~~~
+	 *
+	 * @param string $version the version name that the application should be migrated to.
+	 * This can be either the timestamp or the full name of the migration.
+	 * @throws Exception if the version argument is invalid
+	 */
+	public function actionTo($version)
+	{
+		$originalVersion = $version;
+		if (preg_match('/^m?(\d{6}_\d{6})(_.*?)?$/', $version, $matches)) {
+			$version = 'm' . $matches[1];
+		} else {
+			throw new Exception("The version argument must be either a timestamp (e.g. 101129_185401)\nor the full name of a migration (e.g. m101129_185401_create_user_table).");
+		}
+
+		// try migrate up
+		$migrations = $this->getNewMigrations();
+		foreach ($migrations as $i => $migration) {
+			if (strpos($migration, $version . '_') === 0) {
+				$this->actionUp($i + 1);
+				return;
+			}
+		}
+
+		// try migrate down
+		$migrations = array_keys($this->getMigrationHistory(-1));
+		foreach ($migrations as $i => $migration) {
+			if (strpos($migration, $version . '_') === 0) {
+				if ($i === 0) {
+					echo "Already at '$originalVersion'. Nothing needs to be done.\n";
+				} else {
+					$this->actionDown($i);
+				}
+				return;
+			}
+		}
+
+		throw new Exception("Unable to find the version '$originalVersion'.");
+	}
+
+	/**
+	 * Modifies the migration history to the specified version.
+	 *
+	 * No actual migration will be performed.
+	 *
+	 * ~~~
+	 * yiic migrate/mark 101129_185401                      # using timestamp
+	 * yiic migrate/mark m101129_185401_create_user_table   # using full name
+	 * ~~~
+	 *
+	 * @param string $version the version at which the migration history should be marked.
+	 * This can be either the timestamp or the full name of the migration.
+	 * @throws Exception if the version argument is invalid or the version cannot be found.
+	 */
+	public function actionMark($version)
+	{
+		$originalVersion = $version;
+		if (preg_match('/^m?(\d{6}_\d{6})(_.*?)?$/', $version, $matches)) {
+			$version = 'm' . $matches[1];
+		} else {
+			throw new Exception("The version argument must be either a timestamp (e.g. 101129_185401)\nor the full name of a migration (e.g. m101129_185401_create_user_table).");
+		}
+
+		// try mark up
+		$migrations = $this->getNewMigrations();
+		foreach ($migrations as $i => $migration) {
+			if (strpos($migration, $version . '_') === 0) {
+				if ($this->confirm("Set migration history at $originalVersion?")) {
+					$command = $this->db->createCommand();
+					for ($j = 0; $j <= $i; ++$j) {
+						$command->insert($this->migrationTable, array(
+							'version' => $migrations[$j],
+							'apply_time' => time(),
+						))->execute();
+					}
+					echo "The migration history is set at $originalVersion.\nNo actual migration was performed.\n";
+				}
+				return;
+			}
+		}
+
+		// try mark down
+		$migrations = array_keys($this->getMigrationHistory(-1));
+		foreach ($migrations as $i => $migration) {
+			if (strpos($migration, $version . '_') === 0) {
+				if ($i === 0) {
+					echo "Already at '$originalVersion'. Nothing needs to be done.\n";
+				} else {
+					if ($this->confirm("Set migration history at $originalVersion?")) {
+						$command = $this->db->createCommand();
+						for ($j = 0; $j < $i; ++$j) {
+							$command->delete($this->migrationTable, array(
+								'version' => $migrations[$j],
+							))->execute();
+						}
+						echo "The migration history is set at $originalVersion.\nNo actual migration was performed.\n";
+					}
+				}
+				return;
+			}
+		}
+
+		throw new Exception("Unable to find the version '$originalVersion'.");
+	}
+
+	/**
+	 * Displays the migration history.
+	 *
+	 * This command will show the list of migrations that have been applied
+	 * so far. For example,
+	 *
+	 * ~~~
+	 * yiic migrate/history     # showing the last 10 migrations
+	 * yiic migrate/history 5   # showing the last 5 migrations
+	 * yiic migrate/history 0   # showing the whole history
+	 * ~~~
+	 *
+	 * @param integer $limit the maximum number of migrations to be displayed.
+	 * If it is 0, the whole migration history will be displayed.
+	 */
+	public function actionHistory($limit = 10)
+	{
+		$limit = (int)$limit;
+		$migrations = $this->getMigrationHistory($limit);
+		if ($migrations === array()) {
+			echo "No migration has been done before.\n";
+		} else {
+			$n = count($migrations);
+			if ($limit > 0) {
+				echo "Showing the last $n applied " . ($n === 1 ? 'migration' : 'migrations') . ":\n";
+			} else {
+				echo "Total $n " . ($n === 1 ? 'migration has' : 'migrations have') . " been applied before:\n";
+			}
+			foreach ($migrations as $version => $time) {
+				echo "    (" . date('Y-m-d H:i:s', $time) . ') ' . $version . "\n";
+			}
+		}
+	}
+
+	/**
+	 * Displays the un-applied new migrations.
+	 *
+	 * This command will show the new migrations that have not been applied.
+	 * For example,
+	 *
+	 * ~~~
+	 * yiic migrate/new     # showing the first 10 new migrations
+	 * yiic migrate/new 5   # showing the first 5 new migrations
+	 * yiic migrate/new 0   # showing all new migrations
+	 * ~~~
+	 *
+	 * @param integer $limit the maximum number of new migrations to be displayed.
+	 * If it is 0, all available new migrations will be displayed.
+	 */
+	public function actionNew($limit = 10)
+	{
+		$limit = (int)$limit;
+		$migrations = $this->getNewMigrations();
+		if ($migrations === array()) {
+			echo "No new migrations found. Your system is up-to-date.\n";
+		} else {
+			$n = count($migrations);
+			if ($limit > 0 && $n > $limit) {
+				$migrations = array_slice($migrations, 0, $limit);
+				echo "Showing $limit out of $n new " . ($n === 1 ? 'migration' : 'migrations') . ":\n";
+			} else {
+				echo "Found $n new " . ($n === 1 ? 'migration' : 'migrations') . ":\n";
+			}
+
+			foreach ($migrations as $migration) {
+				echo "    " . $migration . "\n";
+			}
+		}
+	}
+
+	/**
+	 * Creates a new migration.
+	 *
+	 * This command creates a new migration using the available migration template.
+	 * After using this command, developers should modify the created migration
+	 * skeleton by filling up the actual migration logic.
+	 *
+	 * ~~~
+	 * yiic migrate/create create_user_table
+	 * ~~~
+	 *
+	 * @param string $name the name of the new migration. This should only contain
+	 * letters, digits and/or underscores.
+	 * @throws Exception if the name argument is invalid.
+	 */
+	public function actionCreate($name)
+	{
+		if (!preg_match('/^\w+$/', $name)) {
+			throw new Exception("The migration name should contain letters, digits and/or underscore characters only.");
+		}
+
+		$name = 'm' . gmdate('ymd_His') . '_' . $name;
+		$file = $this->migrationPath . DIRECTORY_SEPARATOR . $name . '.php';
+
+		if ($this->confirm("Create new migration '$file'?")) {
+			$content = $this->renderFile(Yii::getAlias($this->templateFile), array(
+				'className' => $name,
+			));
+			file_put_contents($file, $content);
+			echo "New migration created successfully.\n";
+		}
+	}
+
+	/**
+	 * Upgrades with the specified migration class.
+	 * @param string $class the migration class name
+	 * @return boolean whether the migration is successful
+	 */
+	protected function migrateUp($class)
+	{
+		if ($class === self::BASE_MIGRATION) {
+			return true;
+		}
+
+		echo "*** applying $class\n";
+		$start = microtime(true);
+		$migration = $this->createMigration($class);
+		if ($migration->up() !== false) {
+			$this->db->createCommand()->insert($this->migrationTable, array(
+				'version' => $class,
+				'apply_time' => time(),
+			))->execute();
+			$time = microtime(true) - $start;
+			echo "*** applied $class (time: " . sprintf("%.3f", $time) . "s)\n\n";
+			return true;
+		} else {
+			$time = microtime(true) - $start;
+			echo "*** failed to apply $class (time: " . sprintf("%.3f", $time) . "s)\n\n";
+			return false;
+		}
+	}
+
+	/**
+	 * Downgrades with the specified migration class.
+	 * @param string $class the migration class name
+	 * @return boolean whether the migration is successful
+	 */
+	protected function migrateDown($class)
+	{
+		if ($class === self::BASE_MIGRATION) {
+			return true;
+		}
+
+		echo "*** reverting $class\n";
+		$start = microtime(true);
+		$migration = $this->createMigration($class);
+		if ($migration->down() !== false) {
+			$this->db->createCommand()->delete($this->migrationTable, array(
+				'version' => $class,
+			))->execute();
+			$time = microtime(true) - $start;
+			echo "*** reverted $class (time: " . sprintf("%.3f", $time) . "s)\n\n";
+			return true;
+		} else {
+			$time = microtime(true) - $start;
+			echo "*** failed to revert $class (time: " . sprintf("%.3f", $time) . "s)\n\n";
+			return false;
+		}
+	}
+
+	/**
+	 * Creates a new migration instance.
+	 * @param string $class the migration class name
+	 * @return \yii\db\Migration the migration instance
+	 */
+	protected function createMigration($class)
+	{
+		$file = $this->migrationPath . DIRECTORY_SEPARATOR . $class . '.php';
+		require_once($file);
+		return new $class(array(
+			'db' => $this->db,
+		));
+	}
+
+	/**
+	 * Returns the migration history.
+	 * @param integer $limit the maximum number of records in the history to be returned
+	 * @return array the migration history
+	 */
+	protected function getMigrationHistory($limit)
+	{
+		if ($this->db->schema->getTableSchema($this->migrationTable) === null) {
+			$this->createMigrationHistoryTable();
+		}
+		$query = new Query;
+		$rows = $query->select(array('version', 'apply_time'))
+			->from($this->migrationTable)
+			->orderBy('version DESC')
+			->limit($limit)
+			->createCommand()
+			->queryAll();
+		$history = ArrayHelper::map($rows, 'version', 'apply_time');
+		unset($history[self::BASE_MIGRATION]);
+		return $history;
+	}
+
+	/**
+	 * Creates the migration history table.
+	 */
+	protected function createMigrationHistoryTable()
+	{
+		echo 'Creating migration history table "' . $this->migrationTable . '"...';
+		$this->db->createCommand()->createTable($this->migrationTable, array(
+			'version' => 'varchar(255) NOT NULL PRIMARY KEY',
+			'apply_time' => 'integer',
+		))->execute();
+		$this->db->createCommand()->insert($this->migrationTable, array(
+			'version' => self::BASE_MIGRATION,
+			'apply_time' => time(),
+		))->execute();
+		echo "done.\n";
+	}
+
+	/**
+	 * Returns the migrations that are not applied.
+	 * @return array list of new migrations
+	 */
+	protected function getNewMigrations()
+	{
+		$applied = array();
+		foreach ($this->getMigrationHistory(-1) as $version => $time) {
+			$applied[substr($version, 1, 13)] = true;
+		}
+
+		$migrations = array();
+		$handle = opendir($this->migrationPath);
+		while (($file = readdir($handle)) !== false) {
+			if ($file === '.' || $file === '..') {
+				continue;
+			}
+			$path = $this->migrationPath . DIRECTORY_SEPARATOR . $file;
+			if (preg_match('/^(m(\d{6}_\d{6})_.*?)\.php$/', $file, $matches) && is_file($path) && !isset($applied[$matches[2]])) {
+				$migrations[] = $matches[1];
+			}
+		}
+		closedir($handle);
+		sort($migrations);
+		return $migrations;
+	}
+}
diff --git a/framework/console/create/config.php b/framework/console/webapp/config.php
similarity index 94%
rename from framework/console/create/config.php
rename to framework/console/webapp/config.php
index 29f0b0b..112fb18 100644
--- a/framework/console/create/config.php
+++ b/framework/console/webapp/config.php
@@ -1,5 +1,5 @@
 <?php
-/** @var $controller \yii\console\controllers\CreateController */
+/** @var $controller \yii\console\controllers\AppController */
 $controller = $this;
 
 return array(
diff --git a/framework/console/create/default/index.php b/framework/console/webapp/default/index.php
similarity index 100%
rename from framework/console/create/default/index.php
rename to framework/console/webapp/default/index.php
diff --git a/framework/console/create/default/protected/config/main.php b/framework/console/webapp/default/protected/config/main.php
similarity index 100%
rename from framework/console/create/default/protected/config/main.php
rename to framework/console/webapp/default/protected/config/main.php
diff --git a/framework/console/create/default/protected/controllers/SiteController.php b/framework/console/webapp/default/protected/controllers/SiteController.php
similarity index 100%
rename from framework/console/create/default/protected/controllers/SiteController.php
rename to framework/console/webapp/default/protected/controllers/SiteController.php
diff --git a/framework/console/create/default/protected/views/layouts/main.php b/framework/console/webapp/default/protected/views/layouts/main.php
similarity index 100%
rename from framework/console/create/default/protected/views/layouts/main.php
rename to framework/console/webapp/default/protected/views/layouts/main.php
diff --git a/framework/console/create/default/protected/views/site/index.php b/framework/console/webapp/default/protected/views/site/index.php
similarity index 100%
rename from framework/console/create/default/protected/views/site/index.php
rename to framework/console/webapp/default/protected/views/site/index.php
diff --git a/framework/db/ActiveQuery.php b/framework/db/ActiveQuery.php
index de56e14..43c3059 100644
--- a/framework/db/ActiveQuery.php
+++ b/framework/db/ActiveQuery.php
@@ -1,10 +1,8 @@
 <?php
 /**
- * ActiveQuery class file.
- *
  * @author Qiang Xue <qiang.xue@gmail.com>
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
diff --git a/framework/db/ActiveRecord.php b/framework/db/ActiveRecord.php
index c6d3d81..d8f2f65 100644
--- a/framework/db/ActiveRecord.php
+++ b/framework/db/ActiveRecord.php
@@ -1,24 +1,22 @@
 <?php
 /**
- * ActiveRecord class file.
- *
  * @author Qiang Xue <qiang.xue@gmail.com>
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
 namespace yii\db;
 
 use yii\base\Model;
-use yii\base\Event;
+use yii\base\InvalidParamException;
 use yii\base\ModelEvent;
 use yii\base\UnknownMethodException;
 use yii\base\InvalidCallException;
 use yii\db\Connection;
 use yii\db\TableSchema;
 use yii\db\Expression;
-use yii\util\StringHelper;
+use yii\helpers\StringHelper;
 
 /**
  * ActiveRecord is the base class for classes representing relational data in terms of objects.
@@ -96,7 +94,7 @@ class ActiveRecord extends Model
 	 */
 	public static function getDb()
 	{
-		return \Yii::$application->getDb();
+		return \Yii::$app->getDb();
 	}
 
 	/**
@@ -849,7 +847,7 @@ class ActiveRecord extends Model
 	 */
 	public function beforeSave($insert)
 	{
-		$event = new ModelEvent($this);
+		$event = new ModelEvent;
 		$this->trigger($insert ? self::EVENT_BEFORE_INSERT : self::EVENT_BEFORE_UPDATE, $event);
 		return $event->isValid;
 	}
@@ -889,7 +887,7 @@ class ActiveRecord extends Model
 	 */
 	public function beforeDelete()
 	{
-		$event = new ModelEvent($this);
+		$event = new ModelEvent;
 		$this->trigger(self::EVENT_BEFORE_DELETE, $event);
 		return $event->isValid;
 	}
@@ -1045,7 +1043,7 @@ class ActiveRecord extends Model
 	 * It can be declared in either the Active Record class itself or one of its behaviors.
 	 * @param string $name the relation name
 	 * @return ActiveRelation the relation object
-	 * @throws InvalidCallException if the named relation does not exist.
+	 * @throws InvalidParamException if the named relation does not exist.
 	 */
 	public function getRelation($name)
 	{
@@ -1057,7 +1055,7 @@ class ActiveRecord extends Model
 			}
 		} catch (UnknownMethodException $e) {
 		}
-		throw new InvalidCallException(get_class($this) . ' has no relation named "' . $name . '".');
+		throw new InvalidParamException(get_class($this) . ' has no relation named "' . $name . '".');
 	}
 
 	/**
diff --git a/framework/db/ActiveRelation.php b/framework/db/ActiveRelation.php
index 4d87fb3..c547f1a 100644
--- a/framework/db/ActiveRelation.php
+++ b/framework/db/ActiveRelation.php
@@ -1,10 +1,8 @@
 <?php
 /**
- * ActiveRelation class file.
- *
  * @author Qiang Xue <qiang.xue@gmail.com>
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
@@ -57,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;
 	}
@@ -77,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),
@@ -91,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/db/ColumnSchema.php b/framework/db/ColumnSchema.php
index 44e6cb0..ffdafd4 100644
--- a/framework/db/ColumnSchema.php
+++ b/framework/db/ColumnSchema.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * ColumnSchema class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
diff --git a/framework/db/Command.php b/framework/db/Command.php
index 3531fa7..ecd3674 100644
--- a/framework/db/Command.php
+++ b/framework/db/Command.php
@@ -1,15 +1,15 @@
 <?php
 /**
- * Command class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
 namespace yii\db;
 
+use Yii;
 use yii\base\NotSupportedException;
+use yii\caching\Cache;
 
 /**
  * Command represents a SQL statement to be executed against a database.
@@ -134,9 +134,9 @@ class Command extends \yii\base\Component
 			try {
 				$this->pdoStatement = $this->db->pdo->prepare($sql);
 			} catch (\Exception $e) {
-				\Yii::error($e->getMessage() . "\nFailed to prepare SQL: $sql", __CLASS__);
+				Yii::error($e->getMessage() . "\nFailed to prepare SQL: $sql", __CLASS__);
 				$errorInfo = $e instanceof \PDOException ? $e->errorInfo : null;
-				throw new Exception($e->getMessage(), (int)$e->getCode(), $errorInfo);
+				throw new Exception($e->getMessage(), $errorInfo, (int)$e->getCode());
 			}
 		}
 	}
@@ -253,24 +253,20 @@ class Command extends \yii\base\Component
 	 * Executes the SQL statement.
 	 * This method should only be used for executing non-query SQL statement, such as `INSERT`, `DELETE`, `UPDATE` SQLs.
 	 * No result set will be returned.
-	 * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative
-	 * to [[bindValues()]]. Note that if you pass parameters in this way, any previous call to [[bindParam()]]
-	 * or [[bindValue()]] will be ignored.
 	 * @return integer number of rows affected by the execution.
 	 * @throws Exception execution failed
 	 */
-	public function execute($params = array())
+	public function execute()
 	{
 		$sql = $this->getSql();
 
-		$this->_params = array_merge($this->_params, $params);
 		if ($this->_params === array()) {
 			$paramLog = '';
 		} else {
 			$paramLog = "\nParameters: " . var_export($this->_params, true);
 		}
 
-		\Yii::trace("Executing SQL: {$sql}{$paramLog}", __CLASS__);
+		Yii::trace("Executing SQL: {$sql}{$paramLog}", __CLASS__);
 
 		if ($sql == '') {
 			return 0;
@@ -278,94 +274,78 @@ class Command extends \yii\base\Component
 
 		try {
 			if ($this->db->enableProfiling) {
-				\Yii::beginProfile(__METHOD__ . "($sql)", __CLASS__);
+				Yii::beginProfile(__METHOD__ . "($sql)", __CLASS__);
 			}
 
 			$this->prepare();
-			if ($params === array()) {
-				$this->pdoStatement->execute();
-			} else {
-				$this->pdoStatement->execute($params);
-			}
+			$this->pdoStatement->execute();
 			$n = $this->pdoStatement->rowCount();
 
 			if ($this->db->enableProfiling) {
-				\Yii::endProfile(__METHOD__ . "($sql)", __CLASS__);
+				Yii::endProfile(__METHOD__ . "($sql)", __CLASS__);
 			}
 			return $n;
 		} catch (\Exception $e) {
 			if ($this->db->enableProfiling) {
-				\Yii::endProfile(__METHOD__ . "($sql)", __CLASS__);
+				Yii::endProfile(__METHOD__ . "($sql)", __CLASS__);
 			}
 			$message = $e->getMessage();
 
-			\Yii::error("$message\nFailed to execute SQL: {$sql}{$paramLog}", __CLASS__);
+			Yii::error("$message\nFailed to execute SQL: {$sql}{$paramLog}", __CLASS__);
 
 			$errorInfo = $e instanceof \PDOException ? $e->errorInfo : null;
-			throw new Exception($message, (int)$e->getCode(), $errorInfo);
+			throw new Exception($message, $errorInfo, (int)$e->getCode());
 		}
 	}
 
 	/**
 	 * Executes the SQL statement and returns query result.
 	 * This method is for executing a SQL query that returns result set, such as `SELECT`.
-	 * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative
-	 * to [[bindValues()]]. Note that if you pass parameters in this way, any previous call to [[bindParam()]]
-	 * or [[bindValue()]] will be ignored.
 	 * @return DataReader the reader object for fetching the query result
 	 * @throws Exception execution failed
 	 */
-	public function query($params = array())
+	public function query()
 	{
-		return $this->queryInternal('', $params);
+		return $this->queryInternal('');
 	}
 
 	/**
 	 * Executes the SQL statement and returns ALL rows at once.
-	 * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative
-	 * to [[bindValues()]]. Note that if you pass parameters in this way, any previous call to [[bindParam()]]
-	 * or [[bindValue()]] will be ignored.
 	 * @param mixed $fetchMode the result fetch mode. Please refer to [PHP manual](http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php)
 	 * for valid fetch modes. If this parameter is null, the value set in [[fetchMode]] will be used.
 	 * @return array all rows of the query result. Each array element is an array representing a row of data.
 	 * An empty array is returned if the query results in nothing.
 	 * @throws Exception execution failed
 	 */
-	public function queryAll($params = array(), $fetchMode = null)
+	public function queryAll($fetchMode = null)
 	{
-		return $this->queryInternal('fetchAll', $params, $fetchMode);
+		return $this->queryInternal('fetchAll', $fetchMode);
 	}
 
 	/**
 	 * Executes the SQL statement and returns the first row of the result.
 	 * This method is best used when only the first row of result is needed for a query.
-	 * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative
-	 * to [[bindValues()]]. Note that if you pass parameters in this way, any previous call to [[bindParam()]]
-	 * or [[bindValue()]] will be ignored.
 	 * @param mixed $fetchMode the result fetch mode. Please refer to [PHP manual](http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php)
 	 * for valid fetch modes. If this parameter is null, the value set in [[fetchMode]] will be used.
 	 * @return array|boolean the first row (in terms of an array) of the query result. False is returned if the query
 	 * results in nothing.
 	 * @throws Exception execution failed
 	 */
-	public function queryRow($params = array(), $fetchMode = null)
+	public function queryRow($fetchMode = null)
 	{
-		return $this->queryInternal('fetch', $params, $fetchMode);
+		return $this->queryInternal('fetch', $fetchMode);
 	}
 
 	/**
 	 * Executes the SQL statement and returns the value of the first column in the first row of data.
 	 * This method is best used when only a single value is needed for a query.
-	 * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative
-	 * to [[bindValues()]]. Note that if you pass parameters in this way, any previous call to [[bindParam()]]
-	 * or [[bindValue()]] will be ignored.
 	 * @return string|boolean the value of the first column in the first row of the query result.
 	 * False is returned if there is no value.
 	 * @throws Exception execution failed
 	 */
-	public function queryScalar($params = array())
+	public function queryScalar()
 	{
-		$result = $this->queryInternal('fetchColumn', $params, 0);
+		$result = $this->queryInternal('fetchColumn', 0);
 		if (is_resource($result) && get_resource_type($result) === 'stream') {
 			return stream_get_contents($result);
 		} else {
@@ -377,65 +357,60 @@ class Command extends \yii\base\Component
 	 * Executes the SQL statement and returns the first column of the result.
 	 * This method is best used when only the first column of result (i.e. the first element in each row)
 	 * is needed for a query.
-	 * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative
-	 * to [[bindValues()]]. Note that if you pass parameters in this way, any previous call to [[bindParam()]]
-	 * or [[bindValue()]] will be ignored.
 	 * @return array the first column of the query result. Empty array is returned if the query results in nothing.
 	 * @throws Exception execution failed
 	 */
-	public function queryColumn($params = array())
+	public function queryColumn()
 	{
-		return $this->queryInternal('fetchAll', $params, \PDO::FETCH_COLUMN);
+		return $this->queryInternal('fetchAll', \PDO::FETCH_COLUMN);
 	}
 
 	/**
 	 * Performs the actual DB query of a SQL statement.
 	 * @param string $method method of PDOStatement to be called
-	 * @param array $params input parameters (name=>value) for the SQL execution. This is an alternative
-	 * to [[bindValues()]]. Note that if you pass parameters in this way, any previous call to [[bindParam()]]
-	 * or [[bindValue()]] will be ignored.
 	 * @param mixed $fetchMode the result fetch mode. Please refer to [PHP manual](http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php)
 	 * for valid fetch modes. If this parameter is null, the value set in [[fetchMode]] will be used.
 	 * @return mixed the method execution result
 	 * @throws Exception if the query causes any problem
 	 */
-	private function queryInternal($method, $params, $fetchMode = null)
+	private function queryInternal($method, $fetchMode = null)
 	{
 		$db = $this->db;
 		$sql = $this->getSql();
-		$this->_params = array_merge($this->_params, $params);
 		if ($this->_params === array()) {
 			$paramLog = '';
 		} else {
 			$paramLog = "\nParameters: " . var_export($this->_params, true);
 		}
 
-		\Yii::trace("Querying SQL: {$sql}{$paramLog}", __CLASS__);
+		Yii::trace("Querying SQL: {$sql}{$paramLog}", __CLASS__);
 
 		/** @var $cache \yii\caching\Cache */
 		if ($db->enableQueryCache && $method !== '') {
-			$cache = \Yii::$application->getComponent($db->queryCacheID);
+			$cache = is_string($db->queryCache) ? Yii::$app->getComponent($db->queryCache) : $db->queryCache;
 		}
 
-		if (isset($cache)) {
-			$cacheKey = $cache->buildKey(__CLASS__, $db->dsn, $db->username, $sql, $paramLog);
+		if (isset($cache) && $cache instanceof Cache) {
+			$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__);
+				Yii::trace('Query result served from cache', __CLASS__);
 				return $result;
 			}
 		}
 
 		try {
 			if ($db->enableProfiling) {
-				\Yii::beginProfile(__METHOD__ . "($sql)", __CLASS__);
+				Yii::beginProfile(__METHOD__ . "($sql)", __CLASS__);
 			}
 
 			$this->prepare();
-			if ($params === array()) {
-				$this->pdoStatement->execute();
-			} else {
-				$this->pdoStatement->execute($params);
-			}
+			$this->pdoStatement->execute();
 
 			if ($method === '') {
 				$result = new DataReader($this);
@@ -448,23 +423,23 @@ class Command extends \yii\base\Component
 			}
 
 			if ($db->enableProfiling) {
-				\Yii::endProfile(__METHOD__ . "($sql)", __CLASS__);
+				Yii::endProfile(__METHOD__ . "($sql)", __CLASS__);
 			}
 
-			if (isset($cache, $cacheKey)) {
+			if (isset($cache, $cacheKey) && $cache instanceof Cache) {
 				$cache->set($cacheKey, $result, $db->queryCacheDuration, $db->queryCacheDependency);
-				\Yii::trace('Saved query result in cache', __CLASS__);
+				Yii::trace('Saved query result in cache', __CLASS__);
 			}
 
 			return $result;
 		} catch (\Exception $e) {
 			if ($db->enableProfiling) {
-				\Yii::endProfile(__METHOD__ . "($sql)", __CLASS__);
+				Yii::endProfile(__METHOD__ . "($sql)", __CLASS__);
 			}
 			$message = $e->getMessage();
-			\Yii::error("$message\nCommand::$method() failed: {$sql}{$paramLog}", __CLASS__);
+			Yii::error("$message\nCommand::$method() failed: {$sql}{$paramLog}", __CLASS__);
 			$errorInfo = $e instanceof \PDOException ? $e->errorInfo : null;
-			throw new Exception($message, (int)$e->getCode(), $errorInfo);
+			throw new Exception($message, $errorInfo, (int)$e->getCode());
 		}
 	}
 
diff --git a/framework/db/Connection.php b/framework/db/Connection.php
index 3564361..59e8422 100644
--- a/framework/db/Connection.php
+++ b/framework/db/Connection.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * Connection class file
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
@@ -12,6 +10,7 @@ namespace yii\db;
 use yii\base\Component;
 use yii\base\InvalidConfigException;
 use yii\base\NotSupportedException;
+use yii\caching\Cache;
 
 /**
  * Connection represents a connection to a database via [PDO](http://www.php.net/manual/en/ref.pdo.php).
@@ -138,10 +137,10 @@ class Connection extends Component
 	/**
 	 * @var boolean whether to enable schema caching.
 	 * Note that in order to enable truly schema caching, a valid cache component as specified
-	 * by [[schemaCacheID]] must be enabled and [[enableSchemaCache]] must be set true.
+	 * by [[schemaCache]] must be enabled and [[enableSchemaCache]] must be set true.
 	 * @see schemaCacheDuration
 	 * @see schemaCacheExclude
-	 * @see schemaCacheID
+	 * @see schemaCache
 	 */
 	public $enableSchemaCache = false;
 	/**
@@ -157,20 +156,20 @@ class Connection extends Component
 	 */
 	public $schemaCacheExclude = array();
 	/**
-	 * @var string the ID of the cache application component that is used to cache the table metadata.
-	 * Defaults to 'cache'.
+	 * @var Cache|string the cache object or the ID of the cache application component that
+	 * is used to cache the table metadata.
 	 * @see enableSchemaCache
 	 */
-	public $schemaCacheID = 'cache';
+	public $schemaCache = 'cache';
 	/**
 	 * @var boolean whether to enable query caching.
 	 * Note that in order to enable query caching, a valid cache component as specified
-	 * by [[queryCacheID]] must be enabled and [[enableQueryCache]] must be set true.
+	 * by [[queryCache]] must be enabled and [[enableQueryCache]] must be set true.
 	 *
 	 * Methods [[beginCache()]] and [[endCache()]] can be used as shortcuts to turn on
 	 * and off query caching on the fly.
 	 * @see queryCacheDuration
-	 * @see queryCacheID
+	 * @see queryCache
 	 * @see queryCacheDependency
 	 * @see beginCache()
 	 * @see endCache()
@@ -178,7 +177,7 @@ class Connection extends Component
 	public $enableQueryCache = false;
 	/**
 	 * @var integer number of seconds that query results can remain valid in cache.
-	 * Defaults to 3600, meaning one hour.
+	 * Defaults to 3600, meaning 3600 seconds, or one hour.
 	 * Use 0 to indicate that the cached data will never expire.
 	 * @see enableQueryCache
 	 */
@@ -190,11 +189,11 @@ class Connection extends Component
 	 */
 	public $queryCacheDependency;
 	/**
-	 * @var string the ID of the cache application component that is used for query caching.
-	 * Defaults to 'cache'.
+	 * @var Cache|string the cache object or the ID of the cache application component
+	 * that is used for query caching.
 	 * @see enableQueryCache
 	 */
-	public $queryCacheID = 'cache';
+	public $queryCache = 'cache';
 	/**
 	 * @var string the charset used for database connection. The property is only used
 	 * for MySQL and PostgreSQL databases. Defaults to null, meaning using default charset
@@ -292,7 +291,7 @@ class Connection extends Component
 	 * This method is provided as a shortcut to setting two properties that are related
 	 * with query caching: [[queryCacheDuration]] and [[queryCacheDependency]].
 	 * @param integer $duration the number of seconds that query results may remain valid in cache.
-	 * See [[queryCacheDuration]] for more details.
+	 * If not set, it will use the value of [[queryCacheDuration]]. See [[queryCacheDuration]] for more details.
 	 * @param \yii\caching\Dependency $dependency the dependency for the cached query result.
 	 * See [[queryCacheDependency]] for more details.
 	 */
@@ -322,7 +321,7 @@ class Connection extends Component
 	{
 		if ($this->pdo === null) {
 			if (empty($this->dsn)) {
-				throw new InvalidConfigException('Connection.dsn cannot be empty.');
+				throw new InvalidConfigException('Connection::dsn cannot be empty.');
 			}
 			try {
 				\Yii::trace('Opening DB connection: ' . $this->dsn, __CLASS__);
@@ -332,7 +331,7 @@ class Connection extends Component
 			catch (\PDOException $e) {
 				\Yii::error("Failed to open DB connection ({$this->dsn}): " . $e->getMessage(), __CLASS__);
 				$message = YII_DEBUG ? 'Failed to open DB connection: ' . $e->getMessage() : 'Failed to open DB connection.';
-				throw new Exception($message, (int)$e->getCode(), $e->errorInfo);
+				throw new Exception($message, $e->errorInfo, (int)$e->getCode());
 			}
 		}
 	}
diff --git a/framework/db/DataReader.php b/framework/db/DataReader.php
index 8e5291e..20444e7 100644
--- a/framework/db/DataReader.php
+++ b/framework/db/DataReader.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * DataReader class file
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
diff --git a/framework/db/Exception.php b/framework/db/Exception.php
index 209dc40..ad97b5a 100644
--- a/framework/db/Exception.php
+++ b/framework/db/Exception.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * Exception class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
@@ -26,13 +24,14 @@ class Exception extends \yii\base\Exception
 	/**
 	 * Constructor.
 	 * @param string $message PDO error message
-	 * @param integer $code PDO error code
 	 * @param mixed $errorInfo PDO error info
+	 * @param integer $code PDO error code
+	 * @param \Exception $previous The previous exception used for the exception chaining.
 	 */
-	public function __construct($message, $code = 0, $errorInfo = null)
+	public function __construct($message, $errorInfo = null, $code = 0, \Exception $previous = null)
 	{
 		$this->errorInfo = $errorInfo;
-		parent::__construct($message, $code);
+		parent::__construct($message, $code, $previous);
 	}
 
 	/**
@@ -40,6 +39,6 @@ class Exception extends \yii\base\Exception
 	 */
 	public function getName()
 	{
-		return \Yii::t('yii', 'Database Exception');
+		return \Yii::t('yii|Database Exception');
 	}
 }
\ No newline at end of file
diff --git a/framework/db/Expression.php b/framework/db/Expression.php
index 23fb13e..4ebcd5f 100644
--- a/framework/db/Expression.php
+++ b/framework/db/Expression.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * Expression class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
diff --git a/framework/db/Migration.php b/framework/db/Migration.php
index 6dbaa78..ce2cf97 100644
--- a/framework/db/Migration.php
+++ b/framework/db/Migration.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * Migration class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
@@ -51,7 +49,7 @@ class Migration extends \yii\base\Component
 	{
 		parent::init();
 		if ($this->db === null) {
-			$this->db = \Yii::$application->getComponent('db');
+			$this->db = \Yii::$app->getComponent('db');
 		}
 	}
 
diff --git a/framework/db/Query.php b/framework/db/Query.php
index 10bba08..2239f5d 100644
--- a/framework/db/Query.php
+++ b/framework/db/Query.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * Query class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
@@ -37,9 +35,19 @@ namespace yii\db;
 class Query extends \yii\base\Component
 {
 	/**
-	 * @var string|array the columns being selected. This refers to the SELECT clause in a SQL
-	 * statement. It can be either a string (e.g. `'id, name'`) or an array (e.g. `array('id', 'name')`).
-	 * If not set, if means all columns.
+	 * Sort ascending
+	 * @see orderBy
+	 */
+	const SORT_ASC = false;
+	/**
+	 * Sort ascending
+	 * @see orderBy
+	 */
+	const SORT_DESC = true;
+
+	/**
+	 * @var array the columns being selected. For example, `array('id', 'name')`.
+	 * This is used to construct the SELECT clause in a SQL statement. If not set, if means selecting all columns.
 	 * @see select()
 	 */
 	public $select;
@@ -54,8 +62,8 @@ class Query extends \yii\base\Component
 	 */
 	public $distinct;
 	/**
-	 * @var string|array the table(s) to be selected from. This refers to the FROM clause in a SQL statement.
-	 * It can be either a string (e.g. `'tbl_user, tbl_post'`) or an array (e.g. `array('tbl_user', 'tbl_post')`).
+	 * @var array the table(s) to be selected from. For example, `array('tbl_user', 'tbl_post')`.
+	 * This is used to construct the FROM clause in a SQL statement.
 	 * @see from()
 	 */
 	public $from;
@@ -75,20 +83,33 @@ class Query extends \yii\base\Component
 	 */
 	public $offset;
 	/**
-	 * @var string|array how to sort the query results. This refers to the ORDER BY clause in a SQL statement.
-	 * It can be either a string (e.g. `'id ASC, name DESC'`) or an array (e.g. `array('id ASC', 'name DESC')`).
+	 * @var array how to sort the query results. This is used to construct the ORDER BY clause in a SQL statement.
+	 * The array keys are the columns to be sorted by, and the array values are the corresponding sort directions which
+	 * can be either [[Query::SORT_ASC]] or [[Query::SORT_DESC]]. The array may also contain [[Expression]] objects.
+	 * If that is the case, the expressions will be converted into strings without any change.
 	 */
 	public $orderBy;
 	/**
-	 * @var string|array how to group the query results. This refers to the GROUP BY clause in a SQL statement.
-	 * It can be either a string (e.g. `'company, department'`) or an array (e.g. `array('company', 'department')`).
+	 * @var array how to group the query results. For example, `array('company', 'department')`.
+	 * This is used to construct the GROUP BY clause in a SQL statement.
 	 */
 	public $groupBy;
 	/**
-	 * @var string|array how to join with other tables. This refers to the JOIN clause in a SQL statement.
-	 * It can be either a string (e.g. `'LEFT JOIN tbl_user ON tbl_user.id=author_id'`) or an array (e.g.
-	 * `array('LEFT JOIN tbl_user ON tbl_user.id=author_id', 'LEFT JOIN tbl_team ON tbl_team.id=team_id')`).
-	 * @see join()
+	 * @var array how to join with other tables. Each array element represents the specification
+	 * of one join which has the following structure:
+	 *
+	 * ~~~
+	 * array($joinType, $tableName, $joinCondition)
+	 * ~~~
+	 *
+	 * For example,
+	 *
+	 * ~~~
+	 * array(
+	 *     array('INNER JOIN', 'tbl_user', 'tbl_user.id = author_id'),
+	 *     array('LEFT JOIN', 'tbl_team', 'tbl_team.id = team_id'),
+	 * )
+	 * ~~~
 	 */
 	public $join;
 	/**
@@ -97,9 +118,8 @@ class Query extends \yii\base\Component
 	 */
 	public $having;
 	/**
-	 * @var string|Query[] the UNION clause(s) in a SQL statement. This can be either a string
-	 * representing a single UNION clause or an array representing multiple UNION clauses.
-	 * Each union clause can be a string or a `Query` object which refers to the SQL statement.
+	 * @var array this is used to construct the UNION clause(s) in a SQL statement.
+	 * Each array element can be either a string or a [[Query]] object representing a sub-query.
 	 */
 	public $union;
 	/**
@@ -117,7 +137,7 @@ class Query extends \yii\base\Component
 	public function createCommand($db = null)
 	{
 		if ($db === null) {
-			$db = \Yii::$application->db;
+			$db = \Yii::$app->db;
 		}
 		$sql = $db->getQueryBuilder()->build($this);
 		return $db->createCommand($sql, $this->params);
@@ -136,6 +156,9 @@ class Query extends \yii\base\Component
 	 */
 	public function select($columns, $option = null)
 	{
+		if (!is_array($columns)) {
+			$columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
+		}
 		$this->select = $columns;
 		$this->selectOption = $option;
 		return $this;
@@ -163,6 +186,9 @@ class Query extends \yii\base\Component
 	 */
 	public function from($tables)
 	{
+		if (!is_array($tables)) {
+			$tables = preg_split('/\s*,\s*/', trim($tables), -1, PREG_SPLIT_NO_EMPTY);
+		}
 		$this->from = $tables;
 		return $this;
 	}
@@ -362,10 +388,13 @@ class Query extends \yii\base\Component
 	 * The method will automatically quote the column names unless a column contains some parenthesis
 	 * (which means the column contains a DB expression).
 	 * @return Query the query object itself
-	 * @see addGroup()
+	 * @see addGroupBy()
 	 */
 	public function groupBy($columns)
 	{
+		if (!is_array($columns)) {
+			$columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
+		}
 		$this->groupBy = $columns;
 		return $this;
 	}
@@ -377,19 +406,16 @@ class Query extends \yii\base\Component
 	 * The method will automatically quote the column names unless a column contains some parenthesis
 	 * (which means the column contains a DB expression).
 	 * @return Query the query object itself
-	 * @see group()
+	 * @see groupBy()
 	 */
-	public function addGroup($columns)
+	public function addGroupBy($columns)
 	{
-		if (empty($this->groupBy)) {
+		if (!is_array($columns)) {
+			$columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
+		}
+		if ($this->groupBy === null) {
 			$this->groupBy = $columns;
 		} else {
-			if (!is_array($this->groupBy)) {
-				$this->groupBy = preg_split('/\s*,\s*/', trim($this->groupBy), -1, PREG_SPLIT_NO_EMPTY);
-			}
-			if (!is_array($columns)) {
-				$columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
-			}
 			$this->groupBy = array_merge($this->groupBy, $columns);
 		}
 		return $this;
@@ -456,43 +482,58 @@ class Query extends \yii\base\Component
 	/**
 	 * Sets the ORDER BY part of the query.
 	 * @param string|array $columns the columns (and the directions) to be ordered by.
-	 * Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array (e.g. array('id ASC', 'name DESC')).
+	 * Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array
+	 * (e.g. `array('id' => Query::SORT_ASC ASC, 'name' => Query::SORT_DESC)`).
 	 * The method will automatically quote the column names unless a column contains some parenthesis
 	 * (which means the column contains a DB expression).
 	 * @return Query the query object itself
-	 * @see addOrder()
+	 * @see addOrderBy()
 	 */
 	public function orderBy($columns)
 	{
-		$this->orderBy = $columns;
+		$this->orderBy = $this->normalizeOrderBy($columns);
 		return $this;
 	}
 
 	/**
 	 * Adds additional ORDER BY columns to the query.
 	 * @param string|array $columns the columns (and the directions) to be ordered by.
-	 * Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array (e.g. array('id ASC', 'name DESC')).
+	 * Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array
+	 * (e.g. `array('id' => Query::SORT_ASC ASC, 'name' => Query::SORT_DESC)`).
 	 * The method will automatically quote the column names unless a column contains some parenthesis
 	 * (which means the column contains a DB expression).
 	 * @return Query the query object itself
-	 * @see order()
+	 * @see orderBy()
 	 */
 	public function addOrderBy($columns)
 	{
-		if (empty($this->orderBy)) {
+		$columns = $this->normalizeOrderBy($columns);
+		if ($this->orderBy === null) {
 			$this->orderBy = $columns;
 		} else {
-			if (!is_array($this->orderBy)) {
-				$this->orderBy = preg_split('/\s*,\s*/', trim($this->orderBy), -1, PREG_SPLIT_NO_EMPTY);
-			}
-			if (!is_array($columns)) {
-				$columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
-			}
 			$this->orderBy = array_merge($this->orderBy, $columns);
 		}
 		return $this;
 	}
 
+	protected function normalizeOrderBy($columns)
+	{
+		if (is_array($columns)) {
+			return $columns;
+		} else {
+			$columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
+			$result = array();
+			foreach ($columns as $column) {
+				if (preg_match('/^(.*?)\s+(asc|desc)$/i', $column, $matches)) {
+					$result[$matches[1]] = strcasecmp($matches[2], 'desc') ? self::SORT_ASC : self::SORT_DESC;
+				} else {
+					$result[$column] = self::SORT_ASC;
+				}
+			}
+			return $result;
+		}
+	}
+
 	/**
 	 * Sets the LIMIT part of the query.
 	 * @param integer $limit the limit
diff --git a/framework/db/QueryBuilder.php b/framework/db/QueryBuilder.php
index 35bfcb3..75375cc 100644
--- a/framework/db/QueryBuilder.php
+++ b/framework/db/QueryBuilder.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * QueryBuilder class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
@@ -62,10 +60,10 @@ class QueryBuilder extends \yii\base\Object
 			$this->buildFrom($query->from),
 			$this->buildJoin($query->join),
 			$this->buildWhere($query->where),
-			$this->buildGroup($query->groupBy),
+			$this->buildGroupBy($query->groupBy),
 			$this->buildHaving($query->having),
 			$this->buildUnion($query->union),
-			$this->buildOrder($query->orderBy),
+			$this->buildOrderBy($query->orderBy),
 			$this->buildLimit($query->limit, $query->offset),
 		);
 		return implode($this->separator, array_filter($clauses));
@@ -131,11 +129,10 @@ class QueryBuilder extends \yii\base\Object
 	 * @param string $table the table that new rows will be inserted into.
 	 * @param array $columns the column names
 	 * @param array $rows the rows to be batch inserted into the table
-	 * @param array $params the parameters to be bound to the command
 	 * @return string the batch INSERT SQL statement
 	 * @throws NotSupportedException if this is not supported by the underlying DBMS
 	 */
-	public function batchInsert($table, $columns, $rows, $params = array())
+	public function batchInsert($table, $columns, $rows)
 	{
 		throw new NotSupportedException($this->db->getDriverName() . ' does not support batch insert.');
 
@@ -593,21 +590,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) {
@@ -678,7 +673,7 @@ class QueryBuilder extends \yii\base\Object
 	}
 
 	/**
-	 * @param string|array $columns
+	 * @param array $columns
 	 * @param boolean $distinct
 	 * @param string $selectOption
 	 * @return string the SELECT clause built from [[query]].
@@ -694,13 +689,6 @@ class QueryBuilder extends \yii\base\Object
 			return $select . ' *';
 		}
 
-		if (!is_array($columns)) {
-			if (strpos($columns, '(') !== false) {
-				return $select . ' ' . $columns;
-			} else {
-				$columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
-			}
-		}
 		foreach ($columns as $i => $column) {
 			if (is_object($column)) {
 				$columns[$i] = (string)$column;
@@ -721,7 +709,7 @@ class QueryBuilder extends \yii\base\Object
 	}
 
 	/**
-	 * @param string|array $tables
+	 * @param array $tables
 	 * @return string the FROM clause built from [[query]].
 	 */
 	public function buildFrom($tables)
@@ -730,13 +718,6 @@ class QueryBuilder extends \yii\base\Object
 			return '';
 		}
 
-		if (!is_array($tables)) {
-			if (strpos($tables, '(') !== false) {
-				return 'FROM ' . $tables;
-			} else {
-				$tables = preg_split('/\s*,\s*/', trim($tables), -1, PREG_SPLIT_NO_EMPTY);
-			}
-		}
 		foreach ($tables as $i => $table) {
 			if (strpos($table, '(') === false) {
 				if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)(.*)$/i', $table, $matches)) { // with alias
@@ -757,37 +738,36 @@ class QueryBuilder extends \yii\base\Object
 	/**
 	 * @param string|array $joins
 	 * @return string the JOIN clause built from [[query]].
+	 * @throws Exception if the $joins parameter is not in proper format
 	 */
 	public function buildJoin($joins)
 	{
 		if (empty($joins)) {
 			return '';
 		}
-		if (is_string($joins)) {
-			return $joins;
-		}
 
 		foreach ($joins as $i => $join) {
-			if (is_array($join)) { // 0:join type, 1:table name, 2:on-condition
-				if (isset($join[0], $join[1])) {
-					$table = $join[1];
-					if (strpos($table, '(') === false) {
-						if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)(.*)$/', $table, $matches)) { // with alias
-							$table = $this->db->quoteTableName($matches[1]) . ' ' . $this->db->quoteTableName($matches[2]);
-						} else {
-							$table = $this->db->quoteTableName($table);
-						}
+			if (is_object($join)) {
+				$joins[$i] = (string)$join;
+			} elseif (is_array($join) && isset($join[0], $join[1])) {
+				// 0:join type, 1:table name, 2:on-condition
+				$table = $join[1];
+				if (strpos($table, '(') === false) {
+					if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)(.*)$/', $table, $matches)) { // with alias
+						$table = $this->db->quoteTableName($matches[1]) . ' ' . $this->db->quoteTableName($matches[2]);
+					} else {
+						$table = $this->db->quoteTableName($table);
 					}
-					$joins[$i] = $join[0] . ' ' . $table;
-					if (isset($join[2])) {
-						$condition = $this->buildCondition($join[2]);
-						if ($condition !== '') {
-							$joins[$i] .= ' ON ' . $this->buildCondition($join[2]);
-						}
+				}
+				$joins[$i] = $join[0] . ' ' . $table;
+				if (isset($join[2])) {
+					$condition = $this->buildCondition($join[2]);
+					if ($condition !== '') {
+						$joins[$i] .= ' ON ' . $this->buildCondition($join[2]);
 					}
-				} else {
-					throw new Exception('A join clause must be specified as an array of at least two elements.');
 				}
+			} else {
+				throw new Exception('A join clause must be specified as an array of join type, join table, and optionally join condition.');
 			}
 		}
 
@@ -805,16 +785,12 @@ class QueryBuilder extends \yii\base\Object
 	}
 
 	/**
-	 * @param string|array $columns
+	 * @param array $columns
 	 * @return string the GROUP BY clause
 	 */
-	public function buildGroup($columns)
+	public function buildGroupBy($columns)
 	{
-		if (empty($columns)) {
-			return '';
-		} else {
-			return 'GROUP BY ' . $this->buildColumns($columns);
-		}
+		return empty($columns) ? '' : 'GROUP BY ' . $this->buildColumns($columns);
 	}
 
 	/**
@@ -828,36 +804,24 @@ class QueryBuilder extends \yii\base\Object
 	}
 
 	/**
-	 * @param string|array $columns
+	 * @param array $columns
 	 * @return string the ORDER BY clause built from [[query]].
 	 */
-	public function buildOrder($columns)
+	public function buildOrderBy($columns)
 	{
 		if (empty($columns)) {
 			return '';
 		}
-		if (!is_array($columns)) {
-			if (strpos($columns, '(') !== false) {
-				return 'ORDER BY ' . $columns;
+		$orders = array();
+		foreach ($columns as $name => $direction) {
+			if (is_object($direction)) {
+				$orders[] = (string)$direction;
 			} else {
-				$columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
-			}
-		}
-		foreach ($columns as $i => $column) {
-			if (is_object($column)) {
-				$columns[$i] = (string)$column;
-			} elseif (strpos($column, '(') === false) {
-				if (preg_match('/^(.*?)\s+(asc|desc)$/i', $column, $matches)) {
-					$columns[$i] = $this->db->quoteColumnName($matches[1]) . ' ' . $matches[2];
-				} else {
-					$columns[$i] = $this->db->quoteColumnName($column);
-				}
+				$orders[] = $this->db->quoteColumnName($name) . ($direction === Query::SORT_DESC ? ' DESC' : '');
 			}
 		}
-		if (is_array($columns)) {
-			$columns = implode(', ', $columns);
-		}
-		return 'ORDER BY ' . $columns;
+
+		return 'ORDER BY ' . implode(', ', $orders);
 	}
 
 	/**
@@ -878,7 +842,7 @@ class QueryBuilder extends \yii\base\Object
 	}
 
 	/**
-	 * @param string|array $unions
+	 * @param array $unions
 	 * @return string the UNION clause built from [[query]].
 	 */
 	public function buildUnion($unions)
@@ -886,9 +850,6 @@ class QueryBuilder extends \yii\base\Object
 		if (empty($unions)) {
 			return '';
 		}
-		if (!is_array($unions)) {
-			$unions = array($unions);
-		}
 		foreach ($unions as $i => $union) {
 			if ($union instanceof Query) {
 				$unions[$i] = $this->build($union);
diff --git a/framework/db/Schema.php b/framework/db/Schema.php
index 7415bee..71bc9a2 100644
--- a/framework/db/Schema.php
+++ b/framework/db/Schema.php
@@ -1,14 +1,13 @@
 <?php
 /**
- * Driver class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
 namespace yii\db;
 
+use Yii;
 use yii\base\NotSupportedException;
 use yii\base\InvalidCallException;
 use yii\caching\Cache;
@@ -86,21 +85,21 @@ abstract class Schema extends \yii\base\Object
 		$db = $this->db;
 		$realName = $this->getRealTableName($name);
 
-		/** @var $cache Cache */
-		if ($db->enableSchemaCache && ($cache = \Yii::$application->getComponent($db->schemaCacheID)) !== null && !in_array($name, $db->schemaCacheExclude, true)) {
-			$key = $this->getCacheKey($cache, $name);
-			if ($refresh || ($table = $cache->get($key)) === false) {
-				$table = $this->loadTableSchema($realName);
-				if ($table !== null) {
-					$cache->set($key, $table, $db->schemaCacheDuration);
+		if ($db->enableSchemaCache && !in_array($name, $db->schemaCacheExclude, true)) {
+			/** @var $cache Cache */
+			$cache = is_string($db->schemaCache) ? Yii::$app->getComponent($db->schemaCache) : $db->schemaCache;
+			if ($cache instanceof Cache) {
+				$key = $this->getCacheKey($cache, $name);
+				if ($refresh || ($table = $cache->get($key)) === false) {
+					$table = $this->loadTableSchema($realName);
+					if ($table !== null) {
+						$cache->set($key, $table, $db->schemaCacheDuration);
+					}
 				}
+				return $this->_tables[$name] = $table;
 			}
-			$this->_tables[$name] = $table;
-		} else {
-			$this->_tables[$name] = $table = $this->loadTableSchema($realName);
 		}
-
-		return $table;
+		return $this->_tables[$name] = $table = $this->loadTableSchema($realName);
 	}
 
 	/**
@@ -111,7 +110,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,
+		));
 	}
 
 	/**
@@ -170,8 +174,9 @@ abstract class Schema extends \yii\base\Object
 	 */
 	public function refresh()
 	{
-		/** @var $cache \yii\caching\Cache */
-		if ($this->db->enableSchemaCache && ($cache = \Yii::$application->getComponent($this->db->schemaCacheID)) !== null) {
+		/** @var $cache Cache */
+		$cache = is_string($this->db->schemaCache) ? Yii::$app->getComponent($this->db->schemaCache) : $this->db->schemaCache;
+		if ($this->db->enableSchemaCache && $cache instanceof Cache) {
 			foreach ($this->_tables as $name => $table) {
 				$cache->delete($this->getCacheKey($cache, $name));
 			}
diff --git a/framework/db/TableSchema.php b/framework/db/TableSchema.php
index 987d221..1065b51 100644
--- a/framework/db/TableSchema.php
+++ b/framework/db/TableSchema.php
@@ -1,7 +1,5 @@
 <?php
 /**
- * TableSchema class file.
- *
  * @link http://www.yiiframework.com/
  * @copyright Copyright &copy; 2008-2011 Yii Software LLC
  * @license http://www.yiiframework.com/license/
@@ -9,7 +7,7 @@
 
 namespace yii\db;
 
-use yii\base\InvalidCallException;
+use yii\base\InvalidParamException;
 
 /**
  * TableSchema represents the metadata of a database table.
@@ -83,7 +81,7 @@ class TableSchema extends \yii\base\Object
 	/**
 	 * Manually specifies the primary key for this table.
 	 * @param string|array $keys the primary key (can be composite)
-	 * @throws InvalidCallException if the specified key cannot be found in the table.
+	 * @throws InvalidParamException if the specified key cannot be found in the table.
 	 */
 	public function fixPrimaryKey($keys)
 	{
@@ -98,7 +96,7 @@ class TableSchema extends \yii\base\Object
 			if (isset($this->columns[$key])) {
 				$this->columns[$key]->isPrimaryKey = true;
 			} else {
-				throw new InvalidCallException("Primary key '$key' cannot be found in table '{$this->name}'.");
+				throw new InvalidParamException("Primary key '$key' cannot be found in table '{$this->name}'.");
 			}
 		}
 	}
diff --git a/framework/db/Transaction.php b/framework/db/Transaction.php
index 3e53c0c..177d2cb 100644
--- a/framework/db/Transaction.php
+++ b/framework/db/Transaction.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * Transaction class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
diff --git a/framework/db/mysql/QueryBuilder.php b/framework/db/mysql/QueryBuilder.php
index 6168409..a078b9a 100644
--- a/framework/db/mysql/QueryBuilder.php
+++ b/framework/db/mysql/QueryBuilder.php
@@ -1,16 +1,14 @@
 <?php
 /**
- * QueryBuilder class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
 namespace yii\db\mysql;
 
 use yii\db\Exception;
-use yii\base\InvalidCallException;
+use yii\base\InvalidParamException;
 
 /**
  * QueryBuilder is the query builder for MySQL databases.
@@ -54,7 +52,7 @@ class QueryBuilder extends \yii\db\QueryBuilder
 		$quotedTable = $this->db->quoteTableName($table);
 		$row = $this->db->createCommand('SHOW CREATE TABLE ' . $quotedTable)->queryRow();
 		if ($row === false) {
-			throw new Exception("Unable to find '$oldName' in table '$table'.");
+			throw new Exception("Unable to find column '$oldName' in table '$table'.");
 		}
 		if (isset($row['Create Table'])) {
 			$sql = $row['Create Table'];
@@ -98,7 +96,7 @@ class QueryBuilder extends \yii\db\QueryBuilder
 	 * @param mixed $value the value for the primary key of the next new row inserted. If this is not set,
 	 * the next new row's primary key will have a value 1.
 	 * @return string the SQL statement for resetting sequence
-	 * @throws InvalidCallException if the table does not exist or there is no sequence associated with the table.
+	 * @throws InvalidParamException if the table does not exist or there is no sequence associated with the table.
 	 */
 	public function resetSequence($tableName, $value = null)
 	{
@@ -113,9 +111,9 @@ class QueryBuilder extends \yii\db\QueryBuilder
 			}
 			return "ALTER TABLE $tableName AUTO_INCREMENT=$value";
 		} elseif ($table === null) {
-			throw new InvalidCallException("Table not found: $tableName");
+			throw new InvalidParamException("Table not found: $tableName");
 		} else {
-			throw new InvalidCallException("There is not sequence associated with table '$tableName'.'");
+			throw new InvalidParamException("There is not sequence associated with table '$tableName'.'");
 		}
 	}
 
diff --git a/framework/db/mysql/Schema.php b/framework/db/mysql/Schema.php
index 32df0b3..501149a 100644
--- a/framework/db/mysql/Schema.php
+++ b/framework/db/mysql/Schema.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * Schema class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
diff --git a/framework/db/sqlite/QueryBuilder.php b/framework/db/sqlite/QueryBuilder.php
index b9b8f5e..3aa89e7 100644
--- a/framework/db/sqlite/QueryBuilder.php
+++ b/framework/db/sqlite/QueryBuilder.php
@@ -1,17 +1,15 @@
 <?php
 /**
- * QueryBuilder class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
 namespace yii\db\sqlite;
 
 use yii\db\Exception;
+use yii\base\InvalidParamException;
 use yii\base\NotSupportedException;
-use yii\base\InvalidCallException;
 
 /**
  * QueryBuilder is the query builder for SQLite databases.
@@ -50,7 +48,7 @@ class QueryBuilder extends \yii\db\QueryBuilder
 	 * @param mixed $value the value for the primary key of the next new row inserted. If this is not set,
 	 * the next new row's primary key will have a value 1.
 	 * @return string the SQL statement for resetting sequence
-	 * @throws InvalidCallException if the table does not exist or there is no sequence associated with the table.
+	 * @throws InvalidParamException if the table does not exist or there is no sequence associated with the table.
 	 */
 	public function resetSequence($tableName, $value = null)
 	{
@@ -70,9 +68,9 @@ class QueryBuilder extends \yii\db\QueryBuilder
 			} catch (Exception $e) {
 			}
 		} elseif ($table === null) {
-			throw new InvalidCallException("Table not found: $tableName");
+			throw new InvalidParamException("Table not found: $tableName");
 		} else {
-			throw new InvalidCallException("There is not sequence associated with table '$tableName'.'");
+			throw new InvalidParamException("There is not sequence associated with table '$tableName'.'");
 		}
 	}
 
diff --git a/framework/db/sqlite/Schema.php b/framework/db/sqlite/Schema.php
index 8f7cb08..45f8392 100644
--- a/framework/db/sqlite/Schema.php
+++ b/framework/db/sqlite/Schema.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * Schema class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
diff --git a/framework/util/ArrayHelper.php b/framework/helpers/ArrayHelper.php
similarity index 72%
rename from framework/util/ArrayHelper.php
rename to framework/helpers/ArrayHelper.php
index c14121b..65fa962 100644
--- a/framework/util/ArrayHelper.php
+++ b/framework/helpers/ArrayHelper.php
@@ -1,15 +1,14 @@
 <?php
 /**
- * ArrayHelper class file.
- *
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
-namespace yii\util;
+namespace yii\helpers;
 
-use yii\base\InvalidCallException;
+use Yii;
+use yii\base\InvalidParamException;
 
 /**
  * ArrayHelper provides additional array functionality you can use in your
@@ -60,11 +59,11 @@ class ArrayHelper
 	 *
 	 * ~~~
 	 * // working with array
-	 * $username = \yii\util\ArrayHelper::getValue($_POST, 'username');
+	 * $username = \yii\helpers\ArrayHelper::getValue($_POST, 'username');
 	 * // working with object
-	 * $username = \yii\util\ArrayHelper::getValue($user, 'username');
+	 * $username = \yii\helpers\ArrayHelper::getValue($user, 'username');
 	 * // working with anonymous function
-	 * $fullName = \yii\util\ArrayHelper::getValue($user, function($user, $defaultValue) {
+	 * $fullName = \yii\helpers\ArrayHelper::getValue($user, function($user, $defaultValue) {
 	 *     return $user->firstName . ' ' . $user->lastName;
 	 * });
 	 * ~~~
@@ -242,7 +241,7 @@ class ArrayHelper
 	 * 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 InvalidCallException if the $ascending or $sortFlag parameters do not have
+	 * @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)
@@ -255,12 +254,12 @@ class ArrayHelper
 		if (is_scalar($ascending)) {
 			$ascending = array_fill(0, $n, $ascending);
 		} elseif (count($ascending) !== $n) {
-			throw new InvalidCallException('The length of $ascending parameter must be the same as that of $keys.');
+			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 InvalidCallException('The length of $ascending parameter must be the same as that of $keys.');
+			throw new InvalidParamException('The length of $ascending parameter must be the same as that of $keys.');
 		}
 		$args = array();
 		foreach ($keys as $i => $key) {
@@ -281,4 +280,61 @@ 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 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/util/ConsoleColor.php b/framework/helpers/ConsoleColor.php
similarity index 92%
rename from framework/util/ConsoleColor.php
rename to framework/helpers/ConsoleColor.php
index 1fadc40..429aeb1 100644
--- a/framework/util/ConsoleColor.php
+++ b/framework/helpers/ConsoleColor.php
@@ -1,23 +1,13 @@
 <?php
 /**
- * ConsoleColor class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
-namespace yii\console;
+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
@@ -359,7 +349,7 @@ class ConsoleColor
 			}
 			$styleString[] = array();
 			foreach($styleA as $name => $content) {
-				if ($name = 'text-decoration') {
+				if ($name === 'text-decoration') {
 					$content = implode(' ', $content);
 				}
 				$styleString[] = $name.':'.$content;
diff --git a/framework/util/FileHelper.php b/framework/helpers/FileHelper.php
similarity index 97%
rename from framework/util/FileHelper.php
rename to framework/helpers/FileHelper.php
index c65e4f0..f850b98 100644
--- a/framework/util/FileHelper.php
+++ b/framework/helpers/FileHelper.php
@@ -3,11 +3,11 @@
  * Filesystem helper class file.
  *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
-namespace yii\util;
+namespace yii\helpers;
 
 use yii\base\Exception;
 use yii\base\InvalidConfigException;
@@ -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);
@@ -91,10 +91,10 @@ class FileHelper
 	public static function localize($file, $language = null, $sourceLanguage = null)
 	{
 		if ($language === null) {
-			$language = \Yii::$application->getLanguage();
+			$language = \Yii::$app->language;
 		}
 		if ($sourceLanguage === null) {
-			$sourceLanguage = \Yii::$application->sourceLanguage;
+			$sourceLanguage = \Yii::$app->sourceLanguage;
 		}
 		if ($language === $sourceLanguage) {
 			return $file;
diff --git a/framework/helpers/Html.php b/framework/helpers/Html.php
new file mode 100644
index 0000000..b2ca576
--- /dev/null
+++ b/framework/helpers/Html.php
@@ -0,0 +1,981 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\helpers;
+
+use Yii;
+use yii\base\InvalidParamException;
+
+/**
+ * Html provides a set of static methods for generating commonly used HTML tags.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class Html
+{
+	/**
+	 * @var boolean whether to close void (empty) elements. Defaults to true.
+	 * @see voidElements
+	 */
+	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(
+		'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 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',
+		'multiple',
+
+		'size',
+		'maxlength',
+		'width',
+		'height',
+		'rows',
+		'cols',
+
+		'alt',
+		'title',
+		'rel',
+		'media',
+	);
+
+	/**
+	 * Encodes special characters into HTML entities.
+	 * 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($content)
+	{
+		return htmlspecialchars($content, ENT_QUOTES, Yii::$app->charset);
+	}
+
+	/**
+	 * Decodes special HTML entities back to the corresponding characters.
+	 * 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($content)
+	{
+		return htmlspecialchars_decode($content, ENT_QUOTES);
+	}
+
+	/**
+	 * 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 $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
+	 * @see beginTag
+	 * @see endTag
+	 */
+	public static function tag($name, $content = '', $options = array())
+	{
+		$html = '<' . $name . static::renderTagAttributes($options);
+		if (isset(static::$voidElements[strtolower($name)])) {
+			return $html . (static::$closeVoidElements ? ' />' : '>');
+		} else {
+			return $html . ">$content</$name>";
+		}
+	}
+
+	/**
+	 * Generates a start tag.
+	 * @param string $name the tag name
+	 * @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
+	 * @see endTag
+	 * @see tag
+	 */
+	public static function beginTag($name, $options = array())
+	{
+		return '<' . $name . static::renderTagAttributes($options) . '>';
+	}
+
+	/**
+	 * Generates an end tag.
+	 * @param string $name the tag name
+	 * @return string the generated end tag
+	 * @see beginTag
+	 * @see tag
+	 */
+	public static function endTag($name)
+	{
+		return "</$name>";
+	}
+
+	/**
+	 * 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($content)
+	{
+		return '<![CDATA[' . $content . ']]>';
+	}
+
+	/**
+	 * Generates a style tag.
+	 * @param string $content the style content
+	 * @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.
+	 * @return string the generated style tag
+	 */
+	public static function style($content, $options = array())
+	{
+		if (!isset($options['type'])) {
+			$options['type'] = 'text/css';
+		}
+		return static::tag('style', "/*<![CDATA[*/\n{$content}\n/*]]>*/", $options);
+	}
+
+	/**
+	 * Generates a script tag.
+	 * @param string $content the script content
+	 * @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.
+	 * @return string the generated script tag
+	 */
+	public static function script($content, $options = array())
+	{
+		if (!isset($options['type'])) {
+			$options['type'] = 'text/javascript';
+		}
+		return static::tag('script', "/*<![CDATA[*/\n{$content}\n/*]]>*/", $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 $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
+	 * @see url
+	 */
+	public static function cssFile($url, $options = array())
+	{
+		$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 $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
+	 * @see url
+	 */
+	public static function jsFile($url, $options = array())
+	{
+		$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 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.
+	 * @see endForm
+	 */
+	public static function beginForm($action = '', $method = 'post', $options = array())
+	{
+		$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($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);
+		}
+
+		$options['action'] = $action;
+		$options['method'] = $method;
+		$form = static::beginTag('form', $options);
+		if ($hiddens !== array()) {
+			$form .= "\n" . implode("\n", $hiddens);
+		}
+
+		return $form;
+	}
+
+	/**
+	 * Generates a form end tag.
+	 * @return string the generated tag
+	 * @see beginForm
+	 */
+	public static function endForm()
+	{
+		return '</form>';
+	}
+
+	/**
+	 * Generates a hyperlink tag.
+	 * @param string $text link body. It will NOT be HTML-encoded. Therefore you can pass in HTML code
+	 * such as an image tag. If this is is coming from end users, you should consider [[encode()]]
+	 * 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 $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
+	 * @see url
+	 */
+	public static function a($text, $url = null, $options = array())
+	{
+		if ($url !== null) {
+			$options['href'] = static::url($url);
+		}
+		return static::tag('a', $text, $options);
+	}
+
+	/**
+	 * 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 $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
+	 */
+	public static function mailto($text, $email = null, $options = array())
+	{
+		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 $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
+	 */
+	public static function img($src, $options = array())
+	{
+		$options['src'] = static::url($src);
+		if (!isset($options['alt'])) {
+			$options['alt'] = '';
+		}
+		return static::tag('img', null, $options);
+	}
+
+	/**
+	 * 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 $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
+	 */
+	public static function label($content, $for = null, $options = array())
+	{
+		$options['for'] = $for;
+		return static::tag('label', $content, $options);
+	}
+
+	/**
+	 * 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 $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.
+	 * @return string the generated button tag
+	 */
+	public static function button($name = null, $value = null, $content = 'Button', $options = array())
+	{
+		$options['name'] = $name;
+		$options['value'] = $value;
+		if (!isset($options['type'])) {
+			$options['type'] = 'button';
+		}
+		return static::tag('button', $content, $options);
+	}
+
+	/**
+	 * 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 $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
+	 */
+	public static function submitButton($name = null, $value = null, $content = 'Submit', $options = array())
+	{
+		$options['type'] = 'submit';
+		return static::button($name, $value, $content, $options);
+	}
+
+	/**
+	 * 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 $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
+	 */
+	public static function resetButton($name = null, $value = null, $content = 'Reset', $options = array())
+	{
+		$options['type'] = 'reset';
+		return static::button($name, $value, $content, $options);
+	}
+
+	/**
+	 * 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 $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
+	 */
+	public static function input($type, $name = null, $value = null, $options = array())
+	{
+		$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 $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
+	 */
+	public static function buttonInput($name, $value = 'Button', $options = array())
+	{
+		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 $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
+	 */
+	public static function submitInput($name = null, $value = 'Submit', $options = array())
+	{
+		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 $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', $options = array())
+	{
+		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 $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
+	 */
+	public static function textInput($name, $value = null, $options = array())
+	{
+		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 $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
+	 */
+	public static function hiddenInput($name, $value = null, $options = array())
+	{
+		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 $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
+	 */
+	public static function passwordInput($name, $value = null, $options = array())
+	{
+		return static::input('password', $name, $value, $options);
+	}
+
+	/**
+	 * 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 $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
+	 */
+	public static function fileInput($name, $value = null, $options = array())
+	{
+		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 $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
+	 */
+	public static function textarea($name, $value = '', $options = array())
+	{
+		$options['name'] = $name;
+		return static::tag('textarea', static::encode($value), $options);
+	}
+
+	/**
+	 * Generates a radio button input.
+	 * @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 $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', $options = array())
+	{
+		$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, $options['uncheck']);
+			unset($options['uncheck']);
+		} else {
+			$hidden = '';
+		}
+		return $hidden . static::input('radio', $name, $value, $options);
+	}
+
+	/**
+	 * Generates a checkbox input.
+	 * @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 $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', $options = array())
+	{
+		$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, $options['uncheck']);
+			unset($options['uncheck']);
+		} else {
+			$hidden = '';
+		}
+		return $hidden . static::input('checkbox', $name, $value, $options);
+	}
+
+	/**
+	 * 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.
+	 * If you have a list of data models, you may convert them into the format described above using
+	 * [[\yii\helpers\ArrayHelper::map()]].
+	 *
+	 * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in
+	 * the labels will also be HTML-encoded.
+	 * @param array $options the tag options in terms of name-value pairs. The following options are supported:
+	 *
+	 * - prompt: string, a prompt text to be displayed as the first option;
+	 * - 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,
+	 *
+	 * ~~~
+	 * 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.
+	 *
+	 * 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(), $options = array())
+	{
+		$options['name'] = $name;
+		$selectOptions = static::renderSelectOptions($selection, $items, $options);
+		return static::tag('select', "\n" . $selectOptions . "\n", $options);
+	}
+
+	/**
+	 * 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.
+	 * If you have a list of data models, you may convert them into the format described above using
+	 * [[\yii\helpers\ArrayHelper::map()]].
+	 *
+	 * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in
+	 * the labels will also be HTML-encoded.
+	 * @param array $options the tag options in terms of name-value pairs. The following options are supported:
+	 *
+	 * - prompt: string, a prompt text to be displayed as the first option;
+	 * - 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,
+	 *
+	 * ~~~
+	 * 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.
+	 *
+	 * 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(), $options = array())
+	{
+		if (!isset($options['size'])) {
+			$options['size'] = 4;
+		}
+		if (isset($options['multiple']) && $options['multiple'] && substr($name, -2) !== '[]') {
+			$name .= '[]';
+		}
+		$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, $options['unselect']);
+			unset($options['unselect']);
+		} else {
+			$hidden = '';
+		}
+		$selectOptions = static::renderSelectOptions($selection, $items, $options);
+		return $hidden . static::tag('select', "\n" . $selectOptions . "\n", $options);
+	}
+
+	/**
+	 * 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 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 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, $checked, $value)
+	 * ~~~
+	 *
+	 * 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, $selection = null, $items = array(), $options = array())
+	{
+		if (substr($name, -2) !== '[]') {
+			$name .= '[]';
+		}
+
+		$formatter = isset($options['item']) ? $options['item'] : null;
+		$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, $checked, $value);
+			} else {
+				$lines[] = static::label(static::checkbox($name, $checked, $value) . ' ' . $label);
+			}
+			$index++;
+		}
+
+		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);
+	}
+
+	/**
+	 * 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 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, $checked, $value)
+	 * ~~~
+	 *
+	 * 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 radioList($name, $selection = null, $items = array(), $options = array())
+	{
+		$formatter = isset($options['item']) ? $options['item'] : null;
+		$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, $checked, $value);
+			} else {
+				$lines[] = static::label(static::radio($name, $checked, $value) . ' ' . $label);
+			}
+			$index++;
+		}
+
+		$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);
+	}
+
+	/**
+	 * 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.
+	 * If you have a list of data models, you may convert them into the format described above using
+	 * [[\yii\helpers\ArrayHelper::map()]].
+	 *
+	 * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in
+	 * the labels will also be HTML-encoded.
+	 * @param array $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, &$tagOptions = array())
+	{
+		$lines = array();
+		if (isset($tagOptions['prompt'])) {
+			$prompt = str_replace(' ', '&nbsp;', static::encode($tagOptions['prompt']));
+			$lines[] = static::tag('option', $prompt, array('value' => ''));
+		}
+
+		$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)) {
+				$groupAttrs = isset($groups[$key]) ? $groups[$key] : array();
+				$groupAttrs['label'] = $key;
+				$attrs = array('options' => $options, 'groups' => $groups);
+				$content = static::renderSelectOptions($selection, $value, $attrs);
+				$lines[] = static::tag('optgroup', "\n" . $content . "\n", $groupAttrs);
+			} else {
+				$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', str_replace(' ', '&nbsp;', static::encode($value)), $attrs);
+			}
+		}
+
+		return implode("\n", $lines);
+	}
+
+	/**
+	 * 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()]].
+	 * 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($attributes)
+	{
+		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 = '';
+		foreach ($attributes as $name => $value) {
+			if (isset(static::$booleanAttributes[strtolower($name)])) {
+				if ($value || strcasecmp($name, $value) === 0) {
+					$html .= static::$showBooleanAttributeValues ? " $name=\"$name\"" : " $name";
+				}
+			} elseif ($value !== null) {
+				$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()]] and returned;
+	 * - is an array: the first array element is considered a route, while the rest of the name-value
+	 *   pairs are treated as the parameters to be used for URL creation using [[\yii\web\Controller::createUrl()]].
+	 *   For example: `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])) {
+				$route = $url[0];
+				$params = array_splice($url, 1);
+				if (Yii::$app->controller !== null) {
+					return Yii::$app->controller->createUrl($route, $params);
+				} else {
+					return Yii::$app->getUrlManager()->createUrl($route, $params);
+				}
+			} 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/helpers/SecurityHelper.php b/framework/helpers/SecurityHelper.php
new file mode 100644
index 0000000..5029dd6
--- /dev/null
+++ b/framework/helpers/SecurityHelper.php
@@ -0,0 +1,272 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\helpers;
+
+use Yii;
+use yii\base\Exception;
+use yii\base\InvalidConfigException;
+use yii\base\InvalidParamException;
+
+/**
+ * SecurityHelper provides a set of methods to handle common security-related tasks.
+ *
+ * In particular, SecurityHelper supports the following features:
+ *
+ * - Encryption/decryption: [[encrypt()]] and [[decrypt()]]
+ * - Data tampering prevention: [[hashData()]] and [[validateData()]]
+ * - Password validation: [[generatePasswordHash()]] and [[validatePassword()]]
+ *
+ * Additionally, SecurityHelper provides [[getSecretKey()]] to support generating
+ * named secret keys. These secret keys, once generated, will be stored in a file
+ * and made available in future requests.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @author Tom Worster <fsb@thefsb.org>
+ * @since 2.0
+ */
+class SecurityHelper
+{
+	/**
+	 * Encrypts data.
+	 * @param string $data data to be encrypted.
+	 * @param string $key the encryption secret key
+	 * @return string the encrypted data
+	 * @throws Exception if PHP Mcrypt extension is not loaded or failed to be initialized
+	 * @see decrypt()
+	 */
+	public static function encrypt($data, $key)
+	{
+		$module = static::openCryptModule();
+		$key = StringHelper::substr($key, 0, mcrypt_enc_get_key_size($module));
+		srand();
+		$iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($module), MCRYPT_RAND);
+		mcrypt_generic_init($module, $key, $iv);
+		$encrypted = $iv . mcrypt_generic($module, $data);
+		mcrypt_generic_deinit($module);
+		mcrypt_module_close($module);
+		return $encrypted;
+	}
+
+	/**
+	 * Decrypts data
+	 * @param string $data data to be decrypted.
+	 * @param string $key the decryption secret key
+	 * @return string the decrypted data
+	 * @throws Exception if PHP Mcrypt extension is not loaded or failed to be initialized
+	 * @see encrypt()
+	 */
+	public static function decrypt($data, $key)
+	{
+		$module = static::openCryptModule();
+		$key = StringHelper::substr($key, 0, mcrypt_enc_get_key_size($module));
+		$ivSize = mcrypt_enc_get_iv_size($module);
+		$iv = StringHelper::substr($data, 0, $ivSize);
+		mcrypt_generic_init($module, $key, $iv);
+		$decrypted = mdecrypt_generic($module, StringHelper::substr($data, $ivSize, StringHelper::strlen($data)));
+		mcrypt_generic_deinit($module);
+		mcrypt_module_close($module);
+		return rtrim($decrypted, "\0");
+	}
+
+	/**
+	 * Prefixes data with a keyed hash value so that it can later be detected if it is tampered.
+	 * @param string $data the data to be protected
+	 * @param string $key the secret key to be used for generating hash
+	 * @param string $algorithm the hashing algorithm (e.g. "md5", "sha1", "sha256", etc.). Call PHP "hash_algos()"
+	 * function to see the supported hashing algorithms on your system.
+	 * @return string the data prefixed with the keyed hash
+	 * @see validateData()
+	 * @see getSecretKey()
+	 */
+	public static function hashData($data, $key, $algorithm = 'sha256')
+	{
+		return hash_hmac($algorithm, $data, $key) . $data;
+	}
+
+	/**
+	 * Validates if the given data is tampered.
+	 * @param string $data the data to be validated. The data must be previously
+	 * generated by [[hashData()]].
+	 * @param string $key the secret key that was previously used to generate the hash for the data in [[hashData()]].
+	 * @param string $algorithm the hashing algorithm (e.g. "md5", "sha1", "sha256", etc.). Call PHP "hash_algos()"
+	 * function to see the supported hashing algorithms on your system. This must be the same
+	 * as the value passed to [[hashData()]] when generating the hash for the data.
+	 * @return string the real data with the hash stripped off. False if the data is tampered.
+	 * @see hashData()
+	 */
+	public static function validateData($data, $key, $algorithm = 'sha256')
+	{
+		$hashSize = StringHelper::strlen(hash_hmac($algorithm, 'test', $key));
+		$n = StringHelper::strlen($data);
+		if ($n >= $hashSize) {
+			$hash = StringHelper::substr($data, 0, $hashSize);
+			$data2 = StringHelper::substr($data, $hashSize, $n - $hashSize);
+			return $hash === hash_hmac($algorithm, $data2, $key) ? $data2 : false;
+		} else {
+			return false;
+		}
+	}
+
+	/**
+	 * Returns a secret key associated with the specified name.
+	 * If the secret key does not exist, a random key will be generated
+	 * and saved in the file "keys.php" under the application's runtime directory
+	 * so that the same secret key can be returned in future requests.
+	 * @param string $name the name that is associated with the secret key
+	 * @param integer $length the length of the key that should be generated if not exists
+	 * @return string the secret key associated with the specified name
+	 */
+	public static function getSecretKey($name, $length = 32)
+	{
+		static $keys;
+		$keyFile = Yii::$app->getRuntimePath() . '/keys.php';
+		if ($keys === null) {
+			$keys = is_file($keyFile) ? require($keyFile) : array();
+		}
+		if (!isset($keys[$name])) {
+			// generate a 32-char random key
+			$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+			$keys[$name] = substr(str_shuffle(str_repeat($chars, 5)), 0, $length);
+			file_put_contents($keyFile, "<?php\nreturn " . var_export($keys, true) . ";\n");
+		}
+		return $keys[$name];
+	}
+
+	/**
+	 * Opens the mcrypt module.
+	 * @return resource the mcrypt module handle.
+	 * @throws InvalidConfigException if mcrypt extension is not installed
+	 * @throws Exception if mcrypt initialization fails
+	 */
+	protected static function openCryptModule()
+	{
+		if (!extension_loaded('mcrypt')) {
+			throw new InvalidConfigException('The mcrypt PHP extension is not installed.');
+		}
+		$module = @mcrypt_module_open('rijndael-256', '', MCRYPT_MODE_CBC, '');
+		if ($module === false) {
+			throw new Exception('Failed to initialize the mcrypt module.');
+		}
+		return $module;
+	}
+
+	/**
+	 * Generates a secure hash from a password and a random salt.
+	 *
+	 * The generated hash can be stored in database (e.g. `CHAR(64) CHARACTER SET latin1` on MySQL).
+	 * Later when a password needs to be validated, the hash can be fetched and passed
+	 * to [[validatePassword()]]. For example,
+	 *
+	 * ~~~
+	 * // generates the hash (usually done during user registration or when the password is changed)
+	 * $hash = SecurityHelper::hashPassword($password);
+	 * // ...save $hash in database...
+	 *
+	 * // during login, validate if the password entered is correct using $hash fetched from database
+	 * if (PasswordHelper::verifyPassword($password, $hash) {
+	 *     // password is good
+	 * } else {
+	 *     // password is bad
+	 * }
+	 * ~~~
+	 *
+	 * @param string $password The password to be hashed.
+	 * @param integer $cost Cost parameter used by the Blowfish hash algorithm.
+	 * The higher the value of cost,
+	 * the longer it takes to generate the hash and to verify a password against it. Higher cost
+	 * therefore slows down a brute-force attack. For best protection against brute for attacks,
+	 * set it to the highest value that is tolerable on production servers. The time taken to
+	 * compute the hash doubles for every increment by one of $cost. So, for example, if the
+	 * hash takes 1 second to compute when $cost is 14 then then the compute time varies as
+	 * 2^($cost - 14) seconds.
+	 * @throws Exception on bad password parameter or cost parameter
+	 * @return string The password hash string, ASCII and not longer than 64 characters.
+	 * @see validatePassword()
+	 */
+	public static function generatePasswordHash($password, $cost = 13)
+	{
+		$salt = static::generateSalt($cost);
+		$hash = crypt($password, $salt);
+
+		if (!is_string($hash) || strlen($hash) < 32) {
+			throw new Exception('Unknown error occurred while generating hash.');
+		}
+
+		return $hash;
+	}
+
+	/**
+	 * Verifies a password against a hash.
+	 * @param string $password The password to verify.
+	 * @param string $hash The hash to verify the password against.
+	 * @return boolean whether the password is correct.
+	 * @throws InvalidParamException on bad password or hash parameters or if crypt() with Blowfish hash is not available.
+	 * @see generatePasswordHash()
+	 */
+	public static function validatePassword($password, $hash)
+	{
+		if (!is_string($password) || $password === '') {
+			throw new InvalidParamException('Password must be a string and cannot be empty.');
+		}
+
+		if (!preg_match('/^\$2[axy]\$(\d\d)\$[\./0-9A-Za-z]{22}/', $hash, $matches) || $matches[1] < 4 || $matches[1] > 30) {
+			throw new InvalidParamException('Hash is invalid.');
+		}
+
+		$test = crypt($password, $hash);
+		$n = strlen($test);
+		if (strlen($test) < 32 || $n !== strlen($hash)) {
+			return false;
+		}
+
+		// Use a for-loop to compare two strings to prevent timing attacks. See:
+		// http://codereview.stackexchange.com/questions/13512
+		$check = 0;
+		for ($i = 0; $i < $n; ++$i) {
+			$check |= (ord($test[$i]) ^ ord($hash[$i]));
+		}
+
+		return $check === 0;
+	}
+
+	/**
+	 * Generates a salt that can be used to generate a password hash.
+	 *
+	 * The PHP [crypt()](http://php.net/manual/en/function.crypt.php) built-in function
+	 * requires, for the Blowfish hash algorithm, a salt string in a specific format:
+	 * "$2a$", "$2x$" or "$2y$", a two digit cost parameter, "$", and 22 characters
+	 * from the alphabet "./0-9A-Za-z".
+	 *
+	 * @param integer $cost the cost parameter
+	 * @return string the random salt value.
+	 * @throws InvalidParamException if the cost parameter is not between 4 and 30
+	 */
+	protected static function generateSalt($cost = 13)
+	{
+		$cost = (int)$cost;
+		if ($cost < 4 || $cost > 30) {
+			throw new InvalidParamException('Cost must be between 4 and 31.');
+		}
+
+		// Get 20 * 8bits of pseudo-random entropy from mt_rand().
+		$rand = '';
+		for ($i = 0; $i < 20; ++$i) {
+			$rand .= chr(mt_rand(0, 255));
+		}
+
+		// Add the microtime for a little more entropy.
+		$rand .= microtime();
+		// Mix the bits cryptographically into a 20-byte binary string.
+		$rand = sha1($rand, true);
+		// Form the prefix that specifies Blowfish algorithm and cost parameter.
+		$salt = sprintf("$2y$%02d$", $cost);
+		// Append the random salt data in the required base64 format.
+		$salt .= str_replace('+', '.', substr(base64_encode($rand), 0, 22));
+		return $salt;
+	}
+}
\ No newline at end of file
diff --git a/framework/util/StringHelper.php b/framework/helpers/StringHelper.php
similarity index 83%
rename from framework/util/StringHelper.php
rename to framework/helpers/StringHelper.php
index 776657e..ace34db 100644
--- a/framework/util/StringHelper.php
+++ b/framework/helpers/StringHelper.php
@@ -1,13 +1,11 @@
 <?php
 /**
- * StringHelper class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
-namespace yii\util;
+namespace yii\helpers;
 
 /**
  * StringHelper
@@ -19,6 +17,33 @@ namespace yii\util;
 class StringHelper
 {
 	/**
+	 * Returns the number of bytes in the given string.
+	 * This method ensures the string is treated as a byte array.
+	 * It will use `mb_strlen()` if it is available.
+	 * @param string $string the string being measured for length
+	 * @return integer the number of bytes in the given string.
+	 */
+	public static function strlen($string)
+	{
+		return function_exists('mb_strlen') ? mb_strlen($string, '8bit') : strlen($string);
+	}
+
+	/**
+	 * Returns the portion of string specified by the start and length parameters.
+	 * This method ensures the string is treated as a byte array.
+	 * It will use `mb_substr()` if it is available.
+	 * @param string $string the input string. Must be one character or longer.
+	 * @param integer $start the starting position
+	 * @param integer $length the desired portion length
+	 * @return string the extracted part of string, or FALSE on failure or an empty string.
+	 * @see http://www.php.net/manual/en/function.substr.php
+	 */
+	public static function substr($string, $start, $length)
+	{
+		return function_exists('mb_substr') ? mb_substr($string, $start, $length, '8bit') : substr($string, $start, $length);
+	}
+
+	/**
 	 * Converts a word to its plural form.
 	 * Note that this is for English only!
 	 * For example, 'apple' will become 'apples', and 'child' will become 'children'.
@@ -27,7 +52,7 @@ class StringHelper
 	 */
 	public static function pluralize($name)
 	{
-		$rules = array(
+		static $rules = array(
 			'/(m)ove$/i' => '\1oves',
 			'/(f)oot$/i' => '\1eet',
 			'/(c)hild$/i' => '\1hildren',
diff --git a/framework/util/VarDumper.php b/framework/helpers/VarDumper.php
similarity index 56%
rename from framework/util/VarDumper.php
rename to framework/helpers/VarDumper.php
index 7497a03..64c3639 100644
--- a/framework/util/VarDumper.php
+++ b/framework/helpers/VarDumper.php
@@ -1,14 +1,12 @@
 <?php
 /**
- * VarDumper class file.
- *
  * @author Qiang Xue <qiang.xue@gmail.com>
  * @link http://www.yiiframework.com/
  * @copyright Copyright &copy; 2008-2011 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
-namespace yii\util;
+namespace yii\helpers;
 
 /**
  * VarDumper is intended to replace the buggy PHP function var_dump and print_r.
@@ -17,14 +15,15 @@ namespace yii\util;
  * recursive display of some peculiar variables.
  *
  * VarDumper can be used as follows,
- * <pre>
+ *
+ * ~~~
  * VarDumper::dump($var);
- * </pre>
+ * ~~~
  *
  * @author Qiang Xue <qiang.xue@gmail.com>
  * @since 2.0
  */
-class VarDumper
+class CVarDumper
 {
 	private static $_objects;
 	private static $_output;
@@ -38,9 +37,9 @@ class VarDumper
 	 * @param integer $depth maximum depth that the dumper should go into the variable. Defaults to 10.
 	 * @param boolean $highlight whether the result should be syntax-highlighted
 	 */
-	public static function dump($var,$depth=10,$highlight=false)
+	public static function dump($var, $depth = 10, $highlight = false)
 	{
-		echo self::dumpAsString($var,$depth,$highlight);
+		echo self::dumpAsString($var, $depth, $highlight);
 	}
 
 	/**
@@ -52,16 +51,15 @@ class VarDumper
 	 * @param boolean $highlight whether the result should be syntax-highlighted
 	 * @return string the string representation of the variable
 	 */
-	public static function dumpAsString($var,$depth=10,$highlight=false)
+	public static function dumpAsString($var, $depth = 10, $highlight = false)
 	{
-		self::$_output='';
-		self::$_objects=array();
-		self::$_depth=$depth;
-		self::dumpInternal($var,0);
-		if($highlight)
-		{
-			$result=highlight_string("<?php\n".self::$_output,true);
-			self::$_output=preg_replace('/&lt;\\?php<br \\/>/','',$result,1);
+		self::$_output = '';
+		self::$_objects = array();
+		self::$_depth = $depth;
+		self::dumpInternal($var, 0);
+		if ($highlight) {
+			$result = highlight_string("<?php\n" . self::$_output, true);
+			self::$_output = preg_replace('/&lt;\\?php<br \\/>/', '', $result, 1);
 		}
 		return self::$_output;
 	}
@@ -70,73 +68,65 @@ class VarDumper
 	 * @param mixed $var variable to be dumped
 	 * @param integer $level depth level
 	 */
-	private static function dumpInternal($var,$level)
+	private static function dumpInternal($var, $level)
 	{
-		switch(gettype($var))
-		{
+		switch (gettype($var)) {
 			case 'boolean':
-				self::$_output.=$var?'true':'false';
+				self::$_output .= $var ? 'true' : 'false';
 				break;
 			case 'integer':
-				self::$_output.="$var";
+				self::$_output .= "$var";
 				break;
 			case 'double':
-				self::$_output.="$var";
+				self::$_output .= "$var";
 				break;
 			case 'string':
-				self::$_output.="'".addslashes($var)."'";
+				self::$_output .= "'" . addslashes($var) . "'";
 				break;
 			case 'resource':
-				self::$_output.='{resource}';
+				self::$_output .= '{resource}';
 				break;
 			case 'NULL':
-				self::$_output.="null";
+				self::$_output .= "null";
 				break;
 			case 'unknown type':
-				self::$_output.='{unknown}';
+				self::$_output .= '{unknown}';
 				break;
 			case 'array':
-				if(self::$_depth<=$level)
-					self::$_output.='array(...)';
-				else if(empty($var))
-					self::$_output.='array()';
-				else
-				{
-					$keys=array_keys($var);
-					$spaces=str_repeat(' ',$level*4);
-					self::$_output.="array\n".$spaces.'(';
-					foreach($keys as $key)
-					{
-						if(gettype($key)=='integer')
-							$key2=$key;
-						else
-							$key2="'".str_replace("'","\\'",$key)."'";
-
-						self::$_output.="\n".$spaces."    $key2 => ";
-						self::$_output.=self::dumpInternal($var[$key],$level+1);
+				if (self::$_depth <= $level) {
+					self::$_output .= 'array(...)';
+				} elseif (empty($var)) {
+					self::$_output .= 'array()';
+				} else {
+					$keys = array_keys($var);
+					$spaces = str_repeat(' ', $level * 4);
+					self::$_output .= "array\n" . $spaces . '(';
+					foreach ($keys as $key) {
+						self::$_output .= "\n" . $spaces . '    ';
+						self::dumpInternal($key, 0);
+						self::$_output .= ' => ';
+						self::dumpInternal($var[$key], $level + 1);
 					}
-					self::$_output.="\n".$spaces.')';
+					self::$_output .= "\n" . $spaces . ')';
 				}
 				break;
 			case 'object':
-				if(($id=array_search($var,self::$_objects,true))!==false)
-					self::$_output.=get_class($var).'#'.($id+1).'(...)';
-				else if(self::$_depth<=$level)
-					self::$_output.=get_class($var).'(...)';
-				else
-				{
-					$id=array_push(self::$_objects,$var);
-					$className=get_class($var);
-					$members=(array)$var;
-					$spaces=str_repeat(' ',$level*4);
-					self::$_output.="$className#$id\n".$spaces.'(';
-					foreach($members as $key=>$value)
-					{
-						$keyDisplay=strtr(trim($key),array("\0"=>':'));
-						self::$_output.="\n".$spaces."    [$keyDisplay] => ";
-						self::$_output.=self::dumpInternal($value,$level+1);
+				if (($id = array_search($var, self::$_objects, true)) !== false) {
+					self::$_output .= get_class($var) . '#' . ($id + 1) . '(...)';
+				} elseif (self::$_depth <= $level) {
+					self::$_output .= get_class($var) . '(...)';
+				} else {
+					$id = self::$_objects[] = $var;
+					$className = get_class($var);
+					$members = (array)$var;
+					$spaces = str_repeat(' ', $level * 4);
+					self::$_output .= "$className#$id\n" . $spaces . '(';
+					foreach ($members as $key => $value) {
+						$keyDisplay = strtr(trim($key), array("\0" => ':'));
+						self::$_output .= "\n" . $spaces . "    [$keyDisplay] => ";
+						self::dumpInternal($value, $level + 1);
 					}
-					self::$_output.="\n".$spaces.')';
+					self::$_output .= "\n" . $spaces . ')';
 				}
 				break;
 		}
diff --git a/framework/util/mimeTypes.php b/framework/helpers/mimeTypes.php
similarity index 99%
rename from framework/util/mimeTypes.php
rename to framework/helpers/mimeTypes.php
index 87295f8..ffdba4b 100644
--- a/framework/util/mimeTypes.php
+++ b/framework/helpers/mimeTypes.php
@@ -6,7 +6,7 @@
  * according to file extension names.
  *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  * @since 2.0
  */
diff --git a/framework/i18n/I18N.php b/framework/i18n/I18N.php
new file mode 100644
index 0000000..0409da3
--- /dev/null
+++ b/framework/i18n/I18N.php
@@ -0,0 +1,119 @@
+<?php
+
+namespace yii\i18n;
+
+use Yii;
+use yii\base\Component;
+use yii\base\InvalidConfigException;
+
+class I18N extends Component
+{
+	/**
+	 * @var array list of [[MessageSource]] configurations or objects. The array keys are message
+	 * categories, and the array values are the corresponding [[MessageSource]] objects or the configurations
+	 * for creating the [[MessageSource]] objects. The message categories can contain the wildcard '*' at the end
+	 * to match multiple categories with the same prefix. For example, 'app\*' matches both 'app\cat1' and 'app\cat2'.
+	 */
+	public $translations;
+
+	public function init()
+	{
+		if (!isset($this->translations['yii'])) {
+			$this->translations['yii'] = array(
+				'class' => 'yii\i18n\PhpMessageSource',
+				'sourceLanguage' => 'en_US',
+				'basePath' => '@yii/messages',
+			);
+		}
+		if (!isset($this->translations['app'])) {
+			$this->translations['app'] = array(
+				'class' => 'yii\i18n\PhpMessageSource',
+				'sourceLanguage' => 'en_US',
+				'basePath' => '@app/messages',
+			);
+		}
+	}
+
+	public function translate($message, $params = array(), $language = null)
+	{
+		if ($language === null) {
+			$language = Yii::$app->language;
+		}
+
+		// allow chars for category: word chars, ".", "-", "/","\"
+		if (strpos($message, '|') !== false && preg_match('/^([\w\-\\/\.\\\\]+)\|(.*)/', $message, $matches)) {
+			$category = $matches[1];
+			$message = $matches[2];
+		} else {
+			$category = 'app';
+		}
+
+		$message = $this->getMessageSource($category)->translate($category, $message, $language);
+
+		if (!is_array($params)) {
+			$params = array($params);
+		}
+
+		if (isset($params[0])) {
+			$message = $this->getPluralForm($message, $params[0], $language);
+			if (!isset($params['{n}'])) {
+				$params['{n}'] = $params[0];
+			}
+			unset($params[0]);
+		}
+
+		return $params === array() ? $message : strtr($message, $params);
+	}
+
+	public function getMessageSource($category)
+	{
+		if (isset($this->translations[$category])) {
+			$source = $this->translations[$category];
+		} else {
+			// try wildcard matching
+			foreach ($this->translations as $pattern => $config) {
+				if (substr($pattern, -1) === '*' && strpos($category, rtrim($pattern, '*')) === 0) {
+					$source = $config;
+					break;
+				}
+			}
+		}
+		if (isset($source)) {
+			return $source instanceof MessageSource ? $source : Yii::createObject($source);
+		} else {
+			throw new InvalidConfigException("Unable to locate message source for category '$category'.");
+		}
+	}
+
+	public function getLocale($language)
+	{
+
+	}
+
+	protected function getPluralForm($message, $number, $language)
+	{
+		if (strpos($message, '|') === false) {
+			return $message;
+		}
+		$chunks = explode('|', $message);
+		$rules = $this->getLocale($language)->getPluralRules();
+		foreach ($rules as $i => $rule) {
+			if (isset($chunks[$i]) && $this->evaluate($rule, $number)) {
+				return $chunks[$i];
+			}
+		}
+		$n = count($rules);
+		return isset($chunks[$n]) ? $chunks[$n] : $chunks[0];
+	}
+
+	/**
+	 * Evaluates a PHP expression with the given number value.
+	 * @param string $expression the PHP expression
+	 * @param mixed $n the number value
+	 * @return boolean the expression result
+	 */
+	protected function evaluate($expression, $n)
+	{
+		return @eval("return $expression;");
+	}
+}
diff --git a/framework/i18n/MessageSource.php b/framework/i18n/MessageSource.php
new file mode 100644
index 0000000..cf23338
--- /dev/null
+++ b/framework/i18n/MessageSource.php
@@ -0,0 +1,121 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\i18n;
+
+use Yii;
+use yii\base\Component;
+
+/**
+ * MessageSource is the base class for message translation repository classes.
+ *
+ * A message source stores message translations in some persistent storage.
+ *
+ * Child classes should override [[loadMessages()]] to provide translated messages.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class MessageSource extends Component
+{
+	/**
+	 * @event MissingTranslationEvent an event that is triggered when a message translation is not found.
+	 */
+	const EVENT_MISSING_TRANSLATION = 'missingTranslation';
+
+	/**
+	 * @var boolean whether to force message translation when the source and target languages are the same.
+	 * Defaults to false, meaning translation is only performed when source and target languages are different.
+	 */
+	public $forceTranslation = false;
+	/**
+	 * @var string the language that the original messages are in. If not set, it will use the value of
+	 * [[\yii\base\Application::sourceLanguage]].
+	 */
+	public $sourceLanguage;
+
+	private $_messages = array();
+
+	/**
+	 * Initializes this component.
+	 */
+	public function init()
+	{
+		parent::init();
+		if ($this->sourceLanguage === null) {
+			$this->sourceLanguage = Yii::$app->sourceLanguage;
+		}
+	}
+
+	/**
+	 * Loads the message translation for the specified language and category.
+	 * Child classes should override this method to return the message translations of
+	 * the specified language and category.
+	 * @param string $category the message category
+	 * @param string $language the target language
+	 * @return array the loaded messages. The keys are original messages, and the values
+	 * are translated messages.
+	 */
+	protected function loadMessages($category, $language)
+	{
+		return array();
+	}
+
+	/**
+	 * Translates a message to the specified language.
+	 *
+	 * Note that unless [[forceTranslation]] is true, if the target language
+	 * is the same as the [[sourceLanguage|source language]], the message
+	 * will NOT be translated.
+	 *
+	 * If a translation is not found, a [[missingTranslation]] event will be triggered.
+	 *
+	 * @param string $category the message category
+	 * @param string $message the message to be translated
+	 * @param string $language the target language
+	 * @return string the translated message (or the original message if translation is not needed)
+	 */
+	public function translate($category, $message, $language)
+	{
+		if ($this->forceTranslation || $language !== $this->sourceLanguage) {
+			return $this->translateMessage($category, $message, $language);
+		} else {
+			return $message;
+		}
+	}
+
+	/**
+	 * Translates the specified message.
+	 * If the message is not found, a [[missingTranslation]] event will be triggered
+	 * and the original message will be returned.
+	 * @param string $category the category that the message belongs to
+	 * @param string $message the message to be translated
+	 * @param string $language the target language
+	 * @return string the translated message
+	 */
+	protected function translateMessage($category, $message, $language)
+	{
+		$key = $language . '/' . $category;
+		if (!isset($this->_messages[$key])) {
+			$this->_messages[$key] = $this->loadMessages($category, $language);
+		}
+		if (isset($this->_messages[$key][$message]) && $this->_messages[$key][$message] !== '') {
+			return $this->_messages[$key][$message];
+		} elseif ($this->hasEventHandlers('missingTranslation')) {
+			$event = new MissingTranslationEvent(array(
+				'category' => $category,
+				'message' => $message,
+				'language' => $language,
+			));
+			$this->trigger(self::EVENT_MISSING_TRANSLATION, $event);
+			return $this->_messages[$key] = $event->message;
+		} else {
+			return $message;
+		}
+	}
+}
+
diff --git a/framework/i18n/MissingTranslationEvent.php b/framework/i18n/MissingTranslationEvent.php
new file mode 100644
index 0000000..9ac337a
--- /dev/null
+++ b/framework/i18n/MissingTranslationEvent.php
@@ -0,0 +1,33 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\i18n;
+
+use yii\base\Event;
+
+/**
+ * MissingTranslationEvent represents the parameter for the [[MessageSource::missingTranslation]] event.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class MissingTranslationEvent extends Event
+{
+	/**
+	 * @var string the message to be translated. An event handler may overwrite this property
+	 * with a translated version if possible.
+	 */
+	public $message;
+	/**
+	 * @var string the category that the message belongs to
+	 */
+	public $category;
+	/**
+	 * @var string the language ID (e.g. en_US) that the message is to be translated to
+	 */
+	public $language;
+}
diff --git a/framework/i18n/PhpMessageSource.php b/framework/i18n/PhpMessageSource.php
new file mode 100644
index 0000000..6b12353
--- /dev/null
+++ b/framework/i18n/PhpMessageSource.php
@@ -0,0 +1,79 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\i18n;
+
+use Yii;
+
+/**
+ * PhpMessageSource represents a message source that stores translated messages in PHP scripts.
+ *
+ * PhpMessageSource uses PHP arrays to keep message translations.
+ *
+ * - Each PHP script contains one array which stores the message translations in one particular
+ *   language and for a single message category;
+ * - Each PHP script is saved as a file named as `[[basePath]]/LanguageID/CategoryName.php`;
+ * - Within each PHP script, the message translations are returned as an array like the following:
+ *
+ * ~~~
+ * return array(
+ *     'original message 1' => 'translated message 1',
+ *     'original message 2' => 'translated message 2',
+ * );
+ * ~~~
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class PhpMessageSource extends MessageSource
+{
+	/**
+	 * @var string the base path for all translated messages. Defaults to null, meaning
+	 * the "messages" subdirectory of the application directory (e.g. "protected/messages").
+	 */
+	public $basePath = '@app/messages';
+	/**
+	 * @var array mapping between message categories and the corresponding message file paths.
+	 * The file paths are relative to [[basePath]]. For example,
+	 *
+	 * ~~~
+	 * array(
+	 *     'core' => 'core.php',
+	 *     'ext' => 'extensions.php',
+	 * )
+	 * ~~~
+	 */
+	public $fileMap;
+
+	/**
+	 * Loads the message translation for the specified language and category.
+	 * @param string $category the message category
+	 * @param string $language the target language
+	 * @return array the loaded messages
+	 */
+	protected function loadMessages($category, $language)
+	{
+		$messageFile = Yii::getAlias($this->basePath) . "/$language/";
+		if (isset($this->fileMap[$category])) {
+			$messageFile .= $this->fileMap[$category];
+		} elseif (($pos = strrpos($category, '\\')) !== false) {
+			$messageFile .= (substr($category, $pos) . '.php');
+		} else {
+			$messageFile .= "$category.php";
+		}
+		if (is_file($messageFile)) {
+			$messages = include($messageFile);
+			if (!is_array($messages)) {
+				$messages = array();
+			}
+			return $messages;
+		} else {
+			Yii::error("The message file for category '$category' does not exist: $messageFile", __CLASS__);
+			return array();
+		}
+	}
+}
\ No newline at end of file
diff --git a/framework/logging/DbTarget.php b/framework/logging/DbTarget.php
index 129e4d4..e4e30ce 100644
--- a/framework/logging/DbTarget.php
+++ b/framework/logging/DbTarget.php
@@ -1,24 +1,21 @@
 <?php
 /**
- * DbTarget class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
 namespace yii\logging;
 
+use Yii;
 use yii\db\Connection;
 use yii\base\InvalidConfigException;
 
 /**
  * DbTarget stores log messages in a database table.
  *
- * By default, DbTarget will use the database specified by [[connectionID]] and save
- * messages into a table named by [[tableName]]. Please refer to [[tableName]] for the required
- * table structure. Note that this table must be created beforehand. Otherwise an exception
- * will be thrown when DbTarget is saving messages into DB.
+ * By default, DbTarget stores the log messages in a DB table named 'tbl_log'. This table
+ * must be pre-created. The table name can be changed by setting [[logTable]].
  *
  * @author Qiang Xue <qiang.xue@gmail.com>
  * @since 2.0
@@ -26,20 +23,18 @@ use yii\base\InvalidConfigException;
 class DbTarget extends Target
 {
 	/**
-	 * @var string the ID of [[Connection]] application component.
-	 * Defaults to 'db'. Please make sure that your database contains a table
-	 * whose name is as specified in [[tableName]] and has the required table structure.
-	 * @see tableName
+	 * @var Connection|string the DB connection object or the application component ID of the DB connection.
+	 * After the DbTarget object is created, if you want to change this property, you should only assign it
+	 * with a DB connection object.
 	 */
-	public $connectionID = 'db';
+	public $db = 'db';
 	/**
-	 * @var string the name of the DB table that stores log messages. Defaults to 'tbl_log'.
-	 *
-	 * The DB table should have the following structure:
+	 * @var string name of the DB table to store cache content.
+	 * The table should be pre-created as follows:
 	 *
 	 * ~~~
 	 * CREATE TABLE tbl_log (
-	 *	   id       INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
+	 *	   id       BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
 	 *	   level    INTEGER,
 	 *	   category VARCHAR(255),
 	 *	   log_time INTEGER,
@@ -50,42 +45,29 @@ class DbTarget extends Target
 	 * ~~~
 	 *
 	 * Note that the 'id' column must be created as an auto-incremental column.
-	 * The above SQL shows the syntax of MySQL. If you are using other DBMS, you need
+	 * The above SQL uses the MySQL syntax. If you are using other DBMS, you need
 	 * to adjust it accordingly. For example, in PostgreSQL, it should be `id SERIAL PRIMARY KEY`.
 	 *
 	 * The indexes declared above are not required. They are mainly used to improve the performance
 	 * of some queries about message levels and categories. Depending on your actual needs, you may
-	 * want to create additional indexes (e.g. index on log_time).
+	 * want to create additional indexes (e.g. index on `log_time`).
 	 */
-	public $tableName = 'tbl_log';
-
-	private $_db;
+	public $logTable = 'tbl_log';
 
 	/**
-	 * Returns the DB connection used for saving log messages.
-	 * @return Connection the DB connection instance
-	 * @throws InvalidConfigException if [[connectionID]] does not point to a valid application component.
+	 * Initializes the DbTarget component.
+	 * This method will initialize the [[db]] property to make sure it refers to a valid DB connection.
+	 * @throws InvalidConfigException if [[db]] is invalid.
 	 */
-	public function getDb()
+	public function init()
 	{
-		if ($this->_db === null) {
-			$db = \Yii::$application->getComponent($this->connectionID);
-			if ($db instanceof Connection) {
-				$this->_db = $db;
-			} else {
-				throw new InvalidConfigException("DbTarget::connectionID must refer to the ID of a DB application component.");
-			}
+		parent::init();
+		if (is_string($this->db)) {
+			$this->db = Yii::$app->getComponent($this->db);
+		}
+		if (!$this->db instanceof Connection) {
+			throw new InvalidConfigException("DbTarget::db must be either a DB connection instance or the application component ID of a DB connection.");
 		}
-		return $this->_db;
-	}
-
-	/**
-	 * Sets the DB connection used by the cache component.
-	 * @param Connection $value the DB connection instance
-	 */
-	public function setDb($value)
-	{
-		$this->_db = $value;
 	}
 
 	/**
@@ -95,10 +77,9 @@ class DbTarget extends Target
 	 */
 	public function export($messages)
 	{
-		$db = $this->getDb();
-		$tableName = $db->quoteTableName($this->tableName);
+		$tableName = $this->db->quoteTableName($this->logTable);
 		$sql = "INSERT INTO $tableName (level, category, log_time, message) VALUES (:level, :category, :log_time, :message)";
-		$command = $db->createCommand($sql);
+		$command = $this->db->createCommand($sql);
 		foreach ($messages as $message) {
 			$command->bindValues(array(
 				':level' => $message[1],
diff --git a/framework/logging/EmailTarget.php b/framework/logging/EmailTarget.php
index e02e4da..4c84739 100644
--- a/framework/logging/EmailTarget.php
+++ b/framework/logging/EmailTarget.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * EmailTarget class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
@@ -50,7 +48,7 @@ class EmailTarget extends Target
 			$body .= $this->formatMessage($message);
 		}
 		$body = wordwrap($body, 70);
-		$subject = $this->subject === null ? \Yii::t('yii', 'Application Log') : $this->subject;
+		$subject = $this->subject === null ? \Yii::t('yii|Application Log') : $this->subject;
 		foreach ($this->emails as $email) {
 			$this->sendEmail($subject, $body, $email, $this->sentFrom, $this->headers);
 		}
diff --git a/framework/logging/FileTarget.php b/framework/logging/FileTarget.php
index 0eb897e..c3f4031 100644
--- a/framework/logging/FileTarget.php
+++ b/framework/logging/FileTarget.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * FileTarget class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
@@ -48,7 +46,7 @@ class FileTarget extends Target
 	{
 		parent::init();
 		if ($this->logFile === null) {
-			$this->logFile = \Yii::$application->getRuntimePath() . DIRECTORY_SEPARATOR . 'application.log';
+			$this->logFile = \Yii::$app->getRuntimePath() . DIRECTORY_SEPARATOR . 'application.log';
 		} else {
 			$this->logFile = \Yii::getAlias($this->logFile);
 		}
diff --git a/framework/logging/Logger.php b/framework/logging/Logger.php
index a8ffb5e..607c388 100644
--- a/framework/logging/Logger.php
+++ b/framework/logging/Logger.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * Logger class file
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
diff --git a/framework/logging/ProfileTarget.php b/framework/logging/ProfileTarget.php
index 716e6b7..2b6ffe6 100644
--- a/framework/logging/ProfileTarget.php
+++ b/framework/logging/ProfileTarget.php
@@ -1,7 +1,5 @@
 <?php
 /**
- * CProfileLogRoute class file.
- *
  * @link http://www.yiiframework.com/
  * @copyright Copyright &copy; 2008-2011 Yii Software LLC
  * @license http://www.yiiframework.com/license/
@@ -61,7 +59,7 @@ class CProfileLogRoute extends CWebLogRoute
 		if ($value === 'summary' || $value === 'callstack')
 			$this->_report = $value;
 		else
-			throw new CException(Yii::t('yii', 'CProfileLogRoute.report "{report}" is invalid. Valid values include "summary" and "callstack".',
+			throw new CException(Yii::t('yii|CProfileLogRoute.report "{report}" is invalid. Valid values include "summary" and "callstack".',
 				array('{report}' => $value)));
 	}
 
@@ -71,7 +69,7 @@ class CProfileLogRoute extends CWebLogRoute
 	 */
 	public function processLogs($logs)
 	{
-		$app = \Yii::$application;
+		$app = \Yii::$app;
 		if (!($app instanceof CWebApplication) || $app->getRequest()->getIsAjaxRequest())
 			return;
 
@@ -108,7 +106,7 @@ class CProfileLogRoute extends CWebLogRoute
 					$results[$last[4]] = array($token, $delta, count($stack));
 				} else
 				{
-					throw new CException(Yii::t('yii', 'CProfileLogRoute found a mismatching code block "{token}". Make sure the calls to Yii::beginProfile() and Yii::endProfile() be properly nested.',
+					throw new CException(Yii::t('yii|CProfileLogRoute found a mismatching code block "{token}". Make sure the calls to Yii::beginProfile() and Yii::endProfile() be properly nested.',
 						array('{token}' => $token)));
 				}
 			}
@@ -151,7 +149,7 @@ class CProfileLogRoute extends CWebLogRoute
 					else
 						$results[$token] = array($token, 1, $delta, $delta, $delta);
 				} else
-					throw new CException(Yii::t('yii', 'CProfileLogRoute found a mismatching code block "{token}". Make sure the calls to Yii::beginProfile() and Yii::endProfile() be properly nested.',
+					throw new CException(Yii::t('yii|CProfileLogRoute found a mismatching code block "{token}". Make sure the calls to Yii::beginProfile() and Yii::endProfile() be properly nested.',
 						array('{token}' => $token)));
 			}
 		}
diff --git a/framework/logging/Router.php b/framework/logging/Router.php
index 2e6a8dd..2f399fe 100644
--- a/framework/logging/Router.php
+++ b/framework/logging/Router.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * Router class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
@@ -52,7 +50,7 @@ use yii\base\Application;
  * as follows:
  *
  * ~~~
- * Yii::$application->log->targets['file']->enabled = false;
+ * Yii::$app->log->targets['file']->enabled = false;
  * ~~~
  *
  * @author Qiang Xue <qiang.xue@gmail.com>
diff --git a/framework/logging/Target.php b/framework/logging/Target.php
index c9e175a..b88e78d 100644
--- a/framework/logging/Target.php
+++ b/framework/logging/Target.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * Target class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
@@ -110,7 +108,7 @@ abstract class Target extends \yii\base\Component
 	protected function getContextMessage()
 	{
 		$context = array();
-		if ($this->logUser && ($user = \Yii::$application->getComponent('user', false)) !== null) {
+		if ($this->logUser && ($user = \Yii::$app->getComponent('user', false)) !== null) {
 			$context[] = 'User: ' . $user->getName() . ' (ID: ' . $user->getId() . ')';
 		}
 
@@ -194,8 +192,7 @@ abstract class Target extends \yii\base\Component
 
 			$matched = empty($this->categories);
 			foreach ($this->categories as $category) {
-				$prefix = rtrim($category, '*');
-				if (strpos($message[2], $prefix) === 0 && ($message[2] === $category || $prefix !== $category)) {
+				if ($message[2] === $category || substr($category, -1) === '*' && strpos($message[2], rtrim($category, '*')) === 0) {
 					$matched = true;
 					break;
 				}
diff --git a/framework/logging/WebTarget.php b/framework/logging/WebTarget.php
index 6ce8ea0..b71e1a2 100644
--- a/framework/logging/WebTarget.php
+++ b/framework/logging/WebTarget.php
@@ -1,7 +1,5 @@
 <?php
 /**
- * CWebLogRoute class file.
- *
  * @link http://www.yiiframework.com/
  * @copyright Copyright &copy; 2008-2011 Yii Software LLC
  * @license http://www.yiiframework.com/license/
@@ -46,7 +44,7 @@ class CWebLogRoute extends CLogRoute
 	 */
 	protected function render($view, $data)
 	{
-		$app = \Yii::$application;
+		$app = \Yii::$app;
 		$isAjax = $app->getRequest()->getIsAjaxRequest();
 
 		if ($this->showInFireBug)
diff --git a/framework/test/TestCase.php b/framework/test/TestCase.php
index 959bb96..f190e5a 100644
--- a/framework/test/TestCase.php
+++ b/framework/test/TestCase.php
@@ -3,16 +3,16 @@
  * TestCase class.
  *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
 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 @@
+<?php
+/**
+ * WebTestCase class.
+ *
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\test;
+
+require_once('PHPUnit/Runner/Version.php');
+spl_autoload_unregister(array('Yii','autoload'));
+require_once('PHPUnit/Autoload.php');
+spl_autoload_register(array('Yii','autoload')); // put yii's autoloader at the end
+
+/**
+ * WebTestCase is the base class for all test case classes.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+abstract class WebTestCase extends \PHPUnit_Extensions_SeleniumTestCase
+{
+}
diff --git a/framework/validators/BooleanValidator.php b/framework/validators/BooleanValidator.php
index 90e7939..427fa44 100644
--- a/framework/validators/BooleanValidator.php
+++ b/framework/validators/BooleanValidator.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * BooleanValidator class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
@@ -54,7 +52,7 @@ class BooleanValidator extends Validator
 		}
 		if (!$this->strict && $value != $this->trueValue && $value != $this->falseValue
 				|| $this->strict && $value !== $this->trueValue && $value !== $this->falseValue) {
-			$message = ($this->message !== null) ? $this->message : \Yii::t('yii', '{attribute} must be either {true} or {false}.');
+			$message = ($this->message !== null) ? $this->message : \Yii::t('yii|{attribute} must be either {true} or {false}.');
 			$this->addError($object, $attribute, $message, array(
 				'{true}' => $this->trueValue,
 				'{false}' => $this->falseValue,
@@ -70,7 +68,7 @@ class BooleanValidator extends Validator
 	 */
 	public function clientValidateAttribute($object, $attribute)
 	{
-		$message = ($this->message !== null) ? $this->message : \Yii::t('yii', '{attribute} must be either {true} or {false}.');
+		$message = ($this->message !== null) ? $this->message : \Yii::t('yii|{attribute} must be either {true} or {false}.');
 		$message = strtr($message, array(
 			'{attribute}' => $object->getAttributeLabel($attribute),
 			'{value}' => $object->$attribute,
diff --git a/framework/validators/CaptchaValidator.php b/framework/validators/CaptchaValidator.php
index 3da8ed6..3f31f77 100644
--- a/framework/validators/CaptchaValidator.php
+++ b/framework/validators/CaptchaValidator.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * CaptchaValidator class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
@@ -49,7 +47,7 @@ class CaptchaValidator extends Validator
 		}
 		$captcha = $this->getCaptchaAction();
 		if (!$captcha->validate($value, $this->caseSensitive)) {
-			$message = $this->message !== null ? $this->message : \Yii::t('yii', 'The verification code is incorrect.');
+			$message = $this->message !== null ? $this->message : \Yii::t('yii|The verification code is incorrect.');
 			$this->addError($object, $attribute, $message);
 		}
 	}
@@ -61,13 +59,13 @@ class CaptchaValidator extends Validator
 	public function getCaptchaAction()
 	{
 		if (strpos($this->captchaAction, '/') !== false) {  // contains controller or module
-			$ca = \Yii::$application->createController($this->captchaAction);
+			$ca = \Yii::$app->createController($this->captchaAction);
 			if ($ca !== null) {
 				list($controller, $actionID) = $ca;
 				$action = $controller->createAction($actionID);
 			}
 		} else {
-			$action = \Yii::$application->getController()->createAction($this->captchaAction);
+			$action = \Yii::$app->getController()->createAction($this->captchaAction);
 		}
 
 		if ($action === null) {
@@ -85,7 +83,7 @@ class CaptchaValidator extends Validator
 	public function clientValidateAttribute($object, $attribute)
 	{
 		$captcha = $this->getCaptchaAction();
-		$message = $this->message !== null ? $this->message : \Yii::t('yii', 'The verification code is incorrect.');
+		$message = $this->message !== null ? $this->message : \Yii::t('yii|The verification code is incorrect.');
 		$message = strtr($message, array(
 			'{attribute}' => $object->getAttributeLabel($attribute),
 			'{value}' => $object->$attribute,
diff --git a/framework/validators/CompareValidator.php b/framework/validators/CompareValidator.php
index 9345b73..43f2edf 100644
--- a/framework/validators/CompareValidator.php
+++ b/framework/validators/CompareValidator.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * CompareValidator class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
@@ -96,37 +94,37 @@ class CompareValidator extends Validator
 			case '=':
 			case '==':
 				if (($this->strict && $value !== $compareValue) || (!$this->strict && $value != $compareValue)) {
-					$message = ($this->message !== null) ? $this->message : Yii::t('yii', '{attribute} must be repeated exactly.');
+					$message = ($this->message !== null) ? $this->message : Yii::t('yii|{attribute} must be repeated exactly.');
 					$this->addError($object, $attribute, $message, array('{compareAttribute}' => $compareLabel));
 				}
 				break;
 			case '!=':
 				if (($this->strict && $value === $compareValue) || (!$this->strict && $value == $compareValue)) {
-					$message = ($this->message !== null) ? $this->message : Yii::t('yii', '{attribute} must not be equal to "{compareValue}".');
+					$message = ($this->message !== null) ? $this->message : Yii::t('yii|{attribute} must not be equal to "{compareValue}".');
 					$this->addError($object, $attribute, $message, array('{compareAttribute}' => $compareLabel, '{compareValue}' => $compareValue));
 				}
 				break;
 			case '>':
 				if ($value <= $compareValue) {
-					$message = ($this->message !== null) ? $this->message : Yii::t('yii', '{attribute} must be greater than "{compareValue}".');
+					$message = ($this->message !== null) ? $this->message : Yii::t('yii|{attribute} must be greater than "{compareValue}".');
 					$this->addError($object, $attribute, $message, array('{compareAttribute}' => $compareLabel, '{compareValue}' => $compareValue));
 				}
 				break;
 			case '>=':
 				if ($value < $compareValue) {
-					$message = ($this->message !== null) ? $this->message : Yii::t('yii', '{attribute} must be greater than or equal to "{compareValue}".');
+					$message = ($this->message !== null) ? $this->message : Yii::t('yii|{attribute} must be greater than or equal to "{compareValue}".');
 					$this->addError($object, $attribute, $message, array('{compareAttribute}' => $compareLabel, '{compareValue}' => $compareValue));
 				}
 				break;
 			case '<':
 				if ($value >= $compareValue) {
-					$message = ($this->message !== null) ? $this->message : Yii::t('yii', '{attribute} must be less than "{compareValue}".');
+					$message = ($this->message !== null) ? $this->message : Yii::t('yii|{attribute} must be less than "{compareValue}".');
 					$this->addError($object, $attribute, $message, array('{compareAttribute}' => $compareLabel, '{compareValue}' => $compareValue));
 				}
 				break;
 			case '<=':
 				if ($value > $compareValue) {
-					$message = ($this->message !== null) ? $this->message : Yii::t('yii', '{attribute} must be less than or equal to "{compareValue}".');
+					$message = ($this->message !== null) ? $this->message : Yii::t('yii|{attribute} must be less than or equal to "{compareValue}".');
 					$this->addError($object, $attribute, $message, array('{compareAttribute}' => $compareLabel, '{compareValue}' => $compareValue));
 				}
 				break;
@@ -158,37 +156,37 @@ class CompareValidator extends Validator
 			case '=':
 			case '==':
 				if ($message === null) {
-					$message = Yii::t('yii', '{attribute} must be repeated exactly.');
+					$message = Yii::t('yii|{attribute} must be repeated exactly.');
 				}
 				$condition = 'value!=' . $compareValue;
 				break;
 			case '!=':
 				if ($message === null) {
-					$message = Yii::t('yii', '{attribute} must not be equal to "{compareValue}".');
+					$message = Yii::t('yii|{attribute} must not be equal to "{compareValue}".');
 				}
 				$condition = 'value==' . $compareValue;
 				break;
 			case '>':
 				if ($message === null) {
-					$message = Yii::t('yii', '{attribute} must be greater than "{compareValue}".');
+					$message = Yii::t('yii|{attribute} must be greater than "{compareValue}".');
 				}
 				$condition = 'value<=' . $compareValue;
 				break;
 			case '>=':
 				if ($message === null) {
-					$message = Yii::t('yii', '{attribute} must be greater than or equal to "{compareValue}".');
+					$message = Yii::t('yii|{attribute} must be greater than or equal to "{compareValue}".');
 				}
 				$condition = 'value<' . $compareValue;
 				break;
 			case '<':
 				if ($message === null) {
-					$message = Yii::t('yii', '{attribute} must be less than "{compareValue}".');
+					$message = Yii::t('yii|{attribute} must be less than "{compareValue}".');
 				}
 				$condition = 'value>=' . $compareValue;
 				break;
 			case '<=':
 				if ($message === null) {
-					$message = Yii::t('yii', '{attribute} must be less than or equal to "{compareValue}".');
+					$message = Yii::t('yii|{attribute} must be less than or equal to "{compareValue}".');
 				}
 				$condition = 'value>' . $compareValue;
 				break;
diff --git a/framework/validators/DateValidator.php b/framework/validators/DateValidator.php
index f4fa866..7899c95 100644
--- a/framework/validators/DateValidator.php
+++ b/framework/validators/DateValidator.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * DateValidator class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
@@ -66,7 +64,7 @@ class DateValidator extends Validator
 		}
 
 		if (!$valid) {
-			$message = ($this->message !== null) ? $this->message : \Yii::t('yii', 'The format of {attribute} is invalid.');
+			$message = ($this->message !== null) ? $this->message : \Yii::t('yii|The format of {attribute} is invalid.');
 			$this->addError($object, $attribute, $message);
 		}
 	}
diff --git a/framework/validators/DefaultValueValidator.php b/framework/validators/DefaultValueValidator.php
index 1673182..be06768 100644
--- a/framework/validators/DefaultValueValidator.php
+++ b/framework/validators/DefaultValueValidator.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * DefaultValueValidator class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
diff --git a/framework/validators/EmailValidator.php b/framework/validators/EmailValidator.php
index 8fd8120..d1d2257 100644
--- a/framework/validators/EmailValidator.php
+++ b/framework/validators/EmailValidator.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * EmailValidator class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
@@ -63,7 +61,7 @@ class EmailValidator extends Validator
 			return;
 		}
 		if (!$this->validateValue($value)) {
-			$message = ($this->message !== null) ? $this->message : \Yii::t('yii', '{attribute} is not a valid email address.');
+			$message = ($this->message !== null) ? $this->message : \Yii::t('yii|{attribute} is not a valid email address.');
 			$this->addError($object, $attribute, $message);
 		}
 	}
@@ -100,7 +98,7 @@ class EmailValidator extends Validator
 	 */
 	public function clientValidateAttribute($object, $attribute)
 	{
-		$message = ($this->message !== null) ? $this->message : \Yii::t('yii', '{attribute} is not a valid email address.');
+		$message = ($this->message !== null) ? $this->message : \Yii::t('yii|{attribute} is not a valid email address.');
 		$message = strtr($message, array(
 			'{attribute}' => $object->getAttributeLabel($attribute),
 			'{value}' => $object->$attribute,
diff --git a/framework/validators/ExistValidator.php b/framework/validators/ExistValidator.php
index be710bd..8df3e19 100644
--- a/framework/validators/ExistValidator.php
+++ b/framework/validators/ExistValidator.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * ExistValidator class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
@@ -68,7 +66,7 @@ class ExistValidator extends Validator
 		$query = $className::find();
 		$query->where(array($column->name => $value));
 		if (!$query->exists()) {
-			$message = ($this->message !== null) ? $this->message : \Yii::t('yii', '{attribute} "{value}" is invalid.');
+			$message = ($this->message !== null) ? $this->message : \Yii::t('yii|{attribute} "{value}" is invalid.');
 			$this->addError($object, $attribute, $message);
 		}
 	}
diff --git a/framework/validators/FileValidator.php b/framework/validators/FileValidator.php
index 106b9a6..b05ac2a 100644
--- a/framework/validators/FileValidator.php
+++ b/framework/validators/FileValidator.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * CFileValidator class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
@@ -115,7 +113,7 @@ class CFileValidator extends Validator
 				return $this->emptyAttribute($object, $attribute);
 			if (count($files) > $this->maxFiles)
 			{
-				$message = $this->tooMany !== null ? $this->tooMany : \Yii::t('yii', '{attribute} cannot accept more than {limit} files.');
+				$message = $this->tooMany !== null ? $this->tooMany : \Yii::t('yii|{attribute} cannot accept more than {limit} files.');
 				$this->addError($object, $attribute, $message, array('{attribute}' => $attribute, '{limit}' => $this->maxFiles));
 			} else
 				foreach ($files as $file)
@@ -145,20 +143,20 @@ class CFileValidator extends Validator
 			return $this->emptyAttribute($object, $attribute);
 		elseif ($error == UPLOAD_ERR_INI_SIZE || $error == UPLOAD_ERR_FORM_SIZE || $this->maxSize !== null && $file->getSize() > $this->maxSize)
 		{
-			$message = $this->tooLarge !== null ? $this->tooLarge : \Yii::t('yii', 'The file "{file}" is too large. Its size cannot exceed {limit} bytes.');
+			$message = $this->tooLarge !== null ? $this->tooLarge : \Yii::t('yii|The file "{file}" is too large. Its size cannot exceed {limit} bytes.');
 			$this->addError($object, $attribute, $message, array('{file}' => $file->getName(), '{limit}' => $this->getSizeLimit()));
 		} elseif ($error == UPLOAD_ERR_PARTIAL)
-			throw new CException(\Yii::t('yii', 'The file "{file}" was only partially uploaded.', array('{file}' => $file->getName())));
+			throw new CException(\Yii::t('yii|The file "{file}" was only partially uploaded.', array('{file}' => $file->getName())));
 		elseif ($error == UPLOAD_ERR_NO_TMP_DIR)
-			throw new CException(\Yii::t('yii', 'Missing the temporary folder to store the uploaded file "{file}".', array('{file}' => $file->getName())));
+			throw new CException(\Yii::t('yii|Missing the temporary folder to store the uploaded file "{file}".', array('{file}' => $file->getName())));
 		elseif ($error == UPLOAD_ERR_CANT_WRITE)
-			throw new CException(\Yii::t('yii', 'Failed to write the uploaded file "{file}" to disk.', array('{file}' => $file->getName())));
+			throw new CException(\Yii::t('yii|Failed to write the uploaded file "{file}" to disk.', array('{file}' => $file->getName())));
 		elseif (defined('UPLOAD_ERR_EXTENSION') && $error == UPLOAD_ERR_EXTENSION)  // available for PHP 5.2.0 or above
-			throw new CException(\Yii::t('yii', 'File upload was stopped by extension.'));
+			throw new CException(\Yii::t('yii|File upload was stopped by extension.'));
 
 		if ($this->minSize !== null && $file->getSize() < $this->minSize)
 		{
-			$message = $this->tooSmall !== null ? $this->tooSmall : \Yii::t('yii', 'The file "{file}" is too small. Its size cannot be smaller than {limit} bytes.');
+			$message = $this->tooSmall !== null ? $this->tooSmall : \Yii::t('yii|The file "{file}" is too small. Its size cannot be smaller than {limit} bytes.');
 			$this->addError($object, $attribute, $message, array('{file}' => $file->getName(), '{limit}' => $this->minSize));
 		}
 
@@ -170,7 +168,7 @@ class CFileValidator extends Validator
 				$types = $this->types;
 			if (!in_array(strtolower($file->getExtensionName()), $types))
 			{
-				$message = $this->wrongType !== null ? $this->wrongType : \Yii::t('yii', 'The file "{file}" cannot be uploaded. Only files with these extensions are allowed: {extensions}.');
+				$message = $this->wrongType !== null ? $this->wrongType : \Yii::t('yii|The file "{file}" cannot be uploaded. Only files with these extensions are allowed: {extensions}.');
 				$this->addError($object, $attribute, $message, array('{file}' => $file->getName(), '{extensions}' => implode(', ', $types)));
 			}
 		}
@@ -185,7 +183,7 @@ class CFileValidator extends Validator
 	{
 		if (!$this->allowEmpty)
 		{
-			$message = $this->message !== null ? $this->message : \Yii::t('yii', '{attribute} cannot be blank.');
+			$message = $this->message !== null ? $this->message : \Yii::t('yii|{attribute} cannot be blank.');
 			$this->addError($object, $attribute, $message);
 		}
 	}
diff --git a/framework/validators/FilterValidator.php b/framework/validators/FilterValidator.php
index d20defd..c891979 100644
--- a/framework/validators/FilterValidator.php
+++ b/framework/validators/FilterValidator.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * FilterValidator class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
diff --git a/framework/validators/InlineValidator.php b/framework/validators/InlineValidator.php
index e324b4b..5c12d52 100644
--- a/framework/validators/InlineValidator.php
+++ b/framework/validators/InlineValidator.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * InlineValidator class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
diff --git a/framework/validators/NumberValidator.php b/framework/validators/NumberValidator.php
index 4596fc1..89363fb 100644
--- a/framework/validators/NumberValidator.php
+++ b/framework/validators/NumberValidator.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * NumberValidator class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
@@ -73,21 +71,21 @@ class NumberValidator extends Validator
 		}
 		if ($this->integerOnly) {
 			if (!preg_match($this->integerPattern, "$value")) {
-				$message = $this->message !== null ? $this->message : Yii::t('yii', '{attribute} must be an integer.');
+				$message = $this->message !== null ? $this->message : Yii::t('yii|{attribute} must be an integer.');
 				$this->addError($object, $attribute, $message);
 			}
 		} else {
 			if (!preg_match($this->numberPattern, "$value")) {
-				$message = $this->message !== null ? $this->message : Yii::t('yii', '{attribute} must be a number.');
+				$message = $this->message !== null ? $this->message : Yii::t('yii|{attribute} must be a number.');
 				$this->addError($object, $attribute, $message);
 			}
 		}
 		if ($this->min !== null && $value < $this->min) {
-			$message = $this->tooSmall !== null ? $this->tooSmall : Yii::t('yii', '{attribute} is too small (minimum is {min}).');
+			$message = $this->tooSmall !== null ? $this->tooSmall : Yii::t('yii|{attribute} is too small (minimum is {min}).');
 			$this->addError($object, $attribute, $message, array('{min}' => $this->min));
 		}
 		if ($this->max !== null && $value > $this->max) {
-			$message = $this->tooBig !== null ? $this->tooBig : Yii::t('yii', '{attribute} is too big (maximum is {max}).');
+			$message = $this->tooBig !== null ? $this->tooBig : Yii::t('yii|{attribute} is too big (maximum is {max}).');
 			$this->addError($object, $attribute, $message, array('{max}' => $this->max));
 		}
 	}
@@ -103,8 +101,8 @@ class NumberValidator extends Validator
 		$label = $object->getAttributeLabel($attribute);
 
 		if (($message = $this->message) === null) {
-			$message = $this->integerOnly ? Yii::t('yii', '{attribute} must be an integer.')
-					: Yii::t('yii', '{attribute} must be a number.');
+			$message = $this->integerOnly ? Yii::t('yii|{attribute} must be an integer.')
+					: Yii::t('yii|{attribute} must be a number.');
 		}
 		$message = strtr($message, array(
 			'{attribute}' => $label,
@@ -118,7 +116,7 @@ if(!value.match($pattern)) {
 ";
 		if ($this->min !== null) {
 			if (($tooSmall = $this->tooSmall) === null) {
-				$tooSmall = Yii::t('yii', '{attribute} is too small (minimum is {min}).');
+				$tooSmall = Yii::t('yii|{attribute} is too small (minimum is {min}).');
 			}
 			$tooSmall = strtr($tooSmall, array(
 				'{attribute}' => $label,
@@ -133,7 +131,7 @@ if(value<{$this->min}) {
 		}
 		if ($this->max !== null) {
 			if (($tooBig = $this->tooBig) === null) {
-				$tooBig = Yii::t('yii', '{attribute} is too big (maximum is {max}).');
+				$tooBig = Yii::t('yii|{attribute} is too big (maximum is {max}).');
 			}
 			$tooBig = strtr($tooBig, array(
 				'{attribute}' => $label,
diff --git a/framework/validators/RangeValidator.php b/framework/validators/RangeValidator.php
index b2ff773..e23567c 100644
--- a/framework/validators/RangeValidator.php
+++ b/framework/validators/RangeValidator.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * RangeValidator class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
@@ -58,10 +56,10 @@ class RangeValidator extends Validator
 			throw new InvalidConfigException('The "range" property must be specified as an array.');
 		}
 		if (!$this->not && !in_array($value, $this->range, $this->strict)) {
-			$message = ($this->message !== null) ? $this->message : \Yii::t('yii', '{attribute} should be in the list.');
+			$message = ($this->message !== null) ? $this->message : \Yii::t('yii|{attribute} should be in the list.');
 			$this->addError($object, $attribute, $message);
 		} elseif ($this->not && in_array($value, $this->range, $this->strict)) {
-			$message = ($this->message !== null) ? $this->message : \Yii::t('yii', '{attribute} should NOT be in the list.');
+			$message = ($this->message !== null) ? $this->message : \Yii::t('yii|{attribute} should NOT be in the list.');
 			$this->addError($object, $attribute, $message);
 		}
 	}
@@ -80,7 +78,7 @@ class RangeValidator extends Validator
 		}
 
 		if (($message = $this->message) === null) {
-			$message = $this->not ? \Yii::t('yii', '{attribute} should NOT be in the list.') : \Yii::t('yii', '{attribute} should be in the list.');
+			$message = $this->not ? \Yii::t('yii|{attribute} should NOT be in the list.') : \Yii::t('yii|{attribute} should be in the list.');
 		}
 		$message = strtr($message, array(
 			'{attribute}' => $object->getAttributeLabel($attribute),
diff --git a/framework/validators/RegularExpressionValidator.php b/framework/validators/RegularExpressionValidator.php
index fbdb062..df2b657 100644
--- a/framework/validators/RegularExpressionValidator.php
+++ b/framework/validators/RegularExpressionValidator.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * RegularExpressionValidator class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
@@ -51,7 +49,7 @@ class RegularExpressionValidator extends Validator
 			throw new \yii\base\Exception('The "pattern" property must be specified with a valid regular expression.');
 		}
 		if ((!$this->not && !preg_match($this->pattern, $value)) || ($this->not && preg_match($this->pattern, $value))) {
-			$message = ($this->message !== null) ? $this->message : \Yii::t('yii', '{attribute} is invalid.');
+			$message = ($this->message !== null) ? $this->message : \Yii::t('yii|{attribute} is invalid.');
 			$this->addError($object, $attribute, $message);
 		}
 	}
@@ -69,7 +67,7 @@ class RegularExpressionValidator extends Validator
 			throw new \yii\base\Exception('The "pattern" property must be specified with a valid regular expression.');
 		}
 
-		$message = ($this->message !== null) ? $this->message : \Yii::t('yii', '{attribute} is invalid.');
+		$message = ($this->message !== null) ? $this->message : \Yii::t('yii|{attribute} is invalid.');
 		$message = strtr($message, array(
 			'{attribute}' => $object->getAttributeLabel($attribute),
 			'{value}' => $object->$attribute,
diff --git a/framework/validators/RequiredValidator.php b/framework/validators/RequiredValidator.php
index f0f4bfd..66b9c3c 100644
--- a/framework/validators/RequiredValidator.php
+++ b/framework/validators/RequiredValidator.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * RequiredValidator class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
@@ -47,12 +45,12 @@ class RequiredValidator extends Validator
 		$value = $object->$attribute;
 		if ($this->requiredValue === null) {
 			if ($this->strict && $value === null || !$this->strict && $this->isEmpty($value, true)) {
-				$message = ($this->message !== null) ? $this->message : \Yii::t('yii', '{attribute} cannot be blank.');
+				$message = ($this->message !== null) ? $this->message : \Yii::t('yii|{attribute} cannot be blank.');
 				$this->addError($object, $attribute, $message);
 			}
 		} else {
 			if (!$this->strict && $value != $this->requiredValue || $this->strict && $value !== $this->requiredValue) {
-				$message = ($this->message !== null) ? $this->message : \Yii::t('yii', '{attribute} must be "{requiredValue}".');
+				$message = ($this->message !== null) ? $this->message : \Yii::t('yii|{attribute} must be "{requiredValue}".');
 				$this->addError($object, $attribute, $message, array(
 					'{requiredValue}' => $this->requiredValue,
 				));
@@ -71,7 +69,7 @@ class RequiredValidator extends Validator
 		$message = $this->message;
 		if ($this->requiredValue !== null) {
 			if ($message === null) {
-				$message = \Yii::t('yii', '{attribute} must be "{requiredValue}".');
+				$message = \Yii::t('yii|{attribute} must be "{requiredValue}".');
 			}
 			$message = strtr($message, array(
 				'{attribute}' => $object->getAttributeLabel($attribute),
@@ -85,7 +83,7 @@ if (value != " . json_encode($this->requiredValue) . ") {
 ";
 		} else {
 			if ($message === null) {
-				$message = \Yii::t('yii', '{attribute} cannot be blank.');
+				$message = \Yii::t('yii|{attribute} cannot be blank.');
 			}
 			$message = strtr($message, array(
 				'{attribute}' => $object->getAttributeLabel($attribute),
diff --git a/framework/validators/StringValidator.php b/framework/validators/StringValidator.php
index fe69a4d..9135b9e 100644
--- a/framework/validators/StringValidator.php
+++ b/framework/validators/StringValidator.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * StringValidator class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
@@ -76,27 +74,27 @@ class StringValidator extends Validator
 		}
 
 		if (!is_string($value)) {
-			$message = ($this->message !== null) ? $this->message : \Yii::t('yii', '{attribute} must be a string.');
+			$message = ($this->message !== null) ? $this->message : \Yii::t('yii|{attribute} must be a string.');
 			$this->addError($object, $attribute, $message);
 			return;
 		}
 
 		if (function_exists('mb_strlen') && $this->encoding !== false) {
-			$length = mb_strlen($value, $this->encoding ? $this->encoding : \Yii::$application->charset);
+			$length = mb_strlen($value, $this->encoding ? $this->encoding : \Yii::$app->charset);
 		} else {
 			$length = strlen($value);
 		}
 
 		if ($this->min !== null && $length < $this->min) {
-			$message = ($this->tooShort !== null) ? $this->tooShort : \Yii::t('yii', '{attribute} is too short (minimum is {min} characters).');
+			$message = ($this->tooShort !== null) ? $this->tooShort : \Yii::t('yii|{attribute} is too short (minimum is {min} characters).');
 			$this->addError($object, $attribute, $message, array('{min}' => $this->min));
 		}
 		if ($this->max !== null && $length > $this->max) {
-			$message = ($this->tooLong !== null) ? $this->tooLong : \Yii::t('yii', '{attribute} is too long (maximum is {max} characters).');
+			$message = ($this->tooLong !== null) ? $this->tooLong : \Yii::t('yii|{attribute} is too long (maximum is {max} characters).');
 			$this->addError($object, $attribute, $message, array('{max}' => $this->max));
 		}
 		if ($this->is !== null && $length !== $this->is) {
-			$message = ($this->notEqual !== null) ? $this->notEqual : \Yii::t('yii', '{attribute} is of the wrong length (should be {length} characters).');
+			$message = ($this->notEqual !== null) ? $this->notEqual : \Yii::t('yii|{attribute} is of the wrong length (should be {length} characters).');
 			$this->addError($object, $attribute, $message, array('{length}' => $this->is));
 		}
 	}
@@ -113,7 +111,7 @@ class StringValidator extends Validator
 		$value = $object->$attribute;
 
 		if (($notEqual = $this->notEqual) === null) {
-			$notEqual = \Yii::t('yii', '{attribute} is of the wrong length (should be {length} characters).');
+			$notEqual = \Yii::t('yii|{attribute} is of the wrong length (should be {length} characters).');
 		}
 		$notEqual = strtr($notEqual, array(
 			'{attribute}' => $label,
@@ -122,7 +120,7 @@ class StringValidator extends Validator
 		));
 
 		if (($tooShort = $this->tooShort) === null) {
-			$tooShort = \Yii::t('yii', '{attribute} is too short (minimum is {min} characters).');
+			$tooShort = \Yii::t('yii|{attribute} is too short (minimum is {min} characters).');
 		}
 		$tooShort = strtr($tooShort, array(
 			'{attribute}' => $label,
@@ -131,7 +129,7 @@ class StringValidator extends Validator
 		));
 
 		if (($tooLong = $this->tooLong) === null) {
-			$tooLong = \Yii::t('yii', '{attribute} is too long (maximum is {max} characters).');
+			$tooLong = \Yii::t('yii|{attribute} is too long (maximum is {max} characters).');
 		}
 		$tooLong = strtr($tooLong, array(
 			'{attribute}' => $label,
diff --git a/framework/validators/UniqueValidator.php b/framework/validators/UniqueValidator.php
index 5d5e603..bc12f5a 100644
--- a/framework/validators/UniqueValidator.php
+++ b/framework/validators/UniqueValidator.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * UniqueValidator class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
@@ -86,7 +84,7 @@ class UniqueValidator extends Validator
 		}
 
 		if ($exists) {
-			$message = $this->message !== null ? $this->message : \Yii::t('yii', '{attribute} "{value}" has already been taken.');
+			$message = $this->message !== null ? $this->message : \Yii::t('yii|{attribute} "{value}" has already been taken.');
 			$this->addError($object, $attribute, $message);
 		}
 	}
diff --git a/framework/validators/UrlValidator.php b/framework/validators/UrlValidator.php
index c6242a2..0ba039b 100644
--- a/framework/validators/UrlValidator.php
+++ b/framework/validators/UrlValidator.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * UrlValidator class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
@@ -55,7 +53,7 @@ class UrlValidator extends Validator
 		if (($value = $this->validateValue($value)) !== false) {
 			$object->$attribute = $value;
 		} else {
-			$message = ($this->message !== null) ? $this->message : \Yii::t('yii', '{attribute} is not a valid URL.');
+			$message = ($this->message !== null) ? $this->message : \Yii::t('yii|{attribute} is not a valid URL.');
 			$this->addError($object, $attribute, $message);
 		}
 	}
@@ -97,7 +95,7 @@ class UrlValidator extends Validator
 	 */
 	public function clientValidateAttribute($object, $attribute)
 	{
-		$message = ($this->message !== null) ? $this->message : \Yii::t('yii', '{attribute} is not a valid URL.');
+		$message = ($this->message !== null) ? $this->message : \Yii::t('yii|{attribute} is not a valid URL.');
 		$message = strtr($message, array(
 			'{attribute}' => $object->getAttributeLabel($attribute),
 			'{value}' => $object->$attribute,
diff --git a/framework/validators/Validator.php b/framework/validators/Validator.php
index a03da7a..b688f32 100644
--- a/framework/validators/Validator.php
+++ b/framework/validators/Validator.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * Validator class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
diff --git a/framework/views/error.php b/framework/views/error.php
index dbbc848..893640a 100644
--- a/framework/views/error.php
+++ b/framework/views/error.php
@@ -1,15 +1,16 @@
 <?php
 /**
  * @var \Exception $exception
- * @var \yii\base\ErrorHandler $owner
+ * @var \yii\base\ErrorHandler $context
  */
-$owner = $this->owner;
+$context = $this->context;
+$title = $context->htmlEncode($exception instanceof \yii\base\Exception || $exception instanceof \yii\base\ErrorException ? $exception->getName() : get_class($exception));
 ?>
 <!DOCTYPE html>
 <html>
 <head>
 	<meta charset="utf-8" />
-	<title><?php echo get_class($exception)?></title>
+	<title><?php echo $title?></title>
 
 	<style>
 	body {
@@ -50,8 +51,8 @@ $owner = $this->owner;
 </head>
 
 <body>
-	<h1><?php echo get_class($exception)?></h1>
-	<h2><?php echo nl2br($owner->htmlEncode($exception->getMessage()))?>	</h2>
+	<h1><?php echo $title?></h1>
+	<h2><?php echo nl2br($context->htmlEncode($exception->getMessage()))?></h2>
 	<p>
 		The above error occurred while the Web server was processing your request.
 	</p>
@@ -60,7 +61,7 @@ $owner = $this->owner;
 	</p>
 	<div class="version">
 		<?php echo date('Y-m-d H:i:s', time())?>
-		<?php echo YII_DEBUG ? $owner->versionInfo : ''?>
+		<?php echo YII_DEBUG ? $context->versionInfo : ''?>
 	</div>
 </body>
 </html>
\ No newline at end of file
diff --git a/framework/views/exception.php b/framework/views/exception.php
index 8e8e905..db29302 100644
--- a/framework/views/exception.php
+++ b/framework/views/exception.php
@@ -4,12 +4,13 @@
  * @var \yii\base\ErrorHandler $context
  */
 $context = $this->context;
+$title = $context->htmlEncode($exception instanceof \yii\base\Exception || $exception instanceof \yii\base\ErrorException ? $exception->getName().' ('.get_class($exception).')' : get_class($exception));
 ?>
 <!DOCTYPE html>
 <html>
 <head>
 	<meta charset="utf-8" />
-	<title><?php echo get_class($exception)?></title>
+	<title><?php echo $title?></title>
 	<style>
 	html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{border:0;outline:0;font-size:100%;vertical-align:baseline;background:transparent;margin:0;padding:0;}
 	body{line-height:1;}
@@ -160,7 +161,7 @@ $context = $this->context;
 
 <body>
 <div class="container">
-	<h1><?php echo get_class($exception)?></h1>
+	<h1><?php echo $title?></h1>
 
 	<p class="message">
 		<?php echo nl2br($context->htmlEncode($exception->getMessage()))?>
@@ -182,7 +183,7 @@ $context = $this->context;
 
 	<div class="version">
 		<?php echo date('Y-m-d H:i:s', time())?>
-		<?php echo YII_DEBUG ? $context->versionInfo : ''?>
+		<?php echo YII_DEBUG ? $context->getVersionInfo() : ''?>
 	</div>
 </div>
 
diff --git a/framework/views/migration.php b/framework/views/migration.php
new file mode 100644
index 0000000..a75e22f
--- /dev/null
+++ b/framework/views/migration.php
@@ -0,0 +1,23 @@
+<?php
+/**
+ * This view is used by console/controllers/MigrateController.php
+ * The following variables are available in this view:
+ *
+ * @var string $className the new migration class name
+ */
+echo "<?php\n";
+?>
+
+class <?php echo $className; ?> extends \yii\db\Migration
+{
+	public function up()
+	{
+
+	}
+
+	public function down()
+	{
+		echo "<?php echo $className; ?> cannot be reverted.\n";
+		return false;
+	}
+}
diff --git a/framework/web/AccessControl.php b/framework/web/AccessControl.php
new file mode 100644
index 0000000..793fb05
--- /dev/null
+++ b/framework/web/AccessControl.php
@@ -0,0 +1,104 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+use Yii;
+use yii\base\Action;
+use yii\base\ActionFilter;
+use yii\base\HttpException;
+
+/**
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class AccessControl extends ActionFilter
+{
+	/**
+	 * @var callback a callback that will be called if the access should be denied
+	 * to the current user. If not set, [[denyAccess()]] will be called.
+	 *
+	 * The signature of the callback should be as follows:
+	 *
+	 * ~~~
+	 * function ($rule, $action)
+	 * ~~~
+	 *
+	 * where `$rule` is this rule, and `$action` is the current [[Action|action]] object.
+	 */
+	public $denyCallback;
+	/**
+	 * @var string the default class of the access rules. This is used when
+	 * a rule is configured without specifying a class in [[rules]].
+	 */
+	public $defaultRuleClass = 'yii\web\AccessRule';
+	/**
+	 * @var array a list of access rule objects or configurations for creating the rule objects.
+	 */
+	public $rules = array();
+
+	/**
+	 * Initializes the [[rules]] array by instantiating rule objects from configurations.
+	 */
+	public function init()
+	{
+		parent::init();
+		foreach ($this->rules as $i => $rule) {
+			if (is_array($rule)) {
+				if (!isset($rule['class'])) {
+					$rule['class'] = $this->defaultRuleClass;
+				}
+				$this->rules[$i] = Yii::createObject($rule);
+			}
+		}
+	}
+
+	/**
+	 * 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)
+	{
+		$user = Yii::$app->getUser();
+		$request = Yii::$app->getRequest();
+		/** @var $rule AccessRule */
+		foreach ($this->rules as $rule) {
+			if ($allow = $rule->allows($action, $user, $request)) {
+				break;
+			} elseif ($allow === false) {
+				if (isset($rule->denyCallback)) {
+					call_user_func($rule->denyCallback, $rule);
+				} elseif (isset($this->denyCallback)) {
+					call_user_func($this->denyCallback, $rule);
+				} else {
+					$this->denyAccess($user);
+				}
+				return false;
+			}
+		}
+		return true;
+	}
+
+	/**
+	 * Denies the access of the user.
+	 * The default implementation will redirect the user to the login page if he is a guest;
+	 * if the user is already logged, a 403 HTTP exception will be thrown.
+	 * @param User $user the current user
+	 * @throws HttpException if the user is already logged in.
+	 */
+	protected function denyAccess($user)
+	{
+		if ($user->getIsGuest()) {
+			$user->loginRequired();
+		} else {
+			throw new HttpException(403, Yii::t('yii|You are not allowed to perform this action.'));
+		}
+	}
+}
\ No newline at end of file
diff --git a/framework/web/AccessRule.php b/framework/web/AccessRule.php
new file mode 100644
index 0000000..3f8c057
--- /dev/null
+++ b/framework/web/AccessRule.php
@@ -0,0 +1,188 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+use yii\base\Component;
+use yii\base\Action;
+use yii\base\Controller;
+use yii\web\User;
+use yii\web\Request;
+
+/**
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class AccessRule extends Component
+{
+	/**
+	 * @var boolean whether this is an 'allow' rule or 'deny' rule.
+	 */
+	public $allow;
+	/**
+	 * @var array list of action IDs that this rule applies to. The comparison is case-sensitive.
+	 * If not set or empty, it means this rule applies to all actions.
+	 */
+	public $actions;
+	/**
+	 * @var array list of controller IDs that this rule applies to. The comparison is case-sensitive.
+	 * If not set or empty, it means this rule applies to all controllers.
+	 */
+	public $controllers;
+	/**
+	 * @var array list of roles that this rule applies to. Two special roles are recognized, and
+	 * they are checked via [[User::isGuest]]:
+	 *
+	 * - `?`: matches a guest user (not authenticated yet)
+	 * - `@`: matches an authenticated user
+	 *
+	 * Using additional role names requires RBAC (Role-Based Access Control), and
+	 * [[User::hasAccess()]] will be called.
+	 *
+	 * If this property is not set or empty, it means this rule applies to all roles.
+	 */
+	public $roles;
+	/**
+	 * @var array list of user IP addresses that this rule applies to. An IP address
+	 * can contain the wildcard `*` at the end so that it matches IP addresses with the same prefix.
+	 * For example, '192.168.*' matches all IP addresses in the segment '192.168.'.
+	 * If not set or empty, it means this rule applies to all IP addresses.
+	 * @see Request::userIP
+	 */
+	public $ips;
+	/**
+	 * @var array list of request methods (e.g. `GET`, `POST`) that this rule applies to.
+	 * The request methods must be specified in uppercase.
+	 * If not set or empty, it means this rule applies to all request methods.
+	 * @see Request::requestMethod
+	 */
+	public $verbs;
+	/**
+	 * @var callback a callback that will be called to determine if the rule should be applied.
+	 * The signature of the callback should be as follows:
+	 *
+	 * ~~~
+	 * function ($rule, $action)
+	 * ~~~
+	 *
+	 * where `$rule` is this rule, and `$action` is the current [[Action|action]] object.
+	 * The callback should return a boolean value indicating whether this rule should be applied.
+	 */
+	public $matchCallback;
+	/**
+	 * @var callback a callback that will be called if this rule determines the access to
+	 * the current action should be denied. If not set, the behavior will be determined by
+	 * [[AccessControl]].
+	 *
+	 * The signature of the callback should be as follows:
+	 *
+	 * ~~~
+	 * function ($rule, $action)
+	 * ~~~
+	 *
+	 * where `$rule` is this rule, and `$action` is the current [[Action|action]] object.
+	 */
+	public $denyCallback;
+
+
+	/**
+	 * Checks whether the Web user is allowed to perform the specified action.
+	 * @param Action $action the action to be performed
+	 * @param User $user the user object
+	 * @param Request $request
+	 * @return boolean|null true if the user is allowed, false if the user is denied, null if the rule does not apply to the user
+	 */
+	public function allows($action, $user, $request)
+	{
+		if ($this->matchAction($action)
+			&& $this->matchRole($user)
+			&& $this->matchIP($request->getUserIP())
+			&& $this->matchVerb($request->getRequestMethod())
+			&& $this->matchController($action->controller)
+			&& $this->matchCustom($action)
+		) {
+			return $this->allow ? true : false;
+		} else {
+			return null;
+		}
+	}
+
+	/**
+	 * @param Action $action the action
+	 * @return boolean whether the rule applies to the action
+	 */
+	protected function matchAction($action)
+	{
+		return empty($this->actions) || in_array($action->id, $this->actions, true);
+	}
+
+	/**
+	 * @param Controller $controller the controller
+	 * @return boolean whether the rule applies to the controller
+	 */
+	protected function matchController($controller)
+	{
+		return empty($this->controllers) || in_array($controller->id, $this->controllers, true);
+	}
+
+	/**
+	 * @param User $user the user object
+	 * @return boolean whether the rule applies to the role
+	 */
+	protected function matchRole($user)
+	{
+		if (empty($this->roles)) {
+			return true;
+		}
+		foreach ($this->roles as $role) {
+			if ($role === '?' && $user->getIsGuest()) {
+				return true;
+			} elseif ($role === '@' && !$user->getIsGuest()) {
+				return true;
+			} elseif ($user->hasAccess($role)) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * @param string $ip the IP address
+	 * @return boolean whether the rule applies to the IP address
+	 */
+	protected function matchIP($ip)
+	{
+		if (empty($this->ips)) {
+			return true;
+		}
+		foreach ($this->ips as $rule) {
+			if ($rule === '*' || $rule === $ip || (($pos = strpos($rule, '*')) !== false && !strncmp($ip, $rule, $pos))) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * @param string $verb the request method
+	 * @return boolean whether the rule applies to the request
+	 */
+	protected function matchVerb($verb)
+	{
+		return empty($this->verbs) || in_array($verb, $this->verbs, true);
+	}
+
+	/**
+	 * @param Action $action the action to be performed
+	 * @return boolean whether the rule should be applied
+	 */
+	protected function matchCustom($action)
+	{
+		return empty($this->matchCallback) || call_user_func($this->matchCallback, $this, $action);
+	}
+}
\ No newline at end of file
diff --git a/framework/web/Application.php b/framework/web/Application.php
index a25df7a..2533f04 100644
--- a/framework/web/Application.php
+++ b/framework/web/Application.php
@@ -1,14 +1,14 @@
 <?php
 /**
- * Application class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
 namespace yii\web;
 
+use yii\base\InvalidParamException;
+
 /**
  * Application is the base class for all application classes.
  *
@@ -18,12 +18,17 @@ namespace yii\web;
 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()
 	{
 		parent::registerDefaultAliases();
-		\Yii::$aliases['@www'] = dirname($_SERVER['SCRIPT_FILENAME']);
+		\Yii::$aliases['@webroot'] = dirname($_SERVER['SCRIPT_FILENAME']);
 	}
 
 	/**
@@ -32,13 +37,44 @@ class Application extends \yii\base\Application
 	 */
 	public function processRequest()
 	{
-		$route = $this->resolveRequest();
-		return $this->runController($route, null);
+		list ($route, $params) = $this->getRequest()->resolve();
+		return $this->runAction($route, $params);
+	}
+
+	/**
+	 * Returns the request component.
+	 * @return Request the request component
+	 */
+	public function getRequest()
+	{
+		return $this->getComponent('request');
+	}
+
+	/**
+	 * Returns the response component.
+	 * @return Response the response component
+	 */
+	public function getResponse()
+	{
+		return $this->getComponent('response');
+	}
+
+	/**
+	 * Returns the session component.
+	 * @return Session the session component
+	 */
+	public function getSession()
+	{
+		return $this->getComponent('session');
 	}
 
-	protected function resolveRequest()
+	/**
+	 * Returns the user component.
+	 * @return User the user component
+	 */
+	public function getUser()
 	{
-		return array();
+		return $this->getComponent('user');
 	}
 
 	/**
@@ -55,6 +91,12 @@ class Application extends \yii\base\Application
 			'response' => array(
 				'class' => 'yii\web\Response',
 			),
+			'session' => array(
+				'class' => 'yii\web\Session',
+			),
+			'user' => array(
+				'class' => 'yii\web\User',
+			),
 		));
 	}
 }
diff --git a/framework/web/AssetManager.php b/framework/web/AssetManager.php
index 2028a1c..60f4c07 100644
--- a/framework/web/AssetManager.php
+++ b/framework/web/AssetManager.php
@@ -1,7 +1,5 @@
 <?php
 /**
- * CAssetManager class file.
- *
  * @author Qiang Xue <qiang.xue@gmail.com>
  * @link http://www.yiiframework.com/
  * @copyright Copyright &copy; 2008-2011 Yii Software LLC
@@ -97,7 +95,7 @@ class CAssetManager extends CApplicationComponent
 	{
 		if($this->_basePath===null)
 		{
-			$request=\Yii::$application->getRequest();
+			$request=\Yii::$app->getRequest();
 			$this->setBasePath(dirname($request->getScriptFile()).DIRECTORY_SEPARATOR.self::DEFAULT_BASEPATH);
 		}
 		return $this->_basePath;
@@ -113,7 +111,7 @@ class CAssetManager extends CApplicationComponent
 		if(($basePath=realpath($value))!==false && is_dir($basePath) && is_writable($basePath))
 			$this->_basePath=$basePath;
 		else
-			throw new CException(Yii::t('yii','CAssetManager.basePath "{path}" is invalid. Please make sure the directory exists and is writable by the Web server process.',
+			throw new CException(Yii::t('yii|CAssetManager.basePath "{path}" is invalid. Please make sure the directory exists and is writable by the Web server process.',
 				array('{path}'=>$value)));
 	}
 
@@ -125,7 +123,7 @@ class CAssetManager extends CApplicationComponent
 	{
 		if($this->_baseUrl===null)
 		{
-			$request=\Yii::$application->getRequest();
+			$request=\Yii::$app->getRequest();
 			$this->setBaseUrl($request->getBaseUrl().'/'.self::DEFAULT_BASEPATH);
 		}
 		return $this->_baseUrl;
@@ -236,7 +234,7 @@ class CAssetManager extends CApplicationComponent
 				return $this->_published[$path]=$this->getBaseUrl().'/'.$dir;
 			}
 		}
-		throw new CException(Yii::t('yii','The asset "{asset}" to be published does not exist.',
+		throw new CException(Yii::t('yii|The asset "{asset}" to be published does not exist.',
 			array('{asset}'=>$path)));
 	}
 
diff --git a/framework/web/CacheSession.php b/framework/web/CacheSession.php
new file mode 100644
index 0000000..c125f01
--- /dev/null
+++ b/framework/web/CacheSession.php
@@ -0,0 +1,106 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+use Yii;
+use yii\caching\Cache;
+use yii\base\InvalidConfigException;
+
+/**
+ * CacheSession implements a session component using cache as storage medium.
+ *
+ * The cache being used can be any cache application component.
+ * The ID of the cache application component is specified via [[cache]], which defaults to 'cache'.
+ *
+ * Beware, by definition cache storage are volatile, which means the data stored on them
+ * may be swapped out and get lost. Therefore, you must make sure the cache used by this component
+ * is NOT volatile. If you want to use database as storage medium, use [[DbSession]] is a better choice.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class CacheSession extends Session
+{
+	/**
+	 * @var Cache|string the cache object or the application component ID of the cache object.
+	 * The session data will be stored using this cache object.
+	 *
+	 * After the CacheSession object is created, if you want to change this property,
+	 * you should only assign it with a cache object.
+	 */
+	public $cache = 'cache';
+
+	/**
+	 * Initializes the application component.
+	 */
+	public function init()
+	{
+		parent::init();
+		if (is_string($this->cache)) {
+			$this->cache = Yii::$app->getComponent($this->cache);
+		}
+		if (!$this->cache instanceof Cache) {
+			throw new InvalidConfigException('CacheSession::cache must refer to the application component ID of a cache object.');
+		}
+	}
+
+	/**
+	 * Returns a value indicating whether to use custom session storage.
+	 * This method overrides the parent implementation and always returns true.
+	 * @return boolean whether to use custom storage.
+	 */
+	public function getUseCustomStorage()
+	{
+		return true;
+	}
+
+	/**
+	 * Session read handler.
+	 * Do not call this method directly.
+	 * @param string $id session ID
+	 * @return string the session data
+	 */
+	public function readSession($id)
+	{
+		$data = $this->cache->get($this->calculateKey($id));
+		return $data === false ? '' : $data;
+	}
+
+	/**
+	 * Session write handler.
+	 * Do not call this method directly.
+	 * @param string $id session ID
+	 * @param string $data session data
+	 * @return boolean whether session write is successful
+	 */
+	public function writeSession($id, $data)
+	{
+		return $this->cache->set($this->calculateKey($id), $data, $this->getTimeout());
+	}
+
+	/**
+	 * Session destroy handler.
+	 * Do not call this method directly.
+	 * @param string $id session ID
+	 * @return boolean whether session is destroyed successfully
+	 */
+	public function destroySession($id)
+	{
+		return $this->cache->delete($this->calculateKey($id));
+	}
+
+	/**
+	 * Generates a unique key used for storing session data in cache.
+	 * @param string $id session variable name
+	 * @return string a safe cache key associated with the session variable name
+	 */
+	protected function calculateKey($id)
+	{
+		return $this->cache->buildKey(array(__CLASS__, $id));
+	}
+}
diff --git a/framework/web/Controller.php b/framework/web/Controller.php
index 92722b5..93b74aa 100644
--- a/framework/web/Controller.php
+++ b/framework/web/Controller.php
@@ -1,17 +1,13 @@
 <?php
 /**
- * Controller class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
 namespace yii\web;
 
-use yii\base\Action;
-use yii\base\Exception;
-use yii\base\HttpException;
+use Yii;
 
 /**
  * Controller is the base class of Web controllers.
@@ -22,54 +18,27 @@ use yii\base\HttpException;
  */
 class Controller extends \yii\base\Controller
 {
-	private $_pageTitle;
-
 	/**
-	 * Returns the request parameters that will be used for action parameter binding.
-	 * Default implementation simply returns an empty array.
-	 * Child classes may override this method to customize the parameters to be provided
-	 * for action parameter binding (e.g. `$_GET`).
-	 * @return array the request parameters (name-value pairs) to be used for action parameter binding
+	 * Creates a URL using the given route and parameters.
+	 *
+	 * This method enhances [[UrlManager::createUrl()]] by supporting relative routes.
+	 * A relative route is a route without a slash, such as "view". If the route is an empty
+	 * string, [[route]] will be used; Otherwise, [[uniqueId]] will be prepended to a relative route.
+	 *
+	 * After this route conversion, the method This method calls [[UrlManager::createUrl()]]
+	 * to create a URL.
+	 *
+	 * @param string $route the route. This can be either an absolute route or a relative route.
+	 * @param array $params the parameters (name-value pairs) to be included in the generated URL
+	 * @return string the created URL
 	 */
-	public function getActionParams()
+	public function createUrl($route, $params = array())
 	{
-		return $_GET;
-	}
-
-	/**
-	 * This method is invoked when the request parameters do not satisfy the requirement of the specified action.
-	 * The default implementation will throw an exception.
-	 * @param Action $action the action being executed
-	 * @param Exception $exception the exception about the invalid parameters
-	 * @throws HttpException $exception a 400 HTTP exception
-	 */
-	public function invalidActionParams($action, $exception)
-	{
-		throw new HttpException(400, \Yii::t('yii', 'Your request is invalid.'));
-	}
-
-	/**
-	 * @return string the page title. Defaults to the controller name and the action name.
-	 */
-	public function getPageTitle()
-	{
-		if($this->_pageTitle !== null) {
-			return $this->_pageTitle;
-		}
-		else {
-			$name = ucfirst(basename($this->id));
-			if($this->action!==null && strcasecmp($this->action->id,$this->defaultAction))
-				return $this->_pageTitle=\Yii::$application->name.' - '.ucfirst($this->action->id).' '.$name;
-			else
-				return $this->_pageTitle=\Yii::$application->name.' - '.$name;
+		if (strpos($route, '/') === false) {
+			// a relative route
+			$route = $route === '' ? $this->getRoute() : $this->getUniqueId() . '/' . $route;
 		}
+		return Yii::$app->getUrlManager()->createUrl($route, $params);
 	}
 
-	/**
-	 * @param string $value the page title.
-	 */
-	public function setPageTitle($value)
-	{
-		$this->_pageTitle = $value;
-	}
 }
\ No newline at end of file
diff --git a/framework/web/Cookie.php b/framework/web/Cookie.php
index 40bab05..610e5aa 100644
--- a/framework/web/Cookie.php
+++ b/framework/web/Cookie.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * Cookie class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
@@ -47,5 +45,21 @@ class Cookie extends \yii\base\Object
 	 * By setting this property to true, the cookie will not be accessible by scripting languages,
 	 * such as JavaScript, which can effectively help to reduce identity theft through XSS attacks.
 	 */
-	public $httpOnly = false;
+	public $httponly = false;
+
+	/**
+	 * Magic method to turn a cookie object into a string without having to explicitly access [[value]].
+	 *
+	 * ~~~
+	 * if (isset($request->cookies['name'])) {
+	 *     $value = (string)$request->cookies['name'];
+	 * }
+	 * ~~~
+	 *
+	 * @return string The value of the cookie. If the value property is null, an empty string will be returned.
+	 */
+	public function __toString()
+	{
+		return (string)$this->value;
+	}
 }
diff --git a/framework/web/CookieCollection.php b/framework/web/CookieCollection.php
index 1d87bf0..c76926b 100644
--- a/framework/web/CookieCollection.php
+++ b/framework/web/CookieCollection.php
@@ -1,37 +1,20 @@
 <?php
 /**
- * Dictionary class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
 namespace yii\web;
 
+use Yii;
 use yii\base\DictionaryIterator;
+use yii\helpers\SecurityHelper;
 
 /**
- * Dictionary implements a collection that stores key-value pairs.
- *
- * You can access, add or remove an item with a key by using
- * [[itemAt()]], [[add()]], and [[remove()]].
- *
- * To get the number of the items in the dictionary, use [[getCount()]].
+ * CookieCollection maintains the cookies available in the current request.
  *
- * Because Dictionary implements a set of SPL interfaces, it can be used
- * like a regular PHP array as follows,
- *
- * ~~~
- * $dictionary[$key] = $value;		   // add a key-value pair
- * unset($dictionary[$key]);			 // remove the value with the specified key
- * if (isset($dictionary[$key]))		 // if the dictionary contains the key
- * foreach ($dictionary as $key=>$value) // traverse the items in the dictionary
- * $n = count($dictionary);			  // returns the number of items in the dictionary
- * ~~~
- *
- * @property integer $count the number of items in the dictionary
- * @property array $keys The keys in the dictionary
+ * @property integer $count the number of cookies in the collection
  *
  * @author Qiang Xue <qiang.xue@gmail.com>
  * @since 2.0
@@ -39,28 +22,35 @@ use yii\base\DictionaryIterator;
 class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \ArrayAccess, \Countable
 {
 	/**
-	 * @var Cookie[] internal data storage
+	 * @var boolean whether to enable cookie validation. By setting this property to true,
+	 * if a cookie is tampered on the client side, it will be ignored when received on the server side.
+	 */
+	public $enableValidation = true;
+	/**
+	 * @var string the secret key used for cookie validation. If not set, a random key will be generated and used.
+	 */
+	public $validationKey;
+
+	/**
+	 * @var Cookie[] the cookies in this collection (indexed by the cookie names)
 	 */
 	private $_cookies = array();
 
 	/**
 	 * Constructor.
-	 * Initializes the dictionary with an array or an iterable object.
-	 * @param array $cookies the initial data to be populated into the dictionary.
-	 * This can be an array or an iterable object.
 	 * @param array $config name-value pairs that will be used to initialize the object properties
 	 */
-	public function __construct($cookies = array(), $config = array())
+	public function __construct($config = array())
 	{
-		$this->_cookies = $cookies;
 		parent::__construct($config);
+		$this->_cookies = $this->loadCookies();
 	}
 
 	/**
-	 * Returns an iterator for traversing the items in the dictionary.
+	 * Returns an iterator for traversing the cookies in the collection.
 	 * This method is required by the SPL interface `IteratorAggregate`.
-	 * It will be implicitly called when you use `foreach` to traverse the dictionary.
-	 * @return DictionaryIterator an iterator for traversing the items in the dictionary.
+	 * It will be implicitly called when you use `foreach` to traverse the collection.
+	 * @return DictionaryIterator an iterator for traversing the cookies in the collection.
 	 */
 	public function getIterator()
 	{
@@ -68,10 +58,10 @@ class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \
 	}
 
 	/**
-	 * Returns the number of items in the dictionary.
+	 * Returns the number of cookies in the collection.
 	 * This method is required by the SPL `Countable` interface.
-	 * It will be implicitly called when you use `count($dictionary)`.
-	 * @return integer number of items in the dictionary.
+	 * It will be implicitly called when you use `count($collection)`.
+	 * @return integer the number of cookies in the collection.
 	 */
 	public function count()
 	{
@@ -79,8 +69,8 @@ class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \
 	}
 
 	/**
-	 * Returns the number of items in the dictionary.
-	 * @return integer the number of items in the dictionary
+	 * Returns the number of cookies in the collection.
+	 * @return integer the number of cookies in the collection.
 	 */
 	public function getCount()
 	{
@@ -88,72 +78,85 @@ class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \
 	}
 
 	/**
-	 * Returns the keys stored in the dictionary.
-	 * @return array the key list
+	 * Returns the cookie with the specified name.
+	 * @param string $name the cookie name
+	 * @return Cookie the cookie with the specified name. Null if the named cookie does not exist.
+	 * @see getValue()
 	 */
-	public function getNames()
+	public function get($name)
 	{
-		return array_keys($this->_cookies);
+		return isset($this->_cookies[$name]) ? $this->_cookies[$name] : null;
 	}
 
 	/**
-	 * Returns the item with the specified key.
-	 * @param mixed $name the key
-	 * @return Cookie the element with the specified key.
-	 * Null if the key cannot be found in the dictionary.
+	 * Returns the value of the named cookie.
+	 * @param string $name the cookie name
+	 * @param mixed $defaultValue the value that should be returned when the named cookie does not exist.
+	 * @return mixed the value of the named cookie.
+	 * @see get()
 	 */
-	public function getCookie($name)
+	public function getValue($name, $defaultValue = null)
 	{
-		return isset($this->_cookies[$name]) ? $this->_cookies[$name] : null;
+		return isset($this->_cookies[$name]) ? $this->_cookies[$name]->value : $defaultValue;
 	}
 
 	/**
-	 * Adds an item into the dictionary.
-	 * Note, if the specified key already exists, the old value will be overwritten.
-	 * @param Cookie $cookie value
-	 * @throws Exception if the dictionary is read-only
+	 * Adds a cookie to the collection.
+	 * If there is already a cookie with the same name in the collection, it will be removed first.
+	 * @param Cookie $cookie the cookie to be added
 	 */
-	public function add(Cookie $cookie)
+	public function add($cookie)
 	{
 		if (isset($this->_cookies[$cookie->name])) {
-			$this->remove($this->_cookies[$cookie->name]);
+			$c = $this->_cookies[$cookie->name];
+			setcookie($c->name, '', 0, $c->path, $c->domain, $c->secure, $c->httponly);
 		}
-		setcookie($cookie->name, $cookie->value, $cookie->expire, $cookie->path, $cookie->domain, $cookie->secure, $cookie->httpOnly);
+
+		$value = $cookie->value;
+		if ($this->enableValidation) {
+			if ($this->validationKey === null) {
+				$key = SecurityHelper::getSecretKey(__CLASS__ . '/' . Yii::$app->id);
+			} else {
+				$key = $this->validationKey;
+			}
+			$value = SecurityHelper::hashData(serialize($value), $key);
+		}
+
+		setcookie($cookie->name, $value, $cookie->expire, $cookie->path, $cookie->domain, $cookie->secure, $cookie->httponly);
 		$this->_cookies[$cookie->name] = $cookie;
 	}
 
 	/**
-	 * Removes an item from the dictionary by its key.
-	 * @param mixed $key the key of the item to be removed
-	 * @return mixed the removed value, null if no such key exists.
-	 * @throws Exception if the dictionary is read-only
+	 * Removes a cookie from the collection.
+	 * @param Cookie|string $cookie the cookie object or the name of the cookie to be removed.
 	 */
-	public function remove(Cookie $cookie)
+	public function remove($cookie)
 	{
-		setcookie($cookie->name, '', 0, $cookie->path, $cookie->domain, $cookie->secure, $cookie->httpOnly);
-		unset($this->_cookies[$cookie->name]);
+		if (is_string($cookie) && isset($this->_cookies[$cookie])) {
+			$cookie = $this->_cookies[$cookie];
+		}
+		if ($cookie instanceof Cookie) {
+			setcookie($cookie->name, '', 0, $cookie->path, $cookie->domain, $cookie->secure, $cookie->httponly);
+			unset($this->_cookies[$cookie->name]);
+		}
 	}
 
 	/**
-	 * Removes all items from the dictionary.
-	 * @param boolean $safeClear whether to clear every item by calling [[remove]].
-	 * Defaults to false, meaning all items in the dictionary will be cleared directly
-	 * without calling [[remove]].
+	 * Removes all cookies.
 	 */
-	public function clear($safeClear = false)
+	public function removeAll()
 	{
-		if ($safeClear) {
-			foreach (array_keys($this->_cookies) as $key) {
-				$this->remove($key);
-			}
-		} else {
-			$this->_cookies = array();
+		foreach ($this->_cookies as $cookie) {
+			setcookie($cookie->name, '', 0, $cookie->path, $cookie->domain, $cookie->secure, $cookie->httponly);
 		}
+		$this->_cookies = array();
 	}
 
 	/**
-	 * Returns the dictionary as a PHP array.
-	 * @return array the list of items in array
+	 * Returns the collection as a PHP array.
+	 * @return array the array representation of the collection.
+	 * The array keys are cookie names, and the array values are the corresponding
+	 * cookie objects.
 	 */
 	public function toArray()
 	{
@@ -161,76 +164,82 @@ class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \
 	}
 
 	/**
-	 * Returns whether there is an element at the specified offset.
+	 * Returns whether there is a cookie with the specified name.
 	 * This method is required by the SPL interface `ArrayAccess`.
-	 * It is implicitly called when you use something like `isset($dictionary[$offset])`.
-	 * This is equivalent to [[contains]].
-	 * @param mixed $offset the offset to check on
-	 * @return boolean
+	 * It is implicitly called when you use something like `isset($collection[$name])`.
+	 * @param string $name the cookie name
+	 * @return boolean whether the named cookie exists
 	 */
-	public function offsetExists($offset)
+	public function offsetExists($name)
 	{
-		return isset($this->_cookies[$offset]);
+		return isset($this->_cookies[$name]);
 	}
 
 	/**
-	 * Returns the element at the specified offset.
+	 * Returns the cookie with the specified name.
 	 * This method is required by the SPL interface `ArrayAccess`.
-	 * It is implicitly called when you use something like `$value = $dictionary[$offset];`.
-	 * This is equivalent to [[itemAt]].
-	 * @param mixed $offset the offset to retrieve element.
-	 * @return mixed the element at the offset, null if no element is found at the offset
+	 * It is implicitly called when you use something like `$cookie = $collection[$name];`.
+	 * This is equivalent to [[get()]].
+	 * @param string $name the cookie name
+	 * @return Cookie the cookie with the specified name, null if the named cookie does not exist.
 	 */
-	public function offsetGet($offset)
+	public function offsetGet($name)
 	{
-		return $this->getCookie($offset);
+		return $this->get($name);
 	}
 
 	/**
-	 * Sets the element at the specified offset.
+	 * Adds the cookie to the collection.
 	 * This method is required by the SPL interface `ArrayAccess`.
-	 * It is implicitly called when you use something like `$dictionary[$offset] = $item;`.
-	 * If the offset is null, the new item will be appended to the dictionary.
-	 * Otherwise, the existing item at the offset will be replaced with the new item.
-	 * This is equivalent to [[add]].
-	 * @param mixed $offset the offset to set element
-	 * @param mixed $item the element value
-	 */
-	public function offsetSet($offset, $item)
+	 * It is implicitly called when you use something like `$collection[$name] = $cookie;`.
+	 * This is equivalent to [[add()]].
+	 * @param string $name the cookie name
+	 * @param Cookie $cookie the cookie to be added
+	 */
+	public function offsetSet($name, $cookie)
 	{
-		$this->add($item);
+		$this->add($cookie);
 	}
 
 	/**
-	 * Unsets the element at the specified offset.
+	 * Removes the named cookie.
 	 * This method is required by the SPL interface `ArrayAccess`.
-	 * It is implicitly called when you use something like `unset($dictionary[$offset])`.
-	 * This is equivalent to [[remove]].
-	 * @param mixed $offset the offset to unset element
+	 * It is implicitly called when you use something like `unset($collection[$name])`.
+	 * This is equivalent to [[remove()]].
+	 * @param string $name the cookie name
 	 */
-	public function offsetUnset($offset)
+	public function offsetUnset($name)
 	{
-		if (isset($this->_cookies[$offset])) {
-			$this->remove($this->_cookies[$offset]);
-		}
+		$this->remove($name);
 	}
 
 	/**
-	 * @return array list of validated cookies
+	 * Returns the current cookies in terms of [[Cookie]] objects.
+	 * @return Cookie[] list of current cookies
 	 */
-	protected function loadCookies($data)
+	protected function loadCookies()
 	{
 		$cookies = array();
-		if ($this->_request->enableCookieValidation) {
-			$sm = Yii::app()->getSecurityManager();
+		if ($this->enableValidation) {
+			if ($this->validationKey === null) {
+				$key = SecurityHelper::getSecretKey(__CLASS__ . '/' . Yii::$app->id);
+			} else {
+				$key = $this->validationKey;
+			}
 			foreach ($_COOKIE as $name => $value) {
-				if (is_string($value) && ($value = $sm->validateData($value)) !== false) {
-					$cookies[$name] = new CHttpCookie($name, @unserialize($value));
+				if (is_string($value) && ($value = SecurityHelper::validateData($value, $key)) !== false) {
+					$cookies[$name] = new Cookie(array(
+						'name' => $name,
+						'value' => @unserialize($value),
+					));
 				}
 			}
 		} else {
 			foreach ($_COOKIE as $name => $value) {
-				$cookies[$name] = new CHttpCookie($name, $value);
+				$cookies[$name] = new Cookie(array(
+					'name' => $name,
+					'value' => $value,
+				));
 			}
 		}
 		return $cookies;
diff --git a/framework/web/DbSession.php b/framework/web/DbSession.php
new file mode 100644
index 0000000..d3afc76
--- /dev/null
+++ b/framework/web/DbSession.php
@@ -0,0 +1,221 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+use Yii;
+use yii\db\Connection;
+use yii\db\Query;
+use yii\base\InvalidConfigException;
+
+/**
+ * DbSession extends [[Session]] by using database as session data storage.
+ *
+ * By default, DbSession stores session data in a DB table named 'tbl_session'. This table
+ * must be pre-created. The table name can be changed by setting [[sessionTable]].
+ * 
+ * The following example shows how you can configure the application to use DbSession:
+ * 
+ * ~~~
+ * 'session' => array(
+ *     'class' => 'yii\web\DbSession',
+ *     // 'db' => 'mydb',
+ *     // 'sessionTable' => 'my_session',
+ * )
+ * ~~~
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class DbSession extends Session
+{
+	/**
+	 * @var Connection|string the DB connection object or the application component ID of the DB connection.
+	 * After the DbSession object is created, if you want to change this property, you should only assign it
+	 * with a DB connection object.
+	 */
+	public $db = 'db';
+	/**
+	 * @var string the name of the DB table that stores the session data.
+	 * The table should be pre-created as follows:
+	 *
+	 * ~~~
+	 * CREATE TABLE tbl_session
+	 * (
+	 *     id CHAR(40) NOT NULL PRIMARY KEY,
+	 *     expire INTEGER,
+	 *     data BLOB
+	 * )
+	 * ~~~
+	 *
+	 * where 'BLOB' refers to the BLOB-type of your preferred DBMS. Below are the BLOB type
+	 * that can be used for some popular DBMS:
+	 *
+	 * - MySQL: LONGBLOB
+	 * - PostgreSQL: BYTEA
+	 * - MSSQL: BLOB
+	 *
+	 * When using DbSession in a production server, we recommend you create a DB index for the 'expire'
+	 * column in the session table to improve the performance.
+	 */
+	public $sessionTable = 'tbl_session';
+
+	/**
+	 * Initializes the DbSession component.
+	 * This method will initialize the [[db]] property to make sure it refers to a valid DB connection.
+	 * @throws InvalidConfigException if [[db]] is invalid.
+	 */
+	public function init()
+	{
+		parent::init();
+		if (is_string($this->db)) {
+			$this->db = Yii::$app->getComponent($this->db);
+		}
+		if (!$this->db instanceof Connection) {
+			throw new InvalidConfigException("DbSession::db must be either a DB connection instance or the application component ID of a DB connection.");
+		}		
+	}
+
+	/**
+	 * Returns a value indicating whether to use custom session storage.
+	 * This method overrides the parent implementation and always returns true.
+	 * @return boolean whether to use custom storage.
+	 */
+	public function getUseCustomStorage()
+	{
+		return true;
+	}
+
+	/**
+	 * Updates the current session ID with a newly generated one .
+	 * Please refer to [[http://php.net/session_regenerate_id]] for more details.
+	 * @param boolean $deleteOldSession Whether to delete the old associated session file or not.
+	 */
+	public function regenerateID($deleteOldSession = false)
+	{
+		$oldID = session_id();
+
+		// if no session is started, there is nothing to regenerate
+		if (empty($oldID)) {
+			return;
+		}
+
+		parent::regenerateID(false);
+		$newID = session_id();
+
+		$query = new Query;
+		$row = $query->from($this->sessionTable)
+			->where(array('id' => $oldID))
+			->createCommand($this->db)
+			->queryRow();
+		if ($row !== false) {
+			if ($deleteOldSession) {
+				$this->db->createCommand()
+					->update($this->sessionTable, array('id' => $newID), array('id' => $oldID))
+					->execute();
+			} else {
+				$row['id'] = $newID;
+				$this->db->createCommand()
+					->insert($this->sessionTable, $row)
+					->execute();
+			}
+		} else {
+			// shouldn't reach here normally
+			$this->db->createCommand()
+				->insert($this->sessionTable, array(
+					'id' => $newID,
+					'expire' => time() + $this->getTimeout(),
+				))->execute();
+		}
+	}
+
+	/**
+	 * Session read handler.
+	 * Do not call this method directly.
+	 * @param string $id session ID
+	 * @return string the session data
+	 */
+	public function readSession($id)
+	{
+		$query = new Query;
+		$data = $query->select(array('data'))
+			->from($this->sessionTable)
+			->where('expire>:expire AND id=:id', array(':expire' => time(), ':id' => $id))
+			->createCommand($this->db)
+			->queryScalar();
+		return $data === false ? '' : $data;
+	}
+
+	/**
+	 * Session write handler.
+	 * Do not call this method directly.
+	 * @param string $id session ID
+	 * @param string $data session data
+	 * @return boolean whether session write is successful
+	 */
+	public function writeSession($id, $data)
+	{
+		// exception must be caught in session write handler
+		// http://us.php.net/manual/en/function.session-set-save-handler.php
+		try {
+			$expire = time() + $this->getTimeout();
+			$query = new Query;
+			$exists = $query->select(array('id'))
+				->from($this->sessionTable)
+				->where(array('id' => $id))
+				->createCommand($this->db)
+				->queryScalar();
+			if ($exists === false) {
+				$this->db->createCommand()
+					->insert($this->sessionTable, array(
+						'id' => $id,
+						'data' => $data,
+						'expire' => $expire,
+					))->execute();
+			} else {
+				$this->db->createCommand()
+					->update($this->sessionTable, array('data' => $data, 'expire' => $expire), array('id' => $id))
+					->execute();
+			}
+		} catch (\Exception $e) {
+			if (YII_DEBUG) {
+				echo $e->getMessage();
+			}
+			// it is too late to log an error message here
+			return false;
+		}
+		return true;
+	}
+
+	/**
+	 * Session destroy handler.
+	 * Do not call this method directly.
+	 * @param string $id session ID
+	 * @return boolean whether session is destroyed successfully
+	 */
+	public function destroySession($id)
+	{
+		$this->db->createCommand()
+			->delete($this->sessionTable, array('id' => $id))
+			->execute();
+		return true;
+	}
+
+	/**
+	 * Session GC (garbage collection) handler.
+	 * Do not call this method directly.
+	 * @param integer $maxLifetime the number of seconds after which data will be seen as 'garbage' and cleaned up.
+	 * @return boolean whether session is GCed successfully
+	 */
+	public function gcSession($maxLifetime)
+	{
+		$this->db->createCommand()
+			->delete($this->sessionTable, 'expire<:expire', array(':expire' => time()))
+			->execute();
+		return true;
+	}
+}
diff --git a/framework/web/HttpCache.php b/framework/web/HttpCache.php
new file mode 100644
index 0000000..f64b37f
--- /dev/null
+++ b/framework/web/HttpCache.php
@@ -0,0 +1,131 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+use Yii;
+use yii\base\ActionFilter;
+use yii\base\Action;
+
+/**
+ * @author Da:Sourcerer <webmaster@dasourcerer.net>
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class HttpCache extends ActionFilter
+{
+	/**
+	 * @var callback a PHP callback that returns the UNIX timestamp of the last modification time.
+	 * The callback's signature should be:
+	 *
+	 * ~~~
+	 * function ($action, $params)
+	 * ~~~
+	 *
+	 * where `$action` is the [[Action]] object that this filter is currently handling;
+	 * `$params` takes the value of [[params]]. The callback should return a UNIX timestamp.
+	 */
+	public $lastModified;
+	/**
+	 * @var callback a PHP callback that generates the Etag seed string.
+	 * The callback's signature should be:
+	 *
+	 * ~~~
+	 * function ($action, $params)
+	 * ~~~
+	 *
+	 * where `$action` is the [[Action]] object that this filter is currently handling;
+	 * `$params` takes the value of [[params]]. The callback should return a string serving
+	 * as the seed for generating an Etag.
+	 */
+	public $etagSeed;
+	/**
+	 * @var mixed additional parameters that should be passed to the [[lastModified]] and [[etagSeed]] callbacks.
+	 */
+	public $params;
+	/**
+	 * @var string HTTP cache control header. If null, the header will not be sent.
+	 */
+	public $cacheControlHeader = 'Cache-Control: max-age=3600, public';
+
+	/**
+	 * 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)
+	{
+		$verb = Yii::$app->request->getRequestMethod();
+		if ($verb !== 'GET' && $verb !== 'HEAD' || $this->lastModified === null && $this->etagSeed === null) {
+			return true;
+		}
+
+		$lastModified = $etag = null;
+		if ($this->lastModified !== null) {
+			$lastModified = call_user_func($this->lastModified, $action, $this->params);
+		}
+		if ($this->etagSeed !== null) {
+			$seed = call_user_func($this->etagSeed, $action, $this->params);
+			$etag = $this->generateEtag($seed);
+		}
+
+		$this->sendCacheControlHeader();
+		if ($etag !== null) {
+			header("ETag: $etag");
+		}
+
+		if ($this->validateCache($lastModified, $etag)) {
+			header('HTTP/1.1 304 Not Modified');
+			return false;
+		}
+
+		if ($lastModified !== null) {
+			header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $lastModified) . ' GMT');
+		}
+		return true;
+	}
+
+	/**
+	 * Validates if the HTTP cache contains valid content.
+	 * @param integer $lastModified the calculated Last-Modified value in terms of a UNIX timestamp.
+	 * If null, the Last-Modified header will not be validated.
+	 * @param string $etag the calculated ETag value. If null, the ETag header will not be validated.
+	 * @return boolean whether the HTTP cache is still valid.
+	 */
+	protected function validateCache($lastModified, $etag)
+	{
+		if ($lastModified !== null && (!isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) || @strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) < $lastModified)) {
+			return false;
+		} else {
+			return $etag === null || isset($_SERVER['HTTP_IF_NONE_MATCH']) && $_SERVER['HTTP_IF_NONE_MATCH'] === $etag;
+		}
+	}
+
+	/**
+	 * Sends the cache control header to the client
+	 * @see cacheControl
+	 */
+	protected function sendCacheControlHeader()
+	{
+		session_cache_limiter('public');
+		header('Pragma:', true);
+		if ($this->cacheControlHeader !== null) {
+			header($this->cacheControlHeader, true);
+		}
+	}
+
+	/**
+	 * Generates an Etag from the given seed string.
+	 * @param string $seed Seed for the ETag
+	 * @return string the generated Etag
+	 */
+	protected function generateEtag($seed)
+	{
+		return '"' . base64_encode(sha1($seed, true)) . '"';
+	}
+}
\ No newline at end of file
diff --git a/framework/web/Identity.php b/framework/web/Identity.php
new file mode 100644
index 0000000..4668337
--- /dev/null
+++ b/framework/web/Identity.php
@@ -0,0 +1,45 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+/**
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+interface Identity
+{
+	/**
+	 * Returns an ID that can uniquely identify a user identity.
+	 * The returned ID can be a string, an integer, or any serializable data.
+	 * @return mixed an ID that uniquely identifies a user identity.
+	 */
+	public function getId();
+	/**
+	 * Returns a key that can be used to check the validity of a given identity ID.
+	 * The space of such keys should be big and random enough to defeat potential identity attacks.
+	 * The returned key can be a string, an integer, or any serializable data.
+	 * @return mixed a key that is used to check the validity of a given identity ID.
+	 * @see validateAuthKey()
+	 */
+	public function getAuthKey();
+	/**
+	 * Validates the given auth key.
+	 * @param string $authKey the given auth key
+	 * @return boolean whether the given auth key is valid.
+	 * @see getAuthKey()
+	 */
+	public function validateAuthKey($authKey);
+	/**
+	 * Finds an identity by the given ID.
+	 * @param mixed $id the ID to be looked for
+	 * @return Identity the identity object that matches the given ID.
+	 * Null should be returned if such an identity cannot be found.
+	 */
+	public static function findIdentity($id);
+}
\ No newline at end of file
diff --git a/framework/web/PageCache.php b/framework/web/PageCache.php
new file mode 100644
index 0000000..29c8cc8
--- /dev/null
+++ b/framework/web/PageCache.php
@@ -0,0 +1,109 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+use Yii;
+use yii\base\ActionFilter;
+use yii\base\Action;
+use yii\base\View;
+use yii\caching\Dependency;
+
+/**
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @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 application component ID of the [[\yii\caching\Cache|cache]] object.
+	 */
+	public $cache = '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('cache', '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/web/Pagination.php b/framework/web/Pagination.php
index 26d670e..1d41c0c 100644
--- a/framework/web/Pagination.php
+++ b/framework/web/Pagination.php
@@ -1,149 +1,119 @@
 <?php
 /**
- * CPagination class file.
- *
- * @author Qiang Xue <qiang.xue@gmail.com>
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008-2011 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
+namespace yii\web;
+
+use Yii;
+
 /**
- * CPagination represents information relevant to pagination.
+ * Pagination represents information relevant to pagination of data items.
  *
- * When data needs to be rendered in multiple pages, we can use CPagination to
- * represent information such as {@link getItemCount total item count},
- * {@link getPageSize page size}, {@link getCurrentPage current page}, etc.
- * These information can be passed to {@link CBasePager pagers} to render
- * pagination buttons or links.
+ * 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\widgets\Pager|pagers]]
+ * to render pagination buttons or links.
  *
- * Example:
+ * The following example shows how to create a pagination object and feed it
+ * to a pager.
  *
  * Controller action:
- * <pre>
- * function actionIndex(){
- *     $criteria=new CDbCriteria();
- *     $count=Article::model()->count($criteria);
- *     $pages=new CPagination($count);
  *
- *     // results per page
- *     $pages->pageSize=10;
- *     $pages->applyLimit($criteria);
- *     $models=Article::model()->findAll($criteria);
+ * ~~~
+ * function actionIndex()
+ * {
+ *     $query = Article::find()->where(array('status' => 1));
+ *     $countQuery = clone $query;
+ *     $pages = new Pagination($countQuery->count());
+ *     $models = $query->offset($pages->offset)
+ *         ->limit($pages->limit)
+ *         ->all();
  *
  *     $this->render('index', array(
- *     'models' => $models,
- *          'pages' => $pages
+ *          'models' => $models,
+ *          'pages' => $pages,
  *     ));
  * }
- * </pre>
+ * ~~~
  *
  * View:
- * <pre>
- * <?php foreach($models as $model): ?>
- *     // display a model
- * <?php endforeach; ?>
+ *
+ * ~~~
+ * foreach($models as $model) {
+ *     // display $model here
+ * }
  *
  * // display pagination
- * <?php $this->widget('CLinkPager', array(
+ * $this->widget('yii\widgets\LinkPager', array(
  *     'pages' => $pages,
- * )) ?>
- * </pre>
+ * ));
+ * ~~~
  *
- * @property integer $pageSize Number of items in each page. Defaults to 10.
- * @property integer $itemCount Total number of items. Defaults to 0.
  * @property integer $pageCount Number of pages.
- * @property integer $currentPage The zero-based index of the current page. Defaults to 0.
+ * @property integer $page The zero-based index of the current page.
  * @property integer $offset The offset of the data. This may be used to set the
  * OFFSET value for a SQL statement for fetching the current page of data.
  * @property integer $limit The limit of the data. This may be used to set the
  * LIMIT value for a SQL statement for fetching the current page of data.
- * This returns the same value as {@link pageSize}.
  *
  * @author Qiang Xue <qiang.xue@gmail.com>
- * @version $Id$
- * @package system.web
- * @since 1.0
+ * @since 2.0
  */
-class CPagination extends CComponent
+class Pagination extends \yii\base\Object
 {
 	/**
-	 * The default page size.
+	 * @var string name of the parameter storing the current page index. Defaults to 'page'.
+	 * @see params
 	 */
-	const DEFAULT_PAGE_SIZE = 10;
+	public $pageVar = 'page';
 	/**
-	 * @var string name of the GET variable storing the current page index. Defaults to 'page'.
+	 * @var boolean whether to always have the page parameter in the URL created by [[createUrl()]].
+	 * If false and [[page]] is 0, the page parameter will not be put in the URL.
 	 */
-	public $pageVar = 'page';
+	public $forcePageVar = false;
 	/**
-	 * @var string the route (controller ID and action ID) for displaying the paged contents.
-	 * Defaults to empty string, meaning using the current route.
+	 * @var string the route of the controller action for displaying the paged contents.
+	 * If not set, it means using the currently requested route.
 	 */
-	public $route = '';
+	public $route;
 	/**
-	 * @var array of parameters (name=>value) that should be used instead of GET when generating pagination URLs.
-	 * Defaults to null, meaning using the currently available GET parameters.
+	 * @var array parameters (name=>value) that should be used to obtain the current page number
+	 * and to create new pagination URLs. If not set, $_GET will be used instead.
+	 *
+	 * The array element indexed by [[pageVar]] is considered to be the current page number.
+	 * If the element does not exist, the current page number is considered 0.
 	 */
 	public $params;
 	/**
-	 * @var boolean whether to ensure {@link currentPage} is returning a valid page number.
-	 * When this property is true, the value returned by {@link currentPage} will always be between
-	 * 0 and ({@link pageCount}-1). Because {@link pageCount} relies on the correct value of {@link itemCount},
-	 * it means you must have knowledge about the total number of data items when you want to access {@link currentPage}.
-	 * This is fine for SQL-based queries, but may not be feasible for other kinds of queries (e.g. MongoDB).
-	 * In those cases, you may set this property to be false to skip the validation (you may need to validate yourself then).
-	 * Defaults to true.
-	 * @since 1.1.4
+	 * @var boolean whether to check if [[page]] is within valid range.
+	 * When this property is true, the value of [[page]] will always be between 0 and ([[pageCount]]-1).
+	 * Because [[pageCount]] relies on the correct value of [[itemCount]] which may not be available
+	 * in some cases (e.g. MongoDB), you may want to set this property to be false to disable the page
+	 * number validation. By doing so, [[page]] will return the value indexed by [[pageVar]] in [[params]].
 	 */
-	public $validateCurrentPage = true;
-
-	private $_pageSize = self::DEFAULT_PAGE_SIZE;
-	private $_itemCount = 0;
-	private $_currentPage;
-
+	public $validatePage = true;
 	/**
-	 * Constructor.
-	 * @param integer $itemCount total number of items.
+	 * @var integer number of items on each page. Defaults to 10.
+	 * If it is less than 1, it means the page size is infinite, and thus a single page contains all items.
 	 */
-	public function __construct($itemCount = 0)
-	{
-		$this->setItemCount($itemCount);
-	}
-
+	public $pageSize = 10;
 	/**
-	 * @return integer number of items in each page. Defaults to 10.
+	 * @var integer total number of items.
 	 */
-	public function getPageSize()
-	{
-		return $this->_pageSize;
-	}
+	public $itemCount;
 
 	/**
-	 * @param integer $value number of items in each page
-	 */
-	public function setPageSize($value)
-	{
-		if (($this->_pageSize = $value) <= 0) {
-			$this->_pageSize = self::DEFAULT_PAGE_SIZE;
-		}
-	}
-
-	/**
-	 * @return integer total number of items. Defaults to 0.
+	 * Constructor.
+	 * @param integer $itemCount total number of items.
+	 * @param array $config name-value pairs that will be used to initialize the object properties
 	 */
-	public function getItemCount()
+	public function __construct($itemCount, $config = array())
 	{
-		return $this->_itemCount;
-	}
-
-	/**
-	 * @param integer $value total number of items.
-	 */
-	public function setItemCount($value)
-	{
-		if (($this->_itemCount = $value) < 0) {
-			$this->_itemCount = 0;
-		}
+		$this->itemCount = $itemCount;
+		parent::__construct($config);
 	}
 
 	/**
@@ -151,94 +121,88 @@ class CPagination extends CComponent
 	 */
 	public function getPageCount()
 	{
-		return (int)(($this->_itemCount + $this->_pageSize - 1) / $this->_pageSize);
+		if ($this->pageSize < 1) {
+			return $this->itemCount > 0 ? 1 : 0;
+		} else {
+			$itemCount = $this->itemCount < 0 ? 0 : (int)$this->itemCount;
+			return (int)(($itemCount + $this->pageSize - 1) / $this->pageSize);
+		}
 	}
 
+	private $_page;
+
 	/**
+	 * Returns the zero-based current page number.
 	 * @param boolean $recalculate whether to recalculate the current page based on the page size and item count.
-	 * @return integer the zero-based index of the current page. Defaults to 0.
+	 * @return integer the zero-based current page number.
 	 */
-	public function getCurrentPage($recalculate = true)
+	public function getPage($recalculate = false)
 	{
-		if ($this->_currentPage === null || $recalculate) {
-			if (isset($_GET[$this->pageVar])) {
-				$this->_currentPage = (int)$_GET[$this->pageVar] - 1;
-				if ($this->validateCurrentPage) {
+		if ($this->_page === null || $recalculate) {
+			$params = $this->params === null ? $_GET : $this->params;
+			if (isset($params[$this->pageVar]) && is_scalar($params[$this->pageVar])) {
+				$this->_page = (int)$params[$this->pageVar] - 1;
+				if ($this->validatePage) {
 					$pageCount = $this->getPageCount();
-					if ($this->_currentPage >= $pageCount) {
-						$this->_currentPage = $pageCount - 1;
+					if ($this->_page >= $pageCount) {
+						$this->_page = $pageCount - 1;
 					}
 				}
-				if ($this->_currentPage < 0) {
-					$this->_currentPage = 0;
+				if ($this->_page < 0) {
+					$this->_page = 0;
 				}
 			} else {
-				$this->_currentPage = 0;
+				$this->_page = 0;
 			}
 		}
-		return $this->_currentPage;
+		return $this->_page;
 	}
 
 	/**
+	 * Sets the current page number.
 	 * @param integer $value the zero-based index of the current page.
 	 */
-	public function setCurrentPage($value)
+	public function setPage($value)
 	{
-		$this->_currentPage = $value;
-		$_GET[$this->pageVar] = $value + 1;
+		$this->_page = $value;
 	}
 
 	/**
-	 * Creates the URL suitable for pagination.
-	 * This method is mainly called by pagers when creating URLs used to
-	 * perform pagination. The default implementation is to call
-	 * the controller's createUrl method with the page information.
-	 * You may override this method if your URL scheme is not the same as
-	 * the one supported by the controller's createUrl method.
-	 * @param CController $controller the controller that will create the actual URL
-	 * @param integer $page the page that the URL should point to. This is a zero-based index.
+	 * Creates the URL suitable for pagination with the specified page number.
+	 * This method is mainly called by pagers when creating URLs used to perform pagination.
+	 * @param integer $page the zero-based page number that the URL should point to.
 	 * @return string the created URL
+	 * @see params
+	 * @see forcePageVar
 	 */
-	public function createPageUrl($controller, $page)
+	public function createUrl($page)
 	{
 		$params = $this->params === null ? $_GET : $this->params;
-		if ($page > 0) // page 0 is the default
-		{
+		if ($page > 0 || $page >= 0 && $this->forcePageVar) {
 			$params[$this->pageVar] = $page + 1;
 		} else {
 			unset($params[$this->pageVar]);
 		}
-		return $controller->createUrl($this->route, $params);
-	}
-
-	/**
-	 * Applies LIMIT and OFFSET to the specified query criteria.
-	 * @param CDbCriteria $criteria the query criteria that should be applied with the limit
-	 */
-	public function applyLimit($criteria)
-	{
-		$criteria->limit = $this->getLimit();
-		$criteria->offset = $this->getOffset();
+		$route = $this->route === null ? Yii::$app->controller->route : $this->route;
+		return Yii::$app->getUrlManager()->createUrl($route, $params);
 	}
 
 	/**
 	 * @return integer the offset of the data. This may be used to set the
 	 * OFFSET value for a SQL statement for fetching the current page of data.
-	 * @since 1.1.0
 	 */
 	public function getOffset()
 	{
-		return $this->getCurrentPage() * $this->getPageSize();
+		return $this->pageSize < 1 ? 0 : $this->getPage() * $this->pageSize;
 	}
 
 	/**
 	 * @return integer the limit of the data. This may be used to set the
 	 * LIMIT value for a SQL statement for fetching the current page of data.
-	 * This returns the same value as {@link pageSize}.
-	 * @since 1.1.0
+	 * Note that if the page size is infinite, a value -1 will be returned.
 	 */
 	public function getLimit()
 	{
-		return $this->getPageSize();
+		return $this->pageSize < 1 ? -1 : $this->pageSize;
 	}
 }
\ No newline at end of file
diff --git a/framework/web/Request.php b/framework/web/Request.php
index 09e4864..093a394 100644
--- a/framework/web/Request.php
+++ b/framework/web/Request.php
@@ -1,15 +1,15 @@
 <?php
 /**
- * Request class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
 namespace yii\web;
 
-use \yii\base\InvalidConfigException;
+use Yii;
+use yii\base\HttpException;
+use yii\base\InvalidConfigException;
 
 /**
  * @author Qiang Xue <qiang.xue@gmail.com>
@@ -18,19 +18,13 @@ use \yii\base\InvalidConfigException;
 class Request extends \yii\base\Request
 {
 	/**
-	 * @var boolean whether cookies should be validated to ensure they are not tampered. Defaults to false.
+	 * @var boolean whether cookies should be validated to ensure they are not tampered. Defaults to true.
 	 */
-	public $enableCookieValidation = false;
+	public $enableCookieValidation = true;
 	/**
-	 * @var boolean whether to enable CSRF (Cross-Site Request Forgery) validation. Defaults to false.
-	 * By setting this property to true, forms submitted to an Yii Web application must be originated
-	 * from the same application. If not, a 400 HTTP exception will be raised.
-	 * Note, this feature requires that the user client accepts cookie.
-	 * You also need to use {@link CHtml::form} or {@link CHtml::statefulForm} to generate
-	 * the needed HTML forms in your pages.
-	 * @see http://seclab.stanford.edu/websec/csrf/csrf.pdf
+	 * @var string the secret key used for cookie validation. If not set, a random key will be generated and used.
 	 */
-	public $enableCsrfValidation = false;
+	public $cookieValidationKey;
 	/**
 	 * @var string|boolean the name of the POST parameter that is used to indicate if a request is a PUT or DELETE
 	 * request tunneled through POST. If false, it means disabling REST request tunneled through POST.
@@ -38,85 +32,43 @@ class Request extends \yii\base\Request
 	 * @see getRequestMethod
 	 * @see getRestParams
 	 */
-	public $restPostVar = '_method';
-	/**
-	 * @var string the name of the token used to prevent CSRF. Defaults to 'YII_CSRF_TOKEN'.
-	 * This property is effective only when {@link enableCsrfValidation} is true.
-	 */
-	public $csrfTokenName = 'YII_CSRF_TOKEN';
-	/**
-	 * @var array the property values (in name-value pairs) used to initialize the CSRF cookie.
-	 * Any property of {@link CHttpCookie} may be initialized.
-	 * This property is effective only when {@link enableCsrfValidation} is true.
-	 */
-	public $csrfCookie;
+	public $restVar = '_method';
 
 	private $_cookies;
 
 	/**
-	 * Initializes the application component.
-	 * This method overrides the parent implementation by preprocessing
-	 * the user request data.
+	 * Resolves the current request into a route and the associated parameters.
+	 * @return array the first element is the route, and the second is the associated parameters.
+	 * @throws HttpException if the request cannot be resolved.
 	 */
-	public function init()
+	public function resolve()
 	{
-		parent::init();
-		$this->normalizeRequest();
-	}
+		Yii::setAlias('@www', $this->getBaseUrl());
 
-	/**
-	 * Normalizes the request data.
-	 * This method strips off slashes in request data if get_magic_quotes_gpc() returns true.
-	 * It also performs CSRF validation if {@link enableCsrfValidation} is true.
-	 */
-	protected function normalizeRequest()
-	{
-		if (get_magic_quotes_gpc()) {
-			if (isset($_GET)) {
-				$_GET = $this->stripSlashes($_GET);
-			}
-			if (isset($_POST)) {
-				$_POST = $this->stripSlashes($_POST);
-			}
-			if (isset($_REQUEST)) {
-				$_REQUEST = $this->stripSlashes($_REQUEST);
-			}
-			if (isset($_COOKIE)) {
-				$_COOKIE = $this->stripSlashes($_COOKIE);
-			}
-		}
-
-		if ($this->enableCsrfValidation) {
-			\Yii::$application->on('beginRequest', array($this, 'validateCsrfToken'));
+		$result = Yii::$app->getUrlManager()->parseRequest($this);
+		if ($result !== false) {
+			list ($route, $params) = $result;
+			$params = array_merge($_GET, $params);
+			return array($route, $params);
+		} else {
+			throw new HttpException(404, Yii::t('yii|Page not found.'));
 		}
 	}
 
 	/**
-	 * Strips slashes from input data.
-	 * This method is applied when magic quotes is enabled.
-	 * @param mixed $data input data to be processed
-	 * @return mixed processed data
-	 */
-	public function stripSlashes($data)
-	{
-		return is_array($data) ? array_map(array($this, 'stripSlashes'), $data) : stripslashes($data);
-	}
-
-	/**
 	 * Returns the method of the current request (e.g. GET, POST, HEAD, PUT, DELETE).
 	 * @return string request method, such as GET, POST, HEAD, PUT, DELETE.
 	 * The value returned is turned into upper case.
 	 */
 	public function getRequestMethod()
 	{
-		if ($this->restPostVar !== false && isset($_POST[$this->restPostVar])) {
-			return strtoupper($_POST[$this->restPostVar]);
+		if ($this->restVar !== false && isset($_POST[$this->restVar])) {
+			return strtoupper($_POST[$this->restVar]);
 		} else {
 			return isset($_SERVER['REQUEST_METHOD']) ? strtoupper($_SERVER['REQUEST_METHOD']) : 'GET';
 		}
 	}
 
-
 	/**
 	 * Returns whether this is a POST request.
 	 * @return boolean whether this is a POST request.
@@ -154,7 +106,7 @@ class Request extends \yii\base\Request
 	}
 
 	/**
-	 * Returns whether this is an Adobe Flash or Adobe Flex request.
+	 * Returns whether this is an Adobe Flash or Flex request.
 	 * @return boolean whether this is an Adobe Flash or Adobe Flex request.
 	 */
 	public function getIsFlashRequest()
@@ -173,42 +125,41 @@ class Request extends \yii\base\Request
 	public function getRestParams()
 	{
 		if ($this->_restParams === null) {
-			if ($this->restPostVar !== false && isset($_POST[$this->restPostVar])) {
+			if ($this->restVar !== false && isset($_POST[$this->restVar])) {
 				$this->_restParams = $_POST;
 			} else {
 				$this->_restParams = array();
 				if (function_exists('mb_parse_str')) {
-					mb_parse_str(file_get_contents('php://input'), $this->_restParams);
+					mb_parse_str($this->getRawBody(), $this->_restParams);
 				} else {
-					parse_str(file_get_contents('php://input'), $this->_restParams);
+					parse_str($this->getRawBody(), $this->_restParams);
 				}
 			}
 		}
 		return $this->_restParams;
 	}
 
+	private $_rawBody;
+
 	/**
-	 * Sets the RESTful parameters.
-	 * @param array $values the RESTful parameters (name-value pairs)
+	 * Returns the raw HTTP request body.
+	 * @return string the request body
 	 */
-	public function setRestParams($values)
+	public function getRawBody()
 	{
-		$this->_restParams = $values;
+		if ($this->_rawBody === null) {
+			$this->_rawBody = file_get_contents('php://input');
+		}
+		return $this->_rawBody;
 	}
 
 	/**
-	 * Returns the named GET or POST parameter value.
-	 * If the GET or POST parameter does not exist, the second parameter to this method will be returned.
-	 * If both GET and POST contains such a named parameter, the GET parameter takes precedence.
-	 * @param string $name the GET parameter name
-	 * @param mixed $defaultValue the default parameter value if the GET parameter does not exist.
-	 * @return mixed the GET parameter value
-	 * @see getQuery
-	 * @see getPost
+	 * Sets the RESTful parameters.
+	 * @param array $values the RESTful parameters (name-value pairs)
 	 */
-	public function getParam($name, $defaultValue = null)
+	public function setRestParams($values)
 	{
-		return isset($_GET[$name]) ? $_GET[$name] : (isset($_POST[$name]) ? $_POST[$name] : $defaultValue);
+		$this->_restParams = $values;
 	}
 
 	/**
@@ -230,9 +181,8 @@ class Request extends \yii\base\Request
 	 * @param mixed $defaultValue the default parameter value if the GET parameter does not exist.
 	 * @return mixed the GET parameter value
 	 * @see getPost
-	 * @see getParam
 	 */
-	public function getQuery($name, $defaultValue = null)
+	public function getParam($name, $defaultValue = null)
 	{
 		return isset($_GET[$name]) ? $_GET[$name] : $defaultValue;
 	}
@@ -244,7 +194,6 @@ class Request extends \yii\base\Request
 	 * @param mixed $defaultValue the default parameter value if the POST parameter does not exist.
 	 * @return mixed the POST parameter value
 	 * @see getParam
-	 * @see getQuery
 	 */
 	public function getPost($name, $defaultValue = null)
 	{
@@ -273,16 +222,6 @@ class Request extends \yii\base\Request
 		return $this->getIsPutRequest() ? $this->getRestParam($name, $defaultValue) : null;
 	}
 
-	/**
-	 * Returns the currently requested URL.
-	 * This is the same as [[requestUri]].
-	 * @return string part of the request URL after the host info.
-	 */
-	public function getUrl()
-	{
-		return $this->getRequestUri();
-	}
-
 	private $_hostInfo;
 
 	/**
@@ -398,7 +337,7 @@ class Request extends \yii\base\Request
 	 * A path info refers to the part that is after the entry script and before the question mark (query string).
 	 * The starting and ending slashes are both removed.
 	 * @return string part of the request URL that is after the entry script and before the question mark.
-	 * Note, the returned path info is decoded.
+	 * Note, the returned path info is already URL-decoded.
 	 * @throws InvalidConfigException if the path info cannot be determined due to unexpected server configuration
 	 */
 	public function getPathInfo()
@@ -410,6 +349,16 @@ class Request extends \yii\base\Request
 	}
 
 	/**
+	 * Sets the path info of the current request.
+	 * This method is mainly provided for testing purpose.
+	 * @param string $value the path info of the current request
+	 */
+	public function setPathInfo($value)
+	{
+		$this->_pathInfo = trim($value, '/');
+	}
+
+	/**
 	 * Resolves the path info part of the currently requested URL.
 	 * A path info refers to the part that is after the entry script and before the question mark (query string).
 	 * The starting and ending slashes are both removed.
@@ -419,13 +368,28 @@ 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);
 		}
 
-		$pathInfo = $this->decodeUrl($pathInfo);
+		$pathInfo = urldecode($pathInfo);
+
+		// try to encode in UTF8 if not so
+		// http://w3.org/International/questions/qa-forms-utf-8.html
+		if (!preg_match('%^(?:
+				[\x09\x0A\x0D\x20-\x7E]              # ASCII
+				| [\xC2-\xDF][\x80-\xBF]             # non-overlong 2-byte
+				| \xE0[\xA0-\xBF][\x80-\xBF]         # excluding overlongs
+				| [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}  # straight 3-byte
+				| \xED[\x80-\x9F][\x80-\xBF]         # excluding surrogates
+				| \xF0[\x90-\xBF][\x80-\xBF]{2}      # planes 1-3
+				| [\xF1-\xF3][\x80-\xBF]{3}          # planes 4-15
+				| \xF4[\x80-\x8F][\x80-\xBF]{2}      # plane 16
+				)*$%xs', $pathInfo)) {
+			$pathInfo = utf8_encode($pathInfo);
+		}
 
 		$scriptUrl = $this->getScriptUrl();
 		$baseUrl = $this->getBaseUrl();
@@ -436,58 +400,48 @@ class Request extends \yii\base\Request
 		} elseif (strpos($_SERVER['PHP_SELF'], $scriptUrl) === 0) {
 			$pathInfo = substr($_SERVER['PHP_SELF'], strlen($scriptUrl));
 		} else {
-			return false;
+			throw new InvalidConfigException('Unable to determine the path info of the current request.');
 		}
 
 		return trim($pathInfo, '/');
 	}
 
 	/**
-	 * Decodes the given URL.
-	 * This method is an improved variant of the native urldecode() function. It will properly encode
-	 * UTF-8 characters which may be returned by urldecode().
-	 * @param string $url encoded URL
-	 * @return string decoded 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 decodeUrl($url)
+	public function getAbsoluteUrl()
 	{
-		$url = urldecode($url);
-
-		// is it UTF-8?
-		// http://w3.org/International/questions/qa-forms-utf-8.html
-		if (preg_match('%^(?:
-				[\x09\x0A\x0D\x20-\x7E]              # ASCII
-				| [\xC2-\xDF][\x80-\xBF]             # non-overlong 2-byte
-				| \xE0[\xA0-\xBF][\x80-\xBF]         # excluding overlongs
-				| [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}  # straight 3-byte
-				| \xED[\x80-\x9F][\x80-\xBF]         # excluding surrogates
-				| \xF0[\x90-\xBF][\x80-\xBF]{2}      # planes 1-3
-				| [\xF1-\xF3][\x80-\xBF]{3}          # planes 4-15
-				| \xF4[\x80-\x8F][\x80-\xBF]{2}      # plane 16
-				)*$%xs', $url)) {
-			return $url;
-		} else {
-			return utf8_encode($url);
-		}
+		return $this->getHostInfo() . $this->getUrl();
 	}
 
-	private $_requestUri;
+	private $_url;
 
 	/**
-	 * Returns the request URI portion 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->_url;
+	}
 
-		return $this->_requestUri;
+	/**
+	 * 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 setUrl($value)
+	{
+		$this->_url = $value;
 	}
 
 	/**
@@ -504,11 +458,7 @@ class Request extends \yii\base\Request
 			$requestUri = $_SERVER['HTTP_X_REWRITE_URL'];
 		} elseif (isset($_SERVER['REQUEST_URI'])) {
 			$requestUri = $_SERVER['REQUEST_URI'];
-			if (!empty($_SERVER['HTTP_HOST'])) {
-				if (strpos($requestUri, $_SERVER['HTTP_HOST']) !== false) {
-					$requestUri = preg_replace('/^\w+:\/\/[^\/]+/', '', $requestUri);
-				}
-			} else {
+			if ($requestUri !== '' && $requestUri[0] !== '/') {
 				$requestUri = preg_replace('/^(http|https):\/\/[^\/]+/i', '', $requestUri);
 			}
 		} elseif (isset($_SERVER['ORIG_PATH_INFO'])) { // IIS 5.0 CGI
@@ -580,7 +530,7 @@ class Request extends \yii\base\Request
 	 * Returns the user IP address.
 	 * @return string user IP address
 	 */
-	public function getUserHostAddress()
+	public function getUserIP()
 	{
 		return isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '127.0.0.1';
 	}
@@ -594,49 +544,6 @@ class Request extends \yii\base\Request
 		return isset($_SERVER['REMOTE_HOST']) ? $_SERVER['REMOTE_HOST'] : null;
 	}
 
-	private $_scriptFile;
-
-	/**
-	 * Returns entry script file path.
-	 * @return string entry script file path (processed w/ realpath())
-	 * @throws InvalidConfigException if the entry script file path cannot be determined automatically.
-	 */
-	public function getScriptFile()
-	{
-		if ($this->_scriptFile === null) {
-			$this->setScriptFile($_SERVER['SCRIPT_FILENAME']);
-		}
-		return $this->_scriptFile;
-	}
-
-	/**
-	 * Sets the entry script file path.
-	 * The entry script file path can normally be determined based on the `SCRIPT_FILENAME` SERVER variable.
-	 * However, in some server configuration, this may not be correct or feasible.
-	 * This setter is provided so that the entry script file path can be manually specified.
-	 * @param string $value the entry script file path
-	 * @throws InvalidConfigException if the provided entry script file path is invalid.
-	 */
-	public function setScriptFile($value)
-	{
-		$this->_scriptFile = realpath($value);
-		if ($this->_scriptFile === false || !is_file($this->_scriptFile)) {
-			throw new InvalidConfigException('Unable to determine the entry script file path.');
-		}
-	}
-
-	/**
-	 * Returns information about the capabilities of user browser.
-	 * @param string $userAgent the user agent to be analyzed. Defaults to null, meaning using the
-	 * current User-Agent HTTP header information.
-	 * @return array user browser capabilities.
-	 * @see http://www.php.net/manual/en/function.get-browser.php
-	 */
-	public function getBrowser($userAgent = null)
-	{
-		return get_browser($userAgent, true);
-	}
-
 	/**
 	 * Returns user browser accept types, null if not present.
 	 * @return string user browser accept types, null if not present
@@ -744,88 +651,31 @@ class Request extends \yii\base\Request
 		return isset($languages[0]) ? $languages[0] : false;
 	}
 
-
 	/**
 	 * Returns the cookie collection.
-	 * The result can be used like an associative array. Adding {@link CHttpCookie} objects
-	 * to the collection will send the cookies to the client; and removing the objects
-	 * from the collection will delete those cookies on the client.
-	 * @return CCookieCollection the cookie collection.
+	 * Through the returned cookie collection, you may access a cookie using the following syntax:
+	 *
+	 * ~~~
+	 * $cookie = $request->cookies['name']
+	 * if ($cookie !== null) {
+	 *     $value = $cookie->value;
+	 * }
+	 *
+	 * // alternatively
+	 * $value = $request->cookies->getValue('name');
+	 * ~~~
+	 *
+	 * @return CookieCollection the cookie collection.
 	 */
 	public function getCookies()
 	{
-		if ($this->_cookies !== null) {
-			return $this->_cookies;
-		} else {
-			return $this->_cookies = new CCookieCollection($this);
-		}
-	}
-
-	private $_csrfToken;
-
-	/**
-	 * Returns the random token used to perform CSRF validation.
-	 * The token will be read from cookie first. If not found, a new token
-	 * will be generated.
-	 * @return string the random token for CSRF validation.
-	 * @see enableCsrfValidation
-	 */
-	public function getCsrfToken()
-	{
-		if ($this->_csrfToken === null) {
-			$cookie = $this->getCookies()->itemAt($this->csrfTokenName);
-			if (!$cookie || ($this->_csrfToken = $cookie->value) == null) {
-				$cookie = $this->createCsrfCookie();
-				$this->_csrfToken = $cookie->value;
-				$this->getCookies()->add($cookie->name, $cookie);
-			}
-		}
-
-		return $this->_csrfToken;
-	}
-
-	/**
-	 * Creates a cookie with a randomly generated CSRF token.
-	 * Initial values specified in {@link csrfCookie} will be applied
-	 * to the generated cookie.
-	 * @return CHttpCookie the generated cookie
-	 * @see enableCsrfValidation
-	 */
-	protected function createCsrfCookie()
-	{
-		$cookie = new CHttpCookie($this->csrfTokenName, sha1(uniqid(mt_rand(), true)));
-		if (is_array($this->csrfCookie)) {
-			foreach ($this->csrfCookie as $name => $value) {
-				$cookie->$name = $value;
-			}
-		}
-		return $cookie;
-	}
-
-	/**
-	 * Performs the CSRF validation.
-	 * This is the event handler responding to {@link CApplication::onBeginRequest}.
-	 * The default implementation will compare the CSRF token obtained
-	 * from a cookie and from a POST field. If they are different, a CSRF attack is detected.
-	 * @param CEvent $event event parameter
-	 * @throws CHttpException if the validation fails
-	 */
-	public function validateCsrfToken($event)
-	{
-		if ($this->getIsPostRequest()) {
-			// only validate POST requests
-			$cookies = $this->getCookies();
-			if ($cookies->contains($this->csrfTokenName) && isset($_POST[$this->csrfTokenName])) {
-				$tokenFromCookie = $cookies->itemAt($this->csrfTokenName)->value;
-				$tokenFromPost = $_POST[$this->csrfTokenName];
-				$valid = $tokenFromCookie === $tokenFromPost;
-			} else {
-				$valid = false;
-			}
-			if (!$valid) {
-				throw new CHttpException(400, Yii::t('yii', 'The CSRF token could not be verified.'));
-			}
+		if ($this->_cookies === null) {
+			$this->_cookies = new CookieCollection(array(
+				'enableValidation' => $this->enableCookieValidation,
+				'validationKey' => $this->cookieValidationKey,
+			));
 		}
+		return $this->_cookies;
 	}
 }
 
diff --git a/framework/web/Response.php b/framework/web/Response.php
index 73a28e3..d23c5b9 100644
--- a/framework/web/Response.php
+++ b/framework/web/Response.php
@@ -1,15 +1,14 @@
 <?php
 /**
- * Response class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
 namespace yii\web;
 
-use yii\util\FileHelper;
+use Yii;
+use yii\helpers\FileHelper;
 
 /**
  * @author Qiang Xue <qiang.xue@gmail.com>
@@ -82,6 +81,11 @@ class Response extends \yii\base\Response
 	 * If this option is disabled by the web server, when this method is called a download configuration dialog
 	 * will open but the downloaded file will have 0 bytes.
 	 *
+	 * <b>Known issues</b>:
+	 * There is a Bug with Internet Explorer 6, 7 and 8 when X-SENDFILE is used over an SSL connection, it will show
+	 * an error message like this: "Internet Explorer was not able to open this Internet site. The requested site is either unavailable or cannot be found.".
+	 * You can work around this problem by removing the <code>Pragma</code>-header.
+	 *
 	 * <b>Example</b>:
 	 * <pre>
 	 * <?php
@@ -102,63 +106,79 @@ class Response extends \yii\base\Response
 	 * <li>forceDownload: specifies whether the file will be downloaded or shown inline, defaults to true. (Since version 1.1.9.)</li>
 	 * <li>addHeaders: an array of additional http headers in header-value pairs (available since version 1.1.10)</li>
 	 * </ul>
-	 * @todo
 	 */
-	public function xSendFile($filePath, $options = array())
+	public function xSendFile($filePath, $options=array())
 	{
-		if (!isset($options['forceDownload']) || $options['forceDownload']) {
-			$disposition = 'attachment';
-		} else {
-			$disposition = 'inline';
-		}
+		if(!isset($options['forceDownload']) || $options['forceDownload'])
+			$disposition='attachment';
+		else
+			$disposition='inline';
 
-		if (!isset($options['saveName'])) {
-			$options['saveName'] = basename($filePath);
-		}
+		if(!isset($options['saveName']))
+			$options['saveName']=basename($filePath);
 
-		if (!isset($options['mimeType'])) {
-			if (($options['mimeType'] = CFileHelper::getMimeTypeByExtension($filePath)) === null) {
-				$options['mimeType'] = 'text/plain';
-			}
+		if(!isset($options['mimeType']))
+		{
+			if(($options['mimeType']=CFileHelper::getMimeTypeByExtension($filePath))===null)
+				$options['mimeType']='text/plain';
 		}
 
-		if (!isset($options['xHeader'])) {
-			$options['xHeader'] = 'X-Sendfile';
-		}
+		if(!isset($options['xHeader']))
+			$options['xHeader']='X-Sendfile';
 
-		if ($options['mimeType'] !== null) {
-			header('Content-type: ' . $options['mimeType']);
+		if($options['mimeType'] !== null)
+			header('Content-type: '.$options['mimeType']);
+		header('Content-Disposition: '.$disposition.'; filename="'.$options['saveName'].'"');
+		if(isset($options['addHeaders']))
+		{
+			foreach($options['addHeaders'] as $header=>$value)
+				header($header.': '.$value);
 		}
-		header('Content-Disposition: ' . $disposition . '; filename="' . $options['saveName'] . '"');
-		if (isset($options['addHeaders'])) {
-			foreach ($options['addHeaders'] as $header => $value) {
-				header($header . ': ' . $value);
-			}
-		}
-		header(trim($options['xHeader']) . ': ' . $filePath);
+		header(trim($options['xHeader']).': '.$filePath);
 
-		if (!isset($options['terminate']) || $options['terminate']) {
+		if(!isset($options['terminate']) || $options['terminate'])
 			Yii::app()->end();
-		}
 	}
 
-
 	/**
 	 * Redirects the browser to the specified URL.
-	 * @param string $url URL to be redirected to. If the URL is a relative one, the base URL of
-	 * the application will be inserted at the beginning.
+	 * @param string $url URL to be redirected to. Note that when URL is not
+	 * absolute (not starting with "/") it will be relative to current request URL.
 	 * @param boolean $terminate whether to terminate the current application
 	 * @param integer $statusCode the HTTP status code. Defaults to 302. See {@link http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html}
 	 * for details about HTTP status code.
 	 */
-	public function redirect($url, $terminate = true, $statusCode = 302)
+	public function redirect($url,$terminate=true,$statusCode=302)
 	{
-		if (strpos($url, '/') === 0) {
-			$url = $this->getHostInfo() . $url;
-		}
-		header('Location: ' . $url, true, $statusCode);
-		if ($terminate) {
+		if(strpos($url,'/')===0 && strpos($url,'//')!==0)
+			$url=$this->getHostInfo().$url;
+		header('Location: '.$url, true, $statusCode);
+		if($terminate)
 			Yii::app()->end();
-		}
+	}
+
+
+	/**
+	 * Returns the cookie collection.
+	 * Through the returned cookie collection, you add or remove cookies as follows,
+	 *
+	 * ~~~
+	 * // add a cookie
+	 * $response->cookies->add(new Cookie(array(
+	 *     'name' => $name,
+	 *     'value' => $value,
+	 * ));
+	 *
+	 * // remove a cookie
+	 * $response->cookies->remove('name');
+	 * // alternatively
+	 * unset($response->cookies['name']);
+	 * ~~~
+	 *
+	 * @return CookieCollection the cookie collection.
+	 */
+	public function getCookies()
+	{
+		return Yii::$app->getRequest()->getCookies();
 	}
 }
diff --git a/framework/web/Session.php b/framework/web/Session.php
index 4544fc0..c289db2 100644
--- a/framework/web/Session.php
+++ b/framework/web/Session.php
@@ -1,82 +1,63 @@
 <?php
 /**
- * CHttpSession class file.
- *
- * @author Qiang Xue <qiang.xue@gmail.com>
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008-2011 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
+namespace yii\web;
+
+use Yii;
+use yii\base\Component;
+use yii\base\InvalidParamException;
+
 /**
- * CHttpSession provides session-level data management and the related configurations.
- *
- * To start the session, call {@link open()}; To complete and send out session data, call {@link close()};
- * To destroy the session, call {@link destroy()}.
+ * Session provides session data management and the related configurations.
  *
- * If {@link autoStart} is set true, the session will be started automatically
- * when the application component is initialized by the application.
+ * Session is a Web application component that can be accessed via `Yii::$app->session`.
+
+ * To start the session, call [[open()]]; To complete and send out session data, call [[close()]];
+ * To destroy the session, call [[destroy()]].
  *
- * CHttpSession can be used like an array to set and get session data. For example,
- * <pre>
- *   $session=new CHttpSession;
- *   $session->open();
- *   $value1=$session['name1'];  // get session variable 'name1'
- *   $value2=$session['name2'];  // get session variable 'name2'
- *   foreach($session as $name=>$value) // traverse all session variables
- *   $session['name3']=$value3;  // set session variable 'name3'
- * </pre>
+ * By default, [[autoStart]] is true which means the session will be started automatically
+ * when the session component is accessed the first time.
  *
- * The following configurations are available for session:
- * <ul>
- * <li>{@link setSessionID sessionID};</li>
- * <li>{@link setSessionName sessionName};</li>
- * <li>{@link autoStart};</li>
- * <li>{@link setSavePath savePath};</li>
- * <li>{@link setCookieParams cookieParams};</li>
- * <li>{@link setGCProbability gcProbability};</li>
- * <li>{@link setCookieMode cookieMode};</li>
- * <li>{@link setUseTransparentSessionID useTransparentSessionID};</li>
- * <li>{@link setTimeout timeout}.</li>
- * </ul>
- * See the corresponding setter and getter documentation for more information.
- * Note, these properties must be set before the session is started.
+ * Session can be used like an array to set and get session data. For example,
  *
- * CHttpSession can be extended to support customized session storage.
- * Override {@link openSession}, {@link closeSession}, {@link readSession},
- * {@link writeSession}, {@link destroySession} and {@link gcSession}
- * and set {@link useCustomStorage} to true.
- * Then, the session data will be stored and retrieved using the above methods.
+ * ~~~
+ * $session = new Session;
+ * $session->open();
+ * $value1 = $session['name1'];  // get session variable 'name1'
+ * $value2 = $session['name2'];  // get session variable 'name2'
+ * foreach ($session as $name => $value) // traverse all session variables
+ * $session['name3'] = $value3;  // set session variable 'name3'
+ * ~~~
  *
- * CHttpSession is a Web application component that can be accessed via
- * {@link CWebApplication::getSession()}.
+ * Session can be extended to support customized session storage.
+ * To do so, override [[useCustomStorage()]] so that it returns true, and
+ * override these methods with the actual logic about using custom storage:
+ * [[openSession()]], [[closeSession()]], [[readSession()]], [[writeSession()]],
+ * [[destroySession()]] and [[gcSession()]].
  *
- * @property boolean $useCustomStorage Whether to use custom storage.
- * @property boolean $isStarted Whether the session has started.
- * @property string $sessionID The current session ID.
- * @property string $sessionName The current session name.
- * @property string $savePath The current session save path, defaults to '/tmp'.
- * @property array $cookieParams The session cookie parameters.
- * @property string $cookieMode How to use cookie to store session ID. Defaults to 'Allow'.
- * @property integer $gCProbability The probability (percentage) that the gc (garbage collection) process is started on every session initialization, defaults to 1 meaning 1% chance.
- * @property boolean $useTransparentSessionID Whether transparent sid support is enabled or not, defaults to false.
- * @property integer $timeout The number of seconds after which data will be seen as 'garbage' and cleaned up, defaults to 1440 seconds.
- * @property CHttpSessionIterator $iterator An iterator for traversing the session variables.
- * @property integer $count The number of session variables.
- * @property array $keys The list of session variable names.
+ * Session also supports a special type of session data, called *flash messages*.
+ * A flash message is available only in the current request and the next request.
+ * After that, it will be deleted automatically. Flash messages are particularly
+ * useful for displaying confirmation messages. To use flash messages, simply
+ * call methods such as [[setFlash()]], [[getFlash()]].
  *
  * @author Qiang Xue <qiang.xue@gmail.com>
- * @version $Id$
- * @package system.web
- * @since 1.0
+ * @since 2.0
  */
-class CHttpSession extends CApplicationComponent implements IteratorAggregate,ArrayAccess,Countable
+class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Countable
 {
 	/**
-	 * @var boolean whether the session should be automatically started when the session application component is initialized, defaults to true.
+	 * @var boolean whether the session should be automatically started when the session component is initialized.
 	 */
-	public $autoStart=true;
-
+	public $autoStart = true;
+	/**
+	 * @var string the name of the session variable that stores the flash message data.
+	 */
+	public $flashVar = '__flash';
 
 	/**
 	 * Initializes the application component.
@@ -85,18 +66,17 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
 	public function init()
 	{
 		parent::init();
-		if($this->autoStart)
+		if ($this->autoStart) {
 			$this->open();
-		register_shutdown_function(array($this,'close'));
+		}
+		register_shutdown_function(array($this, 'close'));
 	}
 
 	/**
 	 * Returns a value indicating whether to use custom session storage.
-	 * This method should be overriden to return true if custom session storage handler should be used.
-	 * If returning true, make sure the methods {@link openSession}, {@link closeSession}, {@link readSession},
-	 * {@link writeSession}, {@link destroySession}, and {@link gcSession} are overridden in child
-	 * class, because they will be used as the callback handlers.
-	 * The default implementation always return false.
+	 * This method should be overridden to return true by child classes that implement custom session storage.
+	 * To implement custom session storage, override these methods: [[openSession()]], [[closeSession()]],
+	 * [[readSession()]], [[writeSession()]], [[destroySession()]] and [[gcSession()]].
 	 * @return boolean whether to use custom storage.
 	 */
 	public function getUseCustomStorage()
@@ -104,25 +84,44 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
 		return false;
 	}
 
+	private $_opened = false;
+
 	/**
-	 * Starts the session if it has not started yet.
+	 * Starts the session.
 	 */
 	public function open()
 	{
-		if($this->getUseCustomStorage())
-			@session_set_save_handler(array($this,'openSession'),array($this,'closeSession'),array($this,'readSession'),array($this,'writeSession'),array($this,'destroySession'),array($this,'gcSession'));
-
-		@session_start();
-		if(YII_DEBUG && session_id()=='')
-		{
-			$message=Yii::t('yii','Failed to start session.');
-			if(function_exists('error_get_last'))
-			{
-				$error=error_get_last();
-				if(isset($error['message']))
-					$message=$error['message'];
+		// this is available in PHP 5.4.0+
+		if (function_exists('session_status')) {
+			if (session_status() == PHP_SESSION_ACTIVE) {
+				$this->_opened = true;
+				return;
+			}
+		}
+
+		if (!$this->_opened) {
+			if ($this->getUseCustomStorage()) {
+				@session_set_save_handler(
+					array($this, 'openSession'),
+					array($this, 'closeSession'),
+					array($this, 'readSession'),
+					array($this, 'writeSession'),
+					array($this, 'destroySession'),
+					array($this, 'gcSession')
+				);
+			}
+
+			@session_start();
+
+			if (session_id() == '') {
+				$this->_opened = false;
+				$error = error_get_last();
+				$message = isset($error['message']) ? $error['message'] : 'Failed to start session.';
+				Yii::error($message, __CLASS__);
+			} else {
+				$this->_opened = true;
+				$this->updateFlashCounters();
 			}
-			Yii::log($message, CLogger::LEVEL_WARNING, 'system.web.CHttpSession');
 		}
 	}
 
@@ -131,8 +130,10 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
 	 */
 	public function close()
 	{
-		if(session_id()!=='')
+		$this->_opened = false;
+		if (session_id() !== '') {
 			@session_write_close();
+		}
 	}
 
 	/**
@@ -140,8 +141,7 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
 	 */
 	public function destroy()
 	{
-		if(session_id()!=='')
-		{
+		if (session_id() !== '') {
 			@session_unset();
 			@session_destroy();
 		}
@@ -150,15 +150,21 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
 	/**
 	 * @return boolean whether the session has started
 	 */
-	public function getIsStarted()
+	public function getIsActive()
 	{
-		return session_id()!=='';
+		if (function_exists('session_status')) {
+			// available in PHP 5.4.0+
+			return session_status() == PHP_SESSION_ACTIVE;
+		} else {
+			// this is not very reliable
+			return $this->_opened && session_id() !== '';
+		}
 	}
 
 	/**
 	 * @return string the current session ID
 	 */
-	public function getSessionID()
+	public function getId()
 	{
 		return session_id();
 	}
@@ -166,18 +172,17 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
 	/**
 	 * @param string $value the session ID for the current session
 	 */
-	public function setSessionID($value)
+	public function setId($value)
 	{
 		session_id($value);
 	}
 
 	/**
-	 * Updates the current session id with a newly generated one .
-	 * Please refer to {@link http://php.net/session_regenerate_id} for more details.
+	 * Updates the current session ID with a newly generated one .
+	 * Please refer to [[http://php.net/session_regenerate_id]] for more details.
 	 * @param boolean $deleteOldSession Whether to delete the old associated session file or not.
-	 * @since 1.1.8
 	 */
-	public function regenerateID($deleteOldSession=false)
+	public function regenerateID($deleteOldSession = false)
 	{
 		session_regenerate_id($deleteOldSession);
 	}
@@ -185,15 +190,16 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
 	/**
 	 * @return string the current session name
 	 */
-	public function getSessionName()
+	public function getName()
 	{
 		return session_name();
 	}
 
 	/**
-	 * @param string $value the session name for the current session, must be an alphanumeric string, defaults to PHPSESSID
+	 * @param string $value the session name for the current session, must be an alphanumeric string.
+	 * It defaults to "PHPSESSID".
 	 */
-	public function setSessionName($value)
+	public function setName($value)
 	{
 		session_name($value);
 	}
@@ -207,16 +213,17 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
 	}
 
 	/**
-	 * @param string $value the current session save path
-	 * @throws CException if the path is not a valid directory
+	 * @param string $value the current session save path. This can be either a directory name or a path alias.
+	 * @throws InvalidParamException if the path is not a valid directory
 	 */
 	public function setSavePath($value)
 	{
-		if(is_dir($value))
-			session_save_path($value);
-		else
-			throw new CException(Yii::t('yii','CHttpSession.savePath "{path}" is not a valid directory.',
-				array('{path}'=>$value)));
+		$path = Yii::getAlias($value);
+		if (is_dir($path)) {
+			session_save_path($path);
+		} else {
+			throw new InvalidParamException("Session save path is not a valid directory: $value");
+		}
 	}
 
 	/**
@@ -232,80 +239,83 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
 	 * Sets the session cookie parameters.
 	 * The effect of this method only lasts for the duration of the script.
 	 * Call this method before the session starts.
-	 * @param array $value cookie parameters, valid keys include: lifetime, path, domain, secure.
+	 * @param array $value cookie parameters, valid keys include: lifetime, path, domain, secure and httponly.
+	 * @throws InvalidParamException if the parameters are incomplete.
 	 * @see http://us2.php.net/manual/en/function.session-set-cookie-params.php
 	 */
 	public function setCookieParams($value)
 	{
-		$data=session_get_cookie_params();
+		$data = session_get_cookie_params();
 		extract($data);
 		extract($value);
-		if(isset($httponly))
-			session_set_cookie_params($lifetime,$path,$domain,$secure,$httponly);
-		else
-			session_set_cookie_params($lifetime,$path,$domain,$secure);
+		if (isset($lifetime, $path, $domain, $secure, $httponly)) {
+			session_set_cookie_params($lifetime, $path, $domain, $secure, $httponly);
+		} else {
+			throw new InvalidParamException('Please make sure these parameters are provided: lifetime, path, domain, secure and httponly.');
+		}
 	}
 
 	/**
-	 * @return string how to use cookie to store session ID. Defaults to 'Allow'.
+	 * Returns the value indicating whether cookies should be used to store session IDs.
+	 * @return boolean|null the value indicating whether cookies should be used to store session IDs.
+	 * @see setUseCookies()
 	 */
-	public function getCookieMode()
+	public function getUseCookies()
 	{
-		if(ini_get('session.use_cookies')==='0')
-			return 'none';
-		else if(ini_get('session.use_only_cookies')==='0')
-			return 'allow';
-		else
-			return 'only';
+		if (ini_get('session.use_cookies') === '0') {
+			return false;
+		} elseif (ini_get('session.use_only_cookies') === '1') {
+			return true;
+		} else {
+			return null;
+		}
 	}
 
 	/**
-	 * @param string $value how to use cookie to store session ID. Valid values include 'none', 'allow' and 'only'.
+	 * Sets the value indicating whether cookies should be used to store session IDs.
+	 * Three states are possible:
+	 *
+	 * - true: cookies and only cookies will be used to store session IDs.
+	 * - false: cookies will not be used to store session IDs.
+	 * - null: if possible, cookies will be used to store session IDs; if not, other mechanisms will be used (e.g. GET parameter)
+	 *
+	 * @param boolean|null $value the value indicating whether cookies should be used to store session IDs.
 	 */
-	public function setCookieMode($value)
+	public function setUseCookies($value)
 	{
-		if($value==='none')
-		{
-			ini_set('session.use_cookies','0');
-			ini_set('session.use_only_cookies','0');
-		}
-		else if($value==='allow')
-		{
-			ini_set('session.use_cookies','1');
-			ini_set('session.use_only_cookies','0');
+		if ($value === false) {
+			ini_set('session.use_cookies', '0');
+			ini_set('session.use_only_cookies', '0');
+		} elseif ($value === true) {
+			ini_set('session.use_cookies', '1');
+			ini_set('session.use_only_cookies', '1');
+		} else {
+			ini_set('session.use_cookies', '1');
+			ini_set('session.use_only_cookies', '0');
 		}
-		else if($value==='only')
-		{
-			ini_set('session.use_cookies','1');
-			ini_set('session.use_only_cookies','1');
-		}
-		else
-			throw new CException(Yii::t('yii','CHttpSession.cookieMode can only be "none", "allow" or "only".'));
 	}
 
 	/**
-	 * @return integer the probability (percentage) that the gc (garbage collection) process is started on every session initialization, defaults to 1 meaning 1% chance.
+	 * @return float the probability (percentage) that the GC (garbage collection) process is started on every session initialization, defaults to 1 meaning 1% chance.
 	 */
 	public function getGCProbability()
 	{
-		return (int)ini_get('session.gc_probability');
+		return (float)(ini_get('session.gc_probability') / ini_get('session.gc_divisor') * 100);
 	}
 
 	/**
-	 * @param integer $value the probability (percentage) that the gc (garbage collection) process is started on every session initialization.
-	 * @throws CException if the value is beyond [0,100]
+	 * @param float $value the probability (percentage) that the GC (garbage collection) process is started on every session initialization.
+	 * @throws InvalidParamException if the value is not between 0 and 100.
 	 */
 	public function setGCProbability($value)
 	{
-		$value=(int)$value;
-		if($value>=0 && $value<=100)
-		{
-			ini_set('session.gc_probability',$value);
-			ini_set('session.gc_divisor','100');
+		if ($value >= 0 && $value <= 100) {
+			// percent * 21474837 / 2147483647 ≈ percent * 0.01
+			ini_set('session.gc_probability', floor($value * 21474836.47));
+			ini_set('session.gc_divisor', 2147483647);
+		} else {
+			throw new InvalidParamException('GCProbability must be a value between 0 and 100.');
 		}
-		else
-			throw new CException(Yii::t('yii','CHttpSession.gcProbability "{value}" is invalid. It must be an integer between 0 and 100.',
-				array('{value}'=>$value)));
 	}
 
 	/**
@@ -313,7 +323,7 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
 	 */
 	public function getUseTransparentSessionID()
 	{
-		return ini_get('session.use_trans_sid')==1;
+		return ini_get('session.use_trans_sid') == 1;
 	}
 
 	/**
@@ -321,11 +331,12 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
 	 */
 	public function setUseTransparentSessionID($value)
 	{
-		ini_set('session.use_trans_sid',$value?'1':'0');
+		ini_set('session.use_trans_sid', $value ? '1' : '0');
 	}
 
 	/**
-	 * @return integer the number of seconds after which data will be seen as 'garbage' and cleaned up, defaults to 1440 seconds.
+	 * @return integer the number of seconds after which data will be seen as 'garbage' and cleaned up.
+	 * The default value is 1440 seconds (or the value of "session.gc_maxlifetime" set in php.ini).
 	 */
 	public function getTimeout()
 	{
@@ -337,25 +348,25 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
 	 */
 	public function setTimeout($value)
 	{
-		ini_set('session.gc_maxlifetime',$value);
+		ini_set('session.gc_maxlifetime', $value);
 	}
 
 	/**
 	 * Session open handler.
-	 * This method should be overridden if {@link useCustomStorage} is set true.
+	 * This method should be overridden if [[useCustomStorage()]] returns true.
 	 * Do not call this method directly.
 	 * @param string $savePath session save path
 	 * @param string $sessionName session name
 	 * @return boolean whether session is opened successfully
 	 */
-	public function openSession($savePath,$sessionName)
+	public function openSession($savePath, $sessionName)
 	{
 		return true;
 	}
 
 	/**
 	 * Session close handler.
-	 * This method should be overridden if {@link useCustomStorage} is set true.
+	 * This method should be overridden if [[useCustomStorage()]] returns true.
 	 * Do not call this method directly.
 	 * @return boolean whether session is closed successfully
 	 */
@@ -366,7 +377,7 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
 
 	/**
 	 * Session read handler.
-	 * This method should be overridden if {@link useCustomStorage} is set true.
+	 * This method should be overridden if [[useCustomStorage()]] returns true.
 	 * Do not call this method directly.
 	 * @param string $id session ID
 	 * @return string the session data
@@ -378,20 +389,20 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
 
 	/**
 	 * Session write handler.
-	 * This method should be overridden if {@link useCustomStorage} is set true.
+	 * This method should be overridden if [[useCustomStorage()]] returns true.
 	 * Do not call this method directly.
 	 * @param string $id session ID
 	 * @param string $data session data
 	 * @return boolean whether session write is successful
 	 */
-	public function writeSession($id,$data)
+	public function writeSession($id, $data)
 	{
 		return true;
 	}
 
 	/**
 	 * Session destroy handler.
-	 * This method should be overridden if {@link useCustomStorage} is set true.
+	 * This method should be overridden if [[useCustomStorage()]] returns true.
 	 * Do not call this method directly.
 	 * @param string $id session ID
 	 * @return boolean whether session is destroyed successfully
@@ -403,7 +414,7 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
 
 	/**
 	 * Session GC (garbage collection) handler.
-	 * This method should be overridden if {@link useCustomStorage} is set true.
+	 * This method should be overridden if [[useCustomStorage()]] returns true.
 	 * Do not call this method directly.
 	 * @param integer $maxLifetime the number of seconds after which data will be seen as 'garbage' and cleaned up.
 	 * @return boolean whether session is GCed successfully
@@ -413,16 +424,14 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
 		return true;
 	}
 
-	//------ The following methods enable CHttpSession to be CMap-like -----
-
 	/**
 	 * Returns an iterator for traversing the session variables.
 	 * This method is required by the interface IteratorAggregate.
-	 * @return CHttpSessionIterator an iterator for traversing the session variables.
+	 * @return SessionIterator an iterator for traversing the session variables.
 	 */
 	public function getIterator()
 	{
-		return new CHttpSessionIterator;
+		return new SessionIterator;
 	}
 
 	/**
@@ -445,80 +454,59 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
 	}
 
 	/**
-	 * @return array the list of session variable names
-	 */
-	public function getKeys()
-	{
-		return array_keys($_SESSION);
-	}
-
-	/**
 	 * Returns the session variable value with the session variable name.
-	 * This method is very similar to {@link itemAt} and {@link offsetGet},
-	 * except that it will return $defaultValue if the session variable does not exist.
-	 * @param mixed $key the session variable name
+	 * If the session variable does not exist, the `$defaultValue` will be returned.
+	 * @param string $key the session variable name
 	 * @param mixed $defaultValue the default value to be returned when the session variable does not exist.
 	 * @return mixed the session variable value, or $defaultValue if the session variable does not exist.
-	 * @since 1.1.2
 	 */
-	public function get($key,$defaultValue=null)
+	public function get($key, $defaultValue = null)
 	{
 		return isset($_SESSION[$key]) ? $_SESSION[$key] : $defaultValue;
 	}
 
 	/**
-	 * Returns the session variable value with the session variable name.
-	 * This method is exactly the same as {@link offsetGet}.
-	 * @param mixed $key the session variable name
-	 * @return mixed the session variable value, null if no such variable exists
-	 */
-	public function itemAt($key)
-	{
-		return isset($_SESSION[$key]) ? $_SESSION[$key] : null;
-	}
-
-	/**
 	 * Adds a session variable.
-	 * Note, if the specified name already exists, the old value will be removed first.
-	 * @param mixed $key session variable name
+	 * If the specified name already exists, the old value will be overwritten.
+	 * @param string $key session variable name
 	 * @param mixed $value session variable value
 	 */
-	public function add($key,$value)
+	public function set($key, $value)
 	{
-		$_SESSION[$key]=$value;
+		$_SESSION[$key] = $value;
 	}
 
 	/**
 	 * Removes a session variable.
-	 * @param mixed $key the name of the session variable to be removed
+	 * @param string $key the name of the session variable to be removed
 	 * @return mixed the removed value, null if no such session variable.
 	 */
 	public function remove($key)
 	{
-		if(isset($_SESSION[$key]))
-		{
-			$value=$_SESSION[$key];
+		if (isset($_SESSION[$key])) {
+			$value = $_SESSION[$key];
 			unset($_SESSION[$key]);
 			return $value;
-		}
-		else
+		} else {
 			return null;
+		}
 	}
 
 	/**
 	 * Removes all session variables
 	 */
-	public function clear()
+	public function removeAll()
 	{
-		foreach(array_keys($_SESSION) as $key)
+		foreach (array_keys($_SESSION) as $key) {
 			unset($_SESSION[$key]);
+		}
 	}
 
 	/**
 	 * @param mixed $key session variable name
 	 * @return boolean whether there is the named session variable
 	 */
-	public function contains($key)
+	public function has($key)
 	{
 		return isset($_SESSION[$key]);
 	}
@@ -532,6 +520,115 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
 	}
 
 	/**
+	 * Updates the counters for flash messages and removes outdated flash messages.
+	 * This method should only be called once in [[init()]].
+	 */
+	protected function updateFlashCounters()
+	{
+		$counters = $this->get($this->flashVar, array());
+		if (is_array($counters)) {
+			foreach ($counters as $key => $count) {
+				if ($count) {
+					unset($counters[$key], $_SESSION[$key]);
+				} else {
+					$counters[$key]++;
+				}
+			}
+			$_SESSION[$this->flashVar] = $counters;
+		} else {
+			// fix the unexpected problem that flashVar doesn't return an array
+			unset($_SESSION[$this->flashVar]);
+		}
+	}
+
+	/**
+	 * Returns a flash message.
+	 * A flash message is available only in the current request and the next request.
+	 * @param string $key the key identifying the flash message
+	 * @param mixed $defaultValue value to be returned if the flash message does not exist.
+	 * @return mixed the flash message
+	 */
+	public function getFlash($key, $defaultValue = null)
+	{
+		$counters = $this->get($this->flashVar, array());
+		return isset($counters[$key]) ? $this->get($key, $defaultValue) : $defaultValue;
+	}
+
+	/**
+	 * Returns all flash messages.
+	 * @return array flash messages (key => message).
+	 */
+	public function getAllFlashes()
+	{
+		$counters = $this->get($this->flashVar, array());
+		$flashes = array();
+		foreach (array_keys($counters) as $key) {
+			if (isset($_SESSION[$key])) {
+				$flashes[$key] = $_SESSION[$key];
+			}
+		}
+		return $flashes;
+	}
+
+	/**
+	 * Stores a flash message.
+	 * A flash message is available only in the current request and the next request.
+	 * @param string $key the key identifying the flash message. Note that flash messages
+	 * and normal session variables share the same name space. If you have a normal
+	 * session variable using the same name, its value will be overwritten by this method.
+	 * @param mixed $value flash message
+	 */
+	public function setFlash($key, $value)
+	{
+		$counters = $this->get($this->flashVar, array());
+		$counters[$key] = 0;
+		$_SESSION[$key] = $value;
+		$_SESSION[$this->flashVar] = $counters;
+	}
+
+	/**
+	 * Removes a flash message.
+	 * Note that flash messages will be automatically removed after the next request.
+	 * @param string $key the key identifying the flash message. Note that flash messages
+	 * and normal session variables share the same name space.  If you have a normal
+	 * session variable using the same name, it will be removed by this method.
+	 * @return mixed the removed flash message. Null if the flash message does not exist.
+	 */
+	public function removeFlash($key)
+	{
+		$counters = $this->get($this->flashVar, array());
+		$value = isset($_SESSION[$key], $counters[$key]) ? $_SESSION[$key] : null;
+		unset($counters[$key], $_SESSION[$key]);
+		$_SESSION[$this->flashVar] = $counters;
+		return $value;
+	}
+
+	/**
+	 * Removes all flash messages.
+	 * Note that flash messages and normal session variables share the same name space.
+	 * If you have a normal session variable using the same name, it will be removed
+	 * by this method.
+	 */
+	public function removeAllFlashes()
+	{
+		$counters = $this->get($this->flashVar, array());
+		foreach (array_keys($counters) as $key) {
+			unset($_SESSION[$key]);
+		}
+		unset($_SESSION[$this->flashVar]);
+	}
+
+	/**
+	 * Returns a value indicating whether there is a flash message associated with the specified key.
+	 * @param string $key key identifying the flash message
+	 * @return boolean whether the specified flash message exists
+	 */
+	public function hasFlash($key)
+	{
+		return $this->getFlash($key) !== null;
+	}
+
+	/**
 	 * This method is required by the interface ArrayAccess.
 	 * @param mixed $offset the offset to check on
 	 * @return boolean
@@ -556,9 +653,9 @@ class CHttpSession extends CApplicationComponent implements IteratorAggregate,Ar
 	 * @param integer $offset the offset to set element
 	 * @param mixed $item the element value
 	 */
-	public function offsetSet($offset,$item)
+	public function offsetSet($offset, $item)
 	{
-		$_SESSION[$offset]=$item;
+		$_SESSION[$offset] = $item;
 	}
 
 	/**
diff --git a/framework/web/SessionIterator.php b/framework/web/SessionIterator.php
new file mode 100644
index 0000000..c960dd4
--- /dev/null
+++ b/framework/web/SessionIterator.php
@@ -0,0 +1,84 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+/**
+ * SessionIterator implements an iterator for traversing session variables managed by [[Session]].
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class SessionIterator implements \Iterator
+{
+	/**
+	 * @var array list of keys in the map
+	 */
+	private $_keys;
+	/**
+	 * @var mixed current key
+	 */
+	private $_key;
+
+	/**
+	 * Constructor.
+	 */
+	public function __construct()
+	{
+		$this->_keys = array_keys($_SESSION);
+	}
+
+	/**
+	 * Rewinds internal array pointer.
+	 * This method is required by the interface Iterator.
+	 */
+	public function rewind()
+	{
+		$this->_key = reset($this->_keys);
+	}
+
+	/**
+	 * Returns the key of the current array element.
+	 * This method is required by the interface Iterator.
+	 * @return mixed the key of the current array element
+	 */
+	public function key()
+	{
+		return $this->_key;
+	}
+
+	/**
+	 * Returns the current array element.
+	 * This method is required by the interface Iterator.
+	 * @return mixed the current array element
+	 */
+	public function current()
+	{
+		return isset($_SESSION[$this->_key]) ? $_SESSION[$this->_key] : null;
+	}
+
+	/**
+	 * Moves the internal pointer to the next array element.
+	 * This method is required by the interface Iterator.
+	 */
+	public function next()
+	{
+		do {
+			$this->_key = next($this->_keys);
+		} while (!isset($_SESSION[$this->_key]) && $this->_key !== false);
+	}
+
+	/**
+	 * Returns whether there is an element at current position.
+	 * This method is required by the interface Iterator.
+	 * @return boolean
+	 */
+	public function valid()
+	{
+		return $this->_key !== false;
+	}
+}
diff --git a/framework/web/Sort.php b/framework/web/Sort.php
index 12e16a5..7cfeeca 100644
--- a/framework/web/Sort.php
+++ b/framework/web/Sort.php
@@ -1,470 +1,336 @@
 <?php
 /**
- * CSort class file.
- *
- * @author Qiang Xue <qiang.xue@gmail.com>
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008-2011 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
+namespace yii\web;
+
+use Yii;
+use yii\helpers\Html;
+
 /**
- * CSort represents information relevant to sorting.
+ * Sort represents information relevant to sorting.
  *
  * When data needs to be sorted according to one or several attributes,
- * we can use CSort to represent the sorting information and generate
+ * we can use Sort to represent the sorting information and generate
  * appropriate hyperlinks that can lead to sort actions.
  *
- * CSort is designed to be used together with {@link CActiveRecord}.
- * When creating a CSort instance, you need to specify {@link modelClass}.
- * You can use CSort to generate hyperlinks by calling {@link link}.
- * You can also use CSort to modify a {@link CDbCriteria} instance by calling {@link applyOrder} so that
- * it can cause the query results to be sorted according to the specified
- * attributes.
+ * A typical usage example is as follows,
+ *
+ * ~~~
+ * function actionIndex()
+ * {
+ *     $sort = new Sort(array(
+ *         'attributes' => array(
+ *             'age',
+ *             'name' => array(
+ *                 'asc' => array('last_name', 'first_name'),
+ *                 'desc' => array('last_name' => true, 'first_name' => true),
+ *             ),
+ *         ),
+ *     ));
+ *
+ *     $models = Article::find()
+ *         ->where(array('status' => 1))
+ *         ->orderBy($sort->orders)
+ *         ->all();
  *
- * In order to prevent SQL injection attacks, CSort ensures that only valid model attributes
- * can be sorted. This is determined based on {@link modelClass} and {@link attributes}.
- * When {@link attributes} is not set, all attributes belonging to {@link modelClass}
- * can be sorted. When {@link attributes} is set, only those attributes declared in the property
- * can be sorted.
+ *     $this->render('index', array(
+ *          'models' => $models,
+ *          'sort' => $sort,
+ *     ));
+ * }
+ * ~~~
  *
- * By configuring {@link attributes}, one can perform more complex sorts that may
- * consist of things like compound attributes (e.g. sort based on the combination of
- * first name and last name of users).
+ * View:
  *
- * The property {@link attributes} should be an array of key-value pairs, where the keys
- * represent the attribute names, while the values represent the virtual attribute definitions.
- * For more details, please check the documentation about {@link attributes}.
+ * ~~~
+ * // display links leading to sort actions
+ * echo $sort->link('name', 'Name') . ' | ' . $sort->link('age', 'Age');
  *
- * @property string $orderBy The order-by columns represented by this sort object.
- * This can be put in the ORDER BY clause of a SQL statement.
- * @property array $directions Sort directions indexed by attribute names.
- * The sort direction. Can be either CSort::SORT_ASC for ascending order or
- * CSort::SORT_DESC for descending order.
+ * foreach($models as $model) {
+ *     // display $model here
+ * }
+ * ~~~
+ *
+ * In the above, we declare two [[attributes]] that support sorting: name and age.
+ * We pass the sort information to the Article query so that the query results are
+ * sorted by the orders specified by the Sort object. In the view, we show two hyperlinks
+ * that can lead to pages with the data sorted by the corresponding attributes.
+ *
+ * @property array $orders Sort directions indexed by column names. The sort direction
+ * can be either [[Sort::ASC]] for ascending order or [[Sort::DESC]] for descending order.
+ * @property array $attributeOrders Sort directions indexed by attribute names. The sort
+ * direction can be either [[Sort::ASC]] for ascending order or [[Sort::DESC]] for descending order.
  *
  * @author Qiang Xue <qiang.xue@gmail.com>
- * @version $Id$
- * @package system.web
+ * @since 2.0
  */
-class CSort extends CComponent
+class Sort extends \yii\base\Object
 {
 	/**
 	 * Sort ascending
-	 * @since 1.1.10
 	 */
-	const SORT_ASC = false;
+	const ASC = false;
 
 	/**
 	 * Sort descending
-	 * @since 1.1.10
 	 */
-	const SORT_DESC = true;
+	const DESC = true;
 
 	/**
 	 * @var boolean whether the sorting can be applied to multiple attributes simultaneously.
 	 * Defaults to false, which means each time the data can only be sorted by one attribute.
 	 */
-	public $multiSort = false;
-	/**
-	 * @var string the name of the model class whose attributes can be sorted.
-	 * The model class must be a child class of {@link CActiveRecord}.
-	 */
-	public $modelClass;
+	public $enableMultiSort = false;
+
 	/**
-	 * @var array list of attributes that are allowed to be sorted.
-	 * For example, array('user_id','create_time') would specify that only 'user_id'
-	 * and 'create_time' of the model {@link modelClass} can be sorted.
-	 * By default, this property is an empty array, which means all attributes in
-	 * {@link modelClass} are allowed to be sorted.
-	 *
-	 * This property can also be used to specify complex sorting. To do so,
-	 * a virtual attribute can be declared in terms of a key-value pair in the array.
-	 * The key refers to the name of the virtual attribute that may appear in the sort request,
-	 * while the value specifies the definition of the virtual attribute.
+	 * @var array list of attributes that are allowed to be sorted. Its syntax can be
+	 * described using the following example:
 	 *
-	 * In the simple case, a key-value pair can be like <code>'user'=>'user_id'</code>
-	 * where 'user' is the name of the virtual attribute while 'user_id' means the virtual
-	 * attribute is the 'user_id' attribute in the {@link modelClass}.
-	 *
-	 * A more flexible way is to specify the key-value pair as
-	 * <pre>
-	 * 'user'=>array(
-	 *     'asc'=>'first_name, last_name',
-	 *     'desc'=>'first_name DESC, last_name DESC',
-	 *     'label'=>'Name'
-	 * )
-	 * </pre>
-	 * where 'user' is the name of the virtual attribute that specifies the full name of user
-	 * (a compound attribute consisting of first name and last name of user). In this case,
-	 * we have to use an array to define the virtual attribute with three elements: 'asc',
-	 * 'desc' and 'label'.
-	 *
-	 * The above approach can also be used to declare virtual attributes that consist of relational
-	 * attributes. For example,
-	 * <pre>
-	 * 'price'=>array(
-	 *     'asc'=>'item.price',
-	 *     'desc'=>'item.price DESC',
-	 *     'label'=>'Item Price'
+	 * ~~~
+	 * array(
+	 *     'age',
+	 *     'user' => array(
+	 *         'asc' => array('first_name' => Sort::ASC, 'last_name' => Sort::ASC),
+	 *         'desc' => array('first_name' => Sort::DESC, 'last_name' => Sort::DESC),
+	 *         'default' => 'desc',
+	 *     ),
 	 * )
-	 * </pre>
+	 * ~~~
 	 *
-	 * Note, the attribute name should not contain '-' or '.' characters because
-	 * they are used as {@link separators}.
+	 * In the above, two attributes are declared: "age" and "user". The "age" attribute is
+	 * a simple attribute which is equivalent to the following:
 	 *
-	 * Starting from version 1.1.3, an additional option named 'default' can be used in the virtual attribute
-	 * declaration. This option specifies whether an attribute should be sorted in ascending or descending
-	 * order upon user clicking the corresponding sort hyperlink if it is not currently sorted. The valid
-	 * option values include 'asc' (default) and 'desc'. For example,
-	 * <pre>
-	 * 'price'=>array(
-	 *     'asc'=>'item.price',
-	 *     'desc'=>'item.price DESC',
-	 *     'label'=>'Item Price',
-	 *     'default'=>'desc',
+	 * ~~~
+	 * 'age' => array(
+	 *     'asc' => array('age' => Sort::ASC),
+	 *     'desc' => array('age' => Sort::DESC),
 	 * )
-	 * </pre>
+	 * ~~~
 	 *
-	 * Also starting from version 1.1.3, you can include a star ('*') element in this property so that
-	 * all model attributes are available for sorting, in addition to those virtual attributes. For example,
-	 * <pre>
-	 * 'attributes'=>array(
-	 *     'price'=>array(
-	 *         'asc'=>'item.price',
-	 *         'desc'=>'item.price DESC',
-	 *         'label'=>'Item Price',
-	 *         'default'=>'desc',
-	 *     ),
-	 *     '*',
-	 * )
-	 * </pre>
-	 * Note that when a name appears as both a model attribute and a virtual attribute, the position of
-	 * the star element in the array determines which one takes precedence. In particular, if the star
-	 * element is the first element in the array, the model attribute takes precedence; and if the star
-	 * element is the last one, the virtual attribute takes precedence.
+	 * The "user" attribute is a composite attribute:
+	 *
+	 * - The "user" key represents the attribute name which will appear in the URLs leading
+	 *   to sort actions. Attribute names cannot contain characters listed in [[separators]].
+	 * - The "asc" and "desc" elements specify how to sort by the attribute in ascending
+	 *   and descending orders, respectively. Their values represent the actual columns and
+	 *   the directions by which the data should be sorted by.
+	 * - And the "default" element specifies if the attribute is not sorted currently,
+	 *   in which direction it should be sorted (the default value is ascending order).
 	 */
 	public $attributes = array();
 	/**
-	 * @var string the name of the GET parameter that specifies which attributes to be sorted
+	 * @var string the name of the parameter that specifies which attributes to be sorted
 	 * in which direction. Defaults to 'sort'.
+	 * @see params
 	 */
 	public $sortVar = 'sort';
 	/**
-	 * @var string the tag appeared in the GET parameter that indicates the attribute should be sorted
+	 * @var string the tag appeared in the [[sortVar]] parameter that indicates the attribute should be sorted
 	 * in descending order. Defaults to 'desc'.
 	 */
 	public $descTag = 'desc';
 	/**
-	 * @var mixed the default order that should be applied to the query criteria when
-	 * the current request does not specify any sort. For example, 'name, create_time DESC' or
-	 * 'UPPER(name)'.
+	 * @var array the order that should be used when the current request does not specify any order.
+	 * The array keys are attribute names and the array values are the corresponding sort directions. For example,
 	 *
-	 * Starting from version 1.1.3, you can also specify the default order using an array.
-	 * The array keys could be attribute names or virtual attribute names as declared in {@link attributes},
-	 * and the array values indicate whether the sorting of the corresponding attributes should
-	 * be in descending order. For example,
-	 * <pre>
-	 * 'defaultOrder'=>array(
-	 *     'price'=>CSort::SORT_DESC,
+	 * ~~~
+	 * array(
+	 *     'name' => Sort::ASC,
+	 *     'create_time' => Sort::DESC,
 	 * )
-	 * </pre>
-	 * `SORT_DESC` and `SORT_ASC` are available since 1.1.10. In earlier Yii versions you should use
-	 * `true` and `false` respectively.
+	 * ~~~
 	 *
-	 * Please note when using array to specify the default order, the corresponding attributes
-	 * will be put into {@link directions} and thus affect how the sort links are rendered
-	 * (e.g. an arrow may be displayed next to the currently active sort link).
+	 * @see attributeOrders
 	 */
-	public $defaultOrder;
+	public $defaults;
 	/**
-	 * @var string the route (controller ID and action ID) for generating the sorted contents.
-	 * Defaults to empty string, meaning using the currently requested route.
+	 * @var string the route of the controller action for displaying the sorted contents.
+	 * If not set, it means using the currently requested route.
 	 */
-	public $route = '';
+	public $route;
 	/**
 	 * @var array separators used in the generated URL. This must be an array consisting of
 	 * two elements. The first element specifies the character separating different
 	 * attributes, while the second element specifies the character separating attribute name
-	 * and the corresponding sort direction. Defaults to array('-','.').
+	 * and the corresponding sort direction. Defaults to `array('-', '.')`.
 	 */
 	public $separators = array('-', '.');
 	/**
-	 * @var array the additional GET parameters (name=>value) that should be used when generating sort URLs.
-	 * Defaults to null, meaning using the currently available GET parameters.
+	 * @var array parameters (name => value) that should be used to obtain the current sort directions
+	 * and to create new sort URLs. If not set, $_GET will be used instead.
+	 *
+	 * The array element indexed by [[sortVar]] is considered to be the current sort directions.
+	 * If the element does not exist, the [[defaults|default order]] will be used.
+	 *
+	 * @see sortVar
+	 * @see defaults
 	 */
 	public $params;
 
-	private $_directions;
-
 	/**
-	 * Constructor.
-	 * @param string $modelClass the class name of data models that need to be sorted.
-	 * This should be a child class of {@link CActiveRecord}.
+	 * Returns the columns and their corresponding sort directions.
+	 * @return array the columns (keys) and their corresponding sort directions (values).
+	 * This can be passed to [[\yii\db\Query::orderBy()]] to construct a DB query.
 	 */
-	public function __construct($modelClass = null)
+	public function getOrders()
 	{
-		$this->modelClass = $modelClass;
-	}
-
-	/**
-	 * Modifies the query criteria by changing its {@link CDbCriteria::order} property.
-	 * This method will use {@link directions} to determine which columns need to be sorted.
-	 * They will be put in the ORDER BY clause. If the criteria already has non-empty {@link CDbCriteria::order} value,
-	 * the new value will be appended to it.
-	 * @param CDbCriteria $criteria the query criteria
-	 */
-	public function applyOrder($criteria)
-	{
-		$order = $this->getOrderBy();
-		if (!empty($order)) {
-			if (!empty($criteria->order)) {
-				$criteria->order .= ', ';
+		$attributeOrders = $this->getAttributeOrders();
+		$orders = array();
+		foreach ($attributeOrders as $attribute => $direction) {
+			$definition = $this->getAttribute($attribute);
+			$columns = $definition[$direction === self::ASC ? 'asc' : 'desc'];
+			foreach ($columns as $name => $dir) {
+				$orders[$name] = $dir;
 			}
-			$criteria->order .= $order;
 		}
+		return $orders;
 	}
 
 	/**
-	 * @return string the order-by columns represented by this sort object.
-	 * This can be put in the ORDER BY clause of a SQL statement.
-	 * @since 1.1.0
-	 */
-	public function getOrderBy()
-	{
-		$directions = $this->getDirections();
-		if (empty($directions)) {
-			return is_string($this->defaultOrder) ? $this->defaultOrder : '';
-		} else {
-			if ($this->modelClass !== null) {
-				$schema = CActiveRecord::model($this->modelClass)->getDbConnection()->getSchema();
-			}
-			$orders = array();
-			foreach ($directions as $attribute => $descending) {
-				$definition = $this->resolveAttribute($attribute);
-				if (is_array($definition)) {
-					if ($descending) {
-						$orders[] = isset($definition['desc']) ? $definition['desc'] : $attribute . ' DESC';
-					} else {
-						$orders[] = isset($definition['asc']) ? $definition['asc'] : $attribute;
-					}
-				} else {
-					if ($definition !== false) {
-						$attribute = $definition;
-						if (isset($schema)) {
-							if (($pos = strpos($attribute, '.')) !== false) {
-								$attribute = $schema->quoteTableName(substr($attribute, 0, $pos)) . '.' . $schema->quoteColumnName(substr($attribute, $pos + 1));
-							} else {
-								$attribute = CActiveRecord::model($this->modelClass)->getTableAlias(true) . '.' . $schema->quoteColumnName($attribute);
-							}
-						}
-						$orders[] = $descending ? $attribute . ' DESC' : $attribute;
-					}
-				}
-			}
-			return implode(', ', $orders);
-		}
-	}
-
-	/**
-	 * Generates a hyperlink that can be clicked to cause sorting.
-	 * @param string $attribute the attribute name. This must be the actual attribute name, not alias.
-	 * If it is an attribute of a related AR object, the name should be prefixed with
-	 * the relation name (e.g. 'author.name', where 'author' is the relation name).
-	 * @param string $label the link label. If null, the label will be determined according
-	 * to the attribute (see {@link resolveLabel}).
+	 * Generates a hyperlink that links to the sort action to sort by the specified attribute.
+	 * Based on the sort direction, the CSS class of the generated hyperlink will be appended
+	 * with "asc" or "desc".
+	 * @param string $attribute the attribute name by which the data should be sorted by.
+	 * @param string $label the link label. Note that the label will not be HTML-encoded.
 	 * @param array $htmlOptions additional HTML attributes for the hyperlink tag
 	 * @return string the generated hyperlink
 	 */
-	public function link($attribute, $label = null, $htmlOptions = array())
+	public function link($attribute, $label, $htmlOptions = array())
 	{
-		if ($label === null) {
-			$label = $this->resolveLabel($attribute);
-		}
-		if (($definition = $this->resolveAttribute($attribute)) === false) {
+		if (($definition = $this->getAttribute($attribute)) === false) {
 			return $label;
 		}
-		$directions = $this->getDirections();
-		if (isset($directions[$attribute])) {
-			$class = $directions[$attribute] ? 'desc' : 'asc';
+
+		if (($direction = $this->getAttributeOrder($attribute)) !== null) {
+			$class = $direction ? 'desc' : 'asc';
 			if (isset($htmlOptions['class'])) {
 				$htmlOptions['class'] .= ' ' . $class;
 			} else {
 				$htmlOptions['class'] = $class;
 			}
-			$descending = !$directions[$attribute];
-			unset($directions[$attribute]);
-		} else {
-			if (is_array($definition) && isset($definition['default'])) {
-				$descending = $definition['default'] === 'desc';
-			} else {
-				$descending = false;
-			}
-		}
-
-		if ($this->multiSort) {
-			$directions = array_merge(array($attribute => $descending), $directions);
-		} else {
-			$directions = array($attribute => $descending);
 		}
 
-		$url = $this->createUrl(\Yii::$application->getController(), $directions);
+		$url = $this->createUrl($attribute);
 
-		return $this->createLink($attribute, $label, $url, $htmlOptions);
+		return Html::link($label, $url, $htmlOptions);
 	}
 
-	/**
-	 * Resolves the attribute label for the specified attribute.
-	 * This will invoke {@link CActiveRecord::getAttributeLabel} to determine what label to use.
-	 * If the attribute refers to a virtual attribute declared in {@link attributes},
-	 * then the label given in the {@link attributes} will be returned instead.
-	 * @param string $attribute the attribute name.
-	 * @return string the attribute label
-	 */
-	public function resolveLabel($attribute)
-	{
-		$definition = $this->resolveAttribute($attribute);
-		if (is_array($definition)) {
-			if (isset($definition['label'])) {
-				return $definition['label'];
-			}
-		} else {
-			if (is_string($definition)) {
-				$attribute = $definition;
-			}
-		}
-		if ($this->modelClass !== null) {
-			return CActiveRecord::model($this->modelClass)->getAttributeLabel($attribute);
-		} else {
-			return $attribute;
-		}
-	}
+	private $_attributeOrders;
 
 	/**
 	 * Returns the currently requested sort information.
+	 * @param boolean $recalculate whether to recalculate the sort directions
 	 * @return array sort directions indexed by attribute names.
-	 * Sort direction can be either CSort::SORT_ASC for ascending order or
-	 * CSort::SORT_DESC for descending order.
+	 * Sort direction can be either [[Sort::ASC]] for ascending order or
+	 * [[Sort::DESC]] for descending order.
 	 */
-	public function getDirections()
+	public function getAttributeOrders($recalculate = false)
 	{
-		if ($this->_directions === null) {
-			$this->_directions = array();
-			if (isset($_GET[$this->sortVar]) && is_string($_GET[$this->sortVar])) {
-				$attributes = explode($this->separators[0], $_GET[$this->sortVar]);
+		if ($this->_attributeOrders === null || $recalculate) {
+			$this->_attributeOrders = array();
+			$params = $this->params === null ? $_GET : $this->params;
+			if (isset($params[$this->sortVar]) && is_scalar($params[$this->sortVar])) {
+				$attributes = explode($this->separators[0], $params[$this->sortVar]);
 				foreach ($attributes as $attribute) {
+					$descending = false;
 					if (($pos = strrpos($attribute, $this->separators[1])) !== false) {
-						$descending = substr($attribute, $pos + 1) === $this->descTag;
-						if ($descending) {
+						if ($descending = (substr($attribute, $pos + 1) === $this->descTag)) {
 							$attribute = substr($attribute, 0, $pos);
 						}
-					} else {
-						$descending = false;
 					}
 
-					if (($this->resolveAttribute($attribute)) !== false) {
-						$this->_directions[$attribute] = $descending;
-						if (!$this->multiSort) {
-							return $this->_directions;
+					if (($this->getAttribute($attribute)) !== false) {
+						$this->_attributeOrders[$attribute] = $descending;
+						if (!$this->enableMultiSort) {
+							return $this->_attributeOrders;
 						}
 					}
 				}
 			}
-			if ($this->_directions === array() && is_array($this->defaultOrder)) {
-				$this->_directions = $this->defaultOrder;
+			if ($this->_attributeOrders === array() && is_array($this->defaults)) {
+				$this->_attributeOrders = $this->defaults;
 			}
 		}
-		return $this->_directions;
+		return $this->_attributeOrders;
 	}
 
 	/**
 	 * Returns the sort direction of the specified attribute in the current request.
 	 * @param string $attribute the attribute name
-	 * @return mixed Sort direction of the attribute. Can be either CSort::SORT_ASC
-	 * for ascending order or CSort::SORT_DESC for descending order. Value is null
-	 * if the attribute doesn't need to be sorted.
+	 * @return boolean|null Sort direction of the attribute. Can be either [[Sort::ASC]]
+	 * for ascending order or [[Sort::DESC]] for descending order. Null is returned
+	 * if the attribute is invalid or does not need to be sorted.
 	 */
-	public function getDirection($attribute)
+	public function getAttributeOrder($attribute)
 	{
-		$this->getDirections();
-		return isset($this->_directions[$attribute]) ? $this->_directions[$attribute] : null;
+		$this->getAttributeOrders();
+		return isset($this->_attributeOrders[$attribute]) ? $this->_attributeOrders[$attribute] : null;
 	}
 
 	/**
-	 * Creates a URL that can lead to generating sorted data.
-	 * @param CController $controller the controller that will be used to create the URL.
-	 * @param array $directions the sort directions indexed by attribute names.
-	 * The sort direction can be either CSort::SORT_ASC for ascending order or
-	 * CSort::SORT_DESC for descending order.
-	 * @return string the URL for sorting
+	 * Creates a URL for sorting the data by the specified attribute.
+	 * This method will consider the current sorting status given by [[attributeOrders]].
+	 * For example, if the current page already sorts the data by the specified attribute in ascending order,
+	 * then the URL created will lead to a page that sorts the data by the specified attribute in descending order.
+	 * @param string $attribute the attribute name
+	 * @return string|boolean the URL for sorting. False if the attribute is invalid.
+	 * @see attributeOrders
+	 * @see params
 	 */
-	public function createUrl($controller, $directions)
+	public function createUrl($attribute)
 	{
+		if (($definition = $this->getAttribute($attribute)) === false) {
+			return false;
+		}
+		$directions = $this->getAttributeOrders();
+		if (isset($directions[$attribute])) {
+			$descending = !$directions[$attribute];
+			unset($directions[$attribute]);
+		} elseif (isset($definition['default'])) {
+			$descending = $definition['default'] === 'desc';
+		} else {
+			$descending = false;
+		}
+
+		if ($this->enableMultiSort) {
+			$directions = array_merge(array($attribute => $descending), $directions);
+		} else {
+			$directions = array($attribute => $descending);
+		}
+
 		$sorts = array();
 		foreach ($directions as $attribute => $descending) {
 			$sorts[] = $descending ? $attribute . $this->separators[1] . $this->descTag : $attribute;
 		}
 		$params = $this->params === null ? $_GET : $this->params;
 		$params[$this->sortVar] = implode($this->separators[0], $sorts);
-		return $controller->createUrl($this->route, $params);
+		$route = $this->route === null ? Yii::$app->controller->route : $this->route;
+
+		return Yii::$app->getUrlManager()->createUrl($route, $params);
 	}
 
 	/**
-	 * Returns the real definition of an attribute given its name.
-	 *
-	 * The resolution is based on {@link attributes} and {@link CActiveRecord::attributeNames}.
-	 * <ul>
-	 * <li>When {@link attributes} is an empty array, if the name refers to an attribute of {@link modelClass},
-	 * then the name is returned back.</li>
-	 * <li>When {@link attributes} is not empty, if the name refers to an attribute declared in {@link attributes},
-	 * then the corresponding virtual attribute definition is returned. Starting from version 1.1.3, if {@link attributes}
-	 * contains a star ('*') element, the name will also be used to match against all model attributes.</li>
-	 * <li>In all other cases, false is returned, meaning the name does not refer to a valid attribute.</li>
-	 * </ul>
-	 * @param string $attribute the attribute name that the user requests to sort on
-	 * @return mixed the attribute name or the virtual attribute definition. False if the attribute cannot be sorted.
+	 * Returns the attribute definition of the specified name.
+	 * @param string $name the attribute name
+	 * @return array|boolean the sort definition (column names => sort directions).
+	 * False is returned if the attribute cannot be sorted.
+	 * @see attributes
 	 */
-	public function resolveAttribute($attribute)
+	public function getAttribute($name)
 	{
-		if ($this->attributes !== array()) {
-			$attributes = $this->attributes;
+		if (isset($this->attributes[$name])) {
+			return $this->attributes[$name];
+		} elseif (in_array($name, $this->attributes, true)) {
+			return array(
+				'asc' => array($name => self::ASC),
+				'desc' => array($name => self::DESC),
+			);
 		} else {
-			if ($this->modelClass !== null) {
-				$attributes = CActiveRecord::model($this->modelClass)->attributes();
-			} else {
-				return false;
-			}
+			return false;
 		}
-		foreach ($attributes as $name => $definition) {
-			if (is_string($name)) {
-				if ($name === $attribute) {
-					return $definition;
-				}
-			} else {
-				if ($definition === '*') {
-					if ($this->modelClass !== null && CActiveRecord::model($this->modelClass)->hasAttribute($attribute)) {
-						return $attribute;
-					}
-				} else {
-					if ($definition === $attribute) {
-						return $attribute;
-					}
-				}
-			}
-		}
-		return false;
-	}
-
-	/**
-	 * Creates a hyperlink based on the given label and URL.
-	 * You may override this method to customize the link generation.
-	 * @param string $attribute the name of the attribute that this link is for
-	 * @param string $label the label of the hyperlink
-	 * @param string $url the URL
-	 * @param array $htmlOptions additional HTML options
-	 * @return string the generated hyperlink
-	 */
-	protected function createLink($attribute, $label, $url, $htmlOptions)
-	{
-		return CHtml::link($label, $url, $htmlOptions);
 	}
 }
\ No newline at end of file
diff --git a/framework/web/Theme.php b/framework/web/Theme.php
deleted file mode 100644
index 5dcd601..0000000
--- a/framework/web/Theme.php
+++ /dev/null
@@ -1,141 +0,0 @@
-<?php
-/**
- * CTheme class file.
- *
- * @author Qiang Xue <qiang.xue@gmail.com>
- * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008-2011 Yii Software LLC
- * @license http://www.yiiframework.com/license/
- */
-
-/**
- * CTheme represents an application theme.
- *
- * @property string $name Theme name.
- * @property string $baseUrl The relative URL to the theme folder (without ending slash).
- * @property string $basePath The file path to the theme folder.
- * @property string $viewPath The path for controller views. Defaults to 'ThemeRoot/views'.
- * @property string $systemViewPath The path for system views. Defaults to 'ThemeRoot/views/system'.
- * @property string $skinPath The path for widget skins. Defaults to 'ThemeRoot/views/skins'.
- *
- * @author Qiang Xue <qiang.xue@gmail.com>
- * @version $Id$
- * @package system.web
- * @since 1.0
- */
-class CTheme extends CComponent
-{
-	private $_name;
-	private $_basePath;
-	private $_baseUrl;
-
-	/**
-	 * Constructor.
-	 * @param string $name name of the theme
-	 * @param string $basePath base theme path
-	 * @param string $baseUrl base theme URL
-	 */
-	public function __construct($name,$basePath,$baseUrl)
-	{
-		$this->_name=$name;
-		$this->_baseUrl=$baseUrl;
-		$this->_basePath=$basePath;
-	}
-
-	/**
-	 * @return string theme name
-	 */
-	public function getName()
-	{
-		return $this->_name;
-	}
-
-	/**
-	 * @return string the relative URL to the theme folder (without ending slash)
-	 */
-	public function getBaseUrl()
-	{
-		return $this->_baseUrl;
-	}
-
-	/**
-	 * @return string the file path to the theme folder
-	 */
-	public function getBasePath()
-	{
-		return $this->_basePath;
-	}
-
-	/**
-	 * @return string the path for controller views. Defaults to 'ThemeRoot/views'.
-	 */
-	public function getViewPath()
-	{
-		return $this->_basePath.DIRECTORY_SEPARATOR.'views';
-	}
-
-	/**
-	 * @return string the path for system views. Defaults to 'ThemeRoot/views/system'.
-	 */
-	public function getSystemViewPath()
-	{
-		return $this->getViewPath().DIRECTORY_SEPARATOR.'system';
-	}
-
-	/**
-	 * @return string the path for widget skins. Defaults to 'ThemeRoot/views/skins'.
-	 * @since 1.1
-	 */
-	public function getSkinPath()
-	{
-		return $this->getViewPath().DIRECTORY_SEPARATOR.'skins';
-	}
-
-	/**
-	 * Finds the view file for the specified controller's view.
-	 * @param CController $controller the controller
-	 * @param string $viewName the view name
-	 * @return string the view file path. False if the file does not exist.
-	 */
-	public function getViewFile($controller,$viewName)
-	{
-		$moduleViewPath=$this->getViewPath();
-		if(($module=$controller->getModule())!==null)
-			$moduleViewPath.='/'.$module->getId();
-		return $controller->resolveViewFile($viewName,$this->getViewPath().'/'.$controller->getUniqueId(),$this->getViewPath(),$moduleViewPath);
-	}
-
-	/**
-	 * Finds the layout file for the specified controller's layout.
-	 * @param CController $controller the controller
-	 * @param string $layoutName the layout name
-	 * @return string the layout file path. False if the file does not exist.
-	 */
-	public function getLayoutFile($controller,$layoutName)
-	{
-		$moduleViewPath=$basePath=$this->getViewPath();
-		$module=$controller->getModule();
-		if(empty($layoutName))
-		{
-			while($module!==null)
-			{
-				if($module->layout===false)
-					return false;
-				if(!empty($module->layout))
-					break;
-				$module=$module->getParentModule();
-			}
-			if($module===null)
-				$layoutName=\Yii::$application->layout;
-			else
-			{
-				$layoutName=$module->layout;
-				$moduleViewPath.='/'.$module->getId();
-			}
-		}
-		else if($module!==null)
-			$moduleViewPath.='/'.$module->getId();
-
-		return $controller->resolveViewFile($layoutName,$moduleViewPath.'/layouts',$basePath,$moduleViewPath);
-	}
-}
diff --git a/framework/web/UrlManager.php b/framework/web/UrlManager.php
new file mode 100644
index 0000000..459e8e8
--- /dev/null
+++ b/framework/web/UrlManager.php
@@ -0,0 +1,251 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+use Yii;
+use yii\base\Component;
+use yii\caching\Cache;
+
+/**
+ * UrlManager handles HTTP request parsing and creation of URLs based on a set of rules.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class UrlManager extends Component
+{
+	/**
+	 * @var boolean whether to enable pretty URLs. Instead of putting all parameters in the query
+	 * string part of a URL, pretty URLs allow using path info to represent some of the parameters
+	 * and can thus produce more user-friendly URLs, such as "/news/Yii-is-released", instead of
+	 * "/index.php?r=news/view&id=100".
+	 */
+	public $enablePrettyUrl = false;
+	/**
+	 * @var array the rules for creating and parsing URLs when [[enablePrettyUrl]] is true.
+	 * This property is used only if [[enablePrettyUrl]] is true. Each element in the array
+	 * is the configuration of creating a single URL rule whose class by default is [[defaultRuleClass]].
+	 * If you modify this property after the UrlManager object is created, make sure
+	 * you populate the array with rule objects instead of rule configurations.
+	 */
+	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.
+	 * This property is used only if [[enablePrettyUrl]] is true.
+	 */
+	public $suffix;
+	/**
+	 * @var boolean whether to show entry script name in the constructed URL. Defaults to true.
+	 * This property is used only if [[enablePrettyUrl]] is true.
+	 */
+	public $showScriptName = true;
+	/**
+	 * @var string the GET variable name for route. This property is used only if [[enablePrettyUrl]] is false.
+	 */
+	public $routeVar = 'r';
+	/**
+	 * @var Cache|string the cache object or the application component ID of the cache object.
+	 * Compiled URL rules will be cached through this cache object, if it is available.
+	 *
+	 * After the UrlManager object is created, if you want to change this property,
+	 * you should only assign it with a cache object.
+	 * Set this property to null if you do not want to cache the URL rules.
+	 */
+	public $cache = 'cache';
+	/**
+	 * @var string the default class name for creating URL rule instances
+	 * when it is not specified in [[rules]].
+	 */
+	public $defaultRuleClass = 'yii\web\UrlRule';
+
+	private $_baseUrl;
+	private $_hostInfo;
+
+
+	/**
+	 * Initializes UrlManager.
+	 */
+	public function init()
+	{
+		parent::init();
+		if (is_string($this->cache)) {
+			$this->cache = Yii::$app->getComponent($this->cache);
+		}
+		$this->compileRules();
+	}
+
+	/**
+	 * Parses the URL rules.
+	 */
+	protected function compileRules()
+	{
+		if (!$this->enablePrettyUrl || $this->rules === array()) {
+			return;
+		}
+		if ($this->cache instanceof Cache) {
+			$key = $this->cache->buildKey(__CLASS__);
+			$hash = md5(json_encode($this->rules));
+			if (($data = $this->cache->get($key)) !== false && isset($data[1]) && $data[1] === $hash) {
+				$this->rules = $data[0];
+				return;
+			}
+		}
+
+		foreach ($this->rules as $i => $rule) {
+			if (!isset($rule['class'])) {
+				$rule['class'] = $this->defaultRuleClass;
+			}
+			$this->rules[$i] = Yii::createObject($rule);
+		}
+
+		if ($this->cache instanceof Cache) {
+			$this->cache->set($key, array($this->rules, $hash));
+		}
+	}
+
+	/**
+	 * Parses the user request.
+	 * @param Request $request the request component
+	 * @return array|boolean the route and the associated parameters. The latter is always empty
+	 * if [[enablePrettyUrl]] is false. False is returned if the current request cannot be successfully parsed.
+	 */
+	public function parseRequest($request)
+	{
+		if ($this->enablePrettyUrl) {
+			$pathInfo = $request->pathInfo;
+			/** @var $rule UrlRule */
+			foreach ($this->rules as $rule) {
+				if (($result = $rule->parseRequest($this, $request)) !== false) {
+					return $result;
+				}
+			}
+
+			$suffix = (string)$this->suffix;
+			if ($suffix !== '' && $suffix !== '/' && $pathInfo !== '') {
+				$n = strlen($this->suffix);
+				if (substr($pathInfo, -$n) === $this->suffix) {
+					$pathInfo = substr($pathInfo, 0, -$n);
+					if ($pathInfo === '') {
+						// suffix alone is not allowed
+						return false;
+					}
+				} else {
+					// suffix doesn't match
+					return false;
+				}
+			}
+
+			return array($pathInfo, array());
+		} else {
+			$route = $request->getParam($this->routeVar);
+			if (is_array($route)) {
+				$route = '';
+			}
+			return array((string)$route, array());
+		}
+	}
+
+	/**
+	 * Creates a URL using the given route and parameters.
+	 * The URL created is a relative one. Use [[createAbsoluteUrl()]] to create an absolute URL.
+	 * @param string $route the route
+	 * @param array $params the parameters (name-value pairs)
+	 * @return string the created URL
+	 */
+	public function createUrl($route, $params = array())
+	{
+		$anchor = isset($params['#']) ? '#' . $params['#'] : '';
+		unset($params['#']);
+
+		$route = trim($route, '/');
+		$baseUrl = $this->getBaseUrl();
+
+		if ($this->enablePrettyUrl) {
+			/** @var $rule UrlRule */
+			foreach ($this->rules as $rule) {
+				if (($url = $rule->createUrl($this, $route, $params)) !== false) {
+					return rtrim($baseUrl, '/') . '/' . $url . $anchor;
+				}
+			}
+
+			if ($this->suffix !== null) {
+				$route .= $this->suffix;
+			}
+			if ($params !== array()) {
+				$route .= '?' . http_build_query($params);
+			}
+			return rtrim($baseUrl, '/') . '/' . $route . $anchor;
+		} else {
+			$url = $baseUrl . '?' . $this->routeVar . '=' . $route;
+			if ($params !== array()) {
+				$url .= '&' . http_build_query($params);
+			}
+			return $url;
+		}
+	}
+
+	/**
+	 * Creates an absolute URL using the given route and parameters.
+	 * This method prepends the URL created by [[createUrl()]] with the [[hostInfo]].
+	 * @param string $route the route
+	 * @param array $params the parameters (name-value pairs)
+	 * @return string the created URL
+	 * @see createUrl()
+	 */
+	public function createAbsoluteUrl($route, $params = array())
+	{
+		return $this->getHostInfo() . $this->createUrl($route, $params);
+	}
+
+	/**
+	 * Returns the base URL that is used by [[createUrl()]] to prepend URLs it creates.
+	 * It defaults to [[Request::scriptUrl]] if [[showScriptName]] is true or [[enablePrettyUrl]] is false;
+	 * otherwise, it defaults to [[Request::baseUrl]].
+	 * @return string the base URL that is used by [[createUrl()]] to prepend URLs it creates.
+	 */
+	public function getBaseUrl()
+	{
+		if ($this->_baseUrl === null) {
+			/** @var $request \yii\web\Request */
+			$request = Yii::$app->getRequest();
+			$this->_baseUrl = $this->showScriptName || !$this->enablePrettyUrl ? $request->getScriptUrl() : $request->getBaseUrl();
+		}
+		return $this->_baseUrl;
+	}
+
+	/**
+	 * Sets the base URL that is used by [[createUrl()]] to prepend URLs it creates.
+	 * @param string $value the base URL that is used by [[createUrl()]] to prepend URLs it creates.
+	 */
+	public function setBaseUrl($value)
+	{
+		$this->_baseUrl = $value;
+	}
+
+	/**
+	 * Returns the host info that is used by [[createAbsoluteUrl()]] to prepend URLs it creates.
+	 * @return string the host info (e.g. "http://www.example.com") that is used by [[createAbsoluteUrl()]] to prepend URLs it creates.
+	 */
+	public function getHostInfo()
+	{
+		if ($this->_hostInfo === null) {
+			$this->_hostInfo = Yii::$app->getRequest()->getHostInfo();
+		}
+		return $this->_hostInfo;
+	}
+
+	/**
+	 * Sets the host info that is used by [[createAbsoluteUrl()]] to prepend URLs it creates.
+	 * @param string $value the host info (e.g. "http://www.example.com") that is used by [[createAbsoluteUrl()]] to prepend URLs it creates.
+	 */
+	public function setHostInfo($value)
+	{
+		$this->_hostInfo = rtrim($value, '/');
+	}
+}
diff --git a/framework/web/UrlRule.php b/framework/web/UrlRule.php
new file mode 100644
index 0000000..d9cb4fd
--- /dev/null
+++ b/framework/web/UrlRule.php
@@ -0,0 +1,283 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+use yii\base\Object;
+use yii\base\InvalidConfigException;
+
+/**
+ * UrlRule represents a rule used for parsing and generating URLs.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+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;
+	/**
+	 * @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 $defaults = array();
+	/**
+	 * @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.
+	 */
+	public $verb;
+	/**
+	 * @var integer a value indicating if this rule should be used for both request parsing and URL creation,
+	 * parsing only, or creation only.
+	 * If not set or 0, it means the rule is both request parsing and URL creation.
+	 * If it is [[PARSING_ONLY]], the rule is for request 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.
+	 */
+	private $_template;
+	/**
+	 * @var string the regex for matching the route part. This is used in generating URL.
+	 */
+	private $_routeRule;
+	/**
+	 * @var array list of regex for matching parameters. This is used in generating URL.
+	 */
+	private $_paramRules = array();
+	/**
+	 * @var array list of parameters used in the route.
+	 */
+	private $_routeParams = array();
+
+	/**
+	 * Initializes this rule.
+	 */
+	public function init()
+	{
+		if ($this->pattern === null) {
+			throw new InvalidConfigException('UrlRule::pattern must be set.');
+		}
+		if ($this->route === null) {
+			throw new InvalidConfigException('UrlRule::route must be set.');
+		}
+		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));
+			}
+		}
+
+		$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 = '#^' . trim(strtr($this->_template, $tr), '/') . '$#u';
+
+		if ($this->_routeParams !== array()) {
+			$this->_routeRule = '#^' . strtr($this->route, $tr2) . '$#u';
+		}
+	}
+
+	/**
+	 * Parses the given request and returns the corresponding route and parameters.
+	 * @param UrlManager $manager the URL manager
+	 * @param Request $request the request component
+	 * @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 parseRequest($manager, $request)
+	{
+		if ($this->mode === self::CREATION_ONLY) {
+			return false;
+		}
+
+		if ($this->verb !== null && !in_array($request->verb, $this->verb, true)) {
+			return false;
+		}
+
+		$pathInfo = $request->pathInfo;
+		$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;
+		}
+		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 (isset($this->_routeParams[$name])) {
+				$tr[$this->_routeParams[$name]] = $value;
+				unset($params[$name]);
+			} 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);
+	}
+
+	/**
+	 * 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->mode === self::PARSING_ONLY) {
+			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 (isset($this->defaults[$name]) && strcmp($this->defaults[$name], $matches[$name]) === 0) {
+						$tr[$token] = '';
+					} else {
+						$tr[$token] = $matches[$name];
+					}
+				}
+			} 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($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])) {
+					$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 ($url !== '') {
+			$url .= ($this->suffix === null ? $manager->suffix : $this->suffix);
+		}
+
+		if ($params !== array()) {
+			$url .= '?' . http_build_query($params);
+		}
+		return $url;
+	}
+}
diff --git a/framework/web/User.php b/framework/web/User.php
new file mode 100644
index 0000000..2326a10
--- /dev/null
+++ b/framework/web/User.php
@@ -0,0 +1,547 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+use Yii;
+use yii\base\Component;
+use yii\base\InvalidConfigException;
+
+/**
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class User extends Component
+{
+	const ID_VAR = '__id';
+	const AUTH_EXPIRE_VAR = '__expire';
+
+	const EVENT_BEFORE_LOGIN = 'beforeLogin';
+	const EVENT_AFTER_LOGIN = 'afterLogin';
+	const EVENT_BEFORE_LOGOUT = 'beforeLogout';
+	const EVENT_AFTER_LOGOUT = 'afterLogout';
+
+	/**
+	 * @var string the class name of the [[identity]] object.
+	 */
+	public $identityClass;
+	/**
+	 * @var boolean whether to enable cookie-based login. Defaults to false.
+	 */
+	public $enableAutoLogin = false;
+	/**
+	 * @var string|array the URL for login when [[loginRequired()]] is called. 
+	 * If an array is given, [[UrlManager::createUrl()]] will be called to create the corresponding URL.
+	 * The first element of the array should be the route to the login action, and the rest of 
+	 * the name-value pairs are GET parameters used to construct the login URL. For example,
+	 * 
+	 * ~~~
+	 * array('site/login', 'ref' => 1)
+	 * ~~~
+	 *
+	 * If this property is null, a 403 HTTP exception will be raised when [[loginRequired()]] is called.
+	 */
+	public $loginUrl = array('site/login');
+	/**
+	 * @var array the configuration of the identity cookie. This property is used only when [[enableAutoLogin]] is true.
+	 * @see Cookie
+	 */
+	public $identityCookie = array('name' => '__identity');
+	/**
+	 * @var integer the number of seconds in which the user will be logged out automatically if he
+	 * remains inactive. If this property is not set, the user will be logged out after
+	 * the current session expires (c.f. [[Session::timeout]]).
+	 */
+	public $authTimeout;
+	/**
+	 * @var boolean whether to automatically renew the identity cookie each time a page is requested.
+	 * Defaults to false. This property is effective only when {@link enableAutoLogin} is true.
+	 * When this is false, the identity cookie will expire after the specified duration since the user
+	 * is initially logged in. When this is true, the identity cookie will expire after the specified duration
+	 * since the user visits the site the last time.
+	 * @see enableAutoLogin
+	 * @since 1.1.0
+	 */
+	public $autoRenewCookie = false;
+	/**
+	 * @var string value that will be echoed in case that user session has expired during an ajax call.
+	 * When a request is made and user session has expired, {@link loginRequired} redirects to {@link loginUrl} for login.
+	 * If that happens during an ajax call, the complete HTML login page is returned as the result of that ajax call. That could be
+	 * a problem if the ajax call expects the result to be a json array or a predefined string, as the login page is ignored in that case.
+	 * To solve this, set this property to the desired return value.
+	 *
+	 * If this property is set, this value will be returned as the result of the ajax call in case that the user session has expired.
+	 * @since 1.1.9
+	 * @see loginRequired
+	 */
+	public $loginRequiredAjaxResponse;
+	
+
+	public $stateVar = '__states';
+
+	/**
+	 * Initializes the application component.
+	 */
+	public function init()
+	{
+		parent::init();
+
+		if ($this->enableAutoLogin && !isset($this->identityCookie['name'])) {
+			throw new InvalidConfigException('User::identityCookie must contain the "name" element.');
+		}
+
+		Yii::$app->getSession()->open();
+
+		$this->renewAuthStatus();
+
+		if ($this->enableAutoLogin) {
+			if ($this->getIsGuest()) {
+				$this->loginByCookie();
+			} elseif ($this->autoRenewCookie) {
+				$this->renewIdentityCookie();
+			}
+		}
+	}
+
+	/**
+	 * @var Identity the identity object associated with the currently logged user.
+	 */
+	private $_identity = false;
+
+	public function getIdentity()
+	{
+		if ($this->_identity === false) {
+			$id = $this->getId();
+			if ($id === null) {
+				$this->_identity = null;
+			} else {
+				/** @var $class Identity */
+				$class = $this->identityClass;
+				$this->_identity = $class::findIdentity($this->getId());
+			}
+		}
+		return $this->_identity;
+	}
+
+	public function setIdentity($identity)
+	{
+		$this->switchIdentity($identity);
+	}
+
+	/**
+	 * Logs in a user.
+	 *
+	 * The user identity information will be saved in storage that is
+	 * persistent during the user session. By default, the storage is simply
+	 * the session storage. If the duration parameter is greater than 0,
+	 * a cookie will be sent to prepare for cookie-based login in future.
+	 *
+	 * Note, you have to set {@link enableAutoLogin} to true
+	 * if you want to allow user to be authenticated based on the cookie information.
+	 *
+	 * @param Identity $identity the user identity (which should already be authenticated)
+	 * @param integer $duration number of seconds that the user can remain in logged-in status. Defaults to 0, meaning login till the user closes the browser.
+	 * If greater than 0, cookie-based login will be used. In this case, {@link enableAutoLogin}
+	 * must be set true, otherwise an exception will be thrown.
+	 * @return boolean whether the user is logged in
+	 */
+	public function login($identity, $duration = 0)
+	{
+		if ($this->beforeLogin($identity, false)) {
+			$this->switchIdentity($identity);
+			if ($duration > 0 && $this->enableAutoLogin) {
+				$this->saveIdentityCookie($identity, $duration);
+			}
+			$this->afterLogin($identity, false);
+		}
+		return !$this->getIsGuest();
+	}
+
+	/**
+	 * Populates the current user object with the information obtained from cookie.
+	 * This method is used when automatic login ({@link enableAutoLogin}) is enabled.
+	 * The user identity information is recovered from cookie.
+	 * Sufficient security measures are used to prevent cookie data from being tampered.
+	 * @see saveIdentityCookie
+	 */
+	protected function loginByCookie()
+	{
+		$name = $this->identityCookie['name'];
+		$value = Yii::$app->getRequest()->getCookies()->getValue($name);
+		if ($value !== null) {
+			$data = json_decode($value, true);
+			if (count($data) === 3 && isset($data[0], $data[1], $data[2])) {
+				list ($id, $authKey, $duration) = $data;
+				/** @var $class Identity */
+				$class = $this->identityClass;
+				$identity = $class::findIdentity($id);
+				if ($identity !== null && $identity->validateAuthKey($authKey) && $this->beforeLogin($identity, true)) {
+					$this->switchIdentity($identity);
+					if ($this->autoRenewCookie) {
+						$this->saveIdentityCookie($identity, $duration);
+					}
+					$this->afterLogin($identity, true);
+				}
+			}
+		}
+	}
+
+	/**
+	 * Logs out the current user.
+	 * This will remove authentication-related session data.
+	 * If the parameter is true, the whole session will be destroyed as well.
+	 * @param boolean $destroySession whether to destroy the whole session. Defaults to true. If false,
+	 * then {@link clearStates} will be called, which removes only the data stored via {@link setState}.
+	 */
+	public function logout($destroySession = true)
+	{
+		$identity = $this->getIdentity();
+		if ($identity !== null && $this->beforeLogout($identity)) {
+			$this->switchIdentity(null);
+			if ($this->enableAutoLogin) {
+				Yii::$app->getResponse()->getCookies()->remove(new Cookie($this->identityCookie));
+			}
+			if ($destroySession) {
+				Yii::$app->getSession()->destroy();
+			}
+ 			$this->afterLogout($identity);
+		}
+	}
+
+	/**
+	 * Returns a value indicating whether the user is a guest (not authenticated).
+	 * @return boolean whether the current user is a guest.
+	 */
+	public function getIsGuest()
+	{
+		return $this->getIdentity() === null;
+	}
+
+	/**
+	 * Returns a value that uniquely represents the user.
+	 * @return mixed the unique identifier for the user. If null, it means the user is a guest.
+	 */
+	public function getId()
+	{
+		return $this->getState(static::ID_VAR);
+	}
+
+	/**
+	 * @param mixed $value the unique identifier for the user. If null, it means the user is a guest.
+	 */
+	public function setId($value)
+	{
+		$this->setState(static::ID_VAR, $value);
+	}
+
+	/**
+	 * Returns the URL that the user should be redirected to after successful login.
+	 * This property is usually used by the login action. If the login is successful,
+	 * the action should read this property and use it to redirect the user browser.
+	 * @param string $defaultUrl the default return URL in case it was not set previously. If this is null,
+	 * the application entry URL will be considered as the default return URL.
+	 * @return string the URL that the user should be redirected to after login.
+	 * @see loginRequired
+	 */
+	public function getReturnUrl($defaultUrl = null)
+	{
+		if ($defaultUrl === null) {
+			$defaultReturnUrl = Yii::app()->getUrlManager()->showScriptName ? Yii::app()->getRequest()->getScriptUrl() : Yii::app()->getRequest()->getBaseUrl() . '/';
+		} else {
+			$defaultReturnUrl = CHtml::normalizeUrl($defaultUrl);
+		}
+		return $this->getState('__returnUrl', $defaultReturnUrl);
+	}
+
+	/**
+	 * @param string $value the URL that the user should be redirected to after login.
+	 */
+	public function setReturnUrl($value)
+	{
+		$this->setState('__returnUrl', $value);
+	}
+
+	/**
+	 * Redirects the user browser to the login page.
+	 * Before the redirection, the current URL (if it's not an AJAX url) will be
+	 * kept in {@link returnUrl} so that the user browser may be redirected back
+	 * to the current page after successful login. Make sure you set {@link loginUrl}
+	 * so that the user browser can be redirected to the specified login URL after
+	 * calling this method.
+	 * After calling this method, the current request processing will be terminated.
+	 */
+	public function loginRequired()
+	{
+		$app = Yii::app();
+		$request = $app->getRequest();
+
+		if (!$request->getIsAjaxRequest()) {
+			$this->setReturnUrl($request->getUrl());
+		} elseif (isset($this->loginRequiredAjaxResponse)) {
+			echo $this->loginRequiredAjaxResponse;
+			Yii::app()->end();
+		}
+
+		if (($url = $this->loginUrl) !== null) {
+			if (is_array($url)) {
+				$route = isset($url[0]) ? $url[0] : $app->defaultController;
+				$url = $app->createUrl($route, array_splice($url, 1));
+			}
+			$request->redirect($url);
+		} else {
+			throw new CHttpException(403, Yii::t('yii', 'Login Required'));
+		}
+	}
+
+	/**
+	 * This method is called before logging in a user.
+	 * You may override this method to provide additional security check.
+	 * For example, when the login is cookie-based, you may want to verify
+	 * that the user ID together with a random token in the states can be found
+	 * in the database. This will prevent hackers from faking arbitrary
+	 * identity cookies even if they crack down the server private key.
+	 * @param mixed $id the user ID. This is the same as returned by {@link getId()}.
+	 * @param array $states a set of name-value pairs that are provided by the user identity.
+	 * @param boolean $fromCookie whether the login is based on cookie
+	 * @return boolean whether the user should be logged in
+	 */
+	protected function beforeLogin($identity, $fromCookie)
+	{
+		$event = new UserEvent(array(
+			'identity' => $identity,
+			'fromCookie' => $fromCookie,
+		));
+		$this->trigger(self::EVENT_BEFORE_LOGIN, $event);
+		return $event->isValid;
+	}
+
+	/**
+	 * This method is called after the user is successfully logged in.
+	 * You may override this method to do some postprocessing (e.g. log the user
+	 * login IP and time; load the user permission information).
+	 * @param boolean $fromCookie whether the login is based on cookie.
+	 */
+	protected function afterLogin($identity, $fromCookie)
+	{
+		$this->trigger(self::EVENT_AFTER_LOGIN, new UserEvent(array(
+			'identity' => $identity,
+			'fromCookie' => $fromCookie,
+		)));
+	}
+
+	/**
+	 * This method is invoked when calling {@link logout} to log out a user.
+	 * If this method return false, the logout action will be cancelled.
+	 * You may override this method to provide additional check before
+	 * logging out a user.
+	 * @return boolean whether to log out the user
+	 */
+	protected function beforeLogout($identity)
+	{
+		$event = new UserEvent(array(
+			'identity' => $identity,
+		));
+		$this->trigger(self::EVENT_BEFORE_LOGOUT, $event);
+		return $event->isValid;
+	}
+
+	/**
+	 * This method is invoked right after a user is logged out.
+	 * You may override this method to do some extra cleanup work for the user.
+	 */
+	protected function afterLogout($identity)
+	{
+		$this->trigger(self::EVENT_AFTER_LOGOUT, new UserEvent(array(
+			'identity' => $identity,
+		)));
+	}
+
+
+	/**
+	 * Renews the identity cookie.
+	 * This method will set the expiration time of the identity cookie to be the current time
+	 * plus the originally specified cookie duration.
+	 */
+	protected function renewIdentityCookie()
+	{
+		$name = $this->identityCookie['name'];
+		$value = Yii::$app->getRequest()->getCookies()->getValue($name);
+		if ($value !== null) {
+			$data = json_decode($value, true);
+			if (is_array($data) && isset($data[2])) {
+				$cookie = new Cookie($this->identityCookie);
+				$cookie->value = $value;
+				$cookie->expire = time() + (int)$data[2];
+				Yii::$app->getResponse()->getCookies()->add($cookie);
+			}
+		}
+	}
+
+	/**
+	 * Saves necessary user data into a cookie.
+	 * This method is used when automatic login ({@link enableAutoLogin}) is enabled.
+	 * This method saves user ID, username, other identity states and a validation key to cookie.
+	 * These information are used to do authentication next time when user visits the application.
+	 * @param Identity $identity
+	 * @param integer $duration number of seconds that the user can remain in logged-in status. Defaults to 0, meaning login till the user closes the browser.
+	 * @see loginByCookie
+	 */
+	protected function saveIdentityCookie($identity, $duration)
+	{
+		$cookie = new Cookie($this->identityCookie);
+		$cookie->value = json_encode(array(
+			$identity->getId(),
+			$identity->getAuthKey(),
+			$duration,
+		));
+		$cookie->expire = time() + $duration;
+		Yii::$app->getResponse()->getCookies()->add($cookie);
+	}
+
+	/**
+	 * Changes the current user with the specified identity information.
+	 * This method is called by {@link login} and {@link restoreFromCookie}
+	 * when the current user needs to be populated with the corresponding
+	 * identity information. Derived classes may override this method
+	 * by retrieving additional user-related information. Make sure the
+	 * parent implementation is called first.
+	 * @param Identity $identity a unique identifier for the user
+	 */
+	protected function switchIdentity($identity)
+	{
+		Yii::$app->getSession()->regenerateID(true);
+		$this->setIdentity($identity);
+		if ($identity instanceof Identity) {
+			$this->setId($identity->getId());
+			if ($this->authTimeout !== null) {
+				$this->setState(self::AUTH_EXPIRE_VAR, time() + $this->authTimeout);
+			}
+		} else {
+			$this->removeAllStates();
+		}
+	}
+
+	/**
+	 * Updates the authentication status according to {@link authTimeout}.
+	 * If the user has been inactive for {@link authTimeout} seconds,
+	 * he will be automatically logged out.
+	 */
+	protected function renewAuthStatus()
+	{
+		if ($this->authTimeout !== null && !$this->getIsGuest()) {
+			$expire = $this->getState(self::AUTH_EXPIRE_VAR);
+			if ($expire !== null && $expire < time()) {
+				$this->logout(false);
+			} else {
+				$this->setState(self::AUTH_EXPIRE_VAR, time() + $this->authTimeout);
+			}
+		}
+	}
+
+	/**
+	 * Returns a user state.
+	 * A user state is a session data item associated with the current user.
+	 * If the user logs out, all his/her user states will be removed.
+	 * @param string $key the key identifying the state
+	 * @param mixed $defaultValue value to be returned if the state does not exist.
+	 * @return mixed the state
+	 */
+	public function getState($key, $defaultValue = null)
+	{
+		$manifest = isset($_SESSION[$this->stateVar]) ? $_SESSION[$this->stateVar] : null;
+		if (is_array($manifest) && isset($manifest[$key], $_SESSION[$key])) {
+			return $_SESSION[$key];
+		} else {
+			return $defaultValue;
+		}
+	}
+
+	/**
+	 * Returns all user states.
+	 * @return array states (key => state).
+	 */
+	public function getAllStates()
+	{
+		$manifest = isset($_SESSION[$this->stateVar]) ? $_SESSION[$this->stateVar] : null;
+		$states = array();
+		if (is_array($manifest)) {
+			foreach (array_keys($manifest) as $key) {
+				if (isset($_SESSION[$key])) {
+					$states[$key] = $_SESSION[$key];
+				}
+			}
+		}
+		return $states;
+	}
+
+	/**
+	 * Stores a user state.
+	 * A user state is a session data item associated with the current user.
+	 * If the user logs out, all his/her user states will be removed.
+	 * @param string $key the key identifying the state. Note that states
+	 * and normal session variables share the same name space. If you have a normal
+	 * session variable using the same name, its value will be overwritten by this method.
+	 * @param mixed $value state
+	 */
+	public function setState($key, $value)
+	{
+		$manifest = isset($_SESSION[$this->stateVar]) ? $_SESSION[$this->stateVar] : array();
+		$manifest[$value] = true;
+		$_SESSION[$key] = $value;
+		$_SESSION[$this->stateVar] = $manifest;
+	}
+
+	/**
+	 * Removes a user state.
+	 * If the user logs out, all his/her user states will be removed automatically.
+	 * @param string $key the key identifying the state. Note that states
+	 * and normal session variables share the same name space.  If you have a normal
+	 * session variable using the same name, it will be removed by this method.
+	 * @return mixed the removed state. Null if the state does not exist.
+	 */
+	public function removeState($key)
+	{
+		$manifest = isset($_SESSION[$this->stateVar]) ? $_SESSION[$this->stateVar] : null;
+		if (is_array($manifest) && isset($manifest[$key], $_SESSION[$key])) {
+			$value = $_SESSION[$key];
+		} else {
+			$value = null;
+		}
+		unset($_SESSION[$this->stateVar][$key], $_SESSION[$key]);
+		return $value;
+	}
+
+	/**
+	 * Removes all states.
+	 * If the user logs out, all his/her user states will be removed automatically
+	 * without the need to call this method manually.
+	 *
+	 * Note that states and normal session variables share the same name space.
+	 * If you have a normal session variable using the same name, it will be removed
+	 * by this method.
+	 */
+	public function removeAllStates()
+	{
+		$manifest = isset($_SESSION[$this->stateVar]) ? $_SESSION[$this->stateVar] : null;
+		if (is_array($manifest)) {
+			foreach (array_keys($manifest) as $key) {
+				unset($_SESSION[$key]);
+			}			
+		}		
+		unset($_SESSION[$this->stateVar]);
+	}
+
+	/**
+	 * Returns a value indicating whether there is a state associated with the specified key.
+	 * @param string $key key identifying the state
+	 * @return boolean whether the specified state exists
+	 */
+	public function hasState($key)
+	{
+		return $this->getState($key) !== null;
+	}
+}
diff --git a/framework/web/UserEvent.php b/framework/web/UserEvent.php
new file mode 100644
index 0000000..3a8723a
--- /dev/null
+++ b/framework/web/UserEvent.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+use yii\base\Event;
+
+/**
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class UserEvent extends Event
+{
+	/**
+	 * @var Identity the identity object associated with this event
+	 */
+	public $identity;
+	/**
+	 * @var boolean whether the login is cookie-based. This property is only meaningful
+	 * for [[User::EVENT_BEFORE_LOGIN]] and [[User::EVENT_AFTER_LOGIN]] events.
+	 */
+	public $fromCookie;
+	/**
+	 * @var boolean whether the login or logout should proceed.
+	 * Event handlers may modify this property to determine whether the login or logout should proceed.
+	 * This property is only meaningful for [[User::EVENT_BEFORE_LOGIN]] and [[User::EVENT_BEFORE_LOGOUT]] events.
+	 */
+	public $isValid;
+}
\ No newline at end of file
diff --git a/framework/widgets/ActiveForm.php b/framework/widgets/ActiveForm.php
new file mode 100644
index 0000000..2c965e7
--- /dev/null
+++ b/framework/widgets/ActiveForm.php
@@ -0,0 +1,278 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\widgets;
+
+use Yii;
+use yii\base\InvalidParamException;
+use yii\base\Widget;
+use yii\base\Model;
+use yii\helpers\Html;
+use yii\helpers\ArrayHelper;
+
+/**
+ * ActiveForm ...
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class ActiveForm extends Widget
+{
+	/**
+	 * @param array|string $action the form action URL. This parameter will be processed by [[\yii\helpers\Html::url()]].
+	 */
+	public $action = '';
+	/**
+	 * @var string the form submission method. This should be either 'post' or 'get'.
+	 * Defaults to 'post'.
+	 */
+	public $method = 'post';
+	/**
+	 * @var string the default CSS class for the error summary container.
+	 * @see errorSummary()
+	 */
+	public $errorSummaryClass = 'yii-error-summary';
+	public $errorMessageClass = 'yii-error-message';
+	/**
+	 * @var string the default CSS class that indicates an input has error.
+	 * This is
+	 */
+	public $errorClass = 'yii-error';
+	public $successClass = 'yii-success';
+	public $validatingClass = 'yii-validating';
+	/**
+	 * @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}).
+	 */
+	public $enableClientValidation = false;
+
+	public $options = array();
+	/**
+	 * @var array model-class mapped to name prefix
+	 */
+	public $modelMap;
+
+	/**
+	 * @param Model|Model[] $models
+	 * @param array $options
+	 * @return string
+	 */
+	public function errorSummary($models, $options = array())
+	{
+		if (!is_array($models)) {
+			$models = array($models);
+		}
+
+		$showAll = isset($options['showAll']) && $options['showAll'];
+		$lines = array();
+		/** @var $model Model */
+		foreach ($models as $model) {
+			if ($showAll) {
+				foreach ($model->getErrors() as $errors) {
+					$lines = array_merge($lines, $errors);
+				}
+			} else {
+				$lines = array_merge($lines, $model->getFirstErrors());
+			}
+		}
+
+		$header = isset($options['header']) ? $options['header'] : '<p>' . Yii::t('yii|Please fix the following errors:') . '</p>';
+		$footer = isset($options['footer']) ? $options['footer'] : '';
+		$tag = isset($options['tag']) ? $options['tag'] : 'div';
+		unset($options['showAll'], $options['header'], $options['footer'], $options['container']);
+
+		if (!isset($options['class'])) {
+			$options['class'] = $this->errorSummaryClass;
+		} else {
+			$options['class'] .= ' ' . $this->errorSummaryClass;
+		}
+
+		if ($lines !== array()) {
+			$content = "<ul><li>" . implode("</li>\n<li>", ArrayHelper::htmlEncode($lines)) . "</li><ul>";
+			return Html::tag($tag, $header . $content . $footer, $options);
+		} else {
+			$content = "<ul></ul>";
+			$options['style'] = isset($options['style']) ? rtrim($options['style'], ';') . '; display:none' : 'display:none';
+			return Html::tag($tag, $header . $content . $footer, $options);
+		}
+	}
+
+	/**
+	 * @param Model $model
+	 * @param string $attribute
+	 * @param array $options
+	 * @return string
+	 */
+	public function error($model, $attribute, $options = array())
+	{
+		$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($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())
+	{
+		$value = $this->getAttributeValue($model, $attribute);
+		$name = $this->getInputName($model, $attribute);
+		return Html::input($type, $name, $value, $options);
+	}
+
+	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())
+	{
+		$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.');
+		}
+	}
+}
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 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\widgets;
+
+use Yii;
+use yii\base\Widget;
+use yii\base\View;
+
+/**
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @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..4c3ae70
--- /dev/null
+++ b/framework/widgets/ContentDecorator.php
@@ -0,0 +1,59 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\widgets;
+
+use Yii;
+use yii\base\InvalidConfigException;
+use yii\base\Widget;
+use yii\base\View;
+
+/**
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class ContentDecorator extends Widget
+{
+	/**
+	 * @var View the view object for rendering [[viewName]]. If not set, the view registered with the application
+	 * will be used.
+	 */
+	public $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 $viewName;
+	/**
+	 * @var array the parameters (name=>value) to be extracted and made available in the decorative view.
+	 */
+	public $params = array();
+
+	/**
+	 * Starts recording a clip.
+	 */
+	public function init()
+	{
+		if ($this->viewName === null) {
+			throw new InvalidConfigException('ContentDecorator::viewName must be set.');
+		}
+		ob_start();
+		ob_implicit_flush(false);
+	}
+
+	/**
+	 * Ends recording a clip.
+	 * This method stops output buffering and saves the rendering result as a named clip in the controller.
+	 */
+	public function run()
+	{
+		$params = $this->params;
+		$params['content'] = ob_get_clean();
+		$view = $this->view !== null ? $this->view : Yii::$app->getView();
+		echo $view->render($this->viewName, $params);
+	}
+}
diff --git a/framework/widgets/FragmentCache.php b/framework/widgets/FragmentCache.php
new file mode 100644
index 0000000..65bb86b
--- /dev/null
+++ b/framework/widgets/FragmentCache.php
@@ -0,0 +1,184 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\widgets;
+
+use Yii;
+use yii\base\InvalidConfigException;
+use yii\base\Widget;
+use yii\caching\Cache;
+use yii\caching\Dependency;
+
+/**
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class FragmentCache extends Widget
+{
+	/**
+	 * @var Cache|string the cache object or the application component ID of the cache object.
+	 * After the FragmentCache object is created, if you want to change this property,
+	 * you should only assign it with a cache object.
+	 */
+	public $cache = '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;
+	/**
+	 * @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;
+	/**
+	 * @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;
+
+	/**
+	 * Initializes the FragmentCache object.
+	 */
+	public function init()
+	{
+		parent::init();
+
+		if ($this->view === null) {
+			$this->view = Yii::$app->getView();
+		}
+
+		if (!$this->enabled) {
+			$this->cache = null;
+		} elseif (is_string($this->cache)) {
+			$this->cache = Yii::$app->getComponent($this->cache);
+		}
+
+		if ($this->getCachedContent() === false) {
+			$this->view->cacheStack[] = $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 (($content = $this->getCachedContent()) !== false) {
+			echo $content;
+		} elseif ($this->cache instanceof Cache) {
+			$content = ob_get_clean();
+			array_pop($this->view->cacheStack);
+			if (is_array($this->dependency)) {
+				$this->dependency = Yii::createObject($this->dependency);
+			}
+			$data = array($content, $this->dynamicPlaceholders);
+			$this->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;
+		}
+	}
+
+	/**
+	 * @var string|boolean the cached content. False if the content is not cached.
+	 */
+	private $_content;
+
+	/**
+	 * Returns the cached content if available.
+	 * @return string|boolean the cached content. False is returned if valid content is not found in the cache.
+	 */
+	public function getCachedContent()
+	{
+		if ($this->_content === null) {
+			$this->_content = false;
+			if ($this->cache instanceof Cache) {
+				$key = $this->calculateKey();
+				$data = $this->cache->get($key);
+				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) {
+							$this->view->addDynamicPlaceholder($name, $statements);
+						}
+					}
+					$this->_content = $content;
+				}
+			}
+		}
+		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]].
+	 * @return string a valid cache key
+	 */
+	protected function calculateKey()
+	{
+		$factors = array(__CLASS__, $this->getId());
+		if (is_array($this->variations)) {
+			foreach ($this->variations as $factor) {
+				$factors[] = $factor;
+			}
+		}
+		return $this->cache->buildKey($factors);
+	}
+}
\ No newline at end of file
diff --git a/framework/yii.php b/framework/yii.php
index 4e7aa5c..828dc4f 100644
--- a/framework/yii.php
+++ b/framework/yii.php
@@ -3,7 +3,7 @@
  * Yii bootstrap file.
  *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
diff --git a/framework/yiic b/framework/yiic
index bea1efb..d35d262 100755
--- a/framework/yiic
+++ b/framework/yiic
@@ -6,7 +6,7 @@
  * This is the bootstrap script for running yiic on Unix/Linux.
  *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
diff --git a/framework/yiic.php b/framework/yiic.php
index 55b9e60..0db69bb 100644
--- a/framework/yiic.php
+++ b/framework/yiic.php
@@ -3,7 +3,7 @@
  * Yii console bootstrap file.
  *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
diff --git a/tests/unit/.gitignore b/tests/unit/.gitignore
new file mode 100644
index 0000000..34651d7
--- /dev/null
+++ b/tests/unit/.gitignore
@@ -0,0 +1 @@
+runtime/cache/*
\ No newline at end of file
diff --git a/tests/unit/bootstrap.php b/tests/unit/bootstrap.php
index f60eee0..4a388c6 100644
--- a/tests/unit/bootstrap.php
+++ b/tests/unit/bootstrap.php
@@ -9,4 +9,4 @@ require_once(__DIR__ . '/../../framework/yii.php');
 
 Yii::setAlias('@yiiunit', __DIR__);
 
-require_once(__DIR__ . '/TestCase.php');
\ No newline at end of file
+require_once(__DIR__ . '/TestCase.php');
diff --git a/tests/unit/data/ar/ActiveRecord.php b/tests/unit/data/ar/ActiveRecord.php
index 328a597..95346de 100644
--- a/tests/unit/data/ar/ActiveRecord.php
+++ b/tests/unit/data/ar/ActiveRecord.php
@@ -1,9 +1,7 @@
 <?php
 /**
- * ActiveRecord class file.
- *
  * @link http://www.yiiframework.com/
- * @copyright Copyright &copy; 2008 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
  * @license http://www.yiiframework.com/license/
  */
 
diff --git a/tests/unit/data/base/InvalidRulesModel.php b/tests/unit/data/base/InvalidRulesModel.php
new file mode 100644
index 0000000..f5a8438
--- /dev/null
+++ b/tests/unit/data/base/InvalidRulesModel.php
@@ -0,0 +1,17 @@
+<?php
+namespace yiiunit\data\base;
+use yii\base\Model;
+
+/**
+ * InvalidRulesModel
+ */
+class InvalidRulesModel extends Model
+{
+	public function rules()
+	{
+		return array(
+			array('test'),
+		);
+	}
+
+}
diff --git a/tests/unit/data/base/Singer.php b/tests/unit/data/base/Singer.php
new file mode 100644
index 0000000..3305b98
--- /dev/null
+++ b/tests/unit/data/base/Singer.php
@@ -0,0 +1,21 @@
+<?php
+namespace yiiunit\data\base;
+use yii\base\Model;
+
+/**
+ * Singer
+ */
+class Singer extends Model
+{
+	public $fistName;
+	public $lastName;
+
+	public function rules()
+	{
+		return array(
+			array('lastName', 'default', 'value' => 'Lennon'),
+			array('lastName', 'required'),
+			array('underscore_style', 'yii\validators\CaptchaValidator'),
+		);
+	}
+}
\ No newline at end of file
diff --git a/tests/unit/data/base/Speaker.php b/tests/unit/data/base/Speaker.php
new file mode 100644
index 0000000..93dd496
--- /dev/null
+++ b/tests/unit/data/base/Speaker.php
@@ -0,0 +1,39 @@
+<?php
+namespace yiiunit\data\base;
+use yii\base\Model;
+
+/**
+ * Speaker
+ */
+class Speaker extends Model
+{
+	public $firstName;
+	public $lastName;
+
+	public $customLabel;
+	public $underscore_style;
+
+	protected $protectedProperty;
+	private $_privateProperty;
+
+	public function attributeLabels()
+	{
+		return array(
+			'customLabel' => 'This is the custom label',
+		);
+	}
+
+	public function rules()
+	{
+		return array(
+
+		);
+	}
+
+	public function scenarios()
+	{
+		return array(
+			'test' => array('firstName', 'lastName', '!underscore_style'),
+		);
+	}
+}
diff --git a/tests/unit/framework/YiiBaseTest.php b/tests/unit/framework/YiiBaseTest.php
new file mode 100644
index 0000000..df12bf9
--- /dev/null
+++ b/tests/unit/framework/YiiBaseTest.php
@@ -0,0 +1,26 @@
+<?php
+namespace yiiunit\framework;
+
+use yiiunit\TestCase;
+
+/**
+ * YiiBaseTest
+ */
+class YiiBaseTest extends TestCase
+{
+	public function testAlias()
+	{
+
+	}
+
+	public function testGetVersion()
+	{
+		echo \Yii::getVersion();
+		$this->assertTrue((boolean)preg_match('~\d+\.\d+(?:\.\d+)?(?:-\w+)?~', \Yii::getVersion()));
+	}
+
+	public function testPowered()
+	{
+		$this->assertTrue(is_string(\Yii::powered()));
+	}
+}
diff --git a/tests/unit/framework/base/BehaviorTest.php b/tests/unit/framework/base/BehaviorTest.php
index 4b4817b..11fbe7f 100644
--- a/tests/unit/framework/base/BehaviorTest.php
+++ b/tests/unit/framework/base/BehaviorTest.php
@@ -2,12 +2,16 @@
 
 namespace yiiunit\framework\base;
 
-class BarClass extends \yii\base\Component
+use yii\base\Behavior;
+use yii\base\Component;
+use yiiunit\TestCase;
+
+class BarClass extends Component
 {
 
 }
 
-class FooClass extends \yii\base\Component
+class FooClass extends Component
 {
 	public function behaviors()
 	{
@@ -17,7 +21,7 @@ class FooClass extends \yii\base\Component
 	}
 }
 
-class BarBehavior extends \yii\base\Behavior
+class BarBehavior extends Behavior
 {
 	public $behaviorProperty = 'behavior property';
 
@@ -27,7 +31,7 @@ class BarBehavior extends \yii\base\Behavior
 	}
 }
 
-class BehaviorTest extends \yiiunit\TestCase
+class BehaviorTest extends TestCase
 {
 	public function testAttachAndAccessing()
 	{
@@ -38,6 +42,10 @@ class BehaviorTest extends \yiiunit\TestCase
 		$this->assertEquals('behavior method', $bar->behaviorMethod());
 		$this->assertEquals('behavior property', $bar->getBehavior('bar')->behaviorProperty);
 		$this->assertEquals('behavior method', $bar->getBehavior('bar')->behaviorMethod());
+
+		$behavior = new BarBehavior(array('behaviorProperty' => 'reattached'));
+		$bar->attachBehavior('bar', $behavior);
+		$this->assertEquals('reattached', $bar->behaviorProperty);
 	}
 
 	public function testAutomaticAttach()
diff --git a/tests/unit/framework/base/ComponentTest.php b/tests/unit/framework/base/ComponentTest.php
index 3a4bab2..97b0116 100644
--- a/tests/unit/framework/base/ComponentTest.php
+++ b/tests/unit/framework/base/ComponentTest.php
@@ -1,7 +1,11 @@
 <?php
-
 namespace yiiunit\framework\base;
 
+use yii\base\Behavior;
+use yii\base\Component;
+use yii\base\Event;
+use yiiunit\TestCase;
+
 function globalEventHandler($event)
 {
 	$event->sender->eventHandled = true;
@@ -13,7 +17,7 @@ function globalEventHandler2($event)
 	$event->handled = true;
 }
 
-class ComponentTest extends \yiiunit\TestCase
+class ComponentTest extends TestCase
 {
 	/**
 	 * @var NewComponent
@@ -29,6 +33,21 @@ class ComponentTest extends \yiiunit\TestCase
 	{
 		$this->component = null;
 	}
+
+	public function testClone()
+	{
+		$component = new NewComponent();
+		$behavior = new NewBehavior();
+		$component->attachBehavior('a', $behavior);
+		$this->assertSame($behavior, $component->getBehavior('a'));
+		$component->on('test', 'fake');
+		$this->assertEquals(1, $component->getEventHandlers('test')->count);
+
+		$clone = clone $component;
+		$this->assertNotSame($component, $clone);
+		$this->assertNull($clone->getBehavior('a'));
+		$this->assertEquals(0, $clone->getEventHandlers('test')->count);
+	}
 	
 	public function testHasProperty()
 	{
@@ -59,6 +78,13 @@ class ComponentTest extends \yiiunit\TestCase
 		$this->assertTrue($this->component->canSetProperty('content'));
 		$this->assertFalse($this->component->canSetProperty('content', false));
 		$this->assertFalse($this->component->canSetProperty('Content'));
+
+		// behavior
+		$this->assertFalse($this->component->canSetProperty('p2'));
+		$behavior = new NewBehavior();
+		$this->component->attachBehavior('a', $behavior);
+		$this->assertTrue($this->component->canSetProperty('p2'));
+		$this->component->detachBehavior('a');
 	}
 
 	public function testGetProperty()
@@ -89,6 +115,18 @@ class ComponentTest extends \yiiunit\TestCase
 		$this->component->Text = null;
 		$this->assertFalse(isset($this->component->Text));
 		$this->assertTrue(empty($this->component->Text));
+
+
+		$this->assertFalse(isset($this->component->p2));
+		$this->component->attachBehavior('a', new NewBehavior());
+		$this->component->setP2('test');
+		$this->assertTrue(isset($this->component->p2));
+	}
+
+	public function testCallUnknownMethod()
+	{
+		$this->setExpectedException('yii\base\UnknownMethodException');
+		$this->component->unknownMethod();
 	}
 
 	public function testUnset()
@@ -96,6 +134,19 @@ class ComponentTest extends \yiiunit\TestCase
 		unset($this->component->Text);
 		$this->assertFalse(isset($this->component->Text));
 		$this->assertTrue(empty($this->component->Text));
+
+		$this->component->attachBehavior('a', new NewBehavior());
+		$this->component->setP2('test');
+		$this->assertEquals('test', $this->component->getP2());
+
+		unset($this->component->p2);
+		$this->assertNull($this->component->getP2());
+	}
+
+	public function testUnsetReadonly()
+	{
+		$this->setExpectedException('yii\base\InvalidCallException');
+		unset($this->component->object);
 	}
 
 	public function testOn()
@@ -147,6 +198,14 @@ class ComponentTest extends \yiiunit\TestCase
 		});
 		$this->component->raiseEvent();
 		$this->assertTrue($eventRaised);
+
+		// raise event w/o parameters
+		$eventRaised = false;
+		$this->component->on('test', function($event) use (&$eventRaised) {
+			$eventRaised = true;
+		});
+		$this->component->trigger('test');
+		$this->assertTrue($eventRaised);
 	}
 
 	public function testHasEventHandlers()
@@ -193,9 +252,57 @@ class ComponentTest extends \yiiunit\TestCase
 		$component->test();
 		$this->assertTrue($component->behaviorCalled);
 	}
+
+	public function testAttachBehaviors()
+	{
+		$component = new NewComponent;
+		$this->assertNull($component->getBehavior('a'));
+		$this->assertNull($component->getBehavior('b'));
+
+		$behavior = new NewBehavior;
+
+		$component->attachBehaviors(array(
+			'a' => $behavior,
+			'b' => $behavior,
+		));
+
+		$this->assertSame(array('a' => $behavior, 'b' => $behavior), $component->getBehaviors());
+	}
+
+	public function testDetachBehavior()
+	{
+		$component = new NewComponent;
+		$behavior = new NewBehavior;
+
+		$component->attachBehavior('a', $behavior);
+		$this->assertSame($behavior, $component->getBehavior('a'));
+
+		$detachedBehavior = $component->detachBehavior('a');
+		$this->assertSame($detachedBehavior, $behavior);
+		$this->assertNull($component->getBehavior('a'));
+
+		$detachedBehavior = $component->detachBehavior('z');
+		$this->assertNull($detachedBehavior);
+	}
+
+	public function testDetachBehaviors()
+	{
+		$component = new NewComponent;
+		$behavior = new NewBehavior;
+
+		$component->attachBehavior('a', $behavior);
+		$this->assertSame($behavior, $component->getBehavior('a'));
+		$component->attachBehavior('b', $behavior);
+		$this->assertSame($behavior, $component->getBehavior('b'));
+
+		$component->detachBehaviors();
+		$this->assertNull($component->getBehavior('a'));
+		$this->assertNull($component->getBehavior('b'));
+
+	}
 }
 
-class NewComponent extends \yii\base\Component
+class NewComponent extends Component
 {
 	private $_object = null;
 	private $_text = 'default';
@@ -245,13 +352,24 @@ class NewComponent extends \yii\base\Component
 
 	public function raiseEvent()
 	{
-		$this->trigger('click', new \yii\base\Event($this));
+		$this->trigger('click', new Event);
 	}
 }
 
-class NewBehavior extends \yii\base\Behavior
+class NewBehavior extends Behavior
 {
 	public $p;
+	private $p2;
+
+	public function getP2()
+	{
+		return $this->p2;
+	}
+
+	public function setP2($value)
+	{
+		$this->p2 = $value;
+	}
 
 	public function test()
 	{
@@ -260,7 +378,7 @@ class NewBehavior extends \yii\base\Behavior
 	}
 }
 
-class NewComponent2 extends \yii\base\Component
+class NewComponent2 extends Component
 {
 	public $a;
 	public $b;
diff --git a/tests/unit/framework/base/DictionaryTest.php b/tests/unit/framework/base/DictionaryTest.php
index 7828300..9e55547 100644
--- a/tests/unit/framework/base/DictionaryTest.php
+++ b/tests/unit/framework/base/DictionaryTest.php
@@ -61,29 +61,37 @@ class DictionaryTest extends \yiiunit\TestCase
 	{
 		$this->dictionary->add('key3',$this->item3);
 		$this->assertEquals(3,$this->dictionary->getCount());
-		$this->assertTrue($this->dictionary->contains('key3'));
+		$this->assertTrue($this->dictionary->has('key3'));
+
+		$this->dictionary[] = 'test';
 	}
 
 	public function testRemove()
 	{
 		$this->dictionary->remove('key1');
 		$this->assertEquals(1,$this->dictionary->getCount());
-		$this->assertTrue(!$this->dictionary->contains('key1'));
+		$this->assertTrue(!$this->dictionary->has('key1'));
 		$this->assertTrue($this->dictionary->remove('unknown key')===null);
 	}
 
-	public function testClear()
+	public function testRemoveAll()
 	{
-		$this->dictionary->clear();
+		$this->dictionary->add('key3',$this->item3);
+		$this->dictionary->removeAll();
+		$this->assertEquals(0,$this->dictionary->getCount());
+		$this->assertTrue(!$this->dictionary->has('key1') && !$this->dictionary->has('key2'));
+
+		$this->dictionary->add('key3',$this->item3);
+		$this->dictionary->removeAll(true);
 		$this->assertEquals(0,$this->dictionary->getCount());
-		$this->assertTrue(!$this->dictionary->contains('key1') && !$this->dictionary->contains('key2'));
+		$this->assertTrue(!$this->dictionary->has('key1') && !$this->dictionary->has('key2'));
 	}
 
-	public function testContains()
+	public function testHas()
 	{
-		$this->assertTrue($this->dictionary->contains('key1'));
-		$this->assertTrue($this->dictionary->contains('key2'));
-		$this->assertFalse($this->dictionary->contains('key3'));
+		$this->assertTrue($this->dictionary->has('key1'));
+		$this->assertTrue($this->dictionary->has('key2'));
+		$this->assertFalse($this->dictionary->has('key3'));
 	}
 
 	public function testFromArray()
@@ -95,7 +103,7 @@ class DictionaryTest extends \yiiunit\TestCase
 		$this->assertEquals($this->item3, $this->dictionary['key3']);
 		$this->assertEquals($this->item1, $this->dictionary['key4']);
 
-		$this->setExpectedException('yii\base\InvalidCallException');
+		$this->setExpectedException('yii\base\InvalidParamException');
 		$this->dictionary->copyFrom($this);
 	}
 
@@ -114,7 +122,7 @@ class DictionaryTest extends \yiiunit\TestCase
 		$this->assertEquals(3,$this->dictionary->getCount());
 		$this->assertEquals($this->item1,$this->dictionary['key2']);
 		$this->assertEquals($this->item3,$this->dictionary['key3']);
-		$this->setExpectedException('yii\base\InvalidCallException');
+		$this->setExpectedException('yii\base\InvalidParamException');
 		$this->dictionary->mergeWith($this,false);
 	}
 
@@ -154,7 +162,7 @@ class DictionaryTest extends \yiiunit\TestCase
 
 		unset($this->dictionary['key2']);
 		$this->assertEquals(2,$this->dictionary->getCount());
-		$this->assertTrue(!$this->dictionary->contains('key2'));
+		$this->assertTrue(!$this->dictionary->has('key2'));
 
 		unset($this->dictionary['unknown key']);
 	}
diff --git a/tests/unit/framework/base/ModelTest.php b/tests/unit/framework/base/ModelTest.php
new file mode 100644
index 0000000..aa15230
--- /dev/null
+++ b/tests/unit/framework/base/ModelTest.php
@@ -0,0 +1,203 @@
+<?php
+
+namespace yiiunit\framework\base;
+use yii\base\Model;
+use yiiunit\TestCase;
+use yiiunit\data\base\Speaker;
+use yiiunit\data\base\Singer;
+use yiiunit\data\base\InvalidRulesModel;
+
+/**
+ * ModelTest
+ */
+class ModelTest extends TestCase
+{
+	public function testGetAttributeLalel()
+	{
+		$speaker = new Speaker();
+		$this->assertEquals('First Name', $speaker->getAttributeLabel('firstName'));
+		$this->assertEquals('This is the custom label', $speaker->getAttributeLabel('customLabel'));
+		$this->assertEquals('Underscore Style', $speaker->getAttributeLabel('underscore_style'));
+	}
+
+	public function testGetAttributes()
+	{
+		$speaker = new Speaker();
+		$speaker->firstName = 'Qiang';
+		$speaker->lastName = 'Xue';
+
+		$this->assertEquals(array(
+			'firstName' => 'Qiang',
+			'lastName' => 'Xue',
+			'customLabel' => null,
+			'underscore_style' => null,
+		), $speaker->getAttributes());
+
+		$this->assertEquals(array(
+			'firstName' => 'Qiang',
+			'lastName' => 'Xue',
+		), $speaker->getAttributes(array('firstName', 'lastName')));
+
+		$this->assertEquals(array(
+			'firstName' => 'Qiang',
+			'lastName' => 'Xue',
+		), $speaker->getAttributes(null, array('customLabel', 'underscore_style')));
+
+		$this->assertEquals(array(
+			'firstName' => 'Qiang',
+		), $speaker->getAttributes(array('firstName', 'lastName'), array('lastName', 'customLabel', 'underscore_style')));
+	}
+
+	public function testSetAttributes()
+	{
+		// by default mass assignment doesn't work at all
+		$speaker = new Speaker();
+		$speaker->setAttributes(array('firstName' => 'Qiang', 'underscore_style' => 'test'));
+		$this->assertNull($speaker->firstName);
+		$this->assertNull($speaker->underscore_style);
+
+		// in the test scenario
+		$speaker = new Speaker();
+		$speaker->setScenario('test');
+		$speaker->setAttributes(array('firstName' => 'Qiang', 'underscore_style' => 'test'));
+		$this->assertNull($speaker->underscore_style);
+		$this->assertEquals('Qiang', $speaker->firstName);
+
+		$speaker->setAttributes(array('firstName' => 'Qiang', 'underscore_style' => 'test'), false);
+		$this->assertEquals('test', $speaker->underscore_style);
+		$this->assertEquals('Qiang', $speaker->firstName);
+	}
+
+	public function testActiveAttributes()
+	{
+		// by default mass assignment doesn't work at all
+		$speaker = new Speaker();
+		$this->assertEmpty($speaker->activeAttributes());
+
+		$speaker = new Speaker();
+		$speaker->setScenario('test');
+		$this->assertEquals(array('firstName', 'lastName', 'underscore_style'), $speaker->activeAttributes());
+	}
+
+	public function testIsAttributeSafe()
+	{
+		// by default mass assignment doesn't work at all
+		$speaker = new Speaker();
+		$this->assertFalse($speaker->isAttributeSafe('firstName'));
+
+		$speaker = new Speaker();
+		$speaker->setScenario('test');
+		$this->assertTrue($speaker->isAttributeSafe('firstName'));
+
+	}
+
+	public function testErrors()
+	{
+		$speaker = new Speaker();
+
+		$this->assertEmpty($speaker->getErrors());
+		$this->assertEmpty($speaker->getErrors('firstName'));
+		$this->assertEmpty($speaker->getFirstErrors());
+
+		$this->assertFalse($speaker->hasErrors());
+		$this->assertFalse($speaker->hasErrors('firstName'));
+
+		$speaker->addError('firstName', 'Something is wrong!');
+		$this->assertEquals(array('firstName' => array('Something is wrong!')), $speaker->getErrors());
+		$this->assertEquals(array('Something is wrong!'), $speaker->getErrors('firstName'));
+
+		$speaker->addError('firstName', 'Totally wrong!');
+		$this->assertEquals(array('firstName' => array('Something is wrong!', 'Totally wrong!')), $speaker->getErrors());
+		$this->assertEquals(array('Something is wrong!', 'Totally wrong!'), $speaker->getErrors('firstName'));
+
+		$this->assertTrue($speaker->hasErrors());
+		$this->assertTrue($speaker->hasErrors('firstName'));
+		$this->assertFalse($speaker->hasErrors('lastName'));
+
+		$this->assertEquals(array('Something is wrong!'), $speaker->getFirstErrors());
+		$this->assertEquals('Something is wrong!', $speaker->getFirstError('firstName'));
+		$this->assertNull($speaker->getFirstError('lastName'));
+
+		$speaker->addError('lastName', 'Another one!');
+		$this->assertEquals(array(
+			'firstName' => array(
+				'Something is wrong!',
+				'Totally wrong!',
+			),
+			'lastName' => array('Another one!'),
+		), $speaker->getErrors());
+
+		$speaker->clearErrors('firstName');
+		$this->assertEquals(array(
+			'lastName' => array('Another one!'),
+		), $speaker->getErrors());
+
+		$speaker->clearErrors();
+		$this->assertEmpty($speaker->getErrors());
+		$this->assertFalse($speaker->hasErrors());
+	}
+
+	public function testArraySyntax()
+	{
+		$speaker = new Speaker();
+
+		// get
+		$this->assertNull($speaker['firstName']);
+
+		// isset
+		$this->assertFalse(isset($speaker['firstName']));
+
+		// set
+		$speaker['firstName'] = 'Qiang';
+
+		$this->assertEquals('Qiang', $speaker['firstName']);
+		$this->assertTrue(isset($speaker['firstName']));
+
+		// iteration
+		$attributes = array();
+		foreach($speaker as $key => $attribute) {
+			$attributes[$key] = $attribute;
+		}
+		$this->assertEquals(array(
+			'firstName' => 'Qiang',
+			'lastName' => null,
+			'customLabel' => null,
+			'underscore_style' => null,
+		), $attributes);
+
+		// unset
+		unset($speaker['firstName']);
+
+		// exception isn't expected here
+		$this->assertNull($speaker['firstName']);
+		$this->assertFalse(isset($speaker['firstName']));
+	}
+
+	public function testDefaults()
+	{
+		$singer = new Model();
+		$this->assertEquals(array(), $singer->rules());
+		$this->assertEquals(array(), $singer->attributeLabels());
+	}
+
+	public function testDefaultScenarios()
+	{
+		$singer = new Singer();
+		$this->assertEquals(array('default' => array('lastName', 'underscore_style')), $singer->scenarios());
+	}
+
+	public function testIsAttributeRequired()
+	{
+		$singer = new Singer();
+		$this->assertFalse($singer->isAttributeRequired('firstName'));
+		$this->assertTrue($singer->isAttributeRequired('lastName'));
+	}
+
+	public function testCreateValidators()
+	{
+		$this->setExpectedException('yii\base\InvalidConfigException', 'Invalid validation rule: a rule must be an array specifying both attribute names and validator type.');
+
+		$invalid = new InvalidRulesModel();
+		$invalid->createValidators();
+	}
+}
diff --git a/tests/unit/framework/base/ObjectTest.php b/tests/unit/framework/base/ObjectTest.php
index f93b4af..b47b178 100644
--- a/tests/unit/framework/base/ObjectTest.php
+++ b/tests/unit/framework/base/ObjectTest.php
@@ -1,11 +1,13 @@
 <?php
-
 namespace yiiunit\framework\base;
 
+use yii\base\Object;
+use yiiunit\TestCase;
+
 /**
  * ObjectTest
  */
-class ObjectTest extends \yiiunit\TestCase
+class ObjectTest extends TestCase
 {
 	/**
 	 * @var NewObject
@@ -69,6 +71,12 @@ class ObjectTest extends \yiiunit\TestCase
 		$this->object->NewMember = $value;
 	}
 
+	public function testSetReadOnlyProperty()
+	{
+		$this->setExpectedException('yii\base\InvalidCallException');
+		$this->object->object = 'test';
+	}
+
 	public function testIsset()
 	{
 		$this->assertTrue(isset($this->object->Text));
@@ -81,6 +89,9 @@ class ObjectTest extends \yiiunit\TestCase
 		$this->object->Text = null;
 		$this->assertFalse(isset($this->object->Text));
 		$this->assertTrue(empty($this->object->Text));
+
+		$this->assertFalse(isset($this->object->unknownProperty));
+		$this->assertTrue(empty($this->object->unknownProperty));
 	}
 
 	public function testUnset()
@@ -90,6 +101,18 @@ class ObjectTest extends \yiiunit\TestCase
 		$this->assertTrue(empty($this->object->Text));
 	}
 
+	public function testUnsetReadOnlyProperty()
+	{
+		$this->setExpectedException('yii\base\InvalidCallException');
+		unset($this->object->object);
+	}
+
+	public function testCallUnknownMethod()
+	{
+		$this->setExpectedException('yii\base\UnknownMethodException');
+		$this->object->unknownMethod();
+	}
+
 	public function testArrayProperty()
 	{
 		$this->assertEquals(array(), $this->object->items);
@@ -112,10 +135,16 @@ class ObjectTest extends \yiiunit\TestCase
 	{
 		$this->assertEquals(2, $this->object->execute(1));
 	}
+
+	public function testConstruct()
+	{
+		$object = new NewObject(array('text' => 'test text'));
+		$this->assertEquals('test text', $object->getText());
+	}
 }
 
 
-class NewObject extends \yii\base\Component
+class NewObject extends Object
 {
 	private $_object = null;
 	private $_text = 'default';
diff --git a/tests/unit/framework/base/VectorTest.php b/tests/unit/framework/base/VectorTest.php
index f7fadfd..5c44d17 100644
--- a/tests/unit/framework/base/VectorTest.php
+++ b/tests/unit/framework/base/VectorTest.php
@@ -44,6 +44,16 @@ class VectorTest extends \yiiunit\TestCase
 		$this->assertEquals(2,$vector2->getCount());
 	}
 
+	public function testItemAt()
+	{
+		$a=array(1, 2, null, 4);
+		$vector=new Vector($a);
+		$this->assertEquals(1, $vector->itemAt(0));
+		$this->assertEquals(2, $vector->itemAt(1));
+		$this->assertNull($vector->itemAt(2));
+		$this->assertEquals(4, $vector->itemAt(3));
+	}
+
 	public function testGetCount()
 	{
 		$this->assertEquals(2,$this->vector->getCount());
@@ -65,7 +75,7 @@ class VectorTest extends \yiiunit\TestCase
 		$this->assertEquals(2,$this->vector->indexOf($this->item2));
 		$this->assertEquals(0,$this->vector->indexOf($this->item3));
 		$this->assertEquals(1,$this->vector->indexOf($this->item1));
-		$this->setExpectedException('yii\base\InvalidCallException');
+		$this->setExpectedException('yii\base\InvalidParamException');
 		$this->vector->insertAt(4,$this->item3);
 	}
 
@@ -87,23 +97,30 @@ class VectorTest extends \yiiunit\TestCase
 		$this->assertEquals(-1,$this->vector->indexOf($this->item2));
 		$this->assertEquals(1,$this->vector->indexOf($this->item3));
 		$this->assertEquals(0,$this->vector->indexOf($this->item1));
-		$this->setExpectedException('yii\base\InvalidCallException');
+		$this->setExpectedException('yii\base\InvalidParamException');
 		$this->vector->removeAt(2);
 	}
 
-	public function testClear()
+	public function testRemoveAll()
 	{
-		$this->vector->clear();
+		$this->vector->add($this->item3);
+		$this->vector->removeAll();
+		$this->assertEquals(0,$this->vector->getCount());
+		$this->assertEquals(-1,$this->vector->indexOf($this->item1));
+		$this->assertEquals(-1,$this->vector->indexOf($this->item2));
+
+		$this->vector->add($this->item3);
+		$this->vector->removeAll(true);
 		$this->assertEquals(0,$this->vector->getCount());
 		$this->assertEquals(-1,$this->vector->indexOf($this->item1));
 		$this->assertEquals(-1,$this->vector->indexOf($this->item2));
 	}
 
-	public function testContains()
+	public function testHas()
 	{
-		$this->assertTrue($this->vector->contains($this->item1));
-		$this->assertTrue($this->vector->contains($this->item2));
-		$this->assertFalse($this->vector->contains($this->item3));
+		$this->assertTrue($this->vector->has($this->item1));
+		$this->assertTrue($this->vector->has($this->item2));
+		$this->assertFalse($this->vector->has($this->item3));
 	}
 
 	public function testIndexOf()
@@ -118,7 +135,7 @@ class VectorTest extends \yiiunit\TestCase
 		$array=array($this->item3,$this->item1);
 		$this->vector->copyFrom($array);
 		$this->assertTrue(count($array)==2 && $this->vector[0]===$this->item3 && $this->vector[1]===$this->item1);
-		$this->setExpectedException('yii\base\InvalidCallException');
+		$this->setExpectedException('yii\base\InvalidParamException');
 		$this->vector->copyFrom($this);
 	}
 
@@ -127,7 +144,13 @@ class VectorTest extends \yiiunit\TestCase
 		$array=array($this->item3,$this->item1);
 		$this->vector->mergeWith($array);
 		$this->assertTrue($this->vector->getCount()==4 && $this->vector[0]===$this->item1 && $this->vector[3]===$this->item1);
-		$this->setExpectedException('yii\base\InvalidCallException');
+
+		$a=array(1);
+		$vector=new Vector($a);
+		$this->vector->mergeWith($vector);
+		$this->assertTrue($this->vector->getCount()==5 && $this->vector[0]===$this->item1 && $this->vector[3]===$this->item1 && $this->vector[4]===1);
+
+		$this->setExpectedException('yii\base\InvalidParamException');
 		$this->vector->mergeWith($this);
 	}
 
@@ -141,7 +164,7 @@ class VectorTest extends \yiiunit\TestCase
 	{
 		$this->assertTrue($this->vector[0]===$this->item1);
 		$this->assertTrue($this->vector[1]===$this->item2);
-		$this->setExpectedException('yii\base\InvalidCallException');
+		$this->setExpectedException('yii\base\InvalidParamException');
 		$a=$this->vector[2];
 	}
 
diff --git a/tests/unit/framework/caching/ApcCacheTest.php b/tests/unit/framework/caching/ApcCacheTest.php
new file mode 100644
index 0000000..74ede2a
--- /dev/null
+++ b/tests/unit/framework/caching/ApcCacheTest.php
@@ -0,0 +1,27 @@
+<?php
+namespace yiiunit\framework\caching;
+use yii\caching\ApcCache;
+use yiiunit\TestCase;
+
+/**
+ * Class for testing APC cache backend
+ */
+class ApcCacheTest extends CacheTest
+{
+	private $_cacheInstance = null;
+
+	/**
+	 * @return ApcCache
+	 */
+	protected function getCacheInstance()
+	{
+		if(!extension_loaded("apc")) {
+			$this->markTestSkipped("APC not installed. Skipping.");
+		}
+
+		if($this->_cacheInstance === null) {
+			$this->_cacheInstance = new ApcCache();
+		}
+		return $this->_cacheInstance;
+	}
+}
\ No newline at end of file
diff --git a/tests/unit/framework/caching/CacheTest.php b/tests/unit/framework/caching/CacheTest.php
new file mode 100644
index 0000000..ad2fcf5
--- /dev/null
+++ b/tests/unit/framework/caching/CacheTest.php
@@ -0,0 +1,82 @@
+<?php
+namespace yiiunit\framework\caching;
+use yiiunit\TestCase;
+use yii\caching\Cache;
+
+/**
+ * Base class for testing cache backends
+ */
+abstract class CacheTest extends TestCase
+{
+	/**
+	 * @return Cache
+	 */
+	abstract protected function getCacheInstance();
+
+	public function testSet()
+	{
+		$cache = $this->getCacheInstance();
+		$cache->set('string_test', 'string_test');
+		$cache->set('number_test', 42);
+		$cache->set('array_test', array('array_test' => 'array_test'));
+		$cache['arrayaccess_test'] = new \stdClass();
+	}
+
+	public function testGet()
+	{
+		$cache = $this->getCacheInstance();
+		$this->assertEquals('string_test', $cache->get('string_test'));
+
+		$this->assertEquals(42, $cache->get('number_test'));
+
+		$array = $cache->get('array_test');
+		$this->assertArrayHasKey('array_test', $array);
+		$this->assertEquals('array_test', $array['array_test']);
+
+		$this->assertInstanceOf('stdClass', $cache['arrayaccess_test']);
+	}
+
+	public function testMget()
+	{
+		$cache = $this->getCacheInstance();
+		$this->assertEquals(array('string_test' => 'string_test', 'number_test' => 42), $cache->mget(array('string_test', 'number_test')));
+	}
+
+	public function testExpire()
+	{
+		$cache = $this->getCacheInstance();
+		$cache->set('expire_test', 'expire_test', 2);
+		sleep(1);
+		$this->assertEquals('expire_test', $cache->get('expire_test'));
+		sleep(2);
+		$this->assertEquals(false, $cache->get('expire_test'));
+	}
+
+	public function testAdd()
+	{
+		$cache = $this->getCacheInstance();
+
+		// should not change existing keys
+		$cache->add('number_test', 13);
+		$this->assertEquals(42, $cache->get('number_test'));
+
+		// should store data is it's not there yet
+		$cache->add('add_test', 13);
+		$this->assertEquals(13, $cache->get('add_test'));
+	}
+
+	public function testDelete()
+	{
+		$cache = $this->getCacheInstance();
+
+		$cache->delete('number_test');
+		$this->assertEquals(null, $cache->get('number_test'));
+	}
+
+	public function testFlush()
+	{
+		$cache = $this->getCacheInstance();
+		$cache->flush();
+		$this->assertEquals(null, $cache->get('add_test'));
+	}
+}
diff --git a/tests/unit/framework/caching/DbCacheTest.php b/tests/unit/framework/caching/DbCacheTest.php
new file mode 100644
index 0000000..3977ee8
--- /dev/null
+++ b/tests/unit/framework/caching/DbCacheTest.php
@@ -0,0 +1,70 @@
+<?php
+namespace yiiunit\framework\caching;
+use yii\caching\DbCache;
+use yiiunit\TestCase;
+
+/**
+ * Class for testing file cache backend
+ */
+class DbCacheTest extends CacheTest
+{
+	private $_cacheInstance;
+	private $_connection;
+
+	function __construct()
+	{
+		if (!extension_loaded('pdo') || !extension_loaded('pdo_mysql')) {
+			$this->markTestSkipped('pdo and pdo_mysql extensions are required.');
+		}
+
+		$this->getConnection()->createCommand("
+			CREATE TABLE IF NOT EXISTS tbl_cache (
+				id char(128) NOT NULL,
+				expire int(11) DEFAULT NULL,
+				data LONGBLOB,
+				PRIMARY KEY (id),
+				KEY expire (expire)
+			);
+		")->execute();
+	}
+
+	/**
+	 * @param bool $reset whether to clean up the test database
+	 * @return \yii\db\Connection
+	 */
+	function getConnection($reset = true)
+	{
+		if($this->_connection === null) {
+			$params = $this->getParam('mysql');
+			$db = new \yii\db\Connection;
+			$db->dsn = $params['dsn'];
+			$db->username = $params['username'];
+			$db->password = $params['password'];
+			if ($reset) {
+				$db->open();
+				$lines = explode(';', file_get_contents($params['fixture']));
+				foreach ($lines as $line) {
+					if (trim($line) !== '') {
+						$db->pdo->exec($line);
+					}
+				}
+			}
+			$this->_connection = $db;
+		}
+		return $this->_connection;
+	}
+
+
+	/**
+	 * @return DbCache
+	 */
+	protected function getCacheInstance()
+	{
+		if($this->_cacheInstance === null) {
+			$this->_cacheInstance = new DbCache(array(
+				'db' => $this->getConnection(),
+			));
+		}
+		return $this->_cacheInstance;
+	}
+}
\ No newline at end of file
diff --git a/tests/unit/framework/caching/FileCacheTest.php b/tests/unit/framework/caching/FileCacheTest.php
new file mode 100644
index 0000000..1f6debd
--- /dev/null
+++ b/tests/unit/framework/caching/FileCacheTest.php
@@ -0,0 +1,25 @@
+<?php
+namespace yiiunit\framework\caching;
+use yii\caching\FileCache;
+use yiiunit\TestCase;
+
+/**
+ * Class for testing file cache backend
+ */
+class FileCacheTest extends CacheTest
+{
+	private $_cacheInstance = null;
+
+	/**
+	 * @return FileCache
+	 */
+	protected function getCacheInstance()
+	{
+		if($this->_cacheInstance === null) {
+			$this->_cacheInstance = new FileCache(array(
+				'cachePath' => '@yiiunit/runtime/cache',
+			));
+		}
+		return $this->_cacheInstance;
+	}
+}
\ No newline at end of file
diff --git a/tests/unit/framework/caching/MemCacheTest.php b/tests/unit/framework/caching/MemCacheTest.php
new file mode 100644
index 0000000..e4804d9
--- /dev/null
+++ b/tests/unit/framework/caching/MemCacheTest.php
@@ -0,0 +1,27 @@
+<?php
+namespace yiiunit\framework\caching;
+use yii\caching\MemCache;
+use yiiunit\TestCase;
+
+/**
+ * Class for testing memcache cache backend
+ */
+class MemCacheTest extends CacheTest
+{
+	private $_cacheInstance = null;
+
+	/**
+	 * @return MemCache
+	 */
+	protected function getCacheInstance()
+	{
+		if(!extension_loaded("memcache")) {
+			$this->markTestSkipped("memcache not installed. Skipping.");
+		}
+
+		if($this->_cacheInstance === null) {
+			$this->_cacheInstance = new MemCache();
+		}
+		return $this->_cacheInstance;
+	}
+}
\ No newline at end of file
diff --git a/tests/unit/framework/caching/MemCachedTest.php b/tests/unit/framework/caching/MemCachedTest.php
new file mode 100644
index 0000000..59396df
--- /dev/null
+++ b/tests/unit/framework/caching/MemCachedTest.php
@@ -0,0 +1,29 @@
+<?php
+namespace yiiunit\framework\caching;
+use yii\caching\MemCache;
+use yiiunit\TestCase;
+
+/**
+ * Class for testing memcache cache backend
+ */
+class MemCachedTest extends CacheTest
+{
+	private $_cacheInstance = null;
+
+	/**
+	 * @return MemCache
+	 */
+	protected function getCacheInstance()
+	{
+		if(!extension_loaded("memcached")) {
+			$this->markTestSkipped("memcached not installed. Skipping.");
+		}
+
+		if($this->_cacheInstance === null) {
+			$this->_cacheInstance = new MemCache(array(
+				'useMemcached' => true,
+			));
+		}
+		return $this->_cacheInstance;
+	}
+}
\ No newline at end of file
diff --git a/tests/unit/framework/caching/WinCacheTest.php b/tests/unit/framework/caching/WinCacheTest.php
new file mode 100644
index 0000000..b78d57b
--- /dev/null
+++ b/tests/unit/framework/caching/WinCacheTest.php
@@ -0,0 +1,31 @@
+<?php
+namespace yiiunit\framework\caching;
+use yii\caching\FileCache;
+use yiiunit\TestCase;
+
+/**
+ * Class for testing wincache backend
+ */
+class WinCacheTest extends CacheTest
+{
+	private $_cacheInstance = null;
+
+	/**
+	 * @return WinCache
+	 */
+	protected function getCacheInstance()
+	{
+		if(!extension_loaded('wincache')) {
+			$this->markTestSkipped("Wincache not installed. Skipping.");
+		}
+
+		if(!ini_get('wincache.ucenabled')) {
+			$this->markTestSkipped("Wincache user cache disabled. Skipping.");
+		}
+
+		if($this->_cacheInstance === null) {
+			$this->_cacheInstance = new WinCache();
+		}
+		return $this->_cacheInstance;
+	}
+}
\ No newline at end of file
diff --git a/tests/unit/framework/caching/XCacheTest.php b/tests/unit/framework/caching/XCacheTest.php
new file mode 100644
index 0000000..e1ed844
--- /dev/null
+++ b/tests/unit/framework/caching/XCacheTest.php
@@ -0,0 +1,27 @@
+<?php
+namespace yiiunit\framework\caching;
+use yii\caching\XCache;
+use yiiunit\TestCase;
+
+/**
+ * Class for testing xcache backend
+ */
+class XCacheTest extends CacheTest
+{
+	private $_cacheInstance = null;
+
+	/**
+	 * @return XCache
+	 */
+	protected function getCacheInstance()
+	{
+		if(!function_exists("xcache_isset")) {
+			$this->markTestSkipped("XCache not installed. Skipping.");
+		}
+
+		if($this->_cacheInstance === null) {
+			$this->_cacheInstance = new XCache();
+		}
+		return $this->_cacheInstance;
+	}
+}
\ No newline at end of file
diff --git a/tests/unit/framework/caching/ZendDataCacheTest.php b/tests/unit/framework/caching/ZendDataCacheTest.php
new file mode 100644
index 0000000..91dfbb5
--- /dev/null
+++ b/tests/unit/framework/caching/ZendDataCacheTest.php
@@ -0,0 +1,27 @@
+<?php
+namespace yiiunit\framework\caching;
+use yii\caching\ZendDataCache;
+use yiiunit\TestCase;
+
+/**
+ * Class for testing Zend cache backend
+ */
+class ZendDataCacheTest extends CacheTest
+{
+	private $_cacheInstance = null;
+
+	/**
+	 * @return ZendDataCache
+	 */
+	protected function getCacheInstance()
+	{
+		if(!function_exists("zend_shm_cache_store")) {
+			$this->markTestSkipped("Zend Data cache not installed. Skipping.");
+		}
+
+		if($this->_cacheInstance === null) {
+			$this->_cacheInstance = new ZendDataCache();
+		}
+		return $this->_cacheInstance;
+	}
+}
\ No newline at end of file
diff --git a/tests/unit/framework/db/CommandTest.php b/tests/unit/framework/db/CommandTest.php
index 2a11fcb..d505f6d 100644
--- a/tests/unit/framework/db/CommandTest.php
+++ b/tests/unit/framework/db/CommandTest.php
@@ -189,14 +189,6 @@ class CommandTest extends \yiiunit\MysqlTestCase
 		$command = $db->createCommand($sql);
 		$command->bindValue(':name', 'user5');
 		$this->assertEquals('user5@example.com', $command->queryScalar());
-
-		// bind value via query or execute method
-		$sql = 'INSERT INTO tbl_customer(email, name, address) VALUES (:email, \'user6\', \'address6\')';
-		$command = $db->createCommand($sql);
-		$command->execute(array(':email' => 'user6@example.com'));
-		$sql = 'SELECT email FROM tbl_customer WHERE name=:name';
-		$command = $db->createCommand($sql);
-		$this->assertEquals('user5@example.com', $command->queryScalar(array(':name' => 'user5')));
 	}
 
 	function testFetchMode()
diff --git a/tests/unit/framework/db/QueryTest.php b/tests/unit/framework/db/QueryTest.php
index 645b844..2c4359f 100644
--- a/tests/unit/framework/db/QueryTest.php
+++ b/tests/unit/framework/db/QueryTest.php
@@ -14,13 +14,13 @@ class QueryTest extends \yiiunit\MysqlTestCase
 		// default
 		$query = new Query;
 		$query->select('*');
-		$this->assertEquals('*', $query->select);
+		$this->assertEquals(array('*'), $query->select);
 		$this->assertNull($query->distinct);
 		$this->assertEquals(null, $query->selectOption);
 
 		$query = new Query;
 		$query->select('id, name', 'something')->distinct(true);
-		$this->assertEquals('id, name', $query->select);
+		$this->assertEquals(array('id','name'), $query->select);
 		$this->assertTrue($query->distinct);
 		$this->assertEquals('something', $query->selectOption);
 	}
@@ -29,7 +29,7 @@ class QueryTest extends \yiiunit\MysqlTestCase
 	{
 		$query = new Query;
 		$query->from('tbl_user');
-		$this->assertEquals('tbl_user', $query->from);
+		$this->assertEquals(array('tbl_user'), $query->from);
 	}
 
 	function testWhere()
@@ -57,12 +57,12 @@ class QueryTest extends \yiiunit\MysqlTestCase
 	{
 		$query = new Query;
 		$query->groupBy('team');
-		$this->assertEquals('team', $query->groupBy);
+		$this->assertEquals(array('team'), $query->groupBy);
 
-		$query->addGroup('company');
+		$query->addGroupBy('company');
 		$this->assertEquals(array('team', 'company'), $query->groupBy);
 
-		$query->addGroup('age');
+		$query->addGroupBy('age');
 		$this->assertEquals(array('team', 'company', 'age'), $query->groupBy);
 	}
 
@@ -86,13 +86,19 @@ class QueryTest extends \yiiunit\MysqlTestCase
 	{
 		$query = new Query;
 		$query->orderBy('team');
-		$this->assertEquals('team', $query->orderBy);
+		$this->assertEquals(array('team' => false), $query->orderBy);
 
 		$query->addOrderBy('company');
-		$this->assertEquals(array('team', 'company'), $query->orderBy);
+		$this->assertEquals(array('team' => false, 'company' => false), $query->orderBy);
 
 		$query->addOrderBy('age');
-		$this->assertEquals(array('team', 'company', 'age'), $query->orderBy);
+		$this->assertEquals(array('team' => false, 'company' => false, 'age' => false), $query->orderBy);
+
+		$query->addOrderBy(array('age' => true));
+		$this->assertEquals(array('team' => false, 'company' => false, 'age' => true), $query->orderBy);
+
+		$query->addOrderBy('age ASC, company DESC');
+		$this->assertEquals(array('team' => false, 'company' => true, 'age' => false), $query->orderBy);
 	}
 
 	function testLimitOffset()
diff --git a/tests/unit/framework/util/ArrayHelperTest.php b/tests/unit/framework/util/ArrayHelperTest.php
index a713381..117c702 100644
--- a/tests/unit/framework/util/ArrayHelperTest.php
+++ b/tests/unit/framework/util/ArrayHelperTest.php
@@ -1,8 +1,8 @@
 <?php
 
-namespace yiiunit\framework\db;
+namespace yiiunit\framework\util;
 
-use yii\util\ArrayHelper;
+use yii\helpers\ArrayHelper;
 
 class ArrayHelperTest extends \yii\test\TestCase
 {
diff --git a/tests/unit/framework/util/HtmlTest.php b/tests/unit/framework/util/HtmlTest.php
new file mode 100644
index 0000000..eba1a20
--- /dev/null
+++ b/tests/unit/framework/util/HtmlTest.php
@@ -0,0 +1,448 @@
+<?php
+
+namespace yiiunit\framework\util;
+
+use Yii;
+use yii\helpers\Html;
+use yii\web\Application;
+
+class HtmlTest extends \yii\test\TestCase
+{
+	public function setUp()
+	{
+		new Application('test', '@yiiunit/runtime', array(
+			'components' => array(
+				'request' => array(
+					'class' => 'yii\web\Request',
+					'url' => '/test',
+				),
+			),
+		));
+	}
+
+	public function tearDown()
+	{
+		Yii::$app = null;
+	}
+
+	public function testEncode()
+	{
+		$this->assertEquals("a&lt;&gt;&amp;&quot;&#039;", Html::encode("a<>&\"'"));
+	}
+
+	public function testDecode()
+	{
+		$this->assertEquals("a<>&\"'", Html::decode("a&lt;&gt;&amp;&quot;&#039;"));
+	}
+
+	public function testTag()
+	{
+		$this->assertEquals('<br />', Html::tag('br'));
+		$this->assertEquals('<span></span>', Html::tag('span'));
+		$this->assertEquals('<div>content</div>', Html::tag('div', 'content'));
+		$this->assertEquals('<input type="text" name="test" value="&lt;&gt;" />', Html::tag('input', '', array('type' => 'text', 'name' => 'test', 'value' => '<>')));
+
+		Html::$closeVoidElements = false;
+
+		$this->assertEquals('<br>', Html::tag('br'));
+		$this->assertEquals('<span></span>', Html::tag('span'));
+		$this->assertEquals('<div>content</div>', Html::tag('div', 'content'));
+		$this->assertEquals('<input type="text" name="test" value="&lt;&gt;">', Html::tag('input', '', array('type' => 'text', 'name' => 'test', 'value' => '<>')));
+
+		Html::$closeVoidElements = true;
+
+		$this->assertEquals('<span disabled="disabled"></span>', Html::tag('span', '', array('disabled' => true)));
+		Html::$showBooleanAttributeValues = false;
+		$this->assertEquals('<span disabled></span>', Html::tag('span', '', array('disabled' => true)));
+		Html::$showBooleanAttributeValues = true;
+	}
+
+	public function testBeginTag()
+	{
+		$this->assertEquals('<br>', Html::beginTag('br'));
+		$this->assertEquals('<span id="test" class="title">', Html::beginTag('span', array('id' => 'test', 'class' => 'title')));
+	}
+
+	public function testEndTag()
+	{
+		$this->assertEquals('</br>', Html::endTag('br'));
+		$this->assertEquals('</span>', Html::endTag('span'));
+	}
+
+	public function testCdata()
+	{
+		$data = 'test<>';
+		$this->assertEquals('<![CDATA[' . $data . ']]>', Html::cdata($data));
+	}
+
+	public function testStyle()
+	{
+		$content = 'a <>';
+		$this->assertEquals("<style type=\"text/css\">/*<![CDATA[*/\n{$content}\n/*]]>*/</style>", Html::style($content));
+		$this->assertEquals("<style type=\"text/less\">/*<![CDATA[*/\n{$content}\n/*]]>*/</style>", Html::style($content, array('type' => 'text/less')));
+	}
+
+	public function testScript()
+	{
+		$content = 'a <>';
+		$this->assertEquals("<script type=\"text/javascript\">/*<![CDATA[*/\n{$content}\n/*]]>*/</script>", Html::script($content));
+		$this->assertEquals("<script type=\"text/js\">/*<![CDATA[*/\n{$content}\n/*]]>*/</script>", Html::script($content, array('type' => 'text/js')));
+	}
+
+	public function testCssFile()
+	{
+		$this->assertEquals('<link type="text/css" href="http://example.com" rel="stylesheet" />', Html::cssFile('http://example.com'));
+		$this->assertEquals('<link type="text/css" href="/test" rel="stylesheet" />', Html::cssFile(''));
+	}
+
+	public function testJsFile()
+	{
+		$this->assertEquals('<script type="text/javascript" src="http://example.com"></script>', Html::jsFile('http://example.com'));
+		$this->assertEquals('<script type="text/javascript" src="/test"></script>', Html::jsFile(''));
+	}
+
+	public function testBeginForm()
+	{
+		$this->assertEquals('<form action="/test" method="post">', Html::beginForm());
+		$this->assertEquals('<form action="/example" method="get">', Html::beginForm('/example', 'get'));
+		$hiddens = array(
+			'<input type="hidden" name="id" value="1" />',
+			'<input type="hidden" name="title" value="&lt;" />',
+		);
+		$this->assertEquals('<form action="/example" method="get">' . "\n" . implode("\n", $hiddens), Html::beginForm('/example?id=1&title=%3C', 'get'));
+	}
+
+	public function testEndForm()
+	{
+		$this->assertEquals('</form>', Html::endForm());
+	}
+
+	public function testA()
+	{
+		$this->assertEquals('<a>something<></a>', Html::a('something<>'));
+		$this->assertEquals('<a href="/example">something</a>', Html::a('something', '/example'));
+		$this->assertEquals('<a href="/test">something</a>', Html::a('something', ''));
+	}
+
+	public function testMailto()
+	{
+		$this->assertEquals('<a href="mailto:test&lt;&gt;">test<></a>', Html::mailto('test<>'));
+		$this->assertEquals('<a href="mailto:test&gt;">test<></a>', Html::mailto('test<>', 'test>'));
+	}
+
+	public function testImg()
+	{
+		$this->assertEquals('<img src="/example" alt="" />', Html::img('/example'));
+		$this->assertEquals('<img src="/test" alt="" />', Html::img(''));
+		$this->assertEquals('<img src="/example" width="10" alt="something" />', Html::img('/example', array('alt' => 'something', 'width' => 10)));
+	}
+
+	public function testLabel()
+	{
+		$this->assertEquals('<label>something<></label>', Html::label('something<>'));
+		$this->assertEquals('<label for="a">something<></label>', Html::label('something<>', 'a'));
+		$this->assertEquals('<label class="test" for="a">something<></label>', Html::label('something<>', 'a', array('class' => 'test')));
+	}
+
+	public function testButton()
+	{
+		$this->assertEquals('<button type="button">Button</button>', Html::button());
+		$this->assertEquals('<button type="button" name="test" value="value">content<></button>', Html::button('test', 'value', 'content<>'));
+		$this->assertEquals('<button type="submit" class="t" name="test" value="value">content<></button>', Html::button('test', 'value', 'content<>', array('type' => 'submit', 'class' => "t")));
+	}
+
+	public function testSubmitButton()
+	{
+		$this->assertEquals('<button type="submit">Submit</button>', Html::submitButton());
+		$this->assertEquals('<button type="submit" class="t" name="test" value="value">content<></button>', Html::submitButton('test', 'value', 'content<>', array('class' => 't')));
+	}
+
+	public function testResetButton()
+	{
+		$this->assertEquals('<button type="reset">Reset</button>', Html::resetButton());
+		$this->assertEquals('<button type="reset" class="t" name="test" value="value">content<></button>', Html::resetButton('test', 'value', 'content<>', array('class' => 't')));
+	}
+
+	public function testInput()
+	{
+		$this->assertEquals('<input type="text" />', Html::input('text'));
+		$this->assertEquals('<input type="text" class="t" name="test" value="value" />', Html::input('text', 'test', 'value', array('class' => 't')));
+	}
+
+	public function testButtonInput()
+	{
+		$this->assertEquals('<input type="button" name="test" value="Button" />', Html::buttonInput('test'));
+		$this->assertEquals('<input type="button" class="a" name="test" value="text" />', Html::buttonInput('test', 'text', array('class' => 'a')));
+	}
+
+	public function testSubmitInput()
+	{
+		$this->assertEquals('<input type="submit" value="Submit" />', Html::submitInput());
+		$this->assertEquals('<input type="submit" class="a" name="test" value="text" />', Html::submitInput('test', 'text', array('class' => 'a')));
+	}
+
+	public function testResetInput()
+	{
+		$this->assertEquals('<input type="reset" value="Reset" />', Html::resetInput());
+		$this->assertEquals('<input type="reset" class="a" name="test" value="text" />', Html::resetInput('test', 'text', array('class' => 'a')));
+	}
+
+	public function testTextInput()
+	{
+		$this->assertEquals('<input type="text" name="test" />', Html::textInput('test'));
+		$this->assertEquals('<input type="text" class="t" name="test" value="value" />', Html::textInput('test', 'value', array('class' => 't')));
+	}
+
+	public function testHiddenInput()
+	{
+		$this->assertEquals('<input type="hidden" name="test" />', Html::hiddenInput('test'));
+		$this->assertEquals('<input type="hidden" class="t" name="test" value="value" />', Html::hiddenInput('test', 'value', array('class' => 't')));
+	}
+
+	public function testPasswordInput()
+	{
+		$this->assertEquals('<input type="password" name="test" />', Html::passwordInput('test'));
+		$this->assertEquals('<input type="password" class="t" name="test" value="value" />', Html::passwordInput('test', 'value', array('class' => 't')));
+	}
+
+	public function testFileInput()
+	{
+		$this->assertEquals('<input type="file" name="test" />', Html::fileInput('test'));
+		$this->assertEquals('<input type="file" class="t" name="test" value="value" />', Html::fileInput('test', 'value', array('class' => 't')));
+	}
+
+	public function testTextarea()
+	{
+		$this->assertEquals('<textarea name="test"></textarea>', Html::textarea('test'));
+		$this->assertEquals('<textarea class="t" name="test">value&lt;&gt;</textarea>', Html::textarea('test', 'value<>', array('class' => 't')));
+	}
+
+	public function testRadio()
+	{
+		$this->assertEquals('<input type="radio" name="test" value="1" />', Html::radio('test'));
+		$this->assertEquals('<input type="radio" class="a" name="test" checked="checked" />', Html::radio('test', true, null, array('class' => 'a')));
+		$this->assertEquals('<input type="hidden" name="test" value="0" /><input type="radio" class="a" name="test" value="2" checked="checked" />', Html::radio('test', true, 2, array('class' => 'a' , 'uncheck' => '0')));
+	}
+
+	public function testCheckbox()
+	{
+		$this->assertEquals('<input type="checkbox" name="test" value="1" />', Html::checkbox('test'));
+		$this->assertEquals('<input type="checkbox" class="a" name="test" checked="checked" />', Html::checkbox('test', true, null, array('class' => 'a')));
+		$this->assertEquals('<input type="hidden" name="test" value="0" /><input type="checkbox" class="a" name="test" value="2" checked="checked" />', Html::checkbox('test', true, 2, array('class' => 'a', 'uncheck' => '0')));
+	}
+
+	public function testDropDownList()
+	{
+		$expected = <<<EOD
+<select name="test">
+
+</select>
+EOD;
+		$this->assertEquals($expected, Html::dropDownList('test'));
+		$expected = <<<EOD
+<select name="test">
+<option value="value1">text1</option>
+<option value="value2">text2</option>
+</select>
+EOD;
+		$this->assertEquals($expected, Html::dropDownList('test', null, $this->getDataItems()));
+		$expected = <<<EOD
+<select name="test">
+<option value="value1">text1</option>
+<option value="value2" selected="selected">text2</option>
+</select>
+EOD;
+		$this->assertEquals($expected, Html::dropDownList('test', 'value2', $this->getDataItems()));
+	}
+
+	public function testListBox()
+	{
+		$expected = <<<EOD
+<select name="test" size="4">
+
+</select>
+EOD;
+		$this->assertEquals($expected, Html::listBox('test'));
+		$expected = <<<EOD
+<select name="test" size="5">
+<option value="value1">text1</option>
+<option value="value2">text2</option>
+</select>
+EOD;
+		$this->assertEquals($expected, Html::listBox('test', null, $this->getDataItems(), array('size' => 5)));
+		$expected = <<<EOD
+<select name="test" size="4">
+<option value="value1&lt;&gt;">text1&lt;&gt;</option>
+<option value="value  2">text&nbsp;&nbsp;2</option>
+</select>
+EOD;
+		$this->assertEquals($expected, Html::listBox('test', null, $this->getDataItems2()));
+		$expected = <<<EOD
+<select name="test" size="4">
+<option value="value1">text1</option>
+<option value="value2" selected="selected">text2</option>
+</select>
+EOD;
+		$this->assertEquals($expected, Html::listBox('test', 'value2', $this->getDataItems()));
+		$expected = <<<EOD
+<select name="test" size="4">
+<option value="value1" selected="selected">text1</option>
+<option value="value2" selected="selected">text2</option>
+</select>
+EOD;
+		$this->assertEquals($expected, Html::listBox('test', array('value1', 'value2'), $this->getDataItems()));
+
+		$expected = <<<EOD
+<select name="test[]" multiple="multiple" size="4">
+
+</select>
+EOD;
+		$this->assertEquals($expected, Html::listBox('test', null, array(), array('multiple' => true)));
+		$expected = <<<EOD
+<input type="hidden" name="test" value="0" /><select name="test" size="4">
+
+</select>
+EOD;
+		$this->assertEquals($expected, Html::listBox('test', '', array(), array('unselect' => '0')));
+	}
+
+	public function testCheckboxList()
+	{
+		$this->assertEquals('', Html::checkboxList('test'));
+
+		$expected = <<<EOD
+<label><input type="checkbox" name="test[]" value="value1" /> text1</label>
+<label><input type="checkbox" name="test[]" value="value2" checked="checked" /> text2</label>
+EOD;
+		$this->assertEquals($expected, Html::checkboxList('test', array('value2'), $this->getDataItems()));
+
+		$expected = <<<EOD
+<label><input type="checkbox" name="test[]" value="value1&lt;&gt;" /> text1<></label>
+<label><input type="checkbox" name="test[]" value="value  2" /> text  2</label>
+EOD;
+		$this->assertEquals($expected, Html::checkboxList('test', array('value2'), $this->getDataItems2()));
+
+		$expected = <<<EOD
+<input type="hidden" name="test" value="0" /><label><input type="checkbox" name="test[]" value="value1" /> text1</label><br />
+<label><input type="checkbox" name="test[]" value="value2" checked="checked" /> text2</label>
+EOD;
+		$this->assertEquals($expected, Html::checkboxList('test', array('value2'), $this->getDataItems(), array(
+			'separator' => "<br />\n",
+			'unselect' => '0',
+		)));
+
+		$expected = <<<EOD
+0<label>text1 <input type="checkbox" name="test[]" value="value1" /></label>
+1<label>text2 <input type="checkbox" name="test[]" value="value2" checked="checked" /></label>
+EOD;
+		$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));
+			}
+		)));
+	}
+
+	public function testRadioList()
+	{
+		$this->assertEquals('', Html::radioList('test'));
+
+		$expected = <<<EOD
+<label><input type="radio" name="test" value="value1" /> text1</label>
+<label><input type="radio" name="test" value="value2" checked="checked" /> text2</label>
+EOD;
+		$this->assertEquals($expected, Html::radioList('test', array('value2'), $this->getDataItems()));
+
+		$expected = <<<EOD
+<label><input type="radio" name="test" value="value1&lt;&gt;" /> text1<></label>
+<label><input type="radio" name="test" value="value  2" /> text  2</label>
+EOD;
+		$this->assertEquals($expected, Html::radioList('test',  array('value2'), $this->getDataItems2()));
+
+		$expected = <<<EOD
+<input type="hidden" name="test" value="0" /><label><input type="radio" name="test" value="value1" /> text1</label><br />
+<label><input type="radio" name="test" value="value2" checked="checked" /> text2</label>
+EOD;
+		$this->assertEquals($expected, Html::radioList('test', array('value2'), $this->getDataItems(), array(
+			'separator' => "<br />\n",
+			'unselect' => '0',
+		)));
+
+		$expected = <<<EOD
+0<label>text1 <input type="radio" name="test" value="value1" /></label>
+1<label>text2 <input type="radio" name="test" value="value2" checked="checked" /></label>
+EOD;
+		$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()
+	{
+		$data = array(
+			'value1' => 'label1',
+			'group1' => array(
+				'value11' => 'label11',
+				'group11' => array(
+					'value111' => 'label111',
+				),
+				'group12' => array(),
+			),
+			'value2' => 'label2',
+			'group2' => array(),
+		);
+		$expected = <<<EOD
+<option value="">please&nbsp;select&lt;&gt;</option>
+<option value="value1" selected="selected">label1</option>
+<optgroup label="group1">
+<option value="value11">label11</option>
+<optgroup label="group11">
+<option class="option" value="value111" selected="selected">label111</option>
+</optgroup>
+<optgroup class="group" label="group12">
+
+</optgroup>
+</optgroup>
+<option value="value2">label2</option>
+<optgroup label="group2">
+
+</optgroup>
+EOD;
+		$attributes = array(
+			'prompt' => 'please select<>',
+			'options' => array(
+				'value111' => array('class' => 'option'),
+			),
+			'groups' => array(
+				'group12' => array('class' => 'group'),
+			),
+		);
+		$this->assertEquals($expected, Html::renderSelectOptions(array('value111', 'value1'), $data, $attributes));
+	}
+
+	public function testRenderAttributes()
+	{
+		$this->assertEquals('', Html::renderTagAttributes(array()));
+		$this->assertEquals(' name="test" value="1&lt;&gt;"', Html::renderTagAttributes(array('name' => 'test', 'empty' => null, 'value' => '1<>')));
+		Html::$showBooleanAttributeValues = false;
+		$this->assertEquals(' checked disabled', Html::renderTagAttributes(array('checked' => 'checked', 'disabled' => true, 'hidden' => false)));
+		Html::$showBooleanAttributeValues = true;
+	}
+
+	protected function getDataItems()
+	{
+		return array(
+			'value1' => 'text1',
+			'value2' => 'text2',
+		);
+	}
+
+	protected function getDataItems2()
+	{
+		return array(
+			'value1<>' => 'text1<>',
+			'value  2' => 'text  2',
+		);
+	}
+}
diff --git a/tests/unit/framework/validators/EmailValidatorTest.php b/tests/unit/framework/validators/EmailValidatorTest.php
new file mode 100644
index 0000000..fbc2f53
--- /dev/null
+++ b/tests/unit/framework/validators/EmailValidatorTest.php
@@ -0,0 +1,28 @@
+<?php
+namespace yiiunit\framework\validators;
+use yii\validators\EmailValidator;
+use yiiunit\TestCase;
+
+/**
+ * EmailValidatorTest
+ */
+class EmailValidatorTest extends TestCase
+{
+	public function testValidateValue()
+	{
+		$validator = new EmailValidator();
+
+		$this->assertTrue($validator->validateValue('sam@rmcreative.ru'));
+		$this->assertTrue($validator->validateValue('5011@gmail.com'));
+		$this->assertFalse($validator->validateValue('rmcreative.ru'));
+	}
+
+	public function testValidateValueMx()
+	{
+		$validator = new EmailValidator();
+		$validator->checkMX = true;
+
+		$this->assertTrue($validator->validateValue('sam@rmcreative.ru'));
+		$this->assertFalse($validator->validateValue('test@example.com'));
+	}
+}
\ No newline at end of file
diff --git a/tests/unit/framework/web/UrlManagerTest.php b/tests/unit/framework/web/UrlManagerTest.php
new file mode 100644
index 0000000..95b3bf6
--- /dev/null
+++ b/tests/unit/framework/web/UrlManagerTest.php
@@ -0,0 +1,210 @@
+<?php
+namespace yiiunit\framework\web;
+
+use yii\web\Request;
+use yii\web\UrlManager;
+
+class UrlManagerTest extends \yiiunit\TestCase
+{
+	public function testCreateUrl()
+	{
+		// default setting with '/' as base url
+		$manager = new UrlManager(array(
+			'baseUrl' => '/',
+			'cache' => null,
+		));
+		$url = $manager->createUrl('post/view');
+		$this->assertEquals('/?r=post/view', $url);
+		$url = $manager->createUrl('post/view', array('id' => 1, 'title' => 'sample post'));
+		$this->assertEquals('/?r=post/view&id=1&title=sample+post', $url);
+
+		// default setting with '/test/' as base url
+		$manager = new UrlManager(array(
+			'baseUrl' => '/test/',
+			'cache' => null,
+		));
+		$url = $manager->createUrl('post/view', array('id' => 1, 'title' => 'sample post'));
+		$this->assertEquals('/test/?r=post/view&id=1&title=sample+post', $url);
+
+		// pretty URL without rules
+		$manager = new UrlManager(array(
+			'enablePrettyUrl' => true,
+			'baseUrl' => '/',
+			'cache' => null,
+		));
+		$url = $manager->createUrl('post/view', array('id' => 1, 'title' => 'sample post'));
+		$this->assertEquals('/post/view?id=1&title=sample+post', $url);
+		$manager = new UrlManager(array(
+			'enablePrettyUrl' => true,
+			'baseUrl' => '/test/',
+			'cache' => null,
+		));
+		$url = $manager->createUrl('post/view', array('id' => 1, 'title' => 'sample post'));
+		$this->assertEquals('/test/post/view?id=1&title=sample+post', $url);
+		$manager = new UrlManager(array(
+			'enablePrettyUrl' => true,
+			'baseUrl' => '/test/index.php',
+			'cache' => null,
+		));
+		$url = $manager->createUrl('post/view', array('id' => 1, 'title' => 'sample post'));
+		$this->assertEquals('/test/index.php/post/view?id=1&title=sample+post', $url);
+
+		// todo: test showScriptName
+
+		// pretty URL with rules
+		$manager = new UrlManager(array(
+			'enablePrettyUrl' => true,
+			'cache' => null,
+			'rules' => array(
+				array(
+					'pattern' => 'post/<id>/<title>',
+					'route' => 'post/view',
+				),
+			),
+			'baseUrl' => '/',
+		));
+		$url = $manager->createUrl('post/view', array('id' => 1, 'title' => 'sample post'));
+		$this->assertEquals('/post/1/sample+post', $url);
+		$url = $manager->createUrl('post/index', array('page' => 1));
+		$this->assertEquals('/post/index?page=1', $url);
+
+		// pretty URL with rules and suffix
+		$manager = new UrlManager(array(
+			'enablePrettyUrl' => true,
+			'cache' => null,
+			'rules' => array(
+				array(
+					'pattern' => 'post/<id>/<title>',
+					'route' => 'post/view',
+				),
+			),
+			'baseUrl' => '/',
+			'suffix' => '.html',
+		));
+		$url = $manager->createUrl('post/view', array('id' => 1, 'title' => 'sample post'));
+		$this->assertEquals('/post/1/sample+post.html', $url);
+		$url = $manager->createUrl('post/index', array('page' => 1));
+		$this->assertEquals('/post/index.html?page=1', $url);
+	}
+
+	public function testCreateAbsoluteUrl()
+	{
+		$manager = new UrlManager(array(
+			'baseUrl' => '/',
+			'hostInfo' => 'http://www.example.com',
+			'cache' => null,
+		));
+		$url = $manager->createAbsoluteUrl('post/view', array('id' => 1, 'title' => 'sample post'));
+		$this->assertEquals('http://www.example.com/?r=post/view&id=1&title=sample+post', $url);
+	}
+
+	public function testParseRequest()
+	{
+		$manager = new UrlManager(array(
+			'cache' => null,
+		));
+		$request = new Request;
+
+		// default setting without 'r' param
+		unset($_GET['r']);
+		$result = $manager->parseRequest($request);
+		$this->assertEquals(array('', array()), $result);
+
+		// default setting with 'r' param
+		$_GET['r'] = 'site/index';
+		$result = $manager->parseRequest($request);
+		$this->assertEquals(array('site/index', array()), $result);
+
+		// default setting with 'r' param as an array
+		$_GET['r'] = array('site/index');
+		$result = $manager->parseRequest($request);
+		$this->assertEquals(array('', array()), $result);
+
+		// pretty URL without rules
+		$manager = new UrlManager(array(
+			'enablePrettyUrl' => true,
+			'cache' => null,
+		));
+		// empty pathinfo
+		$request->pathInfo = '';
+		$result = $manager->parseRequest($request);
+		$this->assertEquals(array('', array()), $result);
+		// normal pathinfo
+		$request->pathInfo = 'site/index';
+		$result = $manager->parseRequest($request);
+		$this->assertEquals(array('site/index', array()), $result);
+		// pathinfo with module
+		$request->pathInfo = 'module/site/index';
+		$result = $manager->parseRequest($request);
+		$this->assertEquals(array('module/site/index', array()), $result);
+		// pathinfo with trailing slashes
+		$request->pathInfo = 'module/site/index/';
+		$result = $manager->parseRequest($request);
+		$this->assertEquals(array('module/site/index', array()), $result);
+
+		// pretty URL rules
+		$manager = new UrlManager(array(
+			'enablePrettyUrl' => true,
+			'cache' => null,
+			'rules' => array(
+				array(
+					'pattern' => 'post/<id>/<title>',
+					'route' => 'post/view',
+				),
+			),
+		));
+		// matching pathinfo
+		$request->pathInfo = 'post/123/this+is+sample';
+		$result = $manager->parseRequest($request);
+		$this->assertEquals(array('post/view', array('id' => '123', 'title' => 'this+is+sample')), $result);
+		// matching pathinfo with trailing slashes
+		$request->pathInfo = 'post/123/this+is+sample/';
+		$result = $manager->parseRequest($request);
+		$this->assertEquals(array('post/view', array('id' => '123', 'title' => 'this+is+sample')), $result);
+		// empty pathinfo
+		$request->pathInfo = '';
+		$result = $manager->parseRequest($request);
+		$this->assertEquals(array('', array()), $result);
+		// normal pathinfo
+		$request->pathInfo = 'site/index';
+		$result = $manager->parseRequest($request);
+		$this->assertEquals(array('site/index', array()), $result);
+		// pathinfo with module
+		$request->pathInfo = 'module/site/index';
+		$result = $manager->parseRequest($request);
+		$this->assertEquals(array('module/site/index', array()), $result);
+
+		// pretty URL rules
+		$manager = new UrlManager(array(
+			'enablePrettyUrl' => true,
+			'suffix' => '.html',
+			'cache' => null,
+			'rules' => array(
+				array(
+					'pattern' => 'post/<id>/<title>',
+					'route' => 'post/view',
+				),
+			),
+		));
+		// matching pathinfo
+		$request->pathInfo = 'post/123/this+is+sample.html';
+		$result = $manager->parseRequest($request);
+		$this->assertEquals(array('post/view', array('id' => '123', 'title' => 'this+is+sample')), $result);
+		// matching pathinfo without suffix
+		$request->pathInfo = 'post/123/this+is+sample';
+		$result = $manager->parseRequest($request);
+		$this->assertFalse($result);
+		// empty pathinfo
+		$request->pathInfo = '';
+		$result = $manager->parseRequest($request);
+		$this->assertEquals(array('', array()), $result);
+		// normal pathinfo
+		$request->pathInfo = 'site/index.html';
+		$result = $manager->parseRequest($request);
+		$this->assertEquals(array('site/index', array()), $result);
+		// pathinfo without suffix
+		$request->pathInfo = 'site/index';
+		$result = $manager->parseRequest($request);
+		$this->assertFalse($result);
+	}
+}
diff --git a/tests/unit/framework/web/UrlRuleTest.php b/tests/unit/framework/web/UrlRuleTest.php
new file mode 100644
index 0000000..825199e
--- /dev/null
+++ b/tests/unit/framework/web/UrlRuleTest.php
@@ -0,0 +1,615 @@
+<?php
+
+namespace yiiunit\framework\web;
+
+use yii\web\UrlManager;
+use yii\web\UrlRule;
+use yii\web\Request;
+
+class UrlRuleTest extends \yiiunit\TestCase
+{
+	public function testCreateUrl()
+	{
+		$manager = new UrlManager(array('cache' => null));
+		$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($manager, $route, $params);
+				$this->assertEquals($expected, $url, "Test#$i-$j: $name");
+			}
+		}
+	}
+
+	public function testParseRequest()
+	{
+		$manager = new UrlManager(array('cache' => null));
+		$request = new Request;
+		$suites = $this->getTestsForParseRequest();
+		foreach ($suites as $i => $suite) {
+			list ($name, $config, $tests) = $suite;
+			$rule = new UrlRule($config);
+			foreach ($tests as $j => $test) {
+				$request->pathInfo = $test[0];
+				$route = $test[1];
+				$params = isset($test[2]) ? $test[2] : array();
+				$result = $rule->parseRequest($manager, $request);
+				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(
+				'parsing only',
+				array(
+					'pattern' => 'posts',
+					'route' => 'post/index',
+					'mode' => UrlRule::PARSING_ONLY,
+				),
+				array(
+					array('post/index', array(), false),
+				),
+			),
+			array(
+				'with param',
+				array(
+					'pattern' => 'post/<page>',
+					'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/<page:\d+>',
+					'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/<page:\d+>-<tag>',
+					'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/<page:\d+>/<tag>',
+					'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/<tag>',
+					'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/<page:\d+>/<tag>/<sort:yes|no>',
+					'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/<page:\d+>-<tag>',
+					'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/<tag>/<page:\d+>',
+					'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/<page:\d+>/<tag>',
+					'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/<page:\d+>-<tag>',
+					'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' => '<controller>/<action>',
+					'route' => '<controller>/<action>',
+					'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' => '<controller:post|comment>/<action>',
+					'route' => '<controller>/<action>',
+					'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'),
+				),
+			),
+			array(
+				'route has default parameter',
+				array(
+					'pattern' => '<controller:post|comment>/<action>',
+					'route' => '<controller>/<action>',
+					'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('test/index', array('page' => 1), false),
+					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'),
+				),
+			),
+		);
+	}
+
+	protected function getTestsForParseRequest()
+	{
+		// 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(
+				'creation only',
+				array(
+					'pattern' => 'posts',
+					'route' => 'post/index',
+					'mode' => UrlRule::CREATION_ONLY,
+				),
+				array(
+					array('posts', false),
+				),
+			),
+			array(
+				'with param',
+				array(
+					'pattern' => 'post/<page>',
+					'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/<page:\d+>',
+					'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/<page:\d+>-<tag>',
+					'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/<page:\d+>/<tag>',
+					'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/<tag>',
+					'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/<page:\d+>/<tag>/<sort:yes|no>',
+					'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/<page:\d+>-<tag>',
+					'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/<tag>/<page:\d+>',
+					'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/<page:\d+>/<tag>',
+					'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/<page:\d+>-<tag>',
+					'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),
+				),
+			),
+			array(
+				'route has parameters',
+				array(
+					'pattern' => '<controller>/<action>',
+					'route' => '<controller>/<action>',
+					'defaults' => array(),
+				),
+				array(
+					array('post/index', 'post/index'),
+					array('module/post/index', false),
+				),
+			),
+			array(
+				'route has parameters with regex',
+				array(
+					'pattern' => '<controller:post|comment>/<action>',
+					'route' => '<controller>/<action>',
+					'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' => '<controller:post|comment>/<action>',
+					'route' => '<controller>/<action>',
+					'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),
+				),
+			),
+			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),
+				),
+			),
+		);
+	}
+}
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 @@
+*
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/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 @@
+<?php
+
+require(__DIR__ . '/../../../framework/yii.php');
+
+$application = new yii\web\Application('test', __DIR__ . '/protected');
+$application->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 @@
+<?php
+
+return array();
\ No newline at end of file
diff --git a/tests/web/app/protected/controllers/SiteController.php b/tests/web/app/protected/controllers/SiteController.php
new file mode 100644
index 0000000..050bf90
--- /dev/null
+++ b/tests/web/app/protected/controllers/SiteController.php
@@ -0,0 +1,30 @@
+<?php
+
+use yii\helpers\Html;
+
+class DefaultController extends \yii\web\Controller
+{
+	public function actionIndex()
+	{
+		echo 'hello world';
+	}
+	
+	public function actionForm()
+	{
+		echo Html::beginForm();
+		echo Html::checkboxList('test', array(
+			'value 1' => '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/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 @@
+*
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 @@
+<?php
+/**
+ * Created by JetBrains PhpStorm.
+ * User: qiang
+ * Date: 3/16/13
+ * Time: 10:41 AM
+ * To change this template use File | Settings | File Templates.
+ */
\ No newline at end of file
diff --git a/todo.md b/todo.md
index 60e37c5..f66d3c1 100644
--- a/todo.md
+++ b/todo.md
@@ -1,33 +1,20 @@
-- 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
-	* SmartyViewRenderer
-- 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
-	* a console command to clear cached data
-	* 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()?
 	* CompareValidator::clientValidateAttribute(): depends on CHtml::activeId()
 
+memo
+	* Minimal PHP version required: 5.3.7 (http://www.php.net/manual/en/function.crypt.php)
 ---
 
 - base
 	* module
 	  - Module should be able to define its own configuration including routes. Application should be able to overwrite it.
 	* application
-	* security
 - built-in console commands
 	+ api doc builder
 		* support for markdown syntax
@@ -35,12 +22,10 @@
 		* 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.
 - helpers
-	* array
 	* image
 	* string
 	* file
@@ -53,8 +38,6 @@
     * 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.