diff --git a/apps/advanced/README.md b/apps/advanced/README.md
index d8c1e17..f2abc1e 100644
--- a/apps/advanced/README.md
+++ b/apps/advanced/README.md
@@ -10,7 +10,7 @@ if you have a project to be deployed for production soon.
 Thank you for using Yii 2 Advanced Application Template - an application template
 that works out-of-box and can be easily customized to fit for your needs.
 
-Yii 2 Advanced Application Template is best suitable for large projects requiring frontend and backstage separation,
+Yii 2 Advanced Application Template is best suitable for large projects requiring frontend and backend separation,
 deployment in different environments, configuration nesting etc.
 
 
@@ -20,18 +20,18 @@ DIRECTORY STRUCTURE
 ```
 common
 	config/             contains shared configurations
-	models/             contains model classes used in both backstage and frontend
+	models/             contains model classes used in both backend and frontend
 console
 	config/             contains console configurations
 	controllers/        contains console controllers (commands)
 	migrations/         contains database migrations
 	models/             contains console-specific model classes
 	runtime/            contains files generated during runtime
-backstage
+backend
 	assets/             contains application assets such as JavaScript and CSS
-	config/             contains backstage configurations
+	config/             contains backend configurations
 	controllers/        contains Web controller classes
-	models/             contains backstage-specific model classes
+	models/             contains backend-specific model classes
 	runtime/            contains files generated during runtime
 	views/              contains view files for the Web application
 	www/                contains the entry script and Web resources
@@ -107,7 +107,7 @@ the installed application. You only need to do these once for all.
 Now you should be able to access:
 
 - the frontend using the URL `http://localhost/yii-advanced/frontend/www/`
-- the backstage using the URL `http://localhost/yii-advanced/backstage/www/`
+- the backend using the URL `http://localhost/yii-advanced/backend/www/`
 
 assuming `yii-advanced` is directly under the document root of your Web server.
 
diff --git a/apps/advanced/backstage/assets/.gitkeep b/apps/advanced/backend/assets/.gitkeep
similarity index 100%
rename from apps/advanced/backstage/assets/.gitkeep
rename to apps/advanced/backend/assets/.gitkeep
diff --git a/apps/advanced/backstage/config/.gitignore b/apps/advanced/backend/config/.gitignore
similarity index 100%
rename from apps/advanced/backstage/config/.gitignore
rename to apps/advanced/backend/config/.gitignore
diff --git a/apps/advanced/backstage/config/assets.php b/apps/advanced/backend/config/assets.php
similarity index 100%
rename from apps/advanced/backstage/config/assets.php
rename to apps/advanced/backend/config/assets.php
diff --git a/apps/advanced/backstage/config/main.php b/apps/advanced/backend/config/main.php
similarity index 97%
rename from apps/advanced/backstage/config/main.php
rename to apps/advanced/backend/config/main.php
index 6e55c47..3140cd2 100644
--- a/apps/advanced/backstage/config/main.php
+++ b/apps/advanced/backend/config/main.php
@@ -13,7 +13,7 @@ return array(
 	'basePath' => dirname(__DIR__),
 	'vendorPath' => dirname(dirname(__DIR__)) . '/vendor',
 	'preload' => array('log'),
-	'controllerNamespace' => 'backstage\controllers',
+	'controllerNamespace' => 'backend\controllers',
 	'modules' => array(
 	),
 	'components' => array(
diff --git a/apps/advanced/backstage/config/params.php b/apps/advanced/backend/config/params.php
similarity index 100%
rename from apps/advanced/backstage/config/params.php
rename to apps/advanced/backend/config/params.php
diff --git a/apps/advanced/backstage/controllers/SiteController.php b/apps/advanced/backend/controllers/SiteController.php
similarity index 96%
rename from apps/advanced/backstage/controllers/SiteController.php
rename to apps/advanced/backend/controllers/SiteController.php
index d40738a..0306c97 100644
--- a/apps/advanced/backstage/controllers/SiteController.php
+++ b/apps/advanced/backend/controllers/SiteController.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace backstage\controllers;
+namespace backend\controllers;
 
 use Yii;
 use yii\web\Controller;
diff --git a/apps/advanced/backstage/models/.gitkeep b/apps/advanced/backend/models/.gitkeep
similarity index 100%
rename from apps/advanced/backstage/models/.gitkeep
rename to apps/advanced/backend/models/.gitkeep
diff --git a/apps/advanced/backstage/runtime/.gitignore b/apps/advanced/backend/runtime/.gitignore
similarity index 100%
rename from apps/advanced/backstage/runtime/.gitignore
rename to apps/advanced/backend/runtime/.gitignore
diff --git a/apps/advanced/backstage/views/layouts/main.php b/apps/advanced/backend/views/layouts/main.php
similarity index 100%
rename from apps/advanced/backstage/views/layouts/main.php
rename to apps/advanced/backend/views/layouts/main.php
diff --git a/apps/advanced/backstage/views/site/index.php b/apps/advanced/backend/views/site/index.php
similarity index 100%
rename from apps/advanced/backstage/views/site/index.php
rename to apps/advanced/backend/views/site/index.php
diff --git a/apps/advanced/backstage/views/site/login.php b/apps/advanced/backend/views/site/login.php
similarity index 100%
rename from apps/advanced/backstage/views/site/login.php
rename to apps/advanced/backend/views/site/login.php
diff --git a/apps/advanced/backstage/www/.gitignore b/apps/advanced/backend/www/.gitignore
similarity index 100%
rename from apps/advanced/backstage/www/.gitignore
rename to apps/advanced/backend/www/.gitignore
diff --git a/apps/advanced/backstage/www/assets/.gitignore b/apps/advanced/backend/www/assets/.gitignore
similarity index 100%
rename from apps/advanced/backstage/www/assets/.gitignore
rename to apps/advanced/backend/www/assets/.gitignore
diff --git a/apps/advanced/backstage/www/css/site.css b/apps/advanced/backend/www/css/site.css
similarity index 100%
rename from apps/advanced/backstage/www/css/site.css
rename to apps/advanced/backend/www/css/site.css
diff --git a/apps/advanced/composer.json b/apps/advanced/composer.json
index cc3ec2d..0e393cf 100644
--- a/apps/advanced/composer.json
+++ b/apps/advanced/composer.json
@@ -25,8 +25,8 @@
 	},
 	"extra": {
 		"yii-install-writable": [
-			"backstage/runtime",
-			"backstage/www/assets",
+			"backend/runtime",
+			"backend/www/assets",
 
 			"console/runtime",
 			"console/migrations",
diff --git a/apps/advanced/init.bat b/apps/advanced/init.bat
index dc2cd83..4fc52f7 100644
--- a/apps/advanced/init.bat
+++ b/apps/advanced/init.bat
@@ -1,7 +1,7 @@
 @echo off
 
 rem -------------------------------------------------------------
-rem  Yii command line install script for Windows.
+rem  Yii command line init script for Windows.
 rem
 rem  @author Qiang Xue <qiang.xue@gmail.com>
 rem  @link http://www.yiiframework.com/
@@ -15,6 +15,6 @@ set YII_PATH=%~dp0
 
 if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe
 
-"%PHP_COMMAND%" "%YII_PATH%install" %*
+"%PHP_COMMAND%" "%YII_PATH%init" %*
 
 @endlocal
diff --git a/framework/yii/base/ActionFilter.php b/framework/yii/base/ActionFilter.php
index 20ff142..1e957d5 100644
--- a/framework/yii/base/ActionFilter.php
+++ b/framework/yii/base/ActionFilter.php
@@ -16,10 +16,13 @@ class ActionFilter extends Behavior
 	/**
 	 * @var array list of action IDs that this filter should apply to. If this property is not set,
 	 * then the filter applies to all actions, unless they are listed in [[except]].
+	 * If an action ID appears in both [[only]] and [[except]], this filter will NOT apply to it.
+	 * @see except
 	 */
 	public $only;
 	/**
 	 * @var array list of action IDs that this filter should not apply to.
+	 * @see only
 	 */
 	public $except = array();
 
diff --git a/framework/yii/base/Application.php b/framework/yii/base/Application.php
index 09951bd..9969ecd 100644
--- a/framework/yii/base/Application.php
+++ b/framework/yii/base/Application.php
@@ -8,6 +8,7 @@
 namespace yii\base;
 
 use Yii;
+use yii\web\HttpException;
 
 /**
  * Application is the base class for all application classes.
@@ -17,8 +18,14 @@ use Yii;
  */
 class Application extends Module
 {
-	const EVENT_BEFORE_REQUEST = 'beforeRequest';
-	const EVENT_AFTER_REQUEST = 'afterRequest';
+	/**
+	 * @event Event an event that is triggered at the beginning of [[run()]].
+	 */
+	const EVENT_BEFORE_RUN = 'beforeRun';
+	/**
+	 * @event Event an event that is triggered at the end of [[run()]].
+	 */
+	const EVENT_AFTER_RUN = 'afterRun';
 	/**
 	 * @var string the application name.
 	 */
@@ -128,6 +135,10 @@ class Application extends Module
 			ini_set('display_errors', 0);
 			set_exception_handler(array($this, 'handleException'));
 			set_error_handler(array($this, 'handleError'), error_reporting());
+			// Allocating twice more than required to display memory exhausted error
+			// in case of trying to allocate last 1 byte while all memory is taken.
+			$this->_memoryReserve = str_repeat('x', 1024 * 256);
+			register_shutdown_function(array($this, 'handleFatalError'));
 		}
 	}
 
@@ -142,11 +153,10 @@ class Application extends Module
 	{
 		if (!$this->_ended) {
 			$this->_ended = true;
-			$this->afterRequest();
+			$this->getResponse()->end();
+			$this->afterRun();
 		}
 
-		$this->handleFatalError();
-
 		if ($exit) {
 			exit($status);
 		}
@@ -159,30 +169,30 @@ class Application extends Module
 	 */
 	public function run()
 	{
-		$this->beforeRequest();
-		// Allocating twice more than required to display memory exhausted error
-		// in case of trying to allocate last 1 byte while all memory is taken.
-		$this->_memoryReserve = str_repeat('x', 1024 * 256);
+		$this->beforeRun();
+		$response = $this->getResponse();
+		$response->begin();
 		register_shutdown_function(array($this, 'end'), 0, false);
 		$status = $this->processRequest();
-		$this->afterRequest();
+		$response->end();
+		$this->afterRun();
 		return $status;
 	}
 
 	/**
-	 * Raises the [[EVENT_BEFORE_REQUEST]] event right BEFORE the application processes the request.
+	 * Raises the [[EVENT_BEFORE_RUN]] event right BEFORE the application processes the request.
 	 */
-	public function beforeRequest()
+	public function beforeRun()
 	{
-		$this->trigger(self::EVENT_BEFORE_REQUEST);
+		$this->trigger(self::EVENT_BEFORE_RUN);
 	}
 
 	/**
-	 * Raises the [[EVENT_AFTER_REQUEST]] event right AFTER the application processes the request.
+	 * Raises the [[EVENT_AFTER_RUN]] event right AFTER the application processes the request.
 	 */
-	public function afterRequest()
+	public function afterRun()
 	{
-		$this->trigger(self::EVENT_AFTER_REQUEST);
+		$this->trigger(self::EVENT_AFTER_RUN);
 	}
 
 	/**
@@ -315,6 +325,15 @@ class Application extends Module
 	}
 
 	/**
+	 * Returns the response component.
+	 * @return \yii\web\Response|\yii\console\Response the response component
+	 */
+	public function getResponse()
+	{
+		return $this->getComponent('response');
+	}
+
+	/**
 	 * Returns the view object.
 	 * @return View the view object that is used to render various view files.
 	 */
diff --git a/framework/yii/base/ErrorHandler.php b/framework/yii/base/ErrorHandler.php
index 7bf9e7e..fe9eef3 100644
--- a/framework/yii/base/ErrorHandler.php
+++ b/framework/yii/base/ErrorHandler.php
@@ -8,6 +8,7 @@
 namespace yii\base;
 
 use Yii;
+use yii\web\HttpException;
 
 /**
  * ErrorHandler handles uncaught PHP errors and exceptions.
@@ -82,11 +83,12 @@ class ErrorHandler extends Component
 		} elseif (!(Yii::$app instanceof \yii\web\Application)) {
 			Yii::$app->renderException($exception);
 		} else {
+			$response = Yii::$app->getResponse();
 			if (!headers_sent()) {
 				if ($exception instanceof HttpException) {
-					header('HTTP/1.0 ' . $exception->statusCode . ' ' . $exception->getName());
+					$response->setStatusCode($exception->statusCode);
 				} else {
-					header('HTTP/1.0 500 ' . get_class($exception));
+					$response->setStatusCode(500);
 				}
 			}
 			if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest') {
@@ -100,13 +102,13 @@ class ErrorHandler extends Component
 
 				$view = new View();
 				$request = '';
-				foreach (array('GET', 'POST', 'SERVER', 'FILES', 'COOKIE', 'SESSION', 'ENV') as $name) {
-					if (!empty($GLOBALS['_' . $name])) {
-						$request .= '$_' . $name . ' = ' . var_export($GLOBALS['_' . $name], true) . ";\n\n";
+				foreach (array('_GET', '_POST', '_SERVER', '_FILES', '_COOKIE', '_SESSION', '_ENV') as $name) {
+					if (!empty($GLOBALS[$name])) {
+						$request .= '$' . $name . ' = ' . var_export($GLOBALS[$name], true) . ";\n\n";
 					}
 				}
 				$request = rtrim($request, "\n\n");
-				echo $view->renderFile($this->mainView, array(
+				$response->content = $view->renderFile($this->mainView, array(
 					'exception' => $exception,
 					'request' => $request,
 				), $this);
diff --git a/framework/yii/base/Response.php b/framework/yii/base/Response.php
index 396b073..29bddb0 100644
--- a/framework/yii/base/Response.php
+++ b/framework/yii/base/Response.php
@@ -14,19 +14,28 @@ namespace yii\base;
 class Response extends Component
 {
 	/**
+	 * @event Event an event raised when the application begins to generate the response.
+	 */
+	const EVENT_BEGIN_RESPONSE = 'beginResponse';
+	/**
+	 * @event Event an event raised when the generation of the response finishes.
+	 */
+	const EVENT_END_RESPONSE = 'endResponse';
+
+	/**
 	 * Starts output buffering
 	 */
-	public function beginOutput()
+	public function beginBuffer()
 	{
 		ob_start();
 		ob_implicit_flush(false);
 	}
 
 	/**
-	 * Returns contents of the output buffer and discards it
+	 * Returns contents of the output buffer and stops the buffer.
 	 * @return string output buffer contents
 	 */
-	public function endOutput()
+	public function endBuffer()
 	{
 		return ob_get_clean();
 	}
@@ -35,16 +44,16 @@ class Response extends Component
 	 * Returns contents of the output buffer
 	 * @return string output buffer contents
 	 */
-	public function getOutput()
+	public function getBuffer()
 	{
 		return ob_get_contents();
 	}
 
 	/**
 	 * Discards the output buffer
-	 * @param boolean $all if true recursively discards all output buffers used
+	 * @param boolean $all if true, it will discards all output buffers.
 	 */
-	public function cleanOutput($all = true)
+	public function cleanBuffer($all = true)
 	{
 		if ($all) {
 			for ($level = ob_get_level(); $level > 0; --$level) {
@@ -56,4 +65,28 @@ class Response extends Component
 			ob_end_clean();
 		}
 	}
+
+	/**
+	 * Begins generating the response.
+	 * This method is called at the beginning of [[Application::run()]].
+	 * The default implementation will trigger the [[EVENT_BEGIN_RESPONSE]] event.
+	 * If you overwrite this method, make sure you call the parent implementation so that
+	 * the event can be triggered.
+	 */
+	public function begin()
+	{
+		$this->trigger(self::EVENT_BEGIN_RESPONSE);
+	}
+
+	/**
+	 * Ends generating the response.
+	 * This method is called at the end of [[Application::run()]].
+	 * The default implementation will trigger the [[EVENT_END_RESPONSE]] event.
+	 * If you overwrite this method, make sure you call the parent implementation so that
+	 * the event can be triggered.
+	 */
+	public function end()
+	{
+		$this->trigger(self::EVENT_END_RESPONSE);
+	}
 }
diff --git a/framework/yii/console/Response.php b/framework/yii/console/Response.php
new file mode 100644
index 0000000..34f105d
--- /dev/null
+++ b/framework/yii/console/Response.php
@@ -0,0 +1,17 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\console;
+
+/**
+ * @author Qiang Xue <qiang.xue@gmail.com>
+ * @since 2.0
+ */
+class Response extends \yii\base\Response
+{
+	
+}
diff --git a/framework/yii/db/Command.php b/framework/yii/db/Command.php
index 17accf4..a32e892 100644
--- a/framework/yii/db/Command.php
+++ b/framework/yii/db/Command.php
@@ -654,6 +654,32 @@ class Command extends \yii\base\Component
 	}
 
 	/**
+	 * Creates a SQL command for adding a primary key constraint to an existing table.
+	 * The method will properly quote the table and column names.
+	 * @param string $name the name of the primary key constraint.
+	 * @param string $table the table that the primary key constraint will be added to.
+	 * @param string|array $columns comma separated string or array of columns that the primary key will consist of.
+	 * @return Command the command object itself.
+	 */
+	public function addPrimaryKey($name, $table, $columns)
+	{
+		$sql = $this->db->getQueryBuilder()->addPrimaryKey($name, $table, $columns);
+		return $this->setSql($sql);
+	}
+
+	/**
+	 * Creates a SQL command for removing a primary key constraint to an existing table.
+	 * @param string $name the name of the primary key constraint to be removed.
+	 * @param string $table the table that the primary key constraint will be removed from.
+	 * @return Command the command object itself
+	 */
+	public function dropPrimaryKey($name, $table)
+	{
+		$sql = $this->db->getQueryBuilder()->dropPrimaryKey($name, $table);
+		return $this->setSql($sql);
+	}
+
+	/**
 	 * Creates a SQL command for adding a foreign key constraint to an existing table.
 	 * The method will properly quote the table and column names.
 	 * @param string $name the name of the foreign key constraint.
diff --git a/framework/yii/db/Migration.php b/framework/yii/db/Migration.php
index 774ac14..38b1bdc 100644
--- a/framework/yii/db/Migration.php
+++ b/framework/yii/db/Migration.php
@@ -310,6 +310,35 @@ class Migration extends \yii\base\Component
 	}
 
 	/**
+	 * Builds and executes a SQL statement for creating a primary key.
+	 * The method will properly quote the table and column names.
+	 * @param string $name the name of the primary key constraint.
+	 * @param string $table the table that the primary key constraint will be added to.
+	 * @param string|array $columns comma separated string or array of columns that the primary key will consist of.
+	 */
+	public function addPrimaryKey($name, $table, $columns)
+	{
+		echo "    > add primary key $name on $table (".(is_array($columns) ? implode(',',$columns) : $columns).") ...";
+		$time = microtime(true);
+		$this->db->createCommand()->addPrimaryKey($name, $table, $columns)->execute();
+		echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
+	}
+
+	/**
+	 * Builds and executes a SQL statement for dropping a primary key.
+	 * @param string $name the name of the primary key constraint to be removed.
+	 * @param string $table the table that the primary key constraint will be removed from.
+	 * @return Command the command object itself
+	 */
+	public function dropPrimaryKey($name, $table)
+	{
+		echo "    > drop primary key $name ...";
+		$time = microtime(true);
+		$this->db->createCommand()->dropPrimaryKey($name, $table)->execute();
+		echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
+	}
+
+	/**
 	 * Builds a SQL statement for adding a foreign key constraint to an existing table.
 	 * The method will properly quote the table and column names.
 	 * @param string $name the name of the foreign key constraint.
diff --git a/framework/yii/db/QueryBuilder.php b/framework/yii/db/QueryBuilder.php
index 04f1969..0d221bc 100644
--- a/framework/yii/db/QueryBuilder.php
+++ b/framework/yii/db/QueryBuilder.php
@@ -268,6 +268,41 @@ class QueryBuilder extends \yii\base\Object
 	{
 		return "DROP TABLE " . $this->db->quoteTableName($table);
 	}
+	
+	/**
+	 * Builds a SQL statement for adding a primary key constraint to an existing table.
+	 * @param string $name the name of the primary key constraint.
+	 * @param string $table the table that the primary key constraint will be added to.
+	 * @param string|array $columns comma separated string or array of columns that the primary key will consist of.
+	 * @return string the SQL statement for adding a primary key constraint to an existing table.
+	 */
+	public function addPrimaryKey($name, $table, $columns)
+	{
+		if (is_string($columns)) {
+			$columns=preg_split('/\s*,\s*/',$columns,-1,PREG_SPLIT_NO_EMPTY);
+		}
+		
+		foreach ($columns as $i=>$col) {
+			$columns[$i]=$this->db->quoteColumnName($col);
+		}
+		
+		return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ADD CONSTRAINT '
+			. $this->db->quoteColumnName($name) . '  PRIMARY KEY ('
+			. implode(', ', $columns). ' )';
+	}	
+	
+	/**
+	 * Builds a SQL statement for removing a primary key constraint to an existing table.
+	 * @param string $name the name of the primary key constraint to be removed.
+	 * @param string $table the table that the primary key constraint will be removed from.
+	 * @return string the SQL statement for removing a primary key constraint from an existing table.	 *
+	 */
+	public function dropPrimaryKey($name, $table)
+	{
+		return 'ALTER TABLE ' . $this->db->quoteTableName($table)
+			. ' DROP CONSTRAINT ' . $this->db->quoteColumnName($name);
+		
+	}	
 
 	/**
 	 * Builds a SQL statement for truncating a DB table.
diff --git a/framework/yii/db/mysql/QueryBuilder.php b/framework/yii/db/mysql/QueryBuilder.php
index 4b35e24..b4ac996 100644
--- a/framework/yii/db/mysql/QueryBuilder.php
+++ b/framework/yii/db/mysql/QueryBuilder.php
@@ -89,6 +89,17 @@ class QueryBuilder extends \yii\db\QueryBuilder
 	}
 
 	/**
+	 * Builds a SQL statement for removing a primary key constraint to an existing table.
+	 * @param string $name the name of the primary key constraint to be removed.
+	 * @param string $table the table that the primary key constraint will be removed from.
+	 * @return string the SQL statement for removing a primary key constraint from an existing table.
+	 */
+	public function dropPrimaryKey($name, $table)
+	{
+		return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' DROP PRIMARY KEY';
+	}
+
+	/**
 	 * Creates a SQL statement for resetting the sequence value of a table's primary key.
 	 * The sequence will be reset such that the primary key of the next new row inserted
 	 * will have the specified value or 1.
@@ -113,7 +124,7 @@ class QueryBuilder extends \yii\db\QueryBuilder
 		} elseif ($table === null) {
 			throw new InvalidParamException("Table not found: $tableName");
 		} else {
-			throw new InvalidParamException("There is not sequence associated with table '$tableName'.'");
+			throw new InvalidParamException("There is not sequence associated with table '$tableName'.");
 		}
 	}
 
diff --git a/framework/yii/db/pgsql/QueryBuilder.php b/framework/yii/db/pgsql/QueryBuilder.php
index 3417ad9..9701fd6 100644
--- a/framework/yii/db/pgsql/QueryBuilder.php
+++ b/framework/yii/db/pgsql/QueryBuilder.php
@@ -21,21 +21,20 @@ class QueryBuilder extends \yii\db\QueryBuilder
 	 * @var array mapping from abstract column types (keys) to physical column types (values).
 	 */
 	public $typeMap = array(
-	    Schema::TYPE_PK => 'serial not null primary key',
-	    Schema::TYPE_STRING => 'varchar',
-	    Schema::TYPE_TEXT => 'text',
-	    Schema::TYPE_SMALLINT => 'smallint',
-	    Schema::TYPE_INTEGER => 'integer',
-	    Schema::TYPE_BIGINT => 'bigint',
-	    Schema::TYPE_FLOAT => 'double precision',
-	    Schema::TYPE_DECIMAL => 'numeric',
-	    Schema::TYPE_DATETIME => 'timestamp',
-	    Schema::TYPE_TIMESTAMP => 'timestamp',
-	    Schema::TYPE_TIME => 'time',
-	    Schema::TYPE_DATE => 'date',
-	    Schema::TYPE_BINARY => 'bytea',
-	    Schema::TYPE_BOOLEAN => 'boolean',
-	    Schema::TYPE_MONEY => 'numeric(19,4)',
+		Schema::TYPE_PK => 'serial not null primary key',
+		Schema::TYPE_STRING => 'varchar(255)',
+		Schema::TYPE_TEXT => 'text',
+		Schema::TYPE_SMALLINT => 'smallint',
+		Schema::TYPE_INTEGER => 'integer',
+		Schema::TYPE_BIGINT => 'bigint',
+		Schema::TYPE_FLOAT => 'double precision',
+		Schema::TYPE_DECIMAL => 'numeric(10,0)',
+		Schema::TYPE_DATETIME => 'timestamp',
+		Schema::TYPE_TIMESTAMP => 'timestamp',
+		Schema::TYPE_TIME => 'time',
+		Schema::TYPE_DATE => 'date',
+		Schema::TYPE_BINARY => 'bytea',
+		Schema::TYPE_BOOLEAN => 'boolean',
+		Schema::TYPE_MONEY => 'numeric(19,4)',
 	);
-
 }
diff --git a/framework/yii/db/pgsql/Schema.php b/framework/yii/db/pgsql/Schema.php
index 8cfb535..8acb7bd 100644
--- a/framework/yii/db/pgsql/Schema.php
+++ b/framework/yii/db/pgsql/Schema.php
@@ -43,6 +43,7 @@ class Schema extends \yii\db\Schema
 		'circle' => self::TYPE_STRING,
 		'date' => self::TYPE_DATE,
 		'real' => self::TYPE_FLOAT,
+		'decimal' => self::TYPE_DECIMAL,
 		'double precision' => self::TYPE_DECIMAL,
 		'inet' => self::TYPE_STRING,
 		'smallint' => self::TYPE_SMALLINT,
@@ -55,7 +56,6 @@ class Schema extends \yii\db\Schema
 		'money' => self::TYPE_MONEY,
 		'name' => self::TYPE_STRING,
 		'numeric' => self::TYPE_STRING,
-		'numrange' => self::TYPE_DECIMAL,
 		'oid' => self::TYPE_BIGINT, // should not be used. it's pg internal!
 		'path' => self::TYPE_STRING,
 		'point' => self::TYPE_STRING,
@@ -165,11 +165,11 @@ SQL;
 			$columns = explode(',', $constraint['columns']);
 			$fcolumns = explode(',', $constraint['foreign_columns']);
 			if ($constraint['foreign_table_schema'] !== $this->defaultSchema) {
-				$foreign_table = $constraint['foreign_table_schema'] . '.' . $constraint['foreign_table_name'];
+				$foreignTable = $constraint['foreign_table_schema'] . '.' . $constraint['foreign_table_name'];
 			} else {
-				$foreign_table = $constraint['foreign_table_name'];
+				$foreignTable = $constraint['foreign_table_name'];
 			}
-			$citem = array($foreign_table);
+			$citem = array($foreignTable);
 			foreach ($columns as $idx => $column) {
 				$citem[] = array($fcolumns[$idx] => $column);
 			}
@@ -243,6 +243,9 @@ ORDER BY
 SQL;
 
 		$columns = $this->db->createCommand($sql)->queryAll();
+		if (empty($columns)) {
+			return false;
+		}
 		foreach ($columns as $column) {
 			$column = $this->loadColumnSchema($column);
 			$table->columns[$column->name] = $column;
@@ -285,5 +288,4 @@ SQL;
 		$column->phpType = $this->getColumnPhpType($column);
 		return $column;
 	}
-
 }
diff --git a/framework/yii/db/sqlite/QueryBuilder.php b/framework/yii/db/sqlite/QueryBuilder.php
index 52c101b..99198ae 100644
--- a/framework/yii/db/sqlite/QueryBuilder.php
+++ b/framework/yii/db/sqlite/QueryBuilder.php
@@ -179,4 +179,30 @@ class QueryBuilder extends \yii\db\QueryBuilder
 	{
 		throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
 	}
+	
+	/**
+	 * Builds a SQL statement for adding a primary key constraint to an existing table.
+	 * @param string $name the name of the primary key constraint.
+	 * @param string $table the table that the primary key constraint will be added to.
+	 * @param string|array $columns comma separated string or array of columns that the primary key will consist of.
+	 * @return string the SQL statement for adding a primary key constraint to an existing table.
+	 * @throws NotSupportedException this is not supported by SQLite
+	 */
+	public function addPrimaryKey($name, $table, $columns)
+	{
+		throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
+	}	
+	
+	/**
+	 * Builds a SQL statement for removing a primary key constraint to an existing table.
+	 * @param string $name the name of the primary key constraint to be removed.
+	 * @param string $table the table that the primary key constraint will be removed from.
+	 * @return string the SQL statement for removing a primary key constraint from an existing table.
+	 * @throws NotSupportedException this is not supported by SQLite	 *
+	 */
+	public function dropPrimaryKey($name, $table)
+	{
+		throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');		
+	}		
 }
+
diff --git a/framework/yii/helpers/base/FileHelper.php b/framework/yii/helpers/base/FileHelper.php
index 954c86e..fc1f91a 100644
--- a/framework/yii/helpers/base/FileHelper.php
+++ b/framework/yii/helpers/base/FileHelper.php
@@ -10,6 +10,7 @@
 namespace yii\helpers\base;
 
 use Yii;
+use yii\helpers\StringHelper;
 
 /**
  * Filesystem helper
@@ -95,7 +96,7 @@ class FileHelper
 			}
 		}
 
-		return $checkExtension ? self::getMimeTypeByExtension($file) : null;
+		return $checkExtension ? static::getMimeTypeByExtension($file) : null;
 	}
 
 	/**
@@ -133,12 +134,21 @@ class FileHelper
 	 *
 	 * - dirMode: integer, the permission to be set for newly copied directories. Defaults to 0777.
 	 * - fileMode:  integer, the permission to be set for newly copied files. Defaults to the current environment setting.
-	 * - beforeCopy: callback, a PHP callback that is called before copying each sub-directory or file.
-	 *   If the callback returns false, the copy operation for the sub-directory or file will be cancelled.
+	 * - filter: callback, a PHP callback that is called for each sub-directory or file.
+	 *   If the callback returns false, the the sub-directory or file will not be copied.
+	 *   The signature of the callback should be: `function ($path)`, where `$path` refers the full path to be copied.
+	 * - fileTypes: array, list of file name suffix (without dot). Only files with these suffixes will be copied.
+	 * - only: array, list of patterns that the files or directories should match if they want to be copied.
+	 *   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 name in "only" and "except", it will NOT be copied.
+	 * - except: array, list of patterns that the files or directories should NOT match if they want to be copied.
+	 *   For more details on how to specify the patterns, please refer to the "only" option.
+	 * - recursive: boolean, whether the files under the subdirectories should also be copied. Defaults to true.
+	 * - afterCopy: callback, a PHP callback that is called after each sub-directory or file is successfully copied.
 	 *   The signature of the callback should be: `function ($from, $to)`, where `$from` is the sub-directory or
- 	 *   file to be copied from, while `$to` is the copy target.
-	 * - afterCopy: callback, a PHP callback that is called after a sub-directory or file is successfully copied.
-	 *   The signature of the callback is similar to that of `beforeCopy`.
+	 *   file copied from, while `$to` is the copy target.
 	 */
 	public static function copyDirectory($src, $dst, $options = array())
 	{
@@ -153,7 +163,7 @@ class FileHelper
 			}
 			$from = $src . DIRECTORY_SEPARATOR . $file;
 			$to = $dst . DIRECTORY_SEPARATOR . $file;
-			if (!isset($options['beforeCopy']) || call_user_func($options['beforeCopy'], $from, $to)) {
+			if (static::filterPath($from, $options)) {
 				if (is_file($from)) {
 					copy($from, $to);
 					if (isset($options['fileMode'])) {
@@ -169,4 +179,129 @@ class FileHelper
 		}
 		closedir($handle);
 	}
+
+	/**
+	 * Removes a directory (and all its content) recursively.
+	 * @param string $dir the directory to be deleted recursively.
+	 */
+	public static function removeDirectory($dir)
+	{
+		if (!is_dir($dir) || !($handle = opendir($dir))) {
+			return;
+		}
+		while (($file = readdir($handle)) !== false) {
+			if ($file === '.' || $file === '..') {
+				continue;
+			}
+			$path = $dir . DIRECTORY_SEPARATOR . $file;
+			if (is_file($path)) {
+				unlink($path);
+			} else {
+				static::removeDirectory($path);
+			}
+		}
+		closedir($handle);
+		rmdir($dir);
+	}
+
+	/**
+	 * Returns the files found under the specified directory and subdirectories.
+	 * @param string $dir the directory under which the files will be looked for.
+	 * @param array $options options for file searching. Valid options are:
+	 *
+	 * - filter: callback, a PHP callback that is called for each sub-directory or file.
+	 *   If the callback returns false, the the sub-directory or file will be excluded from the returning result.
+	 *   The signature of the callback should be: `function ($path)`, where `$path` refers the full path to be filtered.
+	 * - fileTypes: array, list of file name suffix (without dot). Only files with these suffixes will be returned.
+	 * - only: array, list of patterns that the files or directories should match if they want to be returned.
+	 *   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 name in "only" and "except", it will NOT be returned.
+	 * - except: array, list of patterns that the files or directories should NOT match if they want to be returned.
+	 *   For more details on how to specify the patterns, please refer to the "only" option.
+	 * - recursive: boolean, whether the files under the subdirectories should also be lookied for. Defaults to true.
+	 * @return array files found under the directory. The file list is sorted.
+	 */
+	public static function findFiles($dir, $options = array())
+	{
+		$list = array();
+		$handle = opendir($dir);
+		while (($file = readdir($handle)) !== false) {
+			if ($file === '.' || $file === '..') {
+				continue;
+			}
+			$path = $dir . DIRECTORY_SEPARATOR . $file;
+			if (static::filterPath($path, $options)) {
+				if (is_file($path)) {
+					$list[] = $path;
+				} elseif (!isset($options['recursive']) || $options['recursive']) {
+					$list = array_merge($list, static::findFiles($path, $options));
+				}
+			}
+		}
+		closedir($handle);
+		return $list;
+	}
+
+	/**
+	 * Checks if the given file path satisfies the filtering options.
+	 * @param string $path the path of the file or directory to be checked
+	 * @param array $options the filtering options. See [[findFiles()]] for explanations of
+	 * the supported options.
+	 * @return boolean whether the file or directory satisfies the filtering options.
+	 */
+	public static function filterPath($path, $options)
+	{
+		if (isset($options['filter']) && !call_user_func($options['filter'], $path)) {
+			return false;
+		}
+		$path = str_replace('\\', '/', $path);
+		$n = StringHelper::strlen($path);
+		if (!empty($options['except'])) {
+			foreach ($options['except'] as $name) {
+				if (StringHelper::substr($path, -StringHelper::strlen($name), $n) === $name) {
+					return false;
+				}
+			}
+		}
+		if (!empty($options['only'])) {
+			foreach ($options['only'] as $name) {
+				if (StringHelper::substr($path, -StringHelper::strlen($name), $n) !== $name) {
+					return false;
+				}
+			}
+		}
+		if (!empty($options['fileTypes']) && is_file($path)) {
+			return in_array(pathinfo($path, PATHINFO_EXTENSION), $options['fileTypes']);
+		} else {
+			return true;
+		}
+	}
+
+	/**
+	 * Makes directory.
+	 *
+	 * This method is similar to the PHP `mkdir()` function except that
+	 * it uses `chmod()` to set the permission of the created directory
+	 * in order to avoid the impact of the `umask` setting.
+	 *
+	 * @param string $path path to be created.
+	 * @param integer $mode the permission to be set for created directory.
+	 * @param boolean $recursive whether to create parent directories if they do not exist.
+	 * @return boolean whether the directory is created successfully
+	 */
+	public static function mkdir($path, $mode = 0777, $recursive = true)
+	{
+		if (is_dir($path)) {
+			return true;
+		}
+		$parentDir = dirname($path);
+		if ($recursive && !is_dir($parentDir)) {
+			static::mkdir($parentDir, $mode, true);
+		}
+		$result = mkdir($path, $mode);
+		chmod($path, $mode);
+		return $result;
+	}
 }
diff --git a/framework/yii/helpers/base/StringHelper.php b/framework/yii/helpers/base/StringHelper.php
index 5134bf6..7fbb960 100644
--- a/framework/yii/helpers/base/StringHelper.php
+++ b/framework/yii/helpers/base/StringHelper.php
@@ -43,8 +43,10 @@ class StringHelper
 
 	/**
 	 * Returns the trailing name component of a path.
-	 * This method does the same as the php function basename() except that it will
+	 * This method does the same as the php function `basename()` except that it will
 	 * always use \ and / as directory separators, independent of the operating system.
+	 * This method was mainly created to work on php namespaces. When working with real
+	 * file paths, php's `basename()` should work fine for you.
 	 * Note: this method is not aware of the actual filesystem, or path components such as "..".
 	 * @param string $path A path string.
 	 * @param string $suffix If the name component ends in suffix this will also be cut off.
diff --git a/framework/yii/views/errorHandler/main.php b/framework/yii/views/errorHandler/main.php
index d7bbb3d..05e217e 100644
--- a/framework/yii/views/errorHandler/main.php
+++ b/framework/yii/views/errorHandler/main.php
@@ -14,7 +14,7 @@ $context = $this->context;
 	<meta charset="utf-8"/>
 
 	<title><?php
-		if ($exception instanceof \yii\base\HttpException) {
+		if ($exception instanceof \yii\web\HttpException) {
 			echo (int) $exception->statusCode . ' ' . $context->htmlEncode($exception->getName());
 		} elseif ($exception instanceof \yii\base\Exception) {
 			echo $context->htmlEncode($exception->getName() . ' – ' . get_class($exception));
@@ -362,7 +362,7 @@ pre .diff .change{
 		<?php else: ?>
 			<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAAA6CAIAAACPssguAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyBpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBXaW5kb3dzIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjcwRURBOUQxQzQ3RDExRTJCRjVFODJDQkFGODFDN0VBIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjcwRURBOUQyQzQ3RDExRTJCRjVFODJDQkFGODFDN0VBIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6NzBFREE5Q0ZDNDdEMTFFMkJGNUU4MkNCQUY4MUM3RUEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NzBFREE5RDBDNDdEMTFFMkJGNUU4MkNCQUY4MUM3RUEiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4L6mYsAAAE9klEQVR42tRa227aTBDGiwkhJtD8EanSQ6RKqKpSBbWqql5U7QP0TfsSvWhvStMEO6cqDUpjRylSQ1QC+IQN/mdrhBIb413vgsheIDDr9XwzOzPfzFrwPC/Fe8Cauiybe3uersNPQZJylYr04oUgCNyfJXJfcTAYXH/5YoH0qjq66LbbTrtdfPcOITTXALDuf/40FSWlabeuq6rpeZkHD6Ryma8dOOvDdV19Zycg/XBoml6r9ft9vk9EnNV/dOT8+RM1oXdxof/4wdfrEF/1G7Va+uIiagL8BfaBafMIAHy38/17/+/fydNgAkyDyXMHwLEsS1EmqH9kBOvgACbPFwDQaPfr11S9TjT7+JijEfgAsFst8/CQYv7BQe/6el4AQGTsfPuGbqSt2CGcnrarVS4hlRUAxETz9+/eyQntjXAL3MgeUhG7+vXtbSr1Dx+sqoYss3sCYlS/cXLinJ8nu713emqoKqMRmAD4xAElBQA3GpA62DwBsagfiIN7ecnyeHZyIbKoP4o46KurY2+Rrq7CeQ08YenZs0wmM1MAI+KQDv3VKpd7r1+L2WwQsGH0q9VCyN3dZrOrKPdevUpGsxMCmEAcMqVSaWNjcXExcN00zRbQ7BAAWMSs1ZY2N7NLSzMCMJk4ZBFaXl4OAxBF0Yza68fH3Z2dzNu3Ceo1lMB3raurScQhgqjBDhGiibS1v98zzVlEIax+CJ0TM9fYqAIXB9HRBsiFXq0myGuIVv2xxGGQNCaCVcG2tCGVDkBi4jBU80ThYFlwLVojICr1kxAHFLHRsWoNgzu5oABASBw8247ygVScZJhcyDIVuUDk6tf39oiIg+exUAOwMNiZfAVSAI7jGPv7sSUvhwLl/Jyqc0EEwA+dsR2H+EFWy4OdwdqERiACQNhxGIaaaCcWyDY3Zni7u2BzPgDoOg64Yo92YvJ43e2CzUlCKmIlDsSZ2FcG4Qq4faQoJO0jRKJ+uszFq+tWr5PkNRRPHM7O2IWBpdxGYxrkAk2DODihbAqKdJpNkRIDJhdxniDGEgdavgpSWp8+XX7+HADg9vsJCnDcPnr+XHr0KKpeE6fScdA0l1P7AIwAu2BxfR3qIYotREEcpj8mkwsxkjjs7iYmDuRdCXJykSuXx3YuxEji0O2mE0lvlEpWpZK6vWsxv2s0BCjeE1kVk4ujo2KlEvYEkZE4jMlBCK1sbUFdf7NCx22YlRVHURKuCeSiVpM2N8NGEFmJwzgAIH2hUAhYAD6vGQ6J/bOpe2/eBDoXiJ04jNm1CAVsDT8Zj7ijyAViJQ6zHPV6Z3s7kNfQrcylqlCVpuZ42IeHgbMpdJM4QD2auFc+mxE+m0JUHQfSqHd2Fsg7mMxxWjxwNiX43yBzNT9+9GSZj6I2NsKZH5e5Y9+hSODQ79+vfvjgP0K8SRzSvCw9jgvxJBe/fpmaln/yBAe3WXYcOHYuRmdTCFctmsah4zBjhtdo+D08BGHVhs1zd9Q/MgKIDcJjCwzyefv+/dRdG86/zItzvri+7knS3ZLefPgQPX7sMxSUy+W8ly+NtbW7Ir2+tjaoVHL5fDqdFgFENpstlMtQ/bdlOc3pJZLpjX6x6G1t/ff0KegdH1v96yV7EEk7nU6r1bJtm+PrVPx9FyFQd7FYBMa+sLAwBODnMgirAAM+p/EqLDcuJAiwbaCsgU+fsf8vwADjle3ME1OGEwAAAABJRU5ErkJggg==" alt="Attention"/>
 			<h1><?php
-				if ($exception instanceof \yii\base\HttpException) {
+				if ($exception instanceof \yii\web\HttpException) {
 					echo '<span>' . $context->createHttpStatusLink($exception->statusCode, $context->htmlEncode($exception->getName())) . '</span>';
 					echo ' &ndash; ' . $context->addTypeLinks(get_class($exception));
 				} elseif ($exception instanceof \yii\base\Exception) {
diff --git a/framework/yii/web/AccessControl.php b/framework/yii/web/AccessControl.php
index ce64533..7dedaf9 100644
--- a/framework/yii/web/AccessControl.php
+++ b/framework/yii/web/AccessControl.php
@@ -10,7 +10,7 @@ namespace yii\web;
 use Yii;
 use yii\base\Action;
 use yii\base\ActionFilter;
-use yii\base\HttpException;
+use yii\web\HttpException;
 
 /**
  * AccessControl provides simple access control based on a set of rules.
diff --git a/framework/yii/web/Application.php b/framework/yii/web/Application.php
index 12c9295..ce326a2 100644
--- a/framework/yii/web/Application.php
+++ b/framework/yii/web/Application.php
@@ -8,7 +8,7 @@
 namespace yii\web;
 
 use Yii;
-use yii\base\HttpException;
+use yii\web\HttpException;
 use yii\base\InvalidRouteException;
 
 /**
diff --git a/framework/yii/web/CaptchaAction.php b/framework/yii/web/CaptchaAction.php
index cff2314..1ed1fb0 100644
--- a/framework/yii/web/CaptchaAction.php
+++ b/framework/yii/web/CaptchaAction.php
@@ -277,11 +277,8 @@ class CaptchaAction extends Action
 
 		imagecolordeallocate($image, $foreColor);
 
-		header('Pragma: public');
-		header('Expires: 0');
-		header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
-		header('Content-Transfer-Encoding: binary');
-		header("Content-type: image/png");
+		$this->sendHttpHeaders();
+
 		imagepng($image);
 		imagedestroy($image);
 	}
@@ -319,12 +316,21 @@ class CaptchaAction extends Action
 			$x += (int)($fontMetrics['textWidth']) + $this->offset;
 		}
 
-		header('Pragma: public');
-		header('Expires: 0');
-		header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
-		header('Content-Transfer-Encoding: binary');
-		header("Content-type: image/png");
 		$image->setImageFormat('png');
-		echo $image;
+		Yii::$app->getResponse()->content = (string)$image;
+		$this->sendHttpHeaders();
+	}
+
+	/**
+	 * Sends the HTTP headers needed by image response.
+	 */
+	protected function sendHttpHeaders()
+	{
+		Yii::$app->getResponse()->getHeaders()
+			->set('Pragma', 'public')
+			->set('Expires', '0')
+			->set('Cache-Control', 'must-revalidate, post-check=0, pre-check=0')
+			->set('Content-Transfer-Encoding', 'binary')
+			->set('Content-type', 'image/png');
 	}
 }
diff --git a/framework/yii/web/Controller.php b/framework/yii/web/Controller.php
index 026c078..22a2ebd 100644
--- a/framework/yii/web/Controller.php
+++ b/framework/yii/web/Controller.php
@@ -8,7 +8,7 @@
 namespace yii\web;
 
 use Yii;
-use yii\base\HttpException;
+use yii\web\HttpException;
 use yii\base\InlineAction;
 
 /**
diff --git a/framework/yii/web/Cookie.php b/framework/yii/web/Cookie.php
index 610e5aa..8cbb412 100644
--- a/framework/yii/web/Cookie.php
+++ b/framework/yii/web/Cookie.php
@@ -45,7 +45,7 @@ class Cookie extends \yii\base\Object
 	 * By setting this property to true, the cookie will not be accessible by scripting languages,
 	 * such as JavaScript, which can effectively help to reduce identity theft through XSS attacks.
 	 */
-	public $httponly = false;
+	public $httpOnly = false;
 
 	/**
 	 * Magic method to turn a cookie object into a string without having to explicitly access [[value]].
diff --git a/framework/yii/web/CookieCollection.php b/framework/yii/web/CookieCollection.php
index fc9375e..3e22092 100644
--- a/framework/yii/web/CookieCollection.php
+++ b/framework/yii/web/CookieCollection.php
@@ -9,7 +9,8 @@ namespace yii\web;
 
 use Yii;
 use ArrayIterator;
-use yii\helpers\SecurityHelper;
+use yii\base\InvalidCallException;
+use yii\base\Object;
 
 /**
  * CookieCollection maintains the cookies available in the current request.
@@ -19,17 +20,12 @@ use yii\helpers\SecurityHelper;
  * @author Qiang Xue <qiang.xue@gmail.com>
  * @since 2.0
  */
-class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \ArrayAccess, \Countable
+class CookieCollection extends Object implements \IteratorAggregate, \ArrayAccess, \Countable
 {
 	/**
-	 * @var boolean whether to enable cookie validation. By setting this property to true,
-	 * if a cookie is tampered on the client side, it will be ignored when received on the server side.
+	 * @var boolean whether this collection is read only.
 	 */
-	public $enableValidation = true;
-	/**
-	 * @var string the secret key used for cookie validation. If not set, a random key will be generated and used.
-	 */
-	public $validationKey;
+	public $readOnly = false;
 
 	/**
 	 * @var Cookie[] the cookies in this collection (indexed by the cookie names)
@@ -38,12 +34,14 @@ class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \
 
 	/**
 	 * 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($config = array())
+	public function __construct($cookies = array(), $config = array())
 	{
+		$this->_cookies = $cookies;
 		parent::__construct($config);
-		$this->_cookies = $this->loadCookies();
 	}
 
 	/**
@@ -114,50 +112,53 @@ class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \
 	 * 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 (isset($this->_cookies[$cookie->name])) {
-			$c = $this->_cookies[$cookie->name];
-			setcookie($c->name, '', 0, $c->path, $c->domain, $c->secure, $c->httponly);
-		}
-
-		$value = $cookie->value;
-		if ($this->enableValidation) {
-			if ($this->validationKey === null) {
-				$key = SecurityHelper::getSecretKey(__CLASS__ . '/' . Yii::$app->id);
-			} else {
-				$key = $this->validationKey;
-			}
-			$value = SecurityHelper::hashData(serialize($value), $key);
+		if ($this->readOnly) {
+			throw new InvalidCallException('The cookie collection is read only.');
 		}
-
-		setcookie($cookie->name, $value, $cookie->expire, $cookie->path, $cookie->domain, $cookie->secure, $cookie->httponly);
 		$this->_cookies[$cookie->name] = $cookie;
 	}
 
 	/**
-	 * Removes a cookie from the collection.
+	 * 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)
+	public function remove($cookie, $removeFromBrowser = true)
 	{
-		if (is_string($cookie) && isset($this->_cookies[$cookie])) {
-			$cookie = $this->_cookies[$cookie];
+		if ($this->readOnly) {
+			throw new InvalidCallException('The cookie collection is read only.');
 		}
 		if ($cookie instanceof Cookie) {
-			setcookie($cookie->name, '', 0, $cookie->path, $cookie->domain, $cookie->secure, $cookie->httponly);
+			$cookie->expire = 1;
+			$cookie->value = '';
+		} else {
+			$cookie = new Cookie(array(
+				'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()
 	{
-		foreach ($this->_cookies as $cookie) {
-			setcookie($cookie->name, '', 0, $cookie->path, $cookie->domain, $cookie->secure, $cookie->httponly);
+		if ($this->readOnly) {
+			throw new InvalidCallException('The cookie collection is read only.');
 		}
 		$this->_cookies = array();
 	}
@@ -222,36 +223,4 @@ class CookieCollection extends \yii\base\Object implements \IteratorAggregate, \
 	{
 		$this->remove($name);
 	}
-
-	/**
-	 * Returns the current cookies in terms of [[Cookie]] objects.
-	 * @return Cookie[] list of current cookies
-	 */
-	protected function loadCookies()
-	{
-		$cookies = array();
-		if ($this->enableValidation) {
-			if ($this->validationKey === null) {
-				$key = SecurityHelper::getSecretKey(__CLASS__ . '/' . Yii::$app->id);
-			} else {
-				$key = $this->validationKey;
-			}
-			foreach ($_COOKIE as $name => $value) {
-				if (is_string($value) && ($value = SecurityHelper::validateData($value, $key)) !== false) {
-					$cookies[$name] = new Cookie(array(
-						'name' => $name,
-						'value' => @unserialize($value),
-					));
-				}
-			}
-		} else {
-			foreach ($_COOKIE as $name => $value) {
-				$cookies[$name] = new Cookie(array(
-					'name' => $name,
-					'value' => $value,
-				));
-			}
-		}
-		return $cookies;
-	}
 }
diff --git a/framework/yii/web/HeaderCollection.php b/framework/yii/web/HeaderCollection.php
index c7e1462..aa3e01f 100644
--- a/framework/yii/web/HeaderCollection.php
+++ b/framework/yii/web/HeaderCollection.php
@@ -79,11 +79,13 @@ class HeaderCollection extends Object implements \IteratorAggregate, \ArrayAcces
 	 * 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 HeaderCollection the collection object itself
 	 */
-	public function set($name, $value)
+	public function set($name, $value = '')
 	{
 		$name = strtolower($name);
 		$this->_headers[$name] = (array)$value;
+		return $this;
 	}
 
 	/**
@@ -92,11 +94,29 @@ class HeaderCollection extends Object implements \IteratorAggregate, \ArrayAcces
 	 * 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 HeaderCollection the collection object itself
 	 */
 	public function add($name, $value)
 	{
 		$name = strtolower($name);
 		$this->_headers[$name][] = $value;
+		return $this;
+	}
+
+	/**
+	 * Adds 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 HeaderCollection the collection object itself
+	 */
+	public function addDefault($name, $value)
+	{
+		$name = strtolower($name);
+		if (empty($this->_headers[$name])) {
+			$this->_headers[$name][] = $value;
+		}
+		return $this;
 	}
 
 	/**
diff --git a/framework/yii/web/HttpCache.php b/framework/yii/web/HttpCache.php
index 5b7682d..cc9e6ed 100644
--- a/framework/yii/web/HttpCache.php
+++ b/framework/yii/web/HttpCache.php
@@ -50,7 +50,7 @@ class HttpCache extends ActionFilter
 	/**
 	 * @var string HTTP cache control header. If null, the header will not be sent.
 	 */
-	public $cacheControlHeader = 'Cache-Control: max-age=3600, public';
+	public $cacheControlHeader = 'max-age=3600, public';
 
 	/**
 	 * This method is invoked right before an action is to be executed (after all possible filters.)
@@ -60,7 +60,7 @@ class HttpCache extends ActionFilter
 	 */
 	public function beforeAction($action)
 	{
-		$verb = Yii::$app->request->getMethod();
+		$verb = Yii::$app->getRequest()->getMethod();
 		if ($verb !== 'GET' && $verb !== 'HEAD' || $this->lastModified === null && $this->etagSeed === null) {
 			return true;
 		}
@@ -75,17 +75,18 @@ class HttpCache extends ActionFilter
 		}
 
 		$this->sendCacheControlHeader();
+		$response = Yii::$app->getResponse();
 		if ($etag !== null) {
-			header("ETag: $etag");
+			$response->getHeaders()->set('Etag', $etag);
 		}
 
 		if ($this->validateCache($lastModified, $etag)) {
-			header('HTTP/1.1 304 Not Modified');
+			$response->setStatusCode(304);
 			return false;
 		}
 
 		if ($lastModified !== null) {
-			header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $lastModified) . ' GMT');
+			$response->getHeaders()->set('Last-Modified', gmdate('D, d M Y H:i:s', $lastModified) . ' GMT');
 		}
 		return true;
 	}
@@ -113,9 +114,10 @@ class HttpCache extends ActionFilter
 	protected function sendCacheControlHeader()
 	{
 		session_cache_limiter('public');
-		header('Pragma:', true);
+		$headers = Yii::$app->getResponse()->getHeaders();
+		$headers->set('Pragma');
 		if ($this->cacheControlHeader !== null) {
-			header($this->cacheControlHeader, true);
+			$headers->set('Cache-Control', $this->cacheControlHeader);
 		}
 	}
 
diff --git a/framework/yii/base/HttpException.php b/framework/yii/web/HttpException.php
similarity index 92%
rename from framework/yii/base/HttpException.php
rename to framework/yii/web/HttpException.php
index cce0bb0..384a5b4 100644
--- a/framework/yii/base/HttpException.php
+++ b/framework/yii/web/HttpException.php
@@ -5,8 +5,10 @@
  * @license http://www.yiiframework.com/license/
  */
 
-namespace yii\base;
+namespace yii\web;
 
+use yii\base\UserException;
+use yii\web\Response;
 
 /**
  * HttpException represents an exception caused by an improper request of the end-user.
@@ -43,8 +45,8 @@ class HttpException extends UserException
 	 */
 	public function getName()
 	{
-		if (isset(\yii\web\Response::$statusTexts[$this->statusCode])) {
-			return \yii\web\Response::$statusTexts[$this->statusCode];
+		if (isset(Response::$httpStatuses[$this->statusCode])) {
+			return Response::$httpStatuses[$this->statusCode];
 		} else {
 			return 'Error';
 		}
diff --git a/framework/yii/web/Request.php b/framework/yii/web/Request.php
index 7cec044..afd2f8a 100644
--- a/framework/yii/web/Request.php
+++ b/framework/yii/web/Request.php
@@ -8,8 +8,9 @@
 namespace yii\web;
 
 use Yii;
-use yii\base\HttpException;
+use yii\web\HttpException;
 use yii\base\InvalidConfigException;
+use yii\helpers\SecurityHelper;
 
 /**
  * @author Qiang Xue <qiang.xue@gmail.com>
@@ -37,16 +38,12 @@ class Request extends \yii\base\Request
 	 * @var array the configuration of the CSRF cookie. This property is used only when [[enableCsrfValidation]] is true.
 	 * @see Cookie
 	 */
-	public $csrfCookie = array('httponly' => true);
+	public $csrfCookie = array('httpOnly' => true);
 	/**
 	 * @var boolean whether cookies should be validated to ensure they are not tampered. Defaults to true.
 	 */
 	public $enableCookieValidation = true;
 	/**
-	 * @var string the secret key used for cookie validation. If not set, a random key will be generated and used.
-	 */
-	public $cookieValidationKey;
-	/**
 	 * @var string|boolean the name of the POST parameter that is used to indicate if a request is a PUT or DELETE
 	 * request tunneled through POST. Default to '_method'.
 	 * @see getMethod
@@ -717,14 +714,64 @@ class Request extends \yii\base\Request
 	public function getCookies()
 	{
 		if ($this->_cookies === null) {
-			$this->_cookies = new CookieCollection(array(
-				'enableValidation' => $this->enableCookieValidation,
-				'validationKey' => $this->cookieValidationKey,
+			$this->_cookies = new CookieCollection($this->loadCookies(), array(
+				'readOnly' => true,
 			));
 		}
 		return $this->_cookies;
 	}
 
+	/**
+	 * Converts `$_COOKIE` into an array of [[Cookie]].
+	 * @return array the cookies obtained from request
+	 */
+	protected function loadCookies()
+	{
+		$cookies = array();
+		if ($this->enableCookieValidation) {
+			$key = $this->getCookieValidationKey();
+			foreach ($_COOKIE as $name => $value) {
+				if (is_string($value) && ($value = SecurityHelper::validateData($value, $key)) !== false) {
+					$cookies[$name] = new Cookie(array(
+						'name' => $name,
+						'value' => @unserialize($value),
+					));
+				}
+			}
+		} else {
+			foreach ($_COOKIE as $name => $value) {
+				$cookies[$name] = new Cookie(array(
+					'name' => $name,
+					'value' => $value,
+				));
+			}
+		}
+		return $cookies;
+	}
+
+	private $_cookieValidationKey;
+
+	/**
+	 * @return string the secret key used for cookie validation. If it was set previously,
+	 * a random key will be generated and used.
+	 */
+	public function getCookieValidationKey()
+	{
+		if ($this->_cookieValidationKey === null) {
+			$this->_cookieValidationKey = SecurityHelper::getSecretKey(__CLASS__ . '/' . Yii::$app->id);
+		}
+		return $this->_cookieValidationKey;
+	}
+
+	/**
+	 * Sets the secret key used for cookie validation.
+	 * @param string $value the secret key used for cookie validation.
+	 */
+	public function setCookieValidationKey($value)
+	{
+		$this->_cookieValidationKey = $value;
+	}
+
 	private $_csrfToken;
 
 	/**
diff --git a/framework/yii/web/Response.php b/framework/yii/web/Response.php
index 86978d5..051850f 100644
--- a/framework/yii/web/Response.php
+++ b/framework/yii/web/Response.php
@@ -8,11 +8,12 @@
 namespace yii\web;
 
 use Yii;
-use yii\base\HttpException;
+use yii\web\HttpException;
 use yii\base\InvalidParamException;
 use yii\helpers\FileHelper;
 use yii\helpers\Html;
 use yii\helpers\Json;
+use yii\helpers\SecurityHelper;
 use yii\helpers\StringHelper;
 
 /**
@@ -45,11 +46,10 @@ class Response extends \yii\base\Response
 	 * @var string the version of the HTTP protocol to use
 	 */
 	public $version = '1.0';
-
 	/**
 	 * @var array list of HTTP status codes and the corresponding texts
 	 */
-	public static $statusTexts = array(
+	public static $httpStatuses = array(
 		100 => 'Continue',
 		101 => 'Switching Protocols',
 		102 => 'Processing',
@@ -93,7 +93,7 @@ class Response extends \yii\base\Response
 		415 => 'Unsupported Media Type',
 		416 => 'Requested range unsatisfiable',
 		417 => 'Expectation failed',
-		418 => 'I’m a teapot',
+		418 => 'I\'m a teapot',
 		422 => 'Unprocessable entity',
 		423 => 'Locked',
 		424 => 'Method failure',
@@ -117,7 +117,10 @@ class Response extends \yii\base\Response
 		511 => 'Network Authentication Required',
 	);
 
-	private $_statusCode = 200;
+	/**
+	 * @var integer the HTTP status code to send with the response.
+	 */
+	private $_statusCode;
 	/**
 	 * @var HeaderCollection
 	 */
@@ -131,18 +134,38 @@ class Response extends \yii\base\Response
 		}
 	}
 
+	public function begin()
+	{
+		parent::begin();
+		$this->beginBuffer();
+	}
+
+	public function end()
+	{
+		$this->content .= $this->endBuffer();
+		$this->send();
+		parent::end();
+	}
+
+	/**
+	 * @return integer the HTTP status code to send with the response.
+	 */
 	public function getStatusCode()
 	{
 		return $this->_statusCode;
 	}
 
-	public function setStatusCode($value)
+	public function setStatusCode($value, $text = null)
 	{
 		$this->_statusCode = (int)$value;
-		if ($this->isInvalid()) {
+		if ($this->getIsInvalid()) {
 			throw new InvalidParamException("The HTTP status code is invalid: $value");
 		}
-		$this->statusText = isset(self::$statusTexts[$this->_statusCode]) ? self::$statusTexts[$this->_statusCode] : '';
+		if ($text === null) {
+			$this->statusText = isset(self::$httpStatuses[$this->_statusCode]) ? self::$httpStatuses[$this->_statusCode] : '';
+		} else {
+			$this->statusText = $text;
+		}
 	}
 
 	/**
@@ -160,15 +183,17 @@ class Response extends \yii\base\Response
 
 	public function renderJson($data)
 	{
-		$this->getHeaders()->set('content-type', 'application/json');
+		$this->getHeaders()->set('Content-Type', 'application/json');
 		$this->content = Json::encode($data);
+		$this->send();
 	}
 
 	public function renderJsonp($data, $callbackName)
 	{
-		$this->getHeaders()->set('content-type', 'text/javascript');
+		$this->getHeaders()->set('Content-Type', 'text/javascript');
 		$data = Json::encode($data);
 		$this->content = "$callbackName($data);";
+		$this->send();
 	}
 
 	/**
@@ -179,6 +204,25 @@ class Response extends \yii\base\Response
 	{
 		$this->sendHeaders();
 		$this->sendContent();
+
+		if (function_exists('fastcgi_finish_request')) {
+			fastcgi_finish_request();
+		} else {
+			for ($level = ob_get_level(); $level > 0; --$level) {
+				if (!@ob_end_flush()) {
+					ob_clean();
+				}
+			}
+			flush();
+		}
+	}
+
+	public function reset()
+	{
+		$this->_headers = null;
+		$this->_statusCode = null;
+		$this->statusText = null;
+		$this->content = null;
 	}
 
 	/**
@@ -186,13 +230,45 @@ class Response extends \yii\base\Response
 	 */
 	protected function sendHeaders()
 	{
-		header("HTTP/{$this->version} " . $this->getStatusCode() . " {$this->statusText}");
-		foreach ($this->_headers as $name => $values) {
-			foreach ($values as $value) {
-				header("$name: $value");
+		if (headers_sent()) {
+			return;
+		}
+		$statusCode = $this->getStatusCode();
+		if ($statusCode !== null) {
+			header("HTTP/{$this->version} $statusCode {$this->statusText}");
+		}
+		if ($this->_headers) {
+			$headers = $this->getHeaders();
+			foreach ($headers as $name => $values) {
+				foreach ($values as $value) {
+					header("$name: $value", false);
+				}
 			}
+			$headers->removeAll();
 		}
-		$this->_headers->removeAll();
+		$this->sendCookies();
+	}
+
+	/**
+	 * Sends the cookies to the client.
+	 */
+	protected function sendCookies()
+	{
+		if ($this->_cookies === null) {
+			return;
+		}
+		$request = Yii::$app->getRequest();
+		if ($request->enableCookieValidation) {
+			$validationKey = $request->getCookieValidationKey();
+		}
+		foreach ($this->getCookies() as $cookie) {
+			$value = $cookie->value;
+			if ($cookie->expire != 1  && isset($validationKey)) {
+				$value = SecurityHelper::hashData(serialize($value), $validationKey);
+			}
+			setcookie($cookie->name, $value, $cookie->expire, $cookie->path, $cookie->domain, $cookie->secure, $cookie->httpOnly);
+		}
+		$this->getCookies()->removeAll();
 	}
 
 	/**
@@ -205,89 +281,132 @@ class Response extends \yii\base\Response
 	}
 
 	/**
-	 * Sends a file to user.
-	 * @param string $fileName file name
-	 * @param string $content content to be set.
-	 * @param string $mimeType mime type of the content. If null, it will be guessed automatically based on the given file name.
-	 * @param boolean $terminate whether to terminate the current application after calling this method
-	 * @throws \yii\base\HttpException when range request is not satisfiable.
+	 * Sends a file to the browser.
+	 * @param string $filePath the path of the file to be sent.
+	 * @param string $attachmentName the file name shown to the user. If null, it will be determined from `$filePath`.
+	 * @param string $mimeType the MIME type of the content. If null, it will be guessed based on `$filePath`
 	 */
-	public function sendFile($fileName, $content, $mimeType = null, $terminate = true)
+	public function sendFile($filePath, $attachmentName = null, $mimeType = null)
 	{
-		if ($mimeType === null && (($mimeType = FileHelper::getMimeTypeByExtension($fileName)) === null)) {
+		if ($mimeType === null && ($mimeType = FileHelper::getMimeTypeByExtension($filePath)) === null) {
 			$mimeType = 'application/octet-stream';
 		}
+		if ($attachmentName === null) {
+			$attachmentName = basename($filePath);
+		}
+		$handle = fopen($filePath, 'rb');
+		$this->sendStreamAsFile($handle, $attachmentName, $mimeType);
+	}
 
-		$fileSize = StringHelper::strlen($content);
-		$contentStart = 0;
-		$contentEnd = $fileSize - 1;
-
-		// tell the client that we accept range requests
-		header('Accept-Ranges: bytes');
+	/**
+	 * Sends the specified content as a file to the browser.
+	 * @param string $content the content to be sent. The existing [[content]] will be discarded.
+	 * @param string $attachmentName the file name shown to the user.
+	 * @param string $mimeType the MIME type of the content.
+	 */
+	public function sendContentAsFile($content, $attachmentName, $mimeType = 'application/octet-stream')
+	{
+		$this->getHeaders()
+			->addDefault('Pragma', 'public')
+			->addDefault('Accept-Ranges', 'bytes')
+			->addDefault('Expires', '0')
+			->addDefault('Content-Type', $mimeType)
+			->addDefault('Cache-Control', 'must-revalidate, post-check=0, pre-check=0')
+			->addDefault('Content-Transfer-Encoding', 'binary')
+			->addDefault('Content-Length', StringHelper::strlen($content))
+			->addDefault('Content-Disposition', "attachment; filename=\"$attachmentName\"");
+
+		$this->content = $content;
+		$this->send();
+	}
 
-		if (isset($_SERVER['HTTP_RANGE'])) {
-			// client sent us a multibyte range, can not hold this one for now
-			if (strpos($_SERVER['HTTP_RANGE'], ',') !== false) {
-				header("Content-Range: bytes $contentStart-$contentEnd/$fileSize");
-				throw new HttpException(416, 'Requested Range Not Satisfiable');
-			}
+	/**
+	 * Sends the specified stream as a file to the browser.
+	 * @param resource $handle the handle of the stream to be sent.
+	 * @param string $attachmentName the file name shown to the user.
+	 * @param string $mimeType the MIME type of the stream content.
+	 * @throws HttpException if the requested range cannot be satisfied.
+	 */
+	public function sendStreamAsFile($handle, $attachmentName, $mimeType = 'application/octet-stream')
+	{
+		$headers = $this->getHeaders();
+		fseek($handle, 0, SEEK_END);
+		$fileSize = ftell($handle);
+
+		$range = $this->getHttpRange($fileSize);
+		if ($range === false) {
+			$headers->set('Content-Range', "bytes */$fileSize");
+			throw new HttpException(416, Yii::t('yii', 'Requested range not satisfiable'));
+		}
 
-			$range = str_replace('bytes=', '', $_SERVER['HTTP_RANGE']);
+		list($begin, $end) = $range;
+		if ($begin !=0 || $end != $fileSize - 1) {
+			$this->setStatusCode(206);
+			$headers->set('Content-Range', "bytes $begin-$end/$fileSize");
+		} else {
+			$this->setStatusCode(200);
+		}
 
-			// range requests starts from "-", so it means that data must be dumped the end point.
-			if ($range[0] === '-') {
-				$contentStart = $fileSize - substr($range, 1);
-			} else {
-				$range = explode('-', $range);
-				$contentStart = $range[0];
+		if (isset($options['mimeType'])) {
+			$headers->set('Content-Type', $options['mimeType']);
+		}
 
-				// check if the last-byte-pos presents in header
-				if ((isset($range[1]) && is_numeric($range[1]))) {
-					$contentEnd = $range[1];
-				}
+		$length = $end - $begin + 1;
+
+		$headers->addDefault('Pragma', 'public')
+			->addDefault('Accept-Ranges', 'bytes')
+			->addDefault('Expires', '0')
+			->addDefault('Content-Type', $mimeType)
+			->addDefault('Cache-Control', 'must-revalidate, post-check=0, pre-check=0')
+			->addDefault('Content-Transfer-Encoding', 'binary')
+			->addDefault('Content-Length', $length)
+			->addDefault('Content-Disposition', "attachment; filename=\"$attachmentName\"");
+
+		$this->send();
+
+		fseek($handle, $begin);
+		set_time_limit(0); // Reset time limit for big files
+		$chunkSize = 8 * 1024 * 1024; // 8MB per chunk
+		while (!feof($handle) && ($pos = ftell($handle)) <= $end) {
+			if ($pos + $chunkSize > $end) {
+				$chunkSize = $end - $pos + 1;
 			}
+			echo fread($handle, $chunkSize);
+			flush(); // Free up memory. Otherwise large files will trigger PHP's memory limit.
+		}
+		fclose($handle);
+	}
 
-			/* Check the range and make sure it's treated according to the specs.
-			 * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
-			 */
-			// End bytes can not be larger than $end.
-			$contentEnd = ($contentEnd > $fileSize) ? $fileSize - 1 : $contentEnd;
-
-			// Validate the requested range and return an error if it's not correct.
-			$wrongContentStart = ($contentStart > $contentEnd || $contentStart > $fileSize - 1 || $contentStart < 0);
-
-			if ($wrongContentStart) {
-				header("Content-Range: bytes $contentStart-$contentEnd/$fileSize");
-				throw new HttpException(416, 'Requested Range Not Satisfiable');
+	/**
+	 * Determines the HTTP range given in the request.
+	 * @param integer $fileSize the size of the file that will be used to validate the requested HTTP range.
+	 * @return array|boolean the range (begin, end), or false if the range request is invalid.
+	 */
+	protected function getHttpRange($fileSize)
+	{
+		if (!isset($_SERVER['HTTP_RANGE']) || $_SERVER['HTTP_RANGE'] === '-') {
+			return array(0, $fileSize - 1);
+		}
+		if (!preg_match('/^bytes=(\d*)-(\d*)$/', $_SERVER['HTTP_RANGE'], $matches)) {
+			return false;
+		}
+		if ($matches[1] === '') {
+			$start = $fileSize - $matches[2];
+			$end = $fileSize - 1;
+		} elseif ($matches[2] !== '') {
+			$start = $matches[1];
+			$end = $matches[2];
+			if ($end >= $fileSize) {
+				$end = $fileSize - 1;
 			}
-
-			header('HTTP/1.1 206 Partial Content');
-			header("Content-Range: bytes $contentStart-$contentEnd/$fileSize");
 		} else {
-			header('HTTP/1.1 200 OK');
+			$start = $matches[1];
+			$end = $fileSize - 1;
 		}
-
-		$length = $contentEnd - $contentStart + 1; // Calculate new content length
-
-		header('Pragma: public');
-		header('Expires: 0');
-		header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
-		header('Content-Type: ' . $mimeType);
-		header('Content-Length: ' . $length);
-		header('Content-Disposition: attachment; filename="' . $fileName . '"');
-		header('Content-Transfer-Encoding: binary');
-		$content = StringHelper::substr($content, $contentStart, $length);
-
-		if ($terminate) {
-			// clean up the application first because the file downloading could take long time
-			// which may cause timeout of some resources (such as DB connection)
-			ob_start();
-			Yii::$app->end(0, false);
-			ob_end_clean();
-			echo $content;
-			exit(0);
+		if ($start < 0 || $start > $end) {
+			return false;
 		} else {
-			echo $content;
+			return array($start, $end);
 		}
 	}
 
@@ -305,86 +424,58 @@ class Response extends \yii\base\Response
 	 * specified by that header using web server internals including all optimizations like caching-headers.
 	 *
 	 * As this header directive is non-standard different directives exists for different web servers applications:
-	 * <ul>
-	 * <li>Apache: {@link http://tn123.org/mod_xsendfile X-Sendfile}</li>
-	 * <li>Lighttpd v1.4: {@link http://redmine.lighttpd.net/projects/lighttpd/wiki/X-LIGHTTPD-send-file X-LIGHTTPD-send-file}</li>
-	 * <li>Lighttpd v1.5: {@link http://redmine.lighttpd.net/projects/lighttpd/wiki/X-LIGHTTPD-send-file X-Sendfile}</li>
-	 * <li>Nginx: {@link http://wiki.nginx.org/XSendfile X-Accel-Redirect}</li>
-	 * <li>Cherokee: {@link http://www.cherokee-project.com/doc/other_goodies.html#x-sendfile X-Sendfile and X-Accel-Redirect}</li>
-	 * </ul>
+	 * 
+	 * - Apache: [X-Sendfile](http://tn123.org/mod_xsendfile) 
+	 * - Lighttpd v1.4: [X-LIGHTTPD-send-file](http://redmine.lighttpd.net/projects/lighttpd/wiki/X-LIGHTTPD-send-file)
+	 * - Lighttpd v1.5: [X-Sendfile](http://redmine.lighttpd.net/projects/lighttpd/wiki/X-LIGHTTPD-send-file)
+	 * - Nginx: [X-Accel-Redirect](http://wiki.nginx.org/XSendfile)
+	 * - Cherokee: [X-Sendfile and X-Accel-Redirect](http://www.cherokee-project.com/doc/other_goodies.html#x-sendfile)
+	 *
 	 * So for this method to work the X-SENDFILE option/module should be enabled by the web server and
 	 * a proper xHeader should be sent.
 	 *
-	 * <b>Note:</b>
-	 * This option allows to download files that are not under web folders, and even files that are otherwise protected (deny from all) like .htaccess
+	 * **Note**
+	 * 
+	 * This option allows to download files that are not under web folders, and even files that are otherwise protected 
+	 * (deny from all) like `.htaccess`.
 	 *
-	 * <b>Side effects</b>:
+	 * **Side effects**
+	 * 
 	 * If this option is disabled by the web server, when this method is called a download configuration dialog
 	 * will open but the downloaded file will have 0 bytes.
 	 *
-	 * <b>Known issues</b>:
+	 * **Known issues**
+	 * 
 	 * There is a Bug with Internet Explorer 6, 7 and 8 when X-SENDFILE is used over an SSL connection, it will show
-	 * an error message like this: "Internet Explorer was not able to open this Internet site. The requested site is either unavailable or cannot be found.".
-	 * You can work around this problem by removing the <code>Pragma</code>-header.
+	 * an error message like this: "Internet Explorer was not able to open this Internet site. The requested site 
+	 * is either unavailable or cannot be found.". You can work around this problem by removing the `Pragma`-header.
+	 *
+	 * **Example**
+	 * 
+	 * ~~~
+	 * Yii::app()->request->xSendFile('/home/user/Pictures/picture1.jpg');
+	 * ~~~
 	 *
-	 * <b>Example</b>:
-	 * <pre>
-	 * <?php
-	 *    Yii::app()->request->xSendFile('/home/user/Pictures/picture1.jpg', array(
-	 *        'saveName' => 'image1.jpg',
-	 *        'mimeType' => 'image/jpeg',
-	 *        'terminate' => false,
-	 *    ));
-	 * ?>
-	 * </pre>
 	 * @param string $filePath file name with full path
-	 * @param array $options additional options:
-	 * <ul>
-	 * <li>saveName: file name shown to the user, if not set real file name will be used</li>
-	 * <li>mimeType: mime type of the file, if not set it will be guessed automatically based on the file name, if set to null no content-type header will be sent.</li>
-	 * <li>xHeader: appropriate x-sendfile header, defaults to "X-Sendfile"</li>
-	 * <li>terminate: whether to terminate the current application after calling this method, defaults to true</li>
-	 * <li>forceDownload: specifies whether the file will be downloaded or shown inline, defaults to true</li>
-	 * <li>addHeaders: an array of additional http headers in header-value pairs</li>
-	 * </ul>
-	 * @todo
-	 */
-	public function xSendFile($filePath, $options = array())
+	 * @param string $mimeType the MIME type of the file. If null, it will be determined based on `$filePath`.
+	 * @param string $attachmentName file name shown to the user. If null, it will be determined from `$filePath`.
+	 * @param string $xHeader the name of the x-sendfile header.
+	 */
+	public function xSendFile($filePath, $attachmentName = null, $mimeType = null, $xHeader = 'X-Sendfile')
 	{
-		if (!isset($options['forceDownload']) || $options['forceDownload']) {
-			$disposition = 'attachment';
-		} else {
-			$disposition = 'inline';
-		}
-
-		if (!isset($options['saveName'])) {
-			$options['saveName'] = basename($filePath);
-		}
-
-		if (!isset($options['mimeType'])) {
-			if (($options['mimeType'] = FileHelper::getMimeTypeByExtension($filePath)) === null) {
-				$options['mimeType'] = 'text/plain';
-			}
+		if ($mimeType === null && ($mimeType = FileHelper::getMimeTypeByExtension($filePath)) === null) {
+			$mimeType = 'application/octet-stream';
 		}
-
-		if (!isset($options['xHeader'])) {
-			$options['xHeader'] = 'X-Sendfile';
+		if ($attachmentName === null) {
+			$attachmentName = basename($filePath);
 		}
 
-		if ($options['mimeType'] !== null) {
-			header('Content-type: ' . $options['mimeType']);
-		}
-		header('Content-Disposition: ' . $disposition . '; filename="' . $options['saveName'] . '"');
-		if (isset($options['addHeaders'])) {
-			foreach ($options['addHeaders'] as $header => $value) {
-				header($header . ': ' . $value);
-			}
-		}
-		header(trim($options['xHeader']) . ': ' . $filePath);
+		$this->getHeaders()
+			->addDefault($xHeader, $filePath)
+			->addDefault('Content-Type', $mimeType)
+			->addDefault('Content-Disposition', "attachment; filename=\"$attachmentName\"");
 
-		if (!isset($options['terminate']) || $options['terminate']) {
-			Yii::$app->end();
-		}
+		$this->send();
 	}
 
 	/**
@@ -422,7 +513,8 @@ class Response extends \yii\base\Response
 		if (Yii::$app->getRequest()->getIsAjax()) {
 			$statusCode = $this->ajaxRedirectCode;
 		}
-		header('Location: ' . $url, true, $statusCode);
+		$this->getHeaders()->set('Location', $url);
+		$this->setStatusCode($statusCode);
 		if ($terminate) {
 			Yii::$app->end();
 		}
@@ -441,6 +533,8 @@ class Response extends \yii\base\Response
 		$this->redirect(Yii::$app->getRequest()->getUrl() . $anchor, $terminate);
 	}
 
+	private $_cookies;
+
 	/**
 	 * Returns the cookie collection.
 	 * Through the returned cookie collection, you add or remove cookies as follows,
@@ -462,13 +556,16 @@ class Response extends \yii\base\Response
 	 */
 	public function getCookies()
 	{
-		return Yii::$app->getRequest()->getCookies();
+		if ($this->_cookies === null) {
+			$this->_cookies = new CookieCollection;
+		}
+		return $this->_cookies;
 	}
 
 	/**
 	 * @return boolean whether this response has a valid [[statusCode]].
 	 */
-	public function isInvalid()
+	public function getIsInvalid()
 	{
 		return $this->getStatusCode() < 100 || $this->getStatusCode() >= 600;
 	}
@@ -476,15 +573,15 @@ class Response extends \yii\base\Response
 	/**
 	 * @return boolean whether this response is informational
 	 */
-	public function isInformational()
+	public function getIsInformational()
 	{
 		return $this->getStatusCode() >= 100 && $this->getStatusCode() < 200;
 	}
 
 	/**
-	 * @return boolean whether this response is successfully
+	 * @return boolean whether this response is successful
 	 */
-	public function isSuccessful()
+	public function getIsSuccessful()
 	{
 		return $this->getStatusCode() >= 200 && $this->getStatusCode() < 300;
 	}
@@ -492,7 +589,7 @@ class Response extends \yii\base\Response
 	/**
 	 * @return boolean whether this response is a redirection
 	 */
-	public function isRedirection()
+	public function getIsRedirection()
 	{
 		return $this->getStatusCode() >= 300 && $this->getStatusCode() < 400;
 	}
@@ -500,7 +597,7 @@ class Response extends \yii\base\Response
 	/**
 	 * @return boolean whether this response indicates a client error
 	 */
-	public function isClientError()
+	public function getIsClientError()
 	{
 		return $this->getStatusCode() >= 400 && $this->getStatusCode() < 500;
 	}
@@ -508,7 +605,7 @@ class Response extends \yii\base\Response
 	/**
 	 * @return boolean whether this response indicates a server error
 	 */
-	public function isServerError()
+	public function getIsServerError()
 	{
 		return $this->getStatusCode() >= 500 && $this->getStatusCode() < 600;
 	}
@@ -516,7 +613,7 @@ class Response extends \yii\base\Response
 	/**
 	 * @return boolean whether this response is OK
 	 */
-	public function isOk()
+	public function getIsOk()
 	{
 		return 200 === $this->getStatusCode();
 	}
@@ -524,7 +621,7 @@ class Response extends \yii\base\Response
 	/**
 	 * @return boolean whether this response indicates the current request is forbidden
 	 */
-	public function isForbidden()
+	public function getIsForbidden()
 	{
 		return 403 === $this->getStatusCode();
 	}
@@ -532,7 +629,7 @@ class Response extends \yii\base\Response
 	/**
 	 * @return boolean whether this response indicates the currently requested resource is not found
 	 */
-	public function isNotFound()
+	public function getIsNotFound()
 	{
 		return 404 === $this->getStatusCode();
 	}
@@ -540,7 +637,7 @@ class Response extends \yii\base\Response
 	/**
 	 * @return boolean whether this response is empty
 	 */
-	public function isEmpty()
+	public function getIsEmpty()
 	{
 		return in_array($this->getStatusCode(), array(201, 204, 304));
 	}
diff --git a/framework/yii/web/Session.php b/framework/yii/web/Session.php
index 1b48433..cf1fa21 100644
--- a/framework/yii/web/Session.php
+++ b/framework/yii/web/Session.php
@@ -63,7 +63,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
 	 * @var array parameter-value pairs to override default session cookie parameters
 	 */
 	public $cookieParams = array(
-		'httponly' => true
+		'httpOnly' => true
 	);
 
 	/**
@@ -241,26 +241,31 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
 	 */
 	public function getCookieParams()
 	{
-		return session_get_cookie_params();
+		$params = session_get_cookie_params();
+		if (isset($params['httponly'])) {
+			$params['httpOnly'] = $params['httponly'];
+			unset($params['httponly']);
+		}
+		return $params;
 	}
 
 	/**
 	 * Sets the session cookie parameters.
 	 * The effect of this method only lasts for the duration of the script.
 	 * Call this method before the session starts.
-	 * @param array $value cookie parameters, valid keys include: lifetime, path, domain, secure and httponly.
+	 * @param array $value cookie parameters, valid keys include: `lifetime`, `path`, `domain`, `secure` and `httpOnly`.
 	 * @throws InvalidParamException if the parameters are incomplete.
 	 * @see http://us2.php.net/manual/en/function.session-set-cookie-params.php
 	 */
 	public function setCookieParams($value)
 	{
-		$data = session_get_cookie_params();
+		$data = $this->getCookieParams();
 		extract($data);
 		extract($value);
-		if (isset($lifetime, $path, $domain, $secure, $httponly)) {
-			session_set_cookie_params($lifetime, $path, $domain, $secure, $httponly);
+		if (isset($lifetime, $path, $domain, $secure, $httpOnly)) {
+			session_set_cookie_params($lifetime, $path, $domain, $secure, $httpOnly);
 		} else {
-			throw new InvalidParamException('Please make sure these parameters are provided: lifetime, path, domain, secure and httponly.');
+			throw new InvalidParamException('Please make sure these parameters are provided: lifetime, path, domain, secure and httpOnly.');
 		}
 	}
 
diff --git a/framework/yii/web/User.php b/framework/yii/web/User.php
index 005f987..f273c1a 100644
--- a/framework/yii/web/User.php
+++ b/framework/yii/web/User.php
@@ -9,7 +9,7 @@ namespace yii\web;
 
 use Yii;
 use yii\base\Component;
-use yii\base\HttpException;
+use yii\web\HttpException;
 use yii\base\InvalidConfigException;
 
 /**
@@ -56,7 +56,7 @@ class User extends Component
 	 * @var array the configuration of the identity cookie. This property is used only when [[enableAutoLogin]] is true.
 	 * @see Cookie
 	 */
-	public $identityCookie = array('name' => '_identity', 'httponly' => true);
+	public $identityCookie = array('name' => '_identity', 'httpOnly' => true);
 	/**
 	 * @var integer the number of seconds in which the user will be logged out automatically if he
 	 * remains inactive. If this property is not set, the user will be logged out after
diff --git a/framework/yii/web/VerbFilter.php b/framework/yii/web/VerbFilter.php
index ca6d47d..a3fd662 100644
--- a/framework/yii/web/VerbFilter.php
+++ b/framework/yii/web/VerbFilter.php
@@ -10,7 +10,7 @@ namespace yii\web;
 use Yii;
 use yii\base\ActionEvent;
 use yii\base\Behavior;
-use yii\base\HttpException;
+use yii\web\HttpException;
 
 /**
  * VerbFilter is an action filter that filters by HTTP request methods.
@@ -70,7 +70,7 @@ class VerbFilter extends Behavior
 	/**
 	 * @param ActionEvent $event
 	 * @return boolean
-	 * @throws \yii\base\HttpException when the request method is not allowed.
+	 * @throws HttpException when the request method is not allowed.
 	 */
 	public function beforeAction($event)
 	{
@@ -81,7 +81,7 @@ class VerbFilter extends Behavior
 			if (!in_array($verb, $allowed)) {
 				$event->isValid = false;
 				// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.7
-				header('Allow: ' . implode(', ', $allowed));
+				Yii::$app->getResponse()->getHeaders()->set('Allow', implode(', ', $allowed));
 				throw new HttpException(405, 'Method Not Allowed. This url can only handle the following request methods: ' . implode(', ', $allowed));
 			}
 		}
diff --git a/tests/unit/data/mysql.sql b/tests/unit/data/mysql.sql
index 1bb5558..2e9458e 100644
--- a/tests/unit/data/mysql.sql
+++ b/tests/unit/data/mysql.sql
@@ -9,6 +9,14 @@ DROP TABLE IF EXISTS tbl_order CASCADE;
 DROP TABLE IF EXISTS tbl_category CASCADE;
 DROP TABLE IF EXISTS tbl_customer CASCADE;
 DROP TABLE IF EXISTS tbl_type CASCADE;
+DROP TABLE IF EXISTS tbl_constraints CASCADE;
+
+CREATE TABLE `tbl_constraints`
+(
+  `id` integer not null,
+  `field1` varchar(255)
+);
+
 
 CREATE TABLE `tbl_customer` (
   `id` int(11) NOT NULL AUTO_INCREMENT,
diff --git a/tests/unit/data/postgres.sql b/tests/unit/data/postgres.sql
index 52fad0f..f8fb0eb 100644
--- a/tests/unit/data/postgres.sql
+++ b/tests/unit/data/postgres.sql
@@ -10,6 +10,13 @@ DROP TABLE IF EXISTS tbl_order CASCADE;
 DROP TABLE IF EXISTS tbl_category CASCADE;
 DROP TABLE IF EXISTS tbl_customer CASCADE;
 DROP TABLE IF EXISTS tbl_type CASCADE;
+DROP TABLE IF EXISTS tbl_constraints CASCADE;
+
+CREATE TABLE tbl_constraints
+(
+  id integer not null,
+  field1 varchar(255)
+);
 
 CREATE TABLE tbl_customer (
   id serial not null primary key,
diff --git a/tests/unit/data/web/data.txt b/tests/unit/data/web/data.txt
new file mode 100644
index 0000000..8e58281
--- /dev/null
+++ b/tests/unit/data/web/data.txt
@@ -0,0 +1 @@
+12ёжик3456798áèabcdefghijklmnopqrstuvwxyz!"§$%&/(ёжик)=?
\ No newline at end of file
diff --git a/tests/unit/framework/db/QueryBuilderTest.php b/tests/unit/framework/db/QueryBuilderTest.php
index 7dc4731..869b501 100644
--- a/tests/unit/framework/db/QueryBuilderTest.php
+++ b/tests/unit/framework/db/QueryBuilderTest.php
@@ -7,23 +7,26 @@ use yii\db\Schema;
 use yii\db\mysql\QueryBuilder as MysqlQueryBuilder;
 use yii\db\sqlite\QueryBuilder as SqliteQueryBuilder;
 use yii\db\mssql\QueryBuilder as MssqlQueryBuilder;
+use yii\db\pgsql\QueryBuilder as PgsqlQueryBuilder;
 
 class QueryBuilderTest extends DatabaseTestCase
 {
+
 	/**
 	 * @throws \Exception
 	 * @return QueryBuilder
 	 */
 	protected function getQueryBuilder()
 	{
-		switch($this->driverName)
-		{
+		switch ($this->driverName) {
 			case 'mysql':
 				return new MysqlQueryBuilder($this->getConnection());
 			case 'sqlite':
 				return new SqliteQueryBuilder($this->getConnection());
 			case 'mssql':
 				return new MssqlQueryBuilder($this->getConnection());
+			case 'pgsql':
+				return new PgsqlQueryBuilder($this->getConnection());
 		}
 		throw new \Exception('Test is not implemented for ' . $this->driverName);
 	}
@@ -95,15 +98,31 @@ class QueryBuilderTest extends DatabaseTestCase
 		);
 	}
 
-	/**
-	 *
-	 */
 	public function testGetColumnType()
 	{
 		$qb = $this->getQueryBuilder();
-		foreach($this->columnTypes() as $item) {
+		foreach ($this->columnTypes() as $item) {
 			list ($column, $expected) = $item;
 			$this->assertEquals($expected, $qb->getColumnType($column));
 		}
 	}
+
+	public function testAddDropPrimayKey()
+	{
+		$tableName = 'tbl_constraints';
+		$pkeyName = $tableName . "_pkey";
+		
+		// ADD
+		$qb = $this->getQueryBuilder();
+		$qb->db->createCommand()->addPrimaryKey($pkeyName, $tableName, array('id'))->execute();
+		$tableSchema = $qb->db->getSchema()->getTableSchema($tableName);
+		$this->assertEquals(1, count($tableSchema->primaryKey));
+
+		//DROP
+		$qb->db->createCommand()->dropPrimaryKey($pkeyName, $tableName)->execute();
+		$qb = $this->getQueryBuilder(); // resets the schema
+		$tableSchema = $qb->db->getSchema()->getTableSchema($tableName);
+		$this->assertEquals(0, count($tableSchema->primaryKey));		
+	}
+
 }
diff --git a/tests/unit/framework/db/pgsql/PostgreSQLQueryBuilderTest.php b/tests/unit/framework/db/pgsql/PostgreSQLQueryBuilderTest.php
new file mode 100644
index 0000000..9c5d1e1
--- /dev/null
+++ b/tests/unit/framework/db/pgsql/PostgreSQLQueryBuilderTest.php
@@ -0,0 +1,76 @@
+<?php
+
+namespace yiiunit\framework\db\pgsql;
+
+use yii\base\NotSupportedException;
+use yii\db\pgsql\Schema;
+use yiiunit\framework\db\QueryBuilderTest;
+
+class PostgreSQLQueryBuilderTest extends QueryBuilderTest
+{
+
+	public $driverName = 'pgsql';
+	
+	public function columnTypes()
+	{
+		return array(
+			array(Schema::TYPE_PK, 'serial not null primary key'),
+			array(Schema::TYPE_PK . '(8)', 'serial not null primary key'),
+			array(Schema::TYPE_PK . ' CHECK (value > 5)', 'serial not null primary key CHECK (value > 5)'),
+			array(Schema::TYPE_PK . '(8) CHECK (value > 5)', 'serial not null primary key CHECK (value > 5)'),
+			array(Schema::TYPE_STRING, 'varchar(255)'),
+			array(Schema::TYPE_STRING . '(32)', 'varchar(32)'),
+			array(Schema::TYPE_STRING . ' CHECK (value LIKE "test%")', 'varchar(255) CHECK (value LIKE "test%")'),
+			array(Schema::TYPE_STRING . '(32) CHECK (value LIKE "test%")', 'varchar(32) CHECK (value LIKE "test%")'),
+			array(Schema::TYPE_STRING . ' NOT NULL', 'varchar(255) NOT NULL'),
+			array(Schema::TYPE_TEXT, 'text'),
+			array(Schema::TYPE_TEXT . '(255)', 'text'),
+			array(Schema::TYPE_TEXT . ' CHECK (value LIKE "test%")', 'text CHECK (value LIKE "test%")'),
+			array(Schema::TYPE_TEXT . '(255) CHECK (value LIKE "test%")', 'text CHECK (value LIKE "test%")'),
+			array(Schema::TYPE_TEXT . ' NOT NULL', 'text NOT NULL'),
+			array(Schema::TYPE_TEXT . '(255) NOT NULL', 'text NOT NULL'),
+			array(Schema::TYPE_SMALLINT, 'smallint'),
+			array(Schema::TYPE_SMALLINT . '(8)', 'smallint'),
+			array(Schema::TYPE_INTEGER, 'integer'),
+			array(Schema::TYPE_INTEGER . '(8)', 'integer'),
+			array(Schema::TYPE_INTEGER . ' CHECK (value > 5)', 'integer CHECK (value > 5)'),
+			array(Schema::TYPE_INTEGER . '(8) CHECK (value > 5)', 'integer CHECK (value > 5)'),
+			array(Schema::TYPE_INTEGER . ' NOT NULL', 'integer NOT NULL'),
+			array(Schema::TYPE_BIGINT, 'bigint'),
+			array(Schema::TYPE_BIGINT . '(8)', 'bigint'),
+			array(Schema::TYPE_BIGINT . ' CHECK (value > 5)', 'bigint CHECK (value > 5)'),
+			array(Schema::TYPE_BIGINT . '(8) CHECK (value > 5)', 'bigint CHECK (value > 5)'),
+			array(Schema::TYPE_BIGINT . ' NOT NULL', 'bigint NOT NULL'),
+			array(Schema::TYPE_FLOAT, 'double precision'),
+			array(Schema::TYPE_FLOAT . ' CHECK (value > 5.6)', 'double precision CHECK (value > 5.6)'),
+			array(Schema::TYPE_FLOAT . '(16,5) CHECK (value > 5.6)', 'double precision CHECK (value > 5.6)'),
+			array(Schema::TYPE_FLOAT . ' NOT NULL', 'double precision NOT NULL'),
+			array(Schema::TYPE_DECIMAL, 'numeric(10,0)'),
+			array(Schema::TYPE_DECIMAL . '(12,4)', 'numeric(12,4)'),
+			array(Schema::TYPE_DECIMAL . ' CHECK (value > 5.6)', 'numeric(10,0) CHECK (value > 5.6)'),
+			array(Schema::TYPE_DECIMAL . '(12,4) CHECK (value > 5.6)', 'numeric(12,4) CHECK (value > 5.6)'),
+			array(Schema::TYPE_DECIMAL . ' NOT NULL', 'numeric(10,0) NOT NULL'),
+			array(Schema::TYPE_DATETIME, 'timestamp'),
+			array(Schema::TYPE_DATETIME . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "timestamp CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"),
+			array(Schema::TYPE_DATETIME . ' NOT NULL', 'timestamp NOT NULL'),
+			array(Schema::TYPE_TIMESTAMP, 'timestamp'),
+			array(Schema::TYPE_TIMESTAMP . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "timestamp CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"),
+			array(Schema::TYPE_TIMESTAMP . ' NOT NULL', 'timestamp NOT NULL'),
+			array(Schema::TYPE_TIME, 'time'),
+			array(Schema::TYPE_TIME . " CHECK(value BETWEEN '12:00:00' AND '13:01:01')", "time CHECK(value BETWEEN '12:00:00' AND '13:01:01')"),
+			array(Schema::TYPE_TIME . ' NOT NULL', 'time NOT NULL'),
+			array(Schema::TYPE_DATE, 'date'),
+			array(Schema::TYPE_DATE . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "date CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"),
+			array(Schema::TYPE_DATE . ' NOT NULL', 'date NOT NULL'),
+			array(Schema::TYPE_BINARY, 'bytea'),
+			array(Schema::TYPE_BOOLEAN, 'boolean'),
+			array(Schema::TYPE_BOOLEAN . ' NOT NULL DEFAULT 1', 'boolean NOT NULL DEFAULT 1'),
+			array(Schema::TYPE_MONEY, 'numeric(19,4)'),
+			array(Schema::TYPE_MONEY . '(16,2)', 'numeric(16,2)'),
+			array(Schema::TYPE_MONEY . ' CHECK (value > 0.0)', 'numeric(19,4) CHECK (value > 0.0)'),
+			array(Schema::TYPE_MONEY . '(16,2) CHECK (value > 0.0)', 'numeric(16,2) CHECK (value > 0.0)'),
+			array(Schema::TYPE_MONEY . ' NOT NULL', 'numeric(19,4) NOT NULL'),
+		);
+	}
+
+}
\ No newline at end of file
diff --git a/tests/unit/framework/db/sqlite/SqliteQueryBuilderTest.php b/tests/unit/framework/db/sqlite/SqliteQueryBuilderTest.php
index c36628f..8e769af 100644
--- a/tests/unit/framework/db/sqlite/SqliteQueryBuilderTest.php
+++ b/tests/unit/framework/db/sqlite/SqliteQueryBuilderTest.php
@@ -2,6 +2,7 @@
 
 namespace yiiunit\framework\db\sqlite;
 
+use yii\base\NotSupportedException;
 use yii\db\sqlite\Schema;
 use yiiunit\framework\db\QueryBuilderTest;
 
@@ -71,4 +72,11 @@ class SqliteQueryBuilderTest extends QueryBuilderTest
 			array(Schema::TYPE_MONEY . ' NOT NULL', 'decimal(19,4) NOT NULL'),
 		);
 	}
+	
+	public function testAddDropPrimayKey()
+	{
+		$this->setExpectedException('yii\base\NotSupportedException');
+		parent::testAddDropPrimayKey();
+	}
+
 }
\ No newline at end of file
diff --git a/tests/unit/framework/helpers/FileHelperTest.php b/tests/unit/framework/helpers/FileHelperTest.php
new file mode 100644
index 0000000..8c812b1
--- /dev/null
+++ b/tests/unit/framework/helpers/FileHelperTest.php
@@ -0,0 +1,319 @@
+<?php
+
+use yii\helpers\base\FileHelper;
+use yii\test\TestCase;
+
+/**
+ * Unit test for [[yii\helpers\base\FileHelper]]
+ * @see FileHelper
+ */
+class FileHelperTest extends TestCase
+{
+	/**
+	 * @var string test files path.
+	 */
+	private $testFilePath = '';
+
+	public function setUp()
+	{
+		$this->testFilePath = Yii::getAlias('@yiiunit/runtime') . DIRECTORY_SEPARATOR . get_class($this);
+		$this->createDir($this->testFilePath);
+		if (!file_exists($this->testFilePath)) {
+			$this->markTestIncomplete('Unit tests runtime directory should have writable permissions!');
+		}
+	}
+
+	public function tearDown()
+	{
+		$this->removeDir($this->testFilePath);
+	}
+
+	/**
+	 * Creates directory.
+	 * @param string $dirName directory full name.
+	 */
+	protected function createDir($dirName)
+	{
+		if (!file_exists($dirName)) {
+			mkdir($dirName, 0777, true);
+		}
+	}
+
+	/**
+	 * Removes directory.
+	 * @param string $dirName directory full name.
+	 */
+	protected function removeDir($dirName)
+	{
+		if (!empty($dirName) && is_dir($dirName)) {
+			if ($handle = opendir($dirName)) {
+				while (false !== ($entry = readdir($handle))) {
+					if ($entry != '.' && $entry != '..') {
+						if (is_dir($dirName . DIRECTORY_SEPARATOR . $entry) === true) {
+							$this->removeDir($dirName . DIRECTORY_SEPARATOR . $entry);
+						} else {
+							unlink($dirName . DIRECTORY_SEPARATOR . $entry);
+						}
+					}
+				}
+				closedir($handle);
+				rmdir($dirName);
+			}
+		}
+	}
+
+	/**
+	 * Get file permission mode.
+	 * @param string $file file name.
+	 * @return string permission mode.
+	 */
+	protected function getMode($file)
+	{
+		return substr(sprintf('%o', fileperms($file)), -4);
+	}
+
+	/**
+	 * Creates test files structure,
+	 * @param array $items file system objects to be created in format: objectName => objectContent
+	 * Arrays specifies directories, other values - files.
+	 * @param string $basePath structure base file path.
+	 */
+	protected function createFileStructure(array $items, $basePath = '')
+	{
+		if (empty($basePath)) {
+			$basePath = $this->testFilePath;
+		}
+		foreach ($items as $name => $content) {
+			$itemName = $basePath . DIRECTORY_SEPARATOR . $name;
+			if (is_array($content)) {
+				mkdir($itemName, 0777, true);
+				$this->createFileStructure($content, $itemName);
+			} else {
+				file_put_contents($itemName, $content);
+			}
+		}
+	}
+
+	/**
+	 * Asserts that file has specific permission mode.
+	 * @param integer $expectedMode expected file permission mode.
+	 * @param string $fileName file name.
+	 * @param string $message error message
+	 */
+	protected function assertFileMode($expectedMode, $fileName, $message='')
+	{
+		$expectedMode = sprintf('%o', $expectedMode);
+		$this->assertEquals($expectedMode, $this->getMode($fileName), $message);
+	}
+
+	// Tests :
+
+	public function testCopyDirectory()
+	{
+		$srcDirName = 'test_src_dir';
+		$files = array(
+			'file1.txt' => 'file 1 content',
+			'file2.txt' => 'file 2 content',
+		);
+		$this->createFileStructure(array(
+			$srcDirName => $files
+		));
+
+		$basePath = $this->testFilePath;
+		$srcDirName = $basePath . DIRECTORY_SEPARATOR . $srcDirName;
+		$dstDirName = $basePath . DIRECTORY_SEPARATOR . 'test_dst_dir';
+
+		FileHelper::copyDirectory($srcDirName, $dstDirName);
+
+		$this->assertTrue(file_exists($dstDirName), 'Destination directory does not exist!');
+		foreach ($files as $name => $content) {
+			$fileName = $dstDirName . DIRECTORY_SEPARATOR . $name;
+			$this->assertTrue(file_exists($fileName), 'Directory file is missing!');
+			$this->assertEquals($content, file_get_contents($fileName), 'Incorrect file content!');
+		}
+	}
+
+	/**
+	 * @depends testCopyDirectory
+	 */
+	public function testCopyDirectoryPermissions()
+	{
+		if (substr(PHP_OS, 0, 3) == 'WIN') {
+			$this->markTestSkipped("Can't reliably test it on Windows because fileperms() always return 0777.");
+		}
+
+		$srcDirName = 'test_src_dir';
+		$subDirName = 'test_sub_dir';
+		$fileName = 'test_file.txt';
+		$this->createFileStructure(array(
+			$srcDirName => array(
+				$subDirName => array(),
+				$fileName => 'test file content',
+			),
+		));
+
+		$basePath = $this->testFilePath;
+		$srcDirName = $basePath . DIRECTORY_SEPARATOR . $srcDirName;
+		$dstDirName = $basePath . DIRECTORY_SEPARATOR . 'test_dst_dir';
+
+		$dirMode = 0755;
+		$fileMode = 0755;
+		$options = array(
+			'dirMode' => $dirMode,
+			'fileMode' => $fileMode,
+		);
+		FileHelper::copyDirectory($srcDirName, $dstDirName, $options);
+
+		$this->assertFileMode($dirMode, $dstDirName, 'Destination directory has wrong mode!');
+		$this->assertFileMode($dirMode, $dstDirName . DIRECTORY_SEPARATOR . $subDirName, 'Copied sub directory has wrong mode!');
+		$this->assertFileMode($fileMode, $dstDirName . DIRECTORY_SEPARATOR . $fileName, 'Copied file has wrong mode!');
+	}
+
+	public function stestRemoveDirectory()
+	{
+		$dirName = 'test_dir_for_remove';
+		$this->createFileStructure(array(
+			$dirName => array(
+				'file1.txt' => 'file 1 content',
+				'file2.txt' => 'file 2 content',
+				'test_sub_dir' => array(
+					'sub_dir_file_1.txt' => 'sub dir file 1 content',
+					'sub_dir_file_2.txt' => 'sub dir file 2 content',
+				),
+			),
+		));
+
+		$basePath = $this->testFilePath;
+		$dirName = $basePath . DIRECTORY_SEPARATOR . $dirName;
+
+		FileHelper::removeDirectory($dirName);
+
+		$this->assertFalse(file_exists($dirName), 'Unable to remove directory!');
+	}
+
+	public function testFindFiles()
+	{
+		$dirName = 'test_dir';
+		$this->createFileStructure(array(
+			$dirName => array(
+				'file_1.txt' => 'file 1 content',
+				'file_2.txt' => 'file 2 content',
+				'test_sub_dir' => array(
+					'file_1_1.txt' => 'sub dir file 1 content',
+					'file_1_2.txt' => 'sub dir file 2 content',
+				),
+			),
+		));
+		$basePath = $this->testFilePath;
+		$dirName = $basePath . DIRECTORY_SEPARATOR . $dirName;
+		$expectedFiles = array(
+			$dirName . DIRECTORY_SEPARATOR . 'file_1.txt',
+			$dirName . DIRECTORY_SEPARATOR . 'file_2.txt',
+			$dirName . DIRECTORY_SEPARATOR . 'test_sub_dir' . DIRECTORY_SEPARATOR . 'file_1_1.txt',
+			$dirName . DIRECTORY_SEPARATOR . 'test_sub_dir' . DIRECTORY_SEPARATOR . 'file_1_2.txt',
+		);
+
+		$foundFiles = FileHelper::findFiles($dirName);
+		sort($expectedFiles);
+		sort($foundFiles);
+		$this->assertEquals($expectedFiles, $foundFiles);
+	}
+
+	/**
+	 * @depends testFindFiles
+	 */
+	public function testFindFileFilter()
+	{
+		$dirName = 'test_dir';
+		$passedFileName = 'passed.txt';
+		$this->createFileStructure(array(
+			$dirName => array(
+				$passedFileName => 'passed file content',
+				'declined.txt' => 'declined file content',
+			),
+		));
+		$basePath = $this->testFilePath;
+		$dirName = $basePath . DIRECTORY_SEPARATOR . $dirName;
+
+		$options = array(
+			'filter' => function($path) use ($passedFileName) {
+				return $passedFileName == basename($path);
+			}
+		);
+		$foundFiles = FileHelper::findFiles($dirName, $options);
+		$this->assertEquals(array($dirName . DIRECTORY_SEPARATOR . $passedFileName), $foundFiles);
+	}
+
+	/**
+	 * @depends testFindFiles
+	 */
+	public function testFindFilesExclude()
+	{
+		$dirName = 'test_dir';
+		$fileName = 'test_file.txt';
+		$excludeFileName = 'exclude_file.txt';
+		$this->createFileStructure(array(
+			$dirName => array(
+				$fileName => 'file content',
+				$excludeFileName => 'exclude file content',
+			),
+		));
+		$basePath = $this->testFilePath;
+		$dirName = $basePath . DIRECTORY_SEPARATOR . $dirName;
+
+		$options = array(
+			'except' => array($excludeFileName),
+		);
+		$foundFiles = FileHelper::findFiles($dirName, $options);
+		$this->assertEquals(array($dirName . DIRECTORY_SEPARATOR . $fileName), $foundFiles);
+	}
+
+	/**
+	 * @depends testFindFiles
+	 */
+	public function testFindFilesFileType()
+	{
+		$dirName = 'test_dir';
+		$fileType = 'dat';
+		$fileName = 'test_file.' . $fileType;
+		$excludeFileName = 'exclude_file.txt';
+		$this->createFileStructure(array(
+			$dirName => array(
+				$fileName => 'file content',
+				$excludeFileName => 'exclude file content',
+			),
+		));
+		$basePath = $this->testFilePath;
+		$dirName = $basePath . DIRECTORY_SEPARATOR . $dirName;
+
+		$options = array(
+			'fileTypes' => array($fileType),
+		);
+		$foundFiles = FileHelper::findFiles($dirName, $options);
+		$this->assertEquals(array($dirName . DIRECTORY_SEPARATOR . $fileName), $foundFiles);
+	}
+
+	public function testMkdir() {
+		$basePath = $this->testFilePath;
+		$dirName = $basePath . DIRECTORY_SEPARATOR . 'test_dir_level_1' . DIRECTORY_SEPARATOR . 'test_dir_level_2';
+		FileHelper::mkdir($dirName);
+		$this->assertTrue(file_exists($dirName), 'Unable to create directory recursively!');
+	}
+
+	public function testGetMimeTypeByExtension()
+	{
+		$magicFile = $this->testFilePath . DIRECTORY_SEPARATOR . 'mime_type.php';
+		$mimeTypeMap = array(
+			'txa' => 'application/json',
+			'txb' => 'another/mime',
+		);
+		$magicFileContent = '<?php return ' . var_export($mimeTypeMap, true) . ';';
+		file_put_contents($magicFile, $magicFileContent);
+
+		foreach ($mimeTypeMap as $extension => $mimeType) {
+			$fileName = 'test.' . $extension;
+			$this->assertNull(FileHelper::getMimeTypeByExtension($fileName));
+			$this->assertEquals($mimeType, FileHelper::getMimeTypeByExtension($fileName, $magicFile));
+		}
+	}
+}
diff --git a/tests/unit/framework/web/ResponseTest.php b/tests/unit/framework/web/ResponseTest.php
index 5da9b8c..f35dda4 100644
--- a/tests/unit/framework/web/ResponseTest.php
+++ b/tests/unit/framework/web/ResponseTest.php
@@ -1,45 +1,30 @@
 <?php
 
-namespace yii\web;
-
-use yiiunit\framework\web\ResponseTest;
+namespace yiiunit\framework\web;
 
-/**
- * Mock PHP header function to check for sent headers
- * @param string $string
- * @param bool $replace
- * @param int $httpResponseCode
- */
-function header($string, $replace = true, $httpResponseCode = null) {
-	ResponseTest::$headers[] = $string;
-	// TODO implement replace
+use Yii;
+use yii\helpers\StringHelper;
 
-	if ($httpResponseCode !== null) {
-		ResponseTest::$httpResponseCode = $httpResponseCode;
+class Response extends \yii\web\Response
+{
+	public function send()
+	{
+		// does nothing to allow testing
 	}
 }
 
-namespace yiiunit\framework\web;
-
-use yii\helpers\StringHelper;
-use yii\web\Response;
-
 class ResponseTest extends \yiiunit\TestCase
 {
-	public static $headers = array();
-	public static $httpResponseCode = 200;
+	/**
+	 * @var Response
+	 */
+	public $response;
 
 	protected function setUp()
 	{
 		parent::setUp();
 		$this->mockApplication();
-		$this->reset();
-	}
-
-	protected function reset()
-	{
-		static::$headers = array();
-		static::$httpResponseCode = 200;
+		$this->response = new Response;
 	}
 
 	public function rightRanges()
@@ -56,18 +41,22 @@ class ResponseTest extends \yiiunit\TestCase
 	/**
 	 * @dataProvider rightRanges
 	 */
-	public function testSendFileRanges($rangeHeader, $expectedHeader, $length, $expectedFile)
+	public function testSendFileRanges($rangeHeader, $expectedHeader, $length, $expectedContent)
 	{
-		$content = $this->generateTestFileContent();
+		$dataFile = \Yii::getAlias('@yiiunit/data/web/data.txt');
+		$fullContent = file_get_contents($dataFile);
 		$_SERVER['HTTP_RANGE'] = 'bytes=' . $rangeHeader;
-		$sent = $this->runSendFile('testFile.txt', $content, null);
-
-		$this->assertEquals($expectedFile, $sent);
-		$this->assertTrue(in_array('HTTP/1.1 206 Partial Content', static::$headers));
-		$this->assertTrue(in_array('Accept-Ranges: bytes', static::$headers));
-		$this->assertArrayHasKey('Content-Range: bytes ' . $expectedHeader . '/' . StringHelper::strlen($content), array_flip(static::$headers));
-		$this->assertTrue(in_array('Content-Type: text/plain', static::$headers));
-		$this->assertTrue(in_array('Content-Length: ' . $length, static::$headers));
+		ob_start();
+		$this->response->sendFile($dataFile);
+		$content = ob_get_clean();
+
+		$this->assertEquals($expectedContent, $content);
+		$this->assertEquals(206, $this->response->statusCode);
+		$headers = $this->response->headers;
+		$this->assertEquals("bytes", $headers->get('Accept-Ranges'));
+		$this->assertEquals("bytes " . $expectedHeader . '/' . StringHelper::strlen($fullContent), $headers->get('Content-Range'));
+		$this->assertEquals('text/plain', $headers->get('Content-Type'));
+		$this->assertEquals("$length", $headers->get('Content-Length'));
 	}
 
 	public function wrongRanges()
@@ -87,25 +76,15 @@ class ResponseTest extends \yiiunit\TestCase
 	 */
 	public function testSendFileWrongRanges($rangeHeader)
 	{
-		$this->setExpectedException('yii\base\HttpException', 'Requested Range Not Satisfiable');
+		$this->setExpectedException('yii\web\HttpException');
 
-		$content = $this->generateTestFileContent();
+		$dataFile = \Yii::getAlias('@yiiunit/data/web/data.txt');
 		$_SERVER['HTTP_RANGE'] = 'bytes=' . $rangeHeader;
-		$this->runSendFile('testFile.txt', $content, null);
+		$this->response->sendFile($dataFile);
 	}
 
 	protected function generateTestFileContent()
 	{
 		return '12ёжик3456798áèabcdefghijklmnopqrstuvwxyz!"§$%&/(ёжик)=?';
 	}
-
-	protected function runSendFile($fileName, $content, $mimeType)
-	{
-		ob_start();
-		ob_implicit_flush(false);
-		$response = new Response();
-		$response->sendFile($fileName, $content, $mimeType, false);
-		$file = ob_get_clean();
-		return $file;
-	}
 }