diff --git a/composer.json b/composer.json
index 95d72a1..8babb23 100644
--- a/composer.json
+++ b/composer.json
@@ -97,6 +97,7 @@
 	},
 	"autoload": {
 		"psr-4": {
+			"yii\\": "framework/",
 			"yii\\apidoc\\": "extensions/apidoc/",
 			"yii\\authclient\\": "extensions/authclient/",
 			"yii\\bootstrap\\": "extensions/bootstrap/",
@@ -113,9 +114,6 @@
 			"yii\\swiftmailer\\": "extensions/swiftmailer/",
 			"yii\\sphinx\\": "extensions/sphinx/",
 			"yii\\twig\\": "extensions/twig/"
-		},
-		"psr-0": {
-			"yii\\": "framework/"
 		}
 	}
 }
diff --git a/framework/yii/.gitignore b/framework/.gitignore
similarity index 100%
rename from framework/yii/.gitignore
rename to framework/.gitignore
diff --git a/framework/yii/.htaccess b/framework/.htaccess
similarity index 100%
rename from framework/yii/.htaccess
rename to framework/.htaccess
diff --git a/framework/yii/BaseYii.php b/framework/BaseYii.php
similarity index 100%
rename from framework/yii/BaseYii.php
rename to framework/BaseYii.php
diff --git a/framework/yii/Yii.php b/framework/Yii.php
similarity index 100%
rename from framework/yii/Yii.php
rename to framework/Yii.php
diff --git a/framework/yii/assets/jquery.maskedinput.js b/framework/assets/jquery.maskedinput.js
similarity index 100%
rename from framework/yii/assets/jquery.maskedinput.js
rename to framework/assets/jquery.maskedinput.js
diff --git a/framework/yii/assets/punycode/LICENSE-GPL.txt b/framework/assets/punycode/LICENSE-GPL.txt
similarity index 100%
rename from framework/yii/assets/punycode/LICENSE-GPL.txt
rename to framework/assets/punycode/LICENSE-GPL.txt
diff --git a/framework/yii/assets/punycode/LICENSE-MIT.txt b/framework/assets/punycode/LICENSE-MIT.txt
similarity index 100%
rename from framework/yii/assets/punycode/LICENSE-MIT.txt
rename to framework/assets/punycode/LICENSE-MIT.txt
diff --git a/framework/yii/assets/punycode/punycode.js b/framework/assets/punycode/punycode.js
similarity index 100%
rename from framework/yii/assets/punycode/punycode.js
rename to framework/assets/punycode/punycode.js
diff --git a/framework/yii/assets/punycode/punycode.min.js b/framework/assets/punycode/punycode.min.js
similarity index 100%
rename from framework/yii/assets/punycode/punycode.min.js
rename to framework/assets/punycode/punycode.min.js
diff --git a/framework/yii/assets/yii.activeForm.js b/framework/assets/yii.activeForm.js
similarity index 100%
rename from framework/yii/assets/yii.activeForm.js
rename to framework/assets/yii.activeForm.js
diff --git a/framework/yii/assets/yii.captcha.js b/framework/assets/yii.captcha.js
similarity index 100%
rename from framework/yii/assets/yii.captcha.js
rename to framework/assets/yii.captcha.js
diff --git a/framework/yii/assets/yii.gridView.js b/framework/assets/yii.gridView.js
similarity index 100%
rename from framework/yii/assets/yii.gridView.js
rename to framework/assets/yii.gridView.js
diff --git a/framework/yii/assets/yii.js b/framework/assets/yii.js
similarity index 100%
rename from framework/yii/assets/yii.js
rename to framework/assets/yii.js
diff --git a/framework/yii/assets/yii.validation.js b/framework/assets/yii.validation.js
similarity index 100%
rename from framework/yii/assets/yii.validation.js
rename to framework/assets/yii.validation.js
diff --git a/framework/yii/base/Action.php b/framework/base/Action.php
similarity index 100%
rename from framework/yii/base/Action.php
rename to framework/base/Action.php
diff --git a/framework/yii/base/ActionEvent.php b/framework/base/ActionEvent.php
similarity index 100%
rename from framework/yii/base/ActionEvent.php
rename to framework/base/ActionEvent.php
diff --git a/framework/yii/base/ActionFilter.php b/framework/base/ActionFilter.php
similarity index 100%
rename from framework/yii/base/ActionFilter.php
rename to framework/base/ActionFilter.php
diff --git a/framework/yii/base/Application.php b/framework/base/Application.php
similarity index 100%
rename from framework/yii/base/Application.php
rename to framework/base/Application.php
diff --git a/framework/yii/base/Arrayable.php b/framework/base/Arrayable.php
similarity index 100%
rename from framework/yii/base/Arrayable.php
rename to framework/base/Arrayable.php
diff --git a/framework/yii/base/Behavior.php b/framework/base/Behavior.php
similarity index 100%
rename from framework/yii/base/Behavior.php
rename to framework/base/Behavior.php
diff --git a/framework/yii/base/Component.php b/framework/base/Component.php
similarity index 100%
rename from framework/yii/base/Component.php
rename to framework/base/Component.php
diff --git a/framework/yii/base/Controller.php b/framework/base/Controller.php
similarity index 100%
rename from framework/yii/base/Controller.php
rename to framework/base/Controller.php
diff --git a/framework/yii/base/ErrorException.php b/framework/base/ErrorException.php
similarity index 100%
rename from framework/yii/base/ErrorException.php
rename to framework/base/ErrorException.php
diff --git a/framework/yii/base/ErrorHandler.php b/framework/base/ErrorHandler.php
similarity index 100%
rename from framework/yii/base/ErrorHandler.php
rename to framework/base/ErrorHandler.php
diff --git a/framework/yii/base/Event.php b/framework/base/Event.php
similarity index 100%
rename from framework/yii/base/Event.php
rename to framework/base/Event.php
diff --git a/framework/yii/base/Exception.php b/framework/base/Exception.php
similarity index 100%
rename from framework/yii/base/Exception.php
rename to framework/base/Exception.php
diff --git a/framework/yii/base/Extension.php b/framework/base/Extension.php
similarity index 100%
rename from framework/yii/base/Extension.php
rename to framework/base/Extension.php
diff --git a/framework/yii/base/Formatter.php b/framework/base/Formatter.php
similarity index 100%
rename from framework/yii/base/Formatter.php
rename to framework/base/Formatter.php
diff --git a/framework/yii/base/InlineAction.php b/framework/base/InlineAction.php
similarity index 100%
rename from framework/yii/base/InlineAction.php
rename to framework/base/InlineAction.php
diff --git a/framework/yii/base/InvalidCallException.php b/framework/base/InvalidCallException.php
similarity index 100%
rename from framework/yii/base/InvalidCallException.php
rename to framework/base/InvalidCallException.php
diff --git a/framework/yii/base/InvalidConfigException.php b/framework/base/InvalidConfigException.php
similarity index 100%
rename from framework/yii/base/InvalidConfigException.php
rename to framework/base/InvalidConfigException.php
diff --git a/framework/yii/base/InvalidParamException.php b/framework/base/InvalidParamException.php
similarity index 100%
rename from framework/yii/base/InvalidParamException.php
rename to framework/base/InvalidParamException.php
diff --git a/framework/yii/base/InvalidRouteException.php b/framework/base/InvalidRouteException.php
similarity index 100%
rename from framework/yii/base/InvalidRouteException.php
rename to framework/base/InvalidRouteException.php
diff --git a/framework/yii/base/MailEvent.php b/framework/base/MailEvent.php
similarity index 100%
rename from framework/yii/base/MailEvent.php
rename to framework/base/MailEvent.php
diff --git a/framework/yii/base/Model.php b/framework/base/Model.php
similarity index 100%
rename from framework/yii/base/Model.php
rename to framework/base/Model.php
diff --git a/framework/yii/base/ModelEvent.php b/framework/base/ModelEvent.php
similarity index 100%
rename from framework/yii/base/ModelEvent.php
rename to framework/base/ModelEvent.php
diff --git a/framework/yii/base/Module.php b/framework/base/Module.php
similarity index 100%
rename from framework/yii/base/Module.php
rename to framework/base/Module.php
diff --git a/framework/yii/base/NotSupportedException.php b/framework/base/NotSupportedException.php
similarity index 100%
rename from framework/yii/base/NotSupportedException.php
rename to framework/base/NotSupportedException.php
diff --git a/framework/yii/base/Object.php b/framework/base/Object.php
similarity index 100%
rename from framework/yii/base/Object.php
rename to framework/base/Object.php
diff --git a/framework/yii/base/Request.php b/framework/base/Request.php
similarity index 100%
rename from framework/yii/base/Request.php
rename to framework/base/Request.php
diff --git a/framework/yii/base/Response.php b/framework/base/Response.php
similarity index 100%
rename from framework/yii/base/Response.php
rename to framework/base/Response.php
diff --git a/framework/yii/base/Theme.php b/framework/base/Theme.php
similarity index 100%
rename from framework/yii/base/Theme.php
rename to framework/base/Theme.php
diff --git a/framework/yii/base/UnknownClassException.php b/framework/base/UnknownClassException.php
similarity index 100%
rename from framework/yii/base/UnknownClassException.php
rename to framework/base/UnknownClassException.php
diff --git a/framework/yii/base/UnknownMethodException.php b/framework/base/UnknownMethodException.php
similarity index 100%
rename from framework/yii/base/UnknownMethodException.php
rename to framework/base/UnknownMethodException.php
diff --git a/framework/yii/base/UnknownPropertyException.php b/framework/base/UnknownPropertyException.php
similarity index 100%
rename from framework/yii/base/UnknownPropertyException.php
rename to framework/base/UnknownPropertyException.php
diff --git a/framework/yii/base/UserException.php b/framework/base/UserException.php
similarity index 100%
rename from framework/yii/base/UserException.php
rename to framework/base/UserException.php
diff --git a/framework/yii/base/View.php b/framework/base/View.php
similarity index 100%
rename from framework/yii/base/View.php
rename to framework/base/View.php
diff --git a/framework/yii/base/ViewContextInterface.php b/framework/base/ViewContextInterface.php
similarity index 100%
rename from framework/yii/base/ViewContextInterface.php
rename to framework/base/ViewContextInterface.php
diff --git a/framework/yii/base/ViewEvent.php b/framework/base/ViewEvent.php
similarity index 100%
rename from framework/yii/base/ViewEvent.php
rename to framework/base/ViewEvent.php
diff --git a/framework/yii/base/ViewRenderer.php b/framework/base/ViewRenderer.php
similarity index 100%
rename from framework/yii/base/ViewRenderer.php
rename to framework/base/ViewRenderer.php
diff --git a/framework/yii/base/Widget.php b/framework/base/Widget.php
similarity index 100%
rename from framework/yii/base/Widget.php
rename to framework/base/Widget.php
diff --git a/framework/yii/behaviors/AutoTimestamp.php b/framework/behaviors/AutoTimestamp.php
similarity index 100%
rename from framework/yii/behaviors/AutoTimestamp.php
rename to framework/behaviors/AutoTimestamp.php
diff --git a/framework/yii/caching/ApcCache.php b/framework/caching/ApcCache.php
similarity index 100%
rename from framework/yii/caching/ApcCache.php
rename to framework/caching/ApcCache.php
diff --git a/framework/yii/caching/Cache.php b/framework/caching/Cache.php
similarity index 100%
rename from framework/yii/caching/Cache.php
rename to framework/caching/Cache.php
diff --git a/framework/yii/caching/ChainedDependency.php b/framework/caching/ChainedDependency.php
similarity index 100%
rename from framework/yii/caching/ChainedDependency.php
rename to framework/caching/ChainedDependency.php
diff --git a/framework/yii/caching/DbCache.php b/framework/caching/DbCache.php
similarity index 100%
rename from framework/yii/caching/DbCache.php
rename to framework/caching/DbCache.php
diff --git a/framework/yii/caching/DbDependency.php b/framework/caching/DbDependency.php
similarity index 100%
rename from framework/yii/caching/DbDependency.php
rename to framework/caching/DbDependency.php
diff --git a/framework/yii/caching/Dependency.php b/framework/caching/Dependency.php
similarity index 100%
rename from framework/yii/caching/Dependency.php
rename to framework/caching/Dependency.php
diff --git a/framework/yii/caching/DummyCache.php b/framework/caching/DummyCache.php
similarity index 100%
rename from framework/yii/caching/DummyCache.php
rename to framework/caching/DummyCache.php
diff --git a/framework/yii/caching/ExpressionDependency.php b/framework/caching/ExpressionDependency.php
similarity index 100%
rename from framework/yii/caching/ExpressionDependency.php
rename to framework/caching/ExpressionDependency.php
diff --git a/framework/yii/caching/FileCache.php b/framework/caching/FileCache.php
similarity index 100%
rename from framework/yii/caching/FileCache.php
rename to framework/caching/FileCache.php
diff --git a/framework/yii/caching/FileDependency.php b/framework/caching/FileDependency.php
similarity index 100%
rename from framework/yii/caching/FileDependency.php
rename to framework/caching/FileDependency.php
diff --git a/framework/yii/caching/GroupDependency.php b/framework/caching/GroupDependency.php
similarity index 100%
rename from framework/yii/caching/GroupDependency.php
rename to framework/caching/GroupDependency.php
diff --git a/framework/yii/caching/MemCache.php b/framework/caching/MemCache.php
similarity index 100%
rename from framework/yii/caching/MemCache.php
rename to framework/caching/MemCache.php
diff --git a/framework/yii/caching/MemCacheServer.php b/framework/caching/MemCacheServer.php
similarity index 100%
rename from framework/yii/caching/MemCacheServer.php
rename to framework/caching/MemCacheServer.php
diff --git a/framework/yii/caching/WinCache.php b/framework/caching/WinCache.php
similarity index 100%
rename from framework/yii/caching/WinCache.php
rename to framework/caching/WinCache.php
diff --git a/framework/yii/caching/XCache.php b/framework/caching/XCache.php
similarity index 100%
rename from framework/yii/caching/XCache.php
rename to framework/caching/XCache.php
diff --git a/framework/yii/caching/ZendDataCache.php b/framework/caching/ZendDataCache.php
similarity index 100%
rename from framework/yii/caching/ZendDataCache.php
rename to framework/caching/ZendDataCache.php
diff --git a/framework/yii/captcha/Captcha.php b/framework/captcha/Captcha.php
similarity index 100%
rename from framework/yii/captcha/Captcha.php
rename to framework/captcha/Captcha.php
diff --git a/framework/yii/captcha/CaptchaAction.php b/framework/captcha/CaptchaAction.php
similarity index 100%
rename from framework/yii/captcha/CaptchaAction.php
rename to framework/captcha/CaptchaAction.php
diff --git a/framework/yii/captcha/CaptchaAsset.php b/framework/captcha/CaptchaAsset.php
similarity index 100%
rename from framework/yii/captcha/CaptchaAsset.php
rename to framework/captcha/CaptchaAsset.php
diff --git a/framework/yii/captcha/CaptchaValidator.php b/framework/captcha/CaptchaValidator.php
similarity index 100%
rename from framework/yii/captcha/CaptchaValidator.php
rename to framework/captcha/CaptchaValidator.php
diff --git a/framework/yii/captcha/SpicyRice.md b/framework/captcha/SpicyRice.md
similarity index 100%
rename from framework/yii/captcha/SpicyRice.md
rename to framework/captcha/SpicyRice.md
diff --git a/framework/yii/captcha/SpicyRice.ttf b/framework/captcha/SpicyRice.ttf
similarity index 100%
rename from framework/yii/captcha/SpicyRice.ttf
rename to framework/captcha/SpicyRice.ttf
Binary files a/framework/yii/captcha/SpicyRice.ttf and b/framework/captcha/SpicyRice.ttf differ
diff --git a/framework/yii/classes.php b/framework/classes.php
similarity index 100%
rename from framework/yii/classes.php
rename to framework/classes.php
diff --git a/framework/composer.json b/framework/composer.json
index d3ed7e7..c96726c 100644
--- a/framework/composer.json
+++ b/framework/composer.json
@@ -59,6 +59,6 @@
 		"michelf/php-markdown": "1.3.*"
 	},
 	"autoload": {
-		"psr-0": { "yii\\": "" }
+		"psr-4": { "yii\\": "" }
 	}
 }
diff --git a/framework/yii/console/Application.php b/framework/console/Application.php
similarity index 100%
rename from framework/yii/console/Application.php
rename to framework/console/Application.php
diff --git a/framework/yii/console/Controller.php b/framework/console/Controller.php
similarity index 100%
rename from framework/yii/console/Controller.php
rename to framework/console/Controller.php
diff --git a/framework/yii/console/Exception.php b/framework/console/Exception.php
similarity index 100%
rename from framework/yii/console/Exception.php
rename to framework/console/Exception.php
diff --git a/framework/yii/console/Request.php b/framework/console/Request.php
similarity index 100%
rename from framework/yii/console/Request.php
rename to framework/console/Request.php
diff --git a/framework/yii/console/Response.php b/framework/console/Response.php
similarity index 100%
rename from framework/yii/console/Response.php
rename to framework/console/Response.php
diff --git a/framework/yii/console/controllers/AssetController.php b/framework/console/controllers/AssetController.php
similarity index 100%
rename from framework/yii/console/controllers/AssetController.php
rename to framework/console/controllers/AssetController.php
diff --git a/framework/yii/console/controllers/CacheController.php b/framework/console/controllers/CacheController.php
similarity index 100%
rename from framework/yii/console/controllers/CacheController.php
rename to framework/console/controllers/CacheController.php
diff --git a/framework/yii/console/controllers/FixtureController.php b/framework/console/controllers/FixtureController.php
similarity index 100%
rename from framework/yii/console/controllers/FixtureController.php
rename to framework/console/controllers/FixtureController.php
diff --git a/framework/yii/console/controllers/HelpController.php b/framework/console/controllers/HelpController.php
similarity index 100%
rename from framework/yii/console/controllers/HelpController.php
rename to framework/console/controllers/HelpController.php
diff --git a/framework/yii/console/controllers/MessageController.php b/framework/console/controllers/MessageController.php
similarity index 100%
rename from framework/yii/console/controllers/MessageController.php
rename to framework/console/controllers/MessageController.php
diff --git a/framework/yii/console/controllers/MigrateController.php b/framework/console/controllers/MigrateController.php
similarity index 100%
rename from framework/yii/console/controllers/MigrateController.php
rename to framework/console/controllers/MigrateController.php
diff --git a/framework/yii/console/runtime/.gitignore b/framework/console/runtime/.gitignore
similarity index 100%
rename from framework/yii/console/runtime/.gitignore
rename to framework/console/runtime/.gitignore
diff --git a/framework/yii/data/ActiveDataProvider.php b/framework/data/ActiveDataProvider.php
similarity index 100%
rename from framework/yii/data/ActiveDataProvider.php
rename to framework/data/ActiveDataProvider.php
diff --git a/framework/yii/data/ArrayDataProvider.php b/framework/data/ArrayDataProvider.php
similarity index 100%
rename from framework/yii/data/ArrayDataProvider.php
rename to framework/data/ArrayDataProvider.php
diff --git a/framework/yii/data/BaseDataProvider.php b/framework/data/BaseDataProvider.php
similarity index 100%
rename from framework/yii/data/BaseDataProvider.php
rename to framework/data/BaseDataProvider.php
diff --git a/framework/yii/data/DataProviderInterface.php b/framework/data/DataProviderInterface.php
similarity index 100%
rename from framework/yii/data/DataProviderInterface.php
rename to framework/data/DataProviderInterface.php
diff --git a/framework/yii/data/Pagination.php b/framework/data/Pagination.php
similarity index 100%
rename from framework/yii/data/Pagination.php
rename to framework/data/Pagination.php
diff --git a/framework/yii/data/Sort.php b/framework/data/Sort.php
similarity index 100%
rename from framework/yii/data/Sort.php
rename to framework/data/Sort.php
diff --git a/framework/yii/data/SqlDataProvider.php b/framework/data/SqlDataProvider.php
similarity index 100%
rename from framework/yii/data/SqlDataProvider.php
rename to framework/data/SqlDataProvider.php
diff --git a/framework/yii/db/ActiveQuery.php b/framework/db/ActiveQuery.php
similarity index 100%
rename from framework/yii/db/ActiveQuery.php
rename to framework/db/ActiveQuery.php
diff --git a/framework/yii/db/ActiveQueryInterface.php b/framework/db/ActiveQueryInterface.php
similarity index 100%
rename from framework/yii/db/ActiveQueryInterface.php
rename to framework/db/ActiveQueryInterface.php
diff --git a/framework/yii/db/ActiveQueryTrait.php b/framework/db/ActiveQueryTrait.php
similarity index 100%
rename from framework/yii/db/ActiveQueryTrait.php
rename to framework/db/ActiveQueryTrait.php
diff --git a/framework/yii/db/ActiveRecord.php b/framework/db/ActiveRecord.php
similarity index 100%
rename from framework/yii/db/ActiveRecord.php
rename to framework/db/ActiveRecord.php
diff --git a/framework/yii/db/ActiveRecordInterface.php b/framework/db/ActiveRecordInterface.php
similarity index 100%
rename from framework/yii/db/ActiveRecordInterface.php
rename to framework/db/ActiveRecordInterface.php
diff --git a/framework/yii/db/ActiveRelation.php b/framework/db/ActiveRelation.php
similarity index 100%
rename from framework/yii/db/ActiveRelation.php
rename to framework/db/ActiveRelation.php
diff --git a/framework/yii/db/ActiveRelationInterface.php b/framework/db/ActiveRelationInterface.php
similarity index 100%
rename from framework/yii/db/ActiveRelationInterface.php
rename to framework/db/ActiveRelationInterface.php
diff --git a/framework/yii/db/ActiveRelationTrait.php b/framework/db/ActiveRelationTrait.php
similarity index 100%
rename from framework/yii/db/ActiveRelationTrait.php
rename to framework/db/ActiveRelationTrait.php
diff --git a/framework/yii/db/BaseActiveRecord.php b/framework/db/BaseActiveRecord.php
similarity index 100%
rename from framework/yii/db/BaseActiveRecord.php
rename to framework/db/BaseActiveRecord.php
diff --git a/framework/yii/db/ColumnSchema.php b/framework/db/ColumnSchema.php
similarity index 100%
rename from framework/yii/db/ColumnSchema.php
rename to framework/db/ColumnSchema.php
diff --git a/framework/yii/db/Command.php b/framework/db/Command.php
similarity index 100%
rename from framework/yii/db/Command.php
rename to framework/db/Command.php
diff --git a/framework/yii/db/Connection.php b/framework/db/Connection.php
similarity index 100%
rename from framework/yii/db/Connection.php
rename to framework/db/Connection.php
diff --git a/framework/yii/db/DataReader.php b/framework/db/DataReader.php
similarity index 100%
rename from framework/yii/db/DataReader.php
rename to framework/db/DataReader.php
diff --git a/framework/yii/db/Exception.php b/framework/db/Exception.php
similarity index 100%
rename from framework/yii/db/Exception.php
rename to framework/db/Exception.php
diff --git a/framework/yii/db/Expression.php b/framework/db/Expression.php
similarity index 100%
rename from framework/yii/db/Expression.php
rename to framework/db/Expression.php
diff --git a/framework/yii/db/Migration.php b/framework/db/Migration.php
similarity index 100%
rename from framework/yii/db/Migration.php
rename to framework/db/Migration.php
diff --git a/framework/yii/db/Query.php b/framework/db/Query.php
similarity index 100%
rename from framework/yii/db/Query.php
rename to framework/db/Query.php
diff --git a/framework/yii/db/QueryBuilder.php b/framework/db/QueryBuilder.php
similarity index 100%
rename from framework/yii/db/QueryBuilder.php
rename to framework/db/QueryBuilder.php
diff --git a/framework/yii/db/QueryInterface.php b/framework/db/QueryInterface.php
similarity index 100%
rename from framework/yii/db/QueryInterface.php
rename to framework/db/QueryInterface.php
diff --git a/framework/yii/db/QueryTrait.php b/framework/db/QueryTrait.php
similarity index 100%
rename from framework/yii/db/QueryTrait.php
rename to framework/db/QueryTrait.php
diff --git a/framework/yii/db/Schema.php b/framework/db/Schema.php
similarity index 100%
rename from framework/yii/db/Schema.php
rename to framework/db/Schema.php
diff --git a/framework/yii/db/StaleObjectException.php b/framework/db/StaleObjectException.php
similarity index 100%
rename from framework/yii/db/StaleObjectException.php
rename to framework/db/StaleObjectException.php
diff --git a/framework/yii/db/TableSchema.php b/framework/db/TableSchema.php
similarity index 100%
rename from framework/yii/db/TableSchema.php
rename to framework/db/TableSchema.php
diff --git a/framework/yii/db/Transaction.php b/framework/db/Transaction.php
similarity index 100%
rename from framework/yii/db/Transaction.php
rename to framework/db/Transaction.php
diff --git a/framework/yii/db/cubrid/QueryBuilder.php b/framework/db/cubrid/QueryBuilder.php
similarity index 100%
rename from framework/yii/db/cubrid/QueryBuilder.php
rename to framework/db/cubrid/QueryBuilder.php
diff --git a/framework/yii/db/cubrid/Schema.php b/framework/db/cubrid/Schema.php
similarity index 100%
rename from framework/yii/db/cubrid/Schema.php
rename to framework/db/cubrid/Schema.php
diff --git a/framework/yii/db/mssql/PDO.php b/framework/db/mssql/PDO.php
similarity index 100%
rename from framework/yii/db/mssql/PDO.php
rename to framework/db/mssql/PDO.php
diff --git a/framework/yii/db/mssql/QueryBuilder.php b/framework/db/mssql/QueryBuilder.php
similarity index 100%
rename from framework/yii/db/mssql/QueryBuilder.php
rename to framework/db/mssql/QueryBuilder.php
diff --git a/framework/yii/db/mssql/Schema.php b/framework/db/mssql/Schema.php
similarity index 100%
rename from framework/yii/db/mssql/Schema.php
rename to framework/db/mssql/Schema.php
diff --git a/framework/yii/db/mssql/SqlsrvPDO.php b/framework/db/mssql/SqlsrvPDO.php
similarity index 100%
rename from framework/yii/db/mssql/SqlsrvPDO.php
rename to framework/db/mssql/SqlsrvPDO.php
diff --git a/framework/yii/db/mssql/TableSchema.php b/framework/db/mssql/TableSchema.php
similarity index 100%
rename from framework/yii/db/mssql/TableSchema.php
rename to framework/db/mssql/TableSchema.php
diff --git a/framework/yii/db/mysql/QueryBuilder.php b/framework/db/mysql/QueryBuilder.php
similarity index 100%
rename from framework/yii/db/mysql/QueryBuilder.php
rename to framework/db/mysql/QueryBuilder.php
diff --git a/framework/yii/db/mysql/Schema.php b/framework/db/mysql/Schema.php
similarity index 100%
rename from framework/yii/db/mysql/Schema.php
rename to framework/db/mysql/Schema.php
diff --git a/framework/yii/db/oci/QueryBuilder.php b/framework/db/oci/QueryBuilder.php
similarity index 100%
rename from framework/yii/db/oci/QueryBuilder.php
rename to framework/db/oci/QueryBuilder.php
diff --git a/framework/yii/db/oci/Schema.php b/framework/db/oci/Schema.php
similarity index 100%
rename from framework/yii/db/oci/Schema.php
rename to framework/db/oci/Schema.php
diff --git a/framework/yii/db/pgsql/QueryBuilder.php b/framework/db/pgsql/QueryBuilder.php
similarity index 100%
rename from framework/yii/db/pgsql/QueryBuilder.php
rename to framework/db/pgsql/QueryBuilder.php
diff --git a/framework/yii/db/pgsql/Schema.php b/framework/db/pgsql/Schema.php
similarity index 100%
rename from framework/yii/db/pgsql/Schema.php
rename to framework/db/pgsql/Schema.php
diff --git a/framework/yii/db/sqlite/QueryBuilder.php b/framework/db/sqlite/QueryBuilder.php
similarity index 100%
rename from framework/yii/db/sqlite/QueryBuilder.php
rename to framework/db/sqlite/QueryBuilder.php
diff --git a/framework/yii/db/sqlite/Schema.php b/framework/db/sqlite/Schema.php
similarity index 100%
rename from framework/yii/db/sqlite/Schema.php
rename to framework/db/sqlite/Schema.php
diff --git a/framework/yii/grid/ActionColumn.php b/framework/grid/ActionColumn.php
similarity index 100%
rename from framework/yii/grid/ActionColumn.php
rename to framework/grid/ActionColumn.php
diff --git a/framework/yii/grid/CheckboxColumn.php b/framework/grid/CheckboxColumn.php
similarity index 100%
rename from framework/yii/grid/CheckboxColumn.php
rename to framework/grid/CheckboxColumn.php
diff --git a/framework/yii/grid/Column.php b/framework/grid/Column.php
similarity index 100%
rename from framework/yii/grid/Column.php
rename to framework/grid/Column.php
diff --git a/framework/yii/grid/DataColumn.php b/framework/grid/DataColumn.php
similarity index 100%
rename from framework/yii/grid/DataColumn.php
rename to framework/grid/DataColumn.php
diff --git a/framework/yii/grid/GridView.php b/framework/grid/GridView.php
similarity index 100%
rename from framework/yii/grid/GridView.php
rename to framework/grid/GridView.php
diff --git a/framework/yii/grid/GridViewAsset.php b/framework/grid/GridViewAsset.php
similarity index 100%
rename from framework/yii/grid/GridViewAsset.php
rename to framework/grid/GridViewAsset.php
diff --git a/framework/yii/grid/SerialColumn.php b/framework/grid/SerialColumn.php
similarity index 100%
rename from framework/yii/grid/SerialColumn.php
rename to framework/grid/SerialColumn.php
diff --git a/framework/yii/helpers/ArrayHelper.php b/framework/helpers/ArrayHelper.php
similarity index 100%
rename from framework/yii/helpers/ArrayHelper.php
rename to framework/helpers/ArrayHelper.php
diff --git a/framework/yii/helpers/BaseArrayHelper.php b/framework/helpers/BaseArrayHelper.php
similarity index 100%
rename from framework/yii/helpers/BaseArrayHelper.php
rename to framework/helpers/BaseArrayHelper.php
diff --git a/framework/yii/helpers/BaseConsole.php b/framework/helpers/BaseConsole.php
similarity index 100%
rename from framework/yii/helpers/BaseConsole.php
rename to framework/helpers/BaseConsole.php
diff --git a/framework/yii/helpers/BaseFileHelper.php b/framework/helpers/BaseFileHelper.php
similarity index 100%
rename from framework/yii/helpers/BaseFileHelper.php
rename to framework/helpers/BaseFileHelper.php
diff --git a/framework/yii/helpers/BaseHtml.php b/framework/helpers/BaseHtml.php
similarity index 100%
rename from framework/yii/helpers/BaseHtml.php
rename to framework/helpers/BaseHtml.php
diff --git a/framework/yii/helpers/BaseHtmlPurifier.php b/framework/helpers/BaseHtmlPurifier.php
similarity index 100%
rename from framework/yii/helpers/BaseHtmlPurifier.php
rename to framework/helpers/BaseHtmlPurifier.php
diff --git a/framework/yii/helpers/BaseInflector.php b/framework/helpers/BaseInflector.php
similarity index 100%
rename from framework/yii/helpers/BaseInflector.php
rename to framework/helpers/BaseInflector.php
diff --git a/framework/yii/helpers/BaseJson.php b/framework/helpers/BaseJson.php
similarity index 100%
rename from framework/yii/helpers/BaseJson.php
rename to framework/helpers/BaseJson.php
diff --git a/framework/yii/helpers/BaseMarkdown.php b/framework/helpers/BaseMarkdown.php
similarity index 100%
rename from framework/yii/helpers/BaseMarkdown.php
rename to framework/helpers/BaseMarkdown.php
diff --git a/framework/yii/helpers/BaseSecurity.php b/framework/helpers/BaseSecurity.php
similarity index 100%
rename from framework/yii/helpers/BaseSecurity.php
rename to framework/helpers/BaseSecurity.php
diff --git a/framework/yii/helpers/BaseStringHelper.php b/framework/helpers/BaseStringHelper.php
similarity index 100%
rename from framework/yii/helpers/BaseStringHelper.php
rename to framework/helpers/BaseStringHelper.php
diff --git a/framework/yii/helpers/BaseVarDumper.php b/framework/helpers/BaseVarDumper.php
similarity index 100%
rename from framework/yii/helpers/BaseVarDumper.php
rename to framework/helpers/BaseVarDumper.php
diff --git a/framework/yii/helpers/Console.php b/framework/helpers/Console.php
similarity index 100%
rename from framework/yii/helpers/Console.php
rename to framework/helpers/Console.php
diff --git a/framework/yii/helpers/FileHelper.php b/framework/helpers/FileHelper.php
similarity index 100%
rename from framework/yii/helpers/FileHelper.php
rename to framework/helpers/FileHelper.php
diff --git a/framework/yii/helpers/Html.php b/framework/helpers/Html.php
similarity index 100%
rename from framework/yii/helpers/Html.php
rename to framework/helpers/Html.php
diff --git a/framework/yii/helpers/HtmlPurifier.php b/framework/helpers/HtmlPurifier.php
similarity index 100%
rename from framework/yii/helpers/HtmlPurifier.php
rename to framework/helpers/HtmlPurifier.php
diff --git a/framework/yii/helpers/Inflector.php b/framework/helpers/Inflector.php
similarity index 100%
rename from framework/yii/helpers/Inflector.php
rename to framework/helpers/Inflector.php
diff --git a/framework/yii/helpers/Json.php b/framework/helpers/Json.php
similarity index 100%
rename from framework/yii/helpers/Json.php
rename to framework/helpers/Json.php
diff --git a/framework/yii/helpers/Markdown.php b/framework/helpers/Markdown.php
similarity index 100%
rename from framework/yii/helpers/Markdown.php
rename to framework/helpers/Markdown.php
diff --git a/framework/yii/helpers/Security.php b/framework/helpers/Security.php
similarity index 100%
rename from framework/yii/helpers/Security.php
rename to framework/helpers/Security.php
diff --git a/framework/yii/helpers/StringHelper.php b/framework/helpers/StringHelper.php
similarity index 100%
rename from framework/yii/helpers/StringHelper.php
rename to framework/helpers/StringHelper.php
diff --git a/framework/yii/helpers/VarDumper.php b/framework/helpers/VarDumper.php
similarity index 100%
rename from framework/yii/helpers/VarDumper.php
rename to framework/helpers/VarDumper.php
diff --git a/framework/yii/helpers/mimeTypes.php b/framework/helpers/mimeTypes.php
similarity index 100%
rename from framework/yii/helpers/mimeTypes.php
rename to framework/helpers/mimeTypes.php
diff --git a/framework/yii/i18n/DbMessageSource.php b/framework/i18n/DbMessageSource.php
similarity index 100%
rename from framework/yii/i18n/DbMessageSource.php
rename to framework/i18n/DbMessageSource.php
diff --git a/framework/yii/i18n/Formatter.php b/framework/i18n/Formatter.php
similarity index 100%
rename from framework/yii/i18n/Formatter.php
rename to framework/i18n/Formatter.php
diff --git a/framework/yii/i18n/GettextFile.php b/framework/i18n/GettextFile.php
similarity index 100%
rename from framework/yii/i18n/GettextFile.php
rename to framework/i18n/GettextFile.php
diff --git a/framework/yii/i18n/GettextMessageSource.php b/framework/i18n/GettextMessageSource.php
similarity index 100%
rename from framework/yii/i18n/GettextMessageSource.php
rename to framework/i18n/GettextMessageSource.php
diff --git a/framework/yii/i18n/GettextMoFile.php b/framework/i18n/GettextMoFile.php
similarity index 100%
rename from framework/yii/i18n/GettextMoFile.php
rename to framework/i18n/GettextMoFile.php
diff --git a/framework/yii/i18n/GettextPoFile.php b/framework/i18n/GettextPoFile.php
similarity index 100%
rename from framework/yii/i18n/GettextPoFile.php
rename to framework/i18n/GettextPoFile.php
diff --git a/framework/yii/i18n/I18N.php b/framework/i18n/I18N.php
similarity index 100%
rename from framework/yii/i18n/I18N.php
rename to framework/i18n/I18N.php
diff --git a/framework/yii/i18n/MessageFormatter.php b/framework/i18n/MessageFormatter.php
similarity index 100%
rename from framework/yii/i18n/MessageFormatter.php
rename to framework/i18n/MessageFormatter.php
diff --git a/framework/yii/i18n/MessageSource.php b/framework/i18n/MessageSource.php
similarity index 100%
rename from framework/yii/i18n/MessageSource.php
rename to framework/i18n/MessageSource.php
diff --git a/framework/yii/i18n/MissingTranslationEvent.php b/framework/i18n/MissingTranslationEvent.php
similarity index 100%
rename from framework/yii/i18n/MissingTranslationEvent.php
rename to framework/i18n/MissingTranslationEvent.php
diff --git a/framework/yii/i18n/PhpMessageSource.php b/framework/i18n/PhpMessageSource.php
similarity index 100%
rename from framework/yii/i18n/PhpMessageSource.php
rename to framework/i18n/PhpMessageSource.php
diff --git a/framework/yii/log/DbTarget.php b/framework/log/DbTarget.php
similarity index 100%
rename from framework/yii/log/DbTarget.php
rename to framework/log/DbTarget.php
diff --git a/framework/yii/log/EmailTarget.php b/framework/log/EmailTarget.php
similarity index 100%
rename from framework/yii/log/EmailTarget.php
rename to framework/log/EmailTarget.php
diff --git a/framework/yii/log/FileTarget.php b/framework/log/FileTarget.php
similarity index 100%
rename from framework/yii/log/FileTarget.php
rename to framework/log/FileTarget.php
diff --git a/framework/yii/log/Logger.php b/framework/log/Logger.php
similarity index 100%
rename from framework/yii/log/Logger.php
rename to framework/log/Logger.php
diff --git a/framework/yii/log/Target.php b/framework/log/Target.php
similarity index 100%
rename from framework/yii/log/Target.php
rename to framework/log/Target.php
diff --git a/framework/yii/mail/BaseMailer.php b/framework/mail/BaseMailer.php
similarity index 100%
rename from framework/yii/mail/BaseMailer.php
rename to framework/mail/BaseMailer.php
diff --git a/framework/yii/mail/BaseMessage.php b/framework/mail/BaseMessage.php
similarity index 100%
rename from framework/yii/mail/BaseMessage.php
rename to framework/mail/BaseMessage.php
diff --git a/framework/yii/mail/MailerInterface.php b/framework/mail/MailerInterface.php
similarity index 100%
rename from framework/yii/mail/MailerInterface.php
rename to framework/mail/MailerInterface.php
diff --git a/framework/yii/mail/MessageInterface.php b/framework/mail/MessageInterface.php
similarity index 100%
rename from framework/yii/mail/MessageInterface.php
rename to framework/mail/MessageInterface.php
diff --git a/framework/yii/messages/config.php b/framework/messages/config.php
similarity index 100%
rename from framework/yii/messages/config.php
rename to framework/messages/config.php
diff --git a/framework/yii/messages/da/yii.php b/framework/messages/da/yii.php
similarity index 100%
rename from framework/yii/messages/da/yii.php
rename to framework/messages/da/yii.php
diff --git a/framework/yii/messages/de/yii.php b/framework/messages/de/yii.php
similarity index 100%
rename from framework/yii/messages/de/yii.php
rename to framework/messages/de/yii.php
diff --git a/framework/yii/messages/es/yii.php b/framework/messages/es/yii.php
similarity index 100%
rename from framework/yii/messages/es/yii.php
rename to framework/messages/es/yii.php
diff --git a/framework/yii/messages/it/yii.php b/framework/messages/it/yii.php
similarity index 100%
rename from framework/yii/messages/it/yii.php
rename to framework/messages/it/yii.php
diff --git a/framework/yii/messages/ja/yii.php b/framework/messages/ja/yii.php
similarity index 100%
rename from framework/yii/messages/ja/yii.php
rename to framework/messages/ja/yii.php
diff --git a/framework/yii/messages/pl/yii.php b/framework/messages/pl/yii.php
similarity index 100%
rename from framework/yii/messages/pl/yii.php
rename to framework/messages/pl/yii.php
diff --git a/framework/yii/messages/pt-BR/yii.php b/framework/messages/pt-BR/yii.php
similarity index 100%
rename from framework/yii/messages/pt-BR/yii.php
rename to framework/messages/pt-BR/yii.php
diff --git a/framework/yii/messages/ro/yii.php b/framework/messages/ro/yii.php
similarity index 100%
rename from framework/yii/messages/ro/yii.php
rename to framework/messages/ro/yii.php
diff --git a/framework/yii/messages/ru/yii.php b/framework/messages/ru/yii.php
similarity index 100%
rename from framework/yii/messages/ru/yii.php
rename to framework/messages/ru/yii.php
diff --git a/framework/yii/messages/zh_cn/yii.php b/framework/messages/zh_cn/yii.php
similarity index 100%
rename from framework/yii/messages/zh_cn/yii.php
rename to framework/messages/zh_cn/yii.php
diff --git a/framework/yii/mutex/DbMutex.php b/framework/mutex/DbMutex.php
similarity index 100%
rename from framework/yii/mutex/DbMutex.php
rename to framework/mutex/DbMutex.php
diff --git a/framework/yii/mutex/FileMutex.php b/framework/mutex/FileMutex.php
similarity index 100%
rename from framework/yii/mutex/FileMutex.php
rename to framework/mutex/FileMutex.php
diff --git a/framework/yii/mutex/Mutex.php b/framework/mutex/Mutex.php
similarity index 100%
rename from framework/yii/mutex/Mutex.php
rename to framework/mutex/Mutex.php
diff --git a/framework/yii/mutex/MysqlMutex.php b/framework/mutex/MysqlMutex.php
similarity index 100%
rename from framework/yii/mutex/MysqlMutex.php
rename to framework/mutex/MysqlMutex.php
diff --git a/framework/yii/rbac/Assignment.php b/framework/rbac/Assignment.php
similarity index 100%
rename from framework/yii/rbac/Assignment.php
rename to framework/rbac/Assignment.php
diff --git a/framework/yii/rbac/DbManager.php b/framework/rbac/DbManager.php
similarity index 100%
rename from framework/yii/rbac/DbManager.php
rename to framework/rbac/DbManager.php
diff --git a/framework/yii/rbac/Item.php b/framework/rbac/Item.php
similarity index 100%
rename from framework/yii/rbac/Item.php
rename to framework/rbac/Item.php
diff --git a/framework/yii/rbac/Manager.php b/framework/rbac/Manager.php
similarity index 100%
rename from framework/yii/rbac/Manager.php
rename to framework/rbac/Manager.php
diff --git a/framework/yii/rbac/PhpManager.php b/framework/rbac/PhpManager.php
similarity index 100%
rename from framework/yii/rbac/PhpManager.php
rename to framework/rbac/PhpManager.php
diff --git a/framework/yii/rbac/schema-mssql.sql b/framework/rbac/schema-mssql.sql
similarity index 100%
rename from framework/yii/rbac/schema-mssql.sql
rename to framework/rbac/schema-mssql.sql
diff --git a/framework/yii/rbac/schema-mysql.sql b/framework/rbac/schema-mysql.sql
similarity index 100%
rename from framework/yii/rbac/schema-mysql.sql
rename to framework/rbac/schema-mysql.sql
diff --git a/framework/yii/rbac/schema-oci.sql b/framework/rbac/schema-oci.sql
similarity index 100%
rename from framework/yii/rbac/schema-oci.sql
rename to framework/rbac/schema-oci.sql
diff --git a/framework/yii/rbac/schema-pgsql.sql b/framework/rbac/schema-pgsql.sql
similarity index 100%
rename from framework/yii/rbac/schema-pgsql.sql
rename to framework/rbac/schema-pgsql.sql
diff --git a/framework/yii/rbac/schema-sqlite.sql b/framework/rbac/schema-sqlite.sql
similarity index 100%
rename from framework/yii/rbac/schema-sqlite.sql
rename to framework/rbac/schema-sqlite.sql
diff --git a/framework/yii/requirements/YiiRequirementChecker.php b/framework/requirements/YiiRequirementChecker.php
similarity index 100%
rename from framework/yii/requirements/YiiRequirementChecker.php
rename to framework/requirements/YiiRequirementChecker.php
diff --git a/framework/yii/requirements/requirements.php b/framework/requirements/requirements.php
similarity index 100%
rename from framework/yii/requirements/requirements.php
rename to framework/requirements/requirements.php
diff --git a/framework/yii/requirements/views/console/index.php b/framework/requirements/views/console/index.php
similarity index 100%
rename from framework/yii/requirements/views/console/index.php
rename to framework/requirements/views/console/index.php
diff --git a/framework/yii/requirements/views/web/css.php b/framework/requirements/views/web/css.php
similarity index 100%
rename from framework/yii/requirements/views/web/css.php
rename to framework/requirements/views/web/css.php
diff --git a/framework/requirements/views/web/index.php b/framework/requirements/views/web/index.php
new file mode 100644
index 0000000..287d4bb
--- /dev/null
+++ b/framework/requirements/views/web/index.php
@@ -0,0 +1,82 @@
+<?php
+/* @var YiiRequirementChecker $this */
+/* @var array $summary */
+/* @var array[] $requirements */
+?>
+<!DOCTYPE html>
+<html lang="en">
+<head>
+	<meta charset="utf-8"/>
+	<title>Yii Application Requirement Checker</title>
+	<?php $this->renderViewFile(dirname(__FILE__) . '/css.php'); ?>
+</head>
+<body>
+<div class="container">
+	<div class="header">
+		<h1>Yii Application Requirement Checker</h1>
+	</div>
+	<hr>
+
+	<div class="content">
+		<h3>Description</h3>
+		<p>
+		This script checks if your server configuration meets the requirements
+		for running Yii application.
+		It checks if the server is running the right version of PHP,
+		if appropriate PHP extensions have been loaded, and if php.ini file settings are correct.
+		</p>
+		<p>
+		There are two kinds of requirements being checked. Mandatory requirements are those that have to be met
+		to allow Yii to work as expected. There are also some optional requirements beeing checked which will
+		show you a warning when they do not meet. You can use Yii framework without them but some specific
+		functionality may be not available in this case.
+		</p>
+
+		<h3>Conclusion</h3>
+		<?php if ($summary['errors'] > 0): ?>
+			<div class="alert alert-error">
+				<strong>Unfortunately your server configuration does not satisfy the requirements by this application.<br>Please refer to the table below for detailed explanation.</strong>
+			</div>
+		<?php elseif ($summary['warnings'] > 0): ?>
+			<div class="alert alert-info">
+				<strong>Your server configuration satisfies the minimum requirements by this application.<br>Please pay attention to the warnings listed below and check if your application will use the corresponding features.</strong>
+			</div>
+		<?php else: ?>
+			<div class="alert alert-success">
+				<strong>Congratulations! Your server configuration satisfies all requirements.</strong>
+			</div>
+		<?php endif; ?>
+
+		<h3>Details</h3>
+
+		<table class="table table-bordered">
+			<tr><th>Name</th><th>Result</th><th>Required By</th><th>Memo</th></tr>
+			<?php foreach($requirements as $requirement): ?>
+			<tr class="<?php echo $requirement['condition'] ? 'success' : ($requirement['mandatory'] ? 'error' : 'warning') ?>">
+				<td>
+				<?php echo $requirement['name'] ?>
+				</td>
+				<td>
+				<span class="result"><?php echo $requirement['condition'] ? 'Passed' : ($requirement['mandatory'] ? 'Failed' : 'Warning') ?></span>
+				</td>
+				<td>
+				<?php echo $requirement['by'] ?>
+				</td>
+				<td>
+				<?php echo $requirement['memo'] ?>
+				</td>
+			</tr>
+			<?php endforeach; ?>
+		</table>
+
+	</div>
+
+	<hr>
+
+	<div class="footer">
+		<p>Server: <?php echo $this->getServerInfo() . ' ' . $this->getNowDate() ?></p>
+		<p>Powered by <a href="http://www.yiiframework.com/" rel="external">Yii Framework</a></p>
+	</div>
+</div>
+</body>
+</html>
diff --git a/framework/yii/test/DbFixtureManager.php b/framework/test/DbFixtureManager.php
similarity index 100%
rename from framework/yii/test/DbFixtureManager.php
rename to framework/test/DbFixtureManager.php
diff --git a/framework/yii/test/DbTestTrait.php b/framework/test/DbTestTrait.php
similarity index 100%
rename from framework/yii/test/DbTestTrait.php
rename to framework/test/DbTestTrait.php
diff --git a/framework/yii/validators/BooleanValidator.php b/framework/validators/BooleanValidator.php
similarity index 100%
rename from framework/yii/validators/BooleanValidator.php
rename to framework/validators/BooleanValidator.php
diff --git a/framework/yii/validators/CompareValidator.php b/framework/validators/CompareValidator.php
similarity index 100%
rename from framework/yii/validators/CompareValidator.php
rename to framework/validators/CompareValidator.php
diff --git a/framework/yii/validators/DateValidator.php b/framework/validators/DateValidator.php
similarity index 100%
rename from framework/yii/validators/DateValidator.php
rename to framework/validators/DateValidator.php
diff --git a/framework/yii/validators/DefaultValueValidator.php b/framework/validators/DefaultValueValidator.php
similarity index 100%
rename from framework/yii/validators/DefaultValueValidator.php
rename to framework/validators/DefaultValueValidator.php
diff --git a/framework/validators/EmailValidator.php b/framework/validators/EmailValidator.php
new file mode 100644
index 0000000..e5d9b75
--- /dev/null
+++ b/framework/validators/EmailValidator.php
@@ -0,0 +1,116 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\validators;
+
+use Yii;
+use yii\base\InvalidConfigException;
+use yii\helpers\Html;
+use yii\web\JsExpression;
+use yii\helpers\Json;
+
+/**
+ * EmailValidator validates that the attribute value is a valid email address.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class EmailValidator extends Validator
+{
+	/**
+	 * @var string the regular expression used to validate the attribute value.
+	 * @see http://www.regular-expressions.info/email.html
+	 */
+	public $pattern = '/^[a-zA-Z0-9!#$%&\'*+\\/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&\'*+\\/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?$/';
+	/**
+	 * @var string the regular expression used to validate email addresses with the name part.
+	 * This property is used only when [[allowName]] is true.
+	 * @see allowName
+	 */
+	public $fullPattern = '/^[^@]*<[a-zA-Z0-9!#$%&\'*+\\/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&\'*+\\/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?>$/';
+	/**
+	 * @var boolean whether to allow name in the email address (e.g. "John Smith <john.smith@example.com>"). Defaults to false.
+	 * @see fullPattern
+	 */
+	public $allowName = false;
+	/**
+	 * @var boolean whether to check whether the emails domain exists and has either an A or MX record.
+	 * Be aware of the fact that this check can fail due to temporary DNS problems even if the email address is
+	 * valid and an email would be deliverable. Defaults to false.
+	 */
+	public $checkDNS = false;
+	/**
+	 * @var boolean whether validation process should take into account IDN (internationalized domain
+	 * names). Defaults to false meaning that validation of emails containing IDN will always fail.
+	 * Note that in order to use IDN validation you have to install and enable `intl` PHP extension,
+	 * otherwise an exception would be thrown.
+	 */
+	public $enableIDN = false;
+
+
+	/**
+	 * @inheritdoc
+	 */
+	public function init()
+	{
+		parent::init();
+		if ($this->enableIDN && !function_exists('idn_to_ascii')) {
+			throw new InvalidConfigException('In order to use IDN validation intl extension must be installed and enabled.');
+		}
+		if ($this->message === null) {
+			$this->message = Yii::t('yii', '{attribute} is not a valid email address.');
+		}
+	}
+
+	/**
+	 * @inheritdoc
+	 */
+	protected function validateValue($value)
+	{
+		// make sure string length is limited to avoid DOS attacks
+		if (!is_string($value) || strlen($value) >= 320) {
+			$valid = false;
+		} elseif (!preg_match('/^(.*<?)(.*)@(.*)(>?)$/', $value, $matches)) {
+			$valid = false;
+		} else {
+			$domain = $matches[3];
+			if ($this->enableIDN) {
+				$value = $matches[1] . idn_to_ascii($matches[2]) . '@' . idn_to_ascii($domain) . $matches[4];
+			}
+			$valid = preg_match($this->pattern, $value) || $this->allowName && preg_match($this->fullPattern, $value);
+			if ($valid && $this->checkDNS) {
+				$valid = checkdnsrr($domain, 'MX') || checkdnsrr($domain, 'A');
+			}
+		}
+		return $valid ? null : [$this->message, []];
+	}
+
+	/**
+	 * @inheritdoc
+	 */
+	public function clientValidateAttribute($object, $attribute, $view)
+	{
+		$options = [
+			'pattern' => new JsExpression($this->pattern),
+			'fullPattern' => new JsExpression($this->fullPattern),
+			'allowName' => $this->allowName,
+			'message' => strtr($this->message, [
+				'{attribute}' => $object->getAttributeLabel($attribute),
+			]),
+			'enableIDN' => (boolean)$this->enableIDN,
+		];
+		if ($this->skipOnEmpty) {
+			$options['skipOnEmpty'] = 1;
+		}
+
+		ValidationAsset::register($view);
+		if ($this->enableIDN) {
+			PunycodeAsset::register($view);
+		}
+		return 'yii.validation.email(value, messages, ' . Json::encode($options) . ');';
+	}
+}
diff --git a/framework/validators/ExistValidator.php b/framework/validators/ExistValidator.php
new file mode 100644
index 0000000..caefc49
--- /dev/null
+++ b/framework/validators/ExistValidator.php
@@ -0,0 +1,122 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\validators;
+
+use Yii;
+use yii\base\InvalidConfigException;
+
+/**
+ * ExistValidator validates that the attribute value exists in a table.
+ *
+ * ExistValidator checks if the value being validated can be found in the table column specified by
+ * the ActiveRecord class [[targetClass]] and the attribute [[targetAttribute]].
+ *
+ * This validator is often used to verify that a foreign key contains a value
+ * that can be found in the foreign table.
+ *
+ * The followings are examples of validation rules using this validator:
+ *
+ * ```php
+ * // a1 needs to exist
+ * ['a1', 'exist']
+ * // a1 needs to exist, but its value will use a2 to check for the existence
+ * ['a1', 'exist', 'targetAttribute' => 'a2']
+ * // a1 and a2 need to exist together, and they both will receive error message
+ * [['a1', 'a2'], 'exist', 'targetAttribute' => ['a1', 'a2']]
+ * // a1 and a2 need to exist together, only a1 will receive error message
+ * ['a1', 'exist', 'targetAttribute' => ['a1', 'a2']]
+ * // a1 needs to exist by checking the existence of both a2 and a3 (using a1 value)
+ * ['a1', 'exist', 'targetAttribute' => ['a2', 'a1' => 'a3']]
+ * ```
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class ExistValidator extends Validator
+{
+	/**
+	 * @var string the name of the ActiveRecord class that should be used to validate the existence
+	 * of the current attribute value. It not set, it will use the ActiveRecord class of the attribute being validated.
+	 * @see targetAttribute
+	 */
+	public $targetClass;
+	/**
+	 * @var string|array the name of the ActiveRecord attribute that should be used to
+	 * validate the existence of the current attribute value. If not set, it will use the name
+	 * of the attribute currently being validated. You may use an array to validate the existence
+	 * of multiple columns at the same time. The array values are the attributes that will be
+	 * used to validate the existence, while the array keys are the attributes whose values are to be validated.
+	 * If the key and the value are the same, you can just specify the value.
+	 */
+	public $targetAttribute;
+
+
+	/**
+	 * @inheritdoc
+	 */
+	public function init()
+	{
+		parent::init();
+		if ($this->message === null) {
+			$this->message = Yii::t('yii', '{attribute} is invalid.');
+		}
+	}
+
+	/**
+	 * @inheritdoc
+	 */
+	public function validateAttribute($object, $attribute)
+	{
+		/** @var \yii\db\ActiveRecordInterface $targetClass */
+		$targetClass = $this->targetClass === null ? get_class($object) : $this->targetClass;
+		$targetAttribute = $this->targetAttribute === null ? $attribute : $this->targetAttribute;
+
+		if (is_array($targetAttribute)) {
+			$params = [];
+			foreach ($targetAttribute as $k => $v) {
+				$params[$v] = is_integer($k) ? $object->$v : $object->$k;
+			}
+		} else {
+			$params = [$targetAttribute => $object->$attribute];
+		}
+
+		foreach ($params as $value) {
+			if (is_array($value)) {
+				$this->addError($object, $attribute, Yii::t('yii', '{attribute} is invalid.'));
+				return;
+			}
+		}
+
+		/** @var \yii\db\ActiveRecordInterface $className */
+		if (!$targetClass::find()->where($params)->exists()) {
+			$this->addError($object, $attribute, $this->message);
+		}
+	}
+
+	/**
+	 * @inheritdoc
+	 */
+	protected function validateValue($value)
+	{
+		if (is_array($value)) {
+			return [$this->message, []];
+		}
+		if ($this->targetClass === null) {
+			throw new InvalidConfigException('The "className" property must be set.');
+		}
+		if (!is_string($this->targetAttribute)) {
+			throw new InvalidConfigException('The "attributeName" property must be configured as a string.');
+		}
+
+		/** @var \yii\db\ActiveRecordInterface $targetClass */
+		$targetClass = $this->targetClass;
+		$query = $targetClass::find();
+		$query->where([$this->targetAttribute => $value]);
+		return $query->exists() ? null : [$this->message, []];
+	}
+}
diff --git a/framework/yii/validators/FileValidator.php b/framework/validators/FileValidator.php
similarity index 100%
rename from framework/yii/validators/FileValidator.php
rename to framework/validators/FileValidator.php
diff --git a/framework/yii/validators/FilterValidator.php b/framework/validators/FilterValidator.php
similarity index 100%
rename from framework/yii/validators/FilterValidator.php
rename to framework/validators/FilterValidator.php
diff --git a/framework/yii/validators/ImageValidator.php b/framework/validators/ImageValidator.php
similarity index 100%
rename from framework/yii/validators/ImageValidator.php
rename to framework/validators/ImageValidator.php
diff --git a/framework/validators/InlineValidator.php b/framework/validators/InlineValidator.php
new file mode 100644
index 0000000..b769e4e
--- /dev/null
+++ b/framework/validators/InlineValidator.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\validators;
+
+/**
+ * InlineValidator represents a validator which is defined as a method in the object being validated.
+ *
+ * The validation method must have the following signature:
+ *
+ * ~~~
+ * function foo($attribute, $params)
+ * ~~~
+ *
+ * where `$attribute` refers to the name of the attribute being validated, while `$params`
+ * is an array representing the additional parameters supplied in the validation rule.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class InlineValidator extends Validator
+{
+	/**
+	 * @var string|\Closure an anonymous function or the name of a model class method that will be
+	 * called to perform the actual validation. The signature of the method should be like the following:
+	 *
+	 * ~~~
+	 * function foo($attribute, $params)
+	 * ~~~
+	 */
+	public $method;
+	/**
+	 * @var array additional parameters that are passed to the validation method
+	 */
+	public $params;
+	/**
+	 * @var string|\Closure an anonymous function or the name of a model class method that returns the client validation code.
+	 * The signature of the method should be like the following:
+	 *
+	 * ~~~
+	 * function foo($attribute, $params)
+	 * {
+	 *     return "javascript";
+	 * }
+	 * ~~~
+	 *
+	 * where `$attribute` refers to the attribute name to be validated.
+	 *
+	 * Please refer to [[clientValidateAttribute()]] for details on how to return client validation code.
+	 */
+	public $clientValidate;
+
+	/**
+	 * @inheritdoc
+	 */
+	public function validateAttribute($object, $attribute)
+	{
+		$method = $this->method;
+		if (is_string($method)) {
+			$method = [$object, $method];
+		}
+		call_user_func($method, $attribute, $this->params);
+	}
+
+	/**
+	 * @inheritdoc
+	 */
+	public function clientValidateAttribute($object, $attribute, $view)
+	{
+		if ($this->clientValidate !== null) {
+			$method = $this->clientValidate;
+			if (is_string($method)) {
+				$method = [$object, $method];
+			}
+			return call_user_func($method, $attribute, $this->params);
+		} else {
+			return null;
+		}
+	}
+}
diff --git a/framework/yii/validators/NumberValidator.php b/framework/validators/NumberValidator.php
similarity index 100%
rename from framework/yii/validators/NumberValidator.php
rename to framework/validators/NumberValidator.php
diff --git a/framework/yii/validators/PunycodeAsset.php b/framework/validators/PunycodeAsset.php
similarity index 100%
rename from framework/yii/validators/PunycodeAsset.php
rename to framework/validators/PunycodeAsset.php
diff --git a/framework/validators/RangeValidator.php b/framework/validators/RangeValidator.php
new file mode 100644
index 0000000..a4da139
--- /dev/null
+++ b/framework/validators/RangeValidator.php
@@ -0,0 +1,87 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\validators;
+
+use Yii;
+use yii\base\InvalidConfigException;
+use yii\helpers\Html;
+
+/**
+ * RangeValidator validates that the attribute value is among a list of values.
+ *
+ * The range can be specified via the [[range]] property.
+ * If the [[not]] property is set true, the validator will ensure the attribute value
+ * is NOT among the specified range.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class RangeValidator extends Validator
+{
+	/**
+	 * @var array list of valid values that the attribute value should be among
+	 */
+	public $range;
+	/**
+	 * @var boolean whether the comparison is strict (both type and value must be the same)
+	 */
+	public $strict = false;
+	/**
+	 * @var boolean whether to invert the validation logic. Defaults to false. If set to true,
+	 * the attribute value should NOT be among the list of values defined via [[range]].
+	 **/
+	public $not = false;
+
+	/**
+	 * @inheritdoc
+	 */
+	public function init()
+	{
+		parent::init();
+		if (!is_array($this->range)) {
+			throw new InvalidConfigException('The "range" property must be set.');
+		}
+		if ($this->message === null) {
+			$this->message = Yii::t('yii', '{attribute} is invalid.');
+		}
+	}
+
+	/**
+	 * @inheritdoc
+	 */
+	protected function validateValue($value)
+	{
+		$valid = !$this->not && in_array($value, $this->range, $this->strict)
+			|| $this->not && !in_array($value, $this->range, $this->strict);
+		return $valid ? null : [$this->message, []];
+	}
+
+	/**
+	 * @inheritdoc
+	 */
+	public function clientValidateAttribute($object, $attribute, $view)
+	{
+		$range = [];
+		foreach ($this->range as $value) {
+			$range[] = (string)$value;
+		}
+		$options = [
+			'range' => $range,
+			'not' => $this->not,
+			'message' => strtr($this->message, [
+				'{attribute}' => $object->getAttributeLabel($attribute),
+			]),
+		];
+		if ($this->skipOnEmpty) {
+			$options['skipOnEmpty'] = 1;
+		}
+
+		ValidationAsset::register($view);
+		return 'yii.validation.range(value, messages, ' . json_encode($options) . ');';
+	}
+}
diff --git a/framework/validators/RegularExpressionValidator.php b/framework/validators/RegularExpressionValidator.php
new file mode 100644
index 0000000..28e9bdc
--- /dev/null
+++ b/framework/validators/RegularExpressionValidator.php
@@ -0,0 +1,94 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\validators;
+
+use Yii;
+use yii\base\InvalidConfigException;
+use yii\helpers\Html;
+use yii\web\JsExpression;
+use yii\helpers\Json;
+
+/**
+ * RegularExpressionValidator validates that the attribute value matches the specified [[pattern]].
+ *
+ * If the [[not]] property is set true, the validator will ensure the attribute value do NOT match the [[pattern]].
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class RegularExpressionValidator extends Validator
+{
+	/**
+	 * @var string the regular expression to be matched with
+	 */
+	public $pattern;
+	/**
+	 * @var boolean whether to invert the validation logic. Defaults to false. If set to true,
+	 * the regular expression defined via [[pattern]] should NOT match the attribute value.
+	 **/
+	public $not = false;
+
+	/**
+	 * @inheritdoc
+	 */
+	public function init()
+	{
+		parent::init();
+		if ($this->pattern === null) {
+			throw new InvalidConfigException('The "pattern" property must be set.');
+		}
+		if ($this->message === null) {
+			$this->message = Yii::t('yii', '{attribute} is invalid.');
+		}
+	}
+
+	/**
+	 * @inheritdoc
+	 */
+	protected function validateValue($value)
+	{
+		$valid = !is_array($value) &&
+			(!$this->not && preg_match($this->pattern, $value)
+			|| $this->not && !preg_match($this->pattern, $value));
+		return $valid ? null : [$this->message, []];
+	}
+
+	/**
+	 * @inheritdoc
+	 */
+	public function clientValidateAttribute($object, $attribute, $view)
+	{
+		$pattern = $this->pattern;
+		$pattern = preg_replace('/\\\\x\{?([0-9a-fA-F]+)\}?/', '\u$1', $pattern);
+		$deliminator = substr($pattern, 0, 1);
+		$pos = strrpos($pattern, $deliminator, 1);
+		$flag = substr($pattern, $pos + 1);
+		if ($deliminator !== '/') {
+			$pattern = '/' . str_replace('/', '\\/', substr($pattern, 1, $pos - 1)) . '/';
+		} else {
+			$pattern = substr($pattern, 0, $pos + 1);
+		}
+		if (!empty($flag)) {
+			$pattern .= preg_replace('/[^igm]/', '', $flag);
+		}
+
+		$options = [
+			'pattern' => new JsExpression($pattern),
+			'not' => $this->not,
+			'message' => strtr($this->message, [
+				'{attribute}' => $object->getAttributeLabel($attribute),
+			]),
+		];
+		if ($this->skipOnEmpty) {
+			$options['skipOnEmpty'] = 1;
+		}
+
+		ValidationAsset::register($view);
+		return 'yii.validation.regularExpression(value, messages, ' . Json::encode($options) . ');';
+	}
+}
diff --git a/framework/validators/RequiredValidator.php b/framework/validators/RequiredValidator.php
new file mode 100644
index 0000000..f291f39
--- /dev/null
+++ b/framework/validators/RequiredValidator.php
@@ -0,0 +1,111 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\validators;
+
+use Yii;
+use yii\helpers\Html;
+
+/**
+ * RequiredValidator validates that the specified attribute does not have null or empty value.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class RequiredValidator extends Validator
+{
+	/**
+	 * @var boolean whether to skip this validator if the value being validated is empty.
+	 */
+	public $skipOnEmpty = false;
+	/**
+	 * @var mixed the desired value that the attribute must have.
+	 * If this is null, the validator will validate that the specified attribute is not empty.
+	 * If this is set as a value that is not null, the validator will validate that
+	 * the attribute has a value that is the same as this property value.
+	 * Defaults to null.
+	 * @see strict
+	 */
+	public $requiredValue;
+	/**
+	 * @var boolean whether the comparison between the attribute value and [[requiredValue]] is strict.
+	 * When this is true, both the values and types must match.
+	 * Defaults to false, meaning only the values need to match.
+	 * Note that when [[requiredValue]] is null, if this property is true, the validator will check
+	 * if the attribute value is null; If this property is false, the validator will call [[isEmpty]]
+	 * to check if the attribute value is empty.
+	 */
+	public $strict = false;
+	/**
+	 * @var string the user-defined error message. It may contain the following placeholders which
+	 * will be replaced accordingly by the validator:
+	 *
+	 * - `{attribute}`: the label of the attribute being validated
+	 * - `{value}`: the value of the attribute being validated
+	 * - `{requiredValue}`: the value of [[requiredValue]]
+	 */
+	public $message;
+
+	/**
+	 * @inheritdoc
+	 */
+	public function init()
+	{
+		parent::init();
+		if ($this->message === null) {
+			$this->message = $this->requiredValue === null ? Yii::t('yii', '{attribute} cannot be blank.')
+				: Yii::t('yii', '{attribute} must be "{requiredValue}".');
+		}
+	}
+
+	/**
+	 * @inheritdoc
+	 */
+	protected function validateValue($value)
+	{
+		if ($this->requiredValue === null) {
+			if ($this->strict && $value !== null || !$this->strict && !$this->isEmpty($value, true)) {
+				return null;
+			}
+		} elseif (!$this->strict && $value == $this->requiredValue || $this->strict && $value === $this->requiredValue) {
+			return null;
+		}
+		if ($this->requiredValue === null) {
+			return [$this->message, []];
+		} else {
+			return [$this->message, [
+				'requiredValue' => $this->requiredValue,
+			]];
+		}
+	}
+
+	/**
+	 * @inheritdoc
+	 */
+	public function clientValidateAttribute($object, $attribute, $view)
+	{
+		$options = [];
+		if ($this->requiredValue !== null) {
+			$options['message'] = strtr($this->message, [
+				'{requiredValue}' => $this->requiredValue,
+			]);
+			$options['requiredValue'] = $this->requiredValue;
+		} else {
+			$options['message'] = $this->message;
+		}
+		if ($this->strict) {
+			$options['strict'] = 1;
+		}
+
+		$options['message'] = strtr($options['message'], [
+			'{attribute}' => $object->getAttributeLabel($attribute),
+		]);
+
+		ValidationAsset::register($view);
+		return 'yii.validation.required(value, messages, ' . json_encode($options) . ');';
+	}
+}
diff --git a/framework/yii/validators/SafeValidator.php b/framework/validators/SafeValidator.php
similarity index 100%
rename from framework/yii/validators/SafeValidator.php
rename to framework/validators/SafeValidator.php
diff --git a/framework/yii/validators/StringValidator.php b/framework/validators/StringValidator.php
similarity index 100%
rename from framework/yii/validators/StringValidator.php
rename to framework/validators/StringValidator.php
diff --git a/framework/yii/validators/ExistValidator.php b/framework/validators/UniqueValidator.php
similarity index 60%
rename from framework/yii/validators/ExistValidator.php
rename to framework/validators/UniqueValidator.php
index caefc49..51474b0 100644
--- a/framework/yii/validators/ExistValidator.php
+++ b/framework/validators/UniqueValidator.php
@@ -8,54 +8,50 @@
 namespace yii\validators;
 
 use Yii;
-use yii\base\InvalidConfigException;
+use yii\db\ActiveRecordInterface;
 
 /**
- * ExistValidator validates that the attribute value exists in a table.
+ * UniqueValidator validates that the attribute value is unique in the specified database table.
  *
- * ExistValidator checks if the value being validated can be found in the table column specified by
+ * UniqueValidator checks if the value being validated is unique in the table column specified by
  * the ActiveRecord class [[targetClass]] and the attribute [[targetAttribute]].
  *
- * This validator is often used to verify that a foreign key contains a value
- * that can be found in the foreign table.
- *
  * The followings are examples of validation rules using this validator:
  *
  * ```php
- * // a1 needs to exist
- * ['a1', 'exist']
- * // a1 needs to exist, but its value will use a2 to check for the existence
- * ['a1', 'exist', 'targetAttribute' => 'a2']
- * // a1 and a2 need to exist together, and they both will receive error message
- * [['a1', 'a2'], 'exist', 'targetAttribute' => ['a1', 'a2']]
- * // a1 and a2 need to exist together, only a1 will receive error message
- * ['a1', 'exist', 'targetAttribute' => ['a1', 'a2']]
- * // a1 needs to exist by checking the existence of both a2 and a3 (using a1 value)
- * ['a1', 'exist', 'targetAttribute' => ['a2', 'a1' => 'a3']]
+ * // a1 needs to be unique
+ * ['a1', 'unique']
+ * // a1 needs to be unique, but column a2 will be used to check the uniqueness of the a1 value
+ * ['a1', 'unique', 'targetAttribute' => 'a2']
+ * // a1 and a2 need to unique together, and they both will receive error message
+ * [['a1', 'a2'], 'unique', 'targetAttribute' => ['a1', 'a2']]
+ * // a1 and a2 need to unique together, only a1 will receive error message
+ * ['a1', 'unique', 'targetAttribute' => ['a1', 'a2']]
+ * // a1 needs to be unique by checking the uniqueness of both a2 and a3 (using a1 value)
+ * ['a1', 'unique', 'targetAttribute' => ['a2', 'a1' => 'a3']]
  * ```
  *
  * @author Qiang Xue <qiang.xue@gmail.com>
  * @since 2.0
  */
-class ExistValidator extends Validator
+class UniqueValidator extends Validator
 {
 	/**
-	 * @var string the name of the ActiveRecord class that should be used to validate the existence
+	 * @var string the name of the ActiveRecord class that should be used to validate the uniqueness
 	 * of the current attribute value. It not set, it will use the ActiveRecord class of the attribute being validated.
 	 * @see targetAttribute
 	 */
 	public $targetClass;
 	/**
 	 * @var string|array the name of the ActiveRecord attribute that should be used to
-	 * validate the existence of the current attribute value. If not set, it will use the name
-	 * of the attribute currently being validated. You may use an array to validate the existence
+	 * validate the uniqueness of the current attribute value. If not set, it will use the name
+	 * of the attribute currently being validated. You may use an array to validate the uniqueness
 	 * of multiple columns at the same time. The array values are the attributes that will be
-	 * used to validate the existence, while the array keys are the attributes whose values are to be validated.
+	 * used to validate the uniqueness, while the array keys are the attributes whose values are to be validated.
 	 * If the key and the value are the same, you can just specify the value.
 	 */
 	public $targetAttribute;
 
-
 	/**
 	 * @inheritdoc
 	 */
@@ -63,7 +59,7 @@ class ExistValidator extends Validator
 	{
 		parent::init();
 		if ($this->message === null) {
-			$this->message = Yii::t('yii', '{attribute} is invalid.');
+			$this->message = Yii::t('yii', '{attribute} "{value}" has already been taken.');
 		}
 	}
 
@@ -72,7 +68,7 @@ class ExistValidator extends Validator
 	 */
 	public function validateAttribute($object, $attribute)
 	{
-		/** @var \yii\db\ActiveRecordInterface $targetClass */
+		/** @var ActiveRecordInterface $targetClass */
 		$targetClass = $this->targetClass === null ? get_class($object) : $this->targetClass;
 		$targetAttribute = $this->targetAttribute === null ? $attribute : $this->targetAttribute;
 
@@ -92,31 +88,36 @@ class ExistValidator extends Validator
 			}
 		}
 
-		/** @var \yii\db\ActiveRecordInterface $className */
-		if (!$targetClass::find()->where($params)->exists()) {
-			$this->addError($object, $attribute, $this->message);
-		}
-	}
+		$query = $targetClass::find();
+		$query->where($params);
 
-	/**
-	 * @inheritdoc
-	 */
-	protected function validateValue($value)
-	{
-		if (is_array($value)) {
-			return [$this->message, []];
-		}
-		if ($this->targetClass === null) {
-			throw new InvalidConfigException('The "className" property must be set.');
-		}
-		if (!is_string($this->targetAttribute)) {
-			throw new InvalidConfigException('The "attributeName" property must be configured as a string.');
+		if (!$object instanceof ActiveRecordInterface || $object->getIsNewRecord()) {
+			// if current $object isn't in the database yet then it's OK just to call exists()
+			$exists = $query->exists();
+		} else {
+			// if current $object is in the database already we can't use exists()
+			/** @var ActiveRecordInterface[] $objects */
+			$objects = $query->limit(2)->all();
+			$n = count($objects);
+			if ($n === 1) {
+				$keys = array_keys($params);
+				$pks = $targetClass::primaryKey();
+				sort($keys);
+				sort($pks);
+				if ($keys === $pks) {
+					// primary key is modified and not unique
+					$exists = $object->getOldPrimaryKey() != $object->getPrimaryKey();
+				} else {
+					// non-primary key, need to exclude the current record based on PK
+					$exists = $objects[0]->getPrimaryKey() != $object->getOldPrimaryKey();
+				}
+			} else {
+				$exists = $n > 1;
+			}
 		}
 
-		/** @var \yii\db\ActiveRecordInterface $targetClass */
-		$targetClass = $this->targetClass;
-		$query = $targetClass::find();
-		$query->where([$this->targetAttribute => $value]);
-		return $query->exists() ? null : [$this->message, []];
+		if ($exists) {
+			$this->addError($object, $attribute, $this->message);
+		}
 	}
 }
diff --git a/framework/yii/validators/EmailValidator.php b/framework/validators/UrlValidator.php
similarity index 55%
rename from framework/yii/validators/EmailValidator.php
rename to framework/validators/UrlValidator.php
index e5d9b75..4cb20f6 100644
--- a/framework/yii/validators/EmailValidator.php
+++ b/framework/validators/UrlValidator.php
@@ -14,40 +14,38 @@ use yii\web\JsExpression;
 use yii\helpers\Json;
 
 /**
- * EmailValidator validates that the attribute value is a valid email address.
+ * UrlValidator validates that the attribute value is a valid http or https URL.
+ *
+ * Note that this validator only checks if the URL scheme and host part are correct.
+ * It does not check the rest part of a URL.
  *
  * @author Qiang Xue <qiang.xue@gmail.com>
  * @since 2.0
  */
-class EmailValidator extends Validator
+class UrlValidator extends Validator
 {
 	/**
 	 * @var string the regular expression used to validate the attribute value.
-	 * @see http://www.regular-expressions.info/email.html
-	 */
-	public $pattern = '/^[a-zA-Z0-9!#$%&\'*+\\/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&\'*+\\/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?$/';
-	/**
-	 * @var string the regular expression used to validate email addresses with the name part.
-	 * This property is used only when [[allowName]] is true.
-	 * @see allowName
+	 * The pattern may contain a `{schemes}` token that will be replaced
+	 * by a regular expression which represents the [[validSchemes]].
 	 */
-	public $fullPattern = '/^[^@]*<[a-zA-Z0-9!#$%&\'*+\\/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&\'*+\\/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?>$/';
+	public $pattern = '/^{schemes}:\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)/i';
 	/**
-	 * @var boolean whether to allow name in the email address (e.g. "John Smith <john.smith@example.com>"). Defaults to false.
-	 * @see fullPattern
-	 */
-	public $allowName = false;
+	 * @var array list of URI schemes which should be considered valid. By default, http and https
+	 * are considered to be valid schemes.
+	 **/
+	public $validSchemes = ['http', 'https'];
 	/**
-	 * @var boolean whether to check whether the emails domain exists and has either an A or MX record.
-	 * Be aware of the fact that this check can fail due to temporary DNS problems even if the email address is
-	 * valid and an email would be deliverable. Defaults to false.
-	 */
-	public $checkDNS = false;
+	 * @var string the default URI scheme. If the input doesn't contain the scheme part, the default
+	 * scheme will be prepended to it (thus changing the input). Defaults to null, meaning a URL must
+	 * contain the scheme part.
+	 **/
+	public $defaultScheme;
 	/**
-	 * @var boolean whether validation process should take into account IDN (internationalized domain
-	 * names). Defaults to false meaning that validation of emails containing IDN will always fail.
-	 * Note that in order to use IDN validation you have to install and enable `intl` PHP extension,
-	 * otherwise an exception would be thrown.
+	 * @var boolean whether validation process should take into account IDN (internationalized
+	 * domain names). Defaults to false meaning that validation of URLs containing IDN will always
+	 * fail. Note that in order to use IDN validation you have to install and enable `intl` PHP
+	 * extension, otherwise an exception would be thrown.
 	 */
 	public $enableIDN = false;
 
@@ -62,7 +60,21 @@ class EmailValidator extends Validator
 			throw new InvalidConfigException('In order to use IDN validation intl extension must be installed and enabled.');
 		}
 		if ($this->message === null) {
-			$this->message = Yii::t('yii', '{attribute} is not a valid email address.');
+			$this->message = Yii::t('yii', '{attribute} is not a valid URL.');
+		}
+	}
+
+	/**
+	 * @inheritdoc
+	 */
+	public function validateAttribute($object, $attribute)
+	{
+		$value = $object->$attribute;
+		$result = $this->validateValue($value);
+		if (!empty($result)) {
+			$this->addError($object, $attribute, $result[0], $result[1]);
+		} elseif ($this->defaultScheme !== null && strpos($value, '://') === false) {
+			$object->$attribute = $this->defaultScheme . '://' . $value;
 		}
 	}
 
@@ -71,22 +83,29 @@ class EmailValidator extends Validator
 	 */
 	protected function validateValue($value)
 	{
-		// make sure string length is limited to avoid DOS attacks
-		if (!is_string($value) || strlen($value) >= 320) {
-			$valid = false;
-		} elseif (!preg_match('/^(.*<?)(.*)@(.*)(>?)$/', $value, $matches)) {
-			$valid = false;
-		} else {
-			$domain = $matches[3];
+		// make sure the length is limited to avoid DOS attacks
+		if (is_string($value) && strlen($value) < 2000) {
+			if ($this->defaultScheme !== null && strpos($value, '://') === false) {
+				$value = $this->defaultScheme . '://' . $value;
+			}
+
+			if (strpos($this->pattern, '{schemes}') !== false) {
+				$pattern = str_replace('{schemes}', '(' . implode('|', $this->validSchemes) . ')', $this->pattern);
+			} else {
+				$pattern = $this->pattern;
+			}
+
 			if ($this->enableIDN) {
-				$value = $matches[1] . idn_to_ascii($matches[2]) . '@' . idn_to_ascii($domain) . $matches[4];
+				$value = preg_replace_callback('/:\/\/([^\/]+)/', function ($matches) {
+					return '://' . idn_to_ascii($matches[1]);
+				}, $value);
 			}
-			$valid = preg_match($this->pattern, $value) || $this->allowName && preg_match($this->fullPattern, $value);
-			if ($valid && $this->checkDNS) {
-				$valid = checkdnsrr($domain, 'MX') || checkdnsrr($domain, 'A');
+
+			if (preg_match($pattern, $value)) {
+				return null;
 			}
 		}
-		return $valid ? null : [$this->message, []];
+		return [$this->message, []];
 	}
 
 	/**
@@ -94,10 +113,14 @@ class EmailValidator extends Validator
 	 */
 	public function clientValidateAttribute($object, $attribute, $view)
 	{
+		if (strpos($this->pattern, '{schemes}') !== false) {
+			$pattern = str_replace('{schemes}', '(' . implode('|', $this->validSchemes) . ')', $this->pattern);
+		} else {
+			$pattern = $this->pattern;
+		}
+
 		$options = [
-			'pattern' => new JsExpression($this->pattern),
-			'fullPattern' => new JsExpression($this->fullPattern),
-			'allowName' => $this->allowName,
+			'pattern' => new JsExpression($pattern),
 			'message' => strtr($this->message, [
 				'{attribute}' => $object->getAttributeLabel($attribute),
 			]),
@@ -106,11 +129,14 @@ class EmailValidator extends Validator
 		if ($this->skipOnEmpty) {
 			$options['skipOnEmpty'] = 1;
 		}
+		if ($this->defaultScheme !== null) {
+			$options['defaultScheme'] = $this->defaultScheme;
+		}
 
 		ValidationAsset::register($view);
 		if ($this->enableIDN) {
 			PunycodeAsset::register($view);
 		}
-		return 'yii.validation.email(value, messages, ' . Json::encode($options) . ');';
+		return 'yii.validation.url(value, messages, ' . Json::encode($options) . ');';
 	}
 }
diff --git a/framework/yii/validators/ValidationAsset.php b/framework/validators/ValidationAsset.php
similarity index 100%
rename from framework/yii/validators/ValidationAsset.php
rename to framework/validators/ValidationAsset.php
diff --git a/framework/yii/validators/Validator.php b/framework/validators/Validator.php
similarity index 100%
rename from framework/yii/validators/Validator.php
rename to framework/validators/Validator.php
diff --git a/framework/yii/views/errorHandler/callStackItem.php b/framework/views/errorHandler/callStackItem.php
similarity index 100%
rename from framework/yii/views/errorHandler/callStackItem.php
rename to framework/views/errorHandler/callStackItem.php
diff --git a/framework/views/errorHandler/error.php b/framework/views/errorHandler/error.php
new file mode 100644
index 0000000..066d7e4
--- /dev/null
+++ b/framework/views/errorHandler/error.php
@@ -0,0 +1,86 @@
+<?php
+/**
+ * @var \Exception $exception
+ * @var \yii\base\ErrorHandler $handler
+ */
+if ($exception instanceof \yii\web\HttpException) {
+	$code = $exception->statusCode;
+} else {
+	$code = $exception->getCode();
+}
+if ($exception instanceof \yii\base\Exception) {
+	$name = $exception->getName();
+} else {
+	$name = 'Error';
+}
+if ($code) {
+	$name .= " (#$code)";
+}
+
+if ($exception instanceof \yii\base\UserException) {
+	$message = $exception->getMessage();
+} else {
+	$message = 'An internal server error occurred.';
+}
+?>
+<?php if (method_exists($this, 'beginPage')) $this->beginPage(); ?>
+<!DOCTYPE html>
+<html>
+<head>
+	<meta charset="utf-8" />
+	<title><?= $handler->htmlEncode($name) ?></title>
+
+	<style>
+		body {
+			font: normal 9pt "Verdana";
+			color: #000;
+			background: #fff;
+		}
+
+		h1 {
+			font: normal 18pt "Verdana";
+			color: #f00;
+			margin-bottom: .5em;
+		}
+
+		h2 {
+			font: normal 14pt "Verdana";
+			color: #800000;
+			margin-bottom: .5em;
+		}
+
+		h3 {
+			font: bold 11pt "Verdana";
+		}
+
+		p {
+			font: normal 9pt "Verdana";
+			color: #000;
+		}
+
+		.version {
+			color: gray;
+			font-size: 8pt;
+			border-top: 1px solid #aaa;
+			padding-top: 1em;
+			margin-bottom: 1em;
+		}
+	</style>
+</head>
+
+<body>
+	<h1><?= $handler->htmlEncode($name) ?></h1>
+	<h2><?= nl2br($handler->htmlEncode($message)) ?></h2>
+	<p>
+		The above error occurred while the Web server was processing your request.
+	</p>
+	<p>
+		Please contact us if you think this is a server error. Thank you.
+	</p>
+	<div class="version">
+		<?= date('Y-m-d H:i:s', time()) ?>
+	</div>
+	<?php if (method_exists($this, 'endBody')) $this->endBody(); // to allow injecting code into body (mostly by Yii Debug Toolbar) ?>
+</body>
+</html>
+<?php if (method_exists($this, 'endPage')) $this->endPage(); ?>
diff --git a/framework/yii/views/errorHandler/exception.php b/framework/views/errorHandler/exception.php
similarity index 100%
rename from framework/yii/views/errorHandler/exception.php
rename to framework/views/errorHandler/exception.php
diff --git a/framework/yii/views/errorHandler/previousException.php b/framework/views/errorHandler/previousException.php
similarity index 100%
rename from framework/yii/views/errorHandler/previousException.php
rename to framework/views/errorHandler/previousException.php
diff --git a/framework/views/messageConfig.php b/framework/views/messageConfig.php
new file mode 100644
index 0000000..9babb0c
--- /dev/null
+++ b/framework/views/messageConfig.php
@@ -0,0 +1,45 @@
+<?php
+
+return [
+	// string, required, root directory of all source files
+	'sourcePath' => __DIR__,
+	// string, required, root directory containing message translations.
+	'messagePath' => __DIR__ . DIRECTORY_SEPARATOR . 'messages',
+	// array, required, list of language codes that the extracted messages
+	// should be translated to. For example, ['zh_cn', 'de'].
+	'languages' => ['de'],
+	// string, the name of the function for translating messages.
+	// Defaults to 'Yii::t'. This is used as a mark to find the messages to be
+	// translated. You may use a string for single function name or an array for
+	// multiple function names.
+	'translator' => 'Yii::t',
+	// boolean, whether to sort messages by keys when merging new messages
+	// with the existing ones. Defaults to false, which means the new (untranslated)
+	// messages will be separated from the old (translated) ones.
+	'sort' => false,
+	// boolean, whether the message file should be overwritten with the merged messages
+	'overwrite' => true,
+	// boolean, whether to remove messages that no longer appear in the source code.
+	// Defaults to false, which means each of these messages will be enclosed with a pair of '@@' marks.
+	'removeUnused' => false,
+	// array, list of patterns that specify which files/directories should be processed.
+	// If empty or not set, all files/directories will be processed.
+	// A path matches a pattern if it contains the pattern string at its end. For example,
+	// '/a/b' will match all files and directories ending with '/a/b';
+	// and the '.svn' will match all files and directories whose name ends with '.svn'.
+	// Note, the '/' characters in a pattern matches both '/' and '\'.
+	// If a file/directory matches both a pattern in "only" and "except", it will NOT be processed.
+	'only' => ['.php'],
+	// array, list of patterns that specify which files/directories should NOT be processed.
+	// If empty or not set, all files/directories will be processed.
+	// Please refer to "only" for details about the patterns.
+	'except' => [
+		'.svn',
+		'.git',
+		'.gitignore',
+		'.gitkeep',
+		'.hgignore',
+		'.hgkeep',
+		'/messages',
+	],
+];
diff --git a/framework/yii/views/migration.php b/framework/views/migration.php
similarity index 100%
rename from framework/yii/views/migration.php
rename to framework/views/migration.php
diff --git a/framework/web/AccessControl.php b/framework/web/AccessControl.php
new file mode 100644
index 0000000..e755e80
--- /dev/null
+++ b/framework/web/AccessControl.php
@@ -0,0 +1,144 @@
+<?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;
+
+/**
+ * AccessControl provides simple access control based on a set of rules.
+ *
+ * AccessControl is an action filter. It will check its [[rules]] to find
+ * the first rule that matches the current context variables (such as user IP address, user role).
+ * The matching rule will dictate whether to allow or deny the access to the requested controller
+ * action. If no rule matches, the access will be denied.
+ *
+ * To use AccessControl, declare it in the `behaviors()` method of your controller class.
+ * For example, the following declarations will allow authenticated users to access the "create"
+ * and "update" actions and deny all other users from accessing these two actions.
+ *
+ * ~~~
+ * public function behaviors()
+ * {
+ *     return [
+ *         'access' => [
+ *             'class' => \yii\web\AccessControl::className(),
+ *             'only' => ['create', 'update'],
+ *             'rules' => [
+ *                 // deny all POST requests
+ *                 [
+ *                     'allow' => false,
+ *                     'verbs' => ['POST']
+ *                 ],
+ *                 // allow authenticated users
+ *                 [
+ *                     'allow' => true,
+ *                     'roles' => ['@'],
+ *                 ],
+ *                 // everything else is denied
+ *             ],
+ *         ],
+ *     ];
+ * }
+ * ~~~
+ *
+ * @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 array the default configuration of access rules. Individual rule configurations
+	 * specified via [[rules]] will take precedence when the same property of the rule is configured.
+	 */
+	public $ruleConfig = ['class' => 'yii\web\AccessRule'];
+	/**
+	 * @var array a list of access rule objects or configuration arrays for creating the rule objects.
+	 * If a rule is specified via a configuration array, it will be merged with [[ruleConfig]] first
+	 * before it is used for creating the rule object.
+	 * @see ruleConfig
+	 */
+	public $rules = [];
+
+	/**
+	 * 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)) {
+				$this->rules[$i] = Yii::createObject(array_merge($this->ruleConfig, $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 AccessRule $rule */
+		foreach ($this->rules as $rule) {
+			if ($allow = $rule->allows($action, $user, $request)) {
+				return true;
+			} 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;
+			}
+		}
+		if (isset($this->denyCallback)) {
+			call_user_func($this->denyCallback, $rule);
+		}
+		else {
+			$this->denyAccess($user);
+		}
+		return false;
+	}
+
+	/**
+	 * 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 AccessDeniedHttpException if the user is already logged in.
+	 */
+	protected function denyAccess($user)
+	{
+		if ($user->getIsGuest()) {
+			$user->loginRequired();
+		} else {
+			throw new AccessDeniedHttpException(Yii::t('yii', 'You are not allowed to perform this action.'));
+		}
+	}
+}
diff --git a/framework/yii/web/AccessDeniedHttpException.php b/framework/web/AccessDeniedHttpException.php
similarity index 100%
rename from framework/yii/web/AccessDeniedHttpException.php
rename to framework/web/AccessDeniedHttpException.php
diff --git a/framework/web/AccessRule.php b/framework/web/AccessRule.php
new file mode 100644
index 0000000..7aeaac1
--- /dev/null
+++ b/framework/web/AccessRule.php
@@ -0,0 +1,186 @@
+<?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;
+
+/**
+ * This class represents an access rule defined by the [[AccessControl]] action filter
+ *
+ * @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::checkAccess()]] 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->getMethod())
+			&& $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->uniqueId, $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->checkAccess($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);
+	}
+}
diff --git a/framework/web/Application.php b/framework/web/Application.php
new file mode 100644
index 0000000..17c1411
--- /dev/null
+++ b/framework/web/Application.php
@@ -0,0 +1,178 @@
+<?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\InvalidRouteException;
+
+/**
+ * Application is the base class for all web application classes.
+ *
+ * @property AssetManager $assetManager The asset manager component. This property is read-only.
+ * @property string $homeUrl The homepage URL.
+ * @property Request $request The request component. This property is read-only.
+ * @property Response $response The response component. This property is read-only.
+ * @property Session $session The session component. This property is read-only.
+ * @property User $user The user component. This property is read-only.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class Application extends \yii\base\Application
+{
+	/**
+	 * @var string the default route of this application. Defaults to 'site'.
+	 */
+	public $defaultRoute = 'site';
+	/**
+	 * @var array the configuration specifying a controller action which should handle
+	 * all user requests. This is mainly used when the application is in maintenance mode
+	 * and needs to handle all incoming requests via a single action.
+	 * The configuration is an array whose first element specifies the route of the action.
+	 * The rest of the array elements (key-value pairs) specify the parameters to be bound
+	 * to the action. For example,
+	 *
+	 * ~~~
+	 * [
+	 *     'offline/notice',
+	 *     'param1' => 'value1',
+	 *     'param2' => 'value2',
+	 * ]
+	 * ~~~
+	 *
+	 * Defaults to null, meaning catch-all is not used.
+	 */
+	public $catchAll;
+	/**
+	 * @var Controller the currently active controller instance
+	 */
+	public $controller;
+
+
+	/**
+	 * Handles the specified request.
+	 * @param Request $request the request to be handled
+	 * @return Response the resulting response
+	 * @throws NotFoundHttpException if the requested route is invalid
+	 */
+	public function handleRequest($request)
+	{
+		Yii::setAlias('@webroot', dirname($request->getScriptFile()));
+		Yii::setAlias('@web', $request->getBaseUrl());
+
+		if (empty($this->catchAll)) {
+			list ($route, $params) = $request->resolve();
+		} else {
+			$route = $this->catchAll[0];
+			$params = array_splice($this->catchAll, 1);
+		}
+		try {
+			Yii::trace("Route requested: '$route'", __METHOD__);
+			$this->requestedRoute = $route;
+			$result = $this->runAction($route, $params);
+			if ($result instanceof Response) {
+				return $result;
+			} else {
+				$response = $this->getResponse();
+				if ($result !== null) {
+					$response->data = $result;
+				}
+				return $response;
+			}
+		} catch (InvalidRouteException $e) {
+			throw new NotFoundHttpException($e->getMessage(), $e->getCode(), $e);
+		}
+	}
+
+	private $_homeUrl;
+
+	/**
+	 * @return string the homepage URL
+	 */
+	public function getHomeUrl()
+	{
+		if ($this->_homeUrl === null) {
+			if ($this->getUrlManager()->showScriptName) {
+				return $this->getRequest()->getScriptUrl();
+			} else {
+				return $this->getRequest()->getBaseUrl() . '/';
+			}
+		} else {
+			return $this->_homeUrl;
+		}
+	}
+
+	/**
+	 * @param string $value the homepage URL
+	 */
+	public function setHomeUrl($value)
+	{
+		$this->_homeUrl = $value;
+	}
+
+	/**
+	 * 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');
+	}
+
+	/**
+	 * Returns the user component.
+	 * @return User the user component
+	 */
+	public function getUser()
+	{
+		return $this->getComponent('user');
+	}
+
+	/**
+	 * Returns the asset manager.
+	 * @return AssetManager the asset manager component
+	 */
+	public function getAssetManager()
+	{
+		return $this->getComponent('assetManager');
+	}
+
+	/**
+	 * Registers the core application components.
+	 * @see setComponents
+	 */
+	public function registerCoreComponents()
+	{
+		parent::registerCoreComponents();
+		$this->setComponents([
+			'request' => ['class' => 'yii\web\Request'],
+			'response' => ['class' => 'yii\web\Response'],
+			'session' => ['class' => 'yii\web\Session'],
+			'user' => ['class' => 'yii\web\User'],
+			'assetManager' => ['class' => 'yii\web\AssetManager'],
+		]);
+	}
+}
diff --git a/framework/web/AssetBundle.php b/framework/web/AssetBundle.php
new file mode 100644
index 0000000..da34848
--- /dev/null
+++ b/framework/web/AssetBundle.php
@@ -0,0 +1,194 @@
+<?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\InvalidConfigException;
+use yii\base\Object;
+use yii\web\View;
+
+/**
+ * AssetBundle represents a collection of asset files, such as CSS, JS, images.
+ *
+ * Each asset bundle has a unique name that globally identifies it among all asset bundles used in an application.
+ * The name is the [fully qualified class name](http://php.net/manual/en/language.namespaces.rules.php)
+ * of the class representing it.
+ *
+ * An asset bundle can depend on other asset bundles. When registering an asset bundle
+ * with a view, all its dependent asset bundles will be automatically registered.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class AssetBundle extends Object
+{
+	/**
+	 * @var string the root directory of the source asset files. A source asset file
+	 * is a file that is part of your source code repository of your Web application.
+	 *
+	 * You must set this property if the directory containing the source asset files
+	 * is not Web accessible (this is usually the case for extensions).
+	 *
+	 * By setting this property, the asset manager will publish the source asset files
+	 * to a Web-accessible directory [[basePath]].
+	 *
+	 * You can use either a directory or an alias of the directory.
+	 */
+	public $sourcePath;
+	/**
+	 * @var string the Web-accessible directory that contains the asset files in this bundle.
+	 *
+	 * If [[sourcePath]] is set, this property will be *overwritten* by [[AssetManager]]
+	 * when it publishes the asset files from [[sourcePath]].
+	 *
+	 * If the bundle contains any assets that are specified in terms of relative file path,
+	 * then this property must be set either manually or automatically (by [[AssetManager]] via
+	 * asset publishing).
+	 *
+	 * You can use either a directory or an alias of the directory.
+	 */
+	public $basePath;
+	/**
+	 * @var string the base URL that will be prefixed to the asset files for them to
+	 * be accessed via Web server.
+	 *
+	 * If [[sourcePath]] is set, this property will be *overwritten* by [[AssetManager]]
+	 * when it publishes the asset files from [[sourcePath]].
+	 *
+	 * If the bundle contains any assets that are specified in terms of relative file path,
+	 * then this property must be set either manually or automatically (by asset manager via
+	 * asset publishing).
+	 *
+	 * You can use either a URL or an alias of the URL.
+	 */
+	public $baseUrl;
+	/**
+	 * @var array list of bundle class names that this bundle depends on.
+	 *
+	 * For example:
+	 *
+	 * ```php
+	 * public $depends = [
+	 *    'yii\web\YiiAsset',
+	 *    'yii\bootstrap\BootstrapAsset',
+	 * ];
+	 * ```
+	 */
+	public $depends = [];
+	/**
+	 * @var array list of JavaScript files that this bundle contains. Each JavaScript file can
+	 * be either a file path (without leading slash) relative to [[basePath]] or a URL representing
+	 * an external JavaScript file.
+	 *
+	 * Note that only forward slash "/" can be used as directory separator.
+	 */
+	public $js = [];
+	/**
+	 * @var array list of CSS files that this bundle contains. Each CSS file can
+	 * be either a file path (without leading slash) relative to [[basePath]] or a URL representing
+	 * an external CSS file.
+	 *
+	 * Note that only forward slash "/" can be used as directory separator.
+	 */
+	public $css = [];
+	/**
+	 * @var array the options that will be passed to [[\yii\web\View::registerJsFile()]]
+	 * when registering the JS files in this bundle.
+	 */
+	public $jsOptions = [];
+	/**
+	 * @var array the options that will be passed to [[\yii\web\View::registerCssFile()]]
+	 * when registering the CSS files in this bundle.
+	 */
+	public $cssOptions = [];
+	/**
+	 * @var array the options to be passed to [[AssetManager::publish()]] when the asset bundle
+	 * is being published.
+	 */
+	public $publishOptions = [];
+
+	/**
+	 * @param View $view
+	 * @return AssetBundle the registered asset bundle instance
+	 */
+	public static function register($view)
+	{
+		return $view->registerAssetBundle(get_called_class());
+	}
+
+	/**
+	 * Initializes the bundle.
+	 * If you override this method, make sure you call the parent implementation in the last.
+	 */
+	public function init()
+	{
+		if ($this->sourcePath !== null) {
+			$this->sourcePath = rtrim(Yii::getAlias($this->sourcePath), '/\\');
+		}
+		if ($this->basePath !== null) {
+			$this->basePath = rtrim(Yii::getAlias($this->basePath), '/\\');
+		}
+		if ($this->baseUrl !== null) {
+			$this->baseUrl = rtrim(Yii::getAlias($this->baseUrl), '/');
+		}
+	}
+
+	/**
+	 * Registers the CSS and JS files with the given view.
+	 * @param \yii\web\View $view the view that the asset files are to be registered with.
+	 */
+	public function registerAssetFiles($view)
+	{
+		foreach ($this->js as $js) {
+			if (strpos($js, '/') !== 0 && strpos($js, '://') === false) {
+				$view->registerJsFile($this->baseUrl . '/' . $js, [], $this->jsOptions);
+			} else {
+				$view->registerJsFile($js, [], $this->jsOptions);
+			}
+		}
+		foreach ($this->css as $css) {
+			if (strpos($css, '/') !== 0 && strpos($css, '://') === false) {
+				$view->registerCssFile($this->baseUrl . '/' . $css, [], $this->cssOptions);
+			} else {
+				$view->registerCssFile($css, [], $this->cssOptions);
+			}
+		}
+	}
+
+	/**
+	 * Publishes the asset bundle if its source code is not under Web-accessible directory.
+	 * It will also try to convert non-CSS or JS files (e.g. LESS, Sass) into the corresponding
+	 * CSS or JS files using [[AssetManager::converter|asset converter]].
+	 * @param AssetManager $am the asset manager to perform the asset publishing
+	 */
+	public function publish($am)
+	{
+		if ($this->sourcePath !== null && !isset($this->basePath, $this->baseUrl)) {
+			list ($this->basePath, $this->baseUrl) = $am->publish($this->sourcePath, $this->publishOptions);
+		}
+		$converter = $am->getConverter();
+		foreach ($this->js as $i => $js) {
+			if (strpos($js, '/') !== 0 && strpos($js, '://') === false) {
+				if (isset($this->basePath, $this->baseUrl)) {
+					$this->js[$i] = $converter->convert($js, $this->basePath, $this->baseUrl);
+				} else {
+					$this->js[$i] = '/' . $js;
+				}
+			}
+		}
+		foreach ($this->css as $i => $css) {
+			if (strpos($css, '/') !== 0 && strpos($css, '://') === false) {
+				if (isset($this->basePath, $this->baseUrl)) {
+					$this->css[$i] = $converter->convert($css, $this->basePath, $this->baseUrl);
+				} else {
+					$this->css[$i] = '/' . $css;
+				}
+			}
+		}
+	}
+}
diff --git a/framework/web/AssetConverter.php b/framework/web/AssetConverter.php
new file mode 100644
index 0000000..1b7d1c8
--- /dev/null
+++ b/framework/web/AssetConverter.php
@@ -0,0 +1,99 @@
+<?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\Exception;
+
+/**
+ * AssetConverter supports conversion of several popular script formats into JS or CSS scripts.
+ *
+ * It is used by [[AssetManager]] to convert files after they have been published.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class AssetConverter extends Component implements AssetConverterInterface
+{
+	/**
+	 * @var array the commands that are used to perform the asset conversion.
+	 * The keys are the asset file extension names, and the values are the corresponding
+	 * target script types (either "css" or "js") and the commands used for the conversion.
+	 */
+	public $commands = [
+		'less' => ['css', 'lessc {from} {to} --no-color'],
+		'scss' => ['css', 'sass {from} {to}'],
+		'sass' => ['css', 'sass {from} {to}'],
+		'styl' => ['js', 'stylus < {from} > {to}'],
+		'coffee' => ['js', 'coffee -p {from} > {to}'],
+		'ts' => ['js', 'tsc --out {to} {from}'],
+	];
+
+	/**
+	 * Converts a given asset file into a CSS or JS file.
+	 * @param string $asset the asset file path, relative to $basePath
+	 * @param string $basePath the directory the $asset is relative to.
+	 * @return string the converted asset file path, relative to $basePath.
+	 */
+	public function convert($asset, $basePath)
+	{
+		$pos = strrpos($asset, '.');
+		if ($pos !== false) {
+			$ext = substr($asset, $pos + 1);
+			if (isset($this->commands[$ext])) {
+				list ($ext, $command) = $this->commands[$ext];
+				$result = substr($asset, 0, $pos + 1) . $ext;
+				if (@filemtime("$basePath/$result") < filemtime("$basePath/$asset")) {
+					$this->runCommand($command, $basePath, $asset, $result);
+				}
+				return $result;
+			}
+		}
+		return $asset;
+	}
+
+	/**
+	 * Runs a command to convert asset files.
+	 * @param string $command the command to run
+	 * @param string $basePath asset base path and command working directory
+	 * @param string $asset the name of the asset file
+	 * @param string $result the name of the file to be generated by the converter command
+	 * @return bool true on success, false on failure. Failures will be logged.
+	 * @throws \yii\base\Exception when the command fails and YII_DEBUG is true.
+	 * In production mode the error will be logged.
+	 */
+	protected function runCommand($command, $basePath, $asset, $result)
+	{
+		$command = strtr($command, [
+			'{from}' => escapeshellarg("$basePath/$asset"),
+			'{to}' => escapeshellarg("$basePath/$result"),
+		]);
+		$descriptor = [
+			1 => ['pipe', 'w'],
+			2 => ['pipe', 'w'],
+		];
+		$pipes = [];
+		$proc = proc_open($command, $descriptor, $pipes, $basePath);
+		$stdout = stream_get_contents($pipes[1]);
+		$stderr = stream_get_contents($pipes[2]);
+		foreach($pipes as $pipe) {
+			fclose($pipe);
+		}
+		$status = proc_close($proc);
+
+		if ($status === 0) {
+			Yii::trace("Converted $asset into $result:\nSTDOUT:\n$stdout\nSTDERR:\n$stderr", __METHOD__);
+		} elseif (YII_DEBUG) {
+			throw new Exception("AssetConverter command '$command' failed with exit code $status:\nSTDOUT:\n$stdout\nSTDERR:\n$stderr");
+		} else {
+			Yii::error("AssetConverter command '$command' failed with exit code $status:\nSTDOUT:\n$stdout\nSTDERR:\n$stderr");
+		}
+		return $status === 0;
+	}
+}
diff --git a/framework/yii/web/AssetConverterInterface.php b/framework/web/AssetConverterInterface.php
similarity index 100%
rename from framework/yii/web/AssetConverterInterface.php
rename to framework/web/AssetConverterInterface.php
diff --git a/framework/yii/web/AssetManager.php b/framework/web/AssetManager.php
similarity index 100%
rename from framework/yii/web/AssetManager.php
rename to framework/web/AssetManager.php
diff --git a/framework/yii/web/BadRequestHttpException.php b/framework/web/BadRequestHttpException.php
similarity index 100%
rename from framework/yii/web/BadRequestHttpException.php
rename to framework/web/BadRequestHttpException.php
diff --git a/framework/web/CacheSession.php b/framework/web/CacheSession.php
new file mode 100644
index 0000000..7b4a98d
--- /dev/null
+++ b/framework/web/CacheSession.php
@@ -0,0 +1,118 @@
+<?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, [[DbSession]] is a better choice.
+ *
+ * The following example shows how you can configure the application to use CacheSession:
+ * Add the following to your application config under `components`:
+ *
+ * ~~~
+ * 'session' => [
+ *     'class' => 'yii\web\CacheSession',
+ *     // 'cache' => 'mycache',
+ * ]
+ * ~~~
+ *
+ * @property boolean $useCustomStorage Whether to use custom storage. This property is read-only.
+ *
+ * @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()
+	{
+		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.');
+		}
+		parent::init();
+	}
+
+	/**
+	 * 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 mixed a safe cache key associated with the session variable name
+	 */
+	protected function calculateKey($id)
+	{
+		return [__CLASS__, $id];
+	}
+}
diff --git a/framework/yii/web/Controller.php b/framework/web/Controller.php
similarity index 100%
rename from framework/yii/web/Controller.php
rename to framework/web/Controller.php
diff --git a/framework/web/Cookie.php b/framework/web/Cookie.php
new file mode 100644
index 0000000..8cbb412
--- /dev/null
+++ b/framework/web/Cookie.php
@@ -0,0 +1,65 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+/**
+ * Cookie represents information related with a cookie, such as [[name]], [[value]], [[domain]], etc.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class Cookie extends \yii\base\Object
+{
+	/**
+	 * @var string name of the cookie
+	 */
+	public $name;
+	/**
+	 * @var string value of the cookie
+	 */
+	public $value = '';
+	/**
+	 * @var string domain of the cookie
+	 */
+	public $domain = '';
+	/**
+	 * @var integer the timestamp at which the cookie expires. This is the server timestamp.
+	 * Defaults to 0, meaning "until the browser is closed".
+	 */
+	public $expire = 0;
+	/**
+	 * @var string the path on the server in which the cookie will be available on. The default is '/'.
+	 */
+	public $path = '/';
+	/**
+	 * @var boolean whether cookie should be sent via secure connection
+	 */
+	public $secure = false;
+	/**
+	 * @var boolean whether the cookie should be accessible only through the HTTP protocol.
+	 * 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;
+
+	/**
+	 * 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
new file mode 100644
index 0000000..3cf80ff
--- /dev/null
+++ b/framework/web/CookieCollection.php
@@ -0,0 +1,228 @@
+<?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 ArrayIterator;
+use yii\base\InvalidCallException;
+use yii\base\Object;
+
+/**
+ * CookieCollection maintains the cookies available in the current request.
+ *
+ * @property integer $count The number of cookies in the collection. This property is read-only.
+ * @property ArrayIterator $iterator An iterator for traversing the cookies in the collection. This property
+ * is read-only.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class CookieCollection extends Object implements \IteratorAggregate, \ArrayAccess, \Countable
+{
+	/**
+	 * @var boolean whether this collection is read only.
+	 */
+	public $readOnly = false;
+
+	/**
+	 * @var Cookie[] the cookies in this collection (indexed by the cookie names)
+	 */
+	private $_cookies = [];
+
+	/**
+	 * Constructor.
+	 * @param array $cookies the cookies that this collection initially contains. This should be
+	 * an array of name-value pairs.s
+	 * @param array $config name-value pairs that will be used to initialize the object properties
+	 */
+	public function __construct($cookies = [], $config = [])
+	{
+		$this->_cookies = $cookies;
+		parent::__construct($config);
+	}
+
+	/**
+	 * 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 collection.
+	 * @return ArrayIterator an iterator for traversing the cookies in the collection.
+	 */
+	public function getIterator()
+	{
+		return new ArrayIterator($this->_cookies);
+	}
+
+	/**
+	 * 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($collection)`.
+	 * @return integer the number of cookies in the collection.
+	 */
+	public function count()
+	{
+		return $this->getCount();
+	}
+
+	/**
+	 * Returns the number of cookies in the collection.
+	 * @return integer the number of cookies in the collection.
+	 */
+	public function getCount()
+	{
+		return count($this->_cookies);
+	}
+
+	/**
+	 * 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 get($name)
+	{
+		return isset($this->_cookies[$name]) ? $this->_cookies[$name] : null;
+	}
+
+	/**
+	 * 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 getValue($name, $defaultValue = null)
+	{
+		return isset($this->_cookies[$name]) ? $this->_cookies[$name]->value : $defaultValue;
+	}
+
+	/**
+	 * Returns whether there is a cookie with the specified name.
+	 * @param string $name the cookie name
+	 * @return boolean whether the named cookie exists
+	 */
+	public function has($name)
+	{
+		return isset($this->_cookies[$name]);
+	}
+
+	/**
+	 * 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
+	 * @throws InvalidCallException if the cookie collection is read only
+	 */
+	public function add($cookie)
+	{
+		if ($this->readOnly) {
+			throw new InvalidCallException('The cookie collection is read only.');
+		}
+		$this->_cookies[$cookie->name] = $cookie;
+	}
+
+	/**
+	 * Removes a cookie.
+	 * If `$removeFromBrowser` is true, the cookie will be removed from the browser.
+	 * In this case, a cookie with outdated expiry will be added to the collection.
+	 * @param Cookie|string $cookie the cookie object or the name of the cookie to be removed.
+	 * @param boolean $removeFromBrowser whether to remove the cookie from browser
+	 * @throws InvalidCallException if the cookie collection is read only
+	 */
+	public function remove($cookie, $removeFromBrowser = true)
+	{
+		if ($this->readOnly) {
+			throw new InvalidCallException('The cookie collection is read only.');
+		}
+		if ($cookie instanceof Cookie) {
+			$cookie->expire = 1;
+			$cookie->value = '';
+		} else {
+			$cookie = new Cookie([
+				'name' => $cookie,
+				'expire' => 1,
+			]);
+		}
+		if ($removeFromBrowser) {
+			$this->_cookies[$cookie->name] = $cookie;
+		} else {
+			unset($this->_cookies[$cookie->name]);
+		}
+	}
+
+	/**
+	 * Removes all cookies.
+	 * @throws InvalidCallException if the cookie collection is read only
+	 */
+	public function removeAll()
+	{
+		if ($this->readOnly) {
+			throw new InvalidCallException('The cookie collection is read only.');
+		}
+		$this->_cookies = [];
+	}
+
+	/**
+	 * 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()
+	{
+		return $this->_cookies;
+	}
+
+	/**
+	 * 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($collection[$name])`.
+	 * @param string $name the cookie name
+	 * @return boolean whether the named cookie exists
+	 */
+	public function offsetExists($name)
+	{
+		return $this->has($name);
+	}
+
+	/**
+	 * 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 `$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($name)
+	{
+		return $this->get($name);
+	}
+
+	/**
+	 * Adds the cookie to the collection.
+	 * This method is required by the SPL interface `ArrayAccess`.
+	 * 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($cookie);
+	}
+
+	/**
+	 * Removes the named cookie.
+	 * This method is required by the SPL interface `ArrayAccess`.
+	 * 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($name)
+	{
+		$this->remove($name);
+	}
+}
diff --git a/framework/web/DbSession.php b/framework/web/DbSession.php
new file mode 100644
index 0000000..2b0b0e7
--- /dev/null
+++ b/framework/web/DbSession.php
@@ -0,0 +1,224 @@
+<?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:
+ * Add the following to your application config under `components`:
+ *
+ * ~~~
+ * 'session' => [
+ *     'class' => 'yii\web\DbSession',
+ *     // 'db' => 'mydb',
+ *     // 'sessionTable' => 'my_session',
+ * ]
+ * ~~~
+ *
+ * @property boolean $useCustomStorage Whether to use custom storage. This property is read-only.
+ *
+ * @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 = '{{%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()
+	{
+		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.");
+		}
+		parent::init();
+	}
+
+	/**
+	 * 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(['id' => $oldID])
+			->createCommand($this->db)
+			->queryOne();
+		if ($row !== false) {
+			if ($deleteOldSession) {
+				$this->db->createCommand()
+					->update($this->sessionTable, ['id' => $newID], ['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, [
+					'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(['data'])
+			->from($this->sessionTable)
+			->where('[[expire]]>:expire AND [[id]]=:id', [':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(['id'])
+				->from($this->sessionTable)
+				->where(['id' => $id])
+				->createCommand($this->db)
+				->queryScalar();
+			if ($exists === false) {
+				$this->db->createCommand()
+					->insert($this->sessionTable, [
+						'id' => $id,
+						'data' => $data,
+						'expire' => $expire,
+					])->execute();
+			} else {
+				$this->db->createCommand()
+					->update($this->sessionTable, ['data' => $data, 'expire' => $expire], ['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, ['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', [':expire' => time()])
+			->execute();
+		return true;
+	}
+}
diff --git a/framework/web/ErrorAction.php b/framework/web/ErrorAction.php
new file mode 100644
index 0000000..95f17be
--- /dev/null
+++ b/framework/web/ErrorAction.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\base\Action;
+use yii\base\Exception;
+use yii\base\UserException;
+
+/**
+ * ErrorAction displays application errors using a specified view.
+ *
+ * To use ErrorAction, you need to do the following steps:
+ *
+ * First, declare an action of ErrorAction type in the `actions()` method of your `SiteController`
+ * class (or whatever controller you prefer), like the following:
+ *
+ * ```php
+ * public function actions()
+ * {
+ *     return [
+ *         'error' => ['class' => 'yii\web\ErrorAction'],
+ *     ];
+ * }
+ * ```
+ *
+ * Then, create a view file for this action. If the route of your error action is `site/error`, then
+ * the view file should be `views/site/error.php`. In this view file, the following variables are available:
+ *
+ * - `$name`: the error name
+ * - `$message`: the error message
+ * - `$exception`: the exception being handled
+ *
+ * Finally, configure the "errorHandler" application component as follows,
+ *
+ * ```php
+ * 'errorHandler' => [
+ *     'errorAction' => 'site/error',
+ * ]
+ * ```
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class ErrorAction extends Action
+{
+	/**
+	 * @var string the view file to be rendered. If not set, it will take the value of [[id]].
+	 * That means, if you name the action as "error" in "SiteController", then the view name
+	 * would be "error", and the corresponding view file would be "views/site/error.php".
+	 */
+	public $view;
+	/**
+	 * @var string the name of the error when the exception name cannot be determined.
+	 * Defaults to "Error".
+	 */
+	public $defaultName;
+	/**
+	 * @var string the message to be displayed when the exception message contains sensitive information.
+	 * Defaults to "An internal server error occurred.".
+	 */
+	public $defaultMessage;
+
+
+	public function run()
+	{
+		if (($exception = Yii::$app->exception) === null) {
+			return '';
+		}
+
+		if ($exception instanceof HttpException) {
+			$code = $exception->statusCode;
+		} else {
+			$code = $exception->getCode();
+		}
+		if ($exception instanceof Exception) {
+			$name = $exception->getName();
+		} else {
+			$name = $this->defaultName ?: Yii::t('yii', 'Error');
+		}
+		if ($code) {
+			$name .= " (#$code)";
+		}
+
+		if ($exception instanceof UserException) {
+			$message = $exception->getMessage();
+		} else {
+			$message = $this->defaultMessage ?: Yii::t('yii', 'An internal server error occurred.');
+		}
+
+		if (Yii::$app->getRequest()->getIsAjax()) {
+			return "$name: $message";
+		} else {
+			return $this->controller->render($this->view ?: $this->id, [
+				'name' => $name,
+				'message' => $message,
+				'exception' => $exception,
+			]);
+		}
+	}
+}
diff --git a/framework/web/HeaderCollection.php b/framework/web/HeaderCollection.php
new file mode 100644
index 0000000..e8e4f9c
--- /dev/null
+++ b/framework/web/HeaderCollection.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\base\Object;
+use ArrayIterator;
+
+/**
+ * HeaderCollection is used by [[Response]] to maintain the currently registered HTTP headers.
+ *
+ * @property integer $count The number of headers in the collection. This property is read-only.
+ * @property ArrayIterator $iterator An iterator for traversing the headers in the collection. This property
+ * is read-only.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class HeaderCollection extends Object implements \IteratorAggregate, \ArrayAccess, \Countable
+{
+	/**
+	 * @var array the headers in this collection (indexed by the header names)
+	 */
+	private $_headers = [];
+
+	/**
+	 * Returns an iterator for traversing the headers in the collection.
+	 * This method is required by the SPL interface `IteratorAggregate`.
+	 * It will be implicitly called when you use `foreach` to traverse the collection.
+	 * @return ArrayIterator an iterator for traversing the headers in the collection.
+	 */
+	public function getIterator()
+	{
+		return new ArrayIterator($this->_headers);
+	}
+
+	/**
+	 * Returns the number of headers in the collection.
+	 * This method is required by the SPL `Countable` interface.
+	 * It will be implicitly called when you use `count($collection)`.
+	 * @return integer the number of headers in the collection.
+	 */
+	public function count()
+	{
+		return $this->getCount();
+	}
+
+	/**
+	 * Returns the number of headers in the collection.
+	 * @return integer the number of headers in the collection.
+	 */
+	public function getCount()
+	{
+		return count($this->_headers);
+	}
+
+	/**
+	 * Returns the named header(s).
+	 * @param string $name the name of the header to return
+	 * @param mixed $default the value to return in case the named header does not exist
+	 * @param boolean $first whether to only return the first header of the specified name.
+	 * If false, all headers of the specified name will be returned.
+	 * @return string|array the named header(s). If `$first` is true, a string will be returned;
+	 * If `$first` is false, an array will be returned.
+	 */
+	public function get($name, $default = null, $first = true)
+	{
+		$name = strtolower($name);
+		if (isset($this->_headers[$name])) {
+			return $first ? reset($this->_headers[$name]) : $this->_headers[$name];
+		} else {
+			return $default;
+		}
+	}
+
+	/**
+	 * Adds a new header.
+	 * If there is already a header with the same name, it will be replaced.
+	 * @param string $name the name of the header
+	 * @param string $value the value of the header
+	 * @return static the collection object itself
+	 */
+	public function set($name, $value = '')
+	{
+		$name = strtolower($name);
+		$this->_headers[$name] = (array)$value;
+		return $this;
+	}
+
+	/**
+	 * Adds a new header.
+	 * If there is already a header with the same name, the new one will
+	 * be appended to it instead of replacing it.
+	 * @param string $name the name of the header
+	 * @param string $value the value of the header
+	 * @return static the collection object itself
+	 */
+	public function add($name, $value)
+	{
+		$name = strtolower($name);
+		$this->_headers[$name][] = $value;
+		return $this;
+	}
+
+	/**
+	 * Sets a new header only if it does not exist yet.
+	 * If there is already a header with the same name, the new one will be ignored.
+	 * @param string $name the name of the header
+	 * @param string $value the value of the header
+	 * @return static the collection object itself
+	 */
+	public function setDefault($name, $value)
+	{
+		$name = strtolower($name);
+		if (empty($this->_headers[$name])) {
+			$this->_headers[$name][] = $value;
+		}
+		return $this;
+	}
+
+	/**
+	 * Returns a value indicating whether the named header exists.
+	 * @param string $name the name of the header
+	 * @return boolean whether the named header exists
+	 */
+	public function has($name)
+	{
+		$name = strtolower($name);
+		return isset($this->_headers[$name]);
+	}
+
+	/**
+	 * Removes a header.
+	 * @param string $name the name of the header to be removed.
+	 * @return string the value of the removed header. Null is returned if the header does not exist.
+	 */
+	public function remove($name)
+	{
+		$name = strtolower($name);
+		if (isset($this->_headers[$name])) {
+			$value = $this->_headers[$name];
+			unset($this->_headers[$name]);
+			return $value;
+		} else {
+			return null;
+		}
+	}
+
+	/**
+	 * Removes all headers.
+	 */
+	public function removeAll()
+	{
+		$this->_headers = [];
+	}
+
+	/**
+	 * Returns the collection as a PHP array.
+	 * @return array the array representation of the collection.
+	 * The array keys are header names, and the array values are the corresponding header values.
+	 */
+	public function toArray()
+	{
+		return $this->_headers;
+	}
+
+	/**
+	 * Returns whether there is a header with the specified name.
+	 * This method is required by the SPL interface `ArrayAccess`.
+	 * It is implicitly called when you use something like `isset($collection[$name])`.
+	 * @param string $name the header name
+	 * @return boolean whether the named header exists
+	 */
+	public function offsetExists($name)
+	{
+		return $this->has($name);
+	}
+
+	/**
+	 * Returns the header with the specified name.
+	 * This method is required by the SPL interface `ArrayAccess`.
+	 * It is implicitly called when you use something like `$header = $collection[$name];`.
+	 * This is equivalent to [[get()]].
+	 * @param string $name the header name
+	 * @return string the header value with the specified name, null if the named header does not exist.
+	 */
+	public function offsetGet($name)
+	{
+		return $this->get($name);
+	}
+
+	/**
+	 * Adds the header to the collection.
+	 * This method is required by the SPL interface `ArrayAccess`.
+	 * It is implicitly called when you use something like `$collection[$name] = $header;`.
+	 * This is equivalent to [[add()]].
+	 * @param string $name the header name
+	 * @param string $value the header value to be added
+	 */
+	public function offsetSet($name, $value)
+	{
+		$this->set($name, $value);
+	}
+
+	/**
+	 * Removes the named header.
+	 * This method is required by the SPL interface `ArrayAccess`.
+	 * It is implicitly called when you use something like `unset($collection[$name])`.
+	 * This is equivalent to [[remove()]].
+	 * @param string $name the header name
+	 */
+	public function offsetUnset($name)
+	{
+		$this->remove($name);
+	}
+}
diff --git a/framework/web/HttpCache.php b/framework/web/HttpCache.php
new file mode 100644
index 0000000..134df71
--- /dev/null
+++ b/framework/web/HttpCache.php
@@ -0,0 +1,160 @@
+<?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;
+
+/**
+ * The HttpCache provides functionality for caching via HTTP Last-Modified and Etag headers.
+ *
+ * It is an action filter that can be added to a controller and handles the `beforeAction` event.
+ *
+ * To use AccessControl, declare it in the `behaviors()` method of your controller class.
+ * In the following example the filter will be applied to the `list`-action and
+ * the Last-Modified header will contain the date of the last update to the user table in the database.
+ *
+ * ~~~
+ * public function behaviors()
+ * {
+ *     return [
+ *         'httpCache' => [
+ *             'class' => \yii\web\HttpCache::className(),
+ *             'only' => ['list'],
+ *             'lastModified' => function ($action, $params) {
+ *                 $q = new Query();
+ *                 return strtotime($q->from('users')->max('updated_timestamp'));
+ *             },
+ * //            'etagSeed' => function ($action, $params) {
+ * //                return // generate etag seed here
+ * //            }
+ *         ],
+ *     ];
+ * }
+ * ~~~
+ *
+ * @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 = '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->getRequest()->getMethod();
+		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();
+		$response = Yii::$app->getResponse();
+		if ($etag !== null) {
+			$response->getHeaders()->set('Etag', $etag);
+		}
+
+		if ($this->validateCache($lastModified, $etag)) {
+			$response->setStatusCode(304);
+			return false;
+		}
+
+		if ($lastModified !== null) {
+			$response->getHeaders()->set('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');
+		$headers = Yii::$app->getResponse()->getHeaders();
+		$headers->set('Pragma');
+		if ($this->cacheControlHeader !== null) {
+			$headers->set('Cache-Control', $this->cacheControlHeader);
+		}
+	}
+
+	/**
+	 * 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)) . '"';
+	}
+}
diff --git a/framework/web/HttpException.php b/framework/web/HttpException.php
new file mode 100644
index 0000000..2398437
--- /dev/null
+++ b/framework/web/HttpException.php
@@ -0,0 +1,72 @@
+<?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\UserException;
+
+/**
+ * HttpException represents an exception caused by an improper request of the end-user.
+ *
+ * HttpException can be differentiated via its [[statusCode]] property value which
+ * keeps a standard HTTP status code (e.g. 404, 500). Error handlers may use this status code
+ * to decide how to format the error page.
+ *
+ * Throwing an HttpException like in the following example will result in the 404 page to be displayed.
+ *
+ * ```php
+ * if ($item === null) { // item does not exist
+ *     throw new \yii\web\HttpException(404, 'The requested Item could not be found.');
+ * }
+ * ```
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class HttpException extends UserException
+{
+	/**
+	 * @var integer HTTP status code, such as 403, 404, 500, etc.
+	 */
+	public $statusCode;
+
+	/**
+	 * Constructor.
+	 * @param integer $status HTTP status code, such as 404, 500, etc.
+	 * @param string $message error message
+	 * @param integer $code error code
+	 * @param \Exception $previous The previous exception used for the exception chaining.
+	 */
+	public function __construct($status, $message = null, $code = 0, \Exception $previous = null)
+	{
+		$this->statusCode = $status;
+		parent::__construct($message, $code, $previous);
+	}
+
+	/**
+	 * @return string the user-friendly name of this exception
+	 */
+	public function getName()
+	{
+		if (isset(Response::$httpStatuses[$this->statusCode])) {
+			return Response::$httpStatuses[$this->statusCode];
+		} else {
+			return 'Error';
+		}
+	}
+
+	/**
+	 * Returns the array representation of this object.
+	 * @return array the array representation of this object.
+	 */
+	public function toArray()
+	{
+		$array = parent::toArray();
+		$array['status'] = $this->statusCode;
+		return $array;
+	}
+}
diff --git a/framework/web/IdentityInterface.php b/framework/web/IdentityInterface.php
new file mode 100644
index 0000000..c796b50
--- /dev/null
+++ b/framework/web/IdentityInterface.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\web;
+
+/**
+ * IdentityInterface is the interface that should be implemented by a class providing identity information.
+ *
+ * This interface can typically be implemented by a user model class. For example, the following
+ * code shows how to implement this interface by a User ActiveRecord class:
+ *
+ * ~~~
+ * class User extends ActiveRecord implements IdentityInterface
+ * {
+ *     public static function findIdentity($id)
+ *     {
+ *         return static::find($id);
+ *     }
+ *
+ *     public function getId()
+ *     {
+ *         return $this->id;
+ *     }
+ *
+ *     public function getAuthKey()
+ *     {
+ *         return $this->authKey;
+ *     }
+ *
+ *     public function validateAuthKey($authKey)
+ *     {
+ *         return $this->authKey === $authKey;
+ *     }
+ * }
+ * ~~~
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+interface IdentityInterface
+{
+	/**
+	 * Finds an identity by the given ID.
+	 * @param string|integer $id the ID to be looked for
+	 * @return IdentityInterface the identity object that matches the given ID.
+	 * Null should be returned if such an identity cannot be found
+	 * or the identity is not in an active state (disabled, deleted, etc.)
+	 */
+	public static function findIdentity($id);
+	/**
+	 * Returns an ID that can uniquely identify a user identity.
+	 * @return string|integer 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 key should be unique for each individual user, and should be persistent
+	 * so that it can be used to check the validity of the user identity.
+	 *
+	 * The space of such keys should be big enough to defeat potential identity attacks.
+	 *
+	 * This is required if [[User::enableAutoLogin]] is enabled.
+	 * @return string a key that is used to check the validity of a given identity ID.
+	 * @see validateAuthKey()
+	 */
+	public function getAuthKey();
+	/**
+	 * Validates the given auth key.
+	 *
+	 * This is required if [[User::enableAutoLogin]] is enabled.
+	 * @param string $authKey the given auth key
+	 * @return boolean whether the given auth key is valid.
+	 * @see getAuthKey()
+	 */
+	public function validateAuthKey($authKey);
+}
diff --git a/framework/yii/web/JqueryAsset.php b/framework/web/JqueryAsset.php
similarity index 100%
rename from framework/yii/web/JqueryAsset.php
rename to framework/web/JqueryAsset.php
diff --git a/framework/web/JsExpression.php b/framework/web/JsExpression.php
new file mode 100644
index 0000000..1d05b57
--- /dev/null
+++ b/framework/web/JsExpression.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\web;
+
+use yii\base\Object;
+
+/**
+ * JsExpression marks a string as a JavaScript expression.
+ *
+ * When using [[yii\helpers\Json::encode()]] to encode a value, JsonExpression objects
+ * will be specially handled and encoded as a JavaScript expression instead of a string.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class JsExpression extends Object
+{
+	/**
+	 * @var string the JavaScript expression represented by this object
+	 */
+	public $expression;
+
+	/**
+	 * Constructor.
+	 * @param string $expression the JavaScript expression represented by this object
+	 * @param array $config additional configurations for this object
+	 */
+	public function __construct($expression, $config = [])
+	{
+		$this->expression = $expression;
+		parent::__construct($config);
+	}
+
+	/**
+	 * The PHP magic function converting an object into a string.
+	 * @return string the JavaScript expression.
+	 */
+	public function __toString()
+	{
+		return $this->expression;
+	}
+}
diff --git a/framework/yii/web/MethodNotAllowedHttpException.php b/framework/web/MethodNotAllowedHttpException.php
similarity index 100%
rename from framework/yii/web/MethodNotAllowedHttpException.php
rename to framework/web/MethodNotAllowedHttpException.php
diff --git a/framework/yii/web/NotFoundHttpException.php b/framework/web/NotFoundHttpException.php
similarity index 100%
rename from framework/yii/web/NotFoundHttpException.php
rename to framework/web/NotFoundHttpException.php
diff --git a/framework/web/PageCache.php b/framework/web/PageCache.php
new file mode 100644
index 0000000..4c8cc50
--- /dev/null
+++ b/framework/web/PageCache.php
@@ -0,0 +1,150 @@
+<?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\caching\Dependency;
+
+/**
+ * The PageCache provides functionality for whole page caching
+ *
+ * It is an action filter that can be added to a controller and handles the `beforeAction` event.
+ *
+ * To use PageCache, declare it in the `behaviors()` method of your controller class.
+ * In the following example the filter will be applied to the `list`-action and
+ * cache the whole page for maximum 60 seconds or until the count of entries in the post table changes.
+ * It also stores different versions of the page depended on the route ([[varyByRoute]] is true by default),
+ * the application language and user id.
+ *
+ * ~~~
+ * public function behaviors()
+ * {
+ *     return [
+ *         'pageCache' => [
+ *             'class' => \yii\web\PageCache::className(),
+ *             'only' => ['list'],
+ *             'duration' => 60,
+ *             'dependecy' => [
+ *                 'class' => 'yii\caching\DbDependency',
+ *                 'sql' => 'SELECT COUNT(*) FROM post',
+ *             ],
+ *             'variations' => [
+ *                 Yii::$app->language,
+ *                 Yii::$app->user->id
+ *             ]
+ *         ],
+ *     ];
+ * }
+ * ~~~
+ *
+ * @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 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,
+	 *
+	 * ~~~
+	 * [
+	 *     '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:
+	 *
+	 * ~~~
+	 * [
+	 *     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 component to use for caching. If not set, the default application view component
+	 * [[Application::view]] will be used.
+	 */
+	public $view;
+
+
+	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 = [];
+		foreach (['cache', 'duration', 'dependency', 'variations', 'enabled'] as $name) {
+			$properties[$name] = $this->$name;
+		}
+		$id = $this->varyByRoute ? $action->getUniqueId() : __CLASS__;
+		ob_start();
+		ob_implicit_flush(false);
+		if ($this->view->beginCache($id, $properties)) {
+			return true;
+		} else {
+			Yii::$app->getResponse()->content = ob_get_clean();
+			return false;
+		}
+	}
+
+	/**
+	 * 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.
+	 * @param mixed $result the action execution result
+	 */
+	public function afterAction($action, &$result)
+	{
+		echo $result;
+		$this->view->endCache();
+		$result = ob_get_clean();
+	}
+}
diff --git a/framework/yii/web/Request.php b/framework/web/Request.php
similarity index 100%
rename from framework/yii/web/Request.php
rename to framework/web/Request.php
diff --git a/framework/yii/web/Response.php b/framework/web/Response.php
similarity index 100%
rename from framework/yii/web/Response.php
rename to framework/web/Response.php
diff --git a/framework/yii/web/ResponseFormatterInterface.php b/framework/web/ResponseFormatterInterface.php
similarity index 100%
rename from framework/yii/web/ResponseFormatterInterface.php
rename to framework/web/ResponseFormatterInterface.php
diff --git a/framework/yii/web/Session.php b/framework/web/Session.php
similarity index 100%
rename from framework/yii/web/Session.php
rename to framework/web/Session.php
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/UploadedFile.php b/framework/web/UploadedFile.php
new file mode 100644
index 0000000..5e1e428
--- /dev/null
+++ b/framework/web/UploadedFile.php
@@ -0,0 +1,234 @@
+<?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\helpers\Html;
+
+/**
+ * UploadedFile represents the information for an uploaded file.
+ *
+ * You can call [[getInstance()]] to retrieve the instance of an uploaded file,
+ * and then use [[saveAs()]] to save it on the server.
+ * You may also query other information about the file, including [[name]],
+ * [[tempName]], [[type]], [[size]] and [[error]].
+ *
+ * @property boolean $hasError Whether there is an error with the uploaded file. Check [[error]] for detailed
+ * error code information. This property is read-only.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class UploadedFile extends Object
+{
+	private static $_files;
+
+	/**
+	 * @var string the original name of the file being uploaded
+	 */
+	public $name;
+	/**
+	 * @var string the path of the uploaded file on the server.
+	 * Note, this is a temporary file which will be automatically deleted by PHP
+	 * after the current request is processed.
+	 */
+	public $tempName;
+	/**
+	 * @var string the MIME-type of the uploaded file (such as "image/gif").
+	 * Since this MIME type is not checked on the server side, do not take this value for granted.
+	 * Instead, use [[FileHelper::getMimeType()]] to determine the exact MIME type.
+	 */
+	public $type;
+	/**
+	 * @var integer the actual size of the uploaded file in bytes
+	 */
+	public $size;
+	/**
+	 * @var integer an error code describing the status of this file uploading.
+	 * @see http://www.php.net/manual/en/features.file-upload.errors.php
+	 */
+	public $error;
+
+
+	/**
+	 * String output.
+	 * This is PHP magic method that returns string representation of an object.
+	 * The implementation here returns the uploaded file's name.
+	 * @return string the string representation of the object
+	 */
+	public function __toString()
+	{
+		return $this->name;
+	}
+
+	/**
+	 * Returns an uploaded file for the given model attribute.
+	 * The file should be uploaded using [[ActiveForm::fileInput()]].
+	 * @param \yii\base\Model $model the data model
+	 * @param string $attribute the attribute name. The attribute name may contain array indexes.
+	 * For example, '[1]file' for tabular file uploading; and 'file[1]' for an element in a file array.
+	 * @return UploadedFile the instance of the uploaded file.
+	 * Null is returned if no file is uploaded for the specified model attribute.
+	 * @see getInstanceByName()
+	 */
+	public static function getInstance($model, $attribute)
+	{
+		$name = Html::getInputName($model, $attribute);
+		return static::getInstanceByName($name);
+	}
+
+	/**
+	 * Returns all uploaded files for the given model attribute.
+	 * @param \yii\base\Model $model the data model
+	 * @param string $attribute the attribute name. The attribute name may contain array indexes
+	 * for tabular file uploading, e.g. '[1]file'.
+	 * @return UploadedFile[] array of UploadedFile objects.
+	 * Empty array is returned if no available file was found for the given attribute.
+	 */
+	public static function getInstances($model, $attribute)
+	{
+		$name = Html::getInputName($model, $attribute);
+		return static::getInstancesByName($name);
+	}
+
+	/**
+	 * Returns an uploaded file according to the given file input name.
+	 * The name can be a plain string or a string like an array element (e.g. 'Post[imageFile]', or 'Post[0][imageFile]').
+	 * @param string $name the name of the file input field.
+	 * @return UploadedFile the instance of the uploaded file.
+	 * Null is returned if no file is uploaded for the specified name.
+	 */
+	public static function getInstanceByName($name)
+	{
+		$files = static::loadFiles();
+		return isset($files[$name]) ? $files[$name] : null;
+	}
+
+	/**
+	 * Returns an array of uploaded files corresponding to the specified file input name.
+	 * This is mainly used when multiple files were uploaded and saved as 'files[0]', 'files[1]',
+	 * 'files[n]'..., and you can retrieve them all by passing 'files' as the name.
+	 * @param string $name the name of the array of files
+	 * @return UploadedFile[] the array of CUploadedFile objects. Empty array is returned
+	 * if no adequate upload was found. Please note that this array will contain
+	 * all files from all sub-arrays regardless how deeply nested they are.
+	 */
+	public static function getInstancesByName($name)
+	{
+		$files = static::loadFiles();
+		if (isset($files[$name])) {
+			return [$files[$name]];
+		}
+		$results = [];
+		foreach ($files as $key => $file) {
+			if (strpos($key, "{$name}[") === 0) {
+				$results[] = self::$_files[$key];
+			}
+		}
+		return $results;
+	}
+
+	/**
+	 * Cleans up the loaded UploadedFile instances.
+	 * This method is mainly used by test scripts to set up a fixture.
+	 */
+	public static function reset()
+	{
+		self::$_files = null;
+	}
+
+	/**
+	 * Saves the uploaded file.
+	 * Note that this method uses php's move_uploaded_file() method. If the target file `$file`
+	 * already exists, it will be overwritten.
+	 * @param string $file the file path used to save the uploaded file
+	 * @param boolean $deleteTempFile whether to delete the temporary file after saving.
+	 * If true, you will not be able to save the uploaded file again in the current request.
+	 * @return boolean true whether the file is saved successfully
+	 * @see error
+	 */
+	public function saveAs($file, $deleteTempFile = true)
+	{
+		if ($this->error == UPLOAD_ERR_OK) {
+			if ($deleteTempFile) {
+				return move_uploaded_file($this->tempName, $file);
+			} elseif (is_uploaded_file($this->tempName)) {
+				return copy($this->tempName, $file);
+			}
+		}
+		return false;
+	}
+	
+	/**
+	 * @return string original file base name
+	 */
+	public function getBaseName()
+	{
+		return pathinfo($this->name, PATHINFO_FILENAME);
+	}
+
+	/**
+	 * @return string file extension
+	 */
+	public function getExtension()
+	{
+		return strtolower(pathinfo($this->name, PATHINFO_EXTENSION));
+	}
+
+	/**
+	 * @return boolean whether there is an error with the uploaded file.
+	 * Check [[error]] for detailed error code information.
+	 */
+	public function getHasError()
+	{
+		return $this->error != UPLOAD_ERR_OK;
+	}
+
+	/**
+	 * Creates UploadedFile instances from $_FILE.
+	 * @return array the UploadedFile instances
+	 */
+	private static function loadFiles()
+	{
+		if (self::$_files === null) {
+			self::$_files = [];
+			if (isset($_FILES) && is_array($_FILES)) {
+				foreach ($_FILES as $class => $info) {
+					self::loadFilesRecursive($class, $info['name'], $info['tmp_name'], $info['type'], $info['size'], $info['error']);
+				}
+			}
+		}
+		return self::$_files;
+	}
+
+	/**
+	 * Creates UploadedFile instances from $_FILE recursively.
+	 * @param string $key key for identifying uploaded file: class name and sub-array indexes
+	 * @param mixed $names file names provided by PHP
+	 * @param mixed $tempNames temporary file names provided by PHP
+	 * @param mixed $types file types provided by PHP
+	 * @param mixed $sizes file sizes provided by PHP
+	 * @param mixed $errors uploading issues provided by PHP
+	 */
+	private static function loadFilesRecursive($key, $names, $tempNames, $types, $sizes, $errors)
+	{
+		if (is_array($names)) {
+			foreach ($names as $i => $name) {
+				self::loadFilesRecursive($key . '[' . $i . ']', $name, $tempNames[$i], $types[$i], $sizes[$i], $errors[$i]);
+			}
+		} else {
+			self::$_files[$key] = new static([
+				'name' => $names,
+				'tempName' => $tempNames,
+				'type' => $types,
+				'size' => $sizes,
+				'error' => $errors,
+			]);
+		}
+	}
+}
diff --git a/framework/web/UrlManager.php b/framework/web/UrlManager.php
new file mode 100644
index 0000000..a2044cb
--- /dev/null
+++ b/framework/web/UrlManager.php
@@ -0,0 +1,338 @@
+<?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.
+ *
+ * UrlManager is configured as an application component in [[yii\base\Application]] by default.
+ * You can access that instance via `Yii::$app->urlManager`.
+ *
+ * You can modify its configuration by adding an array to your application config under `components`
+ * as it is shown in the following example:
+ *
+ * ~~~
+ * 'urlManager' => [
+ *     'enablePrettyUrl' => true,
+ *     'rules' => [
+ *         // your rules go here
+ *     ],
+ *     // ...
+ * ]
+ * ~~~
+ *
+ * @property string $baseUrl The base URL that is used by [[createUrl()]] to prepend URLs it creates.
+ * @property string $hostInfo The host info (e.g. "http://www.example.com") that is used by
+ * [[createAbsoluteUrl()]] to prepend URLs it creates.
+ *
+ * @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 boolean whether to enable strict parsing. If strict parsing is enabled, the incoming
+	 * requested URL must match at least one of the [[rules]] in order to be treated as a valid request.
+	 * Otherwise, the path info part of the request will be treated as the requested route.
+	 * This property is used only when [[enablePrettyUrl]] is true.
+	 */
+	public $enableStrictParsing = 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 array for creating a single URL rule. The configuration will
+	 * be merged with [[ruleConfig]] first before it is used for creating the rule object.
+	 *
+	 * A special shortcut format can be used if a rule only specifies [[UrlRule::pattern|pattern]]
+	 * and [[UrlRule::route|route]]: `'pattern' => 'route'`. That is, instead of using a configuration
+	 * array, one can use the key to represent the pattern and the value the corresponding route.
+	 * For example, `'post/<id:\d+>' => 'post/view'`.
+	 *
+	 * For RESTful routing the mentioned shortcut format also allows you to specify the
+	 * [[UrlRule::verb|HTTP verb]] that the rule should apply for.
+	 * You can do that  by prepending it to the pattern, separated by space.
+	 * For example, `'PUT post/<id:\d+>' => 'post/update'`.
+	 * You may specify multiple verbs by separating them with comma
+	 * like this: `'POST,PUT post/index' => 'post/create'`.
+	 * The supported verbs in the shortcut format are: GET, HEAD, POST, PUT, PATCH and DELETE.
+	 * Note that [[UrlRule::mode|mode]] will be set to PARSING_ONLY when specifying verb in this way
+	 * so you normally would not specify a verb for normal GET request.
+	 *
+	 * Here is an example configuration for RESTful CRUD controller:
+	 *
+	 * ~~~php
+	 * [
+	 *     'dashboard' => 'site/index',
+	 *
+	 *     'POST <controller:\w+>s' => '<controller>/create',
+	 *     '<controller:\w+>s' => '<controller>/index',
+	 *
+	 *     'PUT <controller:\w+>/<id:\d+>'    => '<controller>/update',
+	 *     'DELETE <controller:\w+>/<id:\d+>' => '<controller>/delete',
+	 *     '<controller:\w+>/<id:\d+>'        => '<controller>/view',
+	 * ];
+	 * ~~~
+	 *
+	 * Note that if you modify this property after the UrlManager object is created, make sure
+	 * you populate the array with rule objects instead of rule configurations.
+	 */
+	public $rules = [];
+	/**
+	 * @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 array the default configuration of URL rules. Individual rule configurations
+	 * specified via [[rules]] will take precedence when the same property of the rule is configured.
+	 */
+	public $ruleConfig = ['class' => 'yii\web\UrlRule'];
+
+	private $_baseUrl;
+	private $_hostInfo;
+
+	/**
+	 * Initializes UrlManager.
+	 */
+	public function init()
+	{
+		parent::init();
+		$this->compileRules();
+	}
+
+	/**
+	 * Parses the URL rules.
+	 */
+	protected function compileRules()
+	{
+		if (!$this->enablePrettyUrl || empty($this->rules)) {
+			return;
+		}
+		if (is_string($this->cache)) {
+			$this->cache = Yii::$app->getComponent($this->cache);
+		}
+		if ($this->cache instanceof Cache) {
+			$key = __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;
+			}
+		}
+
+		$rules = [];
+		foreach ($this->rules as $key => $rule) {
+			if (!is_array($rule)) {
+				$rule = ['route' => $rule];
+				if (preg_match('/^((?:(GET|HEAD|POST|PUT|PATCH|DELETE),)*(GET|HEAD|POST|PUT|PATCH|DELETE))\s+(.*)$/', $key, $matches)) {
+					$rule['verb'] = explode(',', $matches[1]);
+					$rule['mode'] = UrlRule::PARSING_ONLY;
+					$key = $matches[4];
+				}
+				$rule['pattern'] = $key;
+			}
+			$rules[] = Yii::createObject(array_merge($this->ruleConfig, $rule));
+		}
+		$this->rules = $rules;
+
+		if (isset($key, $hash)) {
+			$this->cache->set($key, [$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->getPathInfo();
+			/** @var UrlRule $rule */
+			foreach ($this->rules as $rule) {
+				if (($result = $rule->parseRequest($this, $request)) !== false) {
+					Yii::trace("Request parsed with URL rule: {$rule->name}", __METHOD__);
+					return $result;
+				}
+			}
+
+			if ($this->enableStrictParsing) {
+				return false;
+			}
+
+			Yii::trace('No matching URL rules. Using default URL parsing logic.', __METHOD__);
+
+			$suffix = (string)$this->suffix;
+			if ($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 [$pathInfo, []];
+		} else {
+			Yii::trace('Pretty URL not enabled. Using default URL parsing logic.', __METHOD__);
+			$route = $request->get($this->routeVar);
+			if (is_array($route)) {
+				$route = '';
+			}
+			return [(string)$route, []];
+		}
+	}
+
+	/**
+	 * 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 = [])
+	{
+		$anchor = isset($params['#']) ? '#' . $params['#'] : '';
+		unset($params['#'], $params[$this->routeVar]);
+
+		$route = trim($route, '/');
+		$baseUrl = $this->getBaseUrl();
+
+		if ($this->enablePrettyUrl) {
+			/** @var UrlRule $rule */
+			foreach ($this->rules as $rule) {
+				if (($url = $rule->createUrl($this, $route, $params)) !== false) {
+					if ($rule->host !== null) {
+						if ($baseUrl !== '' && ($pos = strpos($url, '/', 8)) !== false) {
+							return substr($url, 0, $pos) . $baseUrl . substr($url, $pos);
+						} else {
+							return $url . $baseUrl . $anchor;
+						}
+					} else {
+						return "$baseUrl/{$url}{$anchor}";
+					}
+				}
+			}
+
+			if ($this->suffix !== null) {
+				$route .= $this->suffix;
+			}
+			if (!empty($params)) {
+				$route .= '?' . http_build_query($params);
+			}
+			return "$baseUrl/{$route}{$anchor}";
+		} else {
+			$url = "$baseUrl?{$this->routeVar}=$route";
+			if (!empty($params)) {
+				$url .= '&' . http_build_query($params);
+			}
+			return $url . $anchor;
+		}
+	}
+
+	/**
+	 * 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 = [])
+	{
+		$url = $this->createUrl($route, $params);
+		if (strpos($url, '://') !== false) {
+			return $url;
+		} else {
+			return $this->getHostInfo() . $url;
+		}
+	}
+
+	/**
+	 * 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 \yii\web\Request $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 = rtrim($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..2934b26
--- /dev/null
+++ b/framework/web/UrlRule.php
@@ -0,0 +1,326 @@
+<?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 by [[UrlManager]] for parsing and generating URLs.
+ *
+ * To define your own URL parsing and creation logic you can extend from this class
+ * and add it to [[UrlManager::rules]] like this:
+ *
+ * ~~~
+ * 'rules' => [
+ *     ['class' => 'MyUrlRule', 'pattern' => '...', 'route' => 'site/index', ...],
+ *     // ...
+ * ]
+ * ~~~
+ *
+ * @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 the name of this rule. If not set, it will use [[pattern]] as the name.
+	 */
+	public $name;
+	/**
+	 * @var string the pattern used to parse and create the path info part of a URL.
+	 * @see host
+	 */
+	public $pattern;
+	/**
+	 * @var string the pattern used to parse and create the host info part of a URL.
+	 * @see pattern
+	 */
+	public $host;
+	/**
+	 * @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 = [];
+	/**
+	 * @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 = [];
+	/**
+	 * @var array list of parameters used in the route.
+	 */
+	private $_routeParams = [];
+
+	/**
+	 * 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 = [strtoupper($this->verb)];
+			}
+		}
+		if ($this->name === null) {
+			$this->name = $this->pattern;
+		}
+
+		$this->pattern = trim($this->pattern, '/');
+
+		if ($this->host !== null) {
+			$this->pattern = rtrim($this->host, '/') . rtrim('/' . $this->pattern, '/') . '/';
+		} elseif ($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 = [];
+		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 ($offset > 1 && $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 (!empty($this->_routeParams)) {
+			$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->getMethod(), $this->verb, true)) {
+			return false;
+		}
+
+		$pathInfo = $request->getPathInfo();
+		$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;
+				}
+			} else {
+				return false;
+			}
+		}
+
+		if ($this->host !== null) {
+			$pathInfo = strtolower($request->getHostInfo()) . '/' . $pathInfo;
+		}
+
+		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 = [];
+		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 [$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 = [];
+
+		// 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]) && !is_array($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 ($this->host !== null) {
+			$pos = strpos($url, '/', 8);
+			if ($pos !== false) {
+				$url = substr($url, 0, $pos) . preg_replace('#/+#', '/', substr($url, $pos));
+			}
+		} elseif (strpos($url, '//') !== false) {
+			$url = preg_replace('#/+#', '/', $url);
+		}
+
+		if ($url !== '') {
+			$url .= ($this->suffix === null ? $manager->suffix : $this->suffix);
+		}
+
+		if (!empty($params)) {
+			$url .= '?' . http_build_query($params);
+		}
+		return $url;
+	}
+}
diff --git a/framework/yii/web/User.php b/framework/web/User.php
similarity index 100%
rename from framework/yii/web/User.php
rename to framework/web/User.php
diff --git a/framework/yii/web/JsExpression.php b/framework/web/UserEvent.php
similarity index 50%
rename from framework/yii/web/JsExpression.php
rename to framework/web/UserEvent.php
index 1d05b57..8577ef5 100644
--- a/framework/yii/web/JsExpression.php
+++ b/framework/web/UserEvent.php
@@ -7,41 +7,29 @@
 
 namespace yii\web;
 
-use yii\base\Object;
+use yii\base\Event;
 
 /**
- * JsExpression marks a string as a JavaScript expression.
- *
- * When using [[yii\helpers\Json::encode()]] to encode a value, JsonExpression objects
- * will be specially handled and encoded as a JavaScript expression instead of a string.
+ * This event class is used for Events triggered by the [[User]] class.
  *
  * @author Qiang Xue <qiang.xue@gmail.com>
  * @since 2.0
  */
-class JsExpression extends Object
+class UserEvent extends Event
 {
 	/**
-	 * @var string the JavaScript expression represented by this object
+	 * @var IdentityInterface the identity object associated with this event
 	 */
-	public $expression;
-
+	public $identity;
 	/**
-	 * Constructor.
-	 * @param string $expression the JavaScript expression represented by this object
-	 * @param array $config additional configurations for this object
+	 * @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 function __construct($expression, $config = [])
-	{
-		$this->expression = $expression;
-		parent::__construct($config);
-	}
-
+	public $cookieBased;
 	/**
-	 * The PHP magic function converting an object into a string.
-	 * @return string the JavaScript expression.
+	 * @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 function __toString()
-	{
-		return $this->expression;
-	}
+	public $isValid = true;
 }
diff --git a/framework/web/VerbFilter.php b/framework/web/VerbFilter.php
new file mode 100644
index 0000000..1ca08c2
--- /dev/null
+++ b/framework/web/VerbFilter.php
@@ -0,0 +1,108 @@
+<?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\ActionEvent;
+use yii\base\Behavior;
+
+/**
+ * VerbFilter is an action filter that filters by HTTP request methods.
+ *
+ * It allows to define allowed HTTP request methods for each action and will throw
+ * an HTTP 405 error when the method is not allowed.
+ *
+ * To use VerbFilter, declare it in the `behaviors()` method of your controller class.
+ * For example, the following declarations will define a typical set of allowed
+ * request methods for REST CRUD actions.
+ *
+ * ~~~
+ * public function behaviors()
+ * {
+ *     return [
+ *         'verbs' => [
+ *             'class' => \yii\web\VerbFilter::className(),
+ *             'actions' => [
+ *                 'index'  => ['get'],
+ *                 'view'   => ['get'],
+ *                 'create' => ['get', 'post'],
+ *                 'update' => ['get', 'put', 'post'],
+ *                 'delete' => ['post', 'delete'],
+ *             ],
+ *         ],
+ *     ];
+ * }
+ * ~~~
+ *
+ * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.7
+ * @author Carsten Brandt <mail@cebe.cc>
+ * @since 2.0
+ */
+class VerbFilter extends Behavior
+{
+	/**
+	 * @var array this property defines the allowed request methods for each action.
+	 * For each action that should only support limited set of request methods
+	 * you add an entry with the action id as array key and an array of
+	 * allowed methods (e.g. GET, HEAD, PUT) as the value.
+	 * If an action is not listed all request methods are considered allowed.
+	 *
+	 * You can use '*' to stand for all actions. When an action is explicitly
+	 * specified, it takes precedence over the specification given by '*'.
+	 *
+	 * For example,
+	 *
+	 * ~~~
+	 * [
+	 *   'create' => ['get', 'post'],
+	 *   'update' => ['get', 'put', 'post'],
+	 *   'delete' => ['post', 'delete'],
+	 *   '*' => ['get'],
+	 * ]
+	 * ~~~
+	 */
+	public $actions = [];
+
+
+	/**
+	 * 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 [Controller::EVENT_BEFORE_ACTION => 'beforeAction'];
+	}
+
+	/**
+	 * @param ActionEvent $event
+	 * @return boolean
+	 * @throws HttpException when the request method is not allowed.
+	 */
+	public function beforeAction($event)
+	{
+		$action = $event->action->id;
+		if (isset($this->actions[$action])) {
+			$verbs = $this->actions[$action];
+		} elseif (isset($this->actions['*'])) {
+			$verbs = $this->actions['*'];
+		} else {
+			return $event->isValid;
+		}
+
+		$verb = Yii::$app->getRequest()->getMethod();
+		$allowed = array_map('strtoupper', $verbs);
+		if (!in_array($verb, array_map('strtoupper', $verbs))) {
+			$event->isValid = false;
+			// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.7
+			Yii::$app->getResponse()->getHeaders()->set('Allow', implode(', ', $allowed));
+			throw new MethodNotAllowedHttpException('Method Not Allowed. This url can only handle the following request methods: ' . implode(', ', $allowed) . '.');
+		}
+
+		return $event->isValid;
+	}
+}
diff --git a/framework/yii/web/View.php b/framework/web/View.php
similarity index 100%
rename from framework/yii/web/View.php
rename to framework/web/View.php
diff --git a/framework/web/XmlResponseFormatter.php b/framework/web/XmlResponseFormatter.php
new file mode 100644
index 0000000..292424a
--- /dev/null
+++ b/framework/web/XmlResponseFormatter.php
@@ -0,0 +1,98 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\web;
+
+use DOMDocument;
+use DOMElement;
+use DOMText;
+use yii\base\Arrayable;
+use yii\base\Component;
+use yii\helpers\StringHelper;
+
+/**
+ * XmlResponseFormatter formats the given data into an XML response content.
+ *
+ * It is used by [[Response]] to format response data.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class XmlResponseFormatter extends Component implements ResponseFormatterInterface
+{
+	/**
+	 * @var string the Content-Type header for the response
+	 */
+	public $contentType = 'application/xml';
+	/**
+	 * @var string the XML version
+	 */
+	public $version = '1.0';
+	/**
+	 * @var string the XML encoding. If not set, it will use the value of [[Response::charset]].
+	 */
+	public $encoding;
+	/**
+	 * @var string the name of the root element.
+	 */
+	public $rootTag = 'response';
+	/**
+	 * @var string the name of the elements that represent the array elements with numeric keys.
+	 */
+	public $itemTag = 'item';
+
+	/**
+	 * Formats the specified response.
+	 * @param Response $response the response to be formatted.
+	 */
+	public function format($response)
+	{
+		$response->getHeaders()->set('Content-Type', $this->contentType);
+		$dom = new DOMDocument($this->version, $this->encoding === null ? $response->charset : $this->encoding);
+		$root = new DOMElement($this->rootTag);
+		$dom->appendChild($root);
+		$this->buildXml($root, $response->data);
+		$response->content = $dom->saveXML();
+	}
+
+	/**
+	 * @param DOMElement $element
+	 * @param mixed $data
+	 */
+	protected function buildXml($element, $data)
+	{
+		if (is_object($data)) {
+			$child = new DOMElement(StringHelper::basename(get_class($data)));
+			$element->appendChild($child);
+			if ($data instanceof Arrayable) {
+				$this->buildXml($child, $data->toArray());
+			} else {
+				$array = [];
+				foreach ($data as $name => $value) {
+					$array[$name] = $value;
+				}
+				$this->buildXml($child, $array);
+			}
+		} elseif (is_array($data)) {
+			foreach ($data as $name => $value) {
+				if (is_int($name) && is_object($value)) {
+					$this->buildXml($element, $value);
+				} elseif (is_array($value) || is_object($value)) {
+					$child = new DOMElement(is_int($name) ? $this->itemTag : $name);
+					$element->appendChild($child);
+					$this->buildXml($child, $value);
+				} else {
+					$child = new DOMElement(is_int($name) ? $this->itemTag : $name);
+					$element->appendChild($child);
+					$child->appendChild(new DOMText((string)$value));
+				}
+			}
+		} else {
+			$element->appendChild(new DOMText((string)$data));
+		}
+	}
+}
diff --git a/framework/yii/web/YiiAsset.php b/framework/web/YiiAsset.php
similarity index 100%
rename from framework/yii/web/YiiAsset.php
rename to framework/web/YiiAsset.php
diff --git a/framework/yii/widgets/ActiveField.php b/framework/widgets/ActiveField.php
similarity index 100%
rename from framework/yii/widgets/ActiveField.php
rename to framework/widgets/ActiveField.php
diff --git a/framework/widgets/ActiveForm.php b/framework/widgets/ActiveForm.php
new file mode 100644
index 0000000..b218a2e
--- /dev/null
+++ b/framework/widgets/ActiveForm.php
@@ -0,0 +1,357 @@
+<?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\Model;
+use yii\helpers\Html;
+use yii\helpers\Json;
+use yii\web\JsExpression;
+
+/**
+ * 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 array the HTML attributes (name-value pairs) for the form tag.
+	 * The values will be HTML-encoded using [[Html::encode()]].
+	 * If a value is null, the corresponding attribute will not be rendered.
+	 */
+	public $options = [];
+	/**
+	 * @var array the default configuration used by [[field()]] when creating a new field object.
+	 */
+	public $fieldConfig;
+	/**
+	 * @var string the default CSS class for the error summary container.
+	 * @see errorSummary()
+	 */
+	public $errorSummaryCssClass = 'error-summary';
+	/**
+	 * @var string the CSS class that is added to a field container when the associated attribute is required.
+	 */
+	public $requiredCssClass = 'required';
+	/**
+	 * @var string the CSS class that is added to a field container when the associated attribute has validation error.
+	 */
+	public $errorCssClass = 'has-error';
+	/**
+	 * @var string the CSS class that is added to a field container when the associated attribute is successfully validated.
+	 */
+	public $successCssClass = 'has-success';
+	/**
+	 * @var string the CSS class that is added to a field container when the associated attribute is being validated.
+	 */
+	public $validatingCssClass = 'validating';
+	/**
+	 * @var boolean whether to enable client-side data validation.
+	 * If [[ActiveField::enableClientValidation]] is set, its value will take precedence for that input field.
+	 */
+	public $enableClientValidation = true;
+	/**
+	 * @var boolean whether to enable AJAX-based data validation.
+	 * If [[ActiveField::enableAjaxValidation]] is set, its value will take precedence for that input field.
+	 */
+	public $enableAjaxValidation = false;
+	/**
+	 * @var array|string the URL for performing AJAX-based validation. This property will be processed by
+	 * [[Html::url()]]. Please refer to [[Html::url()]] for more details on how to configure this property.
+	 * If this property is not set, it will take the value of the form's action attribute.
+	 */
+	public $validationUrl;
+	/**
+	 * @var boolean whether to perform validation when the form is submitted.
+	 */
+	public $validateOnSubmit = true;
+	/**
+	 * @var boolean whether to perform validation when an input field loses focus and its value is found changed.
+	 * If [[ActiveField::validateOnChange]] is set, its value will take precedence for that input field.
+	 */
+	public $validateOnChange = true;
+	/**
+	 * @var boolean whether to perform validation while the user is typing in an input field.
+	 * If [[ActiveField::validateOnType]] is set, its value will take precedence for that input field.
+	 * @see validationDelay
+	 */
+	public $validateOnType = false;
+	/**
+	 * @var integer number of milliseconds that the validation should be delayed when an input field
+	 * is changed or the user types in the field.
+	 * If [[ActiveField::validationDelay]] is set, its value will take precedence for that input field.
+	 */
+	public $validationDelay = 200;
+	/**
+	 * @var string the name of the GET parameter indicating the validation request is an AJAX request.
+	 */
+	public $ajaxVar = 'ajax';
+	/**
+	 * @var string|JsExpression a JS callback that will be called when the form is being submitted.
+	 * The signature of the callback should be:
+	 *
+	 * ~~~
+	 * function ($form) {
+	 *     ...return false to cancel submission...
+	 * }
+	 * ~~~
+	 */
+	public $beforeSubmit;
+	/**
+	 * @var string|JsExpression a JS callback that is called before validating an attribute.
+	 * The signature of the callback should be:
+	 *
+	 * ~~~
+	 * function ($form, attribute, messages) {
+	 *     ...return false to cancel the validation...
+	 * }
+	 * ~~~
+	 */
+	public $beforeValidate;
+	/**
+	 * @var string|JsExpression a JS callback that is called after validating an attribute.
+	 * The signature of the callback should be:
+	 *
+	 * ~~~
+	 * function ($form, attribute, messages) {
+	 * }
+	 * ~~~
+	 */
+	public $afterValidate;
+	/**
+	 * @var array the client validation options for individual attributes. Each element of the array
+	 * represents the validation options for a particular attribute.
+	 * @internal
+	 */
+	public $attributes = [];
+
+	/**
+	 * Initializes the widget.
+	 * This renders the form open tag.
+	 */
+	public function init()
+	{
+		if (!isset($this->options['id'])) {
+			$this->options['id'] = $this->getId();
+		}
+		if (!isset($this->fieldConfig['class'])) {
+			$this->fieldConfig['class'] = ActiveField::className();
+		}
+		echo Html::beginForm($this->action, $this->method, $this->options);
+	}
+
+	/**
+	 * Runs the widget.
+	 * This registers the necessary javascript code and renders the form close tag.
+	 */
+	public function run()
+	{
+		if (!empty($this->attributes)) {
+			$id = $this->options['id'];
+			$options = Json::encode($this->getClientOptions());
+			$attributes = Json::encode($this->attributes);
+			$view = $this->getView();
+			ActiveFormAsset::register($view);
+			$view->registerJs("jQuery('#$id').yiiActiveForm($attributes, $options);");
+		}
+		echo Html::endForm();
+	}
+
+	/**
+	 * Returns the options for the form JS widget.
+	 * @return array the options
+	 */
+	protected function getClientOptions()
+	{
+		$options = [
+			'errorSummary' => '.' . $this->errorSummaryCssClass,
+			'validateOnSubmit' => $this->validateOnSubmit,
+			'errorCssClass' => $this->errorCssClass,
+			'successCssClass' => $this->successCssClass,
+			'validatingCssClass' => $this->validatingCssClass,
+			'ajaxVar' => $this->ajaxVar,
+		];
+		if ($this->validationUrl !== null) {
+			$options['validationUrl'] = Html::url($this->validationUrl);
+		}
+		foreach (['beforeSubmit', 'beforeValidate', 'afterValidate'] as $name) {
+			if (($value = $this->$name) !== null) {
+				$options[$name] = $value instanceof JsExpression ? $value : new JsExpression($value);
+			}
+		}
+		return $options;
+	}
+
+	/**
+	 * Generates a summary of the validation errors.
+	 * If there is no validation error, an empty error summary markup will still be generated, but it will be hidden.
+	 * @param Model|Model[] $models the model(s) associated with this form
+	 * @param array $options the tag options in terms of name-value pairs. The following options are specially handled:
+	 *
+	 * - header: string, the header HTML for the error summary. If not set, a default prompt string will be used.
+	 * - footer: string, the footer HTML for the error summary.
+	 *
+	 * The rest of the options will be rendered as the attributes of the container 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 error summary
+	 */
+	public function errorSummary($models, $options = [])
+	{
+		if (!is_array($models)) {
+			$models = [$models];
+		}
+
+		$lines = [];
+		foreach ($models as $model) {
+			/** @var Model $model */
+			foreach ($model->getFirstErrors() as $error) {
+				$lines[] = Html::encode($error);
+			}
+		}
+
+		$header = isset($options['header']) ? $options['header'] : '<p>' . Yii::t('yii', 'Please fix the following errors:') . '</p>';
+		$footer = isset($options['footer']) ? $options['footer'] : '';
+		unset($options['header'], $options['footer']);
+
+		if (!isset($options['class'])) {
+			$options['class'] = $this->errorSummaryCssClass;
+		} else {
+			$options['class'] .= ' ' . $this->errorSummaryCssClass;
+		}
+
+		if (!empty($lines)) {
+			$content = "<ul><li>" . implode("</li>\n<li>", $lines) . "</li><ul>";
+			return Html::tag('div', $header . $content . $footer, $options);
+		} else {
+			$content = "<ul></ul>";
+			$options['style'] = isset($options['style']) ? rtrim($options['style'], ';') . '; display:none' : 'display:none';
+			return Html::tag('div', $header . $content . $footer, $options);
+		}
+	}
+
+	/**
+	 * Generates a form field.
+	 * A form field is associated with a model and an attribute. It contains a label, an input and an error message
+	 * and use them to interact with end users to collect their inputs for the attribute.
+	 * @param Model $model the data model
+	 * @param string $attribute the attribute name or expression. See [[Html::getAttributeName()]] for the format
+	 * about attribute expression.
+	 * @param array $options the additional configurations for the field object
+	 * @return ActiveField the created ActiveField object
+	 * @see fieldConfig
+	 */
+	public function field($model, $attribute, $options = [])
+	{
+		return Yii::createObject(array_merge($this->fieldConfig, $options, [
+			'model' => $model,
+			'attribute' => $attribute,
+			'form' => $this,
+		]));
+	}
+
+	/**
+	 * Validates one or several models and returns an error message array indexed by the attribute IDs.
+	 * This is a helper method that simplifies the way of writing AJAX validation code.
+	 *
+	 * For example, you may use the following code in a controller action to respond
+	 * to an AJAX validation request:
+	 *
+	 * ~~~
+	 * $model = new Post;
+	 * $model->load($_POST);
+	 * if (Yii::$app->request->isAjax) {
+	 *     Yii::$app->response->format = Response::FORMAT_JSON;
+	 *     return ActiveForm::validate($model);
+	 * }
+	 * // ... respond to non-AJAX request ...
+	 * ~~~
+	 *
+	 * To validate multiple models, simply pass each model as a parameter to this method, like
+	 * the following:
+	 *
+	 * ~~~
+	 * ActiveForm::validate($model1, $model2, ...);
+	 * ~~~
+	 *
+	 * @param Model $model the model to be validated
+	 * @param mixed $attributes list of attributes that should be validated.
+	 * If this parameter is empty, it means any attribute listed in the applicable
+	 * validation rules should be validated.
+	 *
+	 * When this method is used to validate multiple models, this parameter will be interpreted
+	 * as a model.
+	 *
+	 * @return array the error message array indexed by the attribute IDs.
+	 */
+	public static function validate($model, $attributes = null)
+	{
+		$result = [];
+		if ($attributes instanceof Model) {
+			// validating multiple models
+			$models = func_get_args();
+			$attributes = null;
+		} else {
+			$models = [$model];
+		}
+		/** @var Model $model */
+		foreach ($models as $model) {
+			$model->validate($attributes);
+			foreach ($model->getErrors() as $attribute => $errors) {
+				$result[Html::getInputId($model, $attribute)] = $errors;
+			}
+		}
+		return $result;
+	}
+
+	/**
+	 * Validates an array of model instances and returns an error message array indexed by the attribute IDs.
+	 * This is a helper method that simplifies the way of writing AJAX validation code for tabular input.
+	 *
+	 * For example, you may use the following code in a controller action to respond
+	 * to an AJAX validation request:
+	 *
+	 * ~~~
+	 * // ... load $models ...
+	 * if (Yii::$app->request->isAjax) {
+	 *     Yii::$app->response->format = Response::FORMAT_JSON;
+	 *     return ActiveForm::validateMultiple($models);
+	 * }
+	 * // ... respond to non-AJAX request ...
+	 * ~~~
+	 *
+	 * @param array $models an array of models to be validated.
+	 * @param mixed $attributes list of attributes that should be validated.
+	 * If this parameter is empty, it means any attribute listed in the applicable
+	 * validation rules should be validated.
+	 * @return array the error message array indexed by the attribute IDs.
+	 */
+	public static function validateMultiple($models, $attributes = null)
+	{
+		$result = [];
+		/** @var Model $model */
+		foreach ($models as $i => $model) {
+			$model->validate($attributes);
+			foreach ($model->getErrors() as $attribute => $errors) {
+				$result[Html::getInputId($model, "[$i]" . $attribute)] = $errors;
+			}
+		}
+		return $result;
+	}
+}
diff --git a/framework/yii/widgets/ActiveFormAsset.php b/framework/widgets/ActiveFormAsset.php
similarity index 100%
rename from framework/yii/widgets/ActiveFormAsset.php
rename to framework/widgets/ActiveFormAsset.php
diff --git a/framework/widgets/BaseListView.php b/framework/widgets/BaseListView.php
new file mode 100644
index 0000000..4c4e5a4
--- /dev/null
+++ b/framework/widgets/BaseListView.php
@@ -0,0 +1,215 @@
+<?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\helpers\ArrayHelper;
+use yii\helpers\Html;
+
+/**
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+abstract class BaseListView extends Widget
+{
+	/**
+	 * @var array the HTML attributes for the container tag of the list view.
+	 * The "tag" element specifies the tag name of the container element and defaults to "div".
+	 */
+	public $options = [];
+	/**
+	 * @var \yii\data\DataProviderInterface the data provider for the view. This property is required.
+	 */
+	public $dataProvider;
+	/**
+	 * @var array the configuration for the pager widget. By default, [[LinkPager]] will be
+	 * used to render the pager. You can use a different widget class by configuring the "class" element.
+	 */
+	public $pager = [];
+	/**
+	 * @var array the configuration for the sorter widget. By default, [[LinkSorter]] will be
+	 * used to render the sorter. You can use a different widget class by configuring the "class" element.
+	 */
+	public $sorter = [];
+	/**
+	 * @var string the HTML content to be displayed as the summary of the list view.
+	 * If you do not want to show the summary, you may set it with an empty string.
+	 *
+	 * The following tokens will be replaced with the corresponding values:
+	 *
+	 * - `{begin}`: the starting row number (1-based) currently being displayed
+	 * - `{end}`: the ending row number (1-based) currently being displayed
+	 * - `{count}`: the number of rows currently being displayed
+	 * - `{totalCount}`: the total number of rows available
+	 * - `{page}`: the page number (1-based) current being displayed
+	 * - `{pageCount}`: the number of pages available
+	 */
+	public $summary;
+	/**
+	 * @var boolean whether to show the list view if [[dataProvider]] returns no data.
+	 */
+	public $showOnEmpty = false;
+	/**
+	 * @var string the HTML content to be displayed when [[dataProvider]] does not have any data.
+	 */
+	public $emptyText;
+	/**
+	 * @var string the layout that determines how different sections of the list view should be organized.
+	 * The following tokens will be replaced with the corresponding section contents:
+	 *
+	 * - `{summary}`: the summary section. See [[renderSummary()]].
+	 * - `{items}`: the list items. See [[renderItems()]].
+	 * - `{sorter}`: the sorter. See [[renderSorter()]].
+	 * - `{pager}`: the pager. See [[renderPager()]].
+	 */
+	public $layout = "{summary}\n{items}\n{pager}";
+
+
+	/**
+	 * Renders the data models.
+	 * @return string the rendering result.
+	 */
+	abstract public function renderItems();
+
+	/**
+	 * Initializes the view.
+	 */
+	public function init()
+	{
+		if ($this->dataProvider === null) {
+			throw new InvalidConfigException('The "dataProvider" property must be set.');
+		}
+		if ($this->emptyText === null) {
+			$this->emptyText = Yii::t('yii', 'No results found.');
+		}
+		$this->dataProvider->prepare();
+	}
+
+	/**
+	 * Runs the widget.
+	 */
+	public function run()
+	{
+		if ($this->dataProvider->getCount() > 0 || $this->showOnEmpty) {
+			$content = preg_replace_callback("/{\\w+}/", function ($matches) {
+				$content = $this->renderSection($matches[0]);
+				return $content === false ? $matches[0] : $content;
+			}, $this->layout);
+		} else {
+			$content = $this->renderEmpty();
+		}
+		$tag = ArrayHelper::remove($this->options, 'tag', 'div');
+		echo Html::tag($tag, $content, $this->options);
+	}
+
+	/**
+	 * Renders a section of the specified name.
+	 * If the named section is not supported, false will be returned.
+	 * @param string $name the section name, e.g., `{summary}`, `{items}`.
+	 * @return string|boolean the rendering result of the section, or false if the named section is not supported.
+	 */
+	public function renderSection($name)
+	{
+		switch ($name) {
+			case '{summary}':
+				return $this->renderSummary();
+			case '{items}':
+				return $this->renderItems();
+			case '{pager}':
+				return $this->renderPager();
+			case '{sorter}':
+				return $this->renderSorter();
+			default:
+				return false;
+		}
+	}
+
+	/**
+	 * Renders the HTML content indicating that the list view has no data.
+	 * @return string the rendering result
+	 * @see emptyText
+	 */
+	public function renderEmpty()
+	{
+		return '<div class="empty">' . ($this->emptyText === null ? Yii::t('yii', 'No results found.') : $this->emptyText) . '</div>';
+	}
+
+	/**
+	 * Renders the summary text.
+	 */
+	public function renderSummary()
+	{
+		$count = $this->dataProvider->getCount();
+		if ($count <= 0) {
+			return '';
+		}
+		if (($pagination = $this->dataProvider->getPagination()) !== false) {
+			$totalCount = $this->dataProvider->getTotalCount();
+			$begin = $pagination->getPage() * $pagination->pageSize + 1;
+			$end = $begin + $count - 1;
+			if ($begin > $end) {
+				$begin = $end;
+			}
+			$page = $pagination->getPage() + 1;
+			$pageCount = $pagination->pageCount;
+			if (($summaryContent = $this->summary) === null) {
+				$summaryContent = '<div class="summary">'
+					. Yii::t('yii', 'Showing <b>{begin, number}-{end, number}</b> of <b>{totalCount, number}</b> {totalCount, plural, one{item} other{items}}.')
+					. '</div>';
+			}
+		} else {
+			$begin = $page = $pageCount = 1;
+			$end = $totalCount = $count;
+			if (($summaryContent = $this->summary) === null) {
+				$summaryContent = '<div class="summary">' . Yii::t('yii', 'Total <b>{count, number}</b> {count, plural, one{item} other{items}}.') . '</div>';
+			}
+		}
+		return Yii::$app->getI18n()->format($summaryContent, [
+			'begin' => $begin,
+			'end' => $end,
+			'count' => $count,
+			'totalCount' => $totalCount,
+			'page' => $page,
+			'pageCount' => $pageCount,
+		], Yii::$app->language);
+	}
+
+	/**
+	 * Renders the pager.
+	 * @return string the rendering result
+	 */
+	public function renderPager()
+	{
+		$pagination = $this->dataProvider->getPagination();
+		if ($pagination === false || $this->dataProvider->getCount() <= 0) {
+			return '';
+		}
+		/** @var LinkPager $class */
+		$class = ArrayHelper::remove($this->pager, 'class', LinkPager::className());
+		$this->pager['pagination'] = $pagination;
+		return $class::widget($this->pager);
+	}
+
+	/**
+	 * Renders the sorter.
+	 * @return string the rendering result
+	 */
+	public function renderSorter()
+	{
+		$sort = $this->dataProvider->getSort();
+		if ($sort === false || empty($sort->attributes) || $this->dataProvider->getCount() <= 0) {
+			return '';
+		}
+		/** @var LinkSorter $class */
+		$class = ArrayHelper::remove($this->sorter, 'class', LinkSorter::className());
+		$this->sorter['sort'] = $sort;
+		return $class::widget($this->sorter);
+	}
+}
diff --git a/framework/widgets/Block.php b/framework/widgets/Block.php
new file mode 100644
index 0000000..fdd210f
--- /dev/null
+++ b/framework/widgets/Block.php
@@ -0,0 +1,49 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\widgets;
+
+use yii\base\Widget;
+
+/**
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class Block extends Widget
+{
+	/**
+	 * @var string the ID of this block.
+	 */
+	public $id;
+	/**
+	 * @var boolean whether to render the block content in place. Defaults to false,
+	 * meaning the captured block content will not be displayed.
+	 */
+	public $renderInPlace = false;
+
+	/**
+	 * Starts recording a block.
+	 */
+	public function init()
+	{
+		ob_start();
+		ob_implicit_flush(false);
+	}
+
+	/**
+	 * Ends recording a block.
+	 * This method stops output buffering and saves the rendering result as a named block in the controller.
+	 */
+	public function run()
+	{
+		$block = ob_get_clean();
+		if ($this->renderInPlace) {
+			echo $block;
+		}
+		$this->view->blocks[$this->id] = $block;
+	}
+}
diff --git a/framework/widgets/Breadcrumbs.php b/framework/widgets/Breadcrumbs.php
new file mode 100644
index 0000000..2353845
--- /dev/null
+++ b/framework/widgets/Breadcrumbs.php
@@ -0,0 +1,141 @@
+<?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\InvalidConfigException;
+use yii\helpers\Html;
+
+/**
+ * Breadcrumbs displays a list of links indicating the position of the current page in the whole site hierarchy.
+ *
+ * For example, breadcrumbs like "Home / Sample Post / Edit" means the user is viewing an edit page
+ * for the "Sample Post". He can click on "Sample Post" to view that page, or he can click on "Home"
+ * to return to the homepage.
+ *
+ * To use Breadcrumbs, you need to configure its [[links]] property, which specifies the links to be displayed. For example,
+ *
+ * ~~~
+ * // $this is the view object currently being used
+ * echo Breadcrumbs::widget([
+ *     'links' => [
+ *         ['label' => 'Sample Post', 'url' => ['post/edit', 'id' => 1]],
+ *         'Edit',
+ *     ],
+ * ]);
+ * ~~~
+ *
+ * Because breadcrumbs usually appears in nearly every page of a website, you may consider placing it in a layout view.
+ * You can use a view parameter (e.g. `$this->params['breadcrumbs']`) to configure the links in different
+ * views. In the layout view, you assign this view parameter to the [[links]] property like the following:
+ *
+ * ~~~
+ * // $this is the view object currently being used
+ * echo Breadcrumbs::widget([
+ *     'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [],
+ * ]);
+ * ~~~
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class Breadcrumbs extends Widget
+{
+	/**
+	 * @var string the name of the breadcrumb container tag.
+	 */
+	public $tag = 'ul';
+	/**
+	 * @var array the HTML attributes for the breadcrumb container tag.
+	 */
+	public $options = ['class' => 'breadcrumb'];
+	/**
+	 * @var boolean whether to HTML-encode the link labels.
+	 */
+	public $encodeLabels = true;
+	/**
+	 * @var string the first hyperlink in the breadcrumbs (called home link).
+	 * If this property is not set, it will default to a link pointing to [[\yii\web\Application::homeUrl]]
+	 * with the label 'Home'. If this property is false, the home link will not be rendered.
+	 */
+	public $homeLink;
+	/**
+	 * @var array list of links to appear in the breadcrumbs. If this property is empty,
+	 * the widget will not render anything. Each array element represents a single link in the breadcrumbs
+	 * with the following structure:
+	 *
+	 * ~~~
+	 * [
+	 *     'label' => 'label of the link',  // required
+	 *     'url' => 'url of the link',      // optional, will be processed by Html::url()
+	 * ]
+	 * ~~~
+	 *
+	 * If a link is active, you only need to specify its "label", and instead of writing `['label' => $label]`,
+	 * you should simply use `$label`.
+	 */
+	public $links = [];
+	/**
+	 * @var string the template used to render each inactive item in the breadcrumbs. The token `{link}`
+	 * will be replaced with the actual HTML link for each inactive item.
+	 */
+	public $itemTemplate = "<li>{link}</li>\n";
+	/**
+	 * @var string the template used to render each active item in the breadcrumbs. The token `{link}`
+	 * will be replaced with the actual HTML link for each active item.
+	 */
+	public $activeItemTemplate = "<li class=\"active\">{link}</li>\n";
+
+	/**
+	 * Renders the widget.
+	 */
+	public function run()
+	{
+		if (empty($this->links)) {
+			return;
+		}
+		$links = [];
+		if ($this->homeLink === null) {
+			$links[] = $this->renderItem([
+				'label' => Yii::t('yii', 'Home'),
+				'url' => Yii::$app->homeUrl,
+			], $this->itemTemplate);
+		} elseif ($this->homeLink !== false) {
+			$links[] = $this->renderItem($this->homeLink, $this->itemTemplate);
+		}
+		foreach ($this->links as $link) {
+			if (!is_array($link)) {
+				$link = ['label' => $link];
+			}
+			$links[] = $this->renderItem($link, isset($link['url']) ? $this->itemTemplate : $this->activeItemTemplate);
+		}
+		echo Html::tag($this->tag, implode('', $links), $this->options);
+	}
+
+	/**
+	 * Renders a single breadcrumb item.
+	 * @param array $link the link to be rendered. It must contain the "label" element. The "url" element is optional.
+	 * @param string $template the template to be used to rendered the link. The token "{link}" will be replaced by the link.
+	 * @return string the rendering result
+	 * @throws InvalidConfigException if `$link` does not have "label" element.
+	 */
+	protected function renderItem($link, $template)
+	{
+		if (isset($link['label'])) {
+			$label = $this->encodeLabels ? Html::encode($link['label']) : $link['label'];
+		} else {
+			throw new InvalidConfigException('The "label" element is required for each link.');
+		}
+		if (isset($link['url'])) {
+			return strtr($template, ['{link}' => Html::a($label, $link['url'])]);
+		} else {
+			return strtr($template, ['{link}' => $label]);
+		}
+	}
+}
diff --git a/framework/widgets/ContentDecorator.php b/framework/widgets/ContentDecorator.php
new file mode 100644
index 0000000..9224f35
--- /dev/null
+++ b/framework/widgets/ContentDecorator.php
@@ -0,0 +1,52 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\widgets;
+
+use yii\base\InvalidConfigException;
+use yii\base\Widget;
+
+/**
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class ContentDecorator extends Widget
+{
+	/**
+	 * @var string the view file that will be used to decorate the content enclosed by this widget.
+	 * This can be specified as either the view file path or path alias.
+	 */
+	public $viewFile;
+	/**
+	 * @var array the parameters (name => value) to be extracted and made available in the decorative view.
+	 */
+	public $params = [];
+
+	/**
+	 * Starts recording a clip.
+	 */
+	public function init()
+	{
+		if ($this->viewFile === null) {
+			throw new InvalidConfigException('ContentDecorator::viewFile 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();
+		// render under the existing context
+		echo $this->view->renderFile($this->viewFile, $params);
+	}
+}
diff --git a/framework/widgets/DetailView.php b/framework/widgets/DetailView.php
new file mode 100644
index 0000000..3a45a4e
--- /dev/null
+++ b/framework/widgets/DetailView.php
@@ -0,0 +1,211 @@
+<?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\Arrayable;
+use yii\base\Formatter;
+use yii\base\InvalidConfigException;
+use yii\base\Model;
+use yii\base\Widget;
+use yii\helpers\ArrayHelper;
+use yii\helpers\Html;
+use yii\helpers\Inflector;
+
+/**
+ * DetailView displays the detail of a single data [[model]].
+ *
+ * DetailView is best used for displaying a model in a regular format (e.g. each model attribute
+ * is displayed as a row in a table.) The model can be either an instance of [[Model]]
+ * or an associative array.
+ *
+ * DetailView uses the [[attributes]] property to determines which model attributes
+ * should be displayed and how they should be formatted.
+ *
+ * A typical usage of DetailView is as follows:
+ *
+ * ~~~
+ * echo DetailView::widget([
+ *     'model' => $model,
+ *     'attributes' => [
+ *         'title',             // title attribute (in plain text)
+ *         'description:html',  // description attribute in HTML
+ *         [                    // the owner name of the model
+ *             'label' => 'Owner',
+ *             'value' => $model->owner->name,
+ *         ],
+ *     ],
+ * ]);
+ * ~~~
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class DetailView extends Widget
+{
+	/**
+	 * @var array|object the data model whose details are to be displayed. This can be either a [[Model]] instance
+	 * or an associative array.
+	 */
+	public $model;
+	/**
+	 * @var array a list of attributes to be displayed in the detail view. Each array element
+	 * represents the specification for displaying one particular attribute.
+	 *
+	 * An attribute can be specified as a string in the format of "Name" or "Name:Format", where "Name" refers to
+	 * the attribute name, and "Format" represents the format of the attribute. The "Format" is passed to the [[Formatter::format()]]
+	 * method to format an attribute value into a displayable text. Please refer to [[Formatter]] for the supported types.
+	 *
+	 * An attribute can also be specified in terms of an array with the following elements:
+	 *
+	 * - name: the attribute name. This is required if either "label" or "value" is not specified.
+	 * - label: the label associated with the attribute. If this is not specified, it will be generated from the attribute name.
+	 * - value: the value to be displayed. If this is not specified, it will be retrieved from [[model]] using the attribute name
+	 *   by calling [[ArrayHelper::getValue()]]. Note that this value will be formatted into a displayable text
+	 *   according to the "format" option.
+	 * - format: the type of the value that determines how the value would be formatted into a displayable text.
+	 *   Please refer to [[Formatter]] for supported types.
+	 * - visible: whether the attribute is visible. If set to `false`, the attribute will NOT be displayed.
+	 */
+	public $attributes;
+	/**
+	 * @var string|callback the template used to render a single attribute. If a string, the token `{label}`
+	 * and `{value}` will be replaced with the label and the value of the corresponding attribute.
+	 * If a callback (e.g. an anonymous function), the signature must be as follows:
+	 *
+	 * ~~~
+	 * function ($attribute, $index, $widget)
+	 * ~~~
+	 *
+	 * where `$attribute` refer to the specification of the attribute being rendered, `$index` is the zero-based
+	 * index of the attribute in the [[attributes]] array, and `$widget` refers to this widget instance.
+	 */
+	public $template = "<tr><th>{label}</th><td>{value}</td></tr>";
+	/**
+	 * @var array the HTML attributes for the container tag of this widget. The "tag" option specifies
+	 * what container tag should be used. It defaults to "table" if not set.
+	 */
+	public $options = ['class' => 'table table-striped table-bordered detail-view'];
+	/**
+	 * @var array|Formatter the formatter used to format model attribute values into displayable texts.
+	 * This can be either an instance of [[Formatter]] or an configuration array for creating the [[Formatter]]
+	 * instance. If this property is not set, the "formatter" application component will be used.
+	 */
+	public $formatter;
+
+	/**
+	 * Initializes the detail view.
+	 * This method will initialize required property values.
+	 */
+	public function init()
+	{
+		if ($this->model === null) {
+			throw new InvalidConfigException('Please specify the "model" property.');
+		}
+		if ($this->formatter == null) {
+			$this->formatter = Yii::$app->getFormatter();
+		} elseif (is_array($this->formatter)) {
+			$this->formatter = Yii::createObject($this->formatter);
+		}
+		if (!$this->formatter instanceof Formatter) {
+			throw new InvalidConfigException('The "formatter" property must be either a Format object or a configuration array.');
+		}
+		$this->normalizeAttributes();
+	}
+
+	/**
+	 * Renders the detail view.
+	 * This is the main entry of the whole detail view rendering.
+	 */
+	public function run()
+	{
+		$rows = [];
+		$i = 0;
+		foreach ($this->attributes as $attribute) {
+			$rows[] = $this->renderAttribute($attribute, $i++);
+		}
+
+		$tag = ArrayHelper::remove($this->options, 'tag', 'table');
+		echo Html::tag($tag, implode("\n", $rows), $this->options);
+	}
+
+	/**
+	 * Renders a single attribute.
+	 * @param array $attribute the specification of the attribute to be rendered.
+	 * @param integer $index the zero-based index of the attribute in the [[attributes]] array
+	 * @return string the rendering result
+	 */
+	protected function renderAttribute($attribute, $index)
+	{
+		if (is_string($this->template)) {
+			return strtr($this->template, [
+				'{label}' => $attribute['label'],
+				'{value}' => $this->formatter->format($attribute['value'], $attribute['format']),
+			]);
+		} else {
+			return call_user_func($this->template, $attribute, $index, $this);
+		}
+	}
+
+	/**
+	 * Normalizes the attribute specifications.
+	 * @throws InvalidConfigException
+	 */
+	protected function normalizeAttributes()
+	{
+		if ($this->attributes === null) {
+			if ($this->model instanceof Model) {
+				$this->attributes = $this->model->attributes();
+			} elseif (is_object($this->model)) {
+				$this->attributes = $this->model instanceof Arrayable ? $this->model->toArray() : array_keys(get_object_vars($this->model));
+			} elseif (is_array($this->model)) {
+				$this->attributes = array_keys($this->model);
+			} else {
+				throw new InvalidConfigException('The "model" property must be either an array or an object.');
+			}
+			sort($this->attributes);
+		}
+
+		foreach ($this->attributes as $i => $attribute) {
+			if (is_string($attribute)) {
+				if (!preg_match('/^(\w+)(\s*:\s*(\w+))?$/', $attribute, $matches)) {
+					throw new InvalidConfigException('The attribute must be specified in the format of "Name" or "Name:Format"');
+				}
+				$attribute = [
+					'name' => $matches[1],
+					'format' => isset($matches[3]) ? $matches[3] : 'text',
+				];
+			}
+
+			if (!is_array($attribute)) {
+				throw new InvalidConfigException('The attribute configuration must be an array.');
+			}
+
+			if (isset($attribute['visible']) && !$attribute['visible']) {
+				continue;
+			}
+
+			if (!isset($attribute['format'])) {
+				$attribute['format'] = 'text';
+			}
+			if (isset($attribute['name'])) {
+				$name = $attribute['name'];
+				if (!isset($attribute['label'])) {
+					$attribute['label'] = $this->model instanceof Model ? $this->model->getAttributeLabel($name) : Inflector::camel2words($name, true);
+				}
+				if (!array_key_exists('value', $attribute)) {
+					$attribute['value'] = ArrayHelper::getValue($this->model, $name);
+				}
+			} elseif (!isset($attribute['label']) || !array_key_exists('value', $attribute)) {
+				throw new InvalidConfigException('The attribute configuration requires the "name" element to determine the value and display label.');
+			}
+
+			$this->attributes[$i] = $attribute;
+		}
+	}
+}
diff --git a/framework/widgets/FragmentCache.php b/framework/widgets/FragmentCache.php
new file mode 100644
index 0000000..57c4659
--- /dev/null
+++ b/framework/widgets/FragmentCache.php
@@ -0,0 +1,178 @@
+<?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\caching\Cache;
+use yii\caching\Dependency;
+
+/**
+ *
+ * @property string|boolean $cachedContent The cached content. False is returned if valid content is not found
+ * in the cache. This property is read-only.
+ *
+ * @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,
+	 *
+	 * ~~~
+	 * [
+	 *     '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:
+	 *
+	 * ~~~
+	 * [
+	 *     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 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->enabled) {
+			$this->cache = null;
+		} elseif (is_string($this->cache)) {
+			$this->cache = Yii::$app->getComponent($this->cache);
+		}
+
+		if ($this->getCachedContent() === false) {
+			$this->getView()->cacheStack[] = $this;
+			ob_start();
+			ob_implicit_flush(false);
+		}
+	}
+
+	/**
+	 * Marks the end of content to be cached.
+	 * Content displayed before this method call and after [[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->getView()->cacheStack);
+			if (is_array($this->dependency)) {
+				$this->dependency = Yii::createObject($this->dependency);
+			}
+			$data = [$content, $this->dynamicPlaceholders];
+			$this->cache->set($this->calculateKey(), $data, $this->duration, $this->dependency);
+
+			if (empty($this->getView()->cacheStack) && !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 (empty($this->getView()->cacheStack)) {
+							// outermost cache: replace placeholder with dynamic content
+							$content = $this->updateDynamicContent($content, $placeholders);
+						}
+						foreach ($placeholders as $name => $statements) {
+							$this->getView()->addDynamicPlaceholder($name, $statements);
+						}
+					}
+					$this->_content = $content;
+				}
+			}
+		}
+		return $this->_content;
+	}
+
+	protected function updateDynamicContent($content, $placeholders)
+	{
+		foreach ($placeholders as $name => $statements) {
+			$placeholders[$name] = $this->getView()->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 mixed a valid cache key
+	 */
+	protected function calculateKey()
+	{
+		$factors = [__CLASS__, $this->getId()];
+		if (is_array($this->variations)) {
+			foreach ($this->variations as $factor) {
+				$factors[] = $factor;
+			}
+		}
+		return $factors;
+	}
+}
diff --git a/framework/widgets/InputWidget.php b/framework/widgets/InputWidget.php
new file mode 100644
index 0000000..0a4b5b7
--- /dev/null
+++ b/framework/widgets/InputWidget.php
@@ -0,0 +1,72 @@
+<?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\Model;
+use yii\base\InvalidConfigException;
+use yii\helpers\Html;
+
+/**
+ * InputWidget is the base class for widgets that collect user inputs.
+ *
+ * An input widget can be associated with a data model and an attribute,
+ * or a name and a value. If the former, the name and the value will
+ * be generated automatically.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class InputWidget extends Widget
+{
+	/**
+	 * @var Model the data model that this widget is associated with.
+	 */
+	public $model;
+	/**
+	 * @var string the model attribute that this widget is associated with.
+	 */
+	public $attribute;
+	/**
+	 * @var string the input name. This must be set if [[model]] and [[attribute]] are not set.
+	 */
+	public $name;
+	/**
+	 * @var string the input value.
+	 */
+	public $value;
+	/**
+	 * @var array the HTML attributes for the input tag.
+	 */
+	public $options = [];
+
+
+	/**
+	 * Initializes the widget.
+	 * If you override this method, make sure you call the parent implementation first.
+	 */
+	public function init()
+	{
+		if (!$this->hasModel() && $this->name === null) {
+			throw new InvalidConfigException("Either 'name', or 'model' and 'attribute' properties must be specified.");
+		}
+		if (!isset($this->options['id'])) {
+			$this->options['id'] = $this->hasModel() ? Html::getInputId($this->model, $this->attribute) : $this->getId();
+		}
+		parent::init();
+	}
+
+	/**
+	 * @return boolean whether this widget is associated with a data model.
+	 */
+	protected function hasModel()
+	{
+		return $this->model instanceof Model && $this->attribute !== null;
+	}
+}
diff --git a/framework/widgets/LinkPager.php b/framework/widgets/LinkPager.php
new file mode 100644
index 0000000..807a4b8
--- /dev/null
+++ b/framework/widgets/LinkPager.php
@@ -0,0 +1,193 @@
+<?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\helpers\Html;
+use yii\base\Widget;
+use yii\data\Pagination;
+
+/**
+ * LinkPager displays a list of hyperlinks that lead to different pages of target.
+ *
+ * LinkPager works with a [[Pagination]] object which specifies the totally number
+ * of pages and the current page number.
+ *
+ * Note that LinkPager only generates the necessary HTML markups. In order for it
+ * to look like a real pager, you should provide some CSS styles for it.
+ * With the default configuration, LinkPager should look good using Twitter Bootstrap CSS framework.
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class LinkPager extends Widget
+{
+	/**
+	 * @var Pagination the pagination object that this pager is associated with.
+	 * You must set this property in order to make LinkPager work.
+	 */
+	public $pagination;
+	/**
+	 * @var array HTML attributes for the pager container tag.
+	 */
+	public $options = ['class' => 'pagination'];
+	/**
+	 * @var string the CSS class for the "first" page button.
+	 */
+	public $firstPageCssClass = 'first';
+	/**
+	 * @var string the CSS class for the "last" page button.
+	 */
+	public $lastPageCssClass = 'last';
+	/**
+	 * @var string the CSS class for the "previous" page button.
+	 */
+	public $prevPageCssClass = 'prev';
+	/**
+	 * @var string the CSS class for the "next" page button.
+	 */
+	public $nextPageCssClass = 'next';
+	/**
+	 * @var string the CSS class for the active (currently selected) page button.
+	 */
+	public $activePageCssClass = 'active';
+	/**
+	 * @var string the CSS class for the disabled page buttons.
+	 */
+	public $disabledPageCssClass = 'disabled';
+	/**
+	 * @var integer maximum number of page buttons that can be displayed. Defaults to 10.
+	 */
+	public $maxButtonCount = 10;
+	/**
+	 * @var string the label for the "next" page button. Note that this will NOT be HTML-encoded.
+	 * If this property is null, the "next" page button will not be displayed.
+	 */
+	public $nextPageLabel = '&raquo;';
+	/**
+	 * @var string the text label for the previous page button. Note that this will NOT be HTML-encoded.
+	 * If this property is null, the "previous" page button will not be displayed.
+	 */
+	public $prevPageLabel = '&laquo;';
+	/**
+	 * @var string the text label for the "first" page button. Note that this will NOT be HTML-encoded.
+	 * If this property is null, the "first" page button will not be displayed.
+	 */
+	public $firstPageLabel;
+	/**
+	 * @var string the text label for the "last" page button. Note that this will NOT be HTML-encoded.
+	 * If this property is null, the "last" page button will not be displayed.
+	 */
+	public $lastPageLabel;
+
+
+	/**
+	 * Initializes the pager.
+	 */
+	public function init()
+	{
+		if ($this->pagination === null) {
+			throw new InvalidConfigException('The "pagination" property must be set.');
+		}
+	}
+
+	/**
+	 * Executes the widget.
+	 * This overrides the parent implementation by displaying the generated page buttons.
+	 */
+	public function run()
+	{
+		echo $this->renderPageButtons();
+	}
+
+	/**
+	 * Renders the page buttons.
+	 * @return string the rendering result
+	 */
+	protected function renderPageButtons()
+	{
+		$buttons = [];
+
+		$pageCount = $this->pagination->getPageCount();
+		$currentPage = $this->pagination->getPage();
+
+		// first page
+		if ($this->firstPageLabel !== null) {
+			$buttons[] = $this->renderPageButton($this->firstPageLabel, 0, $this->firstPageCssClass, $currentPage <= 0, false);
+		}
+
+		// prev page
+		if ($this->prevPageLabel !== null) {
+			if (($page = $currentPage - 1) < 0) {
+				$page = 0;
+			}
+			$buttons[] = $this->renderPageButton($this->prevPageLabel, $page, $this->prevPageCssClass, $currentPage <= 0, false);
+		}
+
+		// internal pages
+		list($beginPage, $endPage) = $this->getPageRange();
+		for ($i = $beginPage; $i <= $endPage; ++$i) {
+			$buttons[] = $this->renderPageButton($i + 1, $i, null, false, $i == $currentPage);
+		}
+
+		// next page
+		if ($this->nextPageLabel !== null) {
+			if (($page = $currentPage + 1) >= $pageCount - 1) {
+				$page = $pageCount - 1;
+			}
+			$buttons[] = $this->renderPageButton($this->nextPageLabel, $page, $this->nextPageCssClass, $currentPage >= $pageCount - 1, false);
+		}
+
+		// last page
+		if ($this->lastPageLabel !== null) {
+			$buttons[] = $this->renderPageButton($this->lastPageLabel, $pageCount - 1, $this->lastPageCssClass, $currentPage >= $pageCount - 1, false);
+		}
+
+		return Html::tag('ul', implode("\n", $buttons), $this->options);
+	}
+
+	/**
+	 * Renders a page button.
+	 * You may override this method to customize the generation of page buttons.
+	 * @param string $label the text label for the button
+	 * @param integer $page the page number
+	 * @param string $class the CSS class for the page button.
+	 * @param boolean $disabled whether this page button is disabled
+	 * @param boolean $active whether this page button is active
+	 * @return string the rendering result
+	 */
+	protected function renderPageButton($label, $page, $class, $disabled, $active)
+	{
+		$options = ['class' => $class === '' ? null : $class];
+		if ($active) {
+			Html::addCssClass($options, $this->activePageCssClass);
+		}
+		if ($disabled) {
+			Html::addCssClass($options, $this->disabledPageCssClass);
+			return Html::tag('li', Html::tag('span', $label), $options);
+		}
+		return Html::tag('li', Html::a($label, $this->pagination->createUrl($page), ['data-page' => $page]), $options);
+	}
+
+	/**
+	 * @return array the begin and end pages that need to be displayed.
+	 */
+	protected function getPageRange()
+	{
+		$currentPage = $this->pagination->getPage();
+		$pageCount = $this->pagination->getPageCount();
+
+		$beginPage = max(0, $currentPage - (int)($this->maxButtonCount / 2));
+		if (($endPage = $beginPage + $this->maxButtonCount - 1) >= $pageCount) {
+			$endPage = $pageCount - 1;
+			$beginPage = max(0, $endPage - $this->maxButtonCount + 1);
+		}
+		return [$beginPage, $endPage];
+	}
+}
diff --git a/framework/widgets/LinkSorter.php b/framework/widgets/LinkSorter.php
new file mode 100644
index 0000000..bfafcea
--- /dev/null
+++ b/framework/widgets/LinkSorter.php
@@ -0,0 +1,74 @@
+<?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\data\Sort;
+use yii\helpers\Html;
+
+/**
+ * LinkSorter renders a list of sort links for the given sort definition.
+ *
+ * LinkSorter will generate a hyperlink for every attribute declared in [[sort]].
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class LinkSorter extends Widget
+{
+	/**
+	 * @var Sort the sort definition
+	 */
+	public $sort;
+	/**
+	 * @var array list of the attributes that support sorting. If not set, it will be determined
+	 * using [[Sort::attributes]].
+	 */
+	public $attributes;
+	/**
+	 * @var array HTML attributes for the sorter container tag.
+	 * See [[yii\helpers\Html::ul()]] for special attributes.
+	 */
+	public $options = ['class' => 'sorter'];
+
+
+	/**
+	 * Initializes the sorter.
+	 */
+	public function init()
+	{
+		if ($this->sort === null) {
+			throw new InvalidConfigException('The "sort" property must be set.');
+		}
+	}
+
+	/**
+	 * Executes the widget.
+	 * This method renders the sort links.
+	 */
+	public function run()
+	{
+		echo $this->renderSortLinks();
+	}
+
+	/**
+	 * Renders the sort links.
+	 * @return string the rendering result
+	 */
+	protected function renderSortLinks()
+	{
+		$attributes = empty($this->attributes) ? array_keys($this->sort->attributes) : $this->attributes;
+		$links = [];
+		foreach ($attributes as $name) {
+			$links[] = $this->sort->link($name);
+		}
+		return Html::ul($links, array_merge($this->options, ['encode' => false]));
+	}
+}
diff --git a/framework/widgets/ListView.php b/framework/widgets/ListView.php
new file mode 100644
index 0000000..43eaab4
--- /dev/null
+++ b/framework/widgets/ListView.php
@@ -0,0 +1,107 @@
+<?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\helpers\ArrayHelper;
+use yii\helpers\Html;
+
+/**
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class ListView extends BaseListView
+{
+	/**
+	 * @var array the HTML attributes for the container of the rendering result of each data model.
+	 * The "tag" element specifies the tag name of the container element and defaults to "div".
+	 * If "tag" is false, it means no container element will be rendered.
+	 */
+	public $itemOptions = [];
+	/**
+	 * @var string|callback the name of the view for rendering each data item, or a callback (e.g. an anonymous function)
+	 * for rendering each data item. If it specifies a view name, the following variables will
+	 * be available in the view:
+	 *
+	 * - `$model`: mixed, the data model
+	 * - `$key`: mixed, the key value associated with the data item
+	 * - `$index`: integer, the zero-based index of the data item in the items array returned by [[dataProvider]].
+	 * - `$widget`: ListView, this widget instance
+	 *
+	 * Note that the view name is resolved into the view file by the current context of the [[view]] object.
+	 *
+	 * If this property is specified as a callback, it should have the following signature:
+	 *
+	 * ~~~
+	 * function ($model, $key, $index, $widget)
+	 * ~~~
+	 */
+	public $itemView;
+	/**
+	 * @var array additional parameters to be passed to [[itemView]] when it is being rendered.
+	 * This property is used only when [[itemView]] is a string representing a view name.
+	 */
+	public $viewParams = [];
+	/**
+	 * @var string the HTML code to be displayed between any two consecutive items.
+	 */
+	public $separator = "\n";
+	/**
+	 * @var array the HTML attributes for the container tag of the list view.
+	 * The "tag" element specifies the tag name of the container element and defaults to "div".
+	 */
+	public $options = ['class' => 'list-view'];
+
+
+	/**
+	 * Renders all data models.
+	 * @return string the rendering result
+	 */
+	public function renderItems()
+	{
+		$models = $this->dataProvider->getModels();
+		$keys = $this->dataProvider->getKeys();
+		$rows = [];
+		foreach (array_values($models) as $index => $model) {
+			$rows[] = $this->renderItem($model, $keys[$index], $index);
+		}
+		return implode($this->separator, $rows);
+	}
+
+	/**
+	 * Renders a single data model.
+	 * @param mixed $model the data model to be rendered
+	 * @param mixed $key the key value associated with the data model
+	 * @param integer $index the zero-based index of the data model in the model array returned by [[dataProvider]].
+	 * @return string the rendering result
+	 */
+	public function renderItem($model, $key, $index)
+	{
+		if ($this->itemView === null) {
+			$content = $key;
+		} elseif (is_string($this->itemView)) {
+			$content = $this->getView()->render($this->itemView, array_merge([
+				'model' => $model,
+				'key' => $key,
+				'index' => $index,
+				'widget' => $this,
+			], $this->viewParams));
+		} else {
+			$content = call_user_func($this->itemView, $model, $key, $index, $this);
+		}
+		$options = $this->itemOptions;
+		$tag = ArrayHelper::remove($options, 'tag', 'div');
+		if ($tag !== false) {
+			$options['data-key'] = is_array($key) ? json_encode($key) : $key;
+			return Html::tag($tag, $content, $options);
+		} else {
+			return $content;
+		}
+	}
+}
diff --git a/framework/widgets/MaskedInput.php b/framework/widgets/MaskedInput.php
new file mode 100644
index 0000000..7eb42a7
--- /dev/null
+++ b/framework/widgets/MaskedInput.php
@@ -0,0 +1,129 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\widgets;
+
+use yii\base\InvalidConfigException;
+use yii\helpers\Html;
+use yii\helpers\Json;
+use yii\web\JsExpression;
+
+/**
+ * MaskedInput generates a masked text input.
+ *
+ * MaskedInput is similar to [[Html::textInput()]] except that
+ * an input mask will be used to force users to enter properly formatted data,
+ * such as phone numbers, social security numbers.
+ *
+ * To use MaskedInput, you must set the [[mask]] property. The following example
+ * shows how to use MaskedInput to collect phone numbers:
+ *
+ * ~~~
+ * echo MaskedInput::widget([
+ *     'name' => 'phone',
+ *     'mask' => '999-999-9999',
+ * ]);
+ * ~~~
+ *
+ * The masked text field is implemented based on the [jQuery masked input plugin](http://digitalbush.com/projects/masked-input-plugin).
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class MaskedInput extends InputWidget
+{
+	/**
+	 * @var string the input mask (e.g. '99/99/9999' for date input). The following characters are predefined:
+	 *
+	 * - `a`: represents an alpha character (A-Z,a-z)
+	 * - `9`: represents a numeric character (0-9)
+	 * - `*`: represents an alphanumeric character (A-Z,a-z,0-9)
+	 * - `?`: anything listed after '?' within the mask is considered optional user input
+	 *
+	 * Additional characters can be defined by specifying the [[charMap]] property.
+	 */
+	public $mask;
+	/**
+	 * @var array the mapping between mask characters and the corresponding patterns.
+	 * For example, `['~' => '[+-]']` specifies that the '~' character expects '+' or '-' input.
+	 * Defaults to null, meaning using the map as described in [[mask]].
+	 */
+	public $charMap;
+	/**
+	 * @var string the character prompting for user input. Defaults to underscore '_'.
+	 */
+	public $placeholder;
+	/**
+	 * @var string a JavaScript function callback that will be invoked when user finishes the input.
+	 */
+	public $completed;
+
+
+	/**
+	 * Initializes the widget.
+	 * @throws InvalidConfigException if the "mask" property is not set.
+	 */
+	public function init()
+	{
+		parent::init();
+		if (empty($this->mask)) {
+			throw new InvalidConfigException('The "mask" property must be set.');
+		}
+	}
+
+	/**
+	 * Runs the widget.
+	 */
+	public function run()
+	{
+		if ($this->hasModel()) {
+			echo Html::activeTextInput($this->model, $this->attribute, $this->options);
+		} else {
+			echo Html::textInput($this->name, $this->value, $this->options);
+		}
+		$this->registerClientScript();
+	}
+
+	/**
+	 * Registers the needed JavaScript.
+	 */
+	public function registerClientScript()
+	{
+		$options = $this->getClientOptions();
+		$options = empty($options) ? '' : ',' . Json::encode($options);
+		$js = '';
+		if (is_array($this->charMap) && !empty($this->charMap)) {
+			$js .= 'jQuery.mask.definitions=' . Json::encode($this->charMap) . ";\n";
+		}
+		$id = $this->options['id'];
+		$js .= "jQuery(\"#{$id}\").mask(\"{$this->mask}\"{$options});";
+		$view = $this->getView();
+		MaskedInputAsset::register($view);
+		$view->registerJs($js);
+	}
+
+	/**
+	 * @return array the options for the text field
+	 */
+	protected function getClientOptions()
+	{
+		$options = [];
+		if ($this->placeholder !== null) {
+			$options['placeholder'] = $this->placeholder;
+		}
+
+		if ($this->completed !== null) {
+			if ($this->completed instanceof JsExpression) {
+				$options['completed'] = $this->completed;
+			} else {
+				$options['completed'] = new JsExpression($this->completed);
+			}
+		}
+
+		return $options;
+	}
+}
diff --git a/framework/yii/widgets/MaskedInputAsset.php b/framework/widgets/MaskedInputAsset.php
similarity index 100%
rename from framework/yii/widgets/MaskedInputAsset.php
rename to framework/widgets/MaskedInputAsset.php
diff --git a/framework/widgets/Menu.php b/framework/widgets/Menu.php
new file mode 100644
index 0000000..d5ff8ef
--- /dev/null
+++ b/framework/widgets/Menu.php
@@ -0,0 +1,307 @@
+<?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\helpers\ArrayHelper;
+use yii\helpers\Html;
+
+/**
+ * Menu displays a multi-level menu using nested HTML lists.
+ *
+ * The main property of Menu is [[items]], which specifies the possible items in the menu.
+ * A menu item can contain sub-items which specify the sub-menu under that menu item.
+ *
+ * Menu checks the current route and request parameters to toggle certain menu items
+ * with active state.
+ *
+ * Note that Menu only renders the HTML tags about the menu. It does do any styling.
+ * You are responsible to provide CSS styles to make it look like a real menu.
+ *
+ * The following example shows how to use Menu:
+ *
+ * ~~~
+ * echo Menu::widget([
+ *     'items' => [
+ *         // Important: you need to specify url as 'controller/action',
+ *         // not just as 'controller' even if default action is used.
+ *         ['label' => 'Home', 'url' => ['site/index']],
+ *         // 'Products' menu item will be selected as long as the route is 'product/index'
+ *         ['label' => 'Products', 'url' => ['product/index'], 'items' => [
+ *             ['label' => 'New Arrivals', 'url' => ['product/index', 'tag' => 'new']],
+ *             ['label' => 'Most Popular', 'url' => ['product/index', 'tag' => 'popular']],
+ *         ]],
+ *         ['label' => 'Login', 'url' => ['site/login'], 'visible' => Yii::$app->user->isGuest],
+ *     ],
+ * ]);
+ * ~~~
+ *
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class Menu extends Widget
+{
+	/**
+	 * @var array list of menu items. Each menu item should be an array of the following structure:
+	 *
+	 * - label: string, optional, specifies the menu item label. When [[encodeLabels]] is true, the label
+	 *   will be HTML-encoded. If the label is not specified, an empty string will be used.
+	 * - url: string or array, optional, specifies the URL of the menu item. It will be processed by [[Html::url]].
+	 *   When this is set, the actual menu item content will be generated using [[linkTemplate]];
+	 *   otherwise, [[labelTemplate]] will be used.
+	 * - visible: boolean, optional, whether this menu item is visible. Defaults to true.
+	 * - items: array, optional, specifies the sub-menu items. Its format is the same as the parent items.
+	 * - active: boolean, optional, whether this menu item is in active state (currently selected).
+	 *   If a menu item is active, its CSS class will be appended with [[activeCssClass]].
+	 *   If this option is not set, the menu item will be set active automatically when the current request
+	 *   is triggered by [[url]]. For more details, please refer to [[isItemActive()]].
+	 * - template: string, optional, the template used to render the content of this menu item.
+	 *   The token `{url}` will be replaced by the URL associated with this menu item,
+	 *   and the token `{label}` will be replaced by the label of the menu item.
+	 *   If this option is not set, [[linkTemplate]] or [[labelTemplate]] will be used instead.
+	 * - options: array, optional, the HTML attributes for the menu container tag.
+	 */
+	public $items = [];
+	/**
+	 * @var array list of HTML attributes for the menu container tag. This will be overwritten
+	 * by the "options" set in individual [[items]]. The following special options are recognized:
+	 *
+	 * - tag: string, defaults to "li", the tag name of the item container tags.
+	 */
+	public $itemOptions = [];
+	/**
+	 * @var string the template used to render the body of a menu which is a link.
+	 * In this template, the token `{url}` will be replaced with the corresponding link URL;
+	 * while `{label}` will be replaced with the link text.
+	 * This property will be overridden by the `template` option set in individual menu items via [[items]].
+	 */
+	public $linkTemplate = '<a href="{url}">{label}</a>';
+	/**
+	 * @var string the template used to render the body of a menu which is NOT a link.
+	 * In this template, the token `{label}` will be replaced with the label of the menu item.
+	 * This property will be overridden by the `template` option set in individual menu items via [[items]].
+	 */
+	public $labelTemplate = '{label}';
+	/**
+	 * @var string the template used to render a list of sub-menus.
+	 * In this template, the token `{items}` will be replaced with the renderer sub-menu items.
+	 */
+	public $submenuTemplate = "\n<ul>\n{items}\n</ul>\n";
+	/**
+	 * @var boolean whether the labels for menu items should be HTML-encoded.
+	 */
+	public $encodeLabels = true;
+	/**
+	 * @var string the CSS class to be appended to the active menu item.
+	 */
+	public $activeCssClass = 'active';
+	/**
+	 * @var boolean whether to automatically activate items according to whether their route setting
+	 * matches the currently requested route.
+	 * @see isItemActive()
+	 */
+	public $activateItems = true;
+	/**
+	 * @var boolean whether to activate parent menu items when one of the corresponding child menu items is active.
+	 * The activated parent menu items will also have its CSS classes appended with [[activeCssClass]].
+	 */
+	public $activateParents = false;
+	/**
+	 * @var boolean whether to hide empty menu items. An empty menu item is one whose `url` option is not
+	 * set and which has no visible child menu items.
+	 */
+	public $hideEmptyItems = true;
+	/**
+	 * @var array the HTML attributes for the menu's container tag. The following special options are recognized:
+	 *
+	 * - tag: string, defaults to "ul", the tag name of the item container tags.
+	 */
+	public $options = [];
+	/**
+	 * @var string the CSS class that will be assigned to the first item in the main menu or each submenu.
+	 * Defaults to null, meaning no such CSS class will be assigned.
+	 */
+	public $firstItemCssClass;
+	/**
+	 * @var string the CSS class that will be assigned to the last item in the main menu or each submenu.
+	 * Defaults to null, meaning no such CSS class will be assigned.
+	 */
+	public $lastItemCssClass;
+	/**
+	 * @var string the route used to determine if a menu item is active or not.
+	 * If not set, it will use the route of the current request.
+	 * @see params
+	 * @see isItemActive()
+	 */
+	public $route;
+	/**
+	 * @var array the parameters used to determine if a menu item is active or not.
+	 * If not set, it will use `$_GET`.
+	 * @see route
+	 * @see isItemActive()
+	 */
+	public $params;
+
+
+	/**
+	 * Renders the menu.
+	 */
+	public function run()
+	{
+		if ($this->route === null && Yii::$app->controller !== null) {
+			$this->route = Yii::$app->controller->getRoute();
+		}
+		if ($this->params === null) {
+			$this->params = $_GET;
+		}
+		$items = $this->normalizeItems($this->items, $hasActiveChild);
+		$options = $this->options;
+		$tag = ArrayHelper::remove($options, 'tag', 'ul');
+		echo Html::tag($tag, $this->renderItems($items), $options);
+	}
+
+	/**
+	 * Recursively renders the menu items (without the container tag).
+	 * @param array $items the menu items to be rendered recursively
+	 * @return string the rendering result
+	 */
+	protected function renderItems($items)
+	{
+		$n = count($items);
+		$lines = [];
+		foreach ($items as $i => $item) {
+			$options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options', []));
+			$tag = ArrayHelper::remove($options, 'tag', 'li');
+			$class = [];
+			if ($item['active']) {
+				$class[] = $this->activeCssClass;
+			}
+			if ($i === 0 && $this->firstItemCssClass !== null) {
+				$class[] = $this->firstItemCssClass;
+			}
+			if ($i === $n - 1 && $this->lastItemCssClass !== null) {
+				$class[] = $this->lastItemCssClass;
+			}
+			if (!empty($class)) {
+				if (empty($options['class'])) {
+					$options['class'] = implode(' ', $class);
+				} else {
+					$options['class'] .= ' ' . implode(' ', $class);
+				}
+			}
+
+			$menu = $this->renderItem($item);
+			if (!empty($item['items'])) {
+				$menu .= strtr($this->submenuTemplate, [
+					'{items}' => $this->renderItems($item['items']),
+				]);
+			}
+			$lines[] = Html::tag($tag, $menu, $options);
+		}
+		return implode("\n", $lines);
+	}
+
+	/**
+	 * Renders the content of a menu item.
+	 * Note that the container and the sub-menus are not rendered here.
+	 * @param array $item the menu item to be rendered. Please refer to [[items]] to see what data might be in the item.
+	 * @return string the rendering result
+	 */
+	protected function renderItem($item)
+	{
+		if (isset($item['url'])) {
+			$template = ArrayHelper::getValue($item, 'template', $this->linkTemplate);
+			return strtr($template, [
+				'{url}' => Html::url($item['url']),
+				'{label}' => $item['label'],
+			]);
+		} else {
+			$template = ArrayHelper::getValue($item, 'template', $this->labelTemplate);
+			return strtr($template, [
+				'{label}' => $item['label'],
+			]);
+		}
+	}
+
+	/**
+	 * Normalizes the [[items]] property to remove invisible items and activate certain items.
+	 * @param array $items the items to be normalized.
+	 * @param boolean $active whether there is an active child menu item.
+	 * @return array the normalized menu items
+	 */
+	protected function normalizeItems($items, &$active)
+	{
+		foreach ($items as $i => $item) {
+			if (isset($item['visible']) && !$item['visible']) {
+				unset($items[$i]);
+				continue;
+			}
+			if (!isset($item['label'])) {
+				$item['label'] = '';
+			}
+			if ($this->encodeLabels) {
+				$items[$i]['label'] = Html::encode($item['label']);
+			}
+			$hasActiveChild = false;
+			if (isset($item['items'])) {
+				$items[$i]['items'] = $this->normalizeItems($item['items'], $hasActiveChild);
+				if (empty($items[$i]['items']) && $this->hideEmptyItems) {
+					unset($items[$i]['items']);
+					if (!isset($item['url'])) {
+						unset($items[$i]);
+						continue;
+					}
+				}
+			}
+			if (!isset($item['active'])) {
+				if ($this->activateParents && $hasActiveChild || $this->activateItems && $this->isItemActive($item)) {
+					$active = $items[$i]['active'] = true;
+				} else {
+					$items[$i]['active'] = false;
+				}
+			} elseif ($item['active']) {
+				$active = true;
+			}
+		}
+		return array_values($items);
+	}
+
+	/**
+	 * Checks whether a menu item is active.
+	 * This is done by checking if [[route]] and [[params]] match that specified in the `url` option of the menu item.
+	 * When the `url` option of a menu item is specified in terms of an array, its first element is treated
+	 * as the route for the item and the rest of the elements are the associated parameters.
+	 * Only when its route and parameters match [[route]] and [[params]], respectively, will a menu item
+	 * be considered active.
+	 * @param array $item the menu item to be checked
+	 * @return boolean whether the menu item is active
+	 */
+	protected function isItemActive($item)
+	{
+		if (isset($item['url']) && is_array($item['url']) && isset($item['url'][0])) {
+			$route = $item['url'][0];
+			if ($route[0] !== '/' && Yii::$app->controller) {
+				$route = Yii::$app->controller->module->getUniqueId() . '/' . $route;
+			}
+			if (ltrim($route, '/') !== $this->route) {
+				return false;
+			}
+			unset($item['url']['#']);
+			if (count($item['url']) > 1) {
+				foreach (array_splice($item['url'], 1) as $name => $value) {
+					if (!isset($this->params[$name]) || $this->params[$name] != $value) {
+						return false;
+					}
+				}
+			}
+			return true;
+		}
+		return false;
+	}
+}
diff --git a/framework/widgets/Spaceless.php b/framework/widgets/Spaceless.php
new file mode 100644
index 0000000..8115f85
--- /dev/null
+++ b/framework/widgets/Spaceless.php
@@ -0,0 +1,69 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\widgets;
+
+use yii\base\Widget;
+
+/**
+ * Spaceless widget removes whitespace characters between HTML tags. Whitespaces within HTML tags
+ * or in a plain text are always left untouched.
+ *
+ * Usage example:
+ *
+ * ```php
+ * <body>
+ *     <?php Spaceless::begin(); ?>
+ *         <div class="nav-bar">
+ *             <!-- tags -->
+ *         </div>
+ *         <div class="content">
+ *             <!-- tags -->
+ *         </div>
+ *     <?php Spaceless::end(); ?>
+ * </body>
+ * ```
+ *
+ * This example will generate the following HTML:
+ *
+ * ```html
+ * <body>
+ *     <div class="navbar"><!-- other tags --></div><div class="content"><!-- other tags --></div></body>
+ * ```
+ *
+ * This method is not designed for content compression (you should use `gzip` output compression to
+ * achieve it). Main intention is to strip out extra whitespace characters between HTML tags in order
+ * to avoid browser rendering quirks in some circumstances (e.g. newlines between inline-block elements).
+ *
+ * Note, never use this method with `pre` or `textarea` tags. It's not that trivial to deal with such tags
+ * as it may seem at first sight. For this case you should consider using
+ * [HTML Tidy Project](http://tidy.sourceforge.net/) instead.
+ *
+ * @see http://tidy.sourceforge.net/
+ * @author resurtm <resurtm@gmail.com>
+ * @since 2.0
+ */
+class Spaceless extends Widget
+{
+	/**
+	 * Starts capturing an output to be cleaned from whitespace characters between HTML tags.
+	 */
+	public function init()
+	{
+		ob_start();
+		ob_implicit_flush(false);
+	}
+
+	/**
+	 * Marks the end of content to be cleaned from whitespace characters between HTML tags.
+	 * Stops capturing an output and echoes cleaned result.
+	 */
+	public function run()
+	{
+		echo trim(preg_replace('/>\s+</', '><', ob_get_clean()));
+	}
+}
diff --git a/framework/yii/yii b/framework/yii
similarity index 100%
rename from framework/yii/yii
rename to framework/yii
diff --git a/framework/yii/yii.bat b/framework/yii.bat
similarity index 100%
rename from framework/yii/yii.bat
rename to framework/yii.bat
diff --git a/framework/yii/requirements/views/web/index.php b/framework/yii/requirements/views/web/index.php
deleted file mode 100644
index 287d4bb..0000000
--- a/framework/yii/requirements/views/web/index.php
+++ /dev/null
@@ -1,82 +0,0 @@
-<?php
-/* @var YiiRequirementChecker $this */
-/* @var array $summary */
-/* @var array[] $requirements */
-?>
-<!DOCTYPE html>
-<html lang="en">
-<head>
-	<meta charset="utf-8"/>
-	<title>Yii Application Requirement Checker</title>
-	<?php $this->renderViewFile(dirname(__FILE__) . '/css.php'); ?>
-</head>
-<body>
-<div class="container">
-	<div class="header">
-		<h1>Yii Application Requirement Checker</h1>
-	</div>
-	<hr>
-
-	<div class="content">
-		<h3>Description</h3>
-		<p>
-		This script checks if your server configuration meets the requirements
-		for running Yii application.
-		It checks if the server is running the right version of PHP,
-		if appropriate PHP extensions have been loaded, and if php.ini file settings are correct.
-		</p>
-		<p>
-		There are two kinds of requirements being checked. Mandatory requirements are those that have to be met
-		to allow Yii to work as expected. There are also some optional requirements beeing checked which will
-		show you a warning when they do not meet. You can use Yii framework without them but some specific
-		functionality may be not available in this case.
-		</p>
-
-		<h3>Conclusion</h3>
-		<?php if ($summary['errors'] > 0): ?>
-			<div class="alert alert-error">
-				<strong>Unfortunately your server configuration does not satisfy the requirements by this application.<br>Please refer to the table below for detailed explanation.</strong>
-			</div>
-		<?php elseif ($summary['warnings'] > 0): ?>
-			<div class="alert alert-info">
-				<strong>Your server configuration satisfies the minimum requirements by this application.<br>Please pay attention to the warnings listed below and check if your application will use the corresponding features.</strong>
-			</div>
-		<?php else: ?>
-			<div class="alert alert-success">
-				<strong>Congratulations! Your server configuration satisfies all requirements.</strong>
-			</div>
-		<?php endif; ?>
-
-		<h3>Details</h3>
-
-		<table class="table table-bordered">
-			<tr><th>Name</th><th>Result</th><th>Required By</th><th>Memo</th></tr>
-			<?php foreach($requirements as $requirement): ?>
-			<tr class="<?php echo $requirement['condition'] ? 'success' : ($requirement['mandatory'] ? 'error' : 'warning') ?>">
-				<td>
-				<?php echo $requirement['name'] ?>
-				</td>
-				<td>
-				<span class="result"><?php echo $requirement['condition'] ? 'Passed' : ($requirement['mandatory'] ? 'Failed' : 'Warning') ?></span>
-				</td>
-				<td>
-				<?php echo $requirement['by'] ?>
-				</td>
-				<td>
-				<?php echo $requirement['memo'] ?>
-				</td>
-			</tr>
-			<?php endforeach; ?>
-		</table>
-
-	</div>
-
-	<hr>
-
-	<div class="footer">
-		<p>Server: <?php echo $this->getServerInfo() . ' ' . $this->getNowDate() ?></p>
-		<p>Powered by <a href="http://www.yiiframework.com/" rel="external">Yii Framework</a></p>
-	</div>
-</div>
-</body>
-</html>
diff --git a/framework/yii/validators/InlineValidator.php b/framework/yii/validators/InlineValidator.php
deleted file mode 100644
index b769e4e..0000000
--- a/framework/yii/validators/InlineValidator.php
+++ /dev/null
@@ -1,84 +0,0 @@
-<?php
-/**
- * @link http://www.yiiframework.com/
- * @copyright Copyright (c) 2008 Yii Software LLC
- * @license http://www.yiiframework.com/license/
- */
-
-namespace yii\validators;
-
-/**
- * InlineValidator represents a validator which is defined as a method in the object being validated.
- *
- * The validation method must have the following signature:
- *
- * ~~~
- * function foo($attribute, $params)
- * ~~~
- *
- * where `$attribute` refers to the name of the attribute being validated, while `$params`
- * is an array representing the additional parameters supplied in the validation rule.
- *
- * @author Qiang Xue <qiang.xue@gmail.com>
- * @since 2.0
- */
-class InlineValidator extends Validator
-{
-	/**
-	 * @var string|\Closure an anonymous function or the name of a model class method that will be
-	 * called to perform the actual validation. The signature of the method should be like the following:
-	 *
-	 * ~~~
-	 * function foo($attribute, $params)
-	 * ~~~
-	 */
-	public $method;
-	/**
-	 * @var array additional parameters that are passed to the validation method
-	 */
-	public $params;
-	/**
-	 * @var string|\Closure an anonymous function or the name of a model class method that returns the client validation code.
-	 * The signature of the method should be like the following:
-	 *
-	 * ~~~
-	 * function foo($attribute, $params)
-	 * {
-	 *     return "javascript";
-	 * }
-	 * ~~~
-	 *
-	 * where `$attribute` refers to the attribute name to be validated.
-	 *
-	 * Please refer to [[clientValidateAttribute()]] for details on how to return client validation code.
-	 */
-	public $clientValidate;
-
-	/**
-	 * @inheritdoc
-	 */
-	public function validateAttribute($object, $attribute)
-	{
-		$method = $this->method;
-		if (is_string($method)) {
-			$method = [$object, $method];
-		}
-		call_user_func($method, $attribute, $this->params);
-	}
-
-	/**
-	 * @inheritdoc
-	 */
-	public function clientValidateAttribute($object, $attribute, $view)
-	{
-		if ($this->clientValidate !== null) {
-			$method = $this->clientValidate;
-			if (is_string($method)) {
-				$method = [$object, $method];
-			}
-			return call_user_func($method, $attribute, $this->params);
-		} else {
-			return null;
-		}
-	}
-}
diff --git a/framework/yii/validators/RangeValidator.php b/framework/yii/validators/RangeValidator.php
deleted file mode 100644
index a4da139..0000000
--- a/framework/yii/validators/RangeValidator.php
+++ /dev/null
@@ -1,87 +0,0 @@
-<?php
-/**
- * @link http://www.yiiframework.com/
- * @copyright Copyright (c) 2008 Yii Software LLC
- * @license http://www.yiiframework.com/license/
- */
-
-namespace yii\validators;
-
-use Yii;
-use yii\base\InvalidConfigException;
-use yii\helpers\Html;
-
-/**
- * RangeValidator validates that the attribute value is among a list of values.
- *
- * The range can be specified via the [[range]] property.
- * If the [[not]] property is set true, the validator will ensure the attribute value
- * is NOT among the specified range.
- *
- * @author Qiang Xue <qiang.xue@gmail.com>
- * @since 2.0
- */
-class RangeValidator extends Validator
-{
-	/**
-	 * @var array list of valid values that the attribute value should be among
-	 */
-	public $range;
-	/**
-	 * @var boolean whether the comparison is strict (both type and value must be the same)
-	 */
-	public $strict = false;
-	/**
-	 * @var boolean whether to invert the validation logic. Defaults to false. If set to true,
-	 * the attribute value should NOT be among the list of values defined via [[range]].
-	 **/
-	public $not = false;
-
-	/**
-	 * @inheritdoc
-	 */
-	public function init()
-	{
-		parent::init();
-		if (!is_array($this->range)) {
-			throw new InvalidConfigException('The "range" property must be set.');
-		}
-		if ($this->message === null) {
-			$this->message = Yii::t('yii', '{attribute} is invalid.');
-		}
-	}
-
-	/**
-	 * @inheritdoc
-	 */
-	protected function validateValue($value)
-	{
-		$valid = !$this->not && in_array($value, $this->range, $this->strict)
-			|| $this->not && !in_array($value, $this->range, $this->strict);
-		return $valid ? null : [$this->message, []];
-	}
-
-	/**
-	 * @inheritdoc
-	 */
-	public function clientValidateAttribute($object, $attribute, $view)
-	{
-		$range = [];
-		foreach ($this->range as $value) {
-			$range[] = (string)$value;
-		}
-		$options = [
-			'range' => $range,
-			'not' => $this->not,
-			'message' => strtr($this->message, [
-				'{attribute}' => $object->getAttributeLabel($attribute),
-			]),
-		];
-		if ($this->skipOnEmpty) {
-			$options['skipOnEmpty'] = 1;
-		}
-
-		ValidationAsset::register($view);
-		return 'yii.validation.range(value, messages, ' . json_encode($options) . ');';
-	}
-}
diff --git a/framework/yii/validators/RegularExpressionValidator.php b/framework/yii/validators/RegularExpressionValidator.php
deleted file mode 100644
index 28e9bdc..0000000
--- a/framework/yii/validators/RegularExpressionValidator.php
+++ /dev/null
@@ -1,94 +0,0 @@
-<?php
-/**
- * @link http://www.yiiframework.com/
- * @copyright Copyright (c) 2008 Yii Software LLC
- * @license http://www.yiiframework.com/license/
- */
-
-namespace yii\validators;
-
-use Yii;
-use yii\base\InvalidConfigException;
-use yii\helpers\Html;
-use yii\web\JsExpression;
-use yii\helpers\Json;
-
-/**
- * RegularExpressionValidator validates that the attribute value matches the specified [[pattern]].
- *
- * If the [[not]] property is set true, the validator will ensure the attribute value do NOT match the [[pattern]].
- *
- * @author Qiang Xue <qiang.xue@gmail.com>
- * @since 2.0
- */
-class RegularExpressionValidator extends Validator
-{
-	/**
-	 * @var string the regular expression to be matched with
-	 */
-	public $pattern;
-	/**
-	 * @var boolean whether to invert the validation logic. Defaults to false. If set to true,
-	 * the regular expression defined via [[pattern]] should NOT match the attribute value.
-	 **/
-	public $not = false;
-
-	/**
-	 * @inheritdoc
-	 */
-	public function init()
-	{
-		parent::init();
-		if ($this->pattern === null) {
-			throw new InvalidConfigException('The "pattern" property must be set.');
-		}
-		if ($this->message === null) {
-			$this->message = Yii::t('yii', '{attribute} is invalid.');
-		}
-	}
-
-	/**
-	 * @inheritdoc
-	 */
-	protected function validateValue($value)
-	{
-		$valid = !is_array($value) &&
-			(!$this->not && preg_match($this->pattern, $value)
-			|| $this->not && !preg_match($this->pattern, $value));
-		return $valid ? null : [$this->message, []];
-	}
-
-	/**
-	 * @inheritdoc
-	 */
-	public function clientValidateAttribute($object, $attribute, $view)
-	{
-		$pattern = $this->pattern;
-		$pattern = preg_replace('/\\\\x\{?([0-9a-fA-F]+)\}?/', '\u$1', $pattern);
-		$deliminator = substr($pattern, 0, 1);
-		$pos = strrpos($pattern, $deliminator, 1);
-		$flag = substr($pattern, $pos + 1);
-		if ($deliminator !== '/') {
-			$pattern = '/' . str_replace('/', '\\/', substr($pattern, 1, $pos - 1)) . '/';
-		} else {
-			$pattern = substr($pattern, 0, $pos + 1);
-		}
-		if (!empty($flag)) {
-			$pattern .= preg_replace('/[^igm]/', '', $flag);
-		}
-
-		$options = [
-			'pattern' => new JsExpression($pattern),
-			'not' => $this->not,
-			'message' => strtr($this->message, [
-				'{attribute}' => $object->getAttributeLabel($attribute),
-			]),
-		];
-		if ($this->skipOnEmpty) {
-			$options['skipOnEmpty'] = 1;
-		}
-
-		ValidationAsset::register($view);
-		return 'yii.validation.regularExpression(value, messages, ' . Json::encode($options) . ');';
-	}
-}
diff --git a/framework/yii/validators/RequiredValidator.php b/framework/yii/validators/RequiredValidator.php
deleted file mode 100644
index f291f39..0000000
--- a/framework/yii/validators/RequiredValidator.php
+++ /dev/null
@@ -1,111 +0,0 @@
-<?php
-/**
- * @link http://www.yiiframework.com/
- * @copyright Copyright (c) 2008 Yii Software LLC
- * @license http://www.yiiframework.com/license/
- */
-
-namespace yii\validators;
-
-use Yii;
-use yii\helpers\Html;
-
-/**
- * RequiredValidator validates that the specified attribute does not have null or empty value.
- *
- * @author Qiang Xue <qiang.xue@gmail.com>
- * @since 2.0
- */
-class RequiredValidator extends Validator
-{
-	/**
-	 * @var boolean whether to skip this validator if the value being validated is empty.
-	 */
-	public $skipOnEmpty = false;
-	/**
-	 * @var mixed the desired value that the attribute must have.
-	 * If this is null, the validator will validate that the specified attribute is not empty.
-	 * If this is set as a value that is not null, the validator will validate that
-	 * the attribute has a value that is the same as this property value.
-	 * Defaults to null.
-	 * @see strict
-	 */
-	public $requiredValue;
-	/**
-	 * @var boolean whether the comparison between the attribute value and [[requiredValue]] is strict.
-	 * When this is true, both the values and types must match.
-	 * Defaults to false, meaning only the values need to match.
-	 * Note that when [[requiredValue]] is null, if this property is true, the validator will check
-	 * if the attribute value is null; If this property is false, the validator will call [[isEmpty]]
-	 * to check if the attribute value is empty.
-	 */
-	public $strict = false;
-	/**
-	 * @var string the user-defined error message. It may contain the following placeholders which
-	 * will be replaced accordingly by the validator:
-	 *
-	 * - `{attribute}`: the label of the attribute being validated
-	 * - `{value}`: the value of the attribute being validated
-	 * - `{requiredValue}`: the value of [[requiredValue]]
-	 */
-	public $message;
-
-	/**
-	 * @inheritdoc
-	 */
-	public function init()
-	{
-		parent::init();
-		if ($this->message === null) {
-			$this->message = $this->requiredValue === null ? Yii::t('yii', '{attribute} cannot be blank.')
-				: Yii::t('yii', '{attribute} must be "{requiredValue}".');
-		}
-	}
-
-	/**
-	 * @inheritdoc
-	 */
-	protected function validateValue($value)
-	{
-		if ($this->requiredValue === null) {
-			if ($this->strict && $value !== null || !$this->strict && !$this->isEmpty($value, true)) {
-				return null;
-			}
-		} elseif (!$this->strict && $value == $this->requiredValue || $this->strict && $value === $this->requiredValue) {
-			return null;
-		}
-		if ($this->requiredValue === null) {
-			return [$this->message, []];
-		} else {
-			return [$this->message, [
-				'requiredValue' => $this->requiredValue,
-			]];
-		}
-	}
-
-	/**
-	 * @inheritdoc
-	 */
-	public function clientValidateAttribute($object, $attribute, $view)
-	{
-		$options = [];
-		if ($this->requiredValue !== null) {
-			$options['message'] = strtr($this->message, [
-				'{requiredValue}' => $this->requiredValue,
-			]);
-			$options['requiredValue'] = $this->requiredValue;
-		} else {
-			$options['message'] = $this->message;
-		}
-		if ($this->strict) {
-			$options['strict'] = 1;
-		}
-
-		$options['message'] = strtr($options['message'], [
-			'{attribute}' => $object->getAttributeLabel($attribute),
-		]);
-
-		ValidationAsset::register($view);
-		return 'yii.validation.required(value, messages, ' . json_encode($options) . ');';
-	}
-}
diff --git a/framework/yii/validators/UniqueValidator.php b/framework/yii/validators/UniqueValidator.php
deleted file mode 100644
index 51474b0..0000000
--- a/framework/yii/validators/UniqueValidator.php
+++ /dev/null
@@ -1,123 +0,0 @@
-<?php
-/**
- * @link http://www.yiiframework.com/
- * @copyright Copyright (c) 2008 Yii Software LLC
- * @license http://www.yiiframework.com/license/
- */
-
-namespace yii\validators;
-
-use Yii;
-use yii\db\ActiveRecordInterface;
-
-/**
- * UniqueValidator validates that the attribute value is unique in the specified database table.
- *
- * UniqueValidator checks if the value being validated is unique in the table column specified by
- * the ActiveRecord class [[targetClass]] and the attribute [[targetAttribute]].
- *
- * The followings are examples of validation rules using this validator:
- *
- * ```php
- * // a1 needs to be unique
- * ['a1', 'unique']
- * // a1 needs to be unique, but column a2 will be used to check the uniqueness of the a1 value
- * ['a1', 'unique', 'targetAttribute' => 'a2']
- * // a1 and a2 need to unique together, and they both will receive error message
- * [['a1', 'a2'], 'unique', 'targetAttribute' => ['a1', 'a2']]
- * // a1 and a2 need to unique together, only a1 will receive error message
- * ['a1', 'unique', 'targetAttribute' => ['a1', 'a2']]
- * // a1 needs to be unique by checking the uniqueness of both a2 and a3 (using a1 value)
- * ['a1', 'unique', 'targetAttribute' => ['a2', 'a1' => 'a3']]
- * ```
- *
- * @author Qiang Xue <qiang.xue@gmail.com>
- * @since 2.0
- */
-class UniqueValidator extends Validator
-{
-	/**
-	 * @var string the name of the ActiveRecord class that should be used to validate the uniqueness
-	 * of the current attribute value. It not set, it will use the ActiveRecord class of the attribute being validated.
-	 * @see targetAttribute
-	 */
-	public $targetClass;
-	/**
-	 * @var string|array the name of the ActiveRecord attribute that should be used to
-	 * validate the uniqueness of the current attribute value. If not set, it will use the name
-	 * of the attribute currently being validated. You may use an array to validate the uniqueness
-	 * of multiple columns at the same time. The array values are the attributes that will be
-	 * used to validate the uniqueness, while the array keys are the attributes whose values are to be validated.
-	 * If the key and the value are the same, you can just specify the value.
-	 */
-	public $targetAttribute;
-
-	/**
-	 * @inheritdoc
-	 */
-	public function init()
-	{
-		parent::init();
-		if ($this->message === null) {
-			$this->message = Yii::t('yii', '{attribute} "{value}" has already been taken.');
-		}
-	}
-
-	/**
-	 * @inheritdoc
-	 */
-	public function validateAttribute($object, $attribute)
-	{
-		/** @var ActiveRecordInterface $targetClass */
-		$targetClass = $this->targetClass === null ? get_class($object) : $this->targetClass;
-		$targetAttribute = $this->targetAttribute === null ? $attribute : $this->targetAttribute;
-
-		if (is_array($targetAttribute)) {
-			$params = [];
-			foreach ($targetAttribute as $k => $v) {
-				$params[$v] = is_integer($k) ? $object->$v : $object->$k;
-			}
-		} else {
-			$params = [$targetAttribute => $object->$attribute];
-		}
-
-		foreach ($params as $value) {
-			if (is_array($value)) {
-				$this->addError($object, $attribute, Yii::t('yii', '{attribute} is invalid.'));
-				return;
-			}
-		}
-
-		$query = $targetClass::find();
-		$query->where($params);
-
-		if (!$object instanceof ActiveRecordInterface || $object->getIsNewRecord()) {
-			// if current $object isn't in the database yet then it's OK just to call exists()
-			$exists = $query->exists();
-		} else {
-			// if current $object is in the database already we can't use exists()
-			/** @var ActiveRecordInterface[] $objects */
-			$objects = $query->limit(2)->all();
-			$n = count($objects);
-			if ($n === 1) {
-				$keys = array_keys($params);
-				$pks = $targetClass::primaryKey();
-				sort($keys);
-				sort($pks);
-				if ($keys === $pks) {
-					// primary key is modified and not unique
-					$exists = $object->getOldPrimaryKey() != $object->getPrimaryKey();
-				} else {
-					// non-primary key, need to exclude the current record based on PK
-					$exists = $objects[0]->getPrimaryKey() != $object->getOldPrimaryKey();
-				}
-			} else {
-				$exists = $n > 1;
-			}
-		}
-
-		if ($exists) {
-			$this->addError($object, $attribute, $this->message);
-		}
-	}
-}
diff --git a/framework/yii/validators/UrlValidator.php b/framework/yii/validators/UrlValidator.php
deleted file mode 100644
index 4cb20f6..0000000
--- a/framework/yii/validators/UrlValidator.php
+++ /dev/null
@@ -1,142 +0,0 @@
-<?php
-/**
- * @link http://www.yiiframework.com/
- * @copyright Copyright (c) 2008 Yii Software LLC
- * @license http://www.yiiframework.com/license/
- */
-
-namespace yii\validators;
-
-use Yii;
-use yii\base\InvalidConfigException;
-use yii\helpers\Html;
-use yii\web\JsExpression;
-use yii\helpers\Json;
-
-/**
- * UrlValidator validates that the attribute value is a valid http or https URL.
- *
- * Note that this validator only checks if the URL scheme and host part are correct.
- * It does not check the rest part of a URL.
- *
- * @author Qiang Xue <qiang.xue@gmail.com>
- * @since 2.0
- */
-class UrlValidator extends Validator
-{
-	/**
-	 * @var string the regular expression used to validate the attribute value.
-	 * The pattern may contain a `{schemes}` token that will be replaced
-	 * by a regular expression which represents the [[validSchemes]].
-	 */
-	public $pattern = '/^{schemes}:\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)/i';
-	/**
-	 * @var array list of URI schemes which should be considered valid. By default, http and https
-	 * are considered to be valid schemes.
-	 **/
-	public $validSchemes = ['http', 'https'];
-	/**
-	 * @var string the default URI scheme. If the input doesn't contain the scheme part, the default
-	 * scheme will be prepended to it (thus changing the input). Defaults to null, meaning a URL must
-	 * contain the scheme part.
-	 **/
-	public $defaultScheme;
-	/**
-	 * @var boolean whether validation process should take into account IDN (internationalized
-	 * domain names). Defaults to false meaning that validation of URLs containing IDN will always
-	 * fail. Note that in order to use IDN validation you have to install and enable `intl` PHP
-	 * extension, otherwise an exception would be thrown.
-	 */
-	public $enableIDN = false;
-
-
-	/**
-	 * @inheritdoc
-	 */
-	public function init()
-	{
-		parent::init();
-		if ($this->enableIDN && !function_exists('idn_to_ascii')) {
-			throw new InvalidConfigException('In order to use IDN validation intl extension must be installed and enabled.');
-		}
-		if ($this->message === null) {
-			$this->message = Yii::t('yii', '{attribute} is not a valid URL.');
-		}
-	}
-
-	/**
-	 * @inheritdoc
-	 */
-	public function validateAttribute($object, $attribute)
-	{
-		$value = $object->$attribute;
-		$result = $this->validateValue($value);
-		if (!empty($result)) {
-			$this->addError($object, $attribute, $result[0], $result[1]);
-		} elseif ($this->defaultScheme !== null && strpos($value, '://') === false) {
-			$object->$attribute = $this->defaultScheme . '://' . $value;
-		}
-	}
-
-	/**
-	 * @inheritdoc
-	 */
-	protected function validateValue($value)
-	{
-		// make sure the length is limited to avoid DOS attacks
-		if (is_string($value) && strlen($value) < 2000) {
-			if ($this->defaultScheme !== null && strpos($value, '://') === false) {
-				$value = $this->defaultScheme . '://' . $value;
-			}
-
-			if (strpos($this->pattern, '{schemes}') !== false) {
-				$pattern = str_replace('{schemes}', '(' . implode('|', $this->validSchemes) . ')', $this->pattern);
-			} else {
-				$pattern = $this->pattern;
-			}
-
-			if ($this->enableIDN) {
-				$value = preg_replace_callback('/:\/\/([^\/]+)/', function ($matches) {
-					return '://' . idn_to_ascii($matches[1]);
-				}, $value);
-			}
-
-			if (preg_match($pattern, $value)) {
-				return null;
-			}
-		}
-		return [$this->message, []];
-	}
-
-	/**
-	 * @inheritdoc
-	 */
-	public function clientValidateAttribute($object, $attribute, $view)
-	{
-		if (strpos($this->pattern, '{schemes}') !== false) {
-			$pattern = str_replace('{schemes}', '(' . implode('|', $this->validSchemes) . ')', $this->pattern);
-		} else {
-			$pattern = $this->pattern;
-		}
-
-		$options = [
-			'pattern' => new JsExpression($pattern),
-			'message' => strtr($this->message, [
-				'{attribute}' => $object->getAttributeLabel($attribute),
-			]),
-			'enableIDN' => (boolean)$this->enableIDN,
-		];
-		if ($this->skipOnEmpty) {
-			$options['skipOnEmpty'] = 1;
-		}
-		if ($this->defaultScheme !== null) {
-			$options['defaultScheme'] = $this->defaultScheme;
-		}
-
-		ValidationAsset::register($view);
-		if ($this->enableIDN) {
-			PunycodeAsset::register($view);
-		}
-		return 'yii.validation.url(value, messages, ' . Json::encode($options) . ');';
-	}
-}
diff --git a/framework/yii/views/errorHandler/error.php b/framework/yii/views/errorHandler/error.php
deleted file mode 100644
index 066d7e4..0000000
--- a/framework/yii/views/errorHandler/error.php
+++ /dev/null
@@ -1,86 +0,0 @@
-<?php
-/**
- * @var \Exception $exception
- * @var \yii\base\ErrorHandler $handler
- */
-if ($exception instanceof \yii\web\HttpException) {
-	$code = $exception->statusCode;
-} else {
-	$code = $exception->getCode();
-}
-if ($exception instanceof \yii\base\Exception) {
-	$name = $exception->getName();
-} else {
-	$name = 'Error';
-}
-if ($code) {
-	$name .= " (#$code)";
-}
-
-if ($exception instanceof \yii\base\UserException) {
-	$message = $exception->getMessage();
-} else {
-	$message = 'An internal server error occurred.';
-}
-?>
-<?php if (method_exists($this, 'beginPage')) $this->beginPage(); ?>
-<!DOCTYPE html>
-<html>
-<head>
-	<meta charset="utf-8" />
-	<title><?= $handler->htmlEncode($name) ?></title>
-
-	<style>
-		body {
-			font: normal 9pt "Verdana";
-			color: #000;
-			background: #fff;
-		}
-
-		h1 {
-			font: normal 18pt "Verdana";
-			color: #f00;
-			margin-bottom: .5em;
-		}
-
-		h2 {
-			font: normal 14pt "Verdana";
-			color: #800000;
-			margin-bottom: .5em;
-		}
-
-		h3 {
-			font: bold 11pt "Verdana";
-		}
-
-		p {
-			font: normal 9pt "Verdana";
-			color: #000;
-		}
-
-		.version {
-			color: gray;
-			font-size: 8pt;
-			border-top: 1px solid #aaa;
-			padding-top: 1em;
-			margin-bottom: 1em;
-		}
-	</style>
-</head>
-
-<body>
-	<h1><?= $handler->htmlEncode($name) ?></h1>
-	<h2><?= nl2br($handler->htmlEncode($message)) ?></h2>
-	<p>
-		The above error occurred while the Web server was processing your request.
-	</p>
-	<p>
-		Please contact us if you think this is a server error. Thank you.
-	</p>
-	<div class="version">
-		<?= date('Y-m-d H:i:s', time()) ?>
-	</div>
-	<?php if (method_exists($this, 'endBody')) $this->endBody(); // to allow injecting code into body (mostly by Yii Debug Toolbar) ?>
-</body>
-</html>
-<?php if (method_exists($this, 'endPage')) $this->endPage(); ?>
diff --git a/framework/yii/views/messageConfig.php b/framework/yii/views/messageConfig.php
deleted file mode 100644
index 9babb0c..0000000
--- a/framework/yii/views/messageConfig.php
+++ /dev/null
@@ -1,45 +0,0 @@
-<?php
-
-return [
-	// string, required, root directory of all source files
-	'sourcePath' => __DIR__,
-	// string, required, root directory containing message translations.
-	'messagePath' => __DIR__ . DIRECTORY_SEPARATOR . 'messages',
-	// array, required, list of language codes that the extracted messages
-	// should be translated to. For example, ['zh_cn', 'de'].
-	'languages' => ['de'],
-	// string, the name of the function for translating messages.
-	// Defaults to 'Yii::t'. This is used as a mark to find the messages to be
-	// translated. You may use a string for single function name or an array for
-	// multiple function names.
-	'translator' => 'Yii::t',
-	// boolean, whether to sort messages by keys when merging new messages
-	// with the existing ones. Defaults to false, which means the new (untranslated)
-	// messages will be separated from the old (translated) ones.
-	'sort' => false,
-	// boolean, whether the message file should be overwritten with the merged messages
-	'overwrite' => true,
-	// boolean, whether to remove messages that no longer appear in the source code.
-	// Defaults to false, which means each of these messages will be enclosed with a pair of '@@' marks.
-	'removeUnused' => false,
-	// array, list of patterns that specify which files/directories should be processed.
-	// If empty or not set, all files/directories will be processed.
-	// A path matches a pattern if it contains the pattern string at its end. For example,
-	// '/a/b' will match all files and directories ending with '/a/b';
-	// and the '.svn' will match all files and directories whose name ends with '.svn'.
-	// Note, the '/' characters in a pattern matches both '/' and '\'.
-	// If a file/directory matches both a pattern in "only" and "except", it will NOT be processed.
-	'only' => ['.php'],
-	// array, list of patterns that specify which files/directories should NOT be processed.
-	// If empty or not set, all files/directories will be processed.
-	// Please refer to "only" for details about the patterns.
-	'except' => [
-		'.svn',
-		'.git',
-		'.gitignore',
-		'.gitkeep',
-		'.hgignore',
-		'.hgkeep',
-		'/messages',
-	],
-];
diff --git a/framework/yii/web/AccessControl.php b/framework/yii/web/AccessControl.php
deleted file mode 100644
index e755e80..0000000
--- a/framework/yii/web/AccessControl.php
+++ /dev/null
@@ -1,144 +0,0 @@
-<?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;
-
-/**
- * AccessControl provides simple access control based on a set of rules.
- *
- * AccessControl is an action filter. It will check its [[rules]] to find
- * the first rule that matches the current context variables (such as user IP address, user role).
- * The matching rule will dictate whether to allow or deny the access to the requested controller
- * action. If no rule matches, the access will be denied.
- *
- * To use AccessControl, declare it in the `behaviors()` method of your controller class.
- * For example, the following declarations will allow authenticated users to access the "create"
- * and "update" actions and deny all other users from accessing these two actions.
- *
- * ~~~
- * public function behaviors()
- * {
- *     return [
- *         'access' => [
- *             'class' => \yii\web\AccessControl::className(),
- *             'only' => ['create', 'update'],
- *             'rules' => [
- *                 // deny all POST requests
- *                 [
- *                     'allow' => false,
- *                     'verbs' => ['POST']
- *                 ],
- *                 // allow authenticated users
- *                 [
- *                     'allow' => true,
- *                     'roles' => ['@'],
- *                 ],
- *                 // everything else is denied
- *             ],
- *         ],
- *     ];
- * }
- * ~~~
- *
- * @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 array the default configuration of access rules. Individual rule configurations
-	 * specified via [[rules]] will take precedence when the same property of the rule is configured.
-	 */
-	public $ruleConfig = ['class' => 'yii\web\AccessRule'];
-	/**
-	 * @var array a list of access rule objects or configuration arrays for creating the rule objects.
-	 * If a rule is specified via a configuration array, it will be merged with [[ruleConfig]] first
-	 * before it is used for creating the rule object.
-	 * @see ruleConfig
-	 */
-	public $rules = [];
-
-	/**
-	 * 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)) {
-				$this->rules[$i] = Yii::createObject(array_merge($this->ruleConfig, $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 AccessRule $rule */
-		foreach ($this->rules as $rule) {
-			if ($allow = $rule->allows($action, $user, $request)) {
-				return true;
-			} 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;
-			}
-		}
-		if (isset($this->denyCallback)) {
-			call_user_func($this->denyCallback, $rule);
-		}
-		else {
-			$this->denyAccess($user);
-		}
-		return false;
-	}
-
-	/**
-	 * 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 AccessDeniedHttpException if the user is already logged in.
-	 */
-	protected function denyAccess($user)
-	{
-		if ($user->getIsGuest()) {
-			$user->loginRequired();
-		} else {
-			throw new AccessDeniedHttpException(Yii::t('yii', 'You are not allowed to perform this action.'));
-		}
-	}
-}
diff --git a/framework/yii/web/AccessRule.php b/framework/yii/web/AccessRule.php
deleted file mode 100644
index 7aeaac1..0000000
--- a/framework/yii/web/AccessRule.php
+++ /dev/null
@@ -1,186 +0,0 @@
-<?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;
-
-/**
- * This class represents an access rule defined by the [[AccessControl]] action filter
- *
- * @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::checkAccess()]] 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->getMethod())
-			&& $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->uniqueId, $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->checkAccess($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);
-	}
-}
diff --git a/framework/yii/web/Application.php b/framework/yii/web/Application.php
deleted file mode 100644
index 17c1411..0000000
--- a/framework/yii/web/Application.php
+++ /dev/null
@@ -1,178 +0,0 @@
-<?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\InvalidRouteException;
-
-/**
- * Application is the base class for all web application classes.
- *
- * @property AssetManager $assetManager The asset manager component. This property is read-only.
- * @property string $homeUrl The homepage URL.
- * @property Request $request The request component. This property is read-only.
- * @property Response $response The response component. This property is read-only.
- * @property Session $session The session component. This property is read-only.
- * @property User $user The user component. This property is read-only.
- *
- * @author Qiang Xue <qiang.xue@gmail.com>
- * @since 2.0
- */
-class Application extends \yii\base\Application
-{
-	/**
-	 * @var string the default route of this application. Defaults to 'site'.
-	 */
-	public $defaultRoute = 'site';
-	/**
-	 * @var array the configuration specifying a controller action which should handle
-	 * all user requests. This is mainly used when the application is in maintenance mode
-	 * and needs to handle all incoming requests via a single action.
-	 * The configuration is an array whose first element specifies the route of the action.
-	 * The rest of the array elements (key-value pairs) specify the parameters to be bound
-	 * to the action. For example,
-	 *
-	 * ~~~
-	 * [
-	 *     'offline/notice',
-	 *     'param1' => 'value1',
-	 *     'param2' => 'value2',
-	 * ]
-	 * ~~~
-	 *
-	 * Defaults to null, meaning catch-all is not used.
-	 */
-	public $catchAll;
-	/**
-	 * @var Controller the currently active controller instance
-	 */
-	public $controller;
-
-
-	/**
-	 * Handles the specified request.
-	 * @param Request $request the request to be handled
-	 * @return Response the resulting response
-	 * @throws NotFoundHttpException if the requested route is invalid
-	 */
-	public function handleRequest($request)
-	{
-		Yii::setAlias('@webroot', dirname($request->getScriptFile()));
-		Yii::setAlias('@web', $request->getBaseUrl());
-
-		if (empty($this->catchAll)) {
-			list ($route, $params) = $request->resolve();
-		} else {
-			$route = $this->catchAll[0];
-			$params = array_splice($this->catchAll, 1);
-		}
-		try {
-			Yii::trace("Route requested: '$route'", __METHOD__);
-			$this->requestedRoute = $route;
-			$result = $this->runAction($route, $params);
-			if ($result instanceof Response) {
-				return $result;
-			} else {
-				$response = $this->getResponse();
-				if ($result !== null) {
-					$response->data = $result;
-				}
-				return $response;
-			}
-		} catch (InvalidRouteException $e) {
-			throw new NotFoundHttpException($e->getMessage(), $e->getCode(), $e);
-		}
-	}
-
-	private $_homeUrl;
-
-	/**
-	 * @return string the homepage URL
-	 */
-	public function getHomeUrl()
-	{
-		if ($this->_homeUrl === null) {
-			if ($this->getUrlManager()->showScriptName) {
-				return $this->getRequest()->getScriptUrl();
-			} else {
-				return $this->getRequest()->getBaseUrl() . '/';
-			}
-		} else {
-			return $this->_homeUrl;
-		}
-	}
-
-	/**
-	 * @param string $value the homepage URL
-	 */
-	public function setHomeUrl($value)
-	{
-		$this->_homeUrl = $value;
-	}
-
-	/**
-	 * 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');
-	}
-
-	/**
-	 * Returns the user component.
-	 * @return User the user component
-	 */
-	public function getUser()
-	{
-		return $this->getComponent('user');
-	}
-
-	/**
-	 * Returns the asset manager.
-	 * @return AssetManager the asset manager component
-	 */
-	public function getAssetManager()
-	{
-		return $this->getComponent('assetManager');
-	}
-
-	/**
-	 * Registers the core application components.
-	 * @see setComponents
-	 */
-	public function registerCoreComponents()
-	{
-		parent::registerCoreComponents();
-		$this->setComponents([
-			'request' => ['class' => 'yii\web\Request'],
-			'response' => ['class' => 'yii\web\Response'],
-			'session' => ['class' => 'yii\web\Session'],
-			'user' => ['class' => 'yii\web\User'],
-			'assetManager' => ['class' => 'yii\web\AssetManager'],
-		]);
-	}
-}
diff --git a/framework/yii/web/AssetBundle.php b/framework/yii/web/AssetBundle.php
deleted file mode 100644
index da34848..0000000
--- a/framework/yii/web/AssetBundle.php
+++ /dev/null
@@ -1,194 +0,0 @@
-<?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\InvalidConfigException;
-use yii\base\Object;
-use yii\web\View;
-
-/**
- * AssetBundle represents a collection of asset files, such as CSS, JS, images.
- *
- * Each asset bundle has a unique name that globally identifies it among all asset bundles used in an application.
- * The name is the [fully qualified class name](http://php.net/manual/en/language.namespaces.rules.php)
- * of the class representing it.
- *
- * An asset bundle can depend on other asset bundles. When registering an asset bundle
- * with a view, all its dependent asset bundles will be automatically registered.
- *
- * @author Qiang Xue <qiang.xue@gmail.com>
- * @since 2.0
- */
-class AssetBundle extends Object
-{
-	/**
-	 * @var string the root directory of the source asset files. A source asset file
-	 * is a file that is part of your source code repository of your Web application.
-	 *
-	 * You must set this property if the directory containing the source asset files
-	 * is not Web accessible (this is usually the case for extensions).
-	 *
-	 * By setting this property, the asset manager will publish the source asset files
-	 * to a Web-accessible directory [[basePath]].
-	 *
-	 * You can use either a directory or an alias of the directory.
-	 */
-	public $sourcePath;
-	/**
-	 * @var string the Web-accessible directory that contains the asset files in this bundle.
-	 *
-	 * If [[sourcePath]] is set, this property will be *overwritten* by [[AssetManager]]
-	 * when it publishes the asset files from [[sourcePath]].
-	 *
-	 * If the bundle contains any assets that are specified in terms of relative file path,
-	 * then this property must be set either manually or automatically (by [[AssetManager]] via
-	 * asset publishing).
-	 *
-	 * You can use either a directory or an alias of the directory.
-	 */
-	public $basePath;
-	/**
-	 * @var string the base URL that will be prefixed to the asset files for them to
-	 * be accessed via Web server.
-	 *
-	 * If [[sourcePath]] is set, this property will be *overwritten* by [[AssetManager]]
-	 * when it publishes the asset files from [[sourcePath]].
-	 *
-	 * If the bundle contains any assets that are specified in terms of relative file path,
-	 * then this property must be set either manually or automatically (by asset manager via
-	 * asset publishing).
-	 *
-	 * You can use either a URL or an alias of the URL.
-	 */
-	public $baseUrl;
-	/**
-	 * @var array list of bundle class names that this bundle depends on.
-	 *
-	 * For example:
-	 *
-	 * ```php
-	 * public $depends = [
-	 *    'yii\web\YiiAsset',
-	 *    'yii\bootstrap\BootstrapAsset',
-	 * ];
-	 * ```
-	 */
-	public $depends = [];
-	/**
-	 * @var array list of JavaScript files that this bundle contains. Each JavaScript file can
-	 * be either a file path (without leading slash) relative to [[basePath]] or a URL representing
-	 * an external JavaScript file.
-	 *
-	 * Note that only forward slash "/" can be used as directory separator.
-	 */
-	public $js = [];
-	/**
-	 * @var array list of CSS files that this bundle contains. Each CSS file can
-	 * be either a file path (without leading slash) relative to [[basePath]] or a URL representing
-	 * an external CSS file.
-	 *
-	 * Note that only forward slash "/" can be used as directory separator.
-	 */
-	public $css = [];
-	/**
-	 * @var array the options that will be passed to [[\yii\web\View::registerJsFile()]]
-	 * when registering the JS files in this bundle.
-	 */
-	public $jsOptions = [];
-	/**
-	 * @var array the options that will be passed to [[\yii\web\View::registerCssFile()]]
-	 * when registering the CSS files in this bundle.
-	 */
-	public $cssOptions = [];
-	/**
-	 * @var array the options to be passed to [[AssetManager::publish()]] when the asset bundle
-	 * is being published.
-	 */
-	public $publishOptions = [];
-
-	/**
-	 * @param View $view
-	 * @return AssetBundle the registered asset bundle instance
-	 */
-	public static function register($view)
-	{
-		return $view->registerAssetBundle(get_called_class());
-	}
-
-	/**
-	 * Initializes the bundle.
-	 * If you override this method, make sure you call the parent implementation in the last.
-	 */
-	public function init()
-	{
-		if ($this->sourcePath !== null) {
-			$this->sourcePath = rtrim(Yii::getAlias($this->sourcePath), '/\\');
-		}
-		if ($this->basePath !== null) {
-			$this->basePath = rtrim(Yii::getAlias($this->basePath), '/\\');
-		}
-		if ($this->baseUrl !== null) {
-			$this->baseUrl = rtrim(Yii::getAlias($this->baseUrl), '/');
-		}
-	}
-
-	/**
-	 * Registers the CSS and JS files with the given view.
-	 * @param \yii\web\View $view the view that the asset files are to be registered with.
-	 */
-	public function registerAssetFiles($view)
-	{
-		foreach ($this->js as $js) {
-			if (strpos($js, '/') !== 0 && strpos($js, '://') === false) {
-				$view->registerJsFile($this->baseUrl . '/' . $js, [], $this->jsOptions);
-			} else {
-				$view->registerJsFile($js, [], $this->jsOptions);
-			}
-		}
-		foreach ($this->css as $css) {
-			if (strpos($css, '/') !== 0 && strpos($css, '://') === false) {
-				$view->registerCssFile($this->baseUrl . '/' . $css, [], $this->cssOptions);
-			} else {
-				$view->registerCssFile($css, [], $this->cssOptions);
-			}
-		}
-	}
-
-	/**
-	 * Publishes the asset bundle if its source code is not under Web-accessible directory.
-	 * It will also try to convert non-CSS or JS files (e.g. LESS, Sass) into the corresponding
-	 * CSS or JS files using [[AssetManager::converter|asset converter]].
-	 * @param AssetManager $am the asset manager to perform the asset publishing
-	 */
-	public function publish($am)
-	{
-		if ($this->sourcePath !== null && !isset($this->basePath, $this->baseUrl)) {
-			list ($this->basePath, $this->baseUrl) = $am->publish($this->sourcePath, $this->publishOptions);
-		}
-		$converter = $am->getConverter();
-		foreach ($this->js as $i => $js) {
-			if (strpos($js, '/') !== 0 && strpos($js, '://') === false) {
-				if (isset($this->basePath, $this->baseUrl)) {
-					$this->js[$i] = $converter->convert($js, $this->basePath, $this->baseUrl);
-				} else {
-					$this->js[$i] = '/' . $js;
-				}
-			}
-		}
-		foreach ($this->css as $i => $css) {
-			if (strpos($css, '/') !== 0 && strpos($css, '://') === false) {
-				if (isset($this->basePath, $this->baseUrl)) {
-					$this->css[$i] = $converter->convert($css, $this->basePath, $this->baseUrl);
-				} else {
-					$this->css[$i] = '/' . $css;
-				}
-			}
-		}
-	}
-}
diff --git a/framework/yii/web/AssetConverter.php b/framework/yii/web/AssetConverter.php
deleted file mode 100644
index 1b7d1c8..0000000
--- a/framework/yii/web/AssetConverter.php
+++ /dev/null
@@ -1,99 +0,0 @@
-<?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\Exception;
-
-/**
- * AssetConverter supports conversion of several popular script formats into JS or CSS scripts.
- *
- * It is used by [[AssetManager]] to convert files after they have been published.
- *
- * @author Qiang Xue <qiang.xue@gmail.com>
- * @since 2.0
- */
-class AssetConverter extends Component implements AssetConverterInterface
-{
-	/**
-	 * @var array the commands that are used to perform the asset conversion.
-	 * The keys are the asset file extension names, and the values are the corresponding
-	 * target script types (either "css" or "js") and the commands used for the conversion.
-	 */
-	public $commands = [
-		'less' => ['css', 'lessc {from} {to} --no-color'],
-		'scss' => ['css', 'sass {from} {to}'],
-		'sass' => ['css', 'sass {from} {to}'],
-		'styl' => ['js', 'stylus < {from} > {to}'],
-		'coffee' => ['js', 'coffee -p {from} > {to}'],
-		'ts' => ['js', 'tsc --out {to} {from}'],
-	];
-
-	/**
-	 * Converts a given asset file into a CSS or JS file.
-	 * @param string $asset the asset file path, relative to $basePath
-	 * @param string $basePath the directory the $asset is relative to.
-	 * @return string the converted asset file path, relative to $basePath.
-	 */
-	public function convert($asset, $basePath)
-	{
-		$pos = strrpos($asset, '.');
-		if ($pos !== false) {
-			$ext = substr($asset, $pos + 1);
-			if (isset($this->commands[$ext])) {
-				list ($ext, $command) = $this->commands[$ext];
-				$result = substr($asset, 0, $pos + 1) . $ext;
-				if (@filemtime("$basePath/$result") < filemtime("$basePath/$asset")) {
-					$this->runCommand($command, $basePath, $asset, $result);
-				}
-				return $result;
-			}
-		}
-		return $asset;
-	}
-
-	/**
-	 * Runs a command to convert asset files.
-	 * @param string $command the command to run
-	 * @param string $basePath asset base path and command working directory
-	 * @param string $asset the name of the asset file
-	 * @param string $result the name of the file to be generated by the converter command
-	 * @return bool true on success, false on failure. Failures will be logged.
-	 * @throws \yii\base\Exception when the command fails and YII_DEBUG is true.
-	 * In production mode the error will be logged.
-	 */
-	protected function runCommand($command, $basePath, $asset, $result)
-	{
-		$command = strtr($command, [
-			'{from}' => escapeshellarg("$basePath/$asset"),
-			'{to}' => escapeshellarg("$basePath/$result"),
-		]);
-		$descriptor = [
-			1 => ['pipe', 'w'],
-			2 => ['pipe', 'w'],
-		];
-		$pipes = [];
-		$proc = proc_open($command, $descriptor, $pipes, $basePath);
-		$stdout = stream_get_contents($pipes[1]);
-		$stderr = stream_get_contents($pipes[2]);
-		foreach($pipes as $pipe) {
-			fclose($pipe);
-		}
-		$status = proc_close($proc);
-
-		if ($status === 0) {
-			Yii::trace("Converted $asset into $result:\nSTDOUT:\n$stdout\nSTDERR:\n$stderr", __METHOD__);
-		} elseif (YII_DEBUG) {
-			throw new Exception("AssetConverter command '$command' failed with exit code $status:\nSTDOUT:\n$stdout\nSTDERR:\n$stderr");
-		} else {
-			Yii::error("AssetConverter command '$command' failed with exit code $status:\nSTDOUT:\n$stdout\nSTDERR:\n$stderr");
-		}
-		return $status === 0;
-	}
-}
diff --git a/framework/yii/web/CacheSession.php b/framework/yii/web/CacheSession.php
deleted file mode 100644
index 7b4a98d..0000000
--- a/framework/yii/web/CacheSession.php
+++ /dev/null
@@ -1,118 +0,0 @@
-<?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, [[DbSession]] is a better choice.
- *
- * The following example shows how you can configure the application to use CacheSession:
- * Add the following to your application config under `components`:
- *
- * ~~~
- * 'session' => [
- *     'class' => 'yii\web\CacheSession',
- *     // 'cache' => 'mycache',
- * ]
- * ~~~
- *
- * @property boolean $useCustomStorage Whether to use custom storage. This property is read-only.
- *
- * @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()
-	{
-		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.');
-		}
-		parent::init();
-	}
-
-	/**
-	 * 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 mixed a safe cache key associated with the session variable name
-	 */
-	protected function calculateKey($id)
-	{
-		return [__CLASS__, $id];
-	}
-}
diff --git a/framework/yii/web/Cookie.php b/framework/yii/web/Cookie.php
deleted file mode 100644
index 8cbb412..0000000
--- a/framework/yii/web/Cookie.php
+++ /dev/null
@@ -1,65 +0,0 @@
-<?php
-/**
- * @link http://www.yiiframework.com/
- * @copyright Copyright (c) 2008 Yii Software LLC
- * @license http://www.yiiframework.com/license/
- */
-
-namespace yii\web;
-
-/**
- * Cookie represents information related with a cookie, such as [[name]], [[value]], [[domain]], etc.
- *
- * @author Qiang Xue <qiang.xue@gmail.com>
- * @since 2.0
- */
-class Cookie extends \yii\base\Object
-{
-	/**
-	 * @var string name of the cookie
-	 */
-	public $name;
-	/**
-	 * @var string value of the cookie
-	 */
-	public $value = '';
-	/**
-	 * @var string domain of the cookie
-	 */
-	public $domain = '';
-	/**
-	 * @var integer the timestamp at which the cookie expires. This is the server timestamp.
-	 * Defaults to 0, meaning "until the browser is closed".
-	 */
-	public $expire = 0;
-	/**
-	 * @var string the path on the server in which the cookie will be available on. The default is '/'.
-	 */
-	public $path = '/';
-	/**
-	 * @var boolean whether cookie should be sent via secure connection
-	 */
-	public $secure = false;
-	/**
-	 * @var boolean whether the cookie should be accessible only through the HTTP protocol.
-	 * 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;
-
-	/**
-	 * 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/yii/web/CookieCollection.php b/framework/yii/web/CookieCollection.php
deleted file mode 100644
index 3cf80ff..0000000
--- a/framework/yii/web/CookieCollection.php
+++ /dev/null
@@ -1,228 +0,0 @@
-<?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 ArrayIterator;
-use yii\base\InvalidCallException;
-use yii\base\Object;
-
-/**
- * CookieCollection maintains the cookies available in the current request.
- *
- * @property integer $count The number of cookies in the collection. This property is read-only.
- * @property ArrayIterator $iterator An iterator for traversing the cookies in the collection. This property
- * is read-only.
- *
- * @author Qiang Xue <qiang.xue@gmail.com>
- * @since 2.0
- */
-class CookieCollection extends Object implements \IteratorAggregate, \ArrayAccess, \Countable
-{
-	/**
-	 * @var boolean whether this collection is read only.
-	 */
-	public $readOnly = false;
-
-	/**
-	 * @var Cookie[] the cookies in this collection (indexed by the cookie names)
-	 */
-	private $_cookies = [];
-
-	/**
-	 * Constructor.
-	 * @param array $cookies the cookies that this collection initially contains. This should be
-	 * an array of name-value pairs.s
-	 * @param array $config name-value pairs that will be used to initialize the object properties
-	 */
-	public function __construct($cookies = [], $config = [])
-	{
-		$this->_cookies = $cookies;
-		parent::__construct($config);
-	}
-
-	/**
-	 * 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 collection.
-	 * @return ArrayIterator an iterator for traversing the cookies in the collection.
-	 */
-	public function getIterator()
-	{
-		return new ArrayIterator($this->_cookies);
-	}
-
-	/**
-	 * 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($collection)`.
-	 * @return integer the number of cookies in the collection.
-	 */
-	public function count()
-	{
-		return $this->getCount();
-	}
-
-	/**
-	 * Returns the number of cookies in the collection.
-	 * @return integer the number of cookies in the collection.
-	 */
-	public function getCount()
-	{
-		return count($this->_cookies);
-	}
-
-	/**
-	 * 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 get($name)
-	{
-		return isset($this->_cookies[$name]) ? $this->_cookies[$name] : null;
-	}
-
-	/**
-	 * 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 getValue($name, $defaultValue = null)
-	{
-		return isset($this->_cookies[$name]) ? $this->_cookies[$name]->value : $defaultValue;
-	}
-
-	/**
-	 * Returns whether there is a cookie with the specified name.
-	 * @param string $name the cookie name
-	 * @return boolean whether the named cookie exists
-	 */
-	public function has($name)
-	{
-		return isset($this->_cookies[$name]);
-	}
-
-	/**
-	 * 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
-	 * @throws InvalidCallException if the cookie collection is read only
-	 */
-	public function add($cookie)
-	{
-		if ($this->readOnly) {
-			throw new InvalidCallException('The cookie collection is read only.');
-		}
-		$this->_cookies[$cookie->name] = $cookie;
-	}
-
-	/**
-	 * Removes a cookie.
-	 * If `$removeFromBrowser` is true, the cookie will be removed from the browser.
-	 * In this case, a cookie with outdated expiry will be added to the collection.
-	 * @param Cookie|string $cookie the cookie object or the name of the cookie to be removed.
-	 * @param boolean $removeFromBrowser whether to remove the cookie from browser
-	 * @throws InvalidCallException if the cookie collection is read only
-	 */
-	public function remove($cookie, $removeFromBrowser = true)
-	{
-		if ($this->readOnly) {
-			throw new InvalidCallException('The cookie collection is read only.');
-		}
-		if ($cookie instanceof Cookie) {
-			$cookie->expire = 1;
-			$cookie->value = '';
-		} else {
-			$cookie = new Cookie([
-				'name' => $cookie,
-				'expire' => 1,
-			]);
-		}
-		if ($removeFromBrowser) {
-			$this->_cookies[$cookie->name] = $cookie;
-		} else {
-			unset($this->_cookies[$cookie->name]);
-		}
-	}
-
-	/**
-	 * Removes all cookies.
-	 * @throws InvalidCallException if the cookie collection is read only
-	 */
-	public function removeAll()
-	{
-		if ($this->readOnly) {
-			throw new InvalidCallException('The cookie collection is read only.');
-		}
-		$this->_cookies = [];
-	}
-
-	/**
-	 * 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()
-	{
-		return $this->_cookies;
-	}
-
-	/**
-	 * 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($collection[$name])`.
-	 * @param string $name the cookie name
-	 * @return boolean whether the named cookie exists
-	 */
-	public function offsetExists($name)
-	{
-		return $this->has($name);
-	}
-
-	/**
-	 * 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 `$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($name)
-	{
-		return $this->get($name);
-	}
-
-	/**
-	 * Adds the cookie to the collection.
-	 * This method is required by the SPL interface `ArrayAccess`.
-	 * 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($cookie);
-	}
-
-	/**
-	 * Removes the named cookie.
-	 * This method is required by the SPL interface `ArrayAccess`.
-	 * 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($name)
-	{
-		$this->remove($name);
-	}
-}
diff --git a/framework/yii/web/DbSession.php b/framework/yii/web/DbSession.php
deleted file mode 100644
index 2b0b0e7..0000000
--- a/framework/yii/web/DbSession.php
+++ /dev/null
@@ -1,224 +0,0 @@
-<?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:
- * Add the following to your application config under `components`:
- *
- * ~~~
- * 'session' => [
- *     'class' => 'yii\web\DbSession',
- *     // 'db' => 'mydb',
- *     // 'sessionTable' => 'my_session',
- * ]
- * ~~~
- *
- * @property boolean $useCustomStorage Whether to use custom storage. This property is read-only.
- *
- * @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 = '{{%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()
-	{
-		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.");
-		}
-		parent::init();
-	}
-
-	/**
-	 * 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(['id' => $oldID])
-			->createCommand($this->db)
-			->queryOne();
-		if ($row !== false) {
-			if ($deleteOldSession) {
-				$this->db->createCommand()
-					->update($this->sessionTable, ['id' => $newID], ['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, [
-					'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(['data'])
-			->from($this->sessionTable)
-			->where('[[expire]]>:expire AND [[id]]=:id', [':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(['id'])
-				->from($this->sessionTable)
-				->where(['id' => $id])
-				->createCommand($this->db)
-				->queryScalar();
-			if ($exists === false) {
-				$this->db->createCommand()
-					->insert($this->sessionTable, [
-						'id' => $id,
-						'data' => $data,
-						'expire' => $expire,
-					])->execute();
-			} else {
-				$this->db->createCommand()
-					->update($this->sessionTable, ['data' => $data, 'expire' => $expire], ['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, ['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', [':expire' => time()])
-			->execute();
-		return true;
-	}
-}
diff --git a/framework/yii/web/ErrorAction.php b/framework/yii/web/ErrorAction.php
deleted file mode 100644
index 95f17be..0000000
--- a/framework/yii/web/ErrorAction.php
+++ /dev/null
@@ -1,106 +0,0 @@
-<?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\Exception;
-use yii\base\UserException;
-
-/**
- * ErrorAction displays application errors using a specified view.
- *
- * To use ErrorAction, you need to do the following steps:
- *
- * First, declare an action of ErrorAction type in the `actions()` method of your `SiteController`
- * class (or whatever controller you prefer), like the following:
- *
- * ```php
- * public function actions()
- * {
- *     return [
- *         'error' => ['class' => 'yii\web\ErrorAction'],
- *     ];
- * }
- * ```
- *
- * Then, create a view file for this action. If the route of your error action is `site/error`, then
- * the view file should be `views/site/error.php`. In this view file, the following variables are available:
- *
- * - `$name`: the error name
- * - `$message`: the error message
- * - `$exception`: the exception being handled
- *
- * Finally, configure the "errorHandler" application component as follows,
- *
- * ```php
- * 'errorHandler' => [
- *     'errorAction' => 'site/error',
- * ]
- * ```
- *
- * @author Qiang Xue <qiang.xue@gmail.com>
- * @since 2.0
- */
-class ErrorAction extends Action
-{
-	/**
-	 * @var string the view file to be rendered. If not set, it will take the value of [[id]].
-	 * That means, if you name the action as "error" in "SiteController", then the view name
-	 * would be "error", and the corresponding view file would be "views/site/error.php".
-	 */
-	public $view;
-	/**
-	 * @var string the name of the error when the exception name cannot be determined.
-	 * Defaults to "Error".
-	 */
-	public $defaultName;
-	/**
-	 * @var string the message to be displayed when the exception message contains sensitive information.
-	 * Defaults to "An internal server error occurred.".
-	 */
-	public $defaultMessage;
-
-
-	public function run()
-	{
-		if (($exception = Yii::$app->exception) === null) {
-			return '';
-		}
-
-		if ($exception instanceof HttpException) {
-			$code = $exception->statusCode;
-		} else {
-			$code = $exception->getCode();
-		}
-		if ($exception instanceof Exception) {
-			$name = $exception->getName();
-		} else {
-			$name = $this->defaultName ?: Yii::t('yii', 'Error');
-		}
-		if ($code) {
-			$name .= " (#$code)";
-		}
-
-		if ($exception instanceof UserException) {
-			$message = $exception->getMessage();
-		} else {
-			$message = $this->defaultMessage ?: Yii::t('yii', 'An internal server error occurred.');
-		}
-
-		if (Yii::$app->getRequest()->getIsAjax()) {
-			return "$name: $message";
-		} else {
-			return $this->controller->render($this->view ?: $this->id, [
-				'name' => $name,
-				'message' => $message,
-				'exception' => $exception,
-			]);
-		}
-	}
-}
diff --git a/framework/yii/web/HeaderCollection.php b/framework/yii/web/HeaderCollection.php
deleted file mode 100644
index e8e4f9c..0000000
--- a/framework/yii/web/HeaderCollection.php
+++ /dev/null
@@ -1,221 +0,0 @@
-<?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\Object;
-use ArrayIterator;
-
-/**
- * HeaderCollection is used by [[Response]] to maintain the currently registered HTTP headers.
- *
- * @property integer $count The number of headers in the collection. This property is read-only.
- * @property ArrayIterator $iterator An iterator for traversing the headers in the collection. This property
- * is read-only.
- *
- * @author Qiang Xue <qiang.xue@gmail.com>
- * @since 2.0
- */
-class HeaderCollection extends Object implements \IteratorAggregate, \ArrayAccess, \Countable
-{
-	/**
-	 * @var array the headers in this collection (indexed by the header names)
-	 */
-	private $_headers = [];
-
-	/**
-	 * Returns an iterator for traversing the headers in the collection.
-	 * This method is required by the SPL interface `IteratorAggregate`.
-	 * It will be implicitly called when you use `foreach` to traverse the collection.
-	 * @return ArrayIterator an iterator for traversing the headers in the collection.
-	 */
-	public function getIterator()
-	{
-		return new ArrayIterator($this->_headers);
-	}
-
-	/**
-	 * Returns the number of headers in the collection.
-	 * This method is required by the SPL `Countable` interface.
-	 * It will be implicitly called when you use `count($collection)`.
-	 * @return integer the number of headers in the collection.
-	 */
-	public function count()
-	{
-		return $this->getCount();
-	}
-
-	/**
-	 * Returns the number of headers in the collection.
-	 * @return integer the number of headers in the collection.
-	 */
-	public function getCount()
-	{
-		return count($this->_headers);
-	}
-
-	/**
-	 * Returns the named header(s).
-	 * @param string $name the name of the header to return
-	 * @param mixed $default the value to return in case the named header does not exist
-	 * @param boolean $first whether to only return the first header of the specified name.
-	 * If false, all headers of the specified name will be returned.
-	 * @return string|array the named header(s). If `$first` is true, a string will be returned;
-	 * If `$first` is false, an array will be returned.
-	 */
-	public function get($name, $default = null, $first = true)
-	{
-		$name = strtolower($name);
-		if (isset($this->_headers[$name])) {
-			return $first ? reset($this->_headers[$name]) : $this->_headers[$name];
-		} else {
-			return $default;
-		}
-	}
-
-	/**
-	 * Adds a new header.
-	 * If there is already a header with the same name, it will be replaced.
-	 * @param string $name the name of the header
-	 * @param string $value the value of the header
-	 * @return static the collection object itself
-	 */
-	public function set($name, $value = '')
-	{
-		$name = strtolower($name);
-		$this->_headers[$name] = (array)$value;
-		return $this;
-	}
-
-	/**
-	 * Adds a new header.
-	 * If there is already a header with the same name, the new one will
-	 * be appended to it instead of replacing it.
-	 * @param string $name the name of the header
-	 * @param string $value the value of the header
-	 * @return static the collection object itself
-	 */
-	public function add($name, $value)
-	{
-		$name = strtolower($name);
-		$this->_headers[$name][] = $value;
-		return $this;
-	}
-
-	/**
-	 * Sets a new header only if it does not exist yet.
-	 * If there is already a header with the same name, the new one will be ignored.
-	 * @param string $name the name of the header
-	 * @param string $value the value of the header
-	 * @return static the collection object itself
-	 */
-	public function setDefault($name, $value)
-	{
-		$name = strtolower($name);
-		if (empty($this->_headers[$name])) {
-			$this->_headers[$name][] = $value;
-		}
-		return $this;
-	}
-
-	/**
-	 * Returns a value indicating whether the named header exists.
-	 * @param string $name the name of the header
-	 * @return boolean whether the named header exists
-	 */
-	public function has($name)
-	{
-		$name = strtolower($name);
-		return isset($this->_headers[$name]);
-	}
-
-	/**
-	 * Removes a header.
-	 * @param string $name the name of the header to be removed.
-	 * @return string the value of the removed header. Null is returned if the header does not exist.
-	 */
-	public function remove($name)
-	{
-		$name = strtolower($name);
-		if (isset($this->_headers[$name])) {
-			$value = $this->_headers[$name];
-			unset($this->_headers[$name]);
-			return $value;
-		} else {
-			return null;
-		}
-	}
-
-	/**
-	 * Removes all headers.
-	 */
-	public function removeAll()
-	{
-		$this->_headers = [];
-	}
-
-	/**
-	 * Returns the collection as a PHP array.
-	 * @return array the array representation of the collection.
-	 * The array keys are header names, and the array values are the corresponding header values.
-	 */
-	public function toArray()
-	{
-		return $this->_headers;
-	}
-
-	/**
-	 * Returns whether there is a header with the specified name.
-	 * This method is required by the SPL interface `ArrayAccess`.
-	 * It is implicitly called when you use something like `isset($collection[$name])`.
-	 * @param string $name the header name
-	 * @return boolean whether the named header exists
-	 */
-	public function offsetExists($name)
-	{
-		return $this->has($name);
-	}
-
-	/**
-	 * Returns the header with the specified name.
-	 * This method is required by the SPL interface `ArrayAccess`.
-	 * It is implicitly called when you use something like `$header = $collection[$name];`.
-	 * This is equivalent to [[get()]].
-	 * @param string $name the header name
-	 * @return string the header value with the specified name, null if the named header does not exist.
-	 */
-	public function offsetGet($name)
-	{
-		return $this->get($name);
-	}
-
-	/**
-	 * Adds the header to the collection.
-	 * This method is required by the SPL interface `ArrayAccess`.
-	 * It is implicitly called when you use something like `$collection[$name] = $header;`.
-	 * This is equivalent to [[add()]].
-	 * @param string $name the header name
-	 * @param string $value the header value to be added
-	 */
-	public function offsetSet($name, $value)
-	{
-		$this->set($name, $value);
-	}
-
-	/**
-	 * Removes the named header.
-	 * This method is required by the SPL interface `ArrayAccess`.
-	 * It is implicitly called when you use something like `unset($collection[$name])`.
-	 * This is equivalent to [[remove()]].
-	 * @param string $name the header name
-	 */
-	public function offsetUnset($name)
-	{
-		$this->remove($name);
-	}
-}
diff --git a/framework/yii/web/HttpCache.php b/framework/yii/web/HttpCache.php
deleted file mode 100644
index 134df71..0000000
--- a/framework/yii/web/HttpCache.php
+++ /dev/null
@@ -1,160 +0,0 @@
-<?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;
-
-/**
- * The HttpCache provides functionality for caching via HTTP Last-Modified and Etag headers.
- *
- * It is an action filter that can be added to a controller and handles the `beforeAction` event.
- *
- * To use AccessControl, declare it in the `behaviors()` method of your controller class.
- * In the following example the filter will be applied to the `list`-action and
- * the Last-Modified header will contain the date of the last update to the user table in the database.
- *
- * ~~~
- * public function behaviors()
- * {
- *     return [
- *         'httpCache' => [
- *             'class' => \yii\web\HttpCache::className(),
- *             'only' => ['list'],
- *             'lastModified' => function ($action, $params) {
- *                 $q = new Query();
- *                 return strtotime($q->from('users')->max('updated_timestamp'));
- *             },
- * //            'etagSeed' => function ($action, $params) {
- * //                return // generate etag seed here
- * //            }
- *         ],
- *     ];
- * }
- * ~~~
- *
- * @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 = '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->getRequest()->getMethod();
-		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();
-		$response = Yii::$app->getResponse();
-		if ($etag !== null) {
-			$response->getHeaders()->set('Etag', $etag);
-		}
-
-		if ($this->validateCache($lastModified, $etag)) {
-			$response->setStatusCode(304);
-			return false;
-		}
-
-		if ($lastModified !== null) {
-			$response->getHeaders()->set('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');
-		$headers = Yii::$app->getResponse()->getHeaders();
-		$headers->set('Pragma');
-		if ($this->cacheControlHeader !== null) {
-			$headers->set('Cache-Control', $this->cacheControlHeader);
-		}
-	}
-
-	/**
-	 * 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)) . '"';
-	}
-}
diff --git a/framework/yii/web/HttpException.php b/framework/yii/web/HttpException.php
deleted file mode 100644
index 2398437..0000000
--- a/framework/yii/web/HttpException.php
+++ /dev/null
@@ -1,72 +0,0 @@
-<?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\UserException;
-
-/**
- * HttpException represents an exception caused by an improper request of the end-user.
- *
- * HttpException can be differentiated via its [[statusCode]] property value which
- * keeps a standard HTTP status code (e.g. 404, 500). Error handlers may use this status code
- * to decide how to format the error page.
- *
- * Throwing an HttpException like in the following example will result in the 404 page to be displayed.
- *
- * ```php
- * if ($item === null) { // item does not exist
- *     throw new \yii\web\HttpException(404, 'The requested Item could not be found.');
- * }
- * ```
- *
- * @author Qiang Xue <qiang.xue@gmail.com>
- * @since 2.0
- */
-class HttpException extends UserException
-{
-	/**
-	 * @var integer HTTP status code, such as 403, 404, 500, etc.
-	 */
-	public $statusCode;
-
-	/**
-	 * Constructor.
-	 * @param integer $status HTTP status code, such as 404, 500, etc.
-	 * @param string $message error message
-	 * @param integer $code error code
-	 * @param \Exception $previous The previous exception used for the exception chaining.
-	 */
-	public function __construct($status, $message = null, $code = 0, \Exception $previous = null)
-	{
-		$this->statusCode = $status;
-		parent::__construct($message, $code, $previous);
-	}
-
-	/**
-	 * @return string the user-friendly name of this exception
-	 */
-	public function getName()
-	{
-		if (isset(Response::$httpStatuses[$this->statusCode])) {
-			return Response::$httpStatuses[$this->statusCode];
-		} else {
-			return 'Error';
-		}
-	}
-
-	/**
-	 * Returns the array representation of this object.
-	 * @return array the array representation of this object.
-	 */
-	public function toArray()
-	{
-		$array = parent::toArray();
-		$array['status'] = $this->statusCode;
-		return $array;
-	}
-}
diff --git a/framework/yii/web/IdentityInterface.php b/framework/yii/web/IdentityInterface.php
deleted file mode 100644
index c796b50..0000000
--- a/framework/yii/web/IdentityInterface.php
+++ /dev/null
@@ -1,81 +0,0 @@
-<?php
-/**
- * @link http://www.yiiframework.com/
- * @copyright Copyright (c) 2008 Yii Software LLC
- * @license http://www.yiiframework.com/license/
- */
-
-namespace yii\web;
-
-/**
- * IdentityInterface is the interface that should be implemented by a class providing identity information.
- *
- * This interface can typically be implemented by a user model class. For example, the following
- * code shows how to implement this interface by a User ActiveRecord class:
- *
- * ~~~
- * class User extends ActiveRecord implements IdentityInterface
- * {
- *     public static function findIdentity($id)
- *     {
- *         return static::find($id);
- *     }
- *
- *     public function getId()
- *     {
- *         return $this->id;
- *     }
- *
- *     public function getAuthKey()
- *     {
- *         return $this->authKey;
- *     }
- *
- *     public function validateAuthKey($authKey)
- *     {
- *         return $this->authKey === $authKey;
- *     }
- * }
- * ~~~
- *
- * @author Qiang Xue <qiang.xue@gmail.com>
- * @since 2.0
- */
-interface IdentityInterface
-{
-	/**
-	 * Finds an identity by the given ID.
-	 * @param string|integer $id the ID to be looked for
-	 * @return IdentityInterface the identity object that matches the given ID.
-	 * Null should be returned if such an identity cannot be found
-	 * or the identity is not in an active state (disabled, deleted, etc.)
-	 */
-	public static function findIdentity($id);
-	/**
-	 * Returns an ID that can uniquely identify a user identity.
-	 * @return string|integer 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 key should be unique for each individual user, and should be persistent
-	 * so that it can be used to check the validity of the user identity.
-	 *
-	 * The space of such keys should be big enough to defeat potential identity attacks.
-	 *
-	 * This is required if [[User::enableAutoLogin]] is enabled.
-	 * @return string a key that is used to check the validity of a given identity ID.
-	 * @see validateAuthKey()
-	 */
-	public function getAuthKey();
-	/**
-	 * Validates the given auth key.
-	 *
-	 * This is required if [[User::enableAutoLogin]] is enabled.
-	 * @param string $authKey the given auth key
-	 * @return boolean whether the given auth key is valid.
-	 * @see getAuthKey()
-	 */
-	public function validateAuthKey($authKey);
-}
diff --git a/framework/yii/web/PageCache.php b/framework/yii/web/PageCache.php
deleted file mode 100644
index 4c8cc50..0000000
--- a/framework/yii/web/PageCache.php
+++ /dev/null
@@ -1,150 +0,0 @@
-<?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\caching\Dependency;
-
-/**
- * The PageCache provides functionality for whole page caching
- *
- * It is an action filter that can be added to a controller and handles the `beforeAction` event.
- *
- * To use PageCache, declare it in the `behaviors()` method of your controller class.
- * In the following example the filter will be applied to the `list`-action and
- * cache the whole page for maximum 60 seconds or until the count of entries in the post table changes.
- * It also stores different versions of the page depended on the route ([[varyByRoute]] is true by default),
- * the application language and user id.
- *
- * ~~~
- * public function behaviors()
- * {
- *     return [
- *         'pageCache' => [
- *             'class' => \yii\web\PageCache::className(),
- *             'only' => ['list'],
- *             'duration' => 60,
- *             'dependecy' => [
- *                 'class' => 'yii\caching\DbDependency',
- *                 'sql' => 'SELECT COUNT(*) FROM post',
- *             ],
- *             'variations' => [
- *                 Yii::$app->language,
- *                 Yii::$app->user->id
- *             ]
- *         ],
- *     ];
- * }
- * ~~~
- *
- * @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 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,
-	 *
-	 * ~~~
-	 * [
-	 *     '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:
-	 *
-	 * ~~~
-	 * [
-	 *     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 component to use for caching. If not set, the default application view component
-	 * [[Application::view]] will be used.
-	 */
-	public $view;
-
-
-	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 = [];
-		foreach (['cache', 'duration', 'dependency', 'variations', 'enabled'] as $name) {
-			$properties[$name] = $this->$name;
-		}
-		$id = $this->varyByRoute ? $action->getUniqueId() : __CLASS__;
-		ob_start();
-		ob_implicit_flush(false);
-		if ($this->view->beginCache($id, $properties)) {
-			return true;
-		} else {
-			Yii::$app->getResponse()->content = ob_get_clean();
-			return false;
-		}
-	}
-
-	/**
-	 * 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.
-	 * @param mixed $result the action execution result
-	 */
-	public function afterAction($action, &$result)
-	{
-		echo $result;
-		$this->view->endCache();
-		$result = ob_get_clean();
-	}
-}
diff --git a/framework/yii/web/SessionIterator.php b/framework/yii/web/SessionIterator.php
deleted file mode 100644
index c960dd4..0000000
--- a/framework/yii/web/SessionIterator.php
+++ /dev/null
@@ -1,84 +0,0 @@
-<?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/yii/web/UploadedFile.php b/framework/yii/web/UploadedFile.php
deleted file mode 100644
index 5e1e428..0000000
--- a/framework/yii/web/UploadedFile.php
+++ /dev/null
@@ -1,234 +0,0 @@
-<?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\helpers\Html;
-
-/**
- * UploadedFile represents the information for an uploaded file.
- *
- * You can call [[getInstance()]] to retrieve the instance of an uploaded file,
- * and then use [[saveAs()]] to save it on the server.
- * You may also query other information about the file, including [[name]],
- * [[tempName]], [[type]], [[size]] and [[error]].
- *
- * @property boolean $hasError Whether there is an error with the uploaded file. Check [[error]] for detailed
- * error code information. This property is read-only.
- *
- * @author Qiang Xue <qiang.xue@gmail.com>
- * @since 2.0
- */
-class UploadedFile extends Object
-{
-	private static $_files;
-
-	/**
-	 * @var string the original name of the file being uploaded
-	 */
-	public $name;
-	/**
-	 * @var string the path of the uploaded file on the server.
-	 * Note, this is a temporary file which will be automatically deleted by PHP
-	 * after the current request is processed.
-	 */
-	public $tempName;
-	/**
-	 * @var string the MIME-type of the uploaded file (such as "image/gif").
-	 * Since this MIME type is not checked on the server side, do not take this value for granted.
-	 * Instead, use [[FileHelper::getMimeType()]] to determine the exact MIME type.
-	 */
-	public $type;
-	/**
-	 * @var integer the actual size of the uploaded file in bytes
-	 */
-	public $size;
-	/**
-	 * @var integer an error code describing the status of this file uploading.
-	 * @see http://www.php.net/manual/en/features.file-upload.errors.php
-	 */
-	public $error;
-
-
-	/**
-	 * String output.
-	 * This is PHP magic method that returns string representation of an object.
-	 * The implementation here returns the uploaded file's name.
-	 * @return string the string representation of the object
-	 */
-	public function __toString()
-	{
-		return $this->name;
-	}
-
-	/**
-	 * Returns an uploaded file for the given model attribute.
-	 * The file should be uploaded using [[ActiveForm::fileInput()]].
-	 * @param \yii\base\Model $model the data model
-	 * @param string $attribute the attribute name. The attribute name may contain array indexes.
-	 * For example, '[1]file' for tabular file uploading; and 'file[1]' for an element in a file array.
-	 * @return UploadedFile the instance of the uploaded file.
-	 * Null is returned if no file is uploaded for the specified model attribute.
-	 * @see getInstanceByName()
-	 */
-	public static function getInstance($model, $attribute)
-	{
-		$name = Html::getInputName($model, $attribute);
-		return static::getInstanceByName($name);
-	}
-
-	/**
-	 * Returns all uploaded files for the given model attribute.
-	 * @param \yii\base\Model $model the data model
-	 * @param string $attribute the attribute name. The attribute name may contain array indexes
-	 * for tabular file uploading, e.g. '[1]file'.
-	 * @return UploadedFile[] array of UploadedFile objects.
-	 * Empty array is returned if no available file was found for the given attribute.
-	 */
-	public static function getInstances($model, $attribute)
-	{
-		$name = Html::getInputName($model, $attribute);
-		return static::getInstancesByName($name);
-	}
-
-	/**
-	 * Returns an uploaded file according to the given file input name.
-	 * The name can be a plain string or a string like an array element (e.g. 'Post[imageFile]', or 'Post[0][imageFile]').
-	 * @param string $name the name of the file input field.
-	 * @return UploadedFile the instance of the uploaded file.
-	 * Null is returned if no file is uploaded for the specified name.
-	 */
-	public static function getInstanceByName($name)
-	{
-		$files = static::loadFiles();
-		return isset($files[$name]) ? $files[$name] : null;
-	}
-
-	/**
-	 * Returns an array of uploaded files corresponding to the specified file input name.
-	 * This is mainly used when multiple files were uploaded and saved as 'files[0]', 'files[1]',
-	 * 'files[n]'..., and you can retrieve them all by passing 'files' as the name.
-	 * @param string $name the name of the array of files
-	 * @return UploadedFile[] the array of CUploadedFile objects. Empty array is returned
-	 * if no adequate upload was found. Please note that this array will contain
-	 * all files from all sub-arrays regardless how deeply nested they are.
-	 */
-	public static function getInstancesByName($name)
-	{
-		$files = static::loadFiles();
-		if (isset($files[$name])) {
-			return [$files[$name]];
-		}
-		$results = [];
-		foreach ($files as $key => $file) {
-			if (strpos($key, "{$name}[") === 0) {
-				$results[] = self::$_files[$key];
-			}
-		}
-		return $results;
-	}
-
-	/**
-	 * Cleans up the loaded UploadedFile instances.
-	 * This method is mainly used by test scripts to set up a fixture.
-	 */
-	public static function reset()
-	{
-		self::$_files = null;
-	}
-
-	/**
-	 * Saves the uploaded file.
-	 * Note that this method uses php's move_uploaded_file() method. If the target file `$file`
-	 * already exists, it will be overwritten.
-	 * @param string $file the file path used to save the uploaded file
-	 * @param boolean $deleteTempFile whether to delete the temporary file after saving.
-	 * If true, you will not be able to save the uploaded file again in the current request.
-	 * @return boolean true whether the file is saved successfully
-	 * @see error
-	 */
-	public function saveAs($file, $deleteTempFile = true)
-	{
-		if ($this->error == UPLOAD_ERR_OK) {
-			if ($deleteTempFile) {
-				return move_uploaded_file($this->tempName, $file);
-			} elseif (is_uploaded_file($this->tempName)) {
-				return copy($this->tempName, $file);
-			}
-		}
-		return false;
-	}
-	
-	/**
-	 * @return string original file base name
-	 */
-	public function getBaseName()
-	{
-		return pathinfo($this->name, PATHINFO_FILENAME);
-	}
-
-	/**
-	 * @return string file extension
-	 */
-	public function getExtension()
-	{
-		return strtolower(pathinfo($this->name, PATHINFO_EXTENSION));
-	}
-
-	/**
-	 * @return boolean whether there is an error with the uploaded file.
-	 * Check [[error]] for detailed error code information.
-	 */
-	public function getHasError()
-	{
-		return $this->error != UPLOAD_ERR_OK;
-	}
-
-	/**
-	 * Creates UploadedFile instances from $_FILE.
-	 * @return array the UploadedFile instances
-	 */
-	private static function loadFiles()
-	{
-		if (self::$_files === null) {
-			self::$_files = [];
-			if (isset($_FILES) && is_array($_FILES)) {
-				foreach ($_FILES as $class => $info) {
-					self::loadFilesRecursive($class, $info['name'], $info['tmp_name'], $info['type'], $info['size'], $info['error']);
-				}
-			}
-		}
-		return self::$_files;
-	}
-
-	/**
-	 * Creates UploadedFile instances from $_FILE recursively.
-	 * @param string $key key for identifying uploaded file: class name and sub-array indexes
-	 * @param mixed $names file names provided by PHP
-	 * @param mixed $tempNames temporary file names provided by PHP
-	 * @param mixed $types file types provided by PHP
-	 * @param mixed $sizes file sizes provided by PHP
-	 * @param mixed $errors uploading issues provided by PHP
-	 */
-	private static function loadFilesRecursive($key, $names, $tempNames, $types, $sizes, $errors)
-	{
-		if (is_array($names)) {
-			foreach ($names as $i => $name) {
-				self::loadFilesRecursive($key . '[' . $i . ']', $name, $tempNames[$i], $types[$i], $sizes[$i], $errors[$i]);
-			}
-		} else {
-			self::$_files[$key] = new static([
-				'name' => $names,
-				'tempName' => $tempNames,
-				'type' => $types,
-				'size' => $sizes,
-				'error' => $errors,
-			]);
-		}
-	}
-}
diff --git a/framework/yii/web/UrlManager.php b/framework/yii/web/UrlManager.php
deleted file mode 100644
index a2044cb..0000000
--- a/framework/yii/web/UrlManager.php
+++ /dev/null
@@ -1,338 +0,0 @@
-<?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.
- *
- * UrlManager is configured as an application component in [[yii\base\Application]] by default.
- * You can access that instance via `Yii::$app->urlManager`.
- *
- * You can modify its configuration by adding an array to your application config under `components`
- * as it is shown in the following example:
- *
- * ~~~
- * 'urlManager' => [
- *     'enablePrettyUrl' => true,
- *     'rules' => [
- *         // your rules go here
- *     ],
- *     // ...
- * ]
- * ~~~
- *
- * @property string $baseUrl The base URL that is used by [[createUrl()]] to prepend URLs it creates.
- * @property string $hostInfo The host info (e.g. "http://www.example.com") that is used by
- * [[createAbsoluteUrl()]] to prepend URLs it creates.
- *
- * @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 boolean whether to enable strict parsing. If strict parsing is enabled, the incoming
-	 * requested URL must match at least one of the [[rules]] in order to be treated as a valid request.
-	 * Otherwise, the path info part of the request will be treated as the requested route.
-	 * This property is used only when [[enablePrettyUrl]] is true.
-	 */
-	public $enableStrictParsing = 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 array for creating a single URL rule. The configuration will
-	 * be merged with [[ruleConfig]] first before it is used for creating the rule object.
-	 *
-	 * A special shortcut format can be used if a rule only specifies [[UrlRule::pattern|pattern]]
-	 * and [[UrlRule::route|route]]: `'pattern' => 'route'`. That is, instead of using a configuration
-	 * array, one can use the key to represent the pattern and the value the corresponding route.
-	 * For example, `'post/<id:\d+>' => 'post/view'`.
-	 *
-	 * For RESTful routing the mentioned shortcut format also allows you to specify the
-	 * [[UrlRule::verb|HTTP verb]] that the rule should apply for.
-	 * You can do that  by prepending it to the pattern, separated by space.
-	 * For example, `'PUT post/<id:\d+>' => 'post/update'`.
-	 * You may specify multiple verbs by separating them with comma
-	 * like this: `'POST,PUT post/index' => 'post/create'`.
-	 * The supported verbs in the shortcut format are: GET, HEAD, POST, PUT, PATCH and DELETE.
-	 * Note that [[UrlRule::mode|mode]] will be set to PARSING_ONLY when specifying verb in this way
-	 * so you normally would not specify a verb for normal GET request.
-	 *
-	 * Here is an example configuration for RESTful CRUD controller:
-	 *
-	 * ~~~php
-	 * [
-	 *     'dashboard' => 'site/index',
-	 *
-	 *     'POST <controller:\w+>s' => '<controller>/create',
-	 *     '<controller:\w+>s' => '<controller>/index',
-	 *
-	 *     'PUT <controller:\w+>/<id:\d+>'    => '<controller>/update',
-	 *     'DELETE <controller:\w+>/<id:\d+>' => '<controller>/delete',
-	 *     '<controller:\w+>/<id:\d+>'        => '<controller>/view',
-	 * ];
-	 * ~~~
-	 *
-	 * Note that if you modify this property after the UrlManager object is created, make sure
-	 * you populate the array with rule objects instead of rule configurations.
-	 */
-	public $rules = [];
-	/**
-	 * @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 array the default configuration of URL rules. Individual rule configurations
-	 * specified via [[rules]] will take precedence when the same property of the rule is configured.
-	 */
-	public $ruleConfig = ['class' => 'yii\web\UrlRule'];
-
-	private $_baseUrl;
-	private $_hostInfo;
-
-	/**
-	 * Initializes UrlManager.
-	 */
-	public function init()
-	{
-		parent::init();
-		$this->compileRules();
-	}
-
-	/**
-	 * Parses the URL rules.
-	 */
-	protected function compileRules()
-	{
-		if (!$this->enablePrettyUrl || empty($this->rules)) {
-			return;
-		}
-		if (is_string($this->cache)) {
-			$this->cache = Yii::$app->getComponent($this->cache);
-		}
-		if ($this->cache instanceof Cache) {
-			$key = __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;
-			}
-		}
-
-		$rules = [];
-		foreach ($this->rules as $key => $rule) {
-			if (!is_array($rule)) {
-				$rule = ['route' => $rule];
-				if (preg_match('/^((?:(GET|HEAD|POST|PUT|PATCH|DELETE),)*(GET|HEAD|POST|PUT|PATCH|DELETE))\s+(.*)$/', $key, $matches)) {
-					$rule['verb'] = explode(',', $matches[1]);
-					$rule['mode'] = UrlRule::PARSING_ONLY;
-					$key = $matches[4];
-				}
-				$rule['pattern'] = $key;
-			}
-			$rules[] = Yii::createObject(array_merge($this->ruleConfig, $rule));
-		}
-		$this->rules = $rules;
-
-		if (isset($key, $hash)) {
-			$this->cache->set($key, [$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->getPathInfo();
-			/** @var UrlRule $rule */
-			foreach ($this->rules as $rule) {
-				if (($result = $rule->parseRequest($this, $request)) !== false) {
-					Yii::trace("Request parsed with URL rule: {$rule->name}", __METHOD__);
-					return $result;
-				}
-			}
-
-			if ($this->enableStrictParsing) {
-				return false;
-			}
-
-			Yii::trace('No matching URL rules. Using default URL parsing logic.', __METHOD__);
-
-			$suffix = (string)$this->suffix;
-			if ($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 [$pathInfo, []];
-		} else {
-			Yii::trace('Pretty URL not enabled. Using default URL parsing logic.', __METHOD__);
-			$route = $request->get($this->routeVar);
-			if (is_array($route)) {
-				$route = '';
-			}
-			return [(string)$route, []];
-		}
-	}
-
-	/**
-	 * 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 = [])
-	{
-		$anchor = isset($params['#']) ? '#' . $params['#'] : '';
-		unset($params['#'], $params[$this->routeVar]);
-
-		$route = trim($route, '/');
-		$baseUrl = $this->getBaseUrl();
-
-		if ($this->enablePrettyUrl) {
-			/** @var UrlRule $rule */
-			foreach ($this->rules as $rule) {
-				if (($url = $rule->createUrl($this, $route, $params)) !== false) {
-					if ($rule->host !== null) {
-						if ($baseUrl !== '' && ($pos = strpos($url, '/', 8)) !== false) {
-							return substr($url, 0, $pos) . $baseUrl . substr($url, $pos);
-						} else {
-							return $url . $baseUrl . $anchor;
-						}
-					} else {
-						return "$baseUrl/{$url}{$anchor}";
-					}
-				}
-			}
-
-			if ($this->suffix !== null) {
-				$route .= $this->suffix;
-			}
-			if (!empty($params)) {
-				$route .= '?' . http_build_query($params);
-			}
-			return "$baseUrl/{$route}{$anchor}";
-		} else {
-			$url = "$baseUrl?{$this->routeVar}=$route";
-			if (!empty($params)) {
-				$url .= '&' . http_build_query($params);
-			}
-			return $url . $anchor;
-		}
-	}
-
-	/**
-	 * 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 = [])
-	{
-		$url = $this->createUrl($route, $params);
-		if (strpos($url, '://') !== false) {
-			return $url;
-		} else {
-			return $this->getHostInfo() . $url;
-		}
-	}
-
-	/**
-	 * 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 \yii\web\Request $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 = rtrim($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/yii/web/UrlRule.php b/framework/yii/web/UrlRule.php
deleted file mode 100644
index 2934b26..0000000
--- a/framework/yii/web/UrlRule.php
+++ /dev/null
@@ -1,326 +0,0 @@
-<?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 by [[UrlManager]] for parsing and generating URLs.
- *
- * To define your own URL parsing and creation logic you can extend from this class
- * and add it to [[UrlManager::rules]] like this:
- *
- * ~~~
- * 'rules' => [
- *     ['class' => 'MyUrlRule', 'pattern' => '...', 'route' => 'site/index', ...],
- *     // ...
- * ]
- * ~~~
- *
- * @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 the name of this rule. If not set, it will use [[pattern]] as the name.
-	 */
-	public $name;
-	/**
-	 * @var string the pattern used to parse and create the path info part of a URL.
-	 * @see host
-	 */
-	public $pattern;
-	/**
-	 * @var string the pattern used to parse and create the host info part of a URL.
-	 * @see pattern
-	 */
-	public $host;
-	/**
-	 * @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 = [];
-	/**
-	 * @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 = [];
-	/**
-	 * @var array list of parameters used in the route.
-	 */
-	private $_routeParams = [];
-
-	/**
-	 * 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 = [strtoupper($this->verb)];
-			}
-		}
-		if ($this->name === null) {
-			$this->name = $this->pattern;
-		}
-
-		$this->pattern = trim($this->pattern, '/');
-
-		if ($this->host !== null) {
-			$this->pattern = rtrim($this->host, '/') . rtrim('/' . $this->pattern, '/') . '/';
-		} elseif ($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 = [];
-		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 ($offset > 1 && $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 (!empty($this->_routeParams)) {
-			$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->getMethod(), $this->verb, true)) {
-			return false;
-		}
-
-		$pathInfo = $request->getPathInfo();
-		$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;
-				}
-			} else {
-				return false;
-			}
-		}
-
-		if ($this->host !== null) {
-			$pathInfo = strtolower($request->getHostInfo()) . '/' . $pathInfo;
-		}
-
-		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 = [];
-		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 [$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 = [];
-
-		// 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]) && !is_array($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 ($this->host !== null) {
-			$pos = strpos($url, '/', 8);
-			if ($pos !== false) {
-				$url = substr($url, 0, $pos) . preg_replace('#/+#', '/', substr($url, $pos));
-			}
-		} elseif (strpos($url, '//') !== false) {
-			$url = preg_replace('#/+#', '/', $url);
-		}
-
-		if ($url !== '') {
-			$url .= ($this->suffix === null ? $manager->suffix : $this->suffix);
-		}
-
-		if (!empty($params)) {
-			$url .= '?' . http_build_query($params);
-		}
-		return $url;
-	}
-}
diff --git a/framework/yii/web/UserEvent.php b/framework/yii/web/UserEvent.php
deleted file mode 100644
index 8577ef5..0000000
--- a/framework/yii/web/UserEvent.php
+++ /dev/null
@@ -1,35 +0,0 @@
-<?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;
-
-/**
- * This event class is used for Events triggered by the [[User]] class.
- *
- * @author Qiang Xue <qiang.xue@gmail.com>
- * @since 2.0
- */
-class UserEvent extends Event
-{
-	/**
-	 * @var IdentityInterface 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 $cookieBased;
-	/**
-	 * @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 = true;
-}
diff --git a/framework/yii/web/VerbFilter.php b/framework/yii/web/VerbFilter.php
deleted file mode 100644
index 1ca08c2..0000000
--- a/framework/yii/web/VerbFilter.php
+++ /dev/null
@@ -1,108 +0,0 @@
-<?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\ActionEvent;
-use yii\base\Behavior;
-
-/**
- * VerbFilter is an action filter that filters by HTTP request methods.
- *
- * It allows to define allowed HTTP request methods for each action and will throw
- * an HTTP 405 error when the method is not allowed.
- *
- * To use VerbFilter, declare it in the `behaviors()` method of your controller class.
- * For example, the following declarations will define a typical set of allowed
- * request methods for REST CRUD actions.
- *
- * ~~~
- * public function behaviors()
- * {
- *     return [
- *         'verbs' => [
- *             'class' => \yii\web\VerbFilter::className(),
- *             'actions' => [
- *                 'index'  => ['get'],
- *                 'view'   => ['get'],
- *                 'create' => ['get', 'post'],
- *                 'update' => ['get', 'put', 'post'],
- *                 'delete' => ['post', 'delete'],
- *             ],
- *         ],
- *     ];
- * }
- * ~~~
- *
- * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.7
- * @author Carsten Brandt <mail@cebe.cc>
- * @since 2.0
- */
-class VerbFilter extends Behavior
-{
-	/**
-	 * @var array this property defines the allowed request methods for each action.
-	 * For each action that should only support limited set of request methods
-	 * you add an entry with the action id as array key and an array of
-	 * allowed methods (e.g. GET, HEAD, PUT) as the value.
-	 * If an action is not listed all request methods are considered allowed.
-	 *
-	 * You can use '*' to stand for all actions. When an action is explicitly
-	 * specified, it takes precedence over the specification given by '*'.
-	 *
-	 * For example,
-	 *
-	 * ~~~
-	 * [
-	 *   'create' => ['get', 'post'],
-	 *   'update' => ['get', 'put', 'post'],
-	 *   'delete' => ['post', 'delete'],
-	 *   '*' => ['get'],
-	 * ]
-	 * ~~~
-	 */
-	public $actions = [];
-
-
-	/**
-	 * 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 [Controller::EVENT_BEFORE_ACTION => 'beforeAction'];
-	}
-
-	/**
-	 * @param ActionEvent $event
-	 * @return boolean
-	 * @throws HttpException when the request method is not allowed.
-	 */
-	public function beforeAction($event)
-	{
-		$action = $event->action->id;
-		if (isset($this->actions[$action])) {
-			$verbs = $this->actions[$action];
-		} elseif (isset($this->actions['*'])) {
-			$verbs = $this->actions['*'];
-		} else {
-			return $event->isValid;
-		}
-
-		$verb = Yii::$app->getRequest()->getMethod();
-		$allowed = array_map('strtoupper', $verbs);
-		if (!in_array($verb, array_map('strtoupper', $verbs))) {
-			$event->isValid = false;
-			// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.7
-			Yii::$app->getResponse()->getHeaders()->set('Allow', implode(', ', $allowed));
-			throw new MethodNotAllowedHttpException('Method Not Allowed. This url can only handle the following request methods: ' . implode(', ', $allowed) . '.');
-		}
-
-		return $event->isValid;
-	}
-}
diff --git a/framework/yii/web/XmlResponseFormatter.php b/framework/yii/web/XmlResponseFormatter.php
deleted file mode 100644
index 292424a..0000000
--- a/framework/yii/web/XmlResponseFormatter.php
+++ /dev/null
@@ -1,98 +0,0 @@
-<?php
-/**
- * @link http://www.yiiframework.com/
- * @copyright Copyright (c) 2008 Yii Software LLC
- * @license http://www.yiiframework.com/license/
- */
-
-namespace yii\web;
-
-use DOMDocument;
-use DOMElement;
-use DOMText;
-use yii\base\Arrayable;
-use yii\base\Component;
-use yii\helpers\StringHelper;
-
-/**
- * XmlResponseFormatter formats the given data into an XML response content.
- *
- * It is used by [[Response]] to format response data.
- *
- * @author Qiang Xue <qiang.xue@gmail.com>
- * @since 2.0
- */
-class XmlResponseFormatter extends Component implements ResponseFormatterInterface
-{
-	/**
-	 * @var string the Content-Type header for the response
-	 */
-	public $contentType = 'application/xml';
-	/**
-	 * @var string the XML version
-	 */
-	public $version = '1.0';
-	/**
-	 * @var string the XML encoding. If not set, it will use the value of [[Response::charset]].
-	 */
-	public $encoding;
-	/**
-	 * @var string the name of the root element.
-	 */
-	public $rootTag = 'response';
-	/**
-	 * @var string the name of the elements that represent the array elements with numeric keys.
-	 */
-	public $itemTag = 'item';
-
-	/**
-	 * Formats the specified response.
-	 * @param Response $response the response to be formatted.
-	 */
-	public function format($response)
-	{
-		$response->getHeaders()->set('Content-Type', $this->contentType);
-		$dom = new DOMDocument($this->version, $this->encoding === null ? $response->charset : $this->encoding);
-		$root = new DOMElement($this->rootTag);
-		$dom->appendChild($root);
-		$this->buildXml($root, $response->data);
-		$response->content = $dom->saveXML();
-	}
-
-	/**
-	 * @param DOMElement $element
-	 * @param mixed $data
-	 */
-	protected function buildXml($element, $data)
-	{
-		if (is_object($data)) {
-			$child = new DOMElement(StringHelper::basename(get_class($data)));
-			$element->appendChild($child);
-			if ($data instanceof Arrayable) {
-				$this->buildXml($child, $data->toArray());
-			} else {
-				$array = [];
-				foreach ($data as $name => $value) {
-					$array[$name] = $value;
-				}
-				$this->buildXml($child, $array);
-			}
-		} elseif (is_array($data)) {
-			foreach ($data as $name => $value) {
-				if (is_int($name) && is_object($value)) {
-					$this->buildXml($element, $value);
-				} elseif (is_array($value) || is_object($value)) {
-					$child = new DOMElement(is_int($name) ? $this->itemTag : $name);
-					$element->appendChild($child);
-					$this->buildXml($child, $value);
-				} else {
-					$child = new DOMElement(is_int($name) ? $this->itemTag : $name);
-					$element->appendChild($child);
-					$child->appendChild(new DOMText((string)$value));
-				}
-			}
-		} else {
-			$element->appendChild(new DOMText((string)$data));
-		}
-	}
-}
diff --git a/framework/yii/widgets/ActiveForm.php b/framework/yii/widgets/ActiveForm.php
deleted file mode 100644
index b218a2e..0000000
--- a/framework/yii/widgets/ActiveForm.php
+++ /dev/null
@@ -1,357 +0,0 @@
-<?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\Model;
-use yii\helpers\Html;
-use yii\helpers\Json;
-use yii\web\JsExpression;
-
-/**
- * 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 array the HTML attributes (name-value pairs) for the form tag.
-	 * The values will be HTML-encoded using [[Html::encode()]].
-	 * If a value is null, the corresponding attribute will not be rendered.
-	 */
-	public $options = [];
-	/**
-	 * @var array the default configuration used by [[field()]] when creating a new field object.
-	 */
-	public $fieldConfig;
-	/**
-	 * @var string the default CSS class for the error summary container.
-	 * @see errorSummary()
-	 */
-	public $errorSummaryCssClass = 'error-summary';
-	/**
-	 * @var string the CSS class that is added to a field container when the associated attribute is required.
-	 */
-	public $requiredCssClass = 'required';
-	/**
-	 * @var string the CSS class that is added to a field container when the associated attribute has validation error.
-	 */
-	public $errorCssClass = 'has-error';
-	/**
-	 * @var string the CSS class that is added to a field container when the associated attribute is successfully validated.
-	 */
-	public $successCssClass = 'has-success';
-	/**
-	 * @var string the CSS class that is added to a field container when the associated attribute is being validated.
-	 */
-	public $validatingCssClass = 'validating';
-	/**
-	 * @var boolean whether to enable client-side data validation.
-	 * If [[ActiveField::enableClientValidation]] is set, its value will take precedence for that input field.
-	 */
-	public $enableClientValidation = true;
-	/**
-	 * @var boolean whether to enable AJAX-based data validation.
-	 * If [[ActiveField::enableAjaxValidation]] is set, its value will take precedence for that input field.
-	 */
-	public $enableAjaxValidation = false;
-	/**
-	 * @var array|string the URL for performing AJAX-based validation. This property will be processed by
-	 * [[Html::url()]]. Please refer to [[Html::url()]] for more details on how to configure this property.
-	 * If this property is not set, it will take the value of the form's action attribute.
-	 */
-	public $validationUrl;
-	/**
-	 * @var boolean whether to perform validation when the form is submitted.
-	 */
-	public $validateOnSubmit = true;
-	/**
-	 * @var boolean whether to perform validation when an input field loses focus and its value is found changed.
-	 * If [[ActiveField::validateOnChange]] is set, its value will take precedence for that input field.
-	 */
-	public $validateOnChange = true;
-	/**
-	 * @var boolean whether to perform validation while the user is typing in an input field.
-	 * If [[ActiveField::validateOnType]] is set, its value will take precedence for that input field.
-	 * @see validationDelay
-	 */
-	public $validateOnType = false;
-	/**
-	 * @var integer number of milliseconds that the validation should be delayed when an input field
-	 * is changed or the user types in the field.
-	 * If [[ActiveField::validationDelay]] is set, its value will take precedence for that input field.
-	 */
-	public $validationDelay = 200;
-	/**
-	 * @var string the name of the GET parameter indicating the validation request is an AJAX request.
-	 */
-	public $ajaxVar = 'ajax';
-	/**
-	 * @var string|JsExpression a JS callback that will be called when the form is being submitted.
-	 * The signature of the callback should be:
-	 *
-	 * ~~~
-	 * function ($form) {
-	 *     ...return false to cancel submission...
-	 * }
-	 * ~~~
-	 */
-	public $beforeSubmit;
-	/**
-	 * @var string|JsExpression a JS callback that is called before validating an attribute.
-	 * The signature of the callback should be:
-	 *
-	 * ~~~
-	 * function ($form, attribute, messages) {
-	 *     ...return false to cancel the validation...
-	 * }
-	 * ~~~
-	 */
-	public $beforeValidate;
-	/**
-	 * @var string|JsExpression a JS callback that is called after validating an attribute.
-	 * The signature of the callback should be:
-	 *
-	 * ~~~
-	 * function ($form, attribute, messages) {
-	 * }
-	 * ~~~
-	 */
-	public $afterValidate;
-	/**
-	 * @var array the client validation options for individual attributes. Each element of the array
-	 * represents the validation options for a particular attribute.
-	 * @internal
-	 */
-	public $attributes = [];
-
-	/**
-	 * Initializes the widget.
-	 * This renders the form open tag.
-	 */
-	public function init()
-	{
-		if (!isset($this->options['id'])) {
-			$this->options['id'] = $this->getId();
-		}
-		if (!isset($this->fieldConfig['class'])) {
-			$this->fieldConfig['class'] = ActiveField::className();
-		}
-		echo Html::beginForm($this->action, $this->method, $this->options);
-	}
-
-	/**
-	 * Runs the widget.
-	 * This registers the necessary javascript code and renders the form close tag.
-	 */
-	public function run()
-	{
-		if (!empty($this->attributes)) {
-			$id = $this->options['id'];
-			$options = Json::encode($this->getClientOptions());
-			$attributes = Json::encode($this->attributes);
-			$view = $this->getView();
-			ActiveFormAsset::register($view);
-			$view->registerJs("jQuery('#$id').yiiActiveForm($attributes, $options);");
-		}
-		echo Html::endForm();
-	}
-
-	/**
-	 * Returns the options for the form JS widget.
-	 * @return array the options
-	 */
-	protected function getClientOptions()
-	{
-		$options = [
-			'errorSummary' => '.' . $this->errorSummaryCssClass,
-			'validateOnSubmit' => $this->validateOnSubmit,
-			'errorCssClass' => $this->errorCssClass,
-			'successCssClass' => $this->successCssClass,
-			'validatingCssClass' => $this->validatingCssClass,
-			'ajaxVar' => $this->ajaxVar,
-		];
-		if ($this->validationUrl !== null) {
-			$options['validationUrl'] = Html::url($this->validationUrl);
-		}
-		foreach (['beforeSubmit', 'beforeValidate', 'afterValidate'] as $name) {
-			if (($value = $this->$name) !== null) {
-				$options[$name] = $value instanceof JsExpression ? $value : new JsExpression($value);
-			}
-		}
-		return $options;
-	}
-
-	/**
-	 * Generates a summary of the validation errors.
-	 * If there is no validation error, an empty error summary markup will still be generated, but it will be hidden.
-	 * @param Model|Model[] $models the model(s) associated with this form
-	 * @param array $options the tag options in terms of name-value pairs. The following options are specially handled:
-	 *
-	 * - header: string, the header HTML for the error summary. If not set, a default prompt string will be used.
-	 * - footer: string, the footer HTML for the error summary.
-	 *
-	 * The rest of the options will be rendered as the attributes of the container 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 error summary
-	 */
-	public function errorSummary($models, $options = [])
-	{
-		if (!is_array($models)) {
-			$models = [$models];
-		}
-
-		$lines = [];
-		foreach ($models as $model) {
-			/** @var Model $model */
-			foreach ($model->getFirstErrors() as $error) {
-				$lines[] = Html::encode($error);
-			}
-		}
-
-		$header = isset($options['header']) ? $options['header'] : '<p>' . Yii::t('yii', 'Please fix the following errors:') . '</p>';
-		$footer = isset($options['footer']) ? $options['footer'] : '';
-		unset($options['header'], $options['footer']);
-
-		if (!isset($options['class'])) {
-			$options['class'] = $this->errorSummaryCssClass;
-		} else {
-			$options['class'] .= ' ' . $this->errorSummaryCssClass;
-		}
-
-		if (!empty($lines)) {
-			$content = "<ul><li>" . implode("</li>\n<li>", $lines) . "</li><ul>";
-			return Html::tag('div', $header . $content . $footer, $options);
-		} else {
-			$content = "<ul></ul>";
-			$options['style'] = isset($options['style']) ? rtrim($options['style'], ';') . '; display:none' : 'display:none';
-			return Html::tag('div', $header . $content . $footer, $options);
-		}
-	}
-
-	/**
-	 * Generates a form field.
-	 * A form field is associated with a model and an attribute. It contains a label, an input and an error message
-	 * and use them to interact with end users to collect their inputs for the attribute.
-	 * @param Model $model the data model
-	 * @param string $attribute the attribute name or expression. See [[Html::getAttributeName()]] for the format
-	 * about attribute expression.
-	 * @param array $options the additional configurations for the field object
-	 * @return ActiveField the created ActiveField object
-	 * @see fieldConfig
-	 */
-	public function field($model, $attribute, $options = [])
-	{
-		return Yii::createObject(array_merge($this->fieldConfig, $options, [
-			'model' => $model,
-			'attribute' => $attribute,
-			'form' => $this,
-		]));
-	}
-
-	/**
-	 * Validates one or several models and returns an error message array indexed by the attribute IDs.
-	 * This is a helper method that simplifies the way of writing AJAX validation code.
-	 *
-	 * For example, you may use the following code in a controller action to respond
-	 * to an AJAX validation request:
-	 *
-	 * ~~~
-	 * $model = new Post;
-	 * $model->load($_POST);
-	 * if (Yii::$app->request->isAjax) {
-	 *     Yii::$app->response->format = Response::FORMAT_JSON;
-	 *     return ActiveForm::validate($model);
-	 * }
-	 * // ... respond to non-AJAX request ...
-	 * ~~~
-	 *
-	 * To validate multiple models, simply pass each model as a parameter to this method, like
-	 * the following:
-	 *
-	 * ~~~
-	 * ActiveForm::validate($model1, $model2, ...);
-	 * ~~~
-	 *
-	 * @param Model $model the model to be validated
-	 * @param mixed $attributes list of attributes that should be validated.
-	 * If this parameter is empty, it means any attribute listed in the applicable
-	 * validation rules should be validated.
-	 *
-	 * When this method is used to validate multiple models, this parameter will be interpreted
-	 * as a model.
-	 *
-	 * @return array the error message array indexed by the attribute IDs.
-	 */
-	public static function validate($model, $attributes = null)
-	{
-		$result = [];
-		if ($attributes instanceof Model) {
-			// validating multiple models
-			$models = func_get_args();
-			$attributes = null;
-		} else {
-			$models = [$model];
-		}
-		/** @var Model $model */
-		foreach ($models as $model) {
-			$model->validate($attributes);
-			foreach ($model->getErrors() as $attribute => $errors) {
-				$result[Html::getInputId($model, $attribute)] = $errors;
-			}
-		}
-		return $result;
-	}
-
-	/**
-	 * Validates an array of model instances and returns an error message array indexed by the attribute IDs.
-	 * This is a helper method that simplifies the way of writing AJAX validation code for tabular input.
-	 *
-	 * For example, you may use the following code in a controller action to respond
-	 * to an AJAX validation request:
-	 *
-	 * ~~~
-	 * // ... load $models ...
-	 * if (Yii::$app->request->isAjax) {
-	 *     Yii::$app->response->format = Response::FORMAT_JSON;
-	 *     return ActiveForm::validateMultiple($models);
-	 * }
-	 * // ... respond to non-AJAX request ...
-	 * ~~~
-	 *
-	 * @param array $models an array of models to be validated.
-	 * @param mixed $attributes list of attributes that should be validated.
-	 * If this parameter is empty, it means any attribute listed in the applicable
-	 * validation rules should be validated.
-	 * @return array the error message array indexed by the attribute IDs.
-	 */
-	public static function validateMultiple($models, $attributes = null)
-	{
-		$result = [];
-		/** @var Model $model */
-		foreach ($models as $i => $model) {
-			$model->validate($attributes);
-			foreach ($model->getErrors() as $attribute => $errors) {
-				$result[Html::getInputId($model, "[$i]" . $attribute)] = $errors;
-			}
-		}
-		return $result;
-	}
-}
diff --git a/framework/yii/widgets/BaseListView.php b/framework/yii/widgets/BaseListView.php
deleted file mode 100644
index 4c4e5a4..0000000
--- a/framework/yii/widgets/BaseListView.php
+++ /dev/null
@@ -1,215 +0,0 @@
-<?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\helpers\ArrayHelper;
-use yii\helpers\Html;
-
-/**
- * @author Qiang Xue <qiang.xue@gmail.com>
- * @since 2.0
- */
-abstract class BaseListView extends Widget
-{
-	/**
-	 * @var array the HTML attributes for the container tag of the list view.
-	 * The "tag" element specifies the tag name of the container element and defaults to "div".
-	 */
-	public $options = [];
-	/**
-	 * @var \yii\data\DataProviderInterface the data provider for the view. This property is required.
-	 */
-	public $dataProvider;
-	/**
-	 * @var array the configuration for the pager widget. By default, [[LinkPager]] will be
-	 * used to render the pager. You can use a different widget class by configuring the "class" element.
-	 */
-	public $pager = [];
-	/**
-	 * @var array the configuration for the sorter widget. By default, [[LinkSorter]] will be
-	 * used to render the sorter. You can use a different widget class by configuring the "class" element.
-	 */
-	public $sorter = [];
-	/**
-	 * @var string the HTML content to be displayed as the summary of the list view.
-	 * If you do not want to show the summary, you may set it with an empty string.
-	 *
-	 * The following tokens will be replaced with the corresponding values:
-	 *
-	 * - `{begin}`: the starting row number (1-based) currently being displayed
-	 * - `{end}`: the ending row number (1-based) currently being displayed
-	 * - `{count}`: the number of rows currently being displayed
-	 * - `{totalCount}`: the total number of rows available
-	 * - `{page}`: the page number (1-based) current being displayed
-	 * - `{pageCount}`: the number of pages available
-	 */
-	public $summary;
-	/**
-	 * @var boolean whether to show the list view if [[dataProvider]] returns no data.
-	 */
-	public $showOnEmpty = false;
-	/**
-	 * @var string the HTML content to be displayed when [[dataProvider]] does not have any data.
-	 */
-	public $emptyText;
-	/**
-	 * @var string the layout that determines how different sections of the list view should be organized.
-	 * The following tokens will be replaced with the corresponding section contents:
-	 *
-	 * - `{summary}`: the summary section. See [[renderSummary()]].
-	 * - `{items}`: the list items. See [[renderItems()]].
-	 * - `{sorter}`: the sorter. See [[renderSorter()]].
-	 * - `{pager}`: the pager. See [[renderPager()]].
-	 */
-	public $layout = "{summary}\n{items}\n{pager}";
-
-
-	/**
-	 * Renders the data models.
-	 * @return string the rendering result.
-	 */
-	abstract public function renderItems();
-
-	/**
-	 * Initializes the view.
-	 */
-	public function init()
-	{
-		if ($this->dataProvider === null) {
-			throw new InvalidConfigException('The "dataProvider" property must be set.');
-		}
-		if ($this->emptyText === null) {
-			$this->emptyText = Yii::t('yii', 'No results found.');
-		}
-		$this->dataProvider->prepare();
-	}
-
-	/**
-	 * Runs the widget.
-	 */
-	public function run()
-	{
-		if ($this->dataProvider->getCount() > 0 || $this->showOnEmpty) {
-			$content = preg_replace_callback("/{\\w+}/", function ($matches) {
-				$content = $this->renderSection($matches[0]);
-				return $content === false ? $matches[0] : $content;
-			}, $this->layout);
-		} else {
-			$content = $this->renderEmpty();
-		}
-		$tag = ArrayHelper::remove($this->options, 'tag', 'div');
-		echo Html::tag($tag, $content, $this->options);
-	}
-
-	/**
-	 * Renders a section of the specified name.
-	 * If the named section is not supported, false will be returned.
-	 * @param string $name the section name, e.g., `{summary}`, `{items}`.
-	 * @return string|boolean the rendering result of the section, or false if the named section is not supported.
-	 */
-	public function renderSection($name)
-	{
-		switch ($name) {
-			case '{summary}':
-				return $this->renderSummary();
-			case '{items}':
-				return $this->renderItems();
-			case '{pager}':
-				return $this->renderPager();
-			case '{sorter}':
-				return $this->renderSorter();
-			default:
-				return false;
-		}
-	}
-
-	/**
-	 * Renders the HTML content indicating that the list view has no data.
-	 * @return string the rendering result
-	 * @see emptyText
-	 */
-	public function renderEmpty()
-	{
-		return '<div class="empty">' . ($this->emptyText === null ? Yii::t('yii', 'No results found.') : $this->emptyText) . '</div>';
-	}
-
-	/**
-	 * Renders the summary text.
-	 */
-	public function renderSummary()
-	{
-		$count = $this->dataProvider->getCount();
-		if ($count <= 0) {
-			return '';
-		}
-		if (($pagination = $this->dataProvider->getPagination()) !== false) {
-			$totalCount = $this->dataProvider->getTotalCount();
-			$begin = $pagination->getPage() * $pagination->pageSize + 1;
-			$end = $begin + $count - 1;
-			if ($begin > $end) {
-				$begin = $end;
-			}
-			$page = $pagination->getPage() + 1;
-			$pageCount = $pagination->pageCount;
-			if (($summaryContent = $this->summary) === null) {
-				$summaryContent = '<div class="summary">'
-					. Yii::t('yii', 'Showing <b>{begin, number}-{end, number}</b> of <b>{totalCount, number}</b> {totalCount, plural, one{item} other{items}}.')
-					. '</div>';
-			}
-		} else {
-			$begin = $page = $pageCount = 1;
-			$end = $totalCount = $count;
-			if (($summaryContent = $this->summary) === null) {
-				$summaryContent = '<div class="summary">' . Yii::t('yii', 'Total <b>{count, number}</b> {count, plural, one{item} other{items}}.') . '</div>';
-			}
-		}
-		return Yii::$app->getI18n()->format($summaryContent, [
-			'begin' => $begin,
-			'end' => $end,
-			'count' => $count,
-			'totalCount' => $totalCount,
-			'page' => $page,
-			'pageCount' => $pageCount,
-		], Yii::$app->language);
-	}
-
-	/**
-	 * Renders the pager.
-	 * @return string the rendering result
-	 */
-	public function renderPager()
-	{
-		$pagination = $this->dataProvider->getPagination();
-		if ($pagination === false || $this->dataProvider->getCount() <= 0) {
-			return '';
-		}
-		/** @var LinkPager $class */
-		$class = ArrayHelper::remove($this->pager, 'class', LinkPager::className());
-		$this->pager['pagination'] = $pagination;
-		return $class::widget($this->pager);
-	}
-
-	/**
-	 * Renders the sorter.
-	 * @return string the rendering result
-	 */
-	public function renderSorter()
-	{
-		$sort = $this->dataProvider->getSort();
-		if ($sort === false || empty($sort->attributes) || $this->dataProvider->getCount() <= 0) {
-			return '';
-		}
-		/** @var LinkSorter $class */
-		$class = ArrayHelper::remove($this->sorter, 'class', LinkSorter::className());
-		$this->sorter['sort'] = $sort;
-		return $class::widget($this->sorter);
-	}
-}
diff --git a/framework/yii/widgets/Block.php b/framework/yii/widgets/Block.php
deleted file mode 100644
index fdd210f..0000000
--- a/framework/yii/widgets/Block.php
+++ /dev/null
@@ -1,49 +0,0 @@
-<?php
-/**
- * @link http://www.yiiframework.com/
- * @copyright Copyright (c) 2008 Yii Software LLC
- * @license http://www.yiiframework.com/license/
- */
-
-namespace yii\widgets;
-
-use yii\base\Widget;
-
-/**
- * @author Qiang Xue <qiang.xue@gmail.com>
- * @since 2.0
- */
-class Block extends Widget
-{
-	/**
-	 * @var string the ID of this block.
-	 */
-	public $id;
-	/**
-	 * @var boolean whether to render the block content in place. Defaults to false,
-	 * meaning the captured block content will not be displayed.
-	 */
-	public $renderInPlace = false;
-
-	/**
-	 * Starts recording a block.
-	 */
-	public function init()
-	{
-		ob_start();
-		ob_implicit_flush(false);
-	}
-
-	/**
-	 * Ends recording a block.
-	 * This method stops output buffering and saves the rendering result as a named block in the controller.
-	 */
-	public function run()
-	{
-		$block = ob_get_clean();
-		if ($this->renderInPlace) {
-			echo $block;
-		}
-		$this->view->blocks[$this->id] = $block;
-	}
-}
diff --git a/framework/yii/widgets/Breadcrumbs.php b/framework/yii/widgets/Breadcrumbs.php
deleted file mode 100644
index 2353845..0000000
--- a/framework/yii/widgets/Breadcrumbs.php
+++ /dev/null
@@ -1,141 +0,0 @@
-<?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\InvalidConfigException;
-use yii\helpers\Html;
-
-/**
- * Breadcrumbs displays a list of links indicating the position of the current page in the whole site hierarchy.
- *
- * For example, breadcrumbs like "Home / Sample Post / Edit" means the user is viewing an edit page
- * for the "Sample Post". He can click on "Sample Post" to view that page, or he can click on "Home"
- * to return to the homepage.
- *
- * To use Breadcrumbs, you need to configure its [[links]] property, which specifies the links to be displayed. For example,
- *
- * ~~~
- * // $this is the view object currently being used
- * echo Breadcrumbs::widget([
- *     'links' => [
- *         ['label' => 'Sample Post', 'url' => ['post/edit', 'id' => 1]],
- *         'Edit',
- *     ],
- * ]);
- * ~~~
- *
- * Because breadcrumbs usually appears in nearly every page of a website, you may consider placing it in a layout view.
- * You can use a view parameter (e.g. `$this->params['breadcrumbs']`) to configure the links in different
- * views. In the layout view, you assign this view parameter to the [[links]] property like the following:
- *
- * ~~~
- * // $this is the view object currently being used
- * echo Breadcrumbs::widget([
- *     'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [],
- * ]);
- * ~~~
- *
- * @author Qiang Xue <qiang.xue@gmail.com>
- * @since 2.0
- */
-class Breadcrumbs extends Widget
-{
-	/**
-	 * @var string the name of the breadcrumb container tag.
-	 */
-	public $tag = 'ul';
-	/**
-	 * @var array the HTML attributes for the breadcrumb container tag.
-	 */
-	public $options = ['class' => 'breadcrumb'];
-	/**
-	 * @var boolean whether to HTML-encode the link labels.
-	 */
-	public $encodeLabels = true;
-	/**
-	 * @var string the first hyperlink in the breadcrumbs (called home link).
-	 * If this property is not set, it will default to a link pointing to [[\yii\web\Application::homeUrl]]
-	 * with the label 'Home'. If this property is false, the home link will not be rendered.
-	 */
-	public $homeLink;
-	/**
-	 * @var array list of links to appear in the breadcrumbs. If this property is empty,
-	 * the widget will not render anything. Each array element represents a single link in the breadcrumbs
-	 * with the following structure:
-	 *
-	 * ~~~
-	 * [
-	 *     'label' => 'label of the link',  // required
-	 *     'url' => 'url of the link',      // optional, will be processed by Html::url()
-	 * ]
-	 * ~~~
-	 *
-	 * If a link is active, you only need to specify its "label", and instead of writing `['label' => $label]`,
-	 * you should simply use `$label`.
-	 */
-	public $links = [];
-	/**
-	 * @var string the template used to render each inactive item in the breadcrumbs. The token `{link}`
-	 * will be replaced with the actual HTML link for each inactive item.
-	 */
-	public $itemTemplate = "<li>{link}</li>\n";
-	/**
-	 * @var string the template used to render each active item in the breadcrumbs. The token `{link}`
-	 * will be replaced with the actual HTML link for each active item.
-	 */
-	public $activeItemTemplate = "<li class=\"active\">{link}</li>\n";
-
-	/**
-	 * Renders the widget.
-	 */
-	public function run()
-	{
-		if (empty($this->links)) {
-			return;
-		}
-		$links = [];
-		if ($this->homeLink === null) {
-			$links[] = $this->renderItem([
-				'label' => Yii::t('yii', 'Home'),
-				'url' => Yii::$app->homeUrl,
-			], $this->itemTemplate);
-		} elseif ($this->homeLink !== false) {
-			$links[] = $this->renderItem($this->homeLink, $this->itemTemplate);
-		}
-		foreach ($this->links as $link) {
-			if (!is_array($link)) {
-				$link = ['label' => $link];
-			}
-			$links[] = $this->renderItem($link, isset($link['url']) ? $this->itemTemplate : $this->activeItemTemplate);
-		}
-		echo Html::tag($this->tag, implode('', $links), $this->options);
-	}
-
-	/**
-	 * Renders a single breadcrumb item.
-	 * @param array $link the link to be rendered. It must contain the "label" element. The "url" element is optional.
-	 * @param string $template the template to be used to rendered the link. The token "{link}" will be replaced by the link.
-	 * @return string the rendering result
-	 * @throws InvalidConfigException if `$link` does not have "label" element.
-	 */
-	protected function renderItem($link, $template)
-	{
-		if (isset($link['label'])) {
-			$label = $this->encodeLabels ? Html::encode($link['label']) : $link['label'];
-		} else {
-			throw new InvalidConfigException('The "label" element is required for each link.');
-		}
-		if (isset($link['url'])) {
-			return strtr($template, ['{link}' => Html::a($label, $link['url'])]);
-		} else {
-			return strtr($template, ['{link}' => $label]);
-		}
-	}
-}
diff --git a/framework/yii/widgets/ContentDecorator.php b/framework/yii/widgets/ContentDecorator.php
deleted file mode 100644
index 9224f35..0000000
--- a/framework/yii/widgets/ContentDecorator.php
+++ /dev/null
@@ -1,52 +0,0 @@
-<?php
-/**
- * @link http://www.yiiframework.com/
- * @copyright Copyright (c) 2008 Yii Software LLC
- * @license http://www.yiiframework.com/license/
- */
-
-namespace yii\widgets;
-
-use yii\base\InvalidConfigException;
-use yii\base\Widget;
-
-/**
- * @author Qiang Xue <qiang.xue@gmail.com>
- * @since 2.0
- */
-class ContentDecorator extends Widget
-{
-	/**
-	 * @var string the view file that will be used to decorate the content enclosed by this widget.
-	 * This can be specified as either the view file path or path alias.
-	 */
-	public $viewFile;
-	/**
-	 * @var array the parameters (name => value) to be extracted and made available in the decorative view.
-	 */
-	public $params = [];
-
-	/**
-	 * Starts recording a clip.
-	 */
-	public function init()
-	{
-		if ($this->viewFile === null) {
-			throw new InvalidConfigException('ContentDecorator::viewFile 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();
-		// render under the existing context
-		echo $this->view->renderFile($this->viewFile, $params);
-	}
-}
diff --git a/framework/yii/widgets/DetailView.php b/framework/yii/widgets/DetailView.php
deleted file mode 100644
index 3a45a4e..0000000
--- a/framework/yii/widgets/DetailView.php
+++ /dev/null
@@ -1,211 +0,0 @@
-<?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\Arrayable;
-use yii\base\Formatter;
-use yii\base\InvalidConfigException;
-use yii\base\Model;
-use yii\base\Widget;
-use yii\helpers\ArrayHelper;
-use yii\helpers\Html;
-use yii\helpers\Inflector;
-
-/**
- * DetailView displays the detail of a single data [[model]].
- *
- * DetailView is best used for displaying a model in a regular format (e.g. each model attribute
- * is displayed as a row in a table.) The model can be either an instance of [[Model]]
- * or an associative array.
- *
- * DetailView uses the [[attributes]] property to determines which model attributes
- * should be displayed and how they should be formatted.
- *
- * A typical usage of DetailView is as follows:
- *
- * ~~~
- * echo DetailView::widget([
- *     'model' => $model,
- *     'attributes' => [
- *         'title',             // title attribute (in plain text)
- *         'description:html',  // description attribute in HTML
- *         [                    // the owner name of the model
- *             'label' => 'Owner',
- *             'value' => $model->owner->name,
- *         ],
- *     ],
- * ]);
- * ~~~
- *
- * @author Qiang Xue <qiang.xue@gmail.com>
- * @since 2.0
- */
-class DetailView extends Widget
-{
-	/**
-	 * @var array|object the data model whose details are to be displayed. This can be either a [[Model]] instance
-	 * or an associative array.
-	 */
-	public $model;
-	/**
-	 * @var array a list of attributes to be displayed in the detail view. Each array element
-	 * represents the specification for displaying one particular attribute.
-	 *
-	 * An attribute can be specified as a string in the format of "Name" or "Name:Format", where "Name" refers to
-	 * the attribute name, and "Format" represents the format of the attribute. The "Format" is passed to the [[Formatter::format()]]
-	 * method to format an attribute value into a displayable text. Please refer to [[Formatter]] for the supported types.
-	 *
-	 * An attribute can also be specified in terms of an array with the following elements:
-	 *
-	 * - name: the attribute name. This is required if either "label" or "value" is not specified.
-	 * - label: the label associated with the attribute. If this is not specified, it will be generated from the attribute name.
-	 * - value: the value to be displayed. If this is not specified, it will be retrieved from [[model]] using the attribute name
-	 *   by calling [[ArrayHelper::getValue()]]. Note that this value will be formatted into a displayable text
-	 *   according to the "format" option.
-	 * - format: the type of the value that determines how the value would be formatted into a displayable text.
-	 *   Please refer to [[Formatter]] for supported types.
-	 * - visible: whether the attribute is visible. If set to `false`, the attribute will NOT be displayed.
-	 */
-	public $attributes;
-	/**
-	 * @var string|callback the template used to render a single attribute. If a string, the token `{label}`
-	 * and `{value}` will be replaced with the label and the value of the corresponding attribute.
-	 * If a callback (e.g. an anonymous function), the signature must be as follows:
-	 *
-	 * ~~~
-	 * function ($attribute, $index, $widget)
-	 * ~~~
-	 *
-	 * where `$attribute` refer to the specification of the attribute being rendered, `$index` is the zero-based
-	 * index of the attribute in the [[attributes]] array, and `$widget` refers to this widget instance.
-	 */
-	public $template = "<tr><th>{label}</th><td>{value}</td></tr>";
-	/**
-	 * @var array the HTML attributes for the container tag of this widget. The "tag" option specifies
-	 * what container tag should be used. It defaults to "table" if not set.
-	 */
-	public $options = ['class' => 'table table-striped table-bordered detail-view'];
-	/**
-	 * @var array|Formatter the formatter used to format model attribute values into displayable texts.
-	 * This can be either an instance of [[Formatter]] or an configuration array for creating the [[Formatter]]
-	 * instance. If this property is not set, the "formatter" application component will be used.
-	 */
-	public $formatter;
-
-	/**
-	 * Initializes the detail view.
-	 * This method will initialize required property values.
-	 */
-	public function init()
-	{
-		if ($this->model === null) {
-			throw new InvalidConfigException('Please specify the "model" property.');
-		}
-		if ($this->formatter == null) {
-			$this->formatter = Yii::$app->getFormatter();
-		} elseif (is_array($this->formatter)) {
-			$this->formatter = Yii::createObject($this->formatter);
-		}
-		if (!$this->formatter instanceof Formatter) {
-			throw new InvalidConfigException('The "formatter" property must be either a Format object or a configuration array.');
-		}
-		$this->normalizeAttributes();
-	}
-
-	/**
-	 * Renders the detail view.
-	 * This is the main entry of the whole detail view rendering.
-	 */
-	public function run()
-	{
-		$rows = [];
-		$i = 0;
-		foreach ($this->attributes as $attribute) {
-			$rows[] = $this->renderAttribute($attribute, $i++);
-		}
-
-		$tag = ArrayHelper::remove($this->options, 'tag', 'table');
-		echo Html::tag($tag, implode("\n", $rows), $this->options);
-	}
-
-	/**
-	 * Renders a single attribute.
-	 * @param array $attribute the specification of the attribute to be rendered.
-	 * @param integer $index the zero-based index of the attribute in the [[attributes]] array
-	 * @return string the rendering result
-	 */
-	protected function renderAttribute($attribute, $index)
-	{
-		if (is_string($this->template)) {
-			return strtr($this->template, [
-				'{label}' => $attribute['label'],
-				'{value}' => $this->formatter->format($attribute['value'], $attribute['format']),
-			]);
-		} else {
-			return call_user_func($this->template, $attribute, $index, $this);
-		}
-	}
-
-	/**
-	 * Normalizes the attribute specifications.
-	 * @throws InvalidConfigException
-	 */
-	protected function normalizeAttributes()
-	{
-		if ($this->attributes === null) {
-			if ($this->model instanceof Model) {
-				$this->attributes = $this->model->attributes();
-			} elseif (is_object($this->model)) {
-				$this->attributes = $this->model instanceof Arrayable ? $this->model->toArray() : array_keys(get_object_vars($this->model));
-			} elseif (is_array($this->model)) {
-				$this->attributes = array_keys($this->model);
-			} else {
-				throw new InvalidConfigException('The "model" property must be either an array or an object.');
-			}
-			sort($this->attributes);
-		}
-
-		foreach ($this->attributes as $i => $attribute) {
-			if (is_string($attribute)) {
-				if (!preg_match('/^(\w+)(\s*:\s*(\w+))?$/', $attribute, $matches)) {
-					throw new InvalidConfigException('The attribute must be specified in the format of "Name" or "Name:Format"');
-				}
-				$attribute = [
-					'name' => $matches[1],
-					'format' => isset($matches[3]) ? $matches[3] : 'text',
-				];
-			}
-
-			if (!is_array($attribute)) {
-				throw new InvalidConfigException('The attribute configuration must be an array.');
-			}
-
-			if (isset($attribute['visible']) && !$attribute['visible']) {
-				continue;
-			}
-
-			if (!isset($attribute['format'])) {
-				$attribute['format'] = 'text';
-			}
-			if (isset($attribute['name'])) {
-				$name = $attribute['name'];
-				if (!isset($attribute['label'])) {
-					$attribute['label'] = $this->model instanceof Model ? $this->model->getAttributeLabel($name) : Inflector::camel2words($name, true);
-				}
-				if (!array_key_exists('value', $attribute)) {
-					$attribute['value'] = ArrayHelper::getValue($this->model, $name);
-				}
-			} elseif (!isset($attribute['label']) || !array_key_exists('value', $attribute)) {
-				throw new InvalidConfigException('The attribute configuration requires the "name" element to determine the value and display label.');
-			}
-
-			$this->attributes[$i] = $attribute;
-		}
-	}
-}
diff --git a/framework/yii/widgets/FragmentCache.php b/framework/yii/widgets/FragmentCache.php
deleted file mode 100644
index 57c4659..0000000
--- a/framework/yii/widgets/FragmentCache.php
+++ /dev/null
@@ -1,178 +0,0 @@
-<?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\caching\Cache;
-use yii\caching\Dependency;
-
-/**
- *
- * @property string|boolean $cachedContent The cached content. False is returned if valid content is not found
- * in the cache. This property is read-only.
- *
- * @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,
-	 *
-	 * ~~~
-	 * [
-	 *     '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:
-	 *
-	 * ~~~
-	 * [
-	 *     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 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->enabled) {
-			$this->cache = null;
-		} elseif (is_string($this->cache)) {
-			$this->cache = Yii::$app->getComponent($this->cache);
-		}
-
-		if ($this->getCachedContent() === false) {
-			$this->getView()->cacheStack[] = $this;
-			ob_start();
-			ob_implicit_flush(false);
-		}
-	}
-
-	/**
-	 * Marks the end of content to be cached.
-	 * Content displayed before this method call and after [[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->getView()->cacheStack);
-			if (is_array($this->dependency)) {
-				$this->dependency = Yii::createObject($this->dependency);
-			}
-			$data = [$content, $this->dynamicPlaceholders];
-			$this->cache->set($this->calculateKey(), $data, $this->duration, $this->dependency);
-
-			if (empty($this->getView()->cacheStack) && !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 (empty($this->getView()->cacheStack)) {
-							// outermost cache: replace placeholder with dynamic content
-							$content = $this->updateDynamicContent($content, $placeholders);
-						}
-						foreach ($placeholders as $name => $statements) {
-							$this->getView()->addDynamicPlaceholder($name, $statements);
-						}
-					}
-					$this->_content = $content;
-				}
-			}
-		}
-		return $this->_content;
-	}
-
-	protected function updateDynamicContent($content, $placeholders)
-	{
-		foreach ($placeholders as $name => $statements) {
-			$placeholders[$name] = $this->getView()->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 mixed a valid cache key
-	 */
-	protected function calculateKey()
-	{
-		$factors = [__CLASS__, $this->getId()];
-		if (is_array($this->variations)) {
-			foreach ($this->variations as $factor) {
-				$factors[] = $factor;
-			}
-		}
-		return $factors;
-	}
-}
diff --git a/framework/yii/widgets/InputWidget.php b/framework/yii/widgets/InputWidget.php
deleted file mode 100644
index 0a4b5b7..0000000
--- a/framework/yii/widgets/InputWidget.php
+++ /dev/null
@@ -1,72 +0,0 @@
-<?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\Model;
-use yii\base\InvalidConfigException;
-use yii\helpers\Html;
-
-/**
- * InputWidget is the base class for widgets that collect user inputs.
- *
- * An input widget can be associated with a data model and an attribute,
- * or a name and a value. If the former, the name and the value will
- * be generated automatically.
- *
- * @author Qiang Xue <qiang.xue@gmail.com>
- * @since 2.0
- */
-class InputWidget extends Widget
-{
-	/**
-	 * @var Model the data model that this widget is associated with.
-	 */
-	public $model;
-	/**
-	 * @var string the model attribute that this widget is associated with.
-	 */
-	public $attribute;
-	/**
-	 * @var string the input name. This must be set if [[model]] and [[attribute]] are not set.
-	 */
-	public $name;
-	/**
-	 * @var string the input value.
-	 */
-	public $value;
-	/**
-	 * @var array the HTML attributes for the input tag.
-	 */
-	public $options = [];
-
-
-	/**
-	 * Initializes the widget.
-	 * If you override this method, make sure you call the parent implementation first.
-	 */
-	public function init()
-	{
-		if (!$this->hasModel() && $this->name === null) {
-			throw new InvalidConfigException("Either 'name', or 'model' and 'attribute' properties must be specified.");
-		}
-		if (!isset($this->options['id'])) {
-			$this->options['id'] = $this->hasModel() ? Html::getInputId($this->model, $this->attribute) : $this->getId();
-		}
-		parent::init();
-	}
-
-	/**
-	 * @return boolean whether this widget is associated with a data model.
-	 */
-	protected function hasModel()
-	{
-		return $this->model instanceof Model && $this->attribute !== null;
-	}
-}
diff --git a/framework/yii/widgets/LinkPager.php b/framework/yii/widgets/LinkPager.php
deleted file mode 100644
index 807a4b8..0000000
--- a/framework/yii/widgets/LinkPager.php
+++ /dev/null
@@ -1,193 +0,0 @@
-<?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\helpers\Html;
-use yii\base\Widget;
-use yii\data\Pagination;
-
-/**
- * LinkPager displays a list of hyperlinks that lead to different pages of target.
- *
- * LinkPager works with a [[Pagination]] object which specifies the totally number
- * of pages and the current page number.
- *
- * Note that LinkPager only generates the necessary HTML markups. In order for it
- * to look like a real pager, you should provide some CSS styles for it.
- * With the default configuration, LinkPager should look good using Twitter Bootstrap CSS framework.
- *
- * @author Qiang Xue <qiang.xue@gmail.com>
- * @since 2.0
- */
-class LinkPager extends Widget
-{
-	/**
-	 * @var Pagination the pagination object that this pager is associated with.
-	 * You must set this property in order to make LinkPager work.
-	 */
-	public $pagination;
-	/**
-	 * @var array HTML attributes for the pager container tag.
-	 */
-	public $options = ['class' => 'pagination'];
-	/**
-	 * @var string the CSS class for the "first" page button.
-	 */
-	public $firstPageCssClass = 'first';
-	/**
-	 * @var string the CSS class for the "last" page button.
-	 */
-	public $lastPageCssClass = 'last';
-	/**
-	 * @var string the CSS class for the "previous" page button.
-	 */
-	public $prevPageCssClass = 'prev';
-	/**
-	 * @var string the CSS class for the "next" page button.
-	 */
-	public $nextPageCssClass = 'next';
-	/**
-	 * @var string the CSS class for the active (currently selected) page button.
-	 */
-	public $activePageCssClass = 'active';
-	/**
-	 * @var string the CSS class for the disabled page buttons.
-	 */
-	public $disabledPageCssClass = 'disabled';
-	/**
-	 * @var integer maximum number of page buttons that can be displayed. Defaults to 10.
-	 */
-	public $maxButtonCount = 10;
-	/**
-	 * @var string the label for the "next" page button. Note that this will NOT be HTML-encoded.
-	 * If this property is null, the "next" page button will not be displayed.
-	 */
-	public $nextPageLabel = '&raquo;';
-	/**
-	 * @var string the text label for the previous page button. Note that this will NOT be HTML-encoded.
-	 * If this property is null, the "previous" page button will not be displayed.
-	 */
-	public $prevPageLabel = '&laquo;';
-	/**
-	 * @var string the text label for the "first" page button. Note that this will NOT be HTML-encoded.
-	 * If this property is null, the "first" page button will not be displayed.
-	 */
-	public $firstPageLabel;
-	/**
-	 * @var string the text label for the "last" page button. Note that this will NOT be HTML-encoded.
-	 * If this property is null, the "last" page button will not be displayed.
-	 */
-	public $lastPageLabel;
-
-
-	/**
-	 * Initializes the pager.
-	 */
-	public function init()
-	{
-		if ($this->pagination === null) {
-			throw new InvalidConfigException('The "pagination" property must be set.');
-		}
-	}
-
-	/**
-	 * Executes the widget.
-	 * This overrides the parent implementation by displaying the generated page buttons.
-	 */
-	public function run()
-	{
-		echo $this->renderPageButtons();
-	}
-
-	/**
-	 * Renders the page buttons.
-	 * @return string the rendering result
-	 */
-	protected function renderPageButtons()
-	{
-		$buttons = [];
-
-		$pageCount = $this->pagination->getPageCount();
-		$currentPage = $this->pagination->getPage();
-
-		// first page
-		if ($this->firstPageLabel !== null) {
-			$buttons[] = $this->renderPageButton($this->firstPageLabel, 0, $this->firstPageCssClass, $currentPage <= 0, false);
-		}
-
-		// prev page
-		if ($this->prevPageLabel !== null) {
-			if (($page = $currentPage - 1) < 0) {
-				$page = 0;
-			}
-			$buttons[] = $this->renderPageButton($this->prevPageLabel, $page, $this->prevPageCssClass, $currentPage <= 0, false);
-		}
-
-		// internal pages
-		list($beginPage, $endPage) = $this->getPageRange();
-		for ($i = $beginPage; $i <= $endPage; ++$i) {
-			$buttons[] = $this->renderPageButton($i + 1, $i, null, false, $i == $currentPage);
-		}
-
-		// next page
-		if ($this->nextPageLabel !== null) {
-			if (($page = $currentPage + 1) >= $pageCount - 1) {
-				$page = $pageCount - 1;
-			}
-			$buttons[] = $this->renderPageButton($this->nextPageLabel, $page, $this->nextPageCssClass, $currentPage >= $pageCount - 1, false);
-		}
-
-		// last page
-		if ($this->lastPageLabel !== null) {
-			$buttons[] = $this->renderPageButton($this->lastPageLabel, $pageCount - 1, $this->lastPageCssClass, $currentPage >= $pageCount - 1, false);
-		}
-
-		return Html::tag('ul', implode("\n", $buttons), $this->options);
-	}
-
-	/**
-	 * Renders a page button.
-	 * You may override this method to customize the generation of page buttons.
-	 * @param string $label the text label for the button
-	 * @param integer $page the page number
-	 * @param string $class the CSS class for the page button.
-	 * @param boolean $disabled whether this page button is disabled
-	 * @param boolean $active whether this page button is active
-	 * @return string the rendering result
-	 */
-	protected function renderPageButton($label, $page, $class, $disabled, $active)
-	{
-		$options = ['class' => $class === '' ? null : $class];
-		if ($active) {
-			Html::addCssClass($options, $this->activePageCssClass);
-		}
-		if ($disabled) {
-			Html::addCssClass($options, $this->disabledPageCssClass);
-			return Html::tag('li', Html::tag('span', $label), $options);
-		}
-		return Html::tag('li', Html::a($label, $this->pagination->createUrl($page), ['data-page' => $page]), $options);
-	}
-
-	/**
-	 * @return array the begin and end pages that need to be displayed.
-	 */
-	protected function getPageRange()
-	{
-		$currentPage = $this->pagination->getPage();
-		$pageCount = $this->pagination->getPageCount();
-
-		$beginPage = max(0, $currentPage - (int)($this->maxButtonCount / 2));
-		if (($endPage = $beginPage + $this->maxButtonCount - 1) >= $pageCount) {
-			$endPage = $pageCount - 1;
-			$beginPage = max(0, $endPage - $this->maxButtonCount + 1);
-		}
-		return [$beginPage, $endPage];
-	}
-}
diff --git a/framework/yii/widgets/LinkSorter.php b/framework/yii/widgets/LinkSorter.php
deleted file mode 100644
index bfafcea..0000000
--- a/framework/yii/widgets/LinkSorter.php
+++ /dev/null
@@ -1,74 +0,0 @@
-<?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\data\Sort;
-use yii\helpers\Html;
-
-/**
- * LinkSorter renders a list of sort links for the given sort definition.
- *
- * LinkSorter will generate a hyperlink for every attribute declared in [[sort]].
- *
- * @author Qiang Xue <qiang.xue@gmail.com>
- * @since 2.0
- */
-class LinkSorter extends Widget
-{
-	/**
-	 * @var Sort the sort definition
-	 */
-	public $sort;
-	/**
-	 * @var array list of the attributes that support sorting. If not set, it will be determined
-	 * using [[Sort::attributes]].
-	 */
-	public $attributes;
-	/**
-	 * @var array HTML attributes for the sorter container tag.
-	 * See [[yii\helpers\Html::ul()]] for special attributes.
-	 */
-	public $options = ['class' => 'sorter'];
-
-
-	/**
-	 * Initializes the sorter.
-	 */
-	public function init()
-	{
-		if ($this->sort === null) {
-			throw new InvalidConfigException('The "sort" property must be set.');
-		}
-	}
-
-	/**
-	 * Executes the widget.
-	 * This method renders the sort links.
-	 */
-	public function run()
-	{
-		echo $this->renderSortLinks();
-	}
-
-	/**
-	 * Renders the sort links.
-	 * @return string the rendering result
-	 */
-	protected function renderSortLinks()
-	{
-		$attributes = empty($this->attributes) ? array_keys($this->sort->attributes) : $this->attributes;
-		$links = [];
-		foreach ($attributes as $name) {
-			$links[] = $this->sort->link($name);
-		}
-		return Html::ul($links, array_merge($this->options, ['encode' => false]));
-	}
-}
diff --git a/framework/yii/widgets/ListView.php b/framework/yii/widgets/ListView.php
deleted file mode 100644
index 43eaab4..0000000
--- a/framework/yii/widgets/ListView.php
+++ /dev/null
@@ -1,107 +0,0 @@
-<?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\helpers\ArrayHelper;
-use yii\helpers\Html;
-
-/**
- *
- * @author Qiang Xue <qiang.xue@gmail.com>
- * @since 2.0
- */
-class ListView extends BaseListView
-{
-	/**
-	 * @var array the HTML attributes for the container of the rendering result of each data model.
-	 * The "tag" element specifies the tag name of the container element and defaults to "div".
-	 * If "tag" is false, it means no container element will be rendered.
-	 */
-	public $itemOptions = [];
-	/**
-	 * @var string|callback the name of the view for rendering each data item, or a callback (e.g. an anonymous function)
-	 * for rendering each data item. If it specifies a view name, the following variables will
-	 * be available in the view:
-	 *
-	 * - `$model`: mixed, the data model
-	 * - `$key`: mixed, the key value associated with the data item
-	 * - `$index`: integer, the zero-based index of the data item in the items array returned by [[dataProvider]].
-	 * - `$widget`: ListView, this widget instance
-	 *
-	 * Note that the view name is resolved into the view file by the current context of the [[view]] object.
-	 *
-	 * If this property is specified as a callback, it should have the following signature:
-	 *
-	 * ~~~
-	 * function ($model, $key, $index, $widget)
-	 * ~~~
-	 */
-	public $itemView;
-	/**
-	 * @var array additional parameters to be passed to [[itemView]] when it is being rendered.
-	 * This property is used only when [[itemView]] is a string representing a view name.
-	 */
-	public $viewParams = [];
-	/**
-	 * @var string the HTML code to be displayed between any two consecutive items.
-	 */
-	public $separator = "\n";
-	/**
-	 * @var array the HTML attributes for the container tag of the list view.
-	 * The "tag" element specifies the tag name of the container element and defaults to "div".
-	 */
-	public $options = ['class' => 'list-view'];
-
-
-	/**
-	 * Renders all data models.
-	 * @return string the rendering result
-	 */
-	public function renderItems()
-	{
-		$models = $this->dataProvider->getModels();
-		$keys = $this->dataProvider->getKeys();
-		$rows = [];
-		foreach (array_values($models) as $index => $model) {
-			$rows[] = $this->renderItem($model, $keys[$index], $index);
-		}
-		return implode($this->separator, $rows);
-	}
-
-	/**
-	 * Renders a single data model.
-	 * @param mixed $model the data model to be rendered
-	 * @param mixed $key the key value associated with the data model
-	 * @param integer $index the zero-based index of the data model in the model array returned by [[dataProvider]].
-	 * @return string the rendering result
-	 */
-	public function renderItem($model, $key, $index)
-	{
-		if ($this->itemView === null) {
-			$content = $key;
-		} elseif (is_string($this->itemView)) {
-			$content = $this->getView()->render($this->itemView, array_merge([
-				'model' => $model,
-				'key' => $key,
-				'index' => $index,
-				'widget' => $this,
-			], $this->viewParams));
-		} else {
-			$content = call_user_func($this->itemView, $model, $key, $index, $this);
-		}
-		$options = $this->itemOptions;
-		$tag = ArrayHelper::remove($options, 'tag', 'div');
-		if ($tag !== false) {
-			$options['data-key'] = is_array($key) ? json_encode($key) : $key;
-			return Html::tag($tag, $content, $options);
-		} else {
-			return $content;
-		}
-	}
-}
diff --git a/framework/yii/widgets/MaskedInput.php b/framework/yii/widgets/MaskedInput.php
deleted file mode 100644
index 7eb42a7..0000000
--- a/framework/yii/widgets/MaskedInput.php
+++ /dev/null
@@ -1,129 +0,0 @@
-<?php
-/**
- * @link http://www.yiiframework.com/
- * @copyright Copyright (c) 2008 Yii Software LLC
- * @license http://www.yiiframework.com/license/
- */
-
-namespace yii\widgets;
-
-use yii\base\InvalidConfigException;
-use yii\helpers\Html;
-use yii\helpers\Json;
-use yii\web\JsExpression;
-
-/**
- * MaskedInput generates a masked text input.
- *
- * MaskedInput is similar to [[Html::textInput()]] except that
- * an input mask will be used to force users to enter properly formatted data,
- * such as phone numbers, social security numbers.
- *
- * To use MaskedInput, you must set the [[mask]] property. The following example
- * shows how to use MaskedInput to collect phone numbers:
- *
- * ~~~
- * echo MaskedInput::widget([
- *     'name' => 'phone',
- *     'mask' => '999-999-9999',
- * ]);
- * ~~~
- *
- * The masked text field is implemented based on the [jQuery masked input plugin](http://digitalbush.com/projects/masked-input-plugin).
- *
- * @author Qiang Xue <qiang.xue@gmail.com>
- * @since 2.0
- */
-class MaskedInput extends InputWidget
-{
-	/**
-	 * @var string the input mask (e.g. '99/99/9999' for date input). The following characters are predefined:
-	 *
-	 * - `a`: represents an alpha character (A-Z,a-z)
-	 * - `9`: represents a numeric character (0-9)
-	 * - `*`: represents an alphanumeric character (A-Z,a-z,0-9)
-	 * - `?`: anything listed after '?' within the mask is considered optional user input
-	 *
-	 * Additional characters can be defined by specifying the [[charMap]] property.
-	 */
-	public $mask;
-	/**
-	 * @var array the mapping between mask characters and the corresponding patterns.
-	 * For example, `['~' => '[+-]']` specifies that the '~' character expects '+' or '-' input.
-	 * Defaults to null, meaning using the map as described in [[mask]].
-	 */
-	public $charMap;
-	/**
-	 * @var string the character prompting for user input. Defaults to underscore '_'.
-	 */
-	public $placeholder;
-	/**
-	 * @var string a JavaScript function callback that will be invoked when user finishes the input.
-	 */
-	public $completed;
-
-
-	/**
-	 * Initializes the widget.
-	 * @throws InvalidConfigException if the "mask" property is not set.
-	 */
-	public function init()
-	{
-		parent::init();
-		if (empty($this->mask)) {
-			throw new InvalidConfigException('The "mask" property must be set.');
-		}
-	}
-
-	/**
-	 * Runs the widget.
-	 */
-	public function run()
-	{
-		if ($this->hasModel()) {
-			echo Html::activeTextInput($this->model, $this->attribute, $this->options);
-		} else {
-			echo Html::textInput($this->name, $this->value, $this->options);
-		}
-		$this->registerClientScript();
-	}
-
-	/**
-	 * Registers the needed JavaScript.
-	 */
-	public function registerClientScript()
-	{
-		$options = $this->getClientOptions();
-		$options = empty($options) ? '' : ',' . Json::encode($options);
-		$js = '';
-		if (is_array($this->charMap) && !empty($this->charMap)) {
-			$js .= 'jQuery.mask.definitions=' . Json::encode($this->charMap) . ";\n";
-		}
-		$id = $this->options['id'];
-		$js .= "jQuery(\"#{$id}\").mask(\"{$this->mask}\"{$options});";
-		$view = $this->getView();
-		MaskedInputAsset::register($view);
-		$view->registerJs($js);
-	}
-
-	/**
-	 * @return array the options for the text field
-	 */
-	protected function getClientOptions()
-	{
-		$options = [];
-		if ($this->placeholder !== null) {
-			$options['placeholder'] = $this->placeholder;
-		}
-
-		if ($this->completed !== null) {
-			if ($this->completed instanceof JsExpression) {
-				$options['completed'] = $this->completed;
-			} else {
-				$options['completed'] = new JsExpression($this->completed);
-			}
-		}
-
-		return $options;
-	}
-}
diff --git a/framework/yii/widgets/Menu.php b/framework/yii/widgets/Menu.php
deleted file mode 100644
index d5ff8ef..0000000
--- a/framework/yii/widgets/Menu.php
+++ /dev/null
@@ -1,307 +0,0 @@
-<?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\helpers\ArrayHelper;
-use yii\helpers\Html;
-
-/**
- * Menu displays a multi-level menu using nested HTML lists.
- *
- * The main property of Menu is [[items]], which specifies the possible items in the menu.
- * A menu item can contain sub-items which specify the sub-menu under that menu item.
- *
- * Menu checks the current route and request parameters to toggle certain menu items
- * with active state.
- *
- * Note that Menu only renders the HTML tags about the menu. It does do any styling.
- * You are responsible to provide CSS styles to make it look like a real menu.
- *
- * The following example shows how to use Menu:
- *
- * ~~~
- * echo Menu::widget([
- *     'items' => [
- *         // Important: you need to specify url as 'controller/action',
- *         // not just as 'controller' even if default action is used.
- *         ['label' => 'Home', 'url' => ['site/index']],
- *         // 'Products' menu item will be selected as long as the route is 'product/index'
- *         ['label' => 'Products', 'url' => ['product/index'], 'items' => [
- *             ['label' => 'New Arrivals', 'url' => ['product/index', 'tag' => 'new']],
- *             ['label' => 'Most Popular', 'url' => ['product/index', 'tag' => 'popular']],
- *         ]],
- *         ['label' => 'Login', 'url' => ['site/login'], 'visible' => Yii::$app->user->isGuest],
- *     ],
- * ]);
- * ~~~
- *
- * @author Qiang Xue <qiang.xue@gmail.com>
- * @since 2.0
- */
-class Menu extends Widget
-{
-	/**
-	 * @var array list of menu items. Each menu item should be an array of the following structure:
-	 *
-	 * - label: string, optional, specifies the menu item label. When [[encodeLabels]] is true, the label
-	 *   will be HTML-encoded. If the label is not specified, an empty string will be used.
-	 * - url: string or array, optional, specifies the URL of the menu item. It will be processed by [[Html::url]].
-	 *   When this is set, the actual menu item content will be generated using [[linkTemplate]];
-	 *   otherwise, [[labelTemplate]] will be used.
-	 * - visible: boolean, optional, whether this menu item is visible. Defaults to true.
-	 * - items: array, optional, specifies the sub-menu items. Its format is the same as the parent items.
-	 * - active: boolean, optional, whether this menu item is in active state (currently selected).
-	 *   If a menu item is active, its CSS class will be appended with [[activeCssClass]].
-	 *   If this option is not set, the menu item will be set active automatically when the current request
-	 *   is triggered by [[url]]. For more details, please refer to [[isItemActive()]].
-	 * - template: string, optional, the template used to render the content of this menu item.
-	 *   The token `{url}` will be replaced by the URL associated with this menu item,
-	 *   and the token `{label}` will be replaced by the label of the menu item.
-	 *   If this option is not set, [[linkTemplate]] or [[labelTemplate]] will be used instead.
-	 * - options: array, optional, the HTML attributes for the menu container tag.
-	 */
-	public $items = [];
-	/**
-	 * @var array list of HTML attributes for the menu container tag. This will be overwritten
-	 * by the "options" set in individual [[items]]. The following special options are recognized:
-	 *
-	 * - tag: string, defaults to "li", the tag name of the item container tags.
-	 */
-	public $itemOptions = [];
-	/**
-	 * @var string the template used to render the body of a menu which is a link.
-	 * In this template, the token `{url}` will be replaced with the corresponding link URL;
-	 * while `{label}` will be replaced with the link text.
-	 * This property will be overridden by the `template` option set in individual menu items via [[items]].
-	 */
-	public $linkTemplate = '<a href="{url}">{label}</a>';
-	/**
-	 * @var string the template used to render the body of a menu which is NOT a link.
-	 * In this template, the token `{label}` will be replaced with the label of the menu item.
-	 * This property will be overridden by the `template` option set in individual menu items via [[items]].
-	 */
-	public $labelTemplate = '{label}';
-	/**
-	 * @var string the template used to render a list of sub-menus.
-	 * In this template, the token `{items}` will be replaced with the renderer sub-menu items.
-	 */
-	public $submenuTemplate = "\n<ul>\n{items}\n</ul>\n";
-	/**
-	 * @var boolean whether the labels for menu items should be HTML-encoded.
-	 */
-	public $encodeLabels = true;
-	/**
-	 * @var string the CSS class to be appended to the active menu item.
-	 */
-	public $activeCssClass = 'active';
-	/**
-	 * @var boolean whether to automatically activate items according to whether their route setting
-	 * matches the currently requested route.
-	 * @see isItemActive()
-	 */
-	public $activateItems = true;
-	/**
-	 * @var boolean whether to activate parent menu items when one of the corresponding child menu items is active.
-	 * The activated parent menu items will also have its CSS classes appended with [[activeCssClass]].
-	 */
-	public $activateParents = false;
-	/**
-	 * @var boolean whether to hide empty menu items. An empty menu item is one whose `url` option is not
-	 * set and which has no visible child menu items.
-	 */
-	public $hideEmptyItems = true;
-	/**
-	 * @var array the HTML attributes for the menu's container tag. The following special options are recognized:
-	 *
-	 * - tag: string, defaults to "ul", the tag name of the item container tags.
-	 */
-	public $options = [];
-	/**
-	 * @var string the CSS class that will be assigned to the first item in the main menu or each submenu.
-	 * Defaults to null, meaning no such CSS class will be assigned.
-	 */
-	public $firstItemCssClass;
-	/**
-	 * @var string the CSS class that will be assigned to the last item in the main menu or each submenu.
-	 * Defaults to null, meaning no such CSS class will be assigned.
-	 */
-	public $lastItemCssClass;
-	/**
-	 * @var string the route used to determine if a menu item is active or not.
-	 * If not set, it will use the route of the current request.
-	 * @see params
-	 * @see isItemActive()
-	 */
-	public $route;
-	/**
-	 * @var array the parameters used to determine if a menu item is active or not.
-	 * If not set, it will use `$_GET`.
-	 * @see route
-	 * @see isItemActive()
-	 */
-	public $params;
-
-
-	/**
-	 * Renders the menu.
-	 */
-	public function run()
-	{
-		if ($this->route === null && Yii::$app->controller !== null) {
-			$this->route = Yii::$app->controller->getRoute();
-		}
-		if ($this->params === null) {
-			$this->params = $_GET;
-		}
-		$items = $this->normalizeItems($this->items, $hasActiveChild);
-		$options = $this->options;
-		$tag = ArrayHelper::remove($options, 'tag', 'ul');
-		echo Html::tag($tag, $this->renderItems($items), $options);
-	}
-
-	/**
-	 * Recursively renders the menu items (without the container tag).
-	 * @param array $items the menu items to be rendered recursively
-	 * @return string the rendering result
-	 */
-	protected function renderItems($items)
-	{
-		$n = count($items);
-		$lines = [];
-		foreach ($items as $i => $item) {
-			$options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options', []));
-			$tag = ArrayHelper::remove($options, 'tag', 'li');
-			$class = [];
-			if ($item['active']) {
-				$class[] = $this->activeCssClass;
-			}
-			if ($i === 0 && $this->firstItemCssClass !== null) {
-				$class[] = $this->firstItemCssClass;
-			}
-			if ($i === $n - 1 && $this->lastItemCssClass !== null) {
-				$class[] = $this->lastItemCssClass;
-			}
-			if (!empty($class)) {
-				if (empty($options['class'])) {
-					$options['class'] = implode(' ', $class);
-				} else {
-					$options['class'] .= ' ' . implode(' ', $class);
-				}
-			}
-
-			$menu = $this->renderItem($item);
-			if (!empty($item['items'])) {
-				$menu .= strtr($this->submenuTemplate, [
-					'{items}' => $this->renderItems($item['items']),
-				]);
-			}
-			$lines[] = Html::tag($tag, $menu, $options);
-		}
-		return implode("\n", $lines);
-	}
-
-	/**
-	 * Renders the content of a menu item.
-	 * Note that the container and the sub-menus are not rendered here.
-	 * @param array $item the menu item to be rendered. Please refer to [[items]] to see what data might be in the item.
-	 * @return string the rendering result
-	 */
-	protected function renderItem($item)
-	{
-		if (isset($item['url'])) {
-			$template = ArrayHelper::getValue($item, 'template', $this->linkTemplate);
-			return strtr($template, [
-				'{url}' => Html::url($item['url']),
-				'{label}' => $item['label'],
-			]);
-		} else {
-			$template = ArrayHelper::getValue($item, 'template', $this->labelTemplate);
-			return strtr($template, [
-				'{label}' => $item['label'],
-			]);
-		}
-	}
-
-	/**
-	 * Normalizes the [[items]] property to remove invisible items and activate certain items.
-	 * @param array $items the items to be normalized.
-	 * @param boolean $active whether there is an active child menu item.
-	 * @return array the normalized menu items
-	 */
-	protected function normalizeItems($items, &$active)
-	{
-		foreach ($items as $i => $item) {
-			if (isset($item['visible']) && !$item['visible']) {
-				unset($items[$i]);
-				continue;
-			}
-			if (!isset($item['label'])) {
-				$item['label'] = '';
-			}
-			if ($this->encodeLabels) {
-				$items[$i]['label'] = Html::encode($item['label']);
-			}
-			$hasActiveChild = false;
-			if (isset($item['items'])) {
-				$items[$i]['items'] = $this->normalizeItems($item['items'], $hasActiveChild);
-				if (empty($items[$i]['items']) && $this->hideEmptyItems) {
-					unset($items[$i]['items']);
-					if (!isset($item['url'])) {
-						unset($items[$i]);
-						continue;
-					}
-				}
-			}
-			if (!isset($item['active'])) {
-				if ($this->activateParents && $hasActiveChild || $this->activateItems && $this->isItemActive($item)) {
-					$active = $items[$i]['active'] = true;
-				} else {
-					$items[$i]['active'] = false;
-				}
-			} elseif ($item['active']) {
-				$active = true;
-			}
-		}
-		return array_values($items);
-	}
-
-	/**
-	 * Checks whether a menu item is active.
-	 * This is done by checking if [[route]] and [[params]] match that specified in the `url` option of the menu item.
-	 * When the `url` option of a menu item is specified in terms of an array, its first element is treated
-	 * as the route for the item and the rest of the elements are the associated parameters.
-	 * Only when its route and parameters match [[route]] and [[params]], respectively, will a menu item
-	 * be considered active.
-	 * @param array $item the menu item to be checked
-	 * @return boolean whether the menu item is active
-	 */
-	protected function isItemActive($item)
-	{
-		if (isset($item['url']) && is_array($item['url']) && isset($item['url'][0])) {
-			$route = $item['url'][0];
-			if ($route[0] !== '/' && Yii::$app->controller) {
-				$route = Yii::$app->controller->module->getUniqueId() . '/' . $route;
-			}
-			if (ltrim($route, '/') !== $this->route) {
-				return false;
-			}
-			unset($item['url']['#']);
-			if (count($item['url']) > 1) {
-				foreach (array_splice($item['url'], 1) as $name => $value) {
-					if (!isset($this->params[$name]) || $this->params[$name] != $value) {
-						return false;
-					}
-				}
-			}
-			return true;
-		}
-		return false;
-	}
-}
diff --git a/framework/yii/widgets/Spaceless.php b/framework/yii/widgets/Spaceless.php
deleted file mode 100644
index 8115f85..0000000
--- a/framework/yii/widgets/Spaceless.php
+++ /dev/null
@@ -1,69 +0,0 @@
-<?php
-/**
- * @link http://www.yiiframework.com/
- * @copyright Copyright (c) 2008 Yii Software LLC
- * @license http://www.yiiframework.com/license/
- */
-
-namespace yii\widgets;
-
-use yii\base\Widget;
-
-/**
- * Spaceless widget removes whitespace characters between HTML tags. Whitespaces within HTML tags
- * or in a plain text are always left untouched.
- *
- * Usage example:
- *
- * ```php
- * <body>
- *     <?php Spaceless::begin(); ?>
- *         <div class="nav-bar">
- *             <!-- tags -->
- *         </div>
- *         <div class="content">
- *             <!-- tags -->
- *         </div>
- *     <?php Spaceless::end(); ?>
- * </body>
- * ```
- *
- * This example will generate the following HTML:
- *
- * ```html
- * <body>
- *     <div class="navbar"><!-- other tags --></div><div class="content"><!-- other tags --></div></body>
- * ```
- *
- * This method is not designed for content compression (you should use `gzip` output compression to
- * achieve it). Main intention is to strip out extra whitespace characters between HTML tags in order
- * to avoid browser rendering quirks in some circumstances (e.g. newlines between inline-block elements).
- *
- * Note, never use this method with `pre` or `textarea` tags. It's not that trivial to deal with such tags
- * as it may seem at first sight. For this case you should consider using
- * [HTML Tidy Project](http://tidy.sourceforge.net/) instead.
- *
- * @see http://tidy.sourceforge.net/
- * @author resurtm <resurtm@gmail.com>
- * @since 2.0
- */
-class Spaceless extends Widget
-{
-	/**
-	 * Starts capturing an output to be cleaned from whitespace characters between HTML tags.
-	 */
-	public function init()
-	{
-		ob_start();
-		ob_implicit_flush(false);
-	}
-
-	/**
-	 * Marks the end of content to be cleaned from whitespace characters between HTML tags.
-	 * Stops capturing an output and echoes cleaned result.
-	 */
-	public function run()
-	{
-		echo trim(preg_replace('/>\s+</', '><', ob_get_clean()));
-	}
-}