Browse Source

Merge branch 'master' into fix-numbervalidator-comma-decimal-separator

tags/2.0.11
Alexander Makarov 8 years ago committed by GitHub
parent
commit
a7f0a3c147
  1. 6
      .codeclimate.yml
  2. 3
      .gitignore
  3. 41
      .travis.yml
  4. 58
      contrib/completion/bash/yii
  5. 38
      contrib/completion/zsh/_yii
  6. 2
      docs/guide-es/security-authorization.md
  7. 2
      docs/guide-fr/security-authorization.md
  8. 2
      docs/guide-ja/security-authorization.md
  9. 4
      docs/guide-ru/caching-data.md
  10. 23
      docs/guide-ru/concept-configurations.md
  11. 175
      docs/guide-ru/concept-di-container.md
  12. 52
      docs/guide-ru/db-active-record.md
  13. 4
      docs/guide-ru/db-query-builder.md
  14. 2
      docs/guide-ru/helper-array.md
  15. 2
      docs/guide-ru/security-authorization.md
  16. 18
      docs/guide/README.md
  17. 22
      docs/guide/concept-configurations.md
  18. 153
      docs/guide/concept-di-container.md
  19. 20
      docs/guide/db-active-record.md
  20. 59
      docs/guide/db-migrations.md
  21. 9
      docs/guide/db-query-builder.md
  22. 31
      docs/guide/input-validation.md
  23. 7
      docs/guide/security-authorization.md
  24. 1
      docs/guide/security-best-practices.md
  25. 47
      docs/guide/tutorial-console.md
  26. 14
      docs/internals/git-workflow.md
  27. 44
      framework/BaseYii.php
  28. 43
      framework/CHANGELOG.md
  29. 16
      framework/UPGRADE.md
  30. 14
      framework/assets/yii.activeForm.js
  31. 11
      framework/assets/yii.captcha.js
  32. 11
      framework/assets/yii.js
  33. 158
      framework/assets/yii.validation.js
  34. 17
      framework/base/Application.php
  35. 110
      framework/base/Component.php
  36. 10
      framework/base/Controller.php
  37. 23
      framework/base/Event.php
  38. 21
      framework/base/Model.php
  39. 15
      framework/base/Module.php
  40. 6
      framework/base/View.php
  41. 2
      framework/caching/ArrayCache.php
  42. 2
      framework/caching/Cache.php
  43. 4
      framework/caching/FileDependency.php
  44. 15
      framework/captcha/CaptchaValidator.php
  45. 2
      framework/console/Application.php
  46. 11
      framework/console/ErrorHandler.php
  47. 141
      framework/console/UnknownCommandException.php
  48. 104
      framework/console/controllers/HelpController.php
  49. 2
      framework/console/controllers/MigrateController.php
  50. 18
      framework/db/ActiveQuery.php
  51. 6
      framework/db/ActiveRelationTrait.php
  52. 6
      framework/db/BaseActiveRecord.php
  53. 3
      framework/db/ColumnSchemaBuilder.php
  54. 126
      framework/db/Query.php
  55. 12
      framework/db/QueryInterface.php
  56. 22
      framework/db/QueryTrait.php
  57. 6
      framework/db/cubrid/ColumnSchemaBuilder.php
  58. 6
      framework/db/mysql/ColumnSchemaBuilder.php
  59. 24
      framework/db/oci/ColumnSchemaBuilder.php
  60. 121
      framework/db/oci/Schema.php
  61. 80
      framework/di/Container.php
  62. 2
      framework/di/ServiceLocator.php
  63. 23
      framework/filters/HostControl.php
  64. 191
      framework/filters/PageCache.php
  65. 92
      framework/grid/RadioButtonColumn.php
  66. 2
      framework/helpers/BaseFileHelper.php
  67. 9
      framework/helpers/BaseJson.php
  68. 2
      framework/helpers/BaseUrl.php
  69. 2
      framework/messages/bg/yii.php
  70. 6
      framework/messages/pl/yii.php
  71. 12
      framework/rbac/BaseManager.php
  72. 5
      framework/rbac/DbManager.php
  73. 5
      framework/rbac/PhpManager.php
  74. 2
      framework/rest/UrlRule.php
  75. 15
      framework/validators/BooleanValidator.php
  76. 15
      framework/validators/CompareValidator.php
  77. 21
      framework/validators/EmailValidator.php
  78. 7
      framework/validators/FileValidator.php
  79. 15
      framework/validators/FilterValidator.php
  80. 2
      framework/validators/ImageValidator.php
  81. 23
      framework/validators/InlineValidator.php
  82. 28
      framework/validators/IpValidator.php
  83. 15
      framework/validators/NumberValidator.php
  84. 15
      framework/validators/RangeValidator.php
  85. 15
      framework/validators/RegularExpressionValidator.php
  86. 15
      framework/validators/RequiredValidator.php
  87. 12
      framework/validators/StringValidator.php
  88. 108
      framework/validators/UniqueValidator.php
  89. 21
      framework/validators/UrlValidator.php
  90. 17
      framework/validators/Validator.php
  91. 35
      framework/web/RangeNotSatisfiableHttpException.php
  92. 8
      framework/web/Response.php
  93. 72
      framework/widgets/ActiveField.php
  94. 4
      framework/widgets/BaseListView.php
  95. 9
      framework/widgets/Pjax.php
  96. 35
      package.json
  97. 7
      tests/data/ar/Order.php
  98. 40
      tests/data/codeclimate/phpmd_ruleset.xml
  99. 6
      tests/data/config.php
  100. 32
      tests/data/console/migrate_create/create_unsigned_big_pk.php
  101. Some files were not shown because too many files have changed in this diff Show More

6
.codeclimate.yml

@ -1,4 +1,3 @@
---
engines:
duplication:
enabled: true
@ -12,9 +11,8 @@ engines:
enabled: true
phpmd:
enabled: true
checks:
CleanCode/StaticAccess:
enabled: false
config:
rulesets: "codesize,design,unusedcode,tests/data/codeclimate/phpmd_ruleset.xml"
ratings:
paths:
- "**.js"

3
.gitignore vendored

@ -38,3 +38,6 @@ phpunit.phar
# ignore sub directory for dev installed apps and extensions
/apps
/extensions
# NPM packages
/node_modules

41
.travis.yml

@ -58,6 +58,44 @@ matrix:
services:
- mysql
- postgresql
- php: 5.4
addons:
apt:
packages:
- php5.4-gd
- php: 5.5
addons:
apt:
packages:
- php5.5-gd
- php: 5.6
addons:
apt:
packages:
- php5.6-gd
# have a separate branch for javascript tests
- language: node_js
node_js: 6
dist: trusty
# overwrite php related settings
php:
services:
addons:
install:
- travis_retry npm install
# disable xdebug for performance in composer
- phpenv config-rm xdebug.ini || echo "xdebug is not installed"
- travis_retry composer self-update && composer --version
- travis_retry composer global require "fxp/composer-asset-plugin:^1.2.0" --no-plugins
- travis_retry composer update --prefer-dist --no-interaction
before_script:
- node --version
- npm --version
- php --version
- composer --version
script: npm test
after_script:
allow_failures:
- php: nightly
@ -70,6 +108,7 @@ cache:
directories:
- vendor
- $HOME/.composer/cache
- $HOME/.npm
# try running against postgres 9.3
addons:
@ -84,7 +123,7 @@ install:
phpenv config-rm xdebug.ini || echo "xdebug is not installed"
fi
- travis_retry composer self-update && composer --version
- travis_retry composer global require "fxp/composer-asset-plugin:^1.2.0"
- travis_retry composer global require "fxp/composer-asset-plugin:^1.2.0" --no-plugins
- export PATH="$HOME/.composer/vendor/bin:$PATH"
# core framework:
- travis_retry composer update --prefer-dist --no-interaction

58
contrib/completion/bash/yii

@ -0,0 +1,58 @@
# This file implements bash completion for the ./yii command file.
# It completes the commands available by the ./yii command.
# See also:
# - https://debian-administration.org/article/317/An_introduction_to_bash_completion_part_2 on how this works.
# - https://www.gnu.org/software/bash/manual/html_node/Programmable-Completion.html
# - http://www.yiiframework.com/doc-2.0/guide-tutorial-console.html#bash-completion
#
# Usage:
# Temporarily you can source this file in you bash by typing: source yii
# For permanent availability, copy or link this file to /etc/bash_completion.d/
#
_yii()
{
local cur opts yii command
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
yii="${COMP_WORDS[0]}"
# exit if ./yii does not exist
test -f $yii || return 0
# lookup for command
for word in ${COMP_WORDS[@]:1}; do
if [[ $word != -* ]]; then
command=$word
break
fi
done
[[ $cur == $command ]] && state="command"
[[ $cur != $command ]] && state="option"
[[ $cur = *=* ]] && state="value"
case $state in
command)
# complete command/route if not given
# fetch available commands from ./yii help/list command
opts=$($yii help/list 2> /dev/null)
;;
option)
# fetch available options from ./yii help/list-action-options command
opts=$($yii help/list-action-options $command 2> /dev/null | grep -o '^--[a-zA-Z0-9]*')
;;
value)
# TODO allow normal file completion after an option, e.g. --migrationPath=...
;;
esac
# generate completion suggestions
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
}
# register completion for the ./yii command
# you may adjust this line if your command file is named differently
complete -F _yii ./yii yii

38
contrib/completion/zsh/_yii

@ -0,0 +1,38 @@
#compdef yii
_yii() {
local state command lastArgument commands options executive
lastArgument=${words[${#words[@]}]}
executive=$words[1]
# lookup for command
for word in ${words[@]:1}; do
if [[ $word != -* ]]; then
command=$word
break
fi
done
[[ $lastArgument == $command ]] && state="command"
[[ $lastArgument != $command ]] && state="option"
case $state in
command)
commands=("${(@f)$(${executive} help/list 2>/dev/null)}")
_describe 'command' commands
;;
option)
options=("${(@f)$(${executive} help/usage ${command} 2>/dev/null)}")
_message -r "$options"
suboptions=("${(@f)$(${executive} help/list-action-options ${command} 2>/dev/null)}")
_describe -V -o -t suboption 'action options' suboptions
;;
*)
esac
}
compdef _yii yii

2
docs/guide-es/security-authorization.md

@ -304,7 +304,7 @@ class RbacController extends Controller
```
> Note: Si estas utilizando el template avanzado, necesitas poner tu `RbacController` dentro del directorio `console/controllers`
y cambiar el espacio de nombres a `console/controllers`.
y cambiar el espacio de nombres a `console\controllers`.
Después de ejecutar el comando `yii rbac/init`, obtendremos la siguiente jerarquía:

2
docs/guide-fr/security-authorization.md

@ -258,7 +258,7 @@ class RbacController extends Controller
}
```
> Note: si vous utilisez le modèle avancé, vous devez mettre votre `RbacController` dans le dossier `console/controllers` et changer l'espace de noms en `console/controllers`.
> Note: si vous utilisez le modèle avancé, vous devez mettre votre `RbacController` dans le dossier `console/controllers` et changer l'espace de noms en `console\controllers`.
Après avoir exécuté la commande `yii rbac/init` vous vous retrouverez avec la hiérarchie suivante :

2
docs/guide-ja/security-authorization.md

@ -306,7 +306,7 @@ class RbacController extends Controller
```
> Note: アドバンストテンプレートを使おうとするときは、`RbacController` を `console/controllers`
ディレクトリの中に置いて、名前空間を `console/controllers` に変更する必要があります。
ディレクトリの中に置いて、名前空間を `console\controllers` に変更する必要があります。
`yii rbac/init` によってコマンドを実行した後には、次の権限階層が得られます。

4
docs/guide-ru/caching-data.md

@ -161,6 +161,10 @@ if ($data === false) {
}
```
Начиная с версии 2.0.11 вы можете изменить значение по умолчанию (бесконечность) для длительности кеширования задав
[[yii\caching\Cache::$defaultDuration|defaultDuration]] в конфигурации компонента кеша. Таким образом, можно будет
не передавать значение `duration` в [[yii\caching\Cache::set()|set()]] каждый раз.
### Зависимости кэша <span id="cache-dependencies"></span>
В добавок к изменению срока действия ключа элемент может быть признан недействительным из-за *изменения зависимостей*. К примеру, [[yii\caching\FileDependency]] представляет собой зависимость от времени изменения файла. Когда это время изменяется, любые устаревшие данные, найденные в кэше, должны быть признаны недействительным, а [[yii\caching\Cache::get()|get()]] в этом случае должен вернуть `false`.

23
docs/guide-ru/concept-configurations.md

@ -135,6 +135,29 @@ $config = [
За более подробной документацией о настройках свойства `components` в конфигурации приложения обратитесь к главам
[приложения](structure-applications.md) и [Service Locator](concept-service-locator.md).
Начиная с версии 2.0.11, можно настраивать [контейнер зависимостей](concept-di-container.md) через конфигурацию
приложения. Для этого используется свойство `container`:
```php
$config = [
'id' => 'basic',
'basePath' => dirname(__DIR__),
'extensions' => require(__DIR__ . '/../vendor/yiisoft/extensions.php'),
'container' => [
'definitions' => [
'yii\widgets\LinkPager' => ['maxButtonCount' => 5]
],
'singletons' => [
// Конфигурация для единожды создающихся объектов
]
]
];
```
Чтобы узнать о возможных значениях `definitions` и `singletons`, а также о реальных примерах использования,
прочитайте подраздел [более сложное практическое применение](concept-di-container.md#advanced-practical-usage) раздела
[Dependency Injection Container](concept-di-container.md).
### Конфигурации виджетов <span id="widget-configurations"></span>

175
docs/guide-ru/concept-di-container.md

@ -104,6 +104,138 @@ $container->get('Foo', [], [
> Info: Метод [[yii\di\Container::get()]] третьим аргументом принимает массив конфигурации, которым инициализируется создаваемый объект. Если класс реализует интерфейс [[yii\base\Configurable]] (например, [[yii\base\Object]]), то массив конфигурации передается в последний параметр конструктора класса. Иначе конфигурация применяется уже *после* создания объекта.
Более сложное практическое применение <span id="advanced-practical-usage"></span>
---------------
Допустим, мы работаем над API и у нас есть:
- `app\components\Request`, наследуемый от `yii\web\Request` и реализующий дополнительные возможности.
- `app\components\Response`, наследуемый от `yii\web\Response` с свойством `format`, по умолчанию инициализируемом как `json`.
- `app\storage\FileStorage` и `app\storage\DocumentsReader`, где реализована некая логика для работы с документами в
неком файловом хранилище:
```php
class FileStorage
{
public function __contruct($root) {
// делаем что-то
}
}
class DocumentsReader
{
public function __contruct(FileStorage $fs) {
// делаем что-то
}
}
```
Возможно настроить несколько компонентов сразу передав массив конфигурации в метод
[[yii\di\Container::setDefinitions()|setDefinitions()]] или [[yii\di\Container::setSingletons()|setSingletons()]].
Внутри метода фреймворк обойдёт массив конфигурации и вызовет для каждого элемента [[yii\di\Container::set()|set()]] или
[[yii\di\Container::setSingleton()|setSingleton()]] соответственно.
Формат массива конфигурации следующий:
- Ключ: имя класса, интерфейса или псевдонима. Ключ передаётся в первый аргумент `$class` метода
[[yii\di\Container::set()|set()]].
- Значение: конфигурация для класса. Возможные значения описаны в документации параметра `$definition` метода
[[yii\di\Container::set()|set()]]. Значение передаётся в аргумент `$definition` метода [[set()]].
Для примера, давайте настроим наш контейнер:
```php
$container->setDefinitions([
'yii\web\Request' => 'app\components\Request',
'yii\web\Response' => [
'class' => 'app\components\Response',
'format' => 'json'
],
'app\storage\DocumentsReader' => function () {
$fs = new app\storage\FileStorage('/var/tempfiles');
return new app\storage\DocumentsReader($fs);
}
]);
$reader = $container->get('app\storage\DocumentsReader);
// Создаст объект DocumentReader со всеми зависимостями
```
> Tip: Начиная с версии 2.0.11 контейнер может быть настроен в декларативном стиле через конфигурацию приложения.
Как это сделать ищите в подразделе [Конфигурация приложения](concept-service-locator.md#application-configurations)
раздела [Конфигурации](concept-configurations.md).
Вроде всё работает, но если нам необходимо создать экземпляр класса `DocumentWriter`, придётся скопировать код,
создающий экземпляр`FileStorage`, что, очевидно, не является оптимальным.
Как описано в подразделе [Разрешение зависимостей](#resolving-dependencies), [[yii\di\Container::set()|set()]]
и [[yii\di\Container::setSingleton()|setSingleton()]] могут опционально принимать третьим аргументов параметры
для конструктора. Формат таков:
- Ключ: имя класса, интерфейса или псевдонима. Ключ передаётся в первый аргумент `$class` метода [[yii\di\Container::set()|set()]].
- Значение: массив из двух элементов. Первый элемент передаётся в метод [[yii\di\Container::set()|set()]] вторым
аргументом `$definition`, второй элемент — аргументом `$params`.
Исправим наш пример:
```php
$container->setDefinitions([
'tempFileStorage' => [ // для удобства мы задали псевдоним
['class' => 'app\storage\FileStorage'],
['/var/tempfiles']
],
'app\storage\DocumentsReader' => [
['class' => 'app\storage\DocumentsReader'],
[Instance::of('tempFileStorage')]
],
'app\storage\DocumentsWriter' => [
['class' => 'app\storage\DocumentsWriter'],
[Instance::of('tempFileStorage')]
]
]);
$reader = $container->get('app\storage\DocumentsReader);
// Код будет работать ровно так же, как и в предыдущем примере.
```
Вы могли заметить вызов `Instance::of('tempFileStorage')`. Он означает, что [[yii\di\Container|Container]]
наявно предоставит зависимость, зарегистрированную с именем `tempFileStorage` и передаст её первым аргументом
в конструктор `app\storage\DocumentsWriter`.
> Note: Методы [[yii\di\Container::setDefinitions()|setDefinitions()]] и [[yii\di\Container::setSingletons()|setSingletons()]]
доступны с версии 2.0.11.
Ещё один шаг по оптимизации конфигурации — регистрировать некоторые зависимости как синглтоны. Зависимость, регистрируемая
через метод [[yii\di\Container::set()|set()]] будет созаваться каждый раз при обращении к ней. Некоторые классы не меняют
своего состояния на протяжении всей работы приложения, поэтому могут быть зарегистрированы как синглтоны. Это увеличит
производительность приложения.
Хорошим примером может быть класс `app\storage\FileStorage`, который выполняет некие операции над файловой системой
через простой API: `$fs->read()`, `$fs->write()`. Обе операции не меняют внутреннее состояние класса, поэтому мы можем
создать класс один раз и далее использовать его.
```php
$container->setSingletons([
'tempFileStorage' => [
['class' => 'app\storage\FileStorage'],
['/var/tempfiles']
],
]);
$container->setDefinitions([
'app\storage\DocumentsReader' => [
['class' => 'app\storage\DocumentsReader'],
[Instance::of('tempFileStorage')]
],
'app\storage\DocumentsWriter' => [
['class' => 'app\storage\DocumentsWriter'],
[Instance::of('tempFileStorage')]
]
]);
$reader = $container->get('app\storage\DocumentsReader);
```
### Внедрение зависимости через PHP callback <span id="php-callable-injection"></span>
В данном случае, контейнер будет использовать зарегистрированный PHP callback для создания новых экземпляров класса.
@ -211,13 +343,16 @@ $container->setSingleton('yii\db\Connection', [
Разрешение зависимостей <span id="resolving-dependencies"></span>
----------------------
После регистрации зависимостей, вы можете использовать контейнер внедрения зависимостей для создания новых объектов,
и контейнер автоматически разрешит зависимости их экземпляра и их внедрений во вновь создаваемых объектах. Разрешение зависимостей рекурсивно, то есть
если зависимость имеет другие зависимости, эти зависимости также будут автоматически разрешены.
и контейнер автоматически разрешит зависимости их экземпляра и их внедрений во вновь создаваемых объектах. Разрешение
зависимостей рекурсивно, то есть если зависимость имеет другие зависимости, эти зависимости также будут автоматически
разрешены.
Вы можете использовать [[yii\di\Container::get()]] для создания новых объектов. Метод принимает имя зависимости, которым может быть имя класса, имя интерфейса или псевдоним.
Имя зависимости может быть или не может быть зарегистрировано через `set()` или `setSingleton()`.
Вы можете опционально предоставить список параметров конструктора класса и [конфигурацию](concept-configurations.md) для настройки созданного объекта.
Например,
Вы можете использовать [[yii\di\Container::get()]] для создания или получения объектов. Метод принимает имя зависимости,
которым может быть имя класса, имя интерфейса или псевдоним. Имя зависимости может быть зарегистрировано через
`set()` или `setSingleton()`. Вы можете опционально предоставить список параметров конструктора класса и
[конфигурацию](concept-configurations.md) для настройки созданного объекта.
Например:
```php
// "db" ранее зарегистрированный псевдоним
@ -228,11 +363,14 @@ $engine = $container->get('app\components\SearchEngine', [$apiKey], ['type' => 1
```
За кулисами, контейнер внедрения зависимостей делает гораздо больше работы, чем просто создание нового объекта.
Прежде всего, контейнер, осмотрит конструктор класса, чтобы узнать имя зависимого класса или интерфейса, а затем автоматически разрешит эти зависимости рекурсивно.
Прежде всего, контейнер, осмотрит конструктор класса, чтобы узнать имя зависимого класса или интерфейса, а затем
автоматически разрешит эти зависимости рекурсивно.
Следующий код демонстрирует более сложный пример. Класс `UserLister` зависит от объекта, реализующего интерфейс `UserFinderInterface`; класс `UserFinder` реализует этот интерфейс и зависит от
объекта `Connection`. Все эти зависимости были объявлены через тип подсказки параметров конструктора класса.
При регистрации зависимости через свойство, контейнер внедрения зависимостей позволяет автоматически разрешить эти зависимости и создаёт новый экземпляр `UserLister` простым вызовом `get('userLister')`.
Следующий код демонстрирует более сложный пример. Класс `UserLister` зависит от объекта, реализующего интерфейс
`UserFinderInterface`; класс `UserFinder` реализует этот интерфейс и зависит от объекта `Connection`. Все эти зависимости
были объявлены через тип подсказки параметров конструктора класса. При регистрации зависимости через свойство, контейнер
внедрения зависимостей позволяет автоматически разрешить эти зависимости и создаёт новый экземпляр `UserLister` простым
вызовом `get('userLister')`.
```php
namespace app\models;
@ -291,17 +429,17 @@ $lister = new UserLister($finder);
```
Практическое использование <span id="practical-usage"></span>
Практическое применение <span id="practical-usage"></span>
---------------
Yii создаёт контейнер внедрения зависимостей когда вы подключаете файл `Yii.php` во [входном скрипте](structure-entry-scripts.md)
вашего приложения. Контейнер внедрения зависимостей доступен через [[Yii::$container]]. При вызове [[Yii::createObject()]],
метод на самом деле вызовет метод контейнера [[yii\di\Container::get()|get()]], чтобы создать новый объект.
Как упомянуто выше, контейнер внедрения зависимостей автоматически разрешит зависимости (если таковые имеются) и внедрит их в только что созданный объект.
Поскольку Yii использует [[Yii::createObject()]] в большей части кода своего ядра для создания новых объектов, это означает,
что вы можете настроить глобальные объекты, имея дело с [[Yii::$container]].
Как упомянуто выше, контейнер внедрения зависимостей автоматически разрешит зависимости (если таковые имеются) и внедрит их
получаемый объект. Поскольку Yii использует [[Yii::createObject()]] в большей части кода своего ядра для создания новых
объектов, это означает, что вы можете настроить глобальные объекты, имея дело с [[Yii::$container]].
Например, вы можете настроить по умолчанию глобальное количество кнопок в пейджере [[yii\widgets\LinkPager]]:
Например, давайте настроим количество кнопок в пейджере [[yii\widgets\LinkPager]] по умолчанию глобально:
```php
\Yii::$container->set('yii\widgets\LinkPager', ['maxButtonCount' => 5]);
@ -356,8 +494,11 @@ class HotelController extends Controller
Поскольку зависимости необходимы тогда, когда создаются новые объекты, то их регистрация должна быть сделана
как можно раньше. Ниже приведены рекомендуемые практики:
* Если вы разработчик приложения, то вы можете зарегистрировать зависимости во [входном скрипте](structure-entry-scripts.md) вашего приложения или в скрипте, подключённого во входном скрипте.
* Если вы разработчик распространяемого [расширения](structure-extensions.md), то вы можете зарегистрировать зависимости в загрузочном классе расширения.
* Если вы разработчик приложения, то вы можете зарегистрировать зависимости в конфигурации вашего приложения.
Как это сделать описано в подразделе [Конфигурация приложения](concept-service-locator.md#application-configurations)
раздела [Конфигурации](concept-configurations.md).
* Если вы разработчик распространяемого [расширения](structure-extensions.md), то вы можете зарегистрировать зависимости
в загрузочном классе расширения.
Итог <span id="summary"></span>

52
docs/guide-ru/db-active-record.md

@ -49,9 +49,20 @@ Yii поддерживает работу с Active Record для следующ
## Объявление классов Active Record <span id="declaring-ar-classes"></span>
Для начала объявите свой собственный класс, унаследовав класс [[yii\db\ActiveRecord]]. Поскольку каждый класс
Active Record сопоставлен с таблицей в базе данных, в своём классе вы должны переопределить метод
[[yii\db\ActiveRecord::tableName()|tableName()]], чтобы указать с какой именно таблицей связан ваш класс.
Для начала объявите свой собственный класс, унаследовав класс [[yii\db\ActiveRecord]].
### Настройка имени таблицы
По умолчанию каждый класс Active Record ассоциирован с таблицей в базе данных. Метод
[[yii\db\ActiveRecord::tableName()|tableName()]] получает имя таблицы из имени класса с помощью [[yii\helpers\Inflector::camel2id()]].
Если таблица не названа соответственно, вы можете переопределить данный метод.
Также может быть применён [[yii\db\Connection::$tablePrefix|tablePrefix]] по умолчанию. Например, если
[[yii\db\Connection::$tablePrefix|tablePrefix]] задан как `tbl_`, `Customer` преобразуется в `tbl_customer`, а
`OrderItem` в `tbl_order_item`.
Если имя таблицы указано в формате `{{%TableName}}`, символ `%` заменяется префиксом. Например, , `{{%post}}` становится
`{{tbl_post}}`. Фигуриные скобки используются для [экранирования в SQL-запросах](db-dao.md#quoting-table-and-column-names).
В нижеследующем примере мы объявляем класс Active Record с названием `Customer` для таблицы `customer`.
@ -70,11 +81,13 @@ class Customer extends ActiveRecord
*/
public static function tableName()
{
return 'customer';
return '{{customer}}';
}
}
```
### Классы Active record называются "моделями"
Объекты Active Record являются [моделями](structure-models.md). Именно поэтому мы обычно задаём классам Active Record
пространство имён `app\models` (или другое пространство имён, предназначенное для моделей).
@ -1135,6 +1148,37 @@ $customers = Customer::find()->joinWith([
содержит оператор JOIN. Если же запрос не содержит оператор JOIN, такое условие будет автоматически размещено в
конструкции `WHERE`.
#### Псевдонимы связанных таблиц <span id="relation-table-aliases"></span>
Как уже было отмечено, при использовании в запросе JOIN-ов, приходится явно решать конфликты имён. Поэтому часто таблицам
дают псевдонимы. Задать псевдоним для реляционного запроса можно следующим образом:
```php
$query->joinWith([
'orders' => function ($q) {
$q->from(['o' => Order::tableName()]);
},
])
```
Выглядит это довольно сложно. Либо приходится задавать явно имена таблиц, либо вызывать `Order::tableName()`.
Начиная с версии 2.0.7 вы можете задать и использовать псевдоним для связанной таблицы следующим образом:
```php
// join the orders relation and sort the result by orders.id
$query->joinWith(['orders o'])->orderBy('o.id');
```
Этот синтаксис работает для простых связей. Если же необходимо использовать связующую таблицу, например
`$query->joinWith(['orders.product'])`, то вызовы joinWith вкладываются друг в друга:
```php
$query->joinWith(['orders o' => function($q) {
$q->joinWith('product p');
}])
->where('o.amount > 100');
```
### Обратные связи <span id="inverse-relations"></span>

4
docs/guide-ru/db-query-builder.md

@ -146,6 +146,10 @@ $subQuery = (new Query())->select('id')->from('user')->where('status=1');
$query->from(['u' => $subQuery]);
```
#### Префиксы
Также может применяться [[yii\db\Connection::$tablePrefix|tablePrefix]] по умолчанию. Подробное описание смотрите
в подразделе [«Экранирование имён таблиц и столбцов» раздела «Объекты доступа к данным (DAO)»](guide-db-dao.html#quoting-table-and-column-names).
### [[yii\db\Query::where()|where()]] <span id="where"></span>

2
docs/guide-ru/helper-array.md

@ -81,7 +81,7 @@ if (!ArrayHelper::keyExists('username', $data1, false) || !ArrayHelper::keyExist
Часто нужно извлечь столбец значений из многомерного массива или объекта. Например, список ID.
```php
$data = [
$array = [
['id' => '123', 'data' => 'abc'],
['id' => '345', 'data' => 'def'],
];

2
docs/guide-ru/security-authorization.md

@ -298,7 +298,7 @@ class RbacController extends Controller
```
> Note: Если вы используете шаблон проекта advanced, `RbacController` необходимо создать в директории `console/controllers`
и сменить пространство имён на `console/controllers`.
и сменить пространство имён на `console\controllers`.
После выполнения команды `yii rbac/init` мы получим следующую иерархию:

18
docs/guide/README.md

@ -147,7 +147,7 @@ Development Tools
* [Debug Toolbar and Debugger](https://github.com/yiisoft/yii2-debug/blob/master/docs/guide/README.md)
* [Generating Code using Gii](https://github.com/yiisoft/yii2-gii/blob/master/docs/guide/README.md)
* **TBD** [Generating API Documentation](https://github.com/yiisoft/yii2-apidoc)
* [Generating API Documentation](https://github.com/yiisoft/yii2-apidoc)
Testing
@ -179,14 +179,14 @@ Special Topics
Widgets
-------
* GridView: **TBD** link to demo page
* ListView: **TBD** link to demo page
* DetailView: **TBD** link to demo page
* ActiveForm: **TBD** link to demo page
* Pjax: **TBD** link to demo page
* Menu: **TBD** link to demo page
* LinkPager: **TBD** link to demo page
* LinkSorter: **TBD** link to demo page
* [GridView](http://www.yiiframework.com/doc-2.0/yii-grid-gridview.html)
* [ListView](http://www.yiiframework.com/doc-2.0/yii-widgets-listview.html)
* [DetailView](http://www.yiiframework.com/doc-2.0/yii-widgets-detailview.html)
* [ActiveForm](http://www.yiiframework.com/doc-2.0/guide-input-forms.html#activerecord-based-forms-activeform)
* [Pjax](http://www.yiiframework.com/doc-2.0/yii-widgets-pjax.html)
* [Menu](http://www.yiiframework.com/doc-2.0/yii-widgets-menu.html)
* [LinkPager](http://www.yiiframework.com/doc-2.0/yii-widgets-linkpager.html)
* [LinkSorter](http://www.yiiframework.com/doc-2.0/yii-widgets-linksorter.html)
* [Bootstrap Widgets](https://github.com/yiisoft/yii2-bootstrap/blob/master/docs/guide/README.md)
* [jQuery UI Widgets](https://github.com/yiisoft/yii2-jui/blob/master/docs/guide/README.md)

22
docs/guide/concept-configurations.md

@ -135,6 +135,28 @@ an [entry script](structure-entry-scripts.md), where the class name is already g
More details about configuring the `components` property of an application can be found
in the [Applications](structure-applications.md) section and the [Service Locator](concept-service-locator.md) section.
Since version 2.0.11, the application configuration supports [Dependency Injection Container](concept-di-container.md)
configuration using `container` property. For example:
```php
$config = [
'id' => 'basic',
'basePath' => dirname(__DIR__),
'extensions' => require(__DIR__ . '/../vendor/yiisoft/extensions.php'),
'container' => [
'definitions' => [
'yii\widgets\LinkPager' => ['maxButtonCount' => 5]
],
'singletons' => [
// Dependency Injection Container singletons configuration
]
]
];
```
To know more about the possible values of `definitions` and `singletons` configuration arrays and real-life examples,
please read [Advanced Practical Usage](concept-di-container.md#advanced-practical-usage) subsection of the
[Dependency Injection Container](concept-di-container.md) article.
### Widget Configurations <span id="widget-configurations"></span>

153
docs/guide/concept-di-container.md

@ -224,11 +224,13 @@ and the container will automatically resolve dependencies by instantiating them
them into the newly created objects. The dependency resolution is recursive, meaning that
if a dependency has other dependencies, those dependencies will also be resolved automatically.
You can use [[yii\di\Container::get()]] to create new objects. The method takes a dependency name,
which can be a class name, an interface name or an alias name. The dependency name may or may
not be registered via `set()` or `setSingleton()`. You may optionally provide a list of class
You can use [[yii\di\Container::get()|get()]] to either create or get object instance.
The method takes a dependency name, which can be a class name, an interface name or an alias name.
The dependency name may be registered via [[yii\di\Container::set()|set()]]
or [[yii\di\Container::setSingleton()|setSingleton()]]. You may optionally provide a list of class
constructor parameters and a [configuration](concept-configurations.md) to configure the newly created object.
For example,
For example:
```php
// "db" is a previously registered alias name
@ -312,10 +314,10 @@ Yii creates a DI container when you include the `Yii.php` file in the [entry scr
of your application. The DI container is accessible via [[Yii::$container]]. When you call [[Yii::createObject()]],
the method will actually call the container's [[yii\di\Container::get()|get()]] method to create a new object.
As aforementioned, the DI container will automatically resolve the dependencies (if any) and inject them
into the newly created object. Because Yii uses [[Yii::createObject()]] in most of its core code to create
into obtained object. Because Yii uses [[Yii::createObject()]] in most of its core code to create
new objects, this means you can customize the objects globally by dealing with [[Yii::$container]].
For example, you can customize globally the default number of pagination buttons of [[yii\widgets\LinkPager]]:
For example, let's customize globally the default number of pagination buttons of [[yii\widgets\LinkPager]].
```php
\Yii::$container->set('yii\widgets\LinkPager', ['maxButtonCount' => 5]);
@ -368,6 +370,140 @@ cannot be instantiated. This is because you need to tell the DI container how to
Now if you access the controller again, an instance of `app\components\BookingService` will be
created and injected as the 3rd parameter to the controller's constructor.
Advanced Practical Usage <span id="advanced-practical-usage"></span>
---------------
Say we work on API application and have:
- `app\components\Request` class that extends `yii\web\Request` and provides additional functionality
- `app\components\Response` class that extends `yii\web\Response` and should have `format` property
set to `json` on creation
- `app\storage\FileStorage` and `app\storage\DocumentsReader` classes the implement some logic on
working with documents that are located in some file storage:
```php
class FileStorage
{
public function __contruct($root) {
// whatever
}
}
class DocumentsReader
{
public function __contruct(FileStorage $fs) {
// whatever
}
}
```
It is possible to configure multiple definitions at once, passing configuration array to
[[yii\di\Container::setDefinitions()|setDefinitions()]] or [[yii\di\Container::setSingletons()|setSingletons()]] method.
Iterating over the configuration array, the methods will call [[yii\di\Container::set()|set()]]
or [[yii\di\Container::setSingleton()|setSingleton()]] respectively for each item.
The configuration array format is:
- `key`: class name, interface name or alias name. The key will be passed to the
[[yii\di\Container::set()|set()]] method as a first argument `$class`.
- `value`: the definition associated with `$class`. Possible values are described in [[yii\di\Container::set()|set()]]
documentation for the `$definition` parameter. Will be passed to the [[set()]] method as
the second argument `$definition`.
For example, let's configure our container to follow the aforementioned requirements:
```php
$container->setDefinitions([
'yii\web\Request' => 'app\components\Request',
'yii\web\Response' => [
'class' => 'app\components\Response',
'format' => 'json'
],
'app\storage\DocumentsReader' => function () {
$fs = new app\storage\FileStorage('/var/tempfiles');
return new app\storage\DocumentsReader($fs);
}
]);
$reader = $container->get('app\storage\DocumentsReader);
// Will create DocumentReader object with its dependencies as described in the config
```
> Tip: Container may be configured in declarative style using application configuration since version 2.0.11.
Check out the [Application Configurations](concept-service-locator.md#application-configurations) subsection of
the [Configurations](concept-configurations.md) guide article.
Everything works, but in case we need to create create `DocumentWriter` class,
we shall copy-paste the line that creates `FileStorage` object, that is not the smartest way, obviously.
As described in the [Resolving Dependencies](#resolving-dependencies) subsection, [[yii\di\Container::set()|set()]]
and [[yii\di\Container::setSingleton()|setSingleton()]] can optionally take dependency's constructor parameters as
a third argument. To set the constructor parameters, you may use the following configuration array format:
- `key`: class name, interface name or alias name. The key will be passed to the
[[yii\di\Container::set()|set()]] method as a first argument `$class`.
- `value`: array of two elements. The first element will be passed the [[yii\di\Container::set()|set()]] method as the
second argument `$definition`, the second one — as `$params`.
Let's modify our example:
```php
$container->setDefinitions([
'tempFileStorage' => [ // we've created an alias for convenience
['class' => 'app\storage\FileStorage'],
['/var/tempfiles'] // could be extracted from some config files
],
'app\storage\DocumentsReader' => [
['class' => 'app\storage\DocumentsReader'],
[Instance::of('tempFileStorage')]
],
'app\storage\DocumentsWriter' => [
['class' => 'app\storage\DocumentsWriter'],
[Instance::of('tempFileStorage')]
]
]);
$reader = $container->get('app\storage\DocumentsReader);
// Will behave exactly the same as in the previous example.
```
You might notice `Instance::of('tempFileStorage')` notation. It means, that the [[yii\di\Container|Container]]
will implicitly provide dependency, registered with `tempFileStorage` name and pass it as the first argument
of `app\storage\DocumentsWriter` constructor.
> Note: [[yii\di\Container::setDefinitions()|setDefinitions()]] and [[yii\di\Container::setSingletons()|setSingletons()]]
methods are available since version 2.0.11.
Another step on configuration optimization is to register some dependencies as singletons.
A dependency registered via [[yii\di\Container::set()|set()]] will be instantiated each time it is needed.
Some classes do not change the state during runtime, therefore they may be registered as singletons
in order to increase the application performance.
A good example could be `app\storage\FileStorage` class, that executes some operations on file system with a simple
API (e.g. `$fs->read()`, `$fs->write()`). These operations do not change the internal class state, so we can
create its instance once and use it multiple times.
```php
$container->setSingletons([
'tempFileStorage' => [
['class' => 'app\storage\FileStorage'],
['/var/tempfiles']
],
]);
$container->setDefinitions([
'app\storage\DocumentsReader' => [
['class' => 'app\storage\DocumentsReader'],
[Instance::of('tempFileStorage')]
],
'app\storage\DocumentsWriter' => [
['class' => 'app\storage\DocumentsWriter'],
[Instance::of('tempFileStorage')]
]
]);
$reader = $container->get('app\storage\DocumentsReader);
```
When to Register Dependencies <span id="when-to-register-dependencies"></span>
-----------------------------
@ -375,8 +511,9 @@ When to Register Dependencies <span id="when-to-register-dependencies"></span>
Because dependencies are needed when new objects are being created, their registration should be done
as early as possible. The following are the recommended practices:
* If you are the developer of an application, you can register dependencies in your
application's [entry script](structure-entry-scripts.md) or in a script that is included by the entry script.
* If you are the developer of an application, you can register your dependencies using application configuration.
Please, read the [Application Configurations](concept-service-locator.md#application-configurations) subsection of
the [Configurations](concept-configurations.md) guide article.
* If you are the developer of a redistributable [extension](structure-extensions.md), you can register dependencies
in the bootstrapping class of the extension.

20
docs/guide/db-active-record.md

@ -50,9 +50,20 @@ However, most content described here are also applicable to Active Record for No
## Declaring Active Record Classes <span id="declaring-ar-classes"></span>
To get started, declare an Active Record class by extending [[yii\db\ActiveRecord]]. Because each Active Record
class is associated with a database table, in this class you should override the [[yii\db\ActiveRecord::tableName()|tableName()]]
method to specify which table the class is associated with.
To get started, declare an Active Record class by extending [[yii\db\ActiveRecord]].
### Setting a table name
By default each Active Record class is associated with its database table.
The [[yii\db\ActiveRecord::tableName()|tableName()]] method returns the table name by converting the class name via [[yii\helpers\Inflector::camel2id()]].
You may override this method if the table is not named after this convention.
Also a default [[yii\db\Connection::$tablePrefix|tablePrefix]] can be applied. For example if
[[yii\db\Connection::$tablePrefix|tablePrefix]] is `tbl_`, `Customer` becomes `tbl_customer` and `OrderItem` becomes `tbl_order_item`.
If a table name is given as `{{%TableName}}`, then the percentage character `%` will be replaced with the table prefix.
For example, `{{%post}}` becomes `{{tbl_post}}`. The brackets around the table name are used for
[quoting in an SQL query](db-dao.md#quoting-table-and-column-names).
In the following example, we declare an Active Record class named `Customer` for the `customer` database table.
@ -71,11 +82,12 @@ class Customer extends ActiveRecord
*/
public static function tableName()
{
return 'customer';
return '{{customer}}';
}
}
```
### Active records are called "models"
Active Record instances are considered as [models](structure-models.md). For this reason, we usually put Active Record
classes under the `app\models` namespace (or other namespaces for keeping model classes).

59
docs/guide/db-migrations.md

@ -891,6 +891,65 @@ will be used to record the migration history. You no longer need to specify it v
command-line option.
### Separated Migrations <span id="separated-migrations"></span>
Sometimes you may need to use migrations from a different namespace. It can be some extension or module in your own
project. One of such examples is migrations for [RBAC component](security-authorization.md#configuring-rbac).
Since version 2.0.10 you can use [[yii\console\controllers\MigrateController::migrationNamespaces|migrationNamespaces]]
to solve this task:
```php
return [
'controllerMap' => [
'migrate' => [
'class' => 'yii\console\controllers\MigrateController',
'migrationNamespaces' => [
'app\migrations', // Common migrations for the whole application
'module\migrations', // Migrations for the specific project's module
               'yii\rbac\migrations', // Migrations for the specific extension
],
],
],
];
```
If you want them to be applied and tracked down completely separated from each other, you can configure multiple
migration commands which will use different namespaces and migration history tables:
```php
return [
'controllerMap' => [
// Common migrations for the whole application
'migrate-app' => [
'class' => 'yii\console\controllers\MigrateController',
'migrationNamespaces' => ['app\migrations'],
'migrationTable' => 'migration_app',
],
// Migrations for the specific project's module
'migrate-module' => [
'class' => 'yii\console\controllers\MigrateController',
'migrationNamespaces' => ['module\migrations'],
'migrationTable' => 'migration_module',
],
// Migrations for the specific extension
'migrate-rbac' => [
'class' => 'yii\console\controllers\MigrateController',
           'migrationNamespaces' => ['yii\rbac\migrations'],
'migrationTable' => 'migration_rbac',
],
],
];
```
Note that to synchronize database you now need to run multiple commands instead of one:
```
yii migrate-app
yii migrate-module
yii migrate-rbac
```
## Migrating Multiple Databases <span id="migrating-multiple-databases"></span>
By default, migrations are applied to the same database specified by the `db` [application component](structure-application-components.md).

9
docs/guide/db-query-builder.md

@ -153,6 +153,9 @@ $subQuery = (new Query())->select('id')->from('user')->where('status=1');
$query->from(['u' => $subQuery]);
```
#### Prefixes
Also a default [[yii\db\Connection::$tablePrefix|tablePrefix]] can be applied. Implementation instructions
are in the ["Quoting Tables" section of the "Database Access Objects" guide](guide-db-dao.html#quoting-table-and-column-names).
### [[yii\db\Query::where()|where()]] <span id="where"></span>
@ -366,6 +369,12 @@ You can also specify operator explicitly:
$query->andFilterCompare('name', 'Doe', 'like');
```
Since Yii 2.0.11 there are similar methods for `HAVING` condition:
- [[yii\db\Query::filterHaving()|filterHaving()]]
- [[yii\db\Query::andFilterHaving()|andFilterHaving()]]
- [[yii\db\Query::orFilterHaving()|orFilterHaving()]]
### [[yii\db\Query::orderBy()|orderBy()]] <span id="order-by"></span>
The [[yii\db\Query::orderBy()|orderBy()]] method specifies the `ORDER BY` fragment of a SQL query. For example,

31
docs/guide/input-validation.md

@ -331,8 +331,9 @@ the method/function is:
/**
* @param string $attribute the attribute currently being validated
* @param mixed $params the value of the "params" given in the rule
* @param \yii\validators\InlineValidator related InlineValidator instance
*/
function ($attribute, $params)
function ($attribute, $params, $validator)
```
If an attribute fails the validation, the method/function should call [[yii\base\Model::addError()]] to save
@ -355,7 +356,7 @@ class MyForm extends Model
['country', 'validateCountry'],
// an inline validator defined as an anonymous function
['token', function ($attribute, $params) {
['token', function ($attribute, $params, $validator) {
if (!ctype_alnum($this->$attribute)) {
$this->addError($attribute, 'The token must contain letters or digits.');
}
@ -363,7 +364,7 @@ class MyForm extends Model
];
}
public function validateCountry($attribute, $params)
public function validateCountry($attribute, $params, $validator)
{
if (!in_array($this->$attribute, ['USA', 'Web'])) {
$this->addError($attribute, 'The country must be either "USA" or "Web".');
@ -372,6 +373,14 @@ class MyForm extends Model
}
```
> Note: Since version 2.0.11 you can use [[yii\validators\InlineValidator::addError()]] for adding errors instead. That way the error
> message can be formatted using [[yii\i18n\I18N::format()]] right away. Use `{attribute}` and `{value}` in the error
> message to refer to an attribute label (no need to get it manually) and attribute value accordingly:
>
> ```php
> $validator->addError($this, $attribute, 'The value "{value}" is not acceptable for {attribute}.');
> ```
> Note: By default, inline validators will not be applied if their associated attributes receive empty inputs
or if they have already failed some validation rules. If you want to make sure a rule is always applied,
you may configure the [[yii\validators\Validator::skipOnEmpty|skipOnEmpty]] and/or [[yii\validators\Validator::skipOnError|skipOnError]]
@ -889,6 +898,22 @@ validation of individual input fields by configuring their [[yii\widgets\ActiveF
property to be false. When `enableClientValidation` is configured at both the input field level and the form level,
the former will take precedence.
> Info: Since version 2.0.11 all validators extending from [[yii\validators\Validator]] receive client-side options
> from separate method - [[yii\validators\Validator::getClientOptions()]]. You can use it:
>
> - if you want to implement your own custom client-side validation but leave the synchronization with server-side
> validator options;
> - to extend or customize to fit your specific needs:
>
> ```php
> public function getClientOptions($model, $attribute)
> {
> $options = parent::getClientOptions($model, $attribute);
> // Modify $options here
>
> return $options;
> }
> ```
### Implementing Client-Side Validation <span id="implementing-client-side-validation"></span>

7
docs/guide/security-authorization.md

@ -242,6 +242,9 @@ Before you can go on you need to create those tables in the database. To do this
`yii migrate --migrationPath=@yii/rbac/migrations`
Read more about working with migrations from different namespaces in
[Separated Migrations](db-migrations.md#separated-migrations) section.
The `authManager` can now be accessed via `\Yii::$app->authManager`.
@ -304,7 +307,7 @@ class RbacController extends Controller
```
> Note: If you are using advanced template, you need to put your `RbacController` inside `console/controllers` directory
and change namespace to `console/controllers`.
and change namespace to `console\controllers`.
After executing the command with `yii rbac/init` we'll get the following hierarchy:
@ -328,7 +331,7 @@ public function signup()
$user->save(false);
// the following three lines were added:
$auth = Yii::$app->authManager;
$auth = \Yii::$app->authManager;
$authorRole = $auth->getRole('author');
$auth->assign($authorRole, $user->getId());

1
docs/guide/security-best-practices.md

@ -256,6 +256,7 @@ return [
'example.com',
'*.example.com',
],
'fallbackHostInfo' => 'https://example.com',
],
// ...
];

47
docs/guide/tutorial-console.md

@ -107,6 +107,53 @@ You can see an example of this in the advanced project template.
> ```
Console command completion <span id="console-command-completion"></span>
---------------
Auto-completion of command arguments is a useful thing when working with the shell.
Since version 2.0.11, the `./yii` command provides auto completion for the bash out of the box.
### Bash completion
Make sure bash completion is installed. For most of installations it is available by default.
Place the completion script in `/etc/bash_completion.d/`:
curl -L https://raw.githubusercontent.com/yiisoft/yii2/master/contrib/completion/bash/yii -o /etc/bash_completion.d/yii
For temporary usage you can put the file into the current directory and include it in the current session via `source yii`.
If globally installed you may need to restart the terminal or `source ~/.bashrc` to activate it.
Check the [Bash Manual](https://www.gnu.org/software/bash/manual/html_node/Programmable-Completion.html) for
other ways of including completion script to your environment.
### ZSH completion
Put the completion script in directory for completions, using e.g. `~/.zsh/completion/`
```
mkdir -p ~/.zsh/completion
curl -L https://raw.githubusercontent.com/yiisoft/yii2/master/contrib/completion/zsh/_yii -o ~/.zsh/completion/_yii
```
Include the directory in the `$fpath`, e.g. by adding it to `~/.zshrc`
```
fpath=(~/.zsh/completion $fpath)
```
Make sure `compinit` is loaded or do it by adding in `~/.zshrc`
```
autoload -Uz compinit && compinit -i
```
Then reload your shell
```
exec $SHELL -l
```
Creating your own console commands <span id="create-command"></span>
----------------------------------

14
docs/internals/git-workflow.md

@ -39,6 +39,14 @@ The following steps are not necessary if you want to work only on translations o
> Note: If you see errors like `Problem 1 The requested package bower-asset/jquery could not be found in any version, there may be a typo in the package name.`, you will need to run `composer global require "fxp/composer-asset-plugin:^1.2.0"`
If you are going to work with JavaScript:
- run `npm install` to install JavaScript testing tools and dependencies (assuming you have [Node.js and NPM installed]
(https://nodejs.org/en/download/package-manager/)).
> Note: JavaScript tests depend on [jsdom](https://github.com/tmpvar/jsdom) library which requires Node.js 4 or newer.
Using of Node.js 6 or 7 is more preferable.
- run `php build/build dev/app basic` to clone the basic app and install composer dependencies for the basic app.
This command will install foreign composer packages as normal but will link the yii2 repo to
the currently checked out repo, so you have one instance of all the code installed.
@ -65,6 +73,12 @@ settings that are configured in `tests/data/config.php`.
You may limit the tests to a group of tests you are working on e.g. to run only tests for the validators and redis
`phpunit --group=validators,redis`. You get the list of available groups by running `phpunit --list-groups`.
You can execute JavaScript unit tests by running `npm test` in the repo root directory.
> Note: If you get timeout errors like `Error: timeout of 2000ms exceeded. Ensure the done() callback is being called
in this test.`, you can increase timeout: `npm test -- --timeout 30000` (don't miss `--`, it's needed for passing
additional arguments).
### Extensions
To work on extensions you have to clone the extension repository. We have created a command that can do this for you:

44
framework/BaseYii.php

@ -139,20 +139,20 @@ class BaseYii
if (isset(static::$aliases[$root])) {
if (is_string(static::$aliases[$root])) {
return $pos === false ? static::$aliases[$root] : static::$aliases[$root] . substr($alias, $pos);
} else {
foreach (static::$aliases[$root] as $name => $path) {
if (strpos($alias . '/', $name . '/') === 0) {
return $path . substr($alias, strlen($name));
}
}
foreach (static::$aliases[$root] as $name => $path) {
if (strpos($alias . '/', $name . '/') === 0) {
return $path . substr($alias, strlen($name));
}
}
}
if ($throwException) {
throw new InvalidParamException("Invalid path alias: $alias");
} else {
return false;
}
return false;
}
/**
@ -170,11 +170,11 @@ class BaseYii
if (isset(static::$aliases[$root])) {
if (is_string(static::$aliases[$root])) {
return $root;
} else {
foreach (static::$aliases[$root] as $name => $path) {
if (strpos($alias . '/', $name . '/') === 0) {
return $name;
}
}
foreach (static::$aliases[$root] as $name => $path) {
if (strpos($alias . '/', $name . '/') === 0) {
return $name;
}
}
}
@ -346,9 +346,9 @@ class BaseYii
return static::$container->invoke($type, $params);
} elseif (is_array($type)) {
throw new InvalidConfigException('Object configuration must be an array containing a "class" element.');
} else {
throw new InvalidConfigException('Unsupported configuration type: ' . gettype($type));
}
throw new InvalidConfigException('Unsupported configuration type: ' . gettype($type));
}
private static $_logger;
@ -360,9 +360,9 @@ class BaseYii
{
if (self::$_logger !== null) {
return self::$_logger;
} else {
return self::$_logger = static::createObject('yii\log\Logger');
}
return self::$_logger = static::createObject('yii\log\Logger');
}
/**
@ -499,14 +499,14 @@ class BaseYii
{
if (static::$app !== null) {
return static::$app->getI18n()->translate($category, $message, $params, $language ?: static::$app->language);
} else {
$p = [];
foreach ((array) $params as $name => $value) {
$p['{' . $name . '}'] = $value;
}
}
return ($p === []) ? $message : strtr($message, $p);
$placeholders = [];
foreach ((array) $params as $name => $value) {
$placeholders['{' . $name . '}'] = $value;
}
return ($placeholders === []) ? $message : strtr($message, $placeholders);
}
/**

43
framework/CHANGELOG.md

@ -5,13 +5,18 @@ Yii Framework 2 Change Log
------------------------
- Bug #4113: Error page stacktrace was generating links to private methods which are not part of the API docs (samdark)
- Bug #7727: Fixed truncateHtml leaving extra tags (developeruz)
- Bug #9305: Fixed MSSQL `Schema::TYPE_TIMESTAMP` to be 'datetime' instead of 'timestamp', which is just an incremental number (nkovacs)
- Bug #9616: Fixed mysql\Schema::loadColumnSchema to set enumValues attribute correctly if enum definition contains commas (fphammerle)
- Bug #9796: Initialization of not existing `yii\grid\ActionColumn` default buttons (arogachev)
- Bug #10488: Fixed incorrect behavior of `yii\validation\NumberValidator` when used with locales where decimal separator is comma (quantum13, samdark)
- Bug #11771: Fixed semantics of `yii\di\ServiceLocator::__isset()` to match the behavior of `__get()` which fixes inconsistent behavior on newer PHP versions (cebe)
- Bug #12213: Fixed `yii\db\ActiveRecord::unlinkAll()` to respect `onCondition()` of the relational query (silverfire)
- Bug #12681: Changed `data` column type from `text` to `blob` to handle null-byte (`\0`) in serialized RBAC rule properly (silverfire)
- Bug #12714: Fixed `yii\validation\EmailValidator` to prevent false-positives checks when property `checkDns` is set to `true` (silverfire)
- Bug #12735: Fixed `yii\console\controllers\MigrateController` creating multiple primary keys for field `bigPrimaryKey:unsigned` (SG5)
- Bug #12791: Fixed `yii\behaviors\AttributeTypecastBehavior` unable to automatically detect `attributeTypes`, triggering PHP Fatal Error (klimov-paul)
- Bug #12795: Fixed inconsistency, `Yii::$app->controller` is available after handling the request since 2.0.10, this is now also the case for `Yii::$app->controller->action` (cebe)
- Bug #12803, #12921: Fixed BC break in `yii.activeForm.js` introduced in #11999. Reverted commit 3ba72da (silverfire)
- Bug #12810: Fixed `yii\rbac\DbManager::getChildRoles()` and `yii\rbac\PhpManager::getChildRoles()` throws an exception when role has no child roles (mysterydragon)
- Bug #12822: Fixed `yii\i18n\Formatter::asTimestamp()` to process timestamp with miliseconds correctly (h311ion)
@ -26,36 +31,59 @@ Yii Framework 2 Change Log
- Bug #12974: Fixed incorrect order of migrations history in case `yii\console\controllers\MigrateController::$migrationNamespaces` is in use (evgen-d, klimov-paul)
- Bug #13071: Help option for commands was not working in modules (arogachev, haimanman)
- Bug #13089: Fixed `yii\console\controllers\AssetController::adjustCssUrl()` breaks URL reference specification (`url(#id)`) (vitalyzhakov)
- Bug #7727: Fixed truncateHtml leaving extra tags (developeruz)
- Enh #6809: Added `\yii\caching\Cache::$defaultDuration` property, allowing to set custom default cache duration (sdkiller)
- Bug #13105: Fixed `validate()` method in `yii.activeForm.js` to prevent unexpected form submit when `forceValidate` set to `true` (silverfire)
- Bug #13118: Fixed `handleAction()` function in `yii.js` to handle attribute `data-pjax=0` as disabled PJAX (silverfire, arisk)
- Bug #13128: Fixed incorrect position of {pos} string in ColumnSchemaBuilder `__toString` (df2)
- Bug #13159: Fixed `destroy` method in `yii.captcha.js` which did not work as expected (arogachev)
- Bug #13198: Fixed order of checks in `yii\validators\IpValidator` that sometimes caused wrong error message (silverfire)
- Bug #13200: Creating Urls for routes specified in `yii\rest\UrlRule::$extraPatterns` did not work if no HTTP verb was specified (cebe)
- Enh #475: Added Bash and Zsh completion support for the `./yii` command (cebe, silverfire)
- Enh #6242: Access to validator in inline validation (arogachev)
- Enh #6373: Introduce `yii\db\Query::emulateExecution()` to force returning an empty result for a query (klimov-paul)
- Enh #6809: Added `yii\caching\Cache::$defaultDuration` property, allowing to set custom default cache duration (sdkiller)
- Enh #7333: Improved error message for `yii\di\Instance::ensure()` when a component does not exist (cebe)
- Enh #7420: Attributes for prompt generated with `renderSelectOptions` of `\yii\helpers\Html` helper (arogachev)
- Enh #9053: Added`yii\grid\RadioButtonColumn` (darwinisgod)
- Enh #9162: Added support of closures in `value` for attributes in `yii\widgets\DetailView` (arogachev)
- Enh #10896: Select only primary key when counting records in UniqueValidator (developeruz)
- Enh #11037: `yii.js` and `yii.validation.js` use `Regexp.test()` instead of `String.match()` (arogachev, nkovacs)
- Enh #11163: Added separate method for client-side validation options `yii\validators\Validator::getClientOptions()` (arogachev)
- Enh #11697: Added `filterHaving()`, `andFilterHaving()` and `orFilterHaving()` to `yii\db\Query` (nicdnepr, samdark)
- Enh #11756: Added type mapping for `varbinary` data type in MySQL DBMS (silverfire)
- Enh #11758: Implemented Dependency Injection Container configuration using Application configuration array (silverfire)
- Enh #11929: Changed `type` column type from `int` to `smallInt` in RBAC migrations (silverfire)
- Enh #12015: Changed visibility `yii\db\ActiveQueryTrait::createModels()` from private to protected (ArekX, dynasource)
- Enh #12145: Added `beforeCacheResponse` and `afterRestoreResponse` to `yii\filters\PageCache` to be more easily extendable (sergeymakinen)
- Enh #12390: Avoid creating queries with false where condition (`0=1`) when fetching relational data (klimov-paul)
- Enh #12399: Added `ActiveField::addAriaAttributes` property for `aria-required` and `aria-invalid` attributes rendering (Oxyaction, samdark)
- Enh #12619: Added catch `Throwable` in `yii\base\ErrorHandler::handleException()` (rob006)
- Enh #12659: Suggest alternatives when console command was not found (mdmunir, cebe)
- Enh #12726: `yii\base\Application::$version` converted to `yii\base\Module::$version` virtual property, allowing to specify version as a PHP callback (klimov-paul)
- Enh #12732: Added `is_dir()` validation to `yii\helpers\BaseFileHelper::findFiles()` method (zalatov, silverfire)
- Enh #12738: Added support for creating protocol-relative URLs in `UrlManager::createAbsoluteUrl()` and `Url` helper methods (rob006)
- Enh #12748: Added Migration tool automatic generation reference column for foreignKey (MKiselev)
- Enh #12748: Migration generator now tries to fetch reference column name for foreignKey from schema if it's not set explicitly (MKiselev)
- Enh #12750: `yii\widgets\ListView::itemOptions` can be a closure now (webdevsega, silverfire)
- Enh #12771: Skip \yii\rbac\PhpManager::checkAccessRecursive and \yii\rbac\DbManager::checkAccessRecursive if role assignments are empty (Ni-san)
- Enh #12790: Added `scrollToErrorOffset` option for `yii\widgets\ActiveForm` which adds ability to specify offset in pixels when scrolling to error (mg-code)
- Enh #12798: Changed `yii\cache\Dependency::getHasChanged()` (deprecated, to be removed in 2.1) to `yii\cache\Dependency::isChanged()` (dynasource)
- Enh #12807: Added console controller checks for `yii\console\controllers\HelpController` (schmunk42)
- Enh #12816: Added `columnSchemaClass` option for `yii\db\Schema` which adds ability to specify custom `yii\db\ColumnSchema` class (nanodesu88)
- Enh #12854: Added `RangeNotSatisfiableHttpException` to cover HTTP error 416 file request exceptions (zalatov)
- Enh #12881: Added `removeValue` method to `yii\helpers\BaseArrayHelper` (nilsburg)
- Enh #12901: Added `getDefaultHelpHeader` method to the `yii\console\controllers\HelpController` class to be able to override default help header in a class heir (diezztsk)
- Enh #12988: Changed `textarea` method within the `yii\helpers\BaseHtml` class to allow users to control whether html entities found within `$value` will be double-encoded or not (cyphix333)
- Enh #13020: Added `disabledListItemSubTagOptions` attribute for `yii\widgets\LinkPager` in order to customize the disabled list item sub tag element (nadar)
- Enh #13035: Use ArrayHelper::getValue() in SluggableBehavior::getValue() (thyseus)
- Enh #13036: Added shortcut methods `asJson()` and `asXml()` for returning JSON and XML data in web controller actions (cebe)
- Enh #13020: Added `disabledListItemSubTagOptions` attribute for `yii\widgets\LinkPager` in order to customize the disabled list item sub tag element (nadar)
- Enh #12988: Changed `textarea` method within the `yii\helpers\BaseHtml` class to allow users to control whether html entities found within `$value` will be double-encoded or not (cyphix333)
- Enh #13074: Improved `\yii\log\SyslogTarget` with `$options` to be able to change the default `openlog` options. (timbeks)
- Enh #13050: Added `yii\filters\HostControl` allowing protection against 'host header' attacks (klimov-paul)
- Enh #13050: Added `yii\filters\HostControl` allowing protection against 'host header' attacks (klimov-paul, rob006)
- Enh #13074: Improved `yii\log\SyslogTarget` with `$options` to be able to change the default `openlog` options. (timbeks)
- Bug: #12969: Improved unique ID generation for `yii\widgets\Pjax` widgets (dynasource, samdark, rob006)
- Enh #13122: Optimized query for information about foreign keys in `yii\db\oci` (zlakomanoff)
- Enh #13202: Refactor validateAttribute method in UniqueValidator (developeruz)
- Enh: Added constants for specifying `yii\validators\CompareValidator::$type` (cebe)
2.0.10 October 20, 2016
-----------------------
@ -106,6 +134,7 @@ Yii Framework 2 Change Log
- Bug #12605: Make 'safe' validator work on write-only properties (arthibald, CeBe)
- Bug #12629: Fixed `yii\widgets\ActiveField::widget()` to call `adjustLabelFor()` for `InputWidget` descendants (coderlex)
- Bug #12649: Fixed consistency of `indexBy` handling for `yii\db\Query::column()` (silverfire)
- Bug #12713: Fixed `yii\caching\FileDependency` to clear stat cache before reading filemtime (SG5)
- Enh #384: Added ability to run migration from several locations via `yii\console\controllers\BaseMigrateController::$migrationNamespaces` (klimov-paul)
- Enh #6996: Added `yii\web\MultipartFormDataParser`, which allows proper processing of 'multipart/form-data' encoded non POST requests (klimov-paul)
- Enh #8719: Add support for HTML5 attributes on submitbutton (formaction/formmethod...) for ActiveForm (VirtualRJ)
@ -140,11 +169,11 @@ Yii Framework 2 Change Log
- Enh #12440: Added `yii\base\Event::offAll()` method allowing clear all registered class-level event handlers (klimov-paul)
- Enh #12499: When AJAX validation in enabled, `yii.activeForm.js` will run it forcefully on form submit to display all possible errors (silverfire)
- Enh #12580: Make `yii.js` comply with strict and non-strict javascript mode to allow concatenation with external code (mikehaertl)
- Enh #12612: Query conditions added with `yii\db\Query::andWhere()` now get appended to the existing conditions if they were already being joined with the `and` operator (brandonkelly)
- Enh #12664: Added support for wildcards for `optional` at `yii\filters\auth\AuthMethod` (mg-code)
- Enh #12744: Added `afterInit` event to `yii.activeForm.js` (werew01f)
- Enh: Method `yii\console\controllers\AssetController::getAssetManager()` automatically enables `yii\web\AssetManager::forceCopy` in case it is not explicitly specified (pana1990, klimov-paul)
2.0.9 July 11, 2016
-------------------

16
framework/UPGRADE.md

@ -50,6 +50,22 @@ if you want to upgrade from version A to version C and there is
version B between A and C, you need to follow the instructions
for both A and B.
Upgrade from Yii 2.0.10
-----------------------
* A new method `public function emulateExecution($value = true);` has been added to the `yii\db\QueryInterace`.
This method is implemented in the `yii\db\QueryTrait`, so this only affects your code if you implement QueryInterface
in a class that does not use the trait.
* `yii\validators\FileValidator::getClientOptions()` and `yii\validators\ImageValidator::getClientOptions()` are now public.
If you extend from these classes and override these methods, you must make them public as well.
* PJAX: Auto generated IDs of the Pjax widget have been changed to use their own prefix to avoid conflicts.
Auto generated IDs are now prefixed with `p` instead of `w`. This is defined by the `$autoIdPrefix`
property of `yii\widgets\Pjax`. If you have any PHP or Javascript code that depends on autogenerated IDs
you should update these to match this new value. It is not a good idea to rely on auto generated values anyway, so
you better fix these cases by specifying an explicit ID.
Upgrade from Yii 2.0.9
----------------------

14
framework/assets/yii.activeForm.js

@ -168,7 +168,9 @@
// whether the validation is cancelled by beforeValidateAttribute event handler
cancelled: false,
// the value of the input
value: undefined
value: undefined,
// whether to update aria-invalid attribute after validation
updateAriaInvalid: true
};
@ -304,9 +306,9 @@
needAjaxValidation = false,
messages = {},
deferreds = deferredArray(),
submitting = data.submitting;
submitting = data.submitting && !forceValidate;
if (submitting) {
if (data.submitting) {
var event = $.Event(events.beforeValidate);
$form.trigger(event, [messages, deferreds]);
@ -707,6 +709,7 @@
hasError = messages[attribute.id].length > 0;
var $container = $form.find(attribute.container);
var $error = $container.find(attribute.error);
updateAriaInvalid($form, attribute, hasError);
if (hasError) {
if (attribute.encodeError) {
$error.text(messages[attribute.id][0]);
@ -775,4 +778,9 @@
}
};
var updateAriaInvalid = function ($form, attribute, hasError) {
if (attribute.updateAriaInvalid) {
$form.find(attribute.input).attr('aria-invalid', hasError ? 'true' : 'false');
}
}
})(window.jQuery);

11
framework/assets/yii.captcha.js

@ -16,7 +16,7 @@
} else if (typeof method === 'object' || !method) {
return methods.init.apply(this, arguments);
} else {
$.error('Method ' + method + ' does not exist on jQuery.yiiCaptcha');
$.error('Method ' + method + ' does not exist in jQuery.yiiCaptcha');
return false;
}
};
@ -39,7 +39,6 @@
methods.refresh.apply($e);
return false;
});
});
},
@ -58,10 +57,9 @@
},
destroy: function () {
return this.each(function () {
$(window).unbind('.yiiCaptcha');
$(this).removeData('yiiCaptcha');
});
this.off('.yiiCaptcha');
this.removeData('yiiCaptcha');
return this;
},
data: function () {
@ -69,4 +67,3 @@
}
};
})(window.jQuery);

11
framework/assets/yii.js

@ -153,7 +153,8 @@ window.yii = (function ($) {
method = !$e.data('method') && $form ? $form.attr('method') : $e.data('method'),
action = $e.attr('href'),
params = $e.data('params'),
pjax = $e.data('pjax'),
pjax = $e.data('pjax') || 0,
usePjax = pjax !== 0 && $.support.pjax,
pjaxPushState = !!$e.data('pjax-push-state'),
pjaxReplaceState = !!$e.data('pjax-replace-state'),
pjaxTimeout = $e.data('pjax-timeout'),
@ -164,7 +165,7 @@ window.yii = (function ($) {
pjaxContainer,
pjaxOptions = {};
if (pjax !== undefined && $.support.pjax) {
if (usePjax) {
if ($e.data('pjax-container')) {
pjaxContainer = $e.data('pjax-container');
} else {
@ -190,13 +191,13 @@ window.yii = (function ($) {
if (method === undefined) {
if (action && action != '#') {
if (pjax !== undefined && $.support.pjax) {
if (usePjax) {
$.pjax.click(event, pjaxOptions);
} else {
window.location = action;
}
} else if ($e.is(':submit') && $form.length) {
if (pjax !== undefined && $.support.pjax) {
if (usePjax) {
$form.on('submit',function(e){
$.pjax.submit(e, pjaxOptions);
})
@ -249,7 +250,7 @@ window.yii = (function ($) {
oldAction = $form.attr('action');
$form.attr('action', action);
}
if (pjax !== undefined && $.support.pjax) {
if (usePjax) {
$form.on('submit',function(e){
$.pjax.submit(e, pjaxOptions);
})

158
framework/assets/yii.validation.js

@ -13,7 +13,7 @@
yii.validation = (function ($) {
var pub = {
isEmpty: function (value) {
return value === null || value === undefined || value == [] || value === '';
return value === null || value === undefined || ($.isArray(value) && value.length === 0) || value === '';
},
addMessage: function (messages, message, value) {
@ -36,6 +36,7 @@ yii.validation = (function ($) {
}
},
// "boolean" is a reserved keyword in older versions of ES so it's quoted for IE < 9 support
'boolean': function (value, messages, options) {
if (options.skipOnEmpty && pub.isEmpty(value)) {
return;
@ -58,15 +59,16 @@ yii.validation = (function ($) {
return;
}
if (options.is !== undefined && value.length != options.is) {
pub.addMessage(messages, options.notEqual, value);
return;
}
if (options.min !== undefined && value.length < options.min) {
pub.addMessage(messages, options.tooShort, value);
}
if (options.max !== undefined && value.length > options.max) {
pub.addMessage(messages, options.tooLong, value);
}
if (options.is !== undefined && value.length != options.is) {
pub.addMessage(messages, options.notEqual, value);
}
},
file: function (attribute, messages, options) {
@ -76,9 +78,8 @@ yii.validation = (function ($) {
});
},
image: function (attribute, messages, options, deferred) {
image: function (attribute, messages, options, deferredList) {
var files = getUploadedFiles(attribute, messages, options);
$.each(files, function (i, file) {
validateFile(file, messages, options);
@ -87,48 +88,33 @@ yii.validation = (function ($) {
return;
}
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.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.replace(/\{file\}/g, file.name));
def.resolve();
};
var deferred = $.Deferred();
pub.validateImage(file, messages, options, deferred, new FileReader(), new Image());
deferredList.push(deferred);
});
},
fr.onload = function () {
img.src = fr.result;
};
validateImage: function (file, messages, options, deferred, fileReader, image) {
image.onload = function() {
validateImageSize(file, image, messages, options);
deferred.resolve();
};
// Resolve deferred if there was error while reading data
fr.onerror = function () {
def.resolve();
};
image.onerror = function () {
messages.push(options.notImage.replace(/\{file\}/g, file.name));
deferred.resolve();
};
fr.readAsDataURL(file);
fileReader.onload = function () {
image.src = this.result;
};
deferred.push(def);
});
// Resolve deferred if there was error while reading data
fileReader.onerror = function () {
deferred.resolve();
};
fileReader.readAsDataURL(file);
},
number: function (value, messages, options) {
@ -170,6 +156,10 @@ yii.validation = (function ($) {
}
});
if (options.not === undefined) {
options.not = false;
}
if (options.not === inArray) {
pub.addMessage(messages, options.message, value);
}
@ -190,25 +180,26 @@ yii.validation = (function ($) {
return;
}
var valid = true;
var regexp = /^((?:"?([^"]*)"?\s)?)(?:\s+)?(?:(<?)((.+)@([^>]+))(>?))$/,
var valid = true,
regexp = /^((?:"?([^"]*)"?\s)?)(?:\s+)?(?:(<?)((.+)@([^>]+))(>?))$/,
matches = regexp.exec(value);
if (matches === null) {
valid = false
valid = false;
} else {
var localPart = matches[5],
domain = matches[6];
if (options.enableIDN) {
matches[5] = punycode.toASCII(matches[5]);
matches[6] = punycode.toASCII(matches[6]);
localPart = punycode.toASCII(localPart);
domain = punycode.toASCII(domain);
value = matches[1] + matches[3] + matches[5] + '@' + matches[6] + matches[7];
value = matches[1] + matches[3] + localPart + '@' + domain + matches[7];
}
if (matches[5].length > 64) {
if (localPart.length > 64) {
valid = false;
} else if ((matches[5] + '@' + matches[6]).length > 254) {
} else if ((localPart + '@' + domain).length > 254) {
valid = false;
} else {
valid = options.pattern.test(value) || (options.allowName && options.fullPattern.test(value));
@ -262,11 +253,7 @@ yii.validation = (function ($) {
// CAPTCHA may be updated via AJAX and the updated hash is stored in body data
var hash = $('body').data(options.hashKey);
if (hash == null) {
hash = options.hash;
} else {
hash = hash[options.caseSensitive ? 0 : 1];
}
hash = hash == null ? options.hash : hash[options.caseSensitive ? 0 : 1];
var v = options.caseSensitive ? value : value.toLowerCase();
for (var i = v.length - 1, h = 0; i >= 0; --i) {
h += v.charCodeAt(i);
@ -281,7 +268,8 @@ yii.validation = (function ($) {
return;
}
var compareValue, valid = true;
var compareValue,
valid = true;
if (options.compareAttribute === undefined) {
compareValue = options.compareValue;
} else {
@ -328,17 +316,13 @@ yii.validation = (function ($) {
},
ip: function (value, messages, options) {
var getIpVersion = function (value) {
return value.indexOf(':') === -1 ? 4 : 6;
};
var negation = null, cidr = null;
if (options.skipOnEmpty && pub.isEmpty(value)) {
return;
}
var matches = new RegExp(options.ipParsePattern).exec(value);
var negation = null,
cidr = null,
matches = new RegExp(options.ipParsePattern).exec(value);
if (matches) {
negation = matches[1] || null;
value = matches[2];
@ -358,20 +342,21 @@ yii.validation = (function ($) {
return;
}
if (getIpVersion(value) == 6) {
var ipVersion = value.indexOf(':') === -1 ? 4 : 6;
if (ipVersion == 6) {
if (!(new RegExp(options.ipv6Pattern)).test(value)) {
pub.addMessage(messages, options.messages.message, value);
}
if (!options.ipv6) {
pub.addMessage(messages, options.messages.ipv6NotAllowed, value);
}
if (!(new RegExp(options.ipv6Pattern)).test(value)) {
} else {
if (!(new RegExp(options.ipv4Pattern)).test(value)) {
pub.addMessage(messages, options.messages.message, value);
}
} else {
if (!options.ipv4) {
pub.addMessage(messages, options.messages.ipv4NotAllowed, value);
}
if (!(new RegExp(options.ipv4Pattern)).test(value)) {
pub.addMessage(messages, options.messages.message, value);
}
}
}
};
@ -405,15 +390,8 @@ yii.validation = (function ($) {
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();
}
var index = file.name.lastIndexOf('.');
var ext = !~index ? '' : file.name.substr(index + 1, file.name.length).toLowerCase();
if (!~options.extensions.indexOf(ext)) {
messages.push(options.wrongExtension.replace(/\{file\}/g, file.name));
@ -445,5 +423,23 @@ yii.validation = (function ($) {
return false;
}
function validateImageSize(file, image, messages, options) {
if (options.minWidth && image.width < options.minWidth) {
messages.push(options.underWidth.replace(/\{file\}/g, file.name));
}
if (options.maxWidth && image.width > options.maxWidth) {
messages.push(options.overWidth.replace(/\{file\}/g, file.name));
}
if (options.minHeight && image.height < options.minHeight) {
messages.push(options.underHeight.replace(/\{file\}/g, file.name));
}
if (options.maxHeight && image.height > options.maxHeight) {
messages.push(options.overHeight.replace(/\{file\}/g, file.name));
}
}
return pub;
})(jQuery);

17
framework/base/Application.php

@ -246,6 +246,12 @@ abstract class Application extends Module
$this->setTimeZone('UTC');
}
if (isset($config['container'])) {
$this->setContainer($config['container']);
unset($config['container']);
}
// merge core components with custom components
foreach ($this->coreComponents() as $id => $component) {
if (!isset($config['components'][$id])) {
@ -652,4 +658,15 @@ abstract class Application extends Module
exit($status);
}
}
/**
* Configures [[Yii::$container]] with the $config
*
* @param array $config values given in terms of name-value pairs
* @since 2.0.11
*/
public function setContainer($config)
{
Yii::configure(Yii::$container, $config);
}
}

110
framework/base/Component.php

@ -130,20 +130,21 @@ class Component extends Object
if (method_exists($this, $getter)) {
// read property, e.g. getName()
return $this->$getter();
} else {
// behavior property
$this->ensureBehaviors();
foreach ($this->_behaviors as $behavior) {
if ($behavior->canGetProperty($name)) {
return $behavior->$name;
}
}
// behavior property
$this->ensureBehaviors();
foreach ($this->_behaviors as $behavior) {
if ($behavior->canGetProperty($name)) {
return $behavior->$name;
}
}
if (method_exists($this, 'set' . $name)) {
throw new InvalidCallException('Getting write-only property: ' . get_class($this) . '::' . $name);
} else {
throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '::' . $name);
}
throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '::' . $name);
}
/**
@ -182,22 +183,22 @@ class Component extends Object
$this->attachBehavior($name, $value instanceof Behavior ? $value : Yii::createObject($value));
return;
} else {
// behavior property
$this->ensureBehaviors();
foreach ($this->_behaviors as $behavior) {
if ($behavior->canSetProperty($name)) {
$behavior->$name = $value;
}
return;
}
// behavior property
$this->ensureBehaviors();
foreach ($this->_behaviors as $behavior) {
if ($behavior->canSetProperty($name)) {
$behavior->$name = $value;
return;
}
}
if (method_exists($this, 'get' . $name)) {
throw new InvalidCallException('Setting read-only property: ' . get_class($this) . '::' . $name);
} else {
throw new UnknownPropertyException('Setting unknown property: ' . get_class($this) . '::' . $name);
}
throw new UnknownPropertyException('Setting unknown property: ' . get_class($this) . '::' . $name);
}
/**
@ -219,15 +220,16 @@ class Component extends Object
$getter = 'get' . $name;
if (method_exists($this, $getter)) {
return $this->$getter() !== null;
} else {
// behavior property
$this->ensureBehaviors();
foreach ($this->_behaviors as $behavior) {
if ($behavior->canGetProperty($name)) {
return $behavior->$name !== null;
}
}
// behavior property
$this->ensureBehaviors();
foreach ($this->_behaviors as $behavior) {
if ($behavior->canGetProperty($name)) {
return $behavior->$name !== null;
}
}
return false;
}
@ -250,16 +252,17 @@ class Component extends Object
if (method_exists($this, $setter)) {
$this->$setter(null);
return;
} else {
// behavior property
$this->ensureBehaviors();
foreach ($this->_behaviors as $behavior) {
if ($behavior->canSetProperty($name)) {
$behavior->$name = null;
return;
}
}
// behavior property
$this->ensureBehaviors();
foreach ($this->_behaviors as $behavior) {
if ($behavior->canSetProperty($name)) {
$behavior->$name = null;
return;
}
}
throw new InvalidCallException('Unsetting an unknown or read-only property: ' . get_class($this) . '::' . $name);
}
@ -503,19 +506,19 @@ class Component extends Object
if ($handler === null) {
unset($this->_events[$name]);
return true;
} else {
$removed = false;
foreach ($this->_events[$name] as $i => $event) {
if ($event[0] === $handler) {
unset($this->_events[$name][$i]);
$removed = true;
}
}
if ($removed) {
$this->_events[$name] = array_values($this->_events[$name]);
}
$removed = false;
foreach ($this->_events[$name] as $i => $event) {
if ($event[0] === $handler) {
unset($this->_events[$name][$i]);
$removed = true;
}
return $removed;
}
if ($removed) {
$this->_events[$name] = array_values($this->_events[$name]);
}
return $removed;
}
/**
@ -621,9 +624,9 @@ class Component extends Object
unset($this->_behaviors[$name]);
$behavior->detach();
return $behavior;
} else {
return null;
}
return null;
}
/**
@ -666,13 +669,14 @@ class Component extends Object
if (is_int($name)) {
$behavior->attach($this);
$this->_behaviors[] = $behavior;
} else {
if (isset($this->_behaviors[$name])) {
$this->_behaviors[$name]->detach();
}
$behavior->attach($this);
$this->_behaviors[$name] = $behavior;
}
if (isset($this->_behaviors[$name])) {
$this->_behaviors[$name]->detach();
}
$behavior->attach($this);
$this->_behaviors[$name] = $behavior;
return $behavior;
}
}

10
framework/base/Controller.php

@ -164,7 +164,9 @@ class Controller extends Component implements ViewContextInterface
}
}
$this->action = $oldAction;
if ($oldAction !== null) {
$this->action = $oldAction;
}
return $result;
}
@ -186,9 +188,8 @@ class Controller extends Component implements ViewContextInterface
return $this->runAction($route, $params);
} elseif ($pos > 0) {
return $this->module->runAction($route, $params);
} else {
return Yii::$app->runAction(ltrim($route, '/'), $params);
}
return Yii::$app->runAction(ltrim($route, '/'), $params);
}
/**
@ -393,9 +394,8 @@ class Controller extends Component implements ViewContextInterface
$layoutFile = $this->findLayoutFile($this->getView());
if ($layoutFile !== false) {
return $this->getView()->renderFile($layoutFile, ['content' => $content], $this);
} else {
return $content;
}
return $content;
}
/**

23
framework/base/Event.php

@ -116,20 +116,19 @@ class Event extends Object
if ($handler === null) {
unset(self::$_events[$name][$class]);
return true;
} else {
$removed = false;
foreach (self::$_events[$name][$class] as $i => $event) {
if ($event[0] === $handler) {
unset(self::$_events[$name][$class][$i]);
$removed = true;
}
}
if ($removed) {
self::$_events[$name][$class] = array_values(self::$_events[$name][$class]);
}
}
return $removed;
$removed = false;
foreach (self::$_events[$name][$class] as $i => $event) {
if ($event[0] === $handler) {
unset(self::$_events[$name][$class][$i]);
$removed = true;
}
}
if ($removed) {
self::$_events[$name][$class] = array_values(self::$_events[$name][$class]);
}
return $removed;
}
/**

21
framework/base/Model.php

@ -562,9 +562,8 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab
{
if ($attribute === null) {
return $this->_errors === null ? [] : $this->_errors;
} else {
return isset($this->_errors[$attribute]) ? $this->_errors[$attribute] : [];
}
return isset($this->_errors[$attribute]) ? $this->_errors[$attribute] : [];
}
/**
@ -578,16 +577,15 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab
{
if (empty($this->_errors)) {
return [];
} else {
$errors = [];
foreach ($this->_errors as $name => $es) {
if (!empty($es)) {
$errors[$name] = reset($es);
}
}
}
return $errors;
$errors = [];
foreach ($this->_errors as $name => $es) {
if (!empty($es)) {
$errors[$name] = reset($es);
}
}
return $errors;
}
/**
@ -829,9 +827,8 @@ class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayab
$this->setAttributes($data[$scope]);
return true;
} else {
return false;
}
return false;
}
/**

15
framework/base/Module.php

@ -391,9 +391,8 @@ class Module extends ServiceLocator
$module = $this->getModule(substr($id, 0, $pos));
return $module === null ? false : $module->hasModule(substr($id, $pos + 1));
} else {
return isset($this->_modules[$id]);
}
return isset($this->_modules[$id]);
}
/**
@ -467,9 +466,8 @@ class Module extends ServiceLocator
}
return $modules;
} else {
return $this->_modules;
}
return $this->_modules;
}
/**
@ -527,10 +525,10 @@ class Module extends ServiceLocator
}
return $result;
} else {
$id = $this->getUniqueId();
throw new InvalidRouteException('Unable to resolve the request "' . ($id === '' ? $route : $id . '/' . $route) . '".');
}
$id = $this->getUniqueId();
throw new InvalidRouteException('Unable to resolve the request "' . ($id === '' ? $route : $id . '/' . $route) . '".');
}
/**
@ -640,9 +638,8 @@ class Module extends ServiceLocator
return get_class($controller) === $className ? $controller : null;
} elseif (YII_DEBUG) {
throw new InvalidConfigException("Controller class must extend from \\yii\\base\\Controller.");
} else {
return null;
}
return null;
}
/**

6
framework/base/View.php

@ -347,9 +347,8 @@ class View extends Component
$this->addDynamicPlaceholder($placeholder, $statements);
return $placeholder;
} else {
return $this->evaluateDynamicContent($statements);
}
return $this->evaluateDynamicContent($statements);
}
/**
@ -465,9 +464,8 @@ class View extends Component
$this->endCache();
return false;
} else {
return true;
}
return true;
}
/**

2
framework/caching/ArrayCache.php

@ -15,6 +15,8 @@ namespace yii\caching;
* Unlike the [[Cache]], ArrayCache allows the expire parameter of [[set]], [[add]], [[multiSet]] and [[multiAdd]] to
* be a floating point number, so you may specify the time in milliseconds (e.g. 0.1 will be 100 milliseconds).
*
* For enhanced performance of ArrayCache, you can disable serialization of the stored data by setting [[$serializer]] to `false`.
*
* For more details and usage information on Cache, see the [guide article on caching](guide:caching-overview).
*
* @author Carsten Brandt <mail@cebe.cc>

2
framework/caching/Cache.php

@ -209,7 +209,7 @@ abstract class Cache extends Component implements \ArrayAccess
* a complex data structure consisting of factors representing the key.
* @param mixed $value the value to be cached
* @param int $duration default duration in seconds before the cache will expire. If not set,
* default [[ttl]] value is used.
* default [[defaultDuration]] value is used.
* @param Dependency $dependency dependency of the cached item. If the dependency changes,
* the corresponding value in the cache will be invalidated when it is fetched via [[get()]].
* This parameter is ignored if [[serializer]] is false.

4
framework/caching/FileDependency.php

@ -43,6 +43,8 @@ class FileDependency extends Dependency
throw new InvalidConfigException('FileDependency::fileName must be set');
}
return @filemtime(Yii::getAlias($this->fileName));
$fileName = Yii::getAlias($this->fileName);
clearstatcache(false, $fileName);
return @filemtime($fileName);
}
}

15
framework/captcha/CaptchaValidator.php

@ -86,6 +86,17 @@ class CaptchaValidator extends Validator
*/
public function clientValidateAttribute($model, $attribute, $view)
{
ValidationAsset::register($view);
$options = $this->getClientOptions($model, $attribute);
return 'yii.validation.captcha(value, messages, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ');';
}
/**
* @inheritdoc
*/
public function getClientOptions($model, $attribute)
{
$captcha = $this->createCaptchaAction();
$code = $captcha->getVerifyCode(false);
$hash = $captcha->generateValidationHash($this->caseSensitive ? $code : strtolower($code));
@ -101,8 +112,6 @@ class CaptchaValidator extends Validator
$options['skipOnEmpty'] = 1;
}
ValidationAsset::register($view);
return 'yii.validation.captcha(value, messages, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ');';
return $options;
}
}

2
framework/console/Application.php

@ -180,7 +180,7 @@ class Application extends \yii\base\Application
$res = parent::runAction($route, $params);
return is_object($res) ? $res : (int)$res;
} catch (InvalidRouteException $e) {
throw new Exception("Unknown command \"$route\".", 0, $e);
throw new UnknownCommandException($route, $this, 0, $e);
}
}

11
framework/console/ErrorHandler.php

@ -29,7 +29,16 @@ class ErrorHandler extends \yii\base\ErrorHandler
*/
protected function renderException($exception)
{
if ($exception instanceof Exception && ($exception instanceof UserException || !YII_DEBUG)) {
if ($exception instanceof UnknownCommandException) {
// display message and suggest alternatives in case of unknown command
$message = $this->formatMessage($exception->getName() . ': ') . $exception->command;
$alternatives = $exception->getSuggestedAlternatives();
if (count($alternatives) === 1) {
$message .= "\n\nDid you mean \"" . reset($alternatives) . "\"?";
} elseif (count($alternatives) > 1) {
$message .= "\n\nDid you mean one of these?\n - " . implode("\n - ", $alternatives);
}
} elseif ($exception instanceof Exception && ($exception instanceof UserException || !YII_DEBUG)) {
$message = $this->formatMessage($exception->getName() . ': ') . $exception->getMessage();
} elseif (YII_DEBUG) {
if ($exception instanceof Exception) {

141
framework/console/UnknownCommandException.php

@ -0,0 +1,141 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\console;
use yii\console\controllers\HelpController;
/**
* UnknownCommandException represents an exception caused by incorrect usage of a console command.
*
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0.11
*/
class UnknownCommandException extends Exception
{
/**
* @var string the name of the command that could not be recognized.
*/
public $command;
/**
* @var Application
*/
protected $application;
/**
* Construct the exception.
*
* @param string $route the route of the command that could not be found.
* @param Application $application the console application instance involved.
* @param int $code the Exception code.
* @param \Exception $previous the previous exception used for the exception chaining.
*/
public function __construct($route, $application, $code = 0, \Exception $previous = null)
{
$this->command = $route;
$this->application = $application;
parent::__construct("Unknown command \"$route\".", $code, $previous);
}
/**
* @return string the user-friendly name of this exception
*/
public function getName()
{
return 'Unknown command';
}
/**
* Suggest alternative commands for [[$command]] based on string similarity.
*
* Alternatives are searched using the following steps:
*
* - suggest alternatives that begin with `$command`
* - find typos by calculating the Levenshtein distance between the unknown command and all
* available commands. The Levenshtein distance is defined as the minimal number of
* characters you have to replace, insert or delete to transform str1 into str2.
*
* @see http://php.net/manual/en/function.levenshtein.php
* @return array a list of suggested alternatives sorted by similarity.
*/
public function getSuggestedAlternatives()
{
$help = $this->application->createController('help');
if ($help === false) {
return [];
}
/** @var $helpController HelpController */
list($helpController, $actionID) = $help;
$availableActions = [];
$commands = $helpController->getCommands();
foreach ($commands as $command) {
$result = $this->application->createController($command);
if ($result === false) {
continue;
}
// add the command itself (default action)
$availableActions[] = $command;
// add all actions of this controller
/** @var $controller Controller */
list($controller, $actionID) = $result;
$actions = $helpController->getActions($controller);
if (!empty($actions)) {
$prefix = $controller->getUniqueId();
foreach ($actions as $action) {
$availableActions[] = $prefix . '/' . $action;
}
}
}
return $this->filterBySimilarity($availableActions, $this->command);
}
/**
* Find suggest alternative commands based on string similarity.
*
* Alternatives are searched using the following steps:
*
* - suggest alternatives that begin with `$command`
* - find typos by calculating the Levenshtein distance between the unknown command and all
* available commands. The Levenshtein distance is defined as the minimal number of
* characters you have to replace, insert or delete to transform str1 into str2.
*
* @see http://php.net/manual/en/function.levenshtein.php
* @param array $actions available command names.
* @param string $command the command to compare to.
* @return array a list of suggested alternatives sorted by similarity.
*/
private function filterBySimilarity($actions, $command)
{
$alternatives = [];
// suggest alternatives that begin with $command first
foreach ($actions as $action) {
if (strpos($action, $command) === 0) {
$alternatives[] = $action;
}
}
// calculate the Levenshtein distance between the unknown command and all available commands.
$distances = array_map(function($action) use ($command) {
$action = strlen($action) > 255 ? substr($action, 0, 255) : $action;
$command = strlen($command) > 255 ? substr($command, 0, 255) : $command;
return levenshtein($action, $command);
}, array_combine($actions, $actions));
// we assume a typo if the levensthein distance is no more than 3, i.e. 3 replacements needed
$relevantTypos = array_filter($distances, function($distance) {
return $distance <= 3;
});
asort($relevantTypos);
$alternatives = array_merge($alternatives, array_flip($relevantTypos));
return array_unique($alternatives);
}
}

104
framework/console/controllers/HelpController.php

@ -69,6 +69,110 @@ class HelpController extends Controller
}
/**
* List all available controllers and actions in machine readable format.
* This is used for shell completion.
* @since 2.0.11
*/
public function actionList()
{
$commands = $this->getCommandDescriptions();
foreach ($commands as $command => $description) {
$result = Yii::$app->createController($command);
if ($result === false || !($result[0] instanceof Controller)) {
continue;
}
/** @var $controller Controller */
list($controller, $actionID) = $result;
$actions = $this->getActions($controller);
if (!empty($actions)) {
$prefix = $controller->getUniqueId();
$this->stdout("$prefix\n");
foreach ($actions as $action) {
$this->stdout("$prefix/$action\n");
}
}
}
}
/**
* List all available options for the $action in machine readable format.
* This is used for shell completion.
*
* @param string $action route to action
* @since 2.0.11
*/
public function actionListActionOptions($action)
{
$result = Yii::$app->createController($action);
if ($result === false || !($result[0] instanceof Controller)) {
return;
}
/** @var Controller $controller */
list($controller, $actionID) = $result;
$action = $controller->createAction($actionID);
if ($action === null) {
return;
}
$arguments = $controller->getActionArgsHelp($action);
foreach ($arguments as $argument => $help) {
$description = str_replace("\n", '', addcslashes($help['comment'], ':')) ?: $argument;
$this->stdout($argument . ':' . $description . "\n");
}
$this->stdout("\n");
$options = $controller->getActionOptionsHelp($action);
foreach ($options as $argument => $help) {
$description = str_replace("\n", '', addcslashes($help['comment'], ':')) ?: $argument;
$this->stdout('--' . $argument . ':' . $description . "\n");
}
}
/**
* Displays usage information for $action
*
* @param string $action route to action
* @since 2.0.11
*/
public function actionUsage($action)
{
$result = Yii::$app->createController($action);
if ($result === false || !($result[0] instanceof Controller)) {
return;
}
/** @var Controller $controller */
list($controller, $actionID) = $result;
$action = $controller->createAction($actionID);
if ($action === null) {
return;
}
$scriptName = $this->getScriptName();
if ($action->id === $controller->defaultAction) {
$this->stdout($scriptName . ' ' . $this->ansiFormat($controller->getUniqueId(), Console::FG_YELLOW));
} else {
$this->stdout($scriptName . ' ' . $this->ansiFormat($action->getUniqueId(), Console::FG_YELLOW));
}
$args = $controller->getActionArgsHelp($action);
foreach ($args as $name => $arg) {
if ($arg['required']) {
$this->stdout(' <' . $name . '>', Console::FG_CYAN);
} else {
$this->stdout(' [' . $name . ']', Console::FG_CYAN);
}
}
$this->stdout("\n");
return;
}
/**
* Returns all available command names.
* @return array all available command names
*/

2
framework/console/controllers/MigrateController.php

@ -462,7 +462,7 @@ class MigrateController extends BaseMigrateController
protected function addDefaultPrimaryKey(&$fields)
{
foreach ($fields as $field) {
if ($field['decorators'] === 'primaryKey()' || $field['decorators'] === 'bigPrimaryKey()') {
if (false !== strripos($field['decorators'], 'primarykey()')) {
return;
}
}

18
framework/db/ActiveQuery.php

@ -155,7 +155,7 @@ class ActiveQuery extends Query implements ActiveQueryInterface
}
if (empty($this->select) && !empty($this->join)) {
list(, $alias) = $this->getQueryTableName($this);
list(, $alias) = $this->getTableNameAndAlias();
$this->select = ["$alias.*"];
}
@ -551,18 +551,18 @@ class ActiveQuery extends Query implements ActiveQueryInterface
/**
* Returns the table name and the table alias for [[modelClass]].
* @param ActiveQuery $query
* @return array the table name and the table alias.
* @internal
*/
private function getQueryTableName($query)
private function getTableNameAndAlias()
{
if (empty($query->from)) {
if (empty($this->from)) {
/* @var $modelClass ActiveRecord */
$modelClass = $query->modelClass;
$modelClass = $this->modelClass;
$tableName = $modelClass::tableName();
} else {
$tableName = '';
foreach ($query->from as $alias => $tableName) {
foreach ($this->from as $alias => $tableName) {
if (is_string($alias)) {
return [$tableName, $alias];
} else {
@ -603,8 +603,8 @@ class ActiveQuery extends Query implements ActiveQueryInterface
return;
}
list ($parentTable, $parentAlias) = $this->getQueryTableName($parent);
list ($childTable, $childAlias) = $this->getQueryTableName($child);
list ($parentTable, $parentAlias) = $parent->getTableNameAndAlias();
list ($childTable, $childAlias) = $child->getTableNameAndAlias();
if (!empty($child->link)) {
@ -778,7 +778,7 @@ class ActiveQuery extends Query implements ActiveQueryInterface
public function alias($alias)
{
if (empty($this->from) || count($this->from) < 2) {
list($tableName, ) = $this->getQueryTableName($this);
list($tableName, ) = $this->getTableNameAndAlias();
$this->from = [$alias => $tableName];
} else {
/* @var $modelClass ActiveRecord */

6
framework/db/ActiveRelationTrait.php

@ -464,6 +464,9 @@ trait ActiveRelationTrait
}
}
}
if (empty($values)) {
$this->emulateExecution();
}
} else {
// composite keys
@ -478,6 +481,9 @@ trait ActiveRelationTrait
$v[$attribute] = $model[$link];
}
$values[] = $v;
if (empty($v)) {
$this->emulateExecution();
}
}
}
$this->andWhere(['in', $attributes, array_unique($values, SORT_REGULAR)]);

6
framework/db/BaseActiveRecord.php

@ -1443,6 +1443,9 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
if (!empty($viaRelation->where)) {
$condition = ['and', $condition, $viaRelation->where];
}
if (!empty($viaRelation->on)) {
$condition = ['and', $condition, $viaRelation->on];
}
if (is_array($relation->via)) {
/* @var $viaClass ActiveRecordInterface */
if ($delete) {
@ -1477,6 +1480,9 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
if (!empty($relation->where)) {
$condition = ['and', $condition, $relation->where];
}
if (!empty($relation->on)) {
$condition = ['and', $condition, $relation->on];
}
if ($delete) {
$relatedModel::deleteAll($condition);
} else {

3
framework/db/ColumnSchemaBuilder.php

@ -258,7 +258,8 @@ class ColumnSchemaBuilder extends Object
}
/**
* Specify additional SQL to be appended to schema string.
* Specify additional SQL to be appended to column definition.
* Position modifiers will be appended after column definition in databases that support them.
* @param string $sql the SQL string to be appended.
* @return $this
* @since 2.0.9

126
framework/db/Query.php

@ -207,6 +207,9 @@ class Query extends Component implements QueryInterface
*/
public function all($db = null)
{
if ($this->emulateExecution) {
return [];
}
$rows = $this->createCommand($db)->queryAll();
return $this->populate($rows);
}
@ -244,6 +247,9 @@ class Query extends Component implements QueryInterface
*/
public function one($db = null)
{
if ($this->emulateExecution) {
return false;
}
return $this->createCommand($db)->queryOne();
}
@ -257,6 +263,9 @@ class Query extends Component implements QueryInterface
*/
public function scalar($db = null)
{
if ($this->emulateExecution) {
return null;
}
return $this->createCommand($db)->queryScalar();
}
@ -268,6 +277,10 @@ class Query extends Component implements QueryInterface
*/
public function column($db = null)
{
if ($this->emulateExecution) {
return [];
}
if ($this->indexBy === null) {
return $this->createCommand($db)->queryColumn();
}
@ -300,6 +313,9 @@ class Query extends Component implements QueryInterface
*/
public function count($q = '*', $db = null)
{
if ($this->emulateExecution) {
return 0;
}
return $this->queryScalar("COUNT($q)", $db);
}
@ -313,6 +329,9 @@ class Query extends Component implements QueryInterface
*/
public function sum($q, $db = null)
{
if ($this->emulateExecution) {
return 0;
}
return $this->queryScalar("SUM($q)", $db);
}
@ -326,6 +345,9 @@ class Query extends Component implements QueryInterface
*/
public function average($q, $db = null)
{
if ($this->emulateExecution) {
return 0;
}
return $this->queryScalar("AVG($q)", $db);
}
@ -363,6 +385,9 @@ class Query extends Component implements QueryInterface
*/
public function exists($db = null)
{
if ($this->emulateExecution) {
return false;
}
$command = $this->createCommand($db);
$params = $command->params;
$command->setSql($command->db->getQueryBuilder()->selectExists($command->getSql()));
@ -379,6 +404,10 @@ class Query extends Component implements QueryInterface
*/
protected function queryScalar($selectExpression, $db)
{
if ($this->emulateExecution) {
return null;
}
$select = $this->select;
$limit = $this->limit;
$offset = $this->offset;
@ -544,7 +573,7 @@ class Query extends Component implements QueryInterface
/**
* Adds an additional WHERE condition to the existing one.
* The new condition and the existing one will be joined using the 'AND' operator.
* The new condition and the existing one will be joined using the `AND` operator.
* @param string|array|Expression $condition the new WHERE condition. Please refer to [[where()]]
* on how to specify this parameter.
* @param array $params the parameters (name => value) to be bound to the query.
@ -556,6 +585,8 @@ class Query extends Component implements QueryInterface
{
if ($this->where === null) {
$this->where = $condition;
} elseif (is_array($this->where) && isset($this->where[0]) && strcasecmp($this->where[0], 'and') === 0) {
$this->where[] = $condition;
} else {
$this->where = ['and', $this->where, $condition];
}
@ -565,7 +596,7 @@ class Query extends Component implements QueryInterface
/**
* Adds an additional WHERE condition to the existing one.
* The new condition and the existing one will be joined using the 'OR' operator.
* The new condition and the existing one will be joined using the `OR` operator.
* @param string|array|Expression $condition the new WHERE condition. Please refer to [[where()]]
* on how to specify this parameter.
* @param array $params the parameters (name => value) to be bound to the query.
@ -590,7 +621,7 @@ class Query extends Component implements QueryInterface
* It adds an additional WHERE condition for the given field and determines the comparison operator
* based on the first few characters of the given value.
* The condition is added in the same way as in [[andFilterWhere]] so [[isEmpty()|empty values]] are ignored.
* The new condition and the existing one will be joined using the 'AND' operator.
* The new condition and the existing one will be joined using the `AND` operator.
*
* The comparison operator is intelligently determined based on the first few characters in the given value.
* In particular, it recognizes the following operators if they appear as the leading characters in the given value:
@ -803,7 +834,7 @@ class Query extends Component implements QueryInterface
/**
* Adds an additional HAVING condition to the existing one.
* The new condition and the existing one will be joined using the 'AND' operator.
* The new condition and the existing one will be joined using the `AND` operator.
* @param string|array|Expression $condition the new HAVING condition. Please refer to [[where()]]
* on how to specify this parameter.
* @param array $params the parameters (name => value) to be bound to the query.
@ -824,7 +855,7 @@ class Query extends Component implements QueryInterface
/**
* Adds an additional HAVING condition to the existing one.
* The new condition and the existing one will be joined using the 'OR' operator.
* The new condition and the existing one will be joined using the `OR` operator.
* @param string|array|Expression $condition the new HAVING condition. Please refer to [[where()]]
* on how to specify this parameter.
* @param array $params the parameters (name => value) to be bound to the query.
@ -844,6 +875,91 @@ class Query extends Component implements QueryInterface
}
/**
* Sets the HAVING part of the query but ignores [[isEmpty()|empty operands]].
*
* This method is similar to [[having()]]. The main difference is that this method will
* remove [[isEmpty()|empty query operands]]. As a result, this method is best suited
* for building query conditions based on filter values entered by users.
*
* The following code shows the difference between this method and [[having()]]:
*
* ```php
* // HAVING `age`=:age
* $query->filterHaving(['name' => null, 'age' => 20]);
* // HAVING `age`=:age
* $query->having(['age' => 20]);
* // HAVING `name` IS NULL AND `age`=:age
* $query->having(['name' => null, 'age' => 20]);
* ```
*
* Note that unlike [[having()]], you cannot pass binding parameters to this method.
*
* @param array $condition the conditions that should be put in the HAVING part.
* See [[having()]] on how to specify this parameter.
* @return $this the query object itself
* @see having()
* @see andFilterHaving()
* @see orFilterHaving()
* @since 2.0.11
*/
public function filterHaving(array $condition)
{
$condition = $this->filterCondition($condition);
if ($condition !== []) {
$this->having($condition);
}
return $this;
}
/**
* Adds an additional HAVING condition to the existing one but ignores [[isEmpty()|empty operands]].
* The new condition and the existing one will be joined using the `AND` operator.
*
* This method is similar to [[andHaving()]]. The main difference is that this method will
* remove [[isEmpty()|empty query operands]]. As a result, this method is best suited
* for building query conditions based on filter values entered by users.
*
* @param array $condition the new HAVING condition. Please refer to [[having()]]
* on how to specify this parameter.
* @return $this the query object itself
* @see filterHaving()
* @see orFilterHaving()
* @since 2.0.11
*/
public function andFilterHaving(array $condition)
{
$condition = $this->filterCondition($condition);
if ($condition !== []) {
$this->andHaving($condition);
}
return $this;
}
/**
* Adds an additional HAVING condition to the existing one but ignores [[isEmpty()|empty operands]].
* The new condition and the existing one will be joined using the `OR` operator.
*
* This method is similar to [[orHaving()]]. The main difference is that this method will
* remove [[isEmpty()|empty query operands]]. As a result, this method is best suited
* for building query conditions based on filter values entered by users.
*
* @param array $condition the new HAVING condition. Please refer to [[having()]]
* on how to specify this parameter.
* @return $this the query object itself
* @see filterHaving()
* @see andFilterHaving()
* @since 2.0.11
*/
public function orFilterHaving(array $condition)
{
$condition = $this->filterCondition($condition);
if ($condition !== []) {
$this->orHaving($condition);
}
return $this;
}
/**
* Appends a SQL statement using UNION operator.
* @param string|Query $sql the SQL statement to be appended using UNION
* @param bool $all TRUE if using UNION ALL and FALSE if using UNION

12
framework/db/QueryInterface.php

@ -252,4 +252,16 @@ interface QueryInterface
* @return $this the query object itself
*/
public function offset($offset);
/**
* Sets whether to emulate query execution, preventing any interaction with data storage.
* After this mode is enabled, methods, returning query results like [[one()]], [[all()]], [[exists()]]
* and so on, will return empty or false values.
* You should use this method in case your program logic indicates query should not return any results, like
* in case you set false where condition like `0=1`.
* @param boolean $value whether to prevent query execution.
* @return $this the query object itself.
* @since 2.0.11
*/
public function emulateExecution($value = true);
}

22
framework/db/QueryTrait.php

@ -50,6 +50,12 @@ trait QueryTrait
* row data. For more details, see [[indexBy()]]. This property is only used by [[QueryInterface::all()|all()]].
*/
public $indexBy;
/**
* @var boolean whether to emulate the actual query execution, returning empty or false results.
* @see emulateExecution()
* @since 2.0.11
*/
public $emulateExecution = false;
/**
@ -388,4 +394,20 @@ trait QueryTrait
$this->offset = $offset;
return $this;
}
/**
* Sets whether to emulate query execution, preventing any interaction with data storage.
* After this mode is enabled, methods, returning query results like [[one()]], [[all()]], [[exists()]]
* and so on, will return empty or false values.
* You should use this method in case your program logic indicates query should not return any results, like
* in case you set false where condition like `0=1`.
* @param boolean $value whether to prevent query execution.
* @return $this the query object itself.
* @since 2.0.11
*/
public function emulateExecution($value = true)
{
$this->emulateExecution = $value;
return $this;
}
}

6
framework/db/cubrid/ColumnSchemaBuilder.php

@ -58,13 +58,13 @@ class ColumnSchemaBuilder extends AbstractColumnSchemaBuilder
{
switch ($this->getTypeCategory()) {
case self::CATEGORY_PK:
$format = '{type}{check}{pos}{comment}{append}';
$format = '{type}{check}{comment}{append}{pos}';
break;
case self::CATEGORY_NUMERIC:
$format = '{type}{length}{unsigned}{notnull}{unique}{default}{check}{comment}{pos}{append}';
$format = '{type}{length}{unsigned}{notnull}{unique}{default}{check}{comment}{append}{pos}';
break;
default:
$format = '{type}{length}{notnull}{unique}{default}{check}{comment}{pos}{append}';
$format = '{type}{length}{notnull}{unique}{default}{check}{comment}{append}{pos}';
}
return $this->buildCompleteString($format);
}

6
framework/db/mysql/ColumnSchemaBuilder.php

@ -58,13 +58,13 @@ class ColumnSchemaBuilder extends AbstractColumnSchemaBuilder
{
switch ($this->getTypeCategory()) {
case self::CATEGORY_PK:
$format = '{type}{length}{check}{comment}{pos}{append}';
$format = '{type}{length}{check}{comment}{append}{pos}';
break;
case self::CATEGORY_NUMERIC:
$format = '{type}{length}{unsigned}{notnull}{unique}{default}{check}{comment}{pos}{append}';
$format = '{type}{length}{unsigned}{notnull}{unique}{default}{check}{comment}{append}{pos}';
break;
default:
$format = '{type}{length}{notnull}{unique}{default}{check}{comment}{pos}{append}';
$format = '{type}{length}{notnull}{unique}{default}{check}{comment}{append}{pos}';
}
return $this->buildCompleteString($format);
}

24
framework/db/oci/ColumnSchemaBuilder.php

@ -29,35 +29,17 @@ class ColumnSchemaBuilder extends AbstractColumnSchemaBuilder
/**
* @inheritdoc
*/
protected function buildAfterString()
{
return $this->after !== null ?
' AFTER ' . $this->db->quoteColumnName($this->after) :
'';
}
/**
* @inheritdoc
*/
protected function buildFirstString()
{
return $this->isFirst ? ' FIRST' : '';
}
/**
* @inheritdoc
*/
public function __toString()
{
switch ($this->getTypeCategory()) {
case self::CATEGORY_PK:
$format = '{type}{length}{check}{pos}{append}';
$format = '{type}{length}{check}{append}';
break;
case self::CATEGORY_NUMERIC:
$format = '{type}{length}{unsigned}{default}{notnull}{check}{pos}{append}';
$format = '{type}{length}{unsigned}{default}{notnull}{check}{append}';
break;
default:
$format = '{type}{length}{default}{notnull}{check}{pos}{append}';
$format = '{type}{length}{default}{notnull}{check}{append}';
}
return $this->buildCompleteString($format);
}

121
framework/db/oci/Schema.php

@ -121,17 +121,23 @@ class Schema extends \yii\db\Schema
protected function findColumns($table)
{
$sql = <<<SQL
SELECT a.column_name, a.data_type, a.data_precision, a.data_scale, a.data_length,
a.nullable, a.data_default,
com.comments as column_comment
SELECT
A.COLUMN_NAME,
A.DATA_TYPE,
A.DATA_PRECISION,
A.DATA_SCALE,
A.DATA_LENGTH,
A.NULLABLE,
A.DATA_DEFAULT,
COM.COMMENTS AS COLUMN_COMMENT
FROM ALL_TAB_COLUMNS A
inner join ALL_OBJECTS B ON b.owner = a.owner and ltrim(B.OBJECT_NAME) = ltrim(A.TABLE_NAME)
LEFT JOIN all_col_comments com ON (A.owner = com.owner AND A.table_name = com.table_name AND A.column_name = com.column_name)
INNER JOIN ALL_OBJECTS B ON B.OWNER = A.OWNER AND LTRIM(B.OBJECT_NAME) = LTRIM(A.TABLE_NAME)
LEFT JOIN ALL_COL_COMMENTS COM ON (A.OWNER = COM.OWNER AND A.TABLE_NAME = COM.TABLE_NAME AND A.COLUMN_NAME = COM.COLUMN_NAME)
WHERE
a.owner = :schemaName
and b.object_type IN ('TABLE', 'VIEW', 'MATERIALIZED VIEW')
and b.object_name = :tableName
ORDER by a.column_id
A.OWNER = :schemaName
AND B.OBJECT_TYPE IN ('TABLE', 'VIEW', 'MATERIALIZED VIEW')
AND B.OBJECT_NAME = :tableName
ORDER BY A.COLUMN_ID
SQL;
try {
@ -167,15 +173,17 @@ SQL;
protected function getTableSequenceName($tableName)
{
$seq_name_sql = <<<SQL
SELECT ud.referenced_name as sequence_name
FROM user_dependencies ud
JOIN user_triggers ut on (ut.trigger_name = ud.name)
WHERE ut.table_name = :tableName
AND ud.type='TRIGGER'
AND ud.referenced_type='SEQUENCE'
$sequenceNameSql = <<<SQL
SELECT
UD.REFERENCED_NAME AS SEQUENCE_NAME
FROM USER_DEPENDENCIES UD
JOIN USER_TRIGGERS UT ON (UT.TRIGGER_NAME = UD.NAME)
WHERE
UT.TABLE_NAME = :tableName
AND UD.TYPE = 'TRIGGER'
AND UD.REFERENCED_TYPE = 'SEQUENCE'
SQL;
$sequenceName = $this->db->createCommand($seq_name_sql, [':tableName' => $tableName])->queryScalar();
$sequenceName = $this->db->createCommand($sequenceNameSql, [':tableName' => $tableName])->queryScalar();
return $sequenceName === false ? null : $sequenceName;
}
@ -251,15 +259,23 @@ SQL;
protected function findConstraints($table)
{
$sql = <<<SQL
SELECT D.CONSTRAINT_NAME, D.CONSTRAINT_TYPE, C.COLUMN_NAME, C.POSITION, D.R_CONSTRAINT_NAME,
E.TABLE_NAME AS TABLE_REF, F.COLUMN_NAME AS COLUMN_REF,
C.TABLE_NAME
SELECT
/*+ PUSH_PRED(C) PUSH_PRED(D) PUSH_PRED(E) */
D.CONSTRAINT_NAME,
D.CONSTRAINT_TYPE,
C.COLUMN_NAME,
C.POSITION,
D.R_CONSTRAINT_NAME,
E.TABLE_NAME AS TABLE_REF,
F.COLUMN_NAME AS COLUMN_REF,
C.TABLE_NAME
FROM ALL_CONS_COLUMNS C
INNER JOIN ALL_CONSTRAINTS D ON D.OWNER = C.OWNER AND D.CONSTRAINT_NAME = C.CONSTRAINT_NAME
LEFT JOIN ALL_CONSTRAINTS E ON E.OWNER = D.R_OWNER AND E.CONSTRAINT_NAME = D.R_CONSTRAINT_NAME
LEFT JOIN ALL_CONS_COLUMNS F ON F.OWNER = E.OWNER AND F.CONSTRAINT_NAME = E.CONSTRAINT_NAME AND F.POSITION = C.POSITION
WHERE C.OWNER = :schemaName
AND C.TABLE_NAME = :tableName
INNER JOIN ALL_CONSTRAINTS D ON D.OWNER = C.OWNER AND D.CONSTRAINT_NAME = C.CONSTRAINT_NAME
LEFT JOIN ALL_CONSTRAINTS E ON E.OWNER = D.R_OWNER AND E.CONSTRAINT_NAME = D.R_CONSTRAINT_NAME
LEFT JOIN ALL_CONS_COLUMNS F ON F.OWNER = E.OWNER AND F.CONSTRAINT_NAME = E.CONSTRAINT_NAME AND F.POSITION = C.POSITION
WHERE
C.OWNER = :schemaName
AND C.TABLE_NAME = :tableName
ORDER BY D.CONSTRAINT_NAME, C.POSITION
SQL;
$command = $this->db->createCommand($sql, [
@ -306,13 +322,12 @@ SQL;
protected function findSchemaNames()
{
$sql = <<<SQL
SELECT username
FROM dba_users u
WHERE EXISTS (
SELECT 1
FROM dba_objects o
WHERE o.owner = u.username )
AND default_tablespace not in ('SYSTEM','SYSAUX')
SELECT
USERNAME
FROM DBA_USERS U
WHERE
EXISTS (SELECT 1 FROM DBA_OBJECTS O WHERE O.OWNER = U.USERNAME)
AND DEFAULT_TABLESPACE NOT IN ('SYSTEM','SYSAUX')
SQL;
return $this->db->createCommand($sql)->queryColumn();
}
@ -324,20 +339,29 @@ SQL;
{
if ($schema === '') {
$sql = <<<SQL
SELECT table_name FROM user_tables
SELECT
TABLE_NAME
FROM USER_TABLES
UNION ALL
SELECT view_name AS table_name FROM user_views
SELECT
VIEW_NAME AS TABLE_NAME
FROM USER_VIEWS
UNION ALL
SELECT mview_name AS table_name FROM user_mviews
ORDER BY table_name
SELECT
MVIEW_NAME AS TABLE_NAME
FROM USER_MVIEWS
ORDER BY TABLE_NAME
SQL;
$command = $this->db->createCommand($sql);
} else {
$sql = <<<SQL
SELECT object_name AS table_name
FROM all_objects
WHERE object_type IN ('TABLE', 'VIEW', 'MATERIALIZED VIEW') AND owner=:schema
ORDER BY object_name
SELECT
OBJECT_NAME AS TABLE_NAME
FROM ALL_OBJECTS
WHERE
OBJECT_TYPE IN ('TABLE', 'VIEW', 'MATERIALIZED VIEW')
AND OWNER = :schema
ORDER BY OBJECT_NAME
SQL;
$command = $this->db->createCommand($sql, [':schema' => $schema]);
}
@ -371,13 +395,16 @@ SQL;
public function findUniqueIndexes($table)
{
$query = <<<SQL
SELECT dic.INDEX_NAME, dic.COLUMN_NAME
FROM ALL_INDEXES di
INNER JOIN ALL_IND_COLUMNS dic ON di.TABLE_NAME = dic.TABLE_NAME AND di.INDEX_NAME = dic.INDEX_NAME
WHERE di.UNIQUENESS = 'UNIQUE'
AND dic.TABLE_OWNER = :schemaName
AND dic.TABLE_NAME = :tableName
ORDER BY dic.TABLE_NAME, dic.INDEX_NAME, dic.COLUMN_POSITION
SELECT
DIC.INDEX_NAME,
DIC.COLUMN_NAME
FROM ALL_INDEXES DI
INNER JOIN ALL_IND_COLUMNS DIC ON DI.TABLE_NAME = DIC.TABLE_NAME AND DI.INDEX_NAME = DIC.INDEX_NAME
WHERE
DI.UNIQUENESS = 'UNIQUE'
AND DIC.TABLE_OWNER = :schemaName
AND DIC.TABLE_NAME = :tableName
ORDER BY DIC.TABLE_NAME, DIC.INDEX_NAME, DIC.COLUMN_POSITION
SQL;
$result = [];
$command = $this->db->createCommand($query, [

80
framework/di/Container.php

@ -566,4 +566,84 @@ class Container extends Component
}
return $args;
}
/**
* Registers class definitions within this container.
*
* @param array $definitions array of definitions. There are two allowed formats of array.
* The first format:
* - key: class name, interface name or alias name. The key will be passed to the [[set()]] method
* as a first argument `$class`.
* - value: the definition associated with `$class`. Possible values are described in
* [[set()]] documentation for the `$definition` parameter. Will be passed to the [[set()]] method
* as the second argument `$definition`.
*
* Example:
* ```php
* $container->setDefinitions([
* 'yii\web\Request' => 'app\components\Request',
* 'yii\web\Response' => [
* 'class' => 'app\components\Response',
* 'format' => 'json'
* ],
* 'foo\Bar' => function () {
* $qux = new Qux;
* $foo = new Foo($qux);
* return new Bar($foo);
* }
* ]);
* ```
*
* The second format:
* - key: class name, interface name or alias name. The key will be passed to the [[set()]] method
* as a first argument `$class`.
* - value: array of two elements. The first element will be passed the [[set()]] method as the
* second argument `$definition`, the second one — as `$params`.
*
* Example:
* ```php
* $container->setDefinitions([
* 'foo\Bar' => [
* ['class' => 'app\Bar'],
* [Instance::of('baz')]
* ]
* ]);
* ```
*
* @see set() to know more about possible values of definitions
* @since 2.0.11
*/
public function setDefinitions(array $definitions)
{
foreach ($definitions as $class => $definition) {
if (count($definition) === 2 && array_values($definition) === $definition) {
$this->set($class, $definition[0], $definition[1]);
continue;
}
$this->set($class, $definition);
}
}
/**
* Registers class definitions as singletons within this container by calling [[setSingleton()]]
*
* @param array $singletons array of singleton definitions. See [[setDefinitions()]]
* for allowed formats of array.
*
* @see setDefinitions() for allowed formats of $singletons parameter
* @see setSingleton() to know more about possible values of definitions
* @since 2.0.11
*/
public function setSingletons(array $singletons)
{
foreach ($singletons as $class => $definition) {
if (count($definition) === 2 && array_values($definition) === $definition) {
$this->setSingleton($class, $definition[0], $definition[1]);
continue;
}
$this->setSingleton($class, $definition);
}
}
}

2
framework/di/ServiceLocator.php

@ -84,7 +84,7 @@ class ServiceLocator extends Component
*/
public function __isset($name)
{
if ($this->has($name, true)) {
if ($this->has($name)) {
return true;
} else {
return parent::__isset($name);

23
framework/filters/HostControl.php

@ -105,6 +105,14 @@ class HostControl extends ActionFilter
* host name, creation of absolute URL links, caching page parts and so on.
*/
public $denyCallback;
/**
* @var string|null fallback host info (e.g. `http://www.yiiframework.com`) used when [[\yii\web\Request::$hostInfo|Request::$hostInfo]] is invalid.
* This value will replace [[\yii\web\Request::$hostInfo|Request::$hostInfo]] before [[$denyCallback]] is called to make sure that
* an invalid host will not be used for further processing. You can set it to `null` to leave [[\yii\web\Request::$hostInfo|Request::$hostInfo]] untouched.
* Default value is empty string (this will result creating relative URLs instead of absolute).
* @see \yii\web\Request::getHostInfo()
*/
public $fallbackHostInfo = '';
/**
@ -132,6 +140,11 @@ class HostControl extends ActionFilter
}
}
// replace invalid host info to prevent using it in further processing
if ($this->fallbackHostInfo !== null) {
Yii::$app->getRequest()->setHostInfo($this->fallbackHostInfo);
}
if ($this->denyCallback !== null) {
call_user_func($this->denyCallback, $action);
} else {
@ -147,14 +160,20 @@ class HostControl extends ActionFilter
* You may override this method, creating your own deny access handler. While doing so, make sure you
* avoid usage of the current requested host name, creation of absolute URL links, caching page parts and so on.
* @param \yii\base\Action $action the action to be executed.
* @throws NotFoundHttpException
*/
protected function denyAccess($action)
{
$exception = new NotFoundHttpException(Yii::t('yii', 'Page not found.'));
// use regular error handling if $this->fallbackHostInfo was set
if (!empty(Yii::$app->getRequest()->hostName)) {
throw $exception;
}
$response = Yii::$app->getResponse();
$errorHandler = Yii::$app->getErrorHandler();
$exception = new NotFoundHttpException(Yii::t('yii', 'Page not found.'));
$response->setStatusCode($exception->statusCode, $exception->getMessage());
$response->data = $errorHandler->renderFile($errorHandler->errorView, ['exception' => $exception]);
$response->send();

191
framework/filters/PageCache.php

@ -8,8 +8,8 @@
namespace yii\filters;
use Yii;
use yii\base\ActionFilter;
use yii\base\Action;
use yii\base\ActionFilter;
use yii\caching\Cache;
use yii\caching\Dependency;
use yii\di\Instance;
@ -46,13 +46,14 @@ use yii\web\Response;
* ```
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @author Sergey Makinen <sergey@makinen.ru>
* @since 2.0
*/
class PageCache extends ActionFilter
{
/**
* @var bool whether the content being cached should be differentiated according to the route.
* A route consists of the requested controller ID and action ID. Defaults to true.
* A route consists of the requested controller ID and action ID. Defaults to `true`.
*/
public $varyByRoute = true;
/**
@ -64,7 +65,7 @@ class PageCache extends ActionFilter
public $cache = 'cache';
/**
* @var int number of seconds that the data can remain valid in cache.
* Use 0 to indicate that the cached data will never expire.
* Use `0` to indicate that the cached data will never expire.
*/
public $duration = 60;
/**
@ -123,6 +124,13 @@ class PageCache extends ActionFilter
* @since 2.0.4
*/
public $cacheHeaders = true;
/**
* @var array a list of placeholders for embedding dynamic contents. This property
* is used internally to implement the content caching feature. Do not modify it.
* @internal
* @since 2.0.11
*/
public $dynamicPlaceholders;
/**
@ -154,57 +162,70 @@ class PageCache extends ActionFilter
$this->dependency = Yii::createObject($this->dependency);
}
$properties = [];
foreach (['cache', 'duration', 'dependency', 'variations'] as $name) {
$properties[$name] = $this->$name;
}
$id = $this->varyByRoute ? $action->getUniqueId() : __CLASS__;
$response = Yii::$app->getResponse();
ob_start();
ob_implicit_flush(false);
if ($this->view->beginCache($id, $properties)) {
$data = $this->cache->get($this->calculateCacheKey());
if (!is_array($data) || !isset($data['cacheVersion']) || $data['cacheVersion'] !== 1) {
$this->view->cacheStack[] = $this;
ob_start();
ob_implicit_flush(false);
$response->on(Response::EVENT_AFTER_SEND, [$this, 'cacheResponse']);
Yii::trace('Valid page content is not found in the cache.', __METHOD__);
return true;
} else {
$data = $this->cache->get($this->calculateCacheKey());
if (is_array($data)) {
$this->restoreResponse($response, $data);
}
$response->content = ob_get_clean();
$this->restoreResponse($response, $data);
Yii::trace('Valid page content is found in the cache.', __METHOD__);
return false;
}
}
/**
* Restores response properties from the given data
* @param Response $response the response to be restored
* @param array $data the response property data
* This method is invoked right before the response caching is to be started.
* You may override this method to cancel caching by returning `false` or store an additional data
* in a cache entry by returning an array instead of `true`.
* @return bool|array whether to cache or not, return an array instead of `true` to store an additional data.
* @since 2.0.11
*/
public function beforeCacheResponse()
{
return true;
}
/**
* This method is invoked right after the response restoring is finished (but before the response is sent).
* You may override this method to do last-minute preparation before the response is sent.
* @param array|null $data an array of an additional data stored in a cache entry or `null`.
* @since 2.0.11
*/
public function afterRestoreResponse($data)
{
}
/**
* Restores response properties from the given data.
* @param Response $response the response to be restored.
* @param array $data the response property data.
* @since 2.0.3
*/
protected function restoreResponse($response, $data)
{
if (isset($data['format'])) {
$response->format = $data['format'];
}
if (isset($data['version'])) {
$response->version = $data['version'];
}
if (isset($data['statusCode'])) {
$response->statusCode = $data['statusCode'];
foreach (['format', 'version', 'statusCode', 'statusText', 'content'] as $name) {
$response->{$name} = $data[$name];
}
if (isset($data['statusText'])) {
$response->statusText = $data['statusText'];
}
if (isset($data['headers']) && is_array($data['headers'])) {
$headers = $response->getHeaders()->toArray();
$response->getHeaders()->fromArray(array_merge($data['headers'], $headers));
foreach (['headers', 'cookies'] as $name) {
if (isset($data[$name]) && is_array($data[$name])) {
$response->{$name}->fromArray(array_merge($data[$name], $response->{$name}->toArray()));
}
}
if (isset($data['cookies']) && is_array($data['cookies'])) {
$cookies = $response->getCookies()->toArray();
$response->getCookies()->fromArray(array_merge($data['cookies'], $cookies));
if (!empty($data['dynamicPlaceholders']) && is_array($data['dynamicPlaceholders'])) {
if (empty($this->view->cacheStack)) {
// outermost cache: replace placeholder with dynamic content
$response->content = $this->updateDynamicContent($response->content, $data['dynamicPlaceholders']);
}
foreach ($data['dynamicPlaceholders'] as $name => $statements) {
$this->view->addDynamicPlaceholder($name, $statements);
}
}
$this->afterRestoreResponse(isset($data['cacheData']) ? $data['cacheData'] : null);
}
/**
@ -213,43 +234,83 @@ class PageCache extends ActionFilter
*/
public function cacheResponse()
{
$this->view->endCache();
array_pop($this->view->cacheStack);
$beforeCacheResponseResult = $this->beforeCacheResponse();
if ($beforeCacheResponseResult === false) {
$content = ob_get_clean();
if (empty($this->view->cacheStack) && !empty($this->dynamicPlaceholders)) {
$content = $this->updateDynamicContent($content, $this->dynamicPlaceholders);
}
echo $content;
return;
}
$response = Yii::$app->getResponse();
$data = [
'format' => $response->format,
'version' => $response->version,
'statusCode' => $response->statusCode,
'statusText' => $response->statusText,
'cacheVersion' => 1,
'cacheData' => is_array($beforeCacheResponseResult) ? $beforeCacheResponseResult : null,
'content' => ob_get_clean()
];
if (!empty($this->cacheHeaders)) {
$headers = $response->getHeaders()->toArray();
if (is_array($this->cacheHeaders)) {
$filtered = [];
foreach ($this->cacheHeaders as $name) {
if ($data['content'] === false || $data['content'] === '') {
return;
}
$data['dynamicPlaceholders'] = $this->dynamicPlaceholders;
foreach (['format', 'version', 'statusCode', 'statusText'] as $name) {
$data[$name] = $response->{$name};
}
$this->insertResponseCollectionIntoData($response, 'headers', $data);
$this->insertResponseCollectionIntoData($response, 'cookies', $data);
$this->cache->set($this->calculateCacheKey(), $data, $this->duration, $this->dependency);
if (empty($this->view->cacheStack) && !empty($this->dynamicPlaceholders)) {
$data['content'] = $this->updateDynamicContent($data['content'], $this->dynamicPlaceholders);
}
echo $data['content'];
}
/**
* Inserts (or filters/ignores according to config) response headers/cookies into a cache data array.
* @param Response $response the response.
* @param string $collectionName currently it's `headers` or `cookies`.
* @param array $data the cache data.
*/
private function insertResponseCollectionIntoData(Response $response, $collectionName, array &$data)
{
$property = 'cache' . ucfirst($collectionName);
if ($this->{$property} === false) {
return;
}
$all = $response->{$collectionName}->toArray();
if (is_array($this->{$property})) {
$filtered = [];
foreach ($this->{$property} as $name) {
if ($collectionName === 'headers') {
$name = strtolower($name);
if (isset($headers[$name])) {
$filtered[$name] = $headers[$name];
}
}
$headers = $filtered;
}
$data['headers'] = $headers;
}
if (!empty($this->cacheCookies)) {
$cookies = $response->getCookies()->toArray();
if (is_array($this->cacheCookies)) {
$filtered = [];
foreach ($this->cacheCookies as $name) {
if (isset($cookies[$name])) {
$filtered[$name] = $cookies[$name];
}
if (isset($all[$name])) {
$filtered[$name] = $all[$name];
}
$cookies = $filtered;
}
$data['cookies'] = $cookies;
$all = $filtered;
}
$this->cache->set($this->calculateCacheKey(), $data, $this->duration, $this->dependency);
echo ob_get_clean();
$data[$collectionName] = $all;
}
/**
* Replaces placeholders in content by results of evaluated dynamic statements.
* @param string $content content to be parsed.
* @param array $placeholders placeholders and their values.
* @return string final content.
* @since 2.0.11
*/
protected function updateDynamicContent($content, $placeholders)
{
foreach ($placeholders as $name => $statements) {
$placeholders[$name] = $this->view->evaluateDynamicContent($statements);
}
return strtr($content, $placeholders);
}
/**

92
framework/grid/RadioButtonColumn.php

@ -0,0 +1,92 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\grid;
use Closure;
use yii\base\InvalidConfigException;
use yii\helpers\Html;
/**
* RadioButtonColumn displays a column of radio buttons in a grid view.
*
* To add a RadioButtonColumn to the [[GridView]], add it to the [[GridView::columns|columns]] configuration as follows:
*
* ```php
* 'columns' => [
* // ...
* [
* 'class' => 'yii\grid\RadioButtonColumn',
* 'radioOptions' => function ($model) {
* return [
* 'value' => $model['value'],
* 'checked' => $model['value'] == 2
* ];
* }
* ],
* ]
* ```
*
* @author Kirk Hansen <hanski07@luther.edu>
* @since 2.0.11
*/
class RadioButtonColumn extends Column
{
/**
* @var string the name of the input radio button input fields.
*/
public $name = 'radioButtonSelection';
/**
* @var array|\Closure the HTML attributes for the radio buttons. This can either be an array of
* attributes or an anonymous function ([[Closure]]) returning such an array.
*
* The signature of the function should be as follows: `function ($model, $key, $index, $column)`
* where `$model`, `$key`, and `$index` refer to the model, key and index of the row currently being rendered
* and `$column` is a reference to the [[RadioButtonColumn]] object.
*
* A function may be used to assign different attributes to different rows based on the data in that row.
* Specifically if you want to set a different value for the radio button you can use this option
* in the following way (in this example using the `name` attribute of the model):
* ```php
* 'radioOptions' => function ($model, $key, $index, $column) {
* return ['value' => $model->attribute];
* }
* ```
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
*/
public $radioOptions = [];
/**
* @inheritdoc
* @throws \yii\base\InvalidConfigException if [[name]] is not set.
*/
public function init()
{
parent::init();
if (empty($this->name)) {
throw new InvalidConfigException('The "name" property must be set.');
}
}
/**
* @inheritdoc
*/
protected function renderDataCellContent($model, $key, $index)
{
if ($this->radioOptions instanceof Closure) {
$options = call_user_func($this->radioOptions, $model, $key, $index, $this);
} else {
$options = $this->radioOptions;
if (!isset($options['value'])) {
$options['value'] = is_array($key) ? json_encode($key, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) : $key;
}
}
$checked = isset($options['checked']) ? $options['checked'] : false;
return Html::radio($this->name, $checked, $options);
}
}

2
framework/helpers/BaseFileHelper.php

@ -411,7 +411,7 @@ class BaseFileHelper
if (static::filterPath($path, $options)) {
if (is_file($path)) {
$list[] = $path;
} elseif (!isset($options['recursive']) || $options['recursive']) {
} elseif (is_dir($path) && (!isset($options['recursive']) || $options['recursive'])) {
$list = array_merge($list, static::findFiles($path, $options));
}
}

9
framework/helpers/BaseJson.php

@ -40,9 +40,14 @@ class BaseJson
/**
* Encodes the given value into a JSON string.
*
* The method enhances `json_encode()` by supporting JavaScript expressions.
* In particular, the method will not encode a JavaScript expression that is
* represented in terms of a [[JsExpression]] object.
*
* Note that data encoded as JSON must be UTF-8 encoded according to the JSON specification.
* You must ensure strings passed to this method have proper encoding before passing them.
*
* @param mixed $value the data to be encoded.
* @param int $options the encoding options. For more details please refer to
* <http://www.php.net/manual/en/function.json-encode.php>. Default is `JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE`.
@ -65,10 +70,14 @@ class BaseJson
/**
* Encodes the given value into a JSON string HTML-escaping entities so it is safe to be embedded in HTML code.
*
* The method enhances `json_encode()` by supporting JavaScript expressions.
* In particular, the method will not encode a JavaScript expression that is
* represented in terms of a [[JsExpression]] object.
*
* Note that data encoded as JSON must be UTF-8 encoded according to the JSON specification.
* You must ensure strings passed to this method have proper encoding before passing them.
*
* @param mixed $value the data to be encoded
* @return string the encoding result
* @since 2.0.4

2
framework/helpers/BaseUrl.php

@ -223,7 +223,7 @@ class BaseUrl
return $url;
}
if (($pos = strpos($url, ':')) === false || !ctype_alpha(substr($url, 0, $pos))) {
if (static::isRelative($url)) {
// turn relative URL into absolute
$url = static::getUrlManager()->getHostInfo() . '/' . ltrim($url, '/');
}

2
framework/messages/bg/yii.php

@ -71,7 +71,7 @@ return array (
'{attribute} is not a valid email address.' => 'Полето "{attribute}" съдържа невалиден email адрес.',
'{attribute} must be "{requiredValue}".' => 'Полето "{attribute}" трябва да съдържа "{requiredValue}".',
'{attribute} must be a number.' => 'Полето "{attribute}" съдържа невалиден номер.',
'{attribute} must be a string.' => 'Полето "{attribute}" трябва съдържа текст.',
'{attribute} must be a string.' => 'Полето "{attribute}" трябва да съдържа текст.',
'{attribute} must be an integer.' => 'Полето "{attribute}" трябва да съдържа цяло число.',
'{attribute} must be either "{true}" or "{false}".' => 'Полето "{attribute}" трябва да бъде "{true}" или "{false}".',
'{attribute} must be greater than "{compareValue}".' => 'Полето "{attribute}" трябва да е по-голямо от "{compareValue}".',

6
framework/messages/pl/yii.php

@ -89,9 +89,9 @@ return [
'{attribute} must not be an IPv4 address.' => '{attribute} nie może być adresem IPv4.',
'{attribute} must not be an IPv6 address.' => '{attribute} nie może być adresem IPv6.',
'{attribute} must not be equal to "{compareValueOrAttribute}".' => '{attribute} musi mieć wartość różną od "{compareValueOrAttribute}".',
'{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} powinien zawierać co najmniej {min, number} {min, plural, one{znak} few{znaki} many{znaków} other{znaku}}.',
'{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} powinien zawierać nie więcej niż {max, number} {max, plural, one{znak} few{znaki} many{znaków} other{znaku}}.',
'{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} powinien zawierać dokładnie {length, number} {length, plural, one{znak} few{znaki} many{znaków} other{znaku}}.',
'{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} musi zawierać co najmniej {min, number} {min, plural, one{znak} few{znaki} many{znaków} other{znaku}}.',
'{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} musi zawierać nie więcej niż {max, number} {max, plural, one{znak} few{znaki} many{znaków} other{znaku}}.',
'{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} musi zawierać dokładnie {length, number} {length, plural, one{znak} few{znaki} many{znaków} other{znaku}}.',
'{delta, plural, =1{1 day} other{# days}}' => '{delta, plural, =1{1 dzień} other{# dni} other{# dnia}}',
'{delta, plural, =1{1 hour} other{# hours}}' => '{delta, plural, =1{1 godzina} few{# godziny} many{# godzin} other{# godziny}}',
'{delta, plural, =1{1 minute} other{# minutes}}' => '{delta, plural, =1{1 minuta} few{# minuty} many{# minut} other{# minuty}}',

12
framework/rbac/BaseManager.php

@ -222,4 +222,16 @@ abstract class BaseManager extends Component implements ManagerInterface
throw new InvalidConfigException("Rule not found: {$item->ruleName}");
}
}
/**
* Checks whether array of $assignments is empty and [[defaultRoles]] property is empty as well
*
* @param Assignment[] $assignments array of user's assignments
* @return bool whether array of $assignments is empty and [[defaultRoles]] property is empty as well
* @since 2.0.11
*/
protected function hasNoAssignments(array $assignments)
{
return empty($assignments) && empty($this->defaultRoles);
}
}

5
framework/rbac/DbManager.php

@ -121,6 +121,11 @@ class DbManager extends BaseManager
public function checkAccess($userId, $permissionName, $params = [])
{
$assignments = $this->getAssignments($userId);
if ($this->hasNoAssignments($assignments)) {
return false;
}
$this->loadFromCache();
if ($this->items !== null) {
return $this->checkAccessFromCache($userId, $permissionName, $params, $assignments);

5
framework/rbac/PhpManager.php

@ -99,6 +99,11 @@ class PhpManager extends BaseManager
public function checkAccess($userId, $permissionName, $params = [])
{
$assignments = $this->getAssignments($userId);
if ($this->hasNoAssignments($assignments)) {
return false;
}
return $this->checkAccessRecursive($userId, $permissionName, $params, $assignments);
}

2
framework/rest/UrlRule.php

@ -203,7 +203,7 @@ class UrlRule extends CompositeUrlRule
$config['verb'] = $verbs;
$config['pattern'] = rtrim($prefix . '/' . strtr($pattern, $this->tokens), '/');
$config['route'] = $action;
if (!in_array('GET', $verbs)) {
if (!empty($verbs) && !in_array('GET', $verbs)) {
$config['mode'] = \yii\web\UrlRule::PARSING_ONLY;
}
$config['suffix'] = $this->suffix;

15
framework/validators/BooleanValidator.php

@ -70,6 +70,17 @@ class BooleanValidator extends Validator
*/
public function clientValidateAttribute($model, $attribute, $view)
{
ValidationAsset::register($view);
$options = $this->getClientOptions($model, $attribute);
return 'yii.validation.boolean(value, messages, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ');';
}
/**
* @inheritdoc
*/
public function getClientOptions($model, $attribute)
{
$options = [
'trueValue' => $this->trueValue,
'falseValue' => $this->falseValue,
@ -86,8 +97,6 @@ class BooleanValidator extends Validator
$options['strict'] = 1;
}
ValidationAsset::register($view);
return 'yii.validation.boolean(value, messages, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ');';
return $options;
}
}

15
framework/validators/CompareValidator.php

@ -225,6 +225,17 @@ class CompareValidator extends Validator
*/
public function clientValidateAttribute($model, $attribute, $view)
{
ValidationAsset::register($view);
$options = $this->getClientOptions($model, $attribute);
return 'yii.validation.compare(value, messages, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ');';
}
/**
* @inheritdoc
*/
public function getClientOptions($model, $attribute)
{
$options = [
'operator' => $this->operator,
'type' => $this->type,
@ -251,8 +262,6 @@ class CompareValidator extends Validator
'compareValueOrAttribute' => $compareValueOrAttribute,
], Yii::$app->language);
ValidationAsset::register($view);
return 'yii.validation.compare(value, messages, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ');';
return $options;
}
}

21
framework/validators/EmailValidator.php

@ -109,6 +109,20 @@ class EmailValidator extends Validator
*/
public function clientValidateAttribute($model, $attribute, $view)
{
ValidationAsset::register($view);
if ($this->enableIDN) {
PunycodeAsset::register($view);
}
$options = $this->getClientOptions($model, $attribute);
return 'yii.validation.email(value, messages, ' . Json::htmlEncode($options) . ');';
}
/**
* @inheritdoc
*/
public function getClientOptions($model, $attribute)
{
$options = [
'pattern' => new JsExpression($this->pattern),
'fullPattern' => new JsExpression($this->fullPattern),
@ -122,11 +136,6 @@ class EmailValidator extends Validator
$options['skipOnEmpty'] = 1;
}
ValidationAsset::register($view);
if ($this->enableIDN) {
PunycodeAsset::register($view);
}
return 'yii.validation.email(value, messages, ' . Json::htmlEncode($options) . ');';
return $options;
}
}

7
framework/validators/FileValidator.php

@ -384,12 +384,9 @@ class FileValidator extends Validator
}
/**
* Returns the client-side validation options.
* @param \yii\base\Model $model the model being validated
* @param string $attribute the attribute name being validated
* @return array the client-side validation options
* @inheritdoc
*/
protected function getClientOptions($model, $attribute)
public function getClientOptions($model, $attribute)
{
$label = $model->getAttributeLabel($attribute);

15
framework/validators/FilterValidator.php

@ -88,13 +88,22 @@ class FilterValidator extends Validator
return null;
}
ValidationAsset::register($view);
$options = $this->getClientOptions($model, $attribute);
return 'value = yii.validation.trim($form, attribute, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ');';
}
/**
* @inheritdoc
*/
public function getClientOptions($model, $attribute)
{
$options = [];
if ($this->skipOnEmpty) {
$options['skipOnEmpty'] = 1;
}
ValidationAsset::register($view);
return 'value = yii.validation.trim($form, attribute, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ');';
return $options;
}
}

2
framework/validators/ImageValidator.php

@ -172,7 +172,7 @@ class ImageValidator extends FileValidator
/**
* @inheritdoc
*/
protected function getClientOptions($model, $attribute)
public function getClientOptions($model, $attribute)
{
$options = parent::getClientOptions($model, $attribute);

23
framework/validators/InlineValidator.php

@ -13,11 +13,12 @@ namespace yii\validators;
* The validation method must have the following signature:
*
* ```php
* function foo($attribute, $params)
* function foo($attribute, $params, $validator)
* ```
*
* where `$attribute` refers to the name of the attribute being validated, while `$params`
* is an array representing the additional parameters supplied in the validation rule.
* where `$attribute` refers to the name of the attribute being validated, while `$params` is an array representing the
* additional parameters supplied in the validation rule. Parameter `$validator` refers to the related
* [[InlineValidator]] object and is available since version 2.0.11.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
@ -26,13 +27,15 @@ class InlineValidator extends Validator
{
/**
* @var string|\Closure an anonymous function or the name of a model class method that will be
* called to perform the actual validation. The signature of the method should be like the following,
* where `$attribute` is the name of the attribute to be validated, and `$params` contains the value
* of [[params]] that you specify when declaring the inline validation rule:
* called to perform the actual validation. The signature of the method should be like the following:
*
* ```php
* function foo($attribute, $params)
* function foo($attribute, $params, $validator)
* ```
*
* - `$attribute` is the name of the attribute to be validated;
* - `$params` contains the value of [[params]] that you specify when declaring the inline validation rule;
* - `$validator` is a reference to related [[InlineValidator]] object.
*/
public $method;
/**
@ -44,7 +47,7 @@ class InlineValidator extends Validator
* The signature of the method should be like the following:
*
* ```php
* function foo($attribute, $params)
* function foo($attribute, $params, $validator)
* {
* return "javascript";
* }
@ -66,7 +69,7 @@ class InlineValidator extends Validator
if (is_string($method)) {
$method = [$model, $method];
}
call_user_func($method, $attribute, $this->params);
call_user_func($method, $attribute, $this->params, $this);
}
/**
@ -80,7 +83,7 @@ class InlineValidator extends Validator
$method = [$model, $method];
}
return call_user_func($method, $attribute, $this->params);
return call_user_func($method, $attribute, $this->params, $this);
} else {
return null;
}

28
framework/validators/IpValidator.php

@ -369,12 +369,12 @@ class IpValidator extends Validator
$cidr = static::IPV6_ADDRESS_LENGTH;
}
if (!$this->ipv6) {
return [$this->ipv6NotAllowed, []];
}
if (!$this->validateIPv6($ip)) {
return [$this->message, []];
}
if (!$this->ipv6) {
return [$this->ipv6NotAllowed, []];
}
if ($this->expandIPv6) {
$ip = $this->expandIPv6($ip);
@ -388,13 +388,12 @@ class IpValidator extends Validator
$isCidrDefault = true;
$cidr = static::IPV4_ADDRESS_LENGTH;
}
if (!$this->ipv4) {
return [$this->ipv4NotAllowed, []];
}
if (!$this->validateIPv4($ip)) {
return [$this->message, []];
}
if (!$this->ipv4) {
return [$this->ipv4NotAllowed, []];
}
}
if (!$this->isAllowed($ip, $cidr)) {
@ -588,6 +587,17 @@ class IpValidator extends Validator
*/
public function clientValidateAttribute($model, $attribute, $view)
{
ValidationAsset::register($view);
$options = $this->getClientOptions($model, $attribute);
return 'yii.validation.ip(value, messages, ' . Json::htmlEncode($options) . ');';
}
/**
* @inheritdoc
*/
public function getClientOptions($model, $attribute)
{
$messages = [
'ipv6NotAllowed' => $this->ipv6NotAllowed,
'ipv4NotAllowed' => $this->ipv4NotAllowed,
@ -615,8 +625,6 @@ class IpValidator extends Validator
$options['skipOnEmpty'] = 1;
}
ValidationAsset::register($view);
return 'yii.validation.ip(value, messages, ' . Json::htmlEncode($options) . ');';
return $options;
}
}

15
framework/validators/NumberValidator.php

@ -142,6 +142,17 @@ class NumberValidator extends Validator
*/
public function clientValidateAttribute($model, $attribute, $view)
{
ValidationAsset::register($view);
$options = $this->getClientOptions($model, $attribute);
return 'yii.validation.number(value, messages, ' . Json::htmlEncode($options) . ');';
}
/**
* @inheritdoc
*/
public function getClientOptions($model, $attribute)
{
$label = $model->getAttributeLabel($attribute);
$options = [
@ -173,8 +184,6 @@ class NumberValidator extends Validator
$options['skipOnEmpty'] = 1;
}
ValidationAsset::register($view);
return 'yii.validation.number(value, messages, ' . Json::htmlEncode($options) . ');';
return $options;
}
}

15
framework/validators/RangeValidator.php

@ -108,6 +108,17 @@ class RangeValidator extends Validator
$this->range = call_user_func($this->range, $model, $attribute);
}
ValidationAsset::register($view);
$options = $this->getClientOptions($model, $attribute);
return 'yii.validation.range(value, messages, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ');';
}
/**
* @inheritdoc
*/
public function getClientOptions($model, $attribute)
{
$range = [];
foreach ($this->range as $value) {
$range[] = (string) $value;
@ -126,8 +137,6 @@ class RangeValidator extends Validator
$options['allowArray'] = 1;
}
ValidationAsset::register($view);
return 'yii.validation.range(value, messages, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ');';
return $options;
}
}

15
framework/validators/RegularExpressionValidator.php

@ -65,6 +65,17 @@ class RegularExpressionValidator extends Validator
*/
public function clientValidateAttribute($model, $attribute, $view)
{
ValidationAsset::register($view);
$options = $this->getClientOptions($model, $attribute);
return 'yii.validation.regularExpression(value, messages, ' . Json::htmlEncode($options) . ');';
}
/**
* @inheritdoc
*/
public function getClientOptions($model, $attribute)
{
$pattern = Html::escapeJsRegularExpression($this->pattern);
$options = [
@ -78,8 +89,6 @@ class RegularExpressionValidator extends Validator
$options['skipOnEmpty'] = 1;
}
ValidationAsset::register($view);
return 'yii.validation.regularExpression(value, messages, ' . Json::htmlEncode($options) . ');';
return $options;
}
}

15
framework/validators/RequiredValidator.php

@ -88,6 +88,17 @@ class RequiredValidator extends Validator
*/
public function clientValidateAttribute($model, $attribute, $view)
{
ValidationAsset::register($view);
$options = $this->getClientOptions($model, $attribute);
return 'yii.validation.required(value, messages, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ');';
}
/**
* @inheritdoc
*/
public function getClientOptions($model, $attribute)
{
$options = [];
if ($this->requiredValue !== null) {
$options['message'] = Yii::$app->getI18n()->format($this->message, [
@ -105,8 +116,6 @@ class RequiredValidator extends Validator
'attribute' => $model->getAttributeLabel($attribute),
], Yii::$app->language);
ValidationAsset::register($view);
return 'yii.validation.required(value, messages, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ');';
return $options;
}
}

12
framework/validators/StringValidator.php

@ -153,6 +153,14 @@ class StringValidator extends Validator
*/
public function clientValidateAttribute($model, $attribute, $view)
{
ValidationAsset::register($view);
$options = $this->getClientOptions($model, $attribute);
return 'yii.validation.string(value, messages, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ');';
}
public function getClientOptions($model, $attribute)
{
$label = $model->getAttributeLabel($attribute);
$options = [
@ -186,8 +194,6 @@ class StringValidator extends Validator
$options['skipOnEmpty'] = 1;
}
ValidationAsset::register($view);
return 'yii.validation.string(value, messages, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ');';
return $options;
}
}

108
framework/validators/UniqueValidator.php

@ -8,7 +8,11 @@
namespace yii\validators;
use Yii;
use yii\base\Model;
use yii\db\ActiveQuery;
use yii\db\ActiveQueryInterface;
use yii\db\ActiveRecordInterface;
use yii\db\Query;
use yii\helpers\Inflector;
/**
@ -44,7 +48,7 @@ class UniqueValidator extends Validator
*/
public $targetClass;
/**
* @var string|array the name of the ActiveRecord attribute that should be used to
* @var string|array the name of the [[\yii\db\ActiveRecord|ActiveRecord]] attribute that should be used to
* validate the uniqueness of the current attribute value. If not set, it will use the name
* of the attribute currently being validated. You may use an array to validate the uniqueness
* of multiple columns at the same time. The array values are the attributes that will be
@ -111,32 +115,38 @@ class UniqueValidator extends Validator
/* @var $targetClass ActiveRecordInterface */
$targetClass = $this->targetClass === null ? get_class($model) : $this->targetClass;
$targetAttribute = $this->targetAttribute === null ? $attribute : $this->targetAttribute;
$conditions = $this->prepareConditions($targetAttribute, $model, $attribute);
if (is_array($targetAttribute)) {
$params = [];
foreach ($targetAttribute as $k => $v) {
$params[$v] = is_int($k) ? $model->$v : $model->$k;
}
} else {
$params = [$targetAttribute => $model->$attribute];
}
foreach ($params as $value) {
foreach ($conditions as $value) {
if (is_array($value)) {
$this->addError($model, $attribute, Yii::t('yii', '{attribute} is invalid.'));
return;
}
}
$query = $targetClass::find();
$query->andWhere($params);
if ($this->filter instanceof \Closure) {
call_user_func($this->filter, $query);
} elseif ($this->filter !== null) {
$query->andWhere($this->filter);
if ($this->modelExists($targetClass, $conditions, $model)) {
if (count($targetAttribute) > 1) {
$this->addComboNotUniqueError($model, $attribute);
} else {
$this->addError($model, $attribute, $this->message);
}
}
}
/**
* Checks whether the $model exists in the database.
*
* @param string $targetClass the name of the ActiveRecord class that should be used to validate the uniqueness
* of the current attribute value.
* @param array $conditions conditions, compatible with [[\yii\db\Query::where()|Query::where()]] key-value format.
* @param Model $model the data model to be validated
*
* @return bool whether the model already exists
*/
private function modelExists($targetClass, $conditions, $model)
{
/** @var ActiveRecordInterface $targetClass $query */
$query = $this->prepareQuery($targetClass, $conditions);
if (!$model instanceof ActiveRecordInterface || $model->getIsNewRecord() || $model->className() !== $targetClass::className()) {
// if current $model isn't in the database yet then it's OK just to call exists()
@ -144,11 +154,11 @@ class UniqueValidator extends Validator
$exists = $query->exists();
} else {
// if current $model is in the database already we can't use exists()
/* @var $models ActiveRecordInterface[] */
/** @var $models ActiveRecordInterface[] */
$models = $query->select($targetClass::primaryKey())->limit(2)->all();
$n = count($models);
if ($n === 1) {
$keys = array_keys($params);
$keys = array_keys($conditions);
$pks = $targetClass::primaryKey();
sort($keys);
sort($pks);
@ -164,13 +174,59 @@ class UniqueValidator extends Validator
}
}
if ($exists) {
if (count($targetAttribute) > 1) {
$this->addComboNotUniqueError($model, $attribute);
} else {
$this->addError($model, $attribute, $this->message);
return $exists;
}
/**
* Prepares a query by applying filtering conditions defined in $conditions method property
* and [[filter]] class property.
*
* @param ActiveRecordInterface $targetClass the name of the ActiveRecord class that should be used to validate
* the uniqueness of the current attribute value.
* @param array $conditions conditions, compatible with [[\yii\db\Query::where()|Query::where()]] key-value format
*
* @return ActiveQueryInterface|ActiveQuery
*/
private function prepareQuery($targetClass, $conditions)
{
$query = $targetClass::find();
$query->andWhere($conditions);
if ($this->filter instanceof \Closure) {
call_user_func($this->filter, $query);
} elseif ($this->filter !== null) {
$query->andWhere($this->filter);
}
return $query;
}
/**
* Processes attributes' relations described in $targetAttribute parameter into conditions, compatible with
* [[\yii\db\Query::where()|Query::where()]] key-value format.
*
* @param string|array $targetAttribute the name of the [[\yii\db\ActiveRecord|ActiveRecord]] attribute that
* should be used to validate the uniqueness of the current attribute value. You may use an array to validate
* the uniqueness of multiple columns at the same time. The array values are the attributes that will be
* used to validate the uniqueness, while the array keys are the attributes whose values are to be validated.
* If the key and the value are the same, you can just specify the value.
* @param Model $model the data model to be validated
* @param string $attribute the name of the attribute to be validated in the $model
* @return array conditions, compatible with [[\yii\db\Query::where()|Query::where()]] key-value format.
*/
private function prepareConditions($targetAttribute, $model, $attribute)
{
if (is_array($targetAttribute)) {
$conditions = [];
foreach ($targetAttribute as $k => $v) {
$conditions[$v] = is_int($k) ? $model->$v : $model->$k;
}
} else {
$conditions = [$targetAttribute => $model->$attribute];
}
return $conditions;
}
/**

21
framework/validators/UrlValidator.php

@ -113,6 +113,20 @@ class UrlValidator extends Validator
*/
public function clientValidateAttribute($model, $attribute, $view)
{
ValidationAsset::register($view);
if ($this->enableIDN) {
PunycodeAsset::register($view);
}
$options = $this->getClientOptions($model, $attribute);
return 'yii.validation.url(value, messages, ' . Json::htmlEncode($options) . ');';
}
/**
* @inheritdoc
*/
public function getClientOptions($model, $attribute)
{
if (strpos($this->pattern, '{schemes}') !== false) {
$pattern = str_replace('{schemes}', '(' . implode('|', $this->validSchemes) . ')', $this->pattern);
} else {
@ -133,11 +147,6 @@ class UrlValidator extends Validator
$options['defaultScheme'] = $this->defaultScheme;
}
ValidationAsset::register($view);
if ($this->enableIDN) {
PunycodeAsset::register($view);
}
return 'yii.validation.url(value, messages, ' . Json::htmlEncode($options) . ');';
return $options;
}
}

17
framework/validators/Validator.php

@ -328,6 +328,8 @@ class Validator extends Component
/**
* Returns the JavaScript needed for performing client-side validation.
*
* Calls [[getClientOptions()]] to generate options array for client-side validation.
*
* You may override this method to return the JavaScript validation code if
* the validator can support client-side validation.
*
@ -353,6 +355,7 @@ class Validator extends Component
* containing a model form with this validator applied.
* @return string the client-side validation script. Null if the validator does not support
* client-side validation.
* @see getClientOptions()
* @see \yii\widgets\ActiveForm::enableClientValidation
*/
public function clientValidateAttribute($model, $attribute, $view)
@ -361,6 +364,20 @@ class Validator extends Component
}
/**
* Returns the client-side validation options.
* This method is usually called from [[clientValidateAttribute()]]. You may override this method to modify options
* that will be passed to the client-side validation.
* @param \yii\base\Model $model the model being validated
* @param string $attribute the attribute name being validated
* @return array the client-side validation options
* @since 2.0.11
*/
public function getClientOptions($model, $attribute)
{
return [];
}
/**
* Returns a value indicating whether the validator is active for the given scenario and attribute.
*
* A validator is active if

35
framework/web/RangeNotSatisfiableHttpException.php

@ -0,0 +1,35 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\web;
/**
* RangeNotSatisfiableHttpException represents an exception caused by an improper request of the end-user.
* This exception thrown when the requested range is not satisfiable: the client asked for a portion of
* the file (byte serving), but the server cannot supply that portion. For example, if the client asked for
* a part of the file that lies beyond the end of the file.
*
* Throwing an RangeNotSatisfiableHttpException like in the following example will result in the error page
* with error 416 to be displayed.
*
* @author Zalatov Alexander <CaHbKa.Z@gmail.com>
*
* @since 2.0.11
*/
class RangeNotSatisfiableHttpException extends HttpException
{
/**
* Constructor.
* @param string $message error message
* @param integer $code error code
* @param \Exception $previous The previous exception used for the exception chaining.
*/
public function __construct($message = null, $code = 0, \Exception $previous = null)
{
parent::__construct(416, $message, $code, $previous);
}
}

8
framework/web/Response.php

@ -482,7 +482,7 @@ class Response extends \yii\base\Response
* meaning a download dialog will pop up.
*
* @return $this the response object itself
* @throws HttpException if the requested range is not satisfiable
* @throws RangeNotSatisfiableHttpException if the requested range is not satisfiable
* @see sendFile() for an example implementation.
*/
public function sendContentAsFile($content, $attachmentName, $options = [])
@ -494,7 +494,7 @@ class Response extends \yii\base\Response
if ($range === false) {
$headers->set('Content-Range', "bytes */$contentLength");
throw new HttpException(416, 'Requested range not satisfiable');
throw new RangeNotSatisfiableHttpException();
}
list($begin, $end) = $range;
@ -533,7 +533,7 @@ class Response extends \yii\base\Response
* This option is available since version 2.0.4.
*
* @return $this the response object itself
* @throws HttpException if the requested range cannot be satisfied.
* @throws RangeNotSatisfiableHttpException if the requested range is not satisfiable
* @see sendFile() for an example implementation.
*/
public function sendStreamAsFile($handle, $attachmentName, $options = [])
@ -549,7 +549,7 @@ class Response extends \yii\base\Response
$range = $this->getHttpRange($fileSize);
if ($range === false) {
$headers->set('Content-Range', "bytes */$fileSize");
throw new HttpException(416, 'Requested range not satisfiable');
throw new RangeNotSatisfiableHttpException();
}
list($begin, $end) = $range;

72
framework/widgets/ActiveField.php

@ -147,6 +147,11 @@ class ActiveField extends Component
* it is maintained by various methods of this class.
*/
public $parts = [];
/**
* @var bool adds aria HTML attributes `aria-required` and `aria-invalid` for inputs
* @since 2.0.11
*/
public $addAriaAttributes = true;
/**
* @var string this property holds a custom input id if it was set using [[inputOptions]] or in one of the
@ -158,7 +163,6 @@ class ActiveField extends Component
*/
private $_skipLabelFor = false;
/**
* PHP magic method that returns the string representation of this object.
* @return string the string representation of this object.
@ -358,6 +362,7 @@ class ActiveField extends Component
public function input($type, $options = [])
{
$options = array_merge($this->inputOptions, $options);
$this->addAriaAttributes($options);
$this->adjustLabelFor($options);
$this->parts['{input}'] = Html::activeInput($type, $this->model, $this->attribute, $options);
@ -384,6 +389,7 @@ class ActiveField extends Component
public function textInput($options = [])
{
$options = array_merge($this->inputOptions, $options);
$this->addAriaAttributes($options);
$this->adjustLabelFor($options);
$this->parts['{input}'] = Html::activeTextInput($this->model, $this->attribute, $options);
@ -429,6 +435,7 @@ class ActiveField extends Component
public function passwordInput($options = [])
{
$options = array_merge($this->inputOptions, $options);
$this->addAriaAttributes($options);
$this->adjustLabelFor($options);
$this->parts['{input}'] = Html::activePasswordInput($this->model, $this->attribute, $options);
@ -456,6 +463,7 @@ class ActiveField extends Component
if (!isset($this->form->options['enctype'])) {
$this->form->options['enctype'] = 'multipart/form-data';
}
$this->addAriaAttributes($options);
$this->adjustLabelFor($options);
$this->parts['{input}'] = Html::activeFileInput($this->model, $this->attribute, $options);
@ -475,6 +483,7 @@ class ActiveField extends Component
public function textarea($options = [])
{
$options = array_merge($this->inputOptions, $options);
$this->addAriaAttributes($options);
$this->adjustLabelFor($options);
$this->parts['{input}'] = Html::activeTextarea($this->model, $this->attribute, $options);
@ -522,6 +531,7 @@ class ActiveField extends Component
$options['label'] = null;
$this->parts['{input}'] = Html::activeRadio($this->model, $this->attribute, $options);
}
$this->addAriaAttributes($options);
$this->adjustLabelFor($options);
return $this;
@ -568,6 +578,7 @@ class ActiveField extends Component
$options['label'] = null;
$this->parts['{input}'] = Html::activeCheckbox($this->model, $this->attribute, $options);
}
$this->addAriaAttributes($options);
$this->adjustLabelFor($options);
return $this;
@ -595,6 +606,7 @@ class ActiveField extends Component
public function dropDownList($items, $options = [])
{
$options = array_merge($this->inputOptions, $options);
$this->addAriaAttributes($options);
$this->adjustLabelFor($options);
$this->parts['{input}'] = Html::activeDropDownList($this->model, $this->attribute, $items, $options);
@ -623,6 +635,7 @@ class ActiveField extends Component
public function listBox($items, $options = [])
{
$options = array_merge($this->inputOptions, $options);
$this->addAriaAttributes($options);
$this->adjustLabelFor($options);
$this->parts['{input}'] = Html::activeListBox($this->model, $this->attribute, $items, $options);
@ -642,6 +655,7 @@ class ActiveField extends Component
*/
public function checkboxList($items, $options = [])
{
$this->addAriaAttributes($options);
$this->adjustLabelFor($options);
$this->_skipLabelFor = true;
$this->parts['{input}'] = Html::activeCheckboxList($this->model, $this->attribute, $items, $options);
@ -661,6 +675,7 @@ class ActiveField extends Component
*/
public function radioList($items, $options = [])
{
$this->addAriaAttributes($options);
$this->adjustLabelFor($options);
$this->_skipLabelFor = true;
$this->parts['{input}'] = Html::activeRadioList($this->model, $this->attribute, $items, $options);
@ -699,6 +714,7 @@ class ActiveField extends Component
$config['attribute'] = $this->attribute;
$config['view'] = $this->form->getView();
if (isset($config['options']) && isset(class_parents($class)['yii\widgets\InputWidget'])) {
$this->addAriaAttributes($config['options']);
$this->adjustLabelFor($config['options']);
}
$this->parts['{input}'] = $class::widget($config);
@ -732,10 +748,10 @@ class ActiveField extends Component
return [];
}
$enableClientValidation = $this->enableClientValidation || $this->enableClientValidation === null && $this->form->enableClientValidation;
$enableAjaxValidation = $this->enableAjaxValidation || $this->enableAjaxValidation === null && $this->form->enableAjaxValidation;
$clientValidation = $this->isClientValidationEnabled();
$ajaxValidation = $this->isAjaxValidationEnabled();
if ($enableClientValidation) {
if ($clientValidation) {
$validators = [];
foreach ($this->model->getActiveValidators($attribute) as $validator) {
/* @var $validator \yii\validators\Validator */
@ -749,7 +765,7 @@ class ActiveField extends Component
}
}
if (!$enableAjaxValidation && (!$enableClientValidation || empty($validators))) {
if (!$ajaxValidation && (!$clientValidation || empty($validators))) {
return [];
}
@ -770,7 +786,7 @@ class ActiveField extends Component
}
$options['encodeError'] = !isset($this->errorOptions['encode']) || $this->errorOptions['encode'];
if ($enableAjaxValidation) {
if ($ajaxValidation) {
$options['enableAjaxValidation'] = true;
}
foreach (['validateOnChange', 'validateOnBlur', 'validateOnType', 'validationDelay'] as $name) {
@ -781,6 +797,10 @@ class ActiveField extends Component
$options['validate'] = new JsExpression("function (attribute, value, messages, deferred, \$form) {" . implode('', $validators) . '}');
}
if ($this->addAriaAttributes === false) {
$options['updateAriaInvalid'] = false;
}
// only get the options that are different from the default ones (set in yii.activeForm.js)
return array_diff_assoc($options, [
'validateOnChange' => true,
@ -789,10 +809,31 @@ class ActiveField extends Component
'validationDelay' => 500,
'encodeError' => true,
'error' => '.help-block',
'updateAriaInvalid' => true,
]);
}
/**
* Checks if client validation enabled for the field
* @return bool
* @since 2.0.11
*/
protected function isClientValidationEnabled()
{
return $this->enableClientValidation || $this->enableClientValidation === null && $this->form->enableClientValidation;
}
/**
* Checks if ajax validation enabled for the field
* @return bool
* @since 2.0.11
*/
protected function isAjaxValidationEnabled()
{
return $this->enableAjaxValidation || $this->enableAjaxValidation === null && $this->form->enableAjaxValidation;
}
/**
* Returns the HTML `id` of the input element of this form field.
* @return string the input id.
* @since 2.0.7
@ -801,4 +842,23 @@ class ActiveField extends Component
{
return $this->_inputId ?: Html::getInputId($this->model, $this->attribute);
}
/**
* Adds aria attributes to the input options
* @param $options array input options
* @since 2.0.11
*/
protected function addAriaAttributes(&$options)
{
if ($this->addAriaAttributes) {
if (!isset($options['aria-required']) && $this->model->isAttributeRequired($this->attribute)) {
$options['aria-required'] = 'true';
}
if (!isset($options['aria-invalid'])) {
if ($this->model->hasErrors($this->attribute)) {
$options['aria-invalid'] = 'true';
}
}
}
}
}

4
framework/widgets/BaseListView.php

@ -71,7 +71,9 @@ abstract class BaseListView extends Widget
*/
public $summaryOptions = ['class' => 'summary'];
/**
* @var bool whether to show the list view if [[dataProvider]] returns no data.
* @var bool whether to show an empty list view if [[dataProvider]] returns no data.
* The default value is false which displays an element according to the `emptyText`
* and `emptyTextOptions` properties.
*/
public $showOnEmpty = false;
/**

9
framework/widgets/Pjax.php

@ -97,6 +97,15 @@ class Pjax extends Widget
* [pjax project page](https://github.com/yiisoft/jquery-pjax) for available options.
*/
public $clientOptions;
/**
* @inheritdoc
* @internal
*/
public static $counter = 0;
/**
* @inheritdoc
*/
public static $autoIdPrefix = 'p';
/**

35
package.json

@ -0,0 +1,35 @@
{
"name": "yii2",
"description": "a modern PHP framework designed for professional Web development",
"main": "index.js",
"directories": {
"doc": "docs",
"test": "tests/js/tests"
},
"dependencies": {},
"devDependencies": {
"chai": "^3.5.0",
"jsdom": "^9.8.3",
"leche": "^2.1.2",
"mocha": "^3.1.2",
"mocha-jsdom": "^1.1.0",
"sinon": "^1.17.6"
},
"scripts": {
"test": "./node_modules/mocha/bin/mocha tests/js/tests/*.test.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/yiisoft/yii2.git"
},
"keywords": [
"php",
"framework"
],
"author": "",
"license": "BSD-3-Clause",
"bugs": {
"url": "https://github.com/yiisoft/yii2/issues"
},
"homepage": "https://github.com/yiisoft/yii2"
}

7
tests/data/ar/Order.php

@ -178,6 +178,13 @@ class Order extends ActiveRecord
->viaTable('order_item', ['order_id' => 'id']);
}
public function getLimitedItems()
{
return $this->hasMany(Item::className(), ['id' => 'item_id'])
->onCondition(['item.id' => [3, 5]])
->via('orderItems');
}
public function beforeSave($insert)
{
if (parent::beforeSave($insert)) {

40
tests/data/codeclimate/phpmd_ruleset.xml

@ -0,0 +1,40 @@
<?xml version="1.0"?>
<ruleset name="PHPMD rule set for Yii 2" xmlns="http://pmd.sf.net/ruleset/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0 http://pmd.sf.net/ruleset_xml_schema.xsd"
xsi:noNamespaceSchemaLocation="http://pmd.sf.net/ruleset_xml_schema.xsd">
<description>Custom PHPMD settings for naming, cleancode and controversial rulesets</description>
<rule ref="rulesets/naming.xml/ConstructorWithNameAsEnclosingClass" />
<rule ref="rulesets/naming.xml/ConstantNamingConventions" />
<!-- Long variable names can help with better understanding so we increase the limit a bit -->
<rule ref="rulesets/naming.xml/LongVariable">
<properties>
<property name="maximum" value="25" />
</properties>
</rule>
<!-- method names like up(), gc(), ... are okay. -->
<rule ref="rulesets/naming.xml/ShortMethodName">
<properties>
<property name="minimum" value="2" />
</properties>
</rule>
<rule ref="rulesets/cleancode.xml">
<!-- else is not always bad. Disabling this as there is no way to differentiate between early return and normal else cases. -->
<exclude name="ElseExpression" />
<!-- Static access on Yii::$app is normal in Yii -->
<exclude name="StaticAccess" />
</rule>
<rule ref="rulesets/controversial.xml/CamelCaseClassName" />
<rule ref="rulesets/controversial.xml/CamelCaseMethodName" />
<rule ref="rulesets/controversial.xml/CamelCaseParameterName" />
<rule ref="rulesets/controversial.xml/CamelCaseVariableName" />
<!-- allow private properties to start with $_ -->
<rule ref="rulesets/controversial.xml/CamelCasePropertyName">
<properties>
<property name="allow-underscore" value="true" />
</properties>
</rule>
</ruleset>

6
tests/data/config.php

@ -43,6 +43,12 @@ $config = [
'password' => 'postgres',
'fixture' => __DIR__ . '/postgres.sql',
],
'oci' => [
'dsn' => 'oci:dbname=LOCAL_XE;charset=AL32UTF8;',
'username' => '',
'password' => '',
'fixture' => __DIR__ . '/oci.sql',
],
],
];

32
tests/data/console/migrate_create/create_unsigned_big_pk.php

@ -0,0 +1,32 @@
<?php
return <<<CODE
<?php
use yii\db\Migration;
/**
* Handles the creation of table `{table}`.
*/
class {$class} extends Migration
{
/**
* @inheritdoc
*/
public function up()
{
\$this->createTable('{table}', [
'brand_id' => \$this->bigPrimaryKey()->unsigned(),
]);
}
/**
* @inheritdoc
*/
public function down()
{
\$this->dropTable('{table}');
}
}
CODE;

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save