diff --git a/apps/advanced/backend/views/layouts/main.php b/apps/advanced/backend/views/layouts/main.php
index 1857e62..d1ede45 100644
--- a/apps/advanced/backend/views/layouts/main.php
+++ b/apps/advanced/backend/views/layouts/main.php
@@ -16,6 +16,7 @@ AppAsset::register($this);
 <html lang="<?= Yii::$app->language ?>">
 <head>
 	<meta charset="<?= Yii::$app->charset ?>"/>
+	<meta name="viewport" content="width=device-width, initial-scale=1">
 	<title><?= Html::encode($this->title) ?></title>
 	<?php $this->head() ?>
 </head>
diff --git a/apps/advanced/backend/web/css/site.css b/apps/advanced/backend/web/css/site.css
index 2d27436..bf9d2b2 100644
--- a/apps/advanced/backend/web/css/site.css
+++ b/apps/advanced/backend/web/css/site.css
@@ -43,3 +43,11 @@ a.desc:after { content: /*"\e114"*/"\e152"; }
 
 .sort-ordinal a.asc:after { content: "\e155"; }
 .sort-ordinal a.desc:after { content: "\e156"; }
+
+.error-summary {
+	color: #b94a48;
+	background: #fdf7f7;
+	border-left: 3px solid #eed3d7;
+	padding: 10px 20px;
+	margin: 0 0 15px 0;
+}
diff --git a/apps/advanced/frontend/views/layouts/main.php b/apps/advanced/frontend/views/layouts/main.php
index 7bc03d2..2bfa9b0 100644
--- a/apps/advanced/frontend/views/layouts/main.php
+++ b/apps/advanced/frontend/views/layouts/main.php
@@ -17,6 +17,7 @@ AppAsset::register($this);
 <html lang="<?= Yii::$app->language ?>">
 <head>
 	<meta charset="<?= Yii::$app->charset ?>"/>
+	<meta name="viewport" content="width=device-width, initial-scale=1">
 	<title><?= Html::encode($this->title) ?></title>
 	<?php $this->head() ?>
 </head>
diff --git a/apps/advanced/frontend/web/css/site.css b/apps/advanced/frontend/web/css/site.css
index 2d27436..bf9d2b2 100644
--- a/apps/advanced/frontend/web/css/site.css
+++ b/apps/advanced/frontend/web/css/site.css
@@ -43,3 +43,11 @@ a.desc:after { content: /*"\e114"*/"\e152"; }
 
 .sort-ordinal a.asc:after { content: "\e155"; }
 .sort-ordinal a.desc:after { content: "\e156"; }
+
+.error-summary {
+	color: #b94a48;
+	background: #fdf7f7;
+	border-left: 3px solid #eed3d7;
+	padding: 10px 20px;
+	margin: 0 0 15px 0;
+}
diff --git a/apps/basic/config/codeception/acceptance.php b/apps/basic/config/codeception/acceptance.php
deleted file mode 100644
index 6036f88..0000000
--- a/apps/basic/config/codeception/acceptance.php
+++ /dev/null
@@ -1,11 +0,0 @@
-<?php
-
-// configuration adjustments for codeception acceptance tests. Will be merged with web.php config.
-
-return [
-	'components' => [
-		'db' => [
-			'dsn' => 'mysql:host=localhost;dbname=yii2basic_acceptance',
-		],
-	],
-];
diff --git a/apps/basic/config/codeception/functional.php b/apps/basic/config/codeception/functional.php
deleted file mode 100644
index 210b97d..0000000
--- a/apps/basic/config/codeception/functional.php
+++ /dev/null
@@ -1,17 +0,0 @@
-<?php
-
-// configuration adjustments for codeception functional tests. Will be merged with web.php config.
-
-return [
-	'components' => [
-		'db' => [
-			'dsn' => 'mysql:host=localhost;dbname=yii2basic_functional',
-		],
-		'request' => [
-			'enableCsrfValidation' => false,
-		],
-		'urlManager' => [
-			'baseUrl' => '/web/index.php',
-		],
-	],
-];
diff --git a/apps/basic/config/codeception/unit.php b/apps/basic/config/codeception/unit.php
deleted file mode 100644
index fd66970..0000000
--- a/apps/basic/config/codeception/unit.php
+++ /dev/null
@@ -1,15 +0,0 @@
-<?php
-
-// configuration adjustments for codeception unit tests. Will be merged with web.php config.
-
-return [
-	'components' => [
-		'fixture'	=>	[
-			'class'		=>	'yii\test\DbFixtureManager',
-			'basePath'	=>	'@tests/unit/fixtures',
-		],
-		'db' => [
-			'dsn' => 'mysql:host=localhost;dbname=yii2basic_unit',
-		],
-	],
-];
diff --git a/apps/basic/mails/layouts/html.php b/apps/basic/mails/layouts/html.php
new file mode 100644
index 0000000..2e6b615
--- /dev/null
+++ b/apps/basic/mails/layouts/html.php
@@ -0,0 +1,24 @@
+<?php
+use yii\helpers\Html;
+use yii\mail\BaseMessage;
+
+/**
+ * @var \yii\web\View $this
+ * @var BaseMessage $content
+ */
+?>
+<?php $this->beginPage() ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+	<meta http-equiv="Content-Type" content="text/html; charset=<?= Yii::$app->charset ?>" />
+	<title><?= Html::encode($this->title) ?></title>
+	<?php $this->head() ?>
+</head>
+<body>
+	<?php $this->beginBody() ?>
+	<?= $content ?>
+	<?php $this->endBody() ?>
+</body>
+</html>
+<?php $this->endPage() ?>
\ No newline at end of file
diff --git a/apps/basic/tests/README.md b/apps/basic/tests/README.md
index 5ff2669..83b0511 100644
--- a/apps/basic/tests/README.md
+++ b/apps/basic/tests/README.md
@@ -1,35 +1,28 @@
 This folder contains various tests for the basic application.
 These tests are developed with [Codeception PHP Testing Framework](http://codeception.com/).
 
-To run the tests, follow these steps:
+After creating the basic application, follow these steps to prepare for the tests:
 
-1. Download Codeception([Quickstart step 1](http://codeception.com/quickstart)) and put the codeception.phar in the
-   application base directory (not in this `tests` directory!).
-2. Adjust the test configuration files based on your environment:
-   - Configure the URL for [acceptance tests](http://codeception.com/docs/04-AcceptanceTests) in `acceptance.suite.yml`.
-     The URL should point to the `index-test-acceptance.php` file that is located under the `web` directory of the application.
-   - `functional.suite.yml` for [functional testing](http://codeception.com/docs/05-FunctionalTests) and
-     `unit.suite.yml` for [unit testing](http://codeception.com/docs/06-UnitTests) should already work out of the box
-     and should not need to be adjusted.
-   - If you want to run acceptance tests, you need to download [selenium standalone](http://www.seleniumhq.org/download/)
-     and start it with command `java -jar {selenium-standalone-name}.jar`.
-     After that you can use `WebDriver` codeception module that will connect to selenium and launch browser.
-     This also allows you to use [Xvfb](https://en.wikipedia.org/wiki/Xvfb) in your tests which allows you to run tests
-     without showing the running browser on the screen. There is codeception [blog post](http://codeception.com/05-24-2013/jenkins-ci-practice.html)
-     that explains how it works.
+1. In the file `_bootstrap.php`, modify the definition of the constant `TEST_ENTRY_URL` so
+   that it points to the correct entry script URL.
+2. Go to the application base directory and build the test suites:
 
-3. Go to the application base directory and build the test suites:
    ```
-   php codecept.phar build    // rebuild test scripts, only need to be run once
-   ```
-4. Run the tests:
-   ```
-   php codecept.phar run      // run all available tests
-   // you can also run a test suite alone:
-   php codecept.phar run acceptance
-   php codecept.phar run functional
-   php codecept.phar run unit
+   vendor/bin/codecept build
    ```
 
+Now you can run the tests with the following commands:
+
+```
+# run all available tests
+vendor/bin/codecept run
+# run acceptance tests
+vendor/bin/codecept run acceptance
+# run functional tests
+vendor/bin/codecept run functional
+# run unit tests
+vendor/bin/codecept run unit
+```
+
 Please refer to [Codeception tutorial](http://codeception.com/docs/01-Introduction) for
-more details about writing acceptance, functional and unit tests.
+more details about writing and running acceptance, functional and unit tests.
diff --git a/apps/basic/tests/_bootstrap.php b/apps/basic/tests/_bootstrap.php
index ec9390e..5a40d64 100644
--- a/apps/basic/tests/_bootstrap.php
+++ b/apps/basic/tests/_bootstrap.php
@@ -1,9 +1,22 @@
 <?php
 
+// the entry script URL (without host info) for functional and acceptance tests
+// PLEASE ADJUST IT TO THE ACTUAL ENTRY SCRIPT URL
+defined('TEST_ENTRY_URL') or define('TEST_ENTRY_URL', '/yii2-basic/web/index-test.php');
+
+// the entry script file path for functional and acceptance tests
+defined('TEST_ENTRY_FILE') or define('TEST_ENTRY_FILE', dirname(__DIR__) . '/web/index-test.php');
+
 defined('YII_DEBUG') or define('YII_DEBUG', true);
+
 defined('YII_ENV') or define('YII_ENV', 'test');
 
 require_once(__DIR__ . '/../vendor/autoload.php');
+
 require_once(__DIR__ . '/../vendor/yiisoft/yii2/yii/Yii.php');
 
+// set correct script paths
+$_SERVER['SCRIPT_FILENAME'] = TEST_ENTRY_FILE;
+$_SERVER['SCRIPT_NAME'] = TEST_ENTRY_URL;
+
 Yii::setAlias('@tests', __DIR__);
diff --git a/apps/basic/tests/_pages/ContactPage.php b/apps/basic/tests/_pages/ContactPage.php
index 4fd6fa8..f8a1236 100644
--- a/apps/basic/tests/_pages/ContactPage.php
+++ b/apps/basic/tests/_pages/ContactPage.php
@@ -9,50 +9,14 @@ class ContactPage extends BasePage
 	public $route = 'site/contact';
 
 	/**
-	 * contact form name text field locator
-	 * @var string 
-	 */
-	public $name = 'input[name="ContactForm[name]"]';
-	/**
-	 * contact form email text field locator
-	 * @var string
-	 */
-	public $email = 'input[name="ContactForm[email]"]';
-	/**
-	 * contact form subject text field locator
-	 * @var string
-	 */
-	public $subject = 'input[name="ContactForm[subject]"]';
-	/**
-	 * contact form body textarea locator
-	 * @var string
-	 */
-	public $body = 'textarea[name="ContactForm[body]"]';
-	/**
-	 * contact form verification code text field locator
-	 * @var string
-	 */
-	public $verifyCode = 'input[name="ContactForm[verifyCode]"]';
-	/**
-	 * contact form submit button
-	 * @var string
-	 */
-	public $button = 'button[type=submit]';
-
-	/**
-	 * 
 	 * @param array $contactData
 	 */
 	public function submit(array $contactData)
 	{
-		if (!empty($contactData))
-		{
-			$this->guy->fillField($this->name, $contactData['name']);
-			$this->guy->fillField($this->email, $contactData['email']);
-			$this->guy->fillField($this->subject, $contactData['subject']);
-			$this->guy->fillField($this->body, $contactData['body']);
-			$this->guy->fillField($this->verifyCode, $contactData['verifyCode']);
+		$data = [];
+		foreach ($contactData as $name => $value) {
+			$data["ContactForm[$name]"] = $value;
 		}
-		$this->guy->click($this->button);
+		$this->guy->submitForm('#contact-form', $data);
 	}
 }
diff --git a/apps/basic/tests/_pages/LoginPage.php b/apps/basic/tests/_pages/LoginPage.php
index 8493d51..aae5e0f 100644
--- a/apps/basic/tests/_pages/LoginPage.php
+++ b/apps/basic/tests/_pages/LoginPage.php
@@ -9,30 +9,14 @@ class LoginPage extends BasePage
 	public $route = 'site/login';
 
 	/**
-	 * login form username text field locator
-	 * @var string
-	 */
-	public $username = 'input[name="LoginForm[username]"]';
-	/**
-	 * login form password text field locator
-	 * @var string
-	 */
-	public $password = 'input[name="LoginForm[password]"]';
-	/**
-	 * login form submit button locator
-	 * @var string
-	 */
-	public $button = 'button[type=submit]';
-
-	/**
-	 * 
 	 * @param string $username
 	 * @param string $password
 	 */
 	public function login($username, $password)
 	{
-		$this->guy->fillField($this->username, $username);
-		$this->guy->fillField($this->password, $password);
-		$this->guy->click($this->button);
+		$this->guy->submitForm('#login-form', [
+			'LoginForm[username]' => $username,
+			'LoginForm[password]' => $password,
+		]);
 	}
 }
diff --git a/apps/basic/tests/acceptance.suite.yml b/apps/basic/tests/acceptance.suite.yml
index 2f37e16..f4d36f5 100644
--- a/apps/basic/tests/acceptance.suite.yml
+++ b/apps/basic/tests/acceptance.suite.yml
@@ -18,7 +18,7 @@ modules:
 #        - WebDriver
     config:
         PhpBrowser:
-            url: 'http://localhost/basic-app/web/index-test-acceptance.php'
+            url: 'http://localhost'
 #        WebDriver:
-#            url: 'http://localhost/basic-app/web/index-test-acceptance.php'
+#            url: 'http://localhost'
 #            browser: firefox
diff --git a/apps/basic/tests/acceptance/HomeCept.php b/apps/basic/tests/acceptance/HomeCept.php
index eb28c39..62456f9 100644
--- a/apps/basic/tests/acceptance/HomeCept.php
+++ b/apps/basic/tests/acceptance/HomeCept.php
@@ -2,7 +2,7 @@
 
 $I = new WebGuy($scenario);
 $I->wantTo('ensure that home page works');
-$I->amOnPage('');
+$I->amOnPage(Yii::$app->homeUrl);
 $I->see('My Company');
 $I->seeLink('About');
 $I->click('About');
diff --git a/apps/basic/tests/acceptance/_bootstrap.php b/apps/basic/tests/acceptance/_bootstrap.php
index 32bac62..6ce3d17 100644
--- a/apps/basic/tests/acceptance/_bootstrap.php
+++ b/apps/basic/tests/acceptance/_bootstrap.php
@@ -1,8 +1,3 @@
 <?php
 
-$config = yii\helpers\ArrayHelper::merge(
-	require(__DIR__ . '/../../config/web.php'),
-	require(__DIR__ . '/../../config/codeception/acceptance.php')
-);
-
-$application = new yii\web\Application($config);
+new yii\web\Application(require(__DIR__ . '/_config.php'));
diff --git a/apps/basic/tests/acceptance/_config.php b/apps/basic/tests/acceptance/_config.php
new file mode 100644
index 0000000..8979253
--- /dev/null
+++ b/apps/basic/tests/acceptance/_config.php
@@ -0,0 +1,16 @@
+<?php
+
+use yii\helpers\ArrayHelper;
+
+$config = require(__DIR__ . '/../../config/web.php');
+
+return ArrayHelper::merge($config, [
+	'components' => [
+		'db' => [
+			'dsn' => 'mysql:host=localhost;dbname=yii2_basic_acceptance',
+		],
+		'urlManager' => [
+			'showScriptName' => true,
+		],
+	],
+]);
diff --git a/apps/basic/tests/functional.suite.yml b/apps/basic/tests/functional.suite.yml
index b7d86de..0d400af 100644
--- a/apps/basic/tests/functional.suite.yml
+++ b/apps/basic/tests/functional.suite.yml
@@ -14,5 +14,4 @@ modules:
       - Yii2
     config:
         Yii2:
-            entryScript: 'web/index-test-functional.php'
-            url: 'http://localhost/'
+            configFile: 'tests/functional/_config.php'
diff --git a/apps/basic/tests/functional/ContactCept.php b/apps/basic/tests/functional/ContactCept.php
index ddc7ca6..14e6197 100644
--- a/apps/basic/tests/functional/ContactCept.php
+++ b/apps/basic/tests/functional/ContactCept.php
@@ -1,6 +1,6 @@
 <?php
 
-use tests\functional\_pages\ContactPage;
+use tests\_pages\ContactPage;
 
 $I = new TestGuy($scenario);
 $I->wantTo('ensure that contact works');
diff --git a/apps/basic/tests/functional/HomeCept.php b/apps/basic/tests/functional/HomeCept.php
index 0658ba2..3258ba3 100644
--- a/apps/basic/tests/functional/HomeCept.php
+++ b/apps/basic/tests/functional/HomeCept.php
@@ -2,7 +2,7 @@
 
 $I = new TestGuy($scenario);
 $I->wantTo('ensure that home page works');
-$I->amOnPage('');
+$I->amOnPage(Yii::$app->homeUrl);
 $I->see('My Company');
 $I->seeLink('About');
 $I->click('About');
diff --git a/apps/basic/tests/functional/LoginCept.php b/apps/basic/tests/functional/LoginCept.php
index 770b823..e5285cd 100644
--- a/apps/basic/tests/functional/LoginCept.php
+++ b/apps/basic/tests/functional/LoginCept.php
@@ -1,6 +1,6 @@
 <?php
 
-use tests\functional\_pages\LoginPage;
+use tests\_pages\LoginPage;
 
 $I = new TestGuy($scenario);
 $I->wantTo('ensure that login works');
diff --git a/apps/basic/tests/functional/_bootstrap.php b/apps/basic/tests/functional/_bootstrap.php
index 6692104..6ce3d17 100644
--- a/apps/basic/tests/functional/_bootstrap.php
+++ b/apps/basic/tests/functional/_bootstrap.php
@@ -1,4 +1,3 @@
 <?php
 
-// create an application instance to support URL creation before running any test
-Yii::createObject(require(__DIR__ . '/../../web/index-test-functional.php'));
+new yii\web\Application(require(__DIR__ . '/_config.php'));
diff --git a/apps/basic/tests/functional/_config.php b/apps/basic/tests/functional/_config.php
new file mode 100644
index 0000000..bb93d96
--- /dev/null
+++ b/apps/basic/tests/functional/_config.php
@@ -0,0 +1,20 @@
+<?php
+
+use yii\helpers\ArrayHelper;
+
+// set correct script paths
+$_SERVER['SCRIPT_FILENAME'] = TEST_ENTRY_FILE;
+$_SERVER['SCRIPT_NAME'] = TEST_ENTRY_URL;
+
+$config = require(__DIR__ . '/../../config/web.php');
+
+return ArrayHelper::merge($config, [
+	'components' => [
+		'db' => [
+			'dsn' => 'mysql:host=localhost;dbname=yii2_basic_functional',
+		],
+		'urlManager' => [
+			'showScriptName' => true,
+		],
+	],
+]);
diff --git a/apps/basic/tests/functional/_pages/ContactPage.php b/apps/basic/tests/functional/_pages/ContactPage.php
deleted file mode 100644
index 0d0eba4..0000000
--- a/apps/basic/tests/functional/_pages/ContactPage.php
+++ /dev/null
@@ -1,51 +0,0 @@
-<?php
-
-namespace tests\functional\_pages;
-
-class ContactPage extends \tests\_pages\ContactPage
-{
-	/**
-	 * contact form name text field locator
-	 * @var string 
-	 */
-	public $name = 'ContactForm[name]';
-	/**
-	 * contact form email text field locator
-	 * @var string
-	 */
-	public $email = 'ContactForm[email]';
-	/**
-	 * contact form subject text field locator
-	 * @var string
-	 */
-	public $subject = 'ContactForm[subject]';
-	/**
-	 * contact form body textarea locator
-	 * @var string
-	 */
-	public $body = 'ContactForm[body]';
-	/**
-	 * contact form verification code text field locator
-	 * @var string
-	 */
-	public $verifyCode = 'ContactForm[verifyCode]';
-
-	/**
-	 * 
-	 * @param array $contactData
-	 */
-	public function submit(array $contactData)
-	{
-		if (empty($contactData)) {
-			$this->guy->submitForm('#contact-form', []);
-		} else {
-			$this->guy->submitForm('#contact-form', [
-				$this->name			=>	$contactData['name'],
-				$this->email		=>	$contactData['email'],
-				$this->subject		=>	$contactData['subject'],
-				$this->body			=>	$contactData['body'],
-				$this->verifyCode	=>	$contactData['verifyCode'],
-			]);
-		}
-	}
-}
diff --git a/apps/basic/tests/functional/_pages/LoginPage.php b/apps/basic/tests/functional/_pages/LoginPage.php
deleted file mode 100644
index 9a8d0ef..0000000
--- a/apps/basic/tests/functional/_pages/LoginPage.php
+++ /dev/null
@@ -1,30 +0,0 @@
-<?php
-
-namespace tests\functional\_pages;
-
-class LoginPage extends \tests\_pages\LoginPage
-{
-	/**
-	 * login form username text field locator
-	 * @var string
-	 */
-	public $username = 'LoginForm[username]';
-	/**
-	 * login form password text field locator
-	 * @var string
-	 */
-	public $password = 'LoginForm[password]';
-
-	/**
-	 * 
-	 * @param string $username
-	 * @param string $password
-	 */
-	public function login($username, $password)
-	{
-		$this->guy->submitForm('#login-form', [
-			$this->username	=> $username,
-			$this->password	=> $password,
-		]);
-	}
-}
diff --git a/apps/basic/views/layouts/main.php b/apps/basic/views/layouts/main.php
index e99b3c5..d1af295 100644
--- a/apps/basic/views/layouts/main.php
+++ b/apps/basic/views/layouts/main.php
@@ -16,6 +16,7 @@ AppAsset::register($this);
 <html lang="<?= Yii::$app->language ?>">
 <head>
 	<meta charset="<?= Yii::$app->charset ?>"/>
+	<meta name="viewport" content="width=device-width, initial-scale=1">
 	<title><?= Html::encode($this->title) ?></title>
 	<?php $this->head() ?>
 </head>
diff --git a/apps/basic/web/css/site.css b/apps/basic/web/css/site.css
index d44558d..7a1fb92 100644
--- a/apps/basic/web/css/site.css
+++ b/apps/basic/web/css/site.css
@@ -44,3 +44,11 @@ a.desc:after { content: /*"\e114"*/"\e152"; }
 
 .sort-ordinal a.asc:after { content: "\e155"; }
 .sort-ordinal a.desc:after { content: "\e156"; }
+
+.error-summary {
+	color: #b94a48;
+	background: #fdf7f7;
+	border-left: 3px solid #eed3d7;
+	padding: 10px 20px;
+	margin: 0 0 15px 0;
+}
diff --git a/apps/basic/web/index-test-functional.php b/apps/basic/web/index-test-functional.php
deleted file mode 100644
index 65fd3fa..0000000
--- a/apps/basic/web/index-test-functional.php
+++ /dev/null
@@ -1,11 +0,0 @@
-<?php
-
-// this file is used as the entry script for codeception functional testing
-
-$config = yii\helpers\ArrayHelper::merge(
-	require(__DIR__ . '/../config/web.php'),
-	require(__DIR__ . '/../config/codeception/functional.php')
-);
-
-$config['class'] = 'yii\web\Application';
-return $config;
diff --git a/apps/basic/web/index-test-acceptance.php b/apps/basic/web/index-test.php
similarity index 63%
rename from apps/basic/web/index-test-acceptance.php
rename to apps/basic/web/index-test.php
index ef9ec75..2bec381 100644
--- a/apps/basic/web/index-test-acceptance.php
+++ b/apps/basic/web/index-test.php
@@ -1,6 +1,9 @@
 <?php
 
 // NOTE: Make sure this file is not accessible when deployed to production
+if (!in_array(@$_SERVER['REMOTE_ADDR'], ['127.0.0.1', '::1'])) {
+	die('You are not allowed to access this file.');
+}
 
 defined('YII_DEBUG') or define('YII_DEBUG', true);
 defined('YII_ENV') or define('YII_ENV', 'test');
@@ -8,9 +11,6 @@ defined('YII_ENV') or define('YII_ENV', 'test');
 require(__DIR__ . '/../vendor/autoload.php');
 require(__DIR__ . '/../vendor/yiisoft/yii2/yii/Yii.php');
 
-$config = yii\helpers\ArrayHelper::merge(
-	require(__DIR__ . '/../config/web.php'),
-	require(__DIR__ . '/../config/codeception/acceptance.php')
-);
+$config = require(__DIR__ . '/../tests/acceptance/_config.php');
 
 (new yii\web\Application($config))->run();
diff --git a/docs/guide/controller.md b/docs/guide/controller.md
index de6cec5..193fe37 100644
--- a/docs/guide/controller.md
+++ b/docs/guide/controller.md
@@ -35,10 +35,49 @@ class SiteController extends Controller
 ```
 
 As you can see, typical controller contains actions that are public class methods named as `actionSomething`.
-The output of an action is what the method returns. The return value will be handled by the `response` application
+The output of an action is what the method returns, it could be rendered result or it can be instance of ```yii\web\Response```, for [example](#custom-response-class).
+The return value will be handled by the `response` application
 component which can convert the output to differnet formats such as JSON for example. The default behavior
 is to output the value unchanged though.
 
+You also can disable CSRF validation per controller and/or action, by setting its property:
+
+```php
+namespace app\controllers;
+
+use yii\web\Controller;
+
+class SiteController extends Controller
+{
+
+	public $enableCsrfValidation = false;
+
+	public function actionIndex()
+	{
+		#CSRF validation will no be applied on this and other actions
+	}
+
+}
+```
+
+To disable CSRF validation per custom actions you can do:
+
+```php
+namespace app\controllers;
+
+use yii\web\Controller;
+
+class SiteController extends Controller
+{
+	public function beforeAction($action)
+	{
+		// ...set `$this->enableCsrfValidation` here based on some conditions...
+		// call parent method that will check CSRF if such property is true.
+		return parent::beforeAction($action);
+	}
+}
+```
+
 Routes
 ------
 
@@ -208,6 +247,29 @@ Catching all incoming requests
 
 TBD
 
+Custom response class
+---------------------
+
+```php
+namespace app\controllers;
+
+use yii\web\Controller;
+use app\components\web\MyCustomResponse; #extended from yii\web\Response
+
+class SiteController extends Controller
+{
+	public function actionCustom()
+	{
+		/*
+		 * do your things here
+		 * since Response in extended from yii\base\Object, you can initialize its values by passing in 
+		 * __constructor() simple array.
+		 */
+		return new MyCustomResponse(['data' => $myCustomData]);
+	}
+}
+```
+
 See also
 --------
 
diff --git a/docs/guide/query-builder.md b/docs/guide/query-builder.md
index f775c76..2bc215d 100644
--- a/docs/guide/query-builder.md
+++ b/docs/guide/query-builder.md
@@ -142,11 +142,16 @@ Operator can be one of the following:
 - `not in`: similar to the `in` operator except that `IN` is replaced with `NOT IN` in the generated condition.
 - `like`: operand 1 should be a column or DB expression, and operand 2 be a string or an array representing
   the values that the column or DB expression should be like.
-  For example, `['like', 'name', '%tester%']` will generate `name LIKE '%tester%'`.
+  For example, `['like', 'name', 'tester']` will generate `name LIKE '%tester%'`.
   When the value range is given as an array, multiple `LIKE` predicates will be generated and concatenated
-  using `AND`. For example, `['like', 'name', ['%test%', '%sample%']]` will generate
+  using `AND`. For example, `['like', 'name', ['test', 'sample']]` will generate
   `name LIKE '%test%' AND name LIKE '%sample%'`.
-  The method will properly quote the column name and escape values in the range.
+  You may also provide an optional third operand to specify how to escape special characters in the values.
+  The operand should be an array of mappings from the special characters to their
+  escaped counterparts. If this operand is not provided, a default escape mapping will be used.
+  You may use `false` or an empty array to indicate the values are already escaped and no escape
+  should be applied. Note that when using an escape mapping (or the third operand is not provided),
+  the values will be automatically enclosed within a pair of percentage characters.
 - `or like`: similar to the `like` operator except that `OR` is used to concatenate the `LIKE`
   predicates when operand 2 is an array.
 - `not like`: similar to the `like` operator except that `LIKE` is replaced with `NOT LIKE`
@@ -162,7 +167,7 @@ $search = 'yii';
 
 $query->where(['status' => $status]);
 if (!empty($search)) {
-	$query->addWhere('like', 'title', $search);
+	$query->addWhere(['like', 'title', $search]);
 }
 ```
 
diff --git a/docs/internals/git-workflow.md b/docs/internals/git-workflow.md
index fc8b275..1f98ebb 100644
--- a/docs/internals/git-workflow.md
+++ b/docs/internals/git-workflow.md
@@ -3,7 +3,7 @@ Git workflow for Yii 2 contributors
 
 So you want to contribute to Yii? Great! But to increase the chances of your changes being accepted quickly, please
 follow the following steps (the first 2 steps only need to be done the first time you contribute). If you are new to git
-and github, you might want to first check out [github help](http://help.github.com/), [learn git](http://gitref.org/)
+and github, you might want to first check out [github help](http://help.github.com/), [try git](https://try.github.com)
 or learn something about [git internal data model](http://nfarina.com/post/9868516270/git-is-simpler).
 
 ### 1. [Fork](http://help.github.com/fork-a-repo/) the Yii repository on github and clone your fork to your development
diff --git a/extensions/yii/authclient/README.md b/extensions/yii/authclient/README.md
index ea7f217..5a7b396 100644
--- a/extensions/yii/authclient/README.md
+++ b/extensions/yii/authclient/README.md
@@ -77,7 +77,7 @@ class SiteController extends Controller
 You may use [[yii\authclient\widgets\Choice]] to compose auth client selection:
 
 ```
-<?= yii\authclient\Choice::widget([
+<?= yii\authclient\widgets\Choice::widget([
      'baseAuthUrl' => ['site/auth']
 ]) ?>
 ```
diff --git a/extensions/yii/debug/LogTarget.php b/extensions/yii/debug/LogTarget.php
index 4b75cfa..1359b36 100644
--- a/extensions/yii/debug/LogTarget.php
+++ b/extensions/yii/debug/LogTarget.php
@@ -52,6 +52,7 @@ class LogTarget extends Target
 			$manifest = unserialize(file_get_contents($indexFile));
 		}
 		$request = Yii::$app->getRequest();
+		$response = Yii::$app->getResponse();
 		$manifest[$this->tag] = $summary = [
 			'tag' => $this->tag,
 			'url' => $request->getAbsoluteUrl(),
@@ -59,6 +60,8 @@ class LogTarget extends Target
 			'method' => $request->getMethod(),
 			'ip' => $request->getUserIP(),
 			'time' => time(),
+			'statusCode' => $response->statusCode,
+			'sqlCount' => $this->getSqlTotalCount(),
 		];
 		$this->gc($manifest);
 
@@ -102,4 +105,21 @@ class LogTarget extends Target
 			}
 		}
 	}
+
+	/**
+	 * Returns total sql count executed in current request. If database panel is not configured
+	 * returns 0.
+	 * @return integer
+	 */
+	protected function getSqlTotalCount()
+	{
+		if (!isset($this->module->panels['db'])) {
+			return 0;
+		}
+		$profileLogs = $this->module->panels['db']->save();
+		
+		# / 2 because messages are in couple (begin/end)
+		return count($profileLogs['messages']) / 2;
+	}
+
 }
diff --git a/extensions/yii/debug/components/search/Filter.php b/extensions/yii/debug/components/search/Filter.php
new file mode 100644
index 0000000..557e12c
--- /dev/null
+++ b/extensions/yii/debug/components/search/Filter.php
@@ -0,0 +1,72 @@
+<?php
+
+namespace yii\debug\components\search;
+
+use yii\base\Component;
+
+class Filter extends Component
+{
+
+	/**
+	 * @var array rules for matching filters in the way: [:fieldName => [rule1, rule2,..]]
+	 */
+	protected $rules = [];
+
+	/**
+	 * Adds rules for filtering data. Match can be partial or exactly.
+	 * @param string $name attribute name
+	 * @param \yii\debug\components\search\matches\Base $rule
+	 */
+	public function addMatch($name, $rule)
+	{
+		if (empty($rule->value) && $rule->value !== 0) {
+			return;
+		}
+
+		$this->rules[$name][] = $rule;
+	}
+
+	/**
+	 * Applies filter on given array and returns filtered data.
+	 * @param array $data data to filter
+	 * @return array filtered data
+	 */
+	public function filter(array $data)
+	{
+		$filtered = [];
+
+		foreach ($data as $row) {
+			if ($this->checkFilter($row)) {
+				$filtered[] = $row;
+			}
+		}
+
+		return $filtered;
+	}
+
+	/**
+	 * Check if the given data satisfies filters.
+	 * @param array $row
+	 */
+	public function checkFilter(array $row)
+	{
+		$matched = true;
+
+		foreach ($row as $name => $value) {
+			if (isset($this->rules[$name])) {
+
+				#check all rules for given attribute
+
+				foreach ($this->rules[$name] as $rule) {
+					if (!$rule->check($value)) {
+						$matched = false;
+					}
+				}
+
+			}
+		}
+
+		return $matched;
+	}
+
+}
diff --git a/extensions/yii/debug/components/search/matches/Base.php b/extensions/yii/debug/components/search/matches/Base.php
new file mode 100644
index 0000000..6d8250d
--- /dev/null
+++ b/extensions/yii/debug/components/search/matches/Base.php
@@ -0,0 +1,26 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\debug\components\search\matches;
+
+use yii\base\Component;
+
+/**
+ * Base mathcer class for all matchers that will be used with filter.
+ * 
+ * @author Mark Jebri <mark.github@yandex.ru>
+ * @since 2.0
+ */
+abstract class Base extends Component implements MatcherInterface
+{
+
+	/**
+	 * @var mixed current value to check for the matcher
+	 */
+	public $value;
+
+}
diff --git a/extensions/yii/debug/components/search/matches/Exact.php b/extensions/yii/debug/components/search/matches/Exact.php
new file mode 100644
index 0000000..34bb0cd
--- /dev/null
+++ b/extensions/yii/debug/components/search/matches/Exact.php
@@ -0,0 +1,36 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\debug\components\search\matches;
+
+/**
+ *
+ * @author Mark Jebri <mark.github@yandex.ru>
+ * @since 2.0
+ */
+class Exact extends Base
+{
+
+	/**
+	 * @var boolean if current matcher should consider partial mathc of given value.
+	 */
+	public $partial = false;
+
+	/**
+	 * Checks if the given value is the same as base one or has partial match with base one.
+	 * @param mixed $value
+	 */
+	public function check($value)
+	{
+		if (!$this->partial) {
+			return (mb_strtolower($this->value, 'utf8') == mb_strtolower($value, 'utf8'));
+		} else {
+			return (mb_strpos($value, $this->value) !== false);
+		}
+	}
+
+}
diff --git a/extensions/yii/debug/components/search/matches/Greater.php b/extensions/yii/debug/components/search/matches/Greater.php
new file mode 100644
index 0000000..e475dc7
--- /dev/null
+++ b/extensions/yii/debug/components/search/matches/Greater.php
@@ -0,0 +1,27 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\debug\components\search\matches;
+
+/**
+ * 
+ * @author Mark Jebri <mark.github@yandex.ru>
+ * @since 2.0
+ */
+class Greater extends Base
+{
+
+	/**
+	 * Checks if the given value is the same as base one or has partial match with base one.
+	 * @param mixed $value
+	 */
+	public function check($value)
+	{
+		return ($value > $this->value);
+	}
+
+}
diff --git a/extensions/yii/debug/components/search/matches/Lower.php b/extensions/yii/debug/components/search/matches/Lower.php
new file mode 100644
index 0000000..9222fd7
--- /dev/null
+++ b/extensions/yii/debug/components/search/matches/Lower.php
@@ -0,0 +1,27 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\debug\components\search\matches;
+
+/**
+ * 
+ * @author Mark Jebri <mark.github@yandex.ru>
+ * @since 2.0
+ */
+class Lower extends Base
+{
+
+	/**
+	 * Checks if the given value is the same as base one or has partial match with base one.
+	 * @param mixed $value
+	 */
+	public function check($value)
+	{
+		return ($value < $this->value);
+	}
+
+}
diff --git a/extensions/yii/debug/components/search/matches/MatcherInterface.php b/extensions/yii/debug/components/search/matches/MatcherInterface.php
new file mode 100644
index 0000000..16c0705
--- /dev/null
+++ b/extensions/yii/debug/components/search/matches/MatcherInterface.php
@@ -0,0 +1,25 @@
+<?php
+/**
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\debug\components\search\matches;
+
+/**
+ * MatcherInterface is the interface that should be implemented by all matchers that will be used in filter.
+ * 
+ * @author Mark Jebri <mark.github@yandex.ru>
+ * @since 2.0
+ */
+interface MatcherInterface
+{
+
+	/**
+	 * Check if the value is correct according current matcher.
+	 * @param mixed $value
+	 */
+	public function check($value);
+
+}
diff --git a/extensions/yii/debug/controllers/DefaultController.php b/extensions/yii/debug/controllers/DefaultController.php
index 4d525f7..6109a39 100644
--- a/extensions/yii/debug/controllers/DefaultController.php
+++ b/extensions/yii/debug/controllers/DefaultController.php
@@ -10,6 +10,7 @@ namespace yii\debug\controllers;
 use Yii;
 use yii\web\Controller;
 use yii\web\NotFoundHttpException;
+use yii\debug\models\search\Debug;
 
 /**
  * @author Qiang Xue <qiang.xue@gmail.com>
@@ -38,7 +39,13 @@ class DefaultController extends Controller
 
 	public function actionIndex()
 	{
-		return $this->render('index', ['manifest' => $this->getManifest()]);
+		$searchModel = new Debug();
+		$dataProvider = $searchModel->search($_GET, $this->getManifest());
+
+		return $this->render('index', [
+			'dataProvider' => $dataProvider,
+			'searchModel' => $searchModel,
+		]);
 	}
 
 	public function actionView($tag = null, $panel = null)
diff --git a/extensions/yii/debug/models/search/Debug.php b/extensions/yii/debug/models/search/Debug.php
new file mode 100644
index 0000000..f89990f
--- /dev/null
+++ b/extensions/yii/debug/models/search/Debug.php
@@ -0,0 +1,149 @@
+<?php
+
+namespace yii\debug\models\search;
+
+use yii\base\Model;
+use yii\data\ArrayDataProvider;
+use yii\debug\components\search\Filter;
+use yii\debug\components\search\matches;
+
+/**
+ * Debug represents the model behind the search form about requests manifest data.
+ */
+class Debug extends Model
+{
+	/**
+	 * @var string tag attribute input search value
+	 */
+	public $tag;
+
+	/**
+	 * @var string ip attribute input search value
+	 */
+	public $ip;
+
+	/**
+	 * @var string method attribute input search value
+	 */
+	public $method;
+
+	/**
+	 * @var integer ajax attribute input search value
+	 */
+	public $ajax;
+
+	/**
+	 * @var string url attribute input search value
+	 */
+	public $url;
+
+	/**
+	 * @var string status code attribute input search value
+	 */
+	public $statusCode;
+
+	/**
+	 *
+	 * @var integer sql count attribute input search value
+	 */
+	public $sqlCount;
+
+	/**
+	 * @var array critical codes, used to determine grid row options.
+	 */
+	public $criticalCodes = [400, 404, 500];
+
+	public function rules()
+	{
+		return [
+			[['tag', 'ip', 'method', 'ajax', 'url', 'statusCode', 'sqlCount'], 'safe'],
+		];
+	}
+
+	/**
+	 * @inheritdoc
+	 */
+	public function attributeLabels()
+	{
+		return [
+			'tag' => 'Tag',
+			'ip' => 'Ip',
+			'method' => 'Method',
+			'ajax' => 'Ajax',
+			'url' => 'url',
+			'statusCode' => 'Status code',
+			'sqlCount' => 'Total queries count',
+		];
+	}
+
+	/**
+	 * Returns data provider with filled models. Filter applied if needed.
+	 * @param array $params
+	 * @param array $models
+	 * @return \yii\data\ArrayDataProvider
+	 */
+	public function search($params, $models)
+	{
+		$dataProvider = new ArrayDataProvider([
+			'allModels' => $models,
+			'sort' => [
+				'attributes' => ['method', 'ip', 'tag', 'time', 'statusCode', 'sqlCount'],
+			],
+			'pagination' => [
+				'pageSize' => 10,
+			],
+		]);
+
+		if (!($this->load($params) && $this->validate())) {
+			return $dataProvider;
+		}
+
+		$filter = new Filter();
+		$this->addCondition($filter, 'tag', true);
+		$this->addCondition($filter, 'ip', true);
+		$this->addCondition($filter, 'method');
+		$this->addCondition($filter, 'ajax');
+		$this->addCondition($filter, 'url', true);
+		$this->addCondition($filter, 'statusCode');
+		$this->addCondition($filter, 'sqlCount');
+		$dataProvider->allModels = $filter->filter($models);
+
+		return $dataProvider;
+	}
+
+	/**
+	 * Checks if the code is critical: 400 or greater, 500 or greater.
+	 * @param integer $code
+	 * @return bool
+	 */
+	public function isCodeCritical($code)
+	{
+		return in_array($code, $this->criticalCodes);
+	}
+
+	/**
+	 * @param Filter $filter
+	 * @param string $attribute
+	 * @param boolean $partial
+	 */
+	public function addCondition($filter, $attribute, $partial = false)
+	{
+		$value = $this->$attribute;
+
+		if (mb_strpos($value, '>') !== false) {
+
+			$value = intval(str_replace('>', '', $value));
+			$filter->addMatch($attribute, new matches\Greater(['value' => $value]));
+
+		} elseif (mb_strpos($value, '<') !== false) {
+
+			$value = intval(str_replace('<', '', $value));
+			$filter->addMatch($attribute, new matches\Lower(['value' => $value]));
+
+		} else {
+			$filter->addMatch($attribute, new matches\Exact(['value' => $value, 'partial' => $partial]));
+		}
+
+	}
+
+}
diff --git a/extensions/yii/debug/panels/DbPanel.php b/extensions/yii/debug/panels/DbPanel.php
index a487dd4..bc79072 100644
--- a/extensions/yii/debug/panels/DbPanel.php
+++ b/extensions/yii/debug/panels/DbPanel.php
@@ -117,7 +117,7 @@ EOD;
 	public function save()
 	{
 		$target = $this->module->logTarget;
-		$messages = $target->filterMessages($target->messages, Logger::LEVEL_PROFILE, ['yii\db\Command::queryInternal']);
+		$messages = $target->filterMessages($target->messages, Logger::LEVEL_PROFILE, ['yii\db\Command::query', 'yii\db\Command::execute']);
 		return ['messages' => $messages];
 	}
 }
diff --git a/extensions/yii/debug/views/default/index.php b/extensions/yii/debug/views/default/index.php
index 93737e2..73f5237 100644
--- a/extensions/yii/debug/views/default/index.php
+++ b/extensions/yii/debug/views/default/index.php
@@ -1,10 +1,14 @@
 <?php
 
 use yii\helpers\Html;
+use yii\grid\GridView;
+use yii\data\ArrayDataProvider;
 
 /**
  * @var \yii\web\View $this
  * @var array $manifest
+ * @var \yii\debug\models\search\Debug $searchModel
+ * @var ArrayDataProvider $dataProvider
  */
 
 $this->title = 'Yii Debugger';
@@ -19,28 +23,63 @@ $this->title = 'Yii Debugger';
 	<div class="container">
 		<div class="row">
 			<h1>Available Debug Data</h1>
-			<table class="table table-condensed table-bordered table-striped table-hover" style="table-layout: fixed;">
-				<thead>
-					<tr>
-						<th style="width: 120px;">Tag</th>
-						<th style="width: 170px;">Time</th>
-						<th style="width: 120px;">IP</th>
-						<th style="width: 70px;">Method</th>
-						<th>URL</th>
-					</tr>
-				</thead>
-				<tbody>
-					<?php foreach ($manifest as $tag => $data): ?>
-					<tr>
-						<td><?= Html::a($tag, ['view', 'tag' => $tag]) ?></td>
-						<td><?= date('Y-m-d h:i:sa', $data['time']) ?></td>
-						<td><?= $data['ip'] ?></td>
-						<td><?= $data['method'] ?></td>
-						<td><?= $data['url'] ?></td>
-					</tr>
-					<?php endforeach; ?>
-				</tbody>
-			</table>
+
+<?php
+
+$timeFormatter = extension_loaded('intl') ? Yii::createObject(['class' => 'yii\i18n\Formatter']) : Yii::$app->formatter;
+
+echo GridView::widget([
+	'dataProvider' => $dataProvider,
+	'filterModel' => $searchModel,
+	'rowOptions' => function ($model, $key, $index, $grid) use ($searchModel) {
+		if ($searchModel->isCodeCritical($model['statusCode'])) {
+			return ['class'=>'danger'];
+		} else {
+			return [];
+		}
+	},
+	'columns' => [
+		['class' => 'yii\grid\SerialColumn'],
+		[
+			'attribute' => 'tag',
+			'value' => function ($data)
+			{
+				return Html::a($data['tag'], ['view', 'tag' => $data['tag']]);
+			},
+			'format' => 'html',
+		],
+		[
+			'attribute' => 'time',
+			'value' => function ($data) use ($timeFormatter)
+			{
+				return $timeFormatter->asDateTime($data['time'], 'long');
+			},
+		],
+		'ip',
+		[
+			'attribute' => 'sqlCount',
+			'label' => 'Total queries count'
+		],
+		[
+			'attribute' => 'method',
+			'filter' => ['get' => 'GET', 'post' => 'POST', 'delete' => 'DELETE', 'put' => 'PUT', 'head' => 'HEAD']
+		],
+		[
+			'attribute'=>'ajax',
+			'value' => function ($data)
+			{
+				return $data['ajax'] ? 'Yes' : 'No';
+			},
+			'filter' => ['No', 'Yes'],
+		],
+		'url',
+		[
+			'attribute' => 'statusCode',
+			'filter' => [200 => 200, 404 => 404, 403 => 403, 500 => 500],
+			'label' => 'Status code'
+		],
+	],
+]); ?>
 		</div>
 	</div>
 </div>
diff --git a/extensions/yii/debug/views/layouts/main.php b/extensions/yii/debug/views/layouts/main.php
index 97ef08c..0f9a02c 100644
--- a/extensions/yii/debug/views/layouts/main.php
+++ b/extensions/yii/debug/views/layouts/main.php
@@ -11,6 +11,8 @@ yii\debug\DebugAsset::register($this);
 <html>
 <?php $this->beginPage() ?>
 <head>
+	<meta charset="utf-8">
+	<meta name="viewport" content="width=device-width, initial-scale=1">
 	<title><?= Html::encode($this->title) ?></title>
 	<?php $this->head() ?>
 </head>
diff --git a/extensions/yii/gii/generators/crud/templates/search.php b/extensions/yii/gii/generators/crud/templates/search.php
index 1411896..bc55c60 100644
--- a/extensions/yii/gii/generators/crud/templates/search.php
+++ b/extensions/yii/gii/generators/crud/templates/search.php
@@ -74,7 +74,6 @@ class <?= $searchModelClass ?> extends Model
 			return;
 		}
 		if ($partialMatch) {
-			$value = '%' . strtr($value, ['%'=>'\%', '_'=>'\_', '\\'=>'\\\\']) . '%';
 			$query->andWhere(['like', $attribute, $value]);
 		} else {
 			$query->andWhere([$attribute => $value]);
diff --git a/extensions/yii/gii/views/layouts/main.php b/extensions/yii/gii/views/layouts/main.php
index 6950668..f324e41 100644
--- a/extensions/yii/gii/views/layouts/main.php
+++ b/extensions/yii/gii/views/layouts/main.php
@@ -14,6 +14,7 @@ $asset = yii\gii\GiiAsset::register($this);
 <html lang="en">
 <head>
 	<meta charset="utf-8"/>
+	<meta name="viewport" content="width=device-width, initial-scale=1">
 	<title><?= Html::encode($this->title) ?></title>
 	<?php $this->head() ?>
 </head>
diff --git a/extensions/yii/sphinx/Query.php b/extensions/yii/sphinx/Query.php
index dd13363..2a51996 100644
--- a/extensions/yii/sphinx/Query.php
+++ b/extensions/yii/sphinx/Query.php
@@ -429,6 +429,8 @@ class Query extends Component implements QueryInterface
 	 * using `AND`. For example, `['like', 'name', ['%test%', '%sample%']]` will generate
 	 * `name LIKE '%test%' AND name LIKE '%sample%'`.
 	 * The method will properly quote the column name and escape values in the range.
+	 * Sometimes, you may want to add the percentage characters to the matching value by yourself, you may supply
+	 * a third operand `false` to do so. For example, `['like', 'name', '%tester', false]` will generate `name LIKE '%tester'`.
 	 *
 	 * - `or like`: similar to the `like` operator except that `OR` is used to concatenate the `LIKE`
 	 * predicates when operand 2 is an array.
@@ -700,4 +702,4 @@ class Query extends Component implements QueryInterface
 			->callSnippets($this->from[0], $source, $match, $this->snippetOptions)
 			->queryColumn();
 	}
-}
\ No newline at end of file
+}
diff --git a/extensions/yii/sphinx/QueryBuilder.php b/extensions/yii/sphinx/QueryBuilder.php
index 586b3a5..6bd6d0c 100644
--- a/extensions/yii/sphinx/QueryBuilder.php
+++ b/extensions/yii/sphinx/QueryBuilder.php
@@ -754,11 +754,19 @@ class QueryBuilder extends Object
 	 * Creates an SQL expressions with the `LIKE` operator.
 	 * @param IndexSchema[] $indexes list of indexes, which affected by query
 	 * @param string $operator the operator to use (e.g. `LIKE`, `NOT LIKE`, `OR LIKE` or `OR NOT LIKE`)
-	 * @param array $operands the first operand is the column name.
-	 * The second operand is a single value or an array of values that column value
-	 * should be compared with.
-	 * If it is an empty array the generated expression will be a `false` value if
-	 * operator is `LIKE` or `OR LIKE` and empty if operator is `NOT LIKE` or `OR NOT LIKE`.
+	 * @param array $operands an array of two or three operands
+	 *
+	 * - The first operand is the column name.
+	 * - The second operand is a single value or an array of values that column value
+	 *   should be compared with. If it is an empty array the generated expression will
+	 *   be a `false` value if operator is `LIKE` or `OR LIKE`, and empty if operator
+	 *   is `NOT LIKE` or `OR NOT LIKE`.
+	 * - An optional third operand can also be provided to specify how to escape special characters
+	 *   in the value(s). The operand should be an array of mappings from the special characters to their
+	 *   escaped counterparts. If this operand is not provided, a default escape mapping will be used.
+	 *   You may use `false` or an empty array to indicate the values are already escaped and no escape
+	 *   should be applied. Note that when using an escape mapping (or the third operand is not provided),
+	 *   the values will be automatically enclosed within a pair of percentage characters.
 	 * @param array $params the binding parameters to be populated
 	 * @return string the generated SQL expression
 	 * @throws InvalidParamException if wrong number of operands have been given.
@@ -769,6 +777,9 @@ class QueryBuilder extends Object
 			throw new InvalidParamException("Operator '$operator' requires two operands.");
 		}
 
+		$escape = isset($operands[2]) ? $operands[2] : ['%'=>'\%', '_'=>'\_', '\\'=>'\\\\'];
+		unset($operands[2]);
+
 		list($column, $values) = $operands;
 
 		$values = (array)$values;
@@ -791,7 +802,7 @@ class QueryBuilder extends Object
 		$parts = [];
 		foreach ($values as $value) {
 			$phName = self::PARAM_PREFIX . count($params);
-			$params[$phName] = $value;
+			$params[$phName] = empty($escape) ? $value : ('%' . strtr($value, $escape) . '%');
 			$parts[] = "$column $operator $phName";
 		}
 
@@ -902,4 +913,4 @@ class QueryBuilder extends Object
 			return $phName;
 		}
 	}
-}
\ No newline at end of file
+}
diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md
index b7699ac..9126e71 100644
--- a/framework/CHANGELOG.md
+++ b/framework/CHANGELOG.md
@@ -15,10 +15,14 @@ Yii Framework 2 Change Log
 - Bug #1582: Error messages shown via client-side validation should not be double encoded (qiangxue)
 - Bug #1591: StringValidator is accessing undefined property (qiangxue)
 - Bug #1597: Added `enableAutoLogin` to basic and advanced application templates so "remember me" now works properly (samdark)
+- Bug #1631: Charset is now explicitly set to UTF-8 when serving JSON (samdark)
+- Bug #1686: ActiveForm is creating duplicated messages in error summary (qiangxue)
 - Bug: Fixed `Call to a member function registerAssetFiles() on a non-object` in case of wrong `sourcePath` for an asset bundle (samdark)
 - Bug: Fixed incorrect event name for `yii\jui\Spinner` (samdark)
 - Bug: Json::encode() did not handle objects that implement JsonSerializable interface correctly (cebe)
 - Bug: Fixed issue with tabular input on ActiveField::radio() and ActiveField::checkbox() (jom)
+- Bug: Fixed the issue that query cache returns the same data for the same SQL but different query methods (qiangxue)
+- Enh #364: Improve Inflector::slug with `intl` transliteration. Improved transliteration char map. (tonydspaniard)
 - Enh #797: Added support for validating multiple columns by `UniqueValidator` and `ExistValidator` (qiangxue)
 - Enh #1293: Replaced Console::showProgress() with a better approach. See Console::startProgress() for details (cebe)
 - Enh #1406: DB Schema support for Oracle Database (p0larbeer, qiangxue)
@@ -36,10 +40,12 @@ Yii Framework 2 Change Log
 - Enh #1641: Added `BaseActiveRecord::updateAttributes()` (qiangxue)
 - Enh #1646: Added postgresql `QueryBuilder::checkIntegrity` and `QueryBuilder::resetSequence` (Ragazzo)
 - Enh #1645: Added `Connection::$pdoClass` property (Ragazzo)
+- Enh #1681: Added support for automatically adjusting the "for" attribute of label generated by `ActiveField::label()` (qiangxue)
 - Enh: Added `favicon.ico` and `robots.txt` to defauly application templates (samdark)
 - Enh: Added `Widget::autoIdPrefix` to support prefixing automatically generated widget IDs (qiangxue)
 - Enh: Support for file aliases in console command 'message' (omnilight)
 - Enh: Sort and Pagination can now create absolute URLs (cebe)
+- Chg #1586: `QueryBuilder::buildLikeCondition()` will now escape special characters and use percentage characters by default (qiangxue)
 - Chg #1610: `Html::activeCheckboxList()` and `Html::activeRadioList()` will submit an empty string if no checkbox/radio is selected (qiangxue)
 - Chg #1643: Added default value for `Captcha::options` (qiangxue)
 - Chg: Renamed `yii\jui\Widget::clientEventsMap` to `clientEventMap` (qiangxue)
diff --git a/framework/yii/assets/yii.activeForm.js b/framework/yii/assets/yii.activeForm.js
index e898efc..d4fc877 100644
--- a/framework/yii/assets/yii.activeForm.js
+++ b/framework/yii/assets/yii.activeForm.js
@@ -365,7 +365,7 @@
 	var updateSummary = function ($form, messages) {
 		var data = $form.data('yiiActiveForm'),
 			$summary = $form.find(data.settings.errorSummary),
-			$ul = $summary.find('ul');
+			$ul = $summary.find('ul').html('');
 
 		if ($summary.length && messages) {
 			$.each(data.attributes, function () {
diff --git a/framework/yii/db/Command.php b/framework/yii/db/Command.php
index 76b4269..f7eb227 100644
--- a/framework/yii/db/Command.php
+++ b/framework/yii/db/Command.php
@@ -368,7 +368,7 @@ class Command extends \yii\base\Component
 		$db = $this->db;
 		$rawSql = $this->getRawSql();
 
-		Yii::info($rawSql, __METHOD__);
+		Yii::info($rawSql, 'yii\db\Command::query');
 
 		/** @var \yii\caching\Cache $cache */
 		if ($db->enableQueryCache && $method !== '') {
@@ -378,19 +378,20 @@ class Command extends \yii\base\Component
 		if (isset($cache) && $cache instanceof Cache) {
 			$cacheKey = [
 				__CLASS__,
+				$method,
 				$db->dsn,
 				$db->username,
 				$rawSql,
 			];
 			if (($result = $cache->get($cacheKey)) !== false) {
-				Yii::trace('Query result served from cache', __METHOD__);
+				Yii::trace('Query result served from cache', 'yii\db\Command::query');
 				return $result;
 			}
 		}
 
 		$token = $rawSql;
 		try {
-			Yii::beginProfile($token, __METHOD__);
+			Yii::beginProfile($token, 'yii\db\Command::query');
 
 			$this->prepare();
 			$this->pdoStatement->execute();
@@ -405,16 +406,16 @@ class Command extends \yii\base\Component
 				$this->pdoStatement->closeCursor();
 			}
 
-			Yii::endProfile($token, __METHOD__);
+			Yii::endProfile($token, 'yii\db\Command::query');
 
 			if (isset($cache, $cacheKey) && $cache instanceof Cache) {
 				$cache->set($cacheKey, $result, $db->queryCacheDuration, $db->queryCacheDependency);
-				Yii::trace('Saved query result in cache', __METHOD__);
+				Yii::trace('Saved query result in cache', 'yii\db\Command::query');
 			}
 
 			return $result;
 		} catch (\Exception $e) {
-			Yii::endProfile($token, __METHOD__);
+			Yii::endProfile($token, 'yii\db\Command::query');
 			if ($e instanceof Exception) {
 				throw $e;
 			} else {
diff --git a/framework/yii/db/Query.php b/framework/yii/db/Query.php
index ee24c2f..9da4f14 100644
--- a/framework/yii/db/Query.php
+++ b/framework/yii/db/Query.php
@@ -387,11 +387,13 @@ class Query extends Component implements QueryInterface
 	 *
 	 * - `like`: operand 1 should be a column or DB expression, and operand 2 be a string or an array representing
 	 * the values that the column or DB expression should be like.
-	 * For example, `['like', 'name', '%tester%']` will generate `name LIKE '%tester%'`.
+	 * For example, `['like', 'name', 'tester']` will generate `name LIKE '%tester%'`.
 	 * When the value range is given as an array, multiple `LIKE` predicates will be generated and concatenated
-	 * using `AND`. For example, `['like', 'name', ['%test%', '%sample%']]` will generate
+	 * using `AND`. For example, `['like', 'name', ['test', 'sample']]` will generate
 	 * `name LIKE '%test%' AND name LIKE '%sample%'`.
-	 * The method will properly quote the column name and escape values in the range.
+	 * The method will properly quote the column name and escape special characters in the values.
+	 * Sometimes, you may want to add the percentage characters to the matching value by yourself, you may supply
+	 * a third operand `false` to do so. For example, `['like', 'name', '%tester', false]` will generate `name LIKE '%tester'`.
 	 *
 	 * - `or like`: similar to the `like` operator except that `OR` is used to concatenate the `LIKE`
 	 * predicates when operand 2 is an array.
diff --git a/framework/yii/db/QueryBuilder.php b/framework/yii/db/QueryBuilder.php
index a76f211..f819362 100644
--- a/framework/yii/db/QueryBuilder.php
+++ b/framework/yii/db/QueryBuilder.php
@@ -1017,11 +1017,19 @@ class QueryBuilder extends \yii\base\Object
 	/**
 	 * Creates an SQL expressions with the `LIKE` operator.
 	 * @param string $operator the operator to use (e.g. `LIKE`, `NOT LIKE`, `OR LIKE` or `OR NOT LIKE`)
-	 * @param array $operands the first operand is the column name.
-	 * The second operand is a single value or an array of values that column value
-	 * should be compared with.
-	 * If it is an empty array the generated expression will be a `false` value if
-	 * operator is `LIKE` or `OR LIKE` and empty if operator is `NOT LIKE` or `OR NOT LIKE`.
+	 * @param array $operands an array of two or three operands
+	 *
+	 * - The first operand is the column name.
+	 * - The second operand is a single value or an array of values that column value
+	 *   should be compared with. If it is an empty array the generated expression will
+	 *   be a `false` value if operator is `LIKE` or `OR LIKE`, and empty if operator
+	 *   is `NOT LIKE` or `OR NOT LIKE`.
+	 * - An optional third operand can also be provided to specify how to escape special characters
+	 *   in the value(s). The operand should be an array of mappings from the special characters to their
+	 *   escaped counterparts. If this operand is not provided, a default escape mapping will be used.
+	 *   You may use `false` or an empty array to indicate the values are already escaped and no escape
+	 *   should be applied. Note that when using an escape mapping (or the third operand is not provided),
+	 *   the values will be automatically enclosed within a pair of percentage characters.
 	 * @param array $params the binding parameters to be populated
 	 * @return string the generated SQL expression
 	 * @throws InvalidParamException if wrong number of operands have been given.
@@ -1032,6 +1040,9 @@ class QueryBuilder extends \yii\base\Object
 			throw new InvalidParamException("Operator '$operator' requires two operands.");
 		}
 
+		$escape = isset($operands[2]) ? $operands[2] : ['%'=>'\%', '_'=>'\_', '\\'=>'\\\\'];
+		unset($operands[2]);
+
 		list($column, $values) = $operands;
 
 		$values = (array)$values;
@@ -1054,7 +1065,7 @@ class QueryBuilder extends \yii\base\Object
 		$parts = [];
 		foreach ($values as $value) {
 			$phName = self::PARAM_PREFIX . count($params);
-			$params[$phName] = $value;
+			$params[$phName] = empty($escape) ? $value : ('%' . strtr($value, $escape) . '%');
 			$parts[] = "$column $operator $phName";
 		}
 
diff --git a/framework/yii/db/QueryInterface.php b/framework/yii/db/QueryInterface.php
index f3cc312..4cf802d 100644
--- a/framework/yii/db/QueryInterface.php
+++ b/framework/yii/db/QueryInterface.php
@@ -122,11 +122,13 @@ interface QueryInterface
 	 *
 	 * - `like`: operand 1 should be a column or DB expression, and operand 2 be a string or an array representing
 	 * the values that the column or DB expression should be like.
-	 * For example, `['like', 'name', '%tester%']` will generate `name LIKE '%tester%'`.
+	 * For example, `['like', 'name', 'tester']` will generate `name LIKE '%tester%'`.
 	 * When the value range is given as an array, multiple `LIKE` predicates will be generated and concatenated
-	 * using `AND`. For example, `['like', 'name', ['%test%', '%sample%']]` will generate
+	 * using `AND`. For example, `['like', 'name', ['test', 'sample']]` will generate
 	 * `name LIKE '%test%' AND name LIKE '%sample%'`.
-	 * The method will properly quote the column name and escape values in the range.
+	 * The method will properly quote the column name and escape special characters in the values.
+	 * Sometimes, you may want to add the percentage characters to the matching value by yourself, you may supply
+	 * a third operand `false` to do so. For example, `['like', 'name', '%tester', false]` will generate `name LIKE '%tester'`.
 	 *
 	 * - `or like`: similar to the `like` operator except that `OR` is used to concatenate the `LIKE`
 	 * predicates when operand 2 is an array.
@@ -203,4 +205,4 @@ interface QueryInterface
 	 * @return static the query object itself
 	 */
 	public function offset($offset);
-}
\ No newline at end of file
+}
diff --git a/framework/yii/db/mysql/Schema.php b/framework/yii/db/mysql/Schema.php
index d5258c1..a649d8a 100644
--- a/framework/yii/db/mysql/Schema.php
+++ b/framework/yii/db/mysql/Schema.php
@@ -280,7 +280,7 @@ class Schema extends \yii\db\Schema
 	 * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
 	 * @return array all table names in the database. The names have NO schema name prefix.
 	 */
-	public function findTableNames($schema = '')
+	protected function findTableNames($schema = '')
 	{
 		$sql = 'SHOW TABLES';
 		if ($schema !== '') {
diff --git a/framework/yii/db/pgsql/QueryBuilder.php b/framework/yii/db/pgsql/QueryBuilder.php
index f1ac032..998e746 100644
--- a/framework/yii/db/pgsql/QueryBuilder.php
+++ b/framework/yii/db/pgsql/QueryBuilder.php
@@ -109,7 +109,7 @@ class QueryBuilder extends \yii\db\QueryBuilder
 	{
 		$enable = $check ? 'ENABLE' : 'DISABLE';
 		$schema = $schema ? $schema : $this->db->schema->defaultSchema;
-		$tableNames = $table ? [$table] : $this->db->schema->findTableNames($schema);
+		$tableNames = $table ? [$table] : $this->db->schema->getTableNames($schema);
 		$command = '';
 
 		foreach ($tableNames as $tableName) {
diff --git a/framework/yii/db/pgsql/Schema.php b/framework/yii/db/pgsql/Schema.php
index 59340c9..96889ab 100644
--- a/framework/yii/db/pgsql/Schema.php
+++ b/framework/yii/db/pgsql/Schema.php
@@ -158,7 +158,7 @@ class Schema extends \yii\db\Schema
 	 * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
 	 * @return array all table names in the database. The names have NO schema name prefix.
 	 */
-	public function findTableNames($schema = '')
+	protected function findTableNames($schema = '')
 	{
 		if ($schema === '') {
 			$schema = $this->defaultSchema;
diff --git a/framework/yii/db/sqlite/Schema.php b/framework/yii/db/sqlite/Schema.php
index 6548999..9f410b4 100644
--- a/framework/yii/db/sqlite/Schema.php
+++ b/framework/yii/db/sqlite/Schema.php
@@ -87,7 +87,7 @@ class Schema extends \yii\db\Schema
 	 * If not empty, the returned table names will be prefixed with the schema name.
 	 * @return array all table names in the database.
 	 */
-	public function findTableNames($schema = '')
+	protected function findTableNames($schema = '')
 	{
 		$sql = "SELECT DISTINCT tbl_name FROM sqlite_master WHERE tbl_name<>'sqlite_sequence'";
 		return $this->db->createCommand($sql)->queryColumn();
diff --git a/framework/yii/helpers/BaseInflector.php b/framework/yii/helpers/BaseInflector.php
index 2f4f01b..cb59986 100644
--- a/framework/yii/helpers/BaseInflector.php
+++ b/framework/yii/helpers/BaseInflector.php
@@ -215,60 +215,67 @@ class BaseInflector
 		'wildebeest' => 'wildebeest',
 		'Yengeese' => 'Yengeese',
 	];
+
 	/**
 	 * @var array map of special chars and its translation. This is used by [[slug()]].
 	 */
 	public static $transliteration = [
-		'/ä|æ|ǽ/' => 'ae',
-		'/ö|œ/' => 'oe',
-		'/ü/' => 'ue',
-		'/Ä/' => 'Ae',
-		'/Ü/' => 'Ue',
-		'/Ö/' => 'Oe',
-		'/À|Á|Â|Ã|Å|Ǻ|Ā|Ă|Ą|Ǎ/' => 'A',
-		'/à|á|â|ã|å|ǻ|ā|ă|ą|ǎ|ª/' => 'a',
-		'/Ç|Ć|Ĉ|Ċ|Č/' => 'C',
-		'/ç|ć|ĉ|ċ|č/' => 'c',
-		'/Ð|Ď|Đ/' => 'D',
-		'/ð|ď|đ/' => 'd',
-		'/È|É|Ê|Ë|Ē|Ĕ|Ė|Ę|Ě/' => 'E',
-		'/è|é|ê|ë|ē|ĕ|ė|ę|ě/' => 'e',
-		'/Ĝ|Ğ|Ġ|Ģ/' => 'G',
-		'/ĝ|ğ|ġ|ģ/' => 'g',
-		'/Ĥ|Ħ/' => 'H',
-		'/ĥ|ħ/' => 'h',
-		'/Ì|Í|Î|Ï|Ĩ|Ī|Ĭ|Ǐ|Į|İ/' => 'I',
-		'/ì|í|î|ï|ĩ|ī|ĭ|ǐ|į|ı/' => 'i',
-		'/Ĵ/' => 'J',
-		'/ĵ/' => 'j',
-		'/Ķ/' => 'K',
-		'/ķ/' => 'k',
-		'/Ĺ|Ļ|Ľ|Ŀ|Ł/' => 'L',
-		'/ĺ|ļ|ľ|ŀ|ł/' => 'l',
-		'/Ñ|Ń|Ņ|Ň/' => 'N',
-		'/ñ|ń|ņ|ň|ʼn/' => 'n',
-		'/Ò|Ó|Ô|Õ|Ō|Ŏ|Ǒ|Ő|Ơ|Ø|Ǿ/' => 'O',
-		'/ò|ó|ô|õ|ō|ŏ|ǒ|ő|ơ|ø|ǿ|º/' => 'o',
-		'/Ŕ|Ŗ|Ř/' => 'R',
-		'/ŕ|ŗ|ř/' => 'r',
-		'/Ś|Ŝ|Ş|Ș|Š/' => 'S',
-		'/ś|ŝ|ş|ș|š|ſ/' => 's',
-		'/Ţ|Ț|Ť|Ŧ/' => 'T',
-		'/ţ|ț|ť|ŧ/' => 't',
-		'/Ù|Ú|Û|Ũ|Ū|Ŭ|Ů|Ű|Ų|Ư|Ǔ|Ǖ|Ǘ|Ǚ|Ǜ/' => 'U',
-		'/ù|ú|û|ũ|ū|ŭ|ů|ű|ų|ư|ǔ|ǖ|ǘ|ǚ|ǜ/' => 'u',
-		'/Ý|Ÿ|Ŷ/' => 'Y',
-		'/ý|ÿ|ŷ/' => 'y',
-		'/Ŵ/' => 'W',
-		'/ŵ/' => 'w',
-		'/Ź|Ż|Ž/' => 'Z',
-		'/ź|ż|ž/' => 'z',
-		'/Æ|Ǽ/' => 'AE',
-		'/ß/' => 'ss',
-		'/IJ/' => 'IJ',
-		'/ij/' => 'ij',
-		'/Œ/' => 'OE',
-		'/ƒ/' => 'f'
+		// Latin
+		'À' => 'A', 'Á' => 'A', 'Â' => 'A', 'Ã' => 'A', 'Ä' => 'A', 'Å' => 'A', 'Æ' => 'AE', 'Ç' => 'C',
+		'È' => 'E', 'É' => 'E', 'Ê' => 'E', 'Ë' => 'E', 'Ì' => 'I', 'Í' => 'I', 'Î' => 'I', 'Ï' => 'I',
+		'Ð' => 'D', 'Ñ' => 'N', 'Ò' => 'O', 'Ó' => 'O', 'Ô' => 'O', 'Õ' => 'O', 'Ö' => 'O', 'Ő' => 'O',
+		'Ø' => 'O', 'Ù' => 'U', 'Ú' => 'U', 'Û' => 'U', 'Ü' => 'U', 'Ű' => 'U', 'Ý' => 'Y', 'Þ' => 'TH',
+		'ß' => 'ss',
+		'à' => 'a', 'á' => 'a', 'â' => 'a', 'ã' => 'a', 'ä' => 'a', 'å' => 'a', 'æ' => 'ae', 'ç' => 'c',
+		'è' => 'e', 'é' => 'e', 'ê' => 'e', 'ë' => 'e', 'ì' => 'i', 'í' => 'i', 'î' => 'i', 'ï' => 'i',
+		'ð' => 'd', 'ñ' => 'n', 'ò' => 'o', 'ó' => 'o', 'ô' => 'o', 'õ' => 'o', 'ö' => 'o', 'ő' => 'o',
+		'ø' => 'o', 'ù' => 'u', 'ú' => 'u', 'û' => 'u', 'ü' => 'u', 'ű' => 'u', 'ý' => 'y', 'þ' => 'th',
+		'ÿ' => 'y',
+		// Latin symbols
+		'©' => '(c)',
+		// Greek
+		'Α' => 'A', 'Β' => 'B', 'Γ' => 'G', 'Δ' => 'D', 'Ε' => 'E', 'Ζ' => 'Z', 'Η' => 'H', 'Θ' => '8',
+		'Ι' => 'I', 'Κ' => 'K', 'Λ' => 'L', 'Μ' => 'M', 'Ν' => 'N', 'Ξ' => '3', 'Ο' => 'O', 'Π' => 'P',
+		'Ρ' => 'R', 'Σ' => 'S', 'Τ' => 'T', 'Υ' => 'Y', 'Φ' => 'F', 'Χ' => 'X', 'Ψ' => 'PS', 'Ω' => 'W',
+		'Ά' => 'A', 'Έ' => 'E', 'Ί' => 'I', 'Ό' => 'O', 'Ύ' => 'Y', 'Ή' => 'H', 'Ώ' => 'W', 'Ϊ' => 'I',
+		'Ϋ' => 'Y',
+		'α' => 'a', 'β' => 'b', 'γ' => 'g', 'δ' => 'd', 'ε' => 'e', 'ζ' => 'z', 'η' => 'h', 'θ' => '8',
+		'ι' => 'i', 'κ' => 'k', 'λ' => 'l', 'μ' => 'm', 'ν' => 'n', 'ξ' => '3', 'ο' => 'o', 'π' => 'p',
+		'ρ' => 'r', 'σ' => 's', 'τ' => 't', 'υ' => 'y', 'φ' => 'f', 'χ' => 'x', 'ψ' => 'ps', 'ω' => 'w',
+		'ά' => 'a', 'έ' => 'e', 'ί' => 'i', 'ό' => 'o', 'ύ' => 'y', 'ή' => 'h', 'ώ' => 'w', 'ς' => 's',
+		'ϊ' => 'i', 'ΰ' => 'y', 'ϋ' => 'y', 'ΐ' => 'i',
+		// Turkish
+		'Ş' => 'S', 'İ' => 'I', 'Ç' => 'C', 'Ü' => 'U', 'Ö' => 'O', 'Ğ' => 'G',
+		'ş' => 's', 'ı' => 'i', 'ç' => 'c', 'ü' => 'u', 'ö' => 'o', 'ğ' => 'g',
+		// Russian
+		'А' => 'A', 'Б' => 'B', 'В' => 'V', 'Г' => 'G', 'Д' => 'D', 'Е' => 'E', 'Ё' => 'Yo', 'Ж' => 'Zh',
+		'З' => 'Z', 'И' => 'I', 'Й' => 'J', 'К' => 'K', 'Л' => 'L', 'М' => 'M', 'Н' => 'N', 'О' => 'O',
+		'П' => 'P', 'Р' => 'R', 'С' => 'S', 'Т' => 'T', 'У' => 'U', 'Ф' => 'F', 'Х' => 'H', 'Ц' => 'C',
+		'Ч' => 'Ch', 'Ш' => 'Sh', 'Щ' => 'Sh', 'Ъ' => '', 'Ы' => 'Y', 'Ь' => '', 'Э' => 'E', 'Ю' => 'Yu',
+		'Я' => 'Ya',
+		'а' => 'a', 'б' => 'b', 'в' => 'v', 'г' => 'g', 'д' => 'd', 'е' => 'e', 'ё' => 'yo', 'ж' => 'zh',
+		'з' => 'z', 'и' => 'i', 'й' => 'j', 'к' => 'k', 'л' => 'l', 'м' => 'm', 'н' => 'n', 'о' => 'o',
+		'п' => 'p', 'р' => 'r', 'с' => 's', 'т' => 't', 'у' => 'u', 'ф' => 'f', 'х' => 'h', 'ц' => 'c',
+		'ч' => 'ch', 'ш' => 'sh', 'щ' => 'sh', 'ъ' => '', 'ы' => 'y', 'ь' => '', 'э' => 'e', 'ю' => 'yu',
+		'я' => 'ya',
+		// Ukrainian
+		'Є' => 'Ye', 'І' => 'I', 'Ї' => 'Yi', 'Ґ' => 'G',
+		'є' => 'ye', 'і' => 'i', 'ї' => 'yi', 'ґ' => 'g',
+		// Czech
+		'Č' => 'C', 'Ď' => 'D', 'Ě' => 'E', 'Ň' => 'N', 'Ř' => 'R', 'Š' => 'S', 'Ť' => 'T', 'Ů' => 'U',
+		'Ž' => 'Z',
+		'č' => 'c', 'ď' => 'd', 'ě' => 'e', 'ň' => 'n', 'ř' => 'r', 'š' => 's', 'ť' => 't', 'ů' => 'u',
+		'ž' => 'z',
+		// Polish
+		'Ą' => 'A', 'Ć' => 'C', 'Ę' => 'e', 'Ł' => 'L', 'Ń' => 'N', 'Ó' => 'o', 'Ś' => 'S', 'Ź' => 'Z',
+		'Ż' => 'Z',
+		'ą' => 'a', 'ć' => 'c', 'ę' => 'e', 'ł' => 'l', 'ń' => 'n', 'ó' => 'o', 'ś' => 's', 'ź' => 'z',
+		'ż' => 'z',
+		// Latvian
+		'Ā' => 'A', 'Č' => 'C', 'Ē' => 'E', 'Ģ' => 'G', 'Ī' => 'i', 'Ķ' => 'k', 'Ļ' => 'L', 'Ņ' => 'N',
+		'Š' => 'S', 'Ū' => 'u', 'Ž' => 'Z',
+		'ā' => 'a', 'č' => 'c', 'ē' => 'e', 'ģ' => 'g', 'ī' => 'i', 'ķ' => 'k', 'ļ' => 'l', 'ņ' => 'n',
+		'š' => 's', 'ū' => 'u', 'ž' => 'z'
 	];
 
 	/**
@@ -434,20 +441,24 @@ class BaseInflector
 	/**
 	 * Returns a string with all spaces converted to given replacement and
 	 * non word characters removed.  Maps special characters to ASCII using
-	 * `Inflector::$transliteration`
+	 * [[$transliteration]] array.
 	 * @param string $string An arbitrary string to convert
 	 * @param string $replacement The replacement to use for spaces
+	 * @param bool $lowercase whether to return the string in lowercase or not. Defaults to `true`.
 	 * @return string The converted string.
 	 */
-	public static function slug($string, $replacement = '-')
+	public static function slug($string, $replacement = '-', $lowercase = true)
 	{
-		$map = static::$transliteration + [
-				'/[^\w\s]/' => ' ',
-				'/\\s+/' => $replacement,
-				'/(?<=[a-z])([A-Z])/' => $replacement . '\\1',
-				str_replace(':rep', preg_quote($replacement, '/'), '/^[:rep]+|[:rep]+$/') => ''
-			];
-		return preg_replace(array_keys($map), array_values($map), $string);
+		if (extension_loaded('intl') === true) {
+			$options = 'Any-Latin; NFD; [:Nonspacing Mark:] Remove; NFC; [:Punctuation:] Remove;';
+			$string = transliterator_transliterate($options, $string);
+			$string = preg_replace('/[-\s]+/', $replacement, $string);
+		} else {
+			$string = str_replace(array_keys(static::$transliteration), static::$transliteration, $string);
+			$string = preg_replace('/[^\p{L}\p{Nd}]+/u', $replacement, $string);
+		}
+		$string = trim($string, $replacement);
+		return $lowercase ? strtolower($string) : $string;
 	}
 
 	/**
diff --git a/framework/yii/rbac/Manager.php b/framework/yii/rbac/Manager.php
index a1bf47a..2f392c5 100644
--- a/framework/yii/rbac/Manager.php
+++ b/framework/yii/rbac/Manager.php
@@ -21,7 +21,7 @@ use yii\base\InvalidParamException;
  * Access Control (RBAC).
  *
  * The main idea is that permissions are organized as a hierarchy of
- * [[Item]] authorization items. Items on higer level inherit the permissions
+ * [[Item]] authorization items. Items on higher level inherit the permissions
  * represented by items on lower level. And roles are simply top-level authorization items
  * that may be assigned to individual users. A user is said to have a permission
  * to do something if the corresponding authorization item is inherited by one of his roles.
diff --git a/framework/yii/web/Request.php b/framework/yii/web/Request.php
index ee232f4..717f403 100644
--- a/framework/yii/web/Request.php
+++ b/framework/yii/web/Request.php
@@ -577,7 +577,7 @@ class Request extends \yii\base\Request
 			$pathInfo = substr($pathInfo, strlen($scriptUrl));
 		} elseif ($baseUrl === '' || strpos($pathInfo, $baseUrl) === 0) {
 			$pathInfo = substr($pathInfo, strlen($baseUrl));
-		} elseif (strpos($_SERVER['PHP_SELF'], $scriptUrl) === 0) {
+		} elseif (isset($_SERVER['PHP_SELF']) && strpos($_SERVER['PHP_SELF'], $scriptUrl) === 0) {
 			$pathInfo = substr($_SERVER['PHP_SELF'], strlen($scriptUrl));
 		} else {
 			throw new InvalidConfigException('Unable to determine the path info of the current request.');
@@ -1121,7 +1121,7 @@ class Request extends \yii\base\Request
 
 	private function validateCsrfTokenInternal($token, $trueToken)
 	{
-		$token = str_replace('.', '+', base64_decode($token));
+		$token = base64_decode(str_replace('.', '+', $token));
 		$n = StringHelper::byteLength($token);
 		if ($n <= self::CSRF_MASK_LENGTH) {
 			return false;
diff --git a/framework/yii/web/Response.php b/framework/yii/web/Response.php
index 43e8f73..79d94dd 100644
--- a/framework/yii/web/Response.php
+++ b/framework/yii/web/Response.php
@@ -799,7 +799,7 @@ class Response extends \yii\base\Response
 					$this->content = $this->data;
 					break;
 				case self::FORMAT_JSON:
-					$this->getHeaders()->set('Content-Type', 'application/json');
+					$this->getHeaders()->set('Content-Type', 'application/json; charset=UTF-8');
 					$this->content = Json::encode($this->data);
 					break;
 				case self::FORMAT_JSONP:
diff --git a/framework/yii/widgets/ActiveField.php b/framework/yii/widgets/ActiveField.php
index bd26237..e188034 100644
--- a/framework/yii/widgets/ActiveField.php
+++ b/framework/yii/widgets/ActiveField.php
@@ -280,6 +280,7 @@ class ActiveField extends Component
 	public function input($type, $options = [])
 	{
 		$options = array_merge($this->inputOptions, $options);
+		$this->adjustLabelFor($options);
 		$this->parts['{input}'] = Html::activeInput($type, $this->model, $this->attribute, $options);
 		return $this;
 	}
@@ -295,6 +296,7 @@ class ActiveField extends Component
 	public function textInput($options = [])
 	{
 		$options = array_merge($this->inputOptions, $options);
+		$this->adjustLabelFor($options);
 		$this->parts['{input}'] = Html::activeTextInput($this->model, $this->attribute, $options);
 		return $this;
 	}
@@ -310,6 +312,7 @@ class ActiveField extends Component
 	public function passwordInput($options = [])
 	{
 		$options = array_merge($this->inputOptions, $options);
+		$this->adjustLabelFor($options);
 		$this->parts['{input}'] = Html::activePasswordInput($this->model, $this->attribute, $options);
 		return $this;
 	}
@@ -328,6 +331,7 @@ class ActiveField extends Component
 		if ($this->inputOptions !== ['class' => 'form-control']) {
 			$options = array_merge($this->inputOptions, $options);
 		}
+		$this->adjustLabelFor($options);
 		$this->parts['{input}'] = Html::activeFileInput($this->model, $this->attribute, $options);
 		return $this;
 	}
@@ -342,6 +346,7 @@ class ActiveField extends Component
 	public function textarea($options = [])
 	{
 		$options = array_merge($this->inputOptions, $options);
+		$this->adjustLabelFor($options);
 		$this->parts['{input}'] = Html::activeTextarea($this->model, $this->attribute, $options);
 		return $this;
 	}
@@ -379,6 +384,7 @@ class ActiveField extends Component
 		} else {
 			$this->parts['{input}'] = Html::activeRadio($this->model, $this->attribute, $options);
 		}
+		$this->adjustLabelFor($options);
 		return $this;
 	}
 
@@ -415,6 +421,7 @@ class ActiveField extends Component
 		} else {
 			$this->parts['{input}'] = Html::activeCheckbox($this->model, $this->attribute, $options);
 		}
+		$this->adjustLabelFor($options);
 		return $this;
 	}
 
@@ -453,6 +460,7 @@ class ActiveField extends Component
 	public function dropDownList($items, $options = [])
 	{
 		$options = array_merge($this->inputOptions, $options);
+		$this->adjustLabelFor($options);
 		$this->parts['{input}'] = Html::activeDropDownList($this->model, $this->attribute, $items, $options);
 		return $this;
 	}
@@ -495,6 +503,7 @@ class ActiveField extends Component
 	public function listBox($items, $options = [])
 	{
 		$options = array_merge($this->inputOptions, $options);
+		$this->adjustLabelFor($options);
 		$this->parts['{input}'] = Html::activeListBox($this->model, $this->attribute, $items, $options);
 		return $this;
 	}
@@ -526,6 +535,7 @@ class ActiveField extends Component
 	 */
 	public function checkboxList($items, $options = [])
 	{
+		$this->adjustLabelFor($options);
 		$this->parts['{input}'] = Html::activeCheckboxList($this->model, $this->attribute, $items, $options);
 		return $this;
 	}
@@ -556,6 +566,7 @@ class ActiveField extends Component
 	 */
 	public function radioList($items, $options = [])
 	{
+		$this->adjustLabelFor($options);
 		$this->parts['{input}'] = Html::activeRadioList($this->model, $this->attribute, $items, $options);
 		return $this;
 	}
@@ -584,6 +595,17 @@ class ActiveField extends Component
 	}
 
 	/**
+	 * Adjusts the "for" attribute for the label based on the input options.
+	 * @param array $options the input options
+	 */
+	protected function adjustLabelFor($options)
+	{
+		if (isset($options['id']) && !isset($this->labelOptions['for'])) {
+			$this->labelOptions['for'] = $options['id'];
+		}
+	}
+
+	/**
 	 * Returns the JS options for the field.
 	 * @return array the JS options
 	 */
diff --git a/tests/unit/framework/db/SchemaTest.php b/tests/unit/framework/db/SchemaTest.php
index 3727737..05a0ff2 100644
--- a/tests/unit/framework/db/SchemaTest.php
+++ b/tests/unit/framework/db/SchemaTest.php
@@ -11,7 +11,7 @@ use yii\db\Schema;
  */
 class SchemaTest extends DatabaseTestCase
 {
-	public function testFindTableNames()
+	public function testGetTableNames()
 	{
 		/** @var Schema $schema */
 		$schema = $this->getConnection()->schema;
diff --git a/tests/unit/framework/helpers/InflectorTest.php b/tests/unit/framework/helpers/InflectorTest.php
index 2cd3c9f..e303ebd 100644
--- a/tests/unit/framework/helpers/InflectorTest.php
+++ b/tests/unit/framework/helpers/InflectorTest.php
@@ -123,6 +123,8 @@ class InflectorTest extends TestCase
 
 	public function testSlug()
 	{
+		$this->assertEquals("privet-hello-jii-framework-kak-dela-how-it-goes", Inflector::slug('Привет Hello Йии-- Framework !--- Как дела ? How it goes ?'));
+
 		$this->assertEquals("this-is-a-title", Inflector::slug('this is a title'));
 	}