diff --git a/apps/advanced/backend/config/main.php b/apps/advanced/backend/config/main.php
index 41261f6..a0b5fc9 100644
--- a/apps/advanced/backend/config/main.php
+++ b/apps/advanced/backend/config/main.php
@@ -13,10 +13,6 @@ return [
     'bootstrap' => ['log'],
     'modules' => [],
     'components' => [
-        'request' => [
-            // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
-            'cookieValidationKey' => '',
-        ],
         'user' => [
             'identityClass' => 'common\models\User',
             'enableAutoLogin' => true,
diff --git a/apps/advanced/composer.json b/apps/advanced/composer.json
index eb6d7fe..639396b 100644
--- a/apps/advanced/composer.json
+++ b/apps/advanced/composer.json
@@ -32,8 +32,7 @@
     },
     "scripts": {
         "post-create-project-cmd": [
-            "yii\\composer\\Installer::setPermission",
-            "yii\\composer\\Installer::generateCookieValidationKey"
+            "yii\\composer\\Installer::setPermission"
         ]
     },
     "config": {
@@ -46,10 +45,6 @@
 
             "frontend/runtime",
             "frontend/web/assets"
-        ],
-        "config": [
-            "frontend/config/main.php",
-            "backend/config/main.php"
         ]
     }
 }
diff --git a/apps/advanced/environments/dev/backend/config/main-local.php b/apps/advanced/environments/dev/backend/config/main-local.php
index 0e6a38e..d9e3809 100644
--- a/apps/advanced/environments/dev/backend/config/main-local.php
+++ b/apps/advanced/environments/dev/backend/config/main-local.php
@@ -1,6 +1,13 @@
 <?php
 
-$config = [];
+$config = [
+    'components' => [
+        'request' => [
+            // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
+            'cookieValidationKey' => '',
+        ],
+    ],
+];
 
 if (!YII_ENV_TEST) {
     // configuration adjustments for 'dev' environment
diff --git a/apps/advanced/environments/dev/frontend/config/main-local.php b/apps/advanced/environments/dev/frontend/config/main-local.php
index 0e6a38e..d9e3809 100644
--- a/apps/advanced/environments/dev/frontend/config/main-local.php
+++ b/apps/advanced/environments/dev/frontend/config/main-local.php
@@ -1,6 +1,13 @@
 <?php
 
-$config = [];
+$config = [
+    'components' => [
+        'request' => [
+            // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
+            'cookieValidationKey' => '',
+        ],
+    ],
+];
 
 if (!YII_ENV_TEST) {
     // configuration adjustments for 'dev' environment
diff --git a/apps/advanced/environments/index.php b/apps/advanced/environments/index.php
index a6fd1b7..e7ab700 100644
--- a/apps/advanced/environments/index.php
+++ b/apps/advanced/environments/index.php
@@ -9,9 +9,15 @@
  * return [
  *     'environment name' => [
  *         'path' => 'directory storing the local files',
- *         'writable' => [
+ *         'setWritable' => [
  *             // list of directories that should be set writable
  *         ],
+ *         'setExecutable' => [
+ *             // list of directories that should be set executable
+ *         ],
+ *         'setCookieValidationKey' => [
+ *             // list of config files that need to be inserted with automatically generated cookie validation keys
+ *         ],
  *     ],
  * ];
  * ```
@@ -19,26 +25,34 @@
 return [
     'Development' => [
         'path' => 'dev',
-        'writable' => [
+        'setWritable' => [
             'backend/runtime',
             'backend/web/assets',
             'frontend/runtime',
             'frontend/web/assets',
         ],
-        'executable' => [
+        'setExecutable' => [
             'yii',
         ],
+        'setCookieValidationKey' => [
+            'backend/config/main-local.php',
+            'frontend/config/main-local.php',
+        ],
     ],
     'Production' => [
         'path' => 'prod',
-        'writable' => [
+        'setWritable' => [
             'backend/runtime',
             'backend/web/assets',
             'frontend/runtime',
             'frontend/web/assets',
         ],
-        'executable' => [
+        'setExecutable' => [
             'yii',
         ],
+        'setCookieValidationKey' => [
+            'backend/config/main-local.php',
+            'frontend/config/main-local.php',
+        ],
     ],
 ];
diff --git a/apps/advanced/environments/prod/backend/config/main-local.php b/apps/advanced/environments/prod/backend/config/main-local.php
index d0b9c34..af46ba3 100644
--- a/apps/advanced/environments/prod/backend/config/main-local.php
+++ b/apps/advanced/environments/prod/backend/config/main-local.php
@@ -1,3 +1,9 @@
 <?php
 return [
+    'components' => [
+        'request' => [
+            // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
+            'cookieValidationKey' => '',
+        ],
+    ],
 ];
diff --git a/apps/advanced/environments/prod/frontend/config/main-local.php b/apps/advanced/environments/prod/frontend/config/main-local.php
index d0b9c34..af46ba3 100644
--- a/apps/advanced/environments/prod/frontend/config/main-local.php
+++ b/apps/advanced/environments/prod/frontend/config/main-local.php
@@ -1,3 +1,9 @@
 <?php
 return [
+    'components' => [
+        'request' => [
+            // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
+            'cookieValidationKey' => '',
+        ],
+    ],
 ];
diff --git a/apps/advanced/frontend/config/main.php b/apps/advanced/frontend/config/main.php
index 1e442b3..1ed8305 100644
--- a/apps/advanced/frontend/config/main.php
+++ b/apps/advanced/frontend/config/main.php
@@ -12,10 +12,6 @@ return [
     'bootstrap' => ['log'],
     'controllerNamespace' => 'frontend\controllers',
     'components' => [
-        'request' => [
-            // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
-            'cookieValidationKey' => '',
-        ],
         'user' => [
             'identityClass' => 'common\models\User',
             'enableAutoLogin' => true,
diff --git a/apps/advanced/init b/apps/advanced/init
index 4858321..db9dd7e 100755
--- a/apps/advanced/init
+++ b/apps/advanced/init
@@ -14,6 +14,10 @@
  * @license http://www.yiiframework.com/license/
  */
 
+if (!extension_loaded('mcrypt')) {
+    die('The mcrypt PHP extension is required by Yii2.');
+}
+
 $params = getParams();
 $root = str_replace('\\', '/', __DIR__);
 $envs = require("$root/environments/index.php");
@@ -23,147 +27,169 @@ echo "Yii Application Initialization Tool v1.0\n\n";
 
 $envName = null;
 if (empty($params['env']) || $params['env'] === '1') {
-	echo "Which environment do you want the application to be initialized in?\n\n";
-	foreach ($envNames as $i => $name) {
-		echo "  [$i] $name\n";
-	}
-	echo "\n  Your choice [0-" . (count($envs) - 1) . ', or "q" to quit] ';
-	$answer = trim(fgets(STDIN));
-
-	if (!ctype_digit($answer) || !in_array($answer, range(0, count($envs) - 1))) {
-		echo "\n  Quit initialization.\n";
-		exit(0);
-	}
-
-	if (isset($envNames[$answer])) {
-		$envName = $envNames[$answer];
-	}
+    echo "Which environment do you want the application to be initialized in?\n\n";
+    foreach ($envNames as $i => $name) {
+        echo "  [$i] $name\n";
+    }
+    echo "\n  Your choice [0-" . (count($envs) - 1) . ', or "q" to quit] ';
+    $answer = trim(fgets(STDIN));
+
+    if (!ctype_digit($answer) || !in_array($answer, range(0, count($envs) - 1))) {
+        echo "\n  Quit initialization.\n";
+        exit(0);
+    }
+
+    if (isset($envNames[$answer])) {
+        $envName = $envNames[$answer];
+    }
 } else {
-	$envName = $params['env'];
+    $envName = $params['env'];
 }
 
 if (!in_array($envName, $envNames)) {
-	$envsList = implode(', ', $envNames);
-	echo "\n  $envName is not a valid environment. Try one of the following: $envsList. \n";
-	exit(2);
+    $envsList = implode(', ', $envNames);
+    echo "\n  $envName is not a valid environment. Try one of the following: $envsList. \n";
+    exit(2);
 }
 
 $env = $envs[$envName];
 
 if (empty($params['env'])) {
-	echo "\n  Initialize the application under '{$envNames[$answer]}' environment? [yes|no] ";
-	$answer = trim(fgets(STDIN));
-	if (strncasecmp($answer, 'y', 1)) {
-		echo "\n  Quit initialization.\n";
-		exit(0);
-	}
+    echo "\n  Initialize the application under '{$envNames[$answer]}' environment? [yes|no] ";
+    $answer = trim(fgets(STDIN));
+    if (strncasecmp($answer, 'y', 1)) {
+        echo "\n  Quit initialization.\n";
+        exit(0);
+    }
 }
 
 echo "\n  Start initialization ...\n\n";
 $files = getFileList("$root/environments/{$env['path']}");
 $all = false;
 foreach ($files as $file) {
-	if (!copyFile($root, "environments/{$env['path']}/$file", $file, $all, $params)) {
-		break;
-	}
-}
-
-if (isset($env['writable'])) {
-	foreach ($env['writable'] as $writable) {
-		echo "      chmod 0777 $writable\n";
-		@chmod("$root/$writable", 0777);
-	}
+    if (!copyFile($root, "environments/{$env['path']}/$file", $file, $all, $params)) {
+        break;
+    }
 }
 
-if (isset($env['executable'])) {
-	foreach ($env['executable'] as $executable) {
-		echo "      chmod 0755 $executable\n";
-		@chmod("$root/$executable", 0755);
-	}
+$callbacks = ['setCookieValidationKey', 'setWritable', 'setExecutable'];
+foreach ($callbacks as $callback) {
+    if (!empty($env[$callback])) {
+        $callback($root, $env[$callback]);
+    }
 }
 
 echo "\n  ... initialization completed.\n\n";
 
 function getFileList($root, $basePath = '')
 {
-	$files = [];
-	$handle = opendir($root);
-	while (($path = readdir($handle)) !== false) {
-		if ($path === '.svn' || $path === '.' || $path === '..') {
-			continue;
-		}
-		$fullPath = "$root/$path";
-		$relativePath = $basePath === '' ? $path : "$basePath/$path";
-		if (is_dir($fullPath)) {
-			$files = array_merge($files, getFileList($fullPath, $relativePath));
-		} else {
-			$files[] = $relativePath;
-		}
-	}
-	closedir($handle);
-	return $files;
+    $files = [];
+    $handle = opendir($root);
+    while (($path = readdir($handle)) !== false) {
+        if ($path === '.svn' || $path === '.' || $path === '..') {
+            continue;
+        }
+        $fullPath = "$root/$path";
+        $relativePath = $basePath === '' ? $path : "$basePath/$path";
+        if (is_dir($fullPath)) {
+            $files = array_merge($files, getFileList($fullPath, $relativePath));
+        } else {
+            $files[] = $relativePath;
+        }
+    }
+    closedir($handle);
+    return $files;
 }
 
 function copyFile($root, $source, $target, &$all, $params)
 {
-	if (!is_file($root . '/' . $source)) {
-		echo "       skip $target ($source not exist)\n";
-		return true;
-	}
-	if (is_file($root . '/' . $target)) {
-		if (file_get_contents($root . '/' . $source) === file_get_contents($root . '/' . $target)) {
-			echo "  unchanged $target\n";
-			return true;
-		}
-		if ($all) {
-			echo "  overwrite $target\n";
-		} else {
-			echo "      exist $target\n";
-			echo "            ...overwrite? [Yes|No|All|Quit] ";
-
-
-			$answer = !empty($params['overwrite']) ? $params['overwrite'] : trim(fgets(STDIN));
-			if (!strncasecmp($answer, 'q', 1)) {
-				return false;
-			} else {
-				if (!strncasecmp($answer, 'y', 1)) {
-					echo "  overwrite $target\n";
-				} else {
-					if (!strncasecmp($answer, 'a', 1)) {
-						echo "  overwrite $target\n";
-						$all = true;
-					} else {
-						echo "       skip $target\n";
-						return true;
-					}
-				}
-			}
-		}
-		file_put_contents($root . '/' . $target, file_get_contents($root . '/' . $source));
-		return true;
-	}
-	echo "   generate $target\n";
-	@mkdir(dirname($root . '/' . $target), 0777, true);
-	file_put_contents($root . '/' . $target, file_get_contents($root . '/' . $source));
-	return true;
+    if (!is_file($root . '/' . $source)) {
+        echo "       skip $target ($source not exist)\n";
+        return true;
+    }
+    if (is_file($root . '/' . $target)) {
+        if (file_get_contents($root . '/' . $source) === file_get_contents($root . '/' . $target)) {
+            echo "  unchanged $target\n";
+            return true;
+        }
+        if ($all) {
+            echo "  overwrite $target\n";
+        } else {
+            echo "      exist $target\n";
+            echo "            ...overwrite? [Yes|No|All|Quit] ";
+
+
+            $answer = !empty($params['overwrite']) ? $params['overwrite'] : trim(fgets(STDIN));
+            if (!strncasecmp($answer, 'q', 1)) {
+                return false;
+            } else {
+                if (!strncasecmp($answer, 'y', 1)) {
+                    echo "  overwrite $target\n";
+                } else {
+                    if (!strncasecmp($answer, 'a', 1)) {
+                        echo "  overwrite $target\n";
+                        $all = true;
+                    } else {
+                        echo "       skip $target\n";
+                        return true;
+                    }
+                }
+            }
+        }
+        file_put_contents($root . '/' . $target, file_get_contents($root . '/' . $source));
+        return true;
+    }
+    echo "   generate $target\n";
+    @mkdir(dirname($root . '/' . $target), 0777, true);
+    file_put_contents($root . '/' . $target, file_get_contents($root . '/' . $source));
+    return true;
 }
 
 function getParams()
 {
-	$rawParams = [];
-	if (isset($_SERVER['argv'])) {
-		$rawParams = $_SERVER['argv'];
-		array_shift($rawParams);
-	}
-
-	$params = [];
-	foreach ($rawParams as $param) {
-		if (preg_match('/^--(\w+)(=(.*))?$/', $param, $matches)) {
-			$name = $matches[1];
-			$params[$name] = isset($matches[3]) ? $matches[3] : true;
-		} else {
-			$params[] = $param;
-		}
-	}
-	return $params;
+    $rawParams = [];
+    if (isset($_SERVER['argv'])) {
+        $rawParams = $_SERVER['argv'];
+        array_shift($rawParams);
+    }
+
+    $params = [];
+    foreach ($rawParams as $param) {
+        if (preg_match('/^--(\w+)(=(.*))?$/', $param, $matches)) {
+            $name = $matches[1];
+            $params[$name] = isset($matches[3]) ? $matches[3] : true;
+        } else {
+            $params[] = $param;
+        }
+    }
+    return $params;
+}
+
+function setWritable($root, $paths)
+{
+    foreach ($paths as $writable) {
+        echo "      chmod 0777 $writable\n";
+        @chmod("$root/$writable", 0777);
+    }
+}
+
+function setExecutable($root, $paths)
+{
+    foreach ($paths as $executable) {
+        echo "      chmod 0755 $executable\n";
+        @chmod("$root/$executable", 0755);
+    }
+}
+
+function setCookieValidationKey($root, $paths)
+{
+    foreach ($paths as $file) {
+        echo "   generate cookie validation key in $file\n";
+        $file = $root . '/' . $file;
+        $length = 32;
+        $bytes = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM);
+        $key = strtr(substr(base64_encode($bytes), 0, $length), '+/=', '_-.');
+        $content = preg_replace('/(("|\')cookieValidationKey("|\')\s*=>\s*)(""|\'\')/', "\\1'$key'", file_get_contents($file));
+        file_put_contents($file, $content);
+    }
 }
diff --git a/docs/guide/db-migrations.md b/docs/guide/db-migrations.md
index c591fa4..adc9751 100644
--- a/docs/guide/db-migrations.md
+++ b/docs/guide/db-migrations.md
@@ -97,7 +97,7 @@ class m101129_185401_create_news_table extends \yii\db\Migration
 }
 ```
 
-The base class [\yii\db\Migration] exposes a database connection via `db`
+The base class [[\yii\db\Migration]] exposes a database connection via `db`
 property. You can use it for manipulating data and schema of a database.
 
 The column types used in this example are abstract types that will be replaced
diff --git a/docs/guide/db-query-builder.md b/docs/guide/db-query-builder.md
index 23329e5..64b0131 100644
--- a/docs/guide/db-query-builder.md
+++ b/docs/guide/db-query-builder.md
@@ -249,6 +249,19 @@ Operator can be one of the following:
 
 - `not exists`: similar to the `exists` operator and builds a `NOT EXISTS (sub-query)` expression.
 
+Additionally you can specify anything as operator:
+
+```php
+$userQuery = (new Query)->select('id')->from('user');
+$query->where(['>=', 'id', 10]);
+```
+
+It will result in:
+
+```sql
+SELECT id FROM user WHERE id >= 10;
+```
+
 If you are building parts of condition dynamically it's very convenient to use `andWhere()` and `orWhere()`:
 
 ```php
@@ -305,8 +318,6 @@ $query->orderBy([
 
 Here we are ordering by `id` ascending and then by `name` descending.
 
-```
-
 ### `GROUP BY` and `HAVING`
 
 In order to add `GROUP BY` to generated SQL you can use the following:
diff --git a/docs/guide/tutorial-template-engines.md b/docs/guide/tutorial-template-engines.md
index 4fc92db..3a7ad8b 100644
--- a/docs/guide/tutorial-template-engines.md
+++ b/docs/guide/tutorial-template-engines.md
@@ -56,7 +56,7 @@ return $this->render('renderer.twig', ['username' => 'Alex']);
 ### Template syntax
 
 The best resource to learn Twig basics is its official documentation you can find at
-[twig.sensiolabs.org](http://twig.sensiolabs.org/documentation). Additionally there are Yii-specific addtions
+[twig.sensiolabs.org](http://twig.sensiolabs.org/documentation). Additionally there are Yii-specific syntax extensions
 described below.
 
 #### Method and function calls
@@ -271,7 +271,13 @@ or `$this->renderPartial()` controller calls:
 return $this->render('renderer.tpl', ['username' => 'Alex']);
 ```
 
-### Additional functions
+### Template syntax
+
+The best resource to learn Smarty template syntax is its official documentation you can find at
+[www.smarty.net](http://www.smarty.net/docs/en/). Additionally there are Yii-specific syntax extensions
+described below.
+
+#### Additional functions
 
 Yii adds the following construct to the standard Smarty syntax:
 
@@ -281,7 +287,7 @@ Yii adds the following construct to the standard Smarty syntax:
 
 Internally, the `path()` function calls Yii's `Url::to()` method.
 
-### Additional variables
+#### Additional variables
 
 Within Smarty templates, you can also make use of these variables:
 
diff --git a/extensions/gii/generators/model/Generator.php b/extensions/gii/generators/model/Generator.php
index f48c18f..8a29795 100644
--- a/extensions/gii/generators/model/Generator.php
+++ b/extensions/gii/generators/model/Generator.php
@@ -197,7 +197,7 @@ class Generator extends \yii\gii\Generator
                 $labels[$column->name] = 'ID';
             } else {
                 $label = Inflector::camel2words($column->name);
-                if (!empty($label) && substr_compare($label, ' id', -3, 3, true)) {
+                if (!empty($label) && substr_compare($label, ' id', -3, 3, true) === 0) {
                     $label = substr($label, 0, -3) . ' ID';
                 }
                 $labels[$column->name] = $label;
@@ -508,16 +508,16 @@ class Generator extends \yii\gii\Generator
         }
     }
 
-    private $_tableNames;
-    private $_classNames;
+    protected $tableNames;
+    protected $classNames;
 
     /**
      * @return array the table names that match the pattern specified by [[tableName]].
      */
     protected function getTableNames()
     {
-        if ($this->_tableNames !== null) {
-            return $this->_tableNames;
+        if ($this->tableNames !== null) {
+            return $this->tableNames;
         }
         $db = $this->getDbConnection();
         if ($db === null) {
@@ -540,10 +540,10 @@ class Generator extends \yii\gii\Generator
             }
         } elseif (($table = $db->getTableSchema($this->tableName, true)) !== null) {
             $tableNames[] = $this->tableName;
-            $this->_classNames[$this->tableName] = $this->modelClass;
+            $this->classNames[$this->tableName] = $this->modelClass;
         }
 
-        return $this->_tableNames = $tableNames;
+        return $this->tableNames = $tableNames;
     }
 
     /**
@@ -574,8 +574,8 @@ class Generator extends \yii\gii\Generator
      */
     protected function generateClassName($tableName)
     {
-        if (isset($this->_classNames[$tableName])) {
-            return $this->_classNames[$tableName];
+        if (isset($this->classNames[$tableName])) {
+            return $this->classNames[$tableName];
         }
 
         if (($pos = strrpos($tableName, '.')) !== false) {
@@ -601,7 +601,7 @@ class Generator extends \yii\gii\Generator
             }
         }
 
-        return $this->_classNames[$tableName] = Inflector::id2camel($className, '_');
+        return $this->classNames[$tableName] = Inflector::id2camel($className, '_');
     }
 
     /**
diff --git a/extensions/sphinx/QueryBuilder.php b/extensions/sphinx/QueryBuilder.php
index f1b434a..cf63ab3 100644
--- a/extensions/sphinx/QueryBuilder.php
+++ b/extensions/sphinx/QueryBuilder.php
@@ -617,12 +617,11 @@ class QueryBuilder extends Object
             $operator = strtoupper($condition[0]);
             if (isset($builders[$operator])) {
                 $method = $builders[$operator];
-                array_shift($condition);
-
-                return $this->$method($indexes, $operator, $condition, $params);
             } else {
-                throw new Exception('Found unknown operator in query: ' . $operator);
+                $method = 'buildSimpleCondition';
             }
+            array_shift($condition);
+            return $this->$method($indexes, $operator, $condition, $params);
         } else { // hash format: 'column1' => 'value1', 'column2' => 'value2', ...
 
             return $this->buildHashCondition($indexes, $condition, $params);
@@ -986,4 +985,29 @@ class QueryBuilder extends Object
             return $phName;
         }
     }
+
+    /**
+     * Creates an SQL expressions like `"column" operator value`.
+     * @param string $operator the operator to use. Anything could be used e.g. `>`, `<=`, etc.
+     * @param array $operands contains two column names.
+     * @param array $params the binding parameters to be populated
+     * @return string the generated SQL expression
+     */
+    public function buildSimpleCondition($operator, $operands, &$params)
+    {
+        if (count($operands) !== 2) {
+            throw new InvalidParamException("Operator '$operator' requires two operands.");
+        }
+
+        list($column, $value) = $operands;
+
+        if (strpos($column, '(') === false) {
+            $column = $this->db->quoteColumnName($column);
+        }
+
+        $phName = self::PARAM_PREFIX . count($params);
+        $params[$phName] = $value === null ? 'NULL' : $value;
+
+        return "$column $operator $phName";
+    }
 }
diff --git a/extensions/twig/ViewRenderer.php b/extensions/twig/ViewRenderer.php
index 8f634a6..c9ccbe2 100644
--- a/extensions/twig/ViewRenderer.php
+++ b/extensions/twig/ViewRenderer.php
@@ -144,16 +144,30 @@ class ViewRenderer extends BaseViewRenderer
     {
         $this->twig->addGlobal('this', $view);
         $loader = new \Twig_Loader_Filesystem(dirname($file));
-
-        foreach (Yii::$aliases as $alias => $path) {
-            $loader->addPath($path, substr($alias, 1));
-        }
+        $this->addAliases($loader, Yii::$aliases);
         $this->twig->setLoader($loader);
 
         return $this->twig->render(pathinfo($file, PATHINFO_BASENAME), $params);
     }
 
     /**
+     * Adds aliases
+     *
+     * @param \Twig_Loader_Filesystem $loader
+     * @param array $aliases
+     */
+    protected function addAliases($loader, $aliases)
+    {
+        foreach ($aliases as $alias => $path) {
+            if (is_array($path)) {
+                $this->addAliases($loader, $path);
+            } elseif (is_string($path) && is_dir($path)) {
+                $loader->addPath($path, substr($alias, 1));
+            }
+        }
+    }
+
+    /**
      * Adds global objects or static classes
      * @param array $globals @see self::$globals
      */
diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md
index e0e8a35..65bdc05 100644
--- a/framework/CHANGELOG.md
+++ b/framework/CHANGELOG.md
@@ -90,6 +90,7 @@ Yii Framework 2 Change Log
 - Enh #1388: Added mapping from physical types to abstract types for OCI DB driver (qiangxue)
 - Enh #1452: Added `Module::getInstance()` to allow accessing the module instance from anywhere within the module (qiangxue)
 - Enh #2264: `CookieCollection::has()` will return false for expired or removed cookies (qiangxue)
+- Enh #2315: Any operator now could be used with `yii\db\Query::->where()` operand format (samdark)
 - Enh #2435: `yii\db\IntegrityException` is now thrown on database integrity errors instead of general `yii\db\Exception` (samdark)
 - Enh #2558: Enhanced support for memcached by adding `yii\caching\MemCache::persistentId` and `yii\caching\MemCache::options` (qiangxue)
 - Enh #2837: Error page now shows arguments in stack trace method calls (samdark)
@@ -167,6 +168,7 @@ Yii Framework 2 Change Log
 - Enh #4436: Added callback functions to AJAX-based form validation (thiagotalma)
 - Enh #4485: Added support for deferred validation in `ActiveForm` (Alex-Code)
 - Enh #4520: Added sasl support to `yii\caching\MemCache` (xjflyttp)
+- Enh #4566: Added client validation support for image validator (Skysplit, qiangxue)
 - Enh: Added support for using sub-queries when building a DB query with `IN` condition (qiangxue)
 - Enh: Supported adding a new response formatter without the need to reconfigure existing formatters (qiangxue)
 - Enh: Added `yii\web\UrlManager::addRules()` to simplify adding new URL rules (qiangxue)
diff --git a/framework/assets/yii.validation.js b/framework/assets/yii.validation.js
index 69c0742..0da298e 100644
--- a/framework/assets/yii.validation.js
+++ b/framework/assets/yii.validation.js
@@ -68,55 +68,67 @@ yii.validation = (function ($) {
                 pub.addMessage(messages, options.notEqual, value);
             }
         },
-
-        file: function (value, messages, options, attribute) {
-            var files = $(attribute.input).get(0).files,
-                index, ext;
-
-            if (options.message && !files) {
-                pub.addMessage(messages, options.message, value);
-            }
-
-            if (!options.skipOnEmpty && files.length == 0) {
-                pub.addMessage(messages, options.uploadRequired, value);
-            } else if (files.length == 0) {
-                return;
-            }
-
-            if (options.maxFiles && options.maxFiles < files.length) {
-                pub.addMessage(messages, options.tooMany);
-            }
-
+        
+        file: function (attribute, messages, options) {
+            var files = getUploadedFiles(attribute, messages, options);
             $.each(files, function (i, file) {
-                if (options.extensions && options.extensions.length > 0) {
-                    index = file.name.lastIndexOf('.');
-
-                    if (!~index) {
-                        ext = '';
-                    } else {
-                        ext = file.name.substr(index + 1, file.name.length).toLowerCase();
-                    }
+                validateFile(file, messages, options);
+            });
+        },
+        
+        image: function (attribute, messages, options, deferred) {
+            var files = getUploadedFiles(attribute, messages, options);
+            
+            $.each(files, function (i, file) {
+                validateFile(file, messages, options);
 
-                    if (!~options.extensions.indexOf(ext)) {
-                        messages.push(options.wrongExtension.replace(/\{file\}/g, file.name));
-                    }
+                // Skip image validation if FileReader API is not available
+                if (typeof FileReader === "undefined") {
+                    return;
                 }
 
-                if (options.mimeTypes && options.mimeTypes.length > 0) {
-                    if (!~options.mimeTypes.indexOf(file.type)) {
-                        messages.push(options.wrongMimeType.replace(/\{file\}/g, file.name));
+                var def = $.Deferred(),
+                    fr = new FileReader(),
+                    img = new Image();
+                    
+                img.onload = function () {
+                    if (options.minWidth && this.width < options.minWidth) {
+                        messages.push(options.underWidth.replace(/\{file\}/g, file.name));
                     }
-                }
-
-                if (options.maxSize && options.maxSize < file.size) {
-                    messages.push(options.tooBig.replace(/\{file\}/g, file.name));
-                }
-
-                if (options.maxSize && options.minSize > file.size) {
-                    messages.push(options.tooSmall.replace(/\{file\}/g, file.name));
-                }
-
+                    
+                    if (options.maxWidth && this.width > options.maxWidth) {
+                        messages.push(options.overWidth.replace(/\{file\}/g, file.name));
+                    }
+                    
+                    if (options.minHeight && this.height < options.minHeight) {
+                        messages.push(options.underHeight.replace(/\{file\}/g, file.name));
+                    }
+                    
+                    if (options.maxHeight && this.height > options.maxHeight) {
+                        messages.push(options.overHeight.replace(/\{file\}/g, file.name));
+                    }
+                    def.resolve();
+                };
+                
+                img.onerror = function () {
+                    messages.push(options.notImage);
+                    def.resolve();
+                };
+                
+                fr.onload = function () {
+                    img.src = fr.result;
+                };
+                
+                // Resolve deferred if there was error while reading data
+                fr.onerror = function () {
+                    def.resolve();
+                };
+                
+                fr.readAsDataURL(file);
+                
+                deferred.push(def);
             });
+        
         },
 
         number: function (value, messages, options) {
@@ -288,5 +300,60 @@ yii.validation = (function ($) {
             }
         }
     };
+
+    function getUploadedFiles(attribute, messages, options) {
+        var files = $(attribute.input).get(0).files;
+        if (!files) {
+            messages.push(options.message);
+            return [];
+        }
+
+        if (files.length === 0) {
+            if (!options.skipOnEmpty) {
+                messages.push(options.uploadRequired);
+            }
+            return [];
+        }
+
+        if (options.maxFiles && options.maxFiles < files.length) {
+            messages.push(options.tooMany);
+            return [];
+        }
+
+        return files;
+    }
+
+    function validateFile(file, messages, options) {
+        if (options.extensions && options.extensions.length > 0) {
+            var index, ext;
+
+            index = file.name.lastIndexOf('.');
+
+            if (!~index) {
+                ext = '';
+            } else {
+                ext = file.name.substr(index + 1, file.name.length).toLowerCase();
+            }
+
+            if (!~options.extensions.indexOf(ext)) {
+                messages.push(options.wrongExtension.replace(/\{file\}/g, file.name));
+            }
+        }
+
+        if (options.mimeTypes && options.mimeTypes.length > 0) {
+            if (!~options.mimeTypes.indexOf(file.type)) {
+                messages.push(options.wrongMimeType.replace(/\{file\}/g, file.name));
+            }
+        }
+
+        if (options.maxSize && options.maxSize < file.size) {
+            messages.push(options.tooBig.replace(/\{file\}/g, file.name));
+        }
+
+        if (options.minSize && options.minSize > file.size) {
+            messages.push(options.tooSmall.replace(/\{file\}/g, file.name));
+        }
+    }
+
     return pub;
 })(jQuery);
diff --git a/framework/base/Model.php b/framework/base/Model.php
index aaf3d5a..de8a024 100644
--- a/framework/base/Model.php
+++ b/framework/base/Model.php
@@ -385,7 +385,6 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab
         if ($this->_validators === null) {
             $this->_validators = $this->createValidators();
         }
-
         return $this->_validators;
     }
 
@@ -404,7 +403,6 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab
                 $validators[] = $validator;
             }
         }
-
         return $validators;
     }
 
@@ -427,7 +425,6 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab
                 throw new InvalidConfigException('Invalid validation rule: a rule must specify both attribute names and validator type.');
             }
         }
-
         return $validators;
     }
 
@@ -436,6 +433,12 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab
      * This is determined by checking if the attribute is associated with a
      * [[\yii\validators\RequiredValidator|required]] validation rule in the
      * current [[scenario]].
+     *
+     * Note that when the validator has a conditional validation applied using
+     * [[\yii\validators\RequiredValidator::$when|$when]] this method will return
+     * `false` regardless of the `when` condition because it may be called be
+     * before the model is loaded with data.
+     *
      * @param string $attribute attribute name
      * @return boolean whether the attribute is required
      */
@@ -446,7 +449,6 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab
                 return true;
             }
         }
-
         return false;
     }
 
@@ -482,7 +484,6 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab
     public function getAttributeLabel($attribute)
     {
         $labels = $this->attributeLabels();
-
         return isset($labels[$attribute]) ? $labels[$attribute] : $this->generateAttributeLabel($attribute);
     }
 
diff --git a/framework/behaviors/AttributeBehavior.php b/framework/behaviors/AttributeBehavior.php
index 1ec315d..3f2d007 100644
--- a/framework/behaviors/AttributeBehavior.php
+++ b/framework/behaviors/AttributeBehavior.php
@@ -93,7 +93,10 @@ class AttributeBehavior extends Behavior
             $attributes = (array) $this->attributes[$event->name];
             $value = $this->getValue($event);
             foreach ($attributes as $attribute) {
-                $this->owner->$attribute = $value;
+                // ignore attribute names which are not string (e.g. when set by TimestampBehavior::updatedAtAttribute)
+                if (is_string($attribute)) {
+                    $this->owner->$attribute = $value;
+                }
             }
         }
     }
diff --git a/framework/behaviors/BlameableBehavior.php b/framework/behaviors/BlameableBehavior.php
index 1b23577..791a077 100644
--- a/framework/behaviors/BlameableBehavior.php
+++ b/framework/behaviors/BlameableBehavior.php
@@ -54,10 +54,12 @@ class BlameableBehavior extends AttributeBehavior
 {
     /**
      * @var string the attribute that will receive current user ID value
+     * Set this property to be null if you do not want to record the creator ID.
      */
     public $createdByAttribute = 'created_by';
     /**
      * @var string the attribute that will receive current user ID value
+     * Set this property to be null if you do not want to record the updater ID.
      */
     public $updatedByAttribute = 'updated_by';
     /**
diff --git a/framework/behaviors/TimestampBehavior.php b/framework/behaviors/TimestampBehavior.php
index 9dc5348..8651d90 100644
--- a/framework/behaviors/TimestampBehavior.php
+++ b/framework/behaviors/TimestampBehavior.php
@@ -64,10 +64,12 @@ class TimestampBehavior extends AttributeBehavior
 {
     /**
      * @var string the attribute that will receive timestamp value
+     * Set this property to be null if you do not want to record the creation time.
      */
     public $createdAtAttribute = 'created_at';
     /**
-     * @var string the attribute that will receive timestamp value
+     * @var string the attribute that will receive timestamp value.
+     * Set this property to be null if you do not want to record the update time.
      */
     public $updatedAtAttribute = 'updated_at';
     /**
diff --git a/framework/console/controllers/MessageController.php b/framework/console/controllers/MessageController.php
index b97c970..1e1b5f1 100644
--- a/framework/console/controllers/MessageController.php
+++ b/framework/console/controllers/MessageController.php
@@ -96,6 +96,9 @@ class MessageController extends Controller
         if (!is_dir($config['sourcePath'])) {
             throw new Exception("The source path {$config['sourcePath']} is not a valid directory.");
         }
+        if (empty($config['format']) || !in_array($config['format'], ['php', 'po', 'db'])) {
+            throw new Exception('Format should be either "php", "po" or "db".');
+        }
         if (in_array($config['format'], ['php', 'po'])) {
             if (!isset($config['messagePath'])) {
                 throw new Exception('The configuration file must specify "messagePath".');
@@ -106,9 +109,6 @@ class MessageController extends Controller
         if (empty($config['languages'])) {
             throw new Exception("Languages cannot be empty.");
         }
-        if (empty($config['format']) || !in_array($config['format'], ['php', 'po', 'db'])) {
-            throw new Exception('Format should be either "php", "po" or "db".');
-        }
 
         $files = FileHelper::findFiles(realpath($config['sourcePath']), $config);
 
diff --git a/framework/db/QueryBuilder.php b/framework/db/QueryBuilder.php
index 33068a5..b247981 100644
--- a/framework/db/QueryBuilder.php
+++ b/framework/db/QueryBuilder.php
@@ -868,7 +868,6 @@ class QueryBuilder extends \yii\base\Object
      * on how to specify a condition.
      * @param array $params the binding parameters to be populated
      * @return string the generated SQL expression
-     * @throws InvalidParamException if the condition is in bad format
      */
     public function buildCondition($condition, &$params)
     {
@@ -882,11 +881,11 @@ class QueryBuilder extends \yii\base\Object
             $operator = strtoupper($condition[0]);
             if (isset($this->conditionBuilders[$operator])) {
                 $method = $this->conditionBuilders[$operator];
-                array_shift($condition);
-                return $this->$method($operator, $condition, $params);
             } else {
-                throw new InvalidParamException('Found unknown operator in query: ' . $operator);
+                $method = 'buildSimpleCondition';
             }
+            array_shift($condition);
+            return $this->$method($operator, $condition, $params);
         } else { // hash format: 'column1' => 'value1', 'column2' => 'value2', ...
             return $this->buildHashCondition($condition, $params);
         }
@@ -1194,4 +1193,29 @@ class QueryBuilder extends \yii\base\Object
             throw new InvalidParamException('Subquery for EXISTS operator must be a Query object.');
         }
     }
+
+    /**
+     * Creates an SQL expressions like `"column" operator value`.
+     * @param string $operator the operator to use. Anything could be used e.g. `>`, `<=`, etc.
+     * @param array $operands contains two column names.
+     * @param array $params the binding parameters to be populated
+     * @return string the generated SQL expression
+     */
+    public function buildSimpleCondition($operator, $operands, &$params)
+    {
+        if (count($operands) !== 2) {
+            throw new InvalidParamException("Operator '$operator' requires two operands.");
+        }
+
+        list($column, $value) = $operands;
+
+        if (strpos($column, '(') === false) {
+            $column = $this->db->quoteColumnName($column);
+        }
+
+        $phName = self::PARAM_PREFIX . count($params);
+        $params[$phName] = $value === null ? 'NULL' : $value;
+
+        return "$column $operator $phName";
+    }
 }
diff --git a/framework/db/QueryTrait.php b/framework/db/QueryTrait.php
index 0244ff6..e700a2e 100644
--- a/framework/db/QueryTrait.php
+++ b/framework/db/QueryTrait.php
@@ -253,20 +253,6 @@ trait QueryTrait
                     return [];
                 }
                 break;
-            case 'IN':
-            case 'NOT IN':
-            case 'LIKE':
-            case 'OR LIKE':
-            case 'NOT LIKE':
-            case 'OR NOT LIKE':
-            case 'ILIKE': // PostgreSQL operator for case insensitive LIKE
-            case 'OR ILIKE':
-            case 'NOT ILIKE':
-            case 'OR NOT ILIKE':
-                if (array_key_exists(1, $condition) && $this->isEmpty($condition[1])) {
-                    return [];
-                }
-                break;
             case 'BETWEEN':
             case 'NOT BETWEEN':
                 if (array_key_exists(1, $condition) && array_key_exists(2, $condition)) {
@@ -276,7 +262,9 @@ trait QueryTrait
                 }
                 break;
             default:
-                throw new NotSupportedException("Operator not supported: $operator");
+                if (array_key_exists(1, $condition) && $this->isEmpty($condition[1])) {
+                    return [];
+                }
         }
 
         array_unshift($condition, $operator);
diff --git a/framework/validators/FileValidator.php b/framework/validators/FileValidator.php
index 9900a88..371e5d0 100644
--- a/framework/validators/FileValidator.php
+++ b/framework/validators/FileValidator.php
@@ -122,7 +122,7 @@ class FileValidator extends Validator
      * - {mimeTypes}: the value of [[mimeTypes]]
      */
     public $wrongMimeType;
-
+    
 
     /**
      * @inheritdoc
@@ -150,12 +150,16 @@ class FileValidator extends Validator
         }
         if (!is_array($this->extensions)) {
             $this->extensions = preg_split('/[\s,]+/', strtolower($this->extensions), -1, PREG_SPLIT_NO_EMPTY);
+        } else {
+            $this->extensions = array_map('strtolower', $this->extensions);
         }
         if ($this->wrongMimeType === null) {
             $this->wrongMimeType = Yii::t('yii', 'Only files with these MIME types are allowed: {mimeTypes}.');
         }
         if (!is_array($this->mimeTypes)) {
             $this->mimeTypes = preg_split('/[\s,]+/', strtolower($this->mimeTypes), -1, PREG_SPLIT_NO_EMPTY);
+        } else {
+            $this->mimeTypes = array_map('strtolower', $this->mimeTypes);
         }
     }
 
@@ -330,58 +334,77 @@ class FileValidator extends Validator
     /**
      * @inheritdoc
      */
-    public function clientValidateAttribute($object, $attribute, $view) {
+    public function clientValidateAttribute($object, $attribute, $view)
+    {
+        ValidationAsset::register($view);
+        $options = $this->getClientOptions($object, $attribute);
+        return 'yii.validation.file(attribute, messages, ' . json_encode($options) . ');';
+    }
+
+    /**
+     * Returns the client side validation options.
+     * @param \yii\base\Model $object the model being validated
+     * @param string $attribute the attribute name being validated
+     * @return array the client side validation options
+     */
+    protected function getClientOptions($object, $attribute)
+    {
         $label = $object->getAttributeLabel($attribute);
-        
+
         if ( $this->message !== null ){
             $options['message'] = Yii::$app->getI18n()->format($this->message, [
                 'attribute' => $label,
             ], Yii::$app->language);
         }
-        
+
         $options['skipOnEmpty'] = $this->skipOnEmpty;
-        
-        if ( !$this->skipOnEmpty ) {    
-            $options['uploadRequired'] = Yii::$app->getI18n()->format($this->uploadRequired, [], Yii::$app->language);
+
+        if ( !$this->skipOnEmpty ) {
+            $options['uploadRequired'] = Yii::$app->getI18n()->format($this->uploadRequired, [
+                'attribute' => $label,
+            ], Yii::$app->language);
         }
-        
+
         if ( $this->mimeTypes !== null ) {
             $options['mimeTypes'] = $this->mimeTypes;
             $options['wrongMimeType'] = Yii::$app->getI18n()->format($this->wrongMimeType, [
+                'attribute' => $label,
                 'mimeTypes' => join(', ', $this->mimeTypes)
             ], Yii::$app->language);
         }
-        
+
         if ( $this->extensions !== null ) {
             $options['extensions'] = $this->extensions;
             $options['wrongExtension'] = Yii::$app->getI18n()->format($this->wrongExtension, [
+                'attribute' => $label,
                 'extensions' => join(', ', $this->extensions)
             ], Yii::$app->language);
         }
-        
+
         if ( $this->minSize !== null ) {
             $options['minSize'] = $this->minSize;
             $options['tooSmall'] = Yii::$app->getI18n()->format($this->tooSmall, [
+                'attribute' => $label,
                 'limit' => $this->minSize
             ], Yii::$app->language);
         }
-        
+
         if ( $this->maxSize !== null ) {
             $options['maxSize'] = $this->maxSize;
             $options['tooBig'] = Yii::$app->getI18n()->format($this->tooBig, [
+                'attribute' => $label,
                 'limit' => $this->maxSize
-            ], Yii::$app->language); 
-       }
-        
+            ], Yii::$app->language);
+        }
+
         if ( $this->maxFiles !== null ) {
             $options['maxFiles'] = $this->maxFiles;
             $options['tooMany'] = Yii::$app->getI18n()->format($this->tooMany, [
+                'attribute' => $label,
                 'limit' => $this->maxFiles
             ], Yii::$app->language);
         }
-        
-        ValidationAsset::register($view);
-        
-        return 'yii.validation.file(value, messages, ' . json_encode($options) . ', attribute);';
+
+        return $options;
     }
 }
diff --git a/framework/validators/ImageValidator.php b/framework/validators/ImageValidator.php
index 0f84b4d..c59722f 100644
--- a/framework/validators/ImageValidator.php
+++ b/framework/validators/ImageValidator.php
@@ -134,7 +134,7 @@ class ImageValidator extends FileValidator
             return [$this->notImage, ['file' => $image->name]];
         }
 
-        list($width, $height, $type) = $imageInfo;
+        list($width, $height) = $imageInfo;
 
         if ($width == 0 || $height == 0) {
             return [$this->notImage, ['file' => $image->name]];
@@ -158,4 +158,64 @@ class ImageValidator extends FileValidator
 
         return null;
     }
+
+    /**
+     * @inheritdoc
+     */
+    public function clientValidateAttribute($object, $attribute, $view)
+    {
+        ValidationAsset::register($view);
+        $options = $this->getClientOptions($object, $attribute);
+        return 'yii.validation.image(attribute, messages, ' . json_encode($options) . ', deferred);';
+    }
+
+    /**
+     * @inheritdoc
+     */
+    protected function getClientOptions($object, $attribute)
+    {
+        $options = parent::getClientOptions($object, $attribute);
+
+        $label = $object->getAttributeLabel($attribute);
+
+        if ($this->notImage !== null) {
+            $options['notImage'] = Yii::$app->getI18n()->format($this->notImage, [
+                'attribute' => $label
+            ], Yii::$app->language);
+        }
+
+        if ($this->minWidth !== null) {
+            $options['minWidth'] = $this->minWidth;
+            $options['underWidth'] = Yii::$app->getI18n()->format($this->underWidth, [
+                'attribute' => $label,
+                'limit' => $this->minWidth
+            ], Yii::$app->language);
+        }
+
+        if ($this->maxWidth !== null) {
+            $options['maxWidth'] = $this->maxWidth;
+            $options['overWidth'] = Yii::$app->getI18n()->format($this->overWidth, [
+                'attribute' => $label,
+                'limit' => $this->maxWidth
+            ], Yii::$app->language);
+        }
+
+        if ($this->minHeight !== null) {
+            $options['minHeight'] = $this->minHeight;
+            $options['underHeight'] = Yii::$app->getI18n()->format($this->underHeight, [
+                'attribute' => $label,
+                'limit' => $this->maxHeight
+            ], Yii::$app->language);
+        }
+
+        if ($this->maxHeight !== null) {
+            $options['maxHeight'] = $this->maxHeight;
+            $options['overHeight'] = Yii::$app->getI18n()->format($this->overHeight, [
+                'attribute' => $label,
+                'limit' => $this->maxHeight
+            ], Yii::$app->language);
+        }
+
+        return $options;
+    }
 }
diff --git a/tests/unit/data/base/Singer.php b/tests/unit/data/base/Singer.php
index 0891690..75989bb 100644
--- a/tests/unit/data/base/Singer.php
+++ b/tests/unit/data/base/Singer.php
@@ -10,6 +10,7 @@ class Singer extends Model
 {
     public $firstName;
     public $lastName;
+    public $test;
 
     public function rules()
     {
@@ -17,6 +18,7 @@ class Singer extends Model
             [['lastName'], 'default', 'value' => 'Lennon'],
             [['lastName'], 'required'],
             [['underscore_style'], 'yii\captcha\CaptchaValidator'],
+            [['test'], 'required', 'when' => function($model) { return $model->firstName === 'cebe'; }],
         ];
     }
 }
diff --git a/tests/unit/extensions/gii/GeneratorsTest.php b/tests/unit/extensions/gii/GeneratorsTest.php
index f8cb16c..c93b1dc 100644
--- a/tests/unit/extensions/gii/GeneratorsTest.php
+++ b/tests/unit/extensions/gii/GeneratorsTest.php
@@ -1,6 +1,7 @@
 <?php
 namespace yiiunit\extensions\gii;
 
+use yii\gii\CodeFile;
 use yii\gii\generators\controller\Generator as ControllerGenerator;
 use yii\gii\generators\crud\Generator as CRUDGenerator;
 use yii\gii\generators\extension\Generator as ExtensionGenerator;
@@ -53,7 +54,11 @@ class GeneratorsTest extends GiiTestCase
         $generator->modelClass = 'Profile';
 
         if ($generator->validate()) {
-            $generator->generate();
+            $files = $generator->generate();
+            $modelCode = $files[0]->content;
+
+            $this->assertTrue(strpos($modelCode, "'id' => 'ID'") !== false, "ID label should be there:\n" . $modelCode);
+            $this->assertTrue(strpos($modelCode, "'description' => 'Description',") !== false, "Description label should be there:\n" . $modelCode);
         } else {
             print_r($generator->getErrors());
         }
diff --git a/tests/unit/framework/base/ModelTest.php b/tests/unit/framework/base/ModelTest.php
index f64afd7..6a2cd2a 100644
--- a/tests/unit/framework/base/ModelTest.php
+++ b/tests/unit/framework/base/ModelTest.php
@@ -216,7 +216,7 @@ class ModelTest extends TestCase
     public function testDefaultScenarios()
     {
         $singer = new Singer();
-        $this->assertEquals(['default' => ['lastName', 'underscore_style']], $singer->scenarios());
+        $this->assertEquals(['default' => ['lastName', 'underscore_style', 'test']], $singer->scenarios());
 
         $scenarios = [
             'default' => ['id', 'name', 'description'],
@@ -238,6 +238,13 @@ class ModelTest extends TestCase
         $singer = new Singer();
         $this->assertFalse($singer->isAttributeRequired('firstName'));
         $this->assertTrue($singer->isAttributeRequired('lastName'));
+
+        // attribute is not marked as required when a conditional validation is applied using `$when`.
+        // the condition should not be applied because this info may be retrieved before model is loaded with data
+        $singer->firstName = 'qiang';
+        $this->assertFalse($singer->isAttributeRequired('test'));
+        $singer->firstName = 'cebe';
+        $this->assertFalse($singer->isAttributeRequired('test'));
     }
 
     public function testCreateValidators()
diff --git a/tests/unit/framework/db/QueryBuilderTest.php b/tests/unit/framework/db/QueryBuilderTest.php
index d05e0b4..af2f157 100644
--- a/tests/unit/framework/db/QueryBuilderTest.php
+++ b/tests/unit/framework/db/QueryBuilderTest.php
@@ -152,10 +152,93 @@ class QueryBuilderTest extends DatabaseTestCase
             [ ['or like', 'name', ['heyho', 'abc']], '"name" LIKE :qp0 OR "name" LIKE :qp1', [':qp0' => '%heyho%', ':qp1' => '%abc%'] ],
             [ ['or not like', 'name', ['heyho', 'abc']], '"name" NOT LIKE :qp0 OR "name" NOT LIKE :qp1', [':qp0' => '%heyho%', ':qp1' => '%abc%'] ],
 
-            // TODO add more conditions
-            // IN
-            // NOT
-            // ...
+            // not
+            [ ['not', 'name'], 'NOT (name)', [] ],
+
+            // and
+            [ ['and', 'id=1', 'id=2'], '(id=1) AND (id=2)', [] ],
+            [ ['and', 'type=1', ['or', 'id=1', 'id=2']], '(type=1) AND ((id=1) OR (id=2))', [] ],
+
+            // or
+            [ ['or', 'id=1', 'id=2'], '(id=1) OR (id=2)', [] ],
+            [ ['or', 'type=1', ['or', 'id=1', 'id=2']], '(type=1) OR ((id=1) OR (id=2))', [] ],
+
+
+            // between
+            [ ['between', 'id', 1, 10], '"id" BETWEEN :qp0 AND :qp1', [':qp0' => 1, ':qp1' => 10] ],
+            [ ['not between', 'id', 1, 10], '"id" NOT BETWEEN :qp0 AND :qp1', [':qp0' => 1, ':qp1' => 10] ],
+
+            // in
+            [ ['in', 'id', [1, 2, 3]], '"id" IN (:qp0, :qp1, :qp2)', [':qp0' => 1, ':qp1' => 2, ':qp2' => 3] ],
+            [ ['not in', 'id', [1, 2, 3]], '"id" NOT IN (:qp0, :qp1, :qp2)', [':qp0' => 1, ':qp1' => 2, ':qp2' => 3] ],
+
+            // TODO: exists and not exists
+
+            // simple conditions
+            [ ['=', 'a', 'b'], '"a" = :qp0', [':qp0' => 'b'] ],
+            [ ['>', 'a', 1], '"a" > :qp0', [':qp0' => 1] ],
+            [ ['>=', 'a', 'b'], '"a" >= :qp0', [':qp0' => 'b'] ],
+            [ ['<', 'a', 2], '"a" < :qp0', [':qp0' => 2] ],
+            [ ['<=', 'a', 'b'], '"a" <= :qp0', [':qp0' => 'b'] ],
+            [ ['<>', 'a', 3], '"a" <> :qp0', [':qp0' => 3] ],
+            [ ['!=', 'a', 'b'], '"a" != :qp0', [':qp0' => 'b'] ],
+        ];
+
+        // adjust dbms specific escaping
+        foreach($conditions as $i => $condition) {
+            switch ($this->driverName) {
+                case 'mssql':
+                case 'mysql':
+                case 'sqlite':
+                    $conditions[$i][1] = str_replace('"', '`', $condition[1]);
+                    break;
+            }
+
+        }
+        return $conditions;
+    }
+
+    public function filterConditionProvider()
+    {
+        $conditions = [
+            // like
+            [ ['like', 'name', []], '', [] ],
+            [ ['not like', 'name', []], '', [] ],
+            [ ['or like', 'name', []], '', [] ],
+            [ ['or not like', 'name', []], '', [] ],
+
+            // not
+            [ ['not', ''], '', [] ],
+
+            // and
+            [ ['and', '', ''], '', [] ],
+            [ ['and', '', 'id=2'], '(id=2)', [] ],
+            [ ['and', 'id=1', ''], '(id=1)', [] ],
+            [ ['and', 'type=1', ['or', '', 'id=2']], '(type=1) AND ((id=2))', [] ],
+
+            // or
+            [ ['or', 'id=1', ''], '(id=1)', [] ],
+            [ ['or', 'type=1', ['or', '', 'id=2']], '(type=1) OR ((id=2))', [] ],
+
+
+            // between
+            [ ['between', 'id', 1, null], '', [] ],
+            [ ['not between', 'id', null, 10], '', [] ],
+
+            // in
+            [ ['in', 'id', []], '', [] ],
+            [ ['not in', 'id', []], '', [] ],
+
+            // TODO: exists and not exists
+
+            // simple conditions
+            [ ['=', 'a', ''], '', [] ],
+            [ ['>', 'a', ''], '', [] ],
+            [ ['>=', 'a', ''], '', [] ],
+            [ ['<', 'a', ''], '', [] ],
+            [ ['<=', 'a', ''], '', [] ],
+            [ ['<>', 'a', ''], '', [] ],
+            [ ['!=', 'a', ''], '', [] ],
         ];
 
         // adjust dbms specific escaping
@@ -183,6 +266,17 @@ class QueryBuilderTest extends DatabaseTestCase
         $this->assertEquals('SELECT *' . (empty($expected) ? '' : ' WHERE ' . $expected), $sql);
     }
 
+    /**
+     * @dataProvider filterConditionProvider
+     */
+    public function testBuildFilterCondition($condition, $expected, $expectedParams)
+    {
+        $query = (new Query())->filterWhere($condition);
+        list($sql, $params) = $this->getQueryBuilder()->build($query);
+        $this->assertEquals($expectedParams, $params);
+        $this->assertEquals('SELECT *' . (empty($expected) ? '' : ' WHERE ' . $expected), $sql);
+    }
+
     public function testAddDropPrimaryKey()
     {
         $tableName = 'constraints';
diff --git a/tests/unit/framework/rbac/PhpManagerTest.php b/tests/unit/framework/rbac/PhpManagerTest.php
index d22c8c4..6bd9cd9 100644
--- a/tests/unit/framework/rbac/PhpManagerTest.php
+++ b/tests/unit/framework/rbac/PhpManagerTest.php
@@ -75,13 +75,13 @@ class PhpManagerTest extends ManagerTestCase
 
     public function testSaveLoad()
     {
-        static::$filemtime = time();
         $this->prepareData();
 
         $items = $this->auth->items;
         $children = $this->auth->children;
         $assignments = $this->auth->assignments;
         $rules = $this->auth->rules;
+        static::$filemtime = time();
         $this->auth->save();
 
         $this->auth = $this->createManager();
diff --git a/tests/unit/framework/widgets/ActiveFieldTest.php b/tests/unit/framework/widgets/ActiveFieldTest.php
index 1330342..f4b3e2a 100644
--- a/tests/unit/framework/widgets/ActiveFieldTest.php
+++ b/tests/unit/framework/widgets/ActiveFieldTest.php
@@ -266,7 +266,7 @@ EOD;
         $this->activeField->model->addRule($this->attributeName, 'yiiunit\framework\widgets\TestValidator');
         $this->activeField->enableClientValidation = true;
         $actualValue = $this->activeField->getClientOptions();
-        $expectedJsExpression = "function (attribute, value, messages) {return true;}";
+        $expectedJsExpression = "function (attribute, value, messages, deferred) {return true;}";
         $expectedValidateOnChange = true;
         $expectedValidateOnType = false;
         $expectedValidationDelay = 200;
@@ -286,7 +286,7 @@ EOD;
         $this->activeField->enableAjaxValidation = true;
         $this->activeField->model->addRule($this->attributeName, 'yiiunit\framework\widgets\TestValidator');
         $actualValue = $this->activeField->getClientOptions();
-        $expectedJsExpression = "function (attribute, value, messages) {return true;}";
+        $expectedJsExpression = "function (attribute, value, messages, deferred) {return true;}";
         $expectedValidateOnChange = true;
         $expectedValidateOnType = false;
         $expectedValidationDelay = 200;
@@ -313,7 +313,7 @@ EOD;
         }
         
         $actualValue = $this->activeField->getClientOptions();
-        $expectedJsExpression = "function (attribute, value, messages) {if (function (attribute, value) "
+        $expectedJsExpression = "function (attribute, value, messages, deferred) {if (function (attribute, value) "
             . "{ return 'yii2' == 'yii2'; }(attribute, value)) { return true; }}";
        
         $expectedValidateOnChange = true;