Browse Source

Merge branch 'master' into 2.1

Conflicts:
	docs/guide-fr/concept-events.md
	docs/guide-ja/concept-events.md
	docs/guide-ru/concept-events.md
	docs/guide/concept-events.md
	framework/caching/Cache.php
	framework/db/ActiveRelationTrait.php
	framework/db/mssql/QueryBuilder.php
	framework/web/User.php
tags/3.0.0-alpha1
SilverFire - Dmitry Naumenko 8 years ago
parent
commit
7f8fdf9d56
  1. 54
      .travis.yml
  2. 2
      build/controllers/DevController.php
  3. 18
      docs/guide-fr/concept-events.md
  4. 2
      docs/guide-fr/security-best-practices.md
  5. 18
      docs/guide-ja/concept-events.md
  6. 2
      docs/guide-ja/security-best-practices.md
  7. 18
      docs/guide-ru/concept-events.md
  8. 4
      docs/guide-ru/runtime-routing.md
  9. 2
      docs/guide-ru/security-best-practices.md
  10. 4
      docs/guide-ru/structure-models.md
  11. 4
      docs/guide/caching-data.md
  12. 20
      docs/guide/concept-events.md
  13. 4
      docs/guide/helper-array.md
  14. 372
      docs/guide/input-validation.md
  15. 22
      docs/guide/output-data-widgets.md
  16. 24
      docs/guide/security-best-practices.md
  17. 4
      docs/guide/structure-models.md
  18. 4
      docs/guide/test-overview.md
  19. 8
      docs/guide/tutorial-i18n.md
  20. 2
      docs/internals/design-decisions.md
  21. 28
      framework/CHANGELOG.md
  22. 2
      framework/assets/yii.gridView.js
  23. 2
      framework/base/Component.php
  24. 6
      framework/base/Security.php
  25. 2
      framework/behaviors/AttributeBehavior.php
  26. 3
      framework/behaviors/SluggableBehavior.php
  27. 19
      framework/caching/Cache.php
  28. 4
      framework/caching/Dependency.php
  29. 6
      framework/caching/MemCache.php
  30. 2
      framework/captcha/Captcha.php
  31. 4
      framework/captcha/CaptchaValidator.php
  32. 12
      framework/console/Controller.php
  33. 5
      framework/console/Markdown.php
  34. 2
      framework/console/controllers/CacheController.php
  35. 50
      framework/console/controllers/MigrateController.php
  36. 10
      framework/db/ActiveQuery.php
  37. 4
      framework/db/ActiveQueryInterface.php
  38. 6
      framework/db/ActiveRecordInterface.php
  39. 15
      framework/db/ActiveRelationTrait.php
  40. 2
      framework/db/BaseActiveRecord.php
  41. 2
      framework/db/Connection.php
  42. 14
      framework/db/mssql/QueryBuilder.php
  43. 1
      framework/db/mysql/Schema.php
  44. 6
      framework/di/Instance.php
  45. 2
      framework/filters/Cors.php
  46. 47
      framework/helpers/BaseHtml.php
  47. 4
      framework/helpers/BaseInflector.php
  48. 2
      framework/helpers/Html.php
  49. 6
      framework/i18n/Formatter.php
  50. 4
      framework/i18n/MessageFormatter.php
  51. 2
      framework/messages/ja/yii.php
  52. 8
      framework/rbac/ManagerInterface.php
  53. 4
      framework/rbac/migrations/m140506_102106_rbac_init.php
  54. 6
      framework/rbac/migrations/schema-mssql.sql
  55. 4
      framework/rbac/migrations/schema-mysql.sql
  56. 5
      framework/rbac/migrations/schema-oci.sql
  57. 4
      framework/rbac/migrations/schema-pgsql.sql
  58. 4
      framework/rbac/migrations/schema-sqlite.sql
  59. 2
      framework/requirements/YiiRequirementChecker.php
  60. 4
      framework/validators/EachValidator.php
  61. 2
      framework/validators/EmailValidator.php
  62. 33
      framework/validators/FileValidator.php
  63. 6
      framework/validators/ImageValidator.php
  64. 2
      framework/validators/SafeValidator.php
  65. 2
      framework/validators/UniqueValidator.php
  66. 14
      framework/web/AssetManager.php
  67. 19
      framework/web/Request.php
  68. 46
      framework/web/User.php
  69. 1
      framework/web/ViewAction.php
  70. 14
      framework/widgets/DetailView.php
  71. 18
      framework/widgets/LinkPager.php
  72. 2
      framework/widgets/MaskedInput.php
  73. 41
      tests/framework/behaviors/SluggableBehaviorTest.php
  74. 7
      tests/framework/caching/CacheTestCase.php
  75. 56
      tests/framework/console/controllers/MigrateControllerTest.php
  76. 8
      tests/framework/db/CommandTest.php
  77. 2
      tests/framework/db/cubrid/ActiveQueryTest.php
  78. 2
      tests/framework/db/mssql/ActiveQueryTest.php
  79. 2
      tests/framework/db/mssql/ExistValidatorTest.php
  80. 16
      tests/framework/db/mysql/QueryTest.php
  81. 12
      tests/framework/db/oci/ActiveQueryTest.php
  82. 2
      tests/framework/db/pgsql/ActiveQueryTest.php
  83. 6
      tests/framework/db/pgsql/CommandTest.php
  84. 2
      tests/framework/db/sqlite/ActiveQueryTest.php
  85. 24
      tests/framework/di/InstanceTest.php
  86. 18
      tests/framework/helpers/HtmlTest.php
  87. 19
      tests/framework/i18n/FormatterTest.php
  88. 16
      tests/framework/rbac/ActionRule.php
  89. 18
      tests/framework/rbac/ManagerTestCase.php
  90. 36
      tests/framework/validators/UniqueValidatorTest.php
  91. 55
      tests/framework/validators/ValidatorTest.php
  92. 133
      tests/framework/widgets/DetailViewTest.php
  93. 30
      tests/framework/widgets/LinkPagerTest.php

54
.travis.yml

@ -1,3 +1,5 @@
# faster builds on new travis setup not using sudo
sudo: false
language: php
php:
@ -6,11 +8,41 @@ php:
- 7.0
- 7.1
- nightly
- hhvm
matrix:
fast_finish: true
include:
# Test against HHVM 3.12 LTS version by using trusty
- php: hhvm-3.12
sudo: true
dist: trusty
group: edge # Use edge image until the next travis CI image update
addons:
apt:
packages:
- mysql-server-5.6
- mysql-client-core-5.6
- mysql-client-5.6
services:
- mysql
env: CUBRID_VERSION=9.3.0/CUBRID-9.3.0.0206 CUBRID_PDO_VERSION=9.3.0.0001
# test against the latest HHVM version by using a newer image
- php: hhvm
sudo: true
dist: trusty
group: edge # Use edge image until the next travis CI image update
addons:
postgresql: "9.3"
apt:
packages:
- mysql-server-5.6
- mysql-client-core-5.6
- mysql-client-5.6
services:
- mysql
- postgresql
env: CUBRID_VERSION=9.3.0/CUBRID-9.3.0.0206 CUBRID_PDO_VERSION=9.3.0.0001
allow_failures:
- php: 7.1
- php: nightly
env:
@ -19,9 +51,6 @@ env:
services:
- memcached
# faster builds on new travis setup not using sudo
sudo: false
# cache vendor dirs
cache:
directories:
@ -35,8 +64,8 @@ addons:
install:
- |
if [ $TRAVIS_PHP_VERSION != '5.6' ]; then
# disable xdebug for performance reasons when code coverage is not needed
if [[ $TRAVIS_PHP_VERSION != '5.6' && $TRAVIS_PHP_VERSION != hhv* ]]; then
# disable xdebug for performance reasons when code coverage is not needed. note: xdebug on hhvm is disabled by default
phpenv config-rm xdebug.ini || echo "xdebug is not installed"
fi
- travis_retry composer self-update && composer --version
@ -56,7 +85,16 @@ before_script:
- psql --version
# initialize databases
- mysql -e 'CREATE DATABASE yiitest;';
- |
if [[ $TRAVIS_PHP_VERSION != hhv* ]]; then
mysql -e 'CREATE DATABASE `yiitest`;'
fi
- |
if [[ $TRAVIS_PHP_VERSION = hhv* ]]; then
mysql -u root -e 'CREATE DATABASE yiitest;';
mysql -u root -e 'CREATE USER 'travis'@'localhost' IDENTIFIED WITH mysql_native_password;'
mysql -u root -e 'GRANT ALL PRIVILEGES ON *.* TO 'travis'@'localhost' WITH GRANT OPTION;'
fi
- psql -U postgres -c 'CREATE DATABASE yiitest;';
- |

2
build/controllers/DevController.php

@ -261,7 +261,7 @@ class DevController extends Controller
}
/**
* Creates symlinks to freamework and extension sources for the application
* Creates symlinks to framework and extension sources for the application
* @param string $dir application directory
* @param string $base Yii sources base directory
*

18
docs/guide-fr/concept-events.md

@ -229,6 +229,8 @@ Il y a encore une manière plus abstraite d'utiliser les événements. Vous pouv
Par exemple, vous pouvez créer l'interface suivante :
```php
namespace app\interfaces;
interface DanceEventInterface
{
const EVENT_DANCE = 'dance';
@ -260,32 +262,36 @@ class Developer extends Component implements DanceEventInterface
Pour gérer l'évenement `EVENT_DANCE` déclenché par n'importe laquelle de ces classes, appelez [[yii\base\Event::on()|Event::on()]] et passez-lui le nom de l'interface comme premier argument :
```php
Event::on('DanceEventInterface', DanceEventInterface::EVENT_DANCE, function ($event) {
Yii::trace($event->sender->className . ' danse'); // enregistrer le message disant que le chien ou le développeur danse.
Event::on(DanceEventInterface::class, DanceEventInterface::EVENT_DANCE, function ($event) {
Yii::trace(get_class($event->sender) . ' danse'); // enregistrer le message disant que le chien ou le développeur danse.
})
```
Vous pouvez déclencher l'événement de ces classes :
```php
Event::trigger(DanceEventInterface::class, DanceEventInterface::EVENT_DANCE);
// trigger event for Dog class
Event::trigger(Dog::class, DanceEventInterface::EVENT_DANCE);
// trigger event for Developer class
Event::trigger(Developer::class, DanceEventInterface::EVENT_DANCE);
```
Notez bien que vous ne pouvez pas déclencher l'événement de toutes les classes qui implémentent l'interface :,
```php
// NE FONCTIONNE PAS
Event::trigger('DanceEventInterface', DanceEventInterface::EVENT_DANCE); // error
Event::trigger(DanceEventInterface::class, DanceEventInterface::EVENT_DANCE); // error
```
Pour détacher le gestionnaire d'événement, appelez [[yii\base\Event::off()|Event::off()]]. Par exemple :
```php
// détache $handler
Event::off('DanceEventInterface', DanceEventInterface::EVENT_DANCE, $handler);
Event::off(DanceEventInterface::class, DanceEventInterface::EVENT_DANCE, $handler);
// détache tous les gestionnaires de DanceEventInterface::EVENT_DANCE
Event::off('DanceEventInterface', DanceEventInterface::EVENT_DANCE);
Event::off(DanceEventInterface::class, DanceEventInterface::EVENT_DANCE);
```

2
docs/guide-fr/security-best-practices.md

@ -121,7 +121,7 @@ La CSRF est une abréviation de cross-site request forgery (falsification de req
Par exemple, un site web `an.example.com` a une URL `/logout`, qui, lorsqu'elle est accédée en utilisant une simple requête GET, déconnecte l'utilisateur. Tant qu'il s'agit d'une requête de l'utilisateur lui-même, tout va bien. Mais, un jour, des gens mal intentionnés, postent `<img src="http://an.example.com/logout">` sur un forum que l'utilisateur visite fréquemment. Le navigateur ne fait pas de différence entre la requête d'une image et celle d'une page. C'est pourquoi, lorsque l'utilisateur ouvre une page avec une telle balise `img`, le navigateur envoie la requête GET vers cette URL, et l'utilisateur est déconnecté du site `an.example.com`.
C'est l'idée de base. D'aucuns diront que déconnecter un utilisateur n'a rien de très sérieux, mais les gens mal intentionnés peuvent faire bien plus, à partir de cette idée. Imaginez qu'un site web possède une URL `http://an.example.com/purse/transfer?to=anotherUser&amout=2000`. Accéder à cette URL en utilisant une requête GET, provoque le transfert de 2000€ d'un compte autorisé à l'utilisateur vers un autre compte `anotherUser`. Nous savons que le navigateur envoie toujours une requête GET pour charger une image. Nous pouvons donc modifier le code pour que seules des requêtes POST soient acceptées sur cette URL. Malheureusement, cela ne nous est pas d'un grand secours parce qu'un attaquant peut placer un peu le JavaScript à la place de la balise `<img>`, ce qui permet d'envoyer des requêtes POST sur cette URL:
C'est l'idée de base. D'aucuns diront que déconnecter un utilisateur n'a rien de très sérieux, mais les gens mal intentionnés peuvent faire bien plus, à partir de cette idée. Imaginez qu'un site web possède une URL `http://an.example.com/purse/transfer?to=anotherUser&amount=2000`. Accéder à cette URL en utilisant une requête GET, provoque le transfert de 2000€ d'un compte autorisé à l'utilisateur vers un autre compte `anotherUser`. Nous savons que le navigateur envoie toujours une requête GET pour charger une image. Nous pouvons donc modifier le code pour que seules des requêtes POST soient acceptées sur cette URL. Malheureusement, cela ne nous est pas d'un grand secours parce qu'un attaquant peut placer un peu le JavaScript à la place de la balise `<img>`, ce qui permet d'envoyer des requêtes POST sur cette URL:
Afin d'éviter la falsification des requêtes inter-sites vous devez toujours :

18
docs/guide-ja/concept-events.md

@ -258,6 +258,8 @@ Event::off(Foo::class, Foo::EVENT_HELLO);
例えば、次のようなインタフェイスを作ります。
```php
namespace app\interfaces;
interface DanceEventInterface
{
const EVENT_DANCE = 'dance';
@ -289,22 +291,26 @@ class Developer extends Component implements DanceEventInterface
これらのクラスのどれかによってトリガされた `EVENT_DANCE` を扱うためには、インターフェイスの名前を最初の引数にして [[yii\base\Event::on()|Event::on()]] を呼びます。
```php
Event::on('DanceEventInterface', DanceEventInterface::EVENT_DANCE, function ($event) {
Yii::trace($event->sender->className . ' が躍り上がって喜んだ。'); // 犬または開発者が躍り上がって喜んだことをログに記録。
Event::on(DanceEventInterface::class, DanceEventInterface::EVENT_DANCE, function ($event) {
Yii::trace(get_class($event->sender) . ' が躍り上がって喜んだ。'); // 犬または開発者が躍り上がって喜んだことをログに記録。
})
```
これらのクラスのイベントをトリガすることも出来ます。
```php
Event::trigger(DanceEventInterface::class, DanceEventInterface::EVENT_DANCE);
// trigger event for Dog class
Event::trigger(Dog::class, DanceEventInterface::EVENT_DANCE);
// trigger event for Developer class
Event::trigger(Developer::class, DanceEventInterface::EVENT_DANCE);
```
ただし、このインタフェイスを実装する全クラスのイベントをトリガすることは出来ない、ということに注意して下さい。
```php
// これは動かない
Event::trigger('DanceEventInterface', DanceEventInterface::EVENT_DANCE); // エラー
Event::trigger(DanceEventInterface::class, DanceEventInterface::EVENT_DANCE); // エラー
```
イベントハンドラをデタッチするためには、[[yii\base\Event::off()|Event::off()]] を呼びます。
@ -312,10 +318,10 @@ Event::trigger('DanceEventInterface', DanceEventInterface::EVENT_DANCE); // エ
```php
// $handler をデタッチ
Event::off('DanceEventInterface', DanceEventInterface::EVENT_DANCE, $handler);
Event::off(DanceEventInterface::class, DanceEventInterface::EVENT_DANCE, $handler);
// DanceEventInterface::EVENT_DANCE の全てのハンドラをデタッチ
Event::off('DanceEventInterface', DanceEventInterface::EVENT_DANCE);
Event::off(DanceEventInterface::class, DanceEventInterface::EVENT_DANCE);
```

2
docs/guide-ja/security-best-practices.md

@ -141,7 +141,7 @@ CSRF は、クロスサイトリクエストフォージェリ (cross-site reque
これは基本的な考え方です。ユーザがログアウトされるぐらいは大したことではない、と言うことも出来るでしょう。
しかし、悪い奴は、この考え方を使って、もっとひどいことをすることも出来ます。
例えば、`http://an.example.com/purse/transfer?to=anotherUser&amout=2000` という URL を持つウェブサイトがあると考えて見てください。
例えば、`http://an.example.com/purse/transfer?to=anotherUser&amount=2000` という URL を持つウェブサイトがあると考えて見てください。
この URL に GET リクエストを使ってアクセスすると、権限を持つユーザアカウントから `anotherUser` に $2000 が送金されるのです。
私たちは、ブラウザは画像をロードするのに常に GET リクエストを使う、ということを知っていますから、この URL が POST リクエストだけを受け入れるようにコードを修正することは出来ます。
しかし残念なことに、それで問題が解決する訳ではありません。

18
docs/guide-ru/concept-events.md

@ -231,6 +231,8 @@ Event::off(Foo::class, Foo::EVENT_HELLO);
Например, создадим следующий интерфейс:
```php
namespace app\interfaces;
interface DanceEventInterface
{
const EVENT_DANCE = 'dance';
@ -263,32 +265,36 @@ class Developer extends Component implements DanceEventInterface
вызовите [[yii\base\Event::on()|Event:on()]], передав ему в качестве первого параметра имя интерфейса.
```php
Event::on('DanceEventInterface', DanceEventInterface::EVENT_DANCE, function ($event) {
Yii::trace($event->sender->className . ' just danced'); // Оставит запись в журнале о том, что кто-то танцевал
Event::on(DanceEventInterface::class, DanceEventInterface::EVENT_DANCE, function ($event) {
Yii::trace(get_class($event->sender) . ' just danced'); // Оставит запись в журнале о том, что кто-то танцевал
});
```
Вы можете также инициализировать эти события:
```php
Event::trigger(DanceEventInterface::class, DanceEventInterface::EVENT_DANCE);
// trigger event for Dog class
Event::trigger(Dog::class), DanceEventInterface::EVENT_DANCE);
// trigger event for Developer class
Event::trigger(Developer::class, DanceEventInterface::EVENT_DANCE);
```
Однако, невозможно инициализировать событие во всех классах, которые реализуют интерфейс:
```php
// НЕ БУДЕТ РАБОТАТЬ
Event::trigger('DanceEventInterface', DanceEventInterface::EVENT_DANCE); // ошибка
Event::trigger(DanceEventInterface::class, DanceEventInterface::EVENT_DANCE); // ошибка
```
Отсоединить обработчик события можно с помощью метода [[yii\base\Event::off()|Event::off()]]. Например:
```php
// отсоединяет $handler
Event::off('DanceEventInterface', DanceEventInterface::EVENT_DANCE, $handler);
Event::off(DanceEventInterface::class, DanceEventInterface::EVENT_DANCE, $handler);
// отсоединяются все обработчики DanceEventInterface::EVENT_DANCE
Event::off('DanceEventInterface', DanceEventInterface::EVENT_DANCE);
Event::off(DanceEventInterface::class, DanceEventInterface::EVENT_DANCE);
```
Глобальные события <span id="global-events"></span>

4
docs/guide-ru/runtime-routing.md

@ -231,7 +231,7 @@ echo Url::previous();
При включенном режиме ЧПУ, компонент [[yii\web\UrlManager|URL manager]] использует правила URL, содержащиеся в его свойстве [[yii\web\UrlManager::rules|rules]], для разбора входящих запросов и создания URL. Обычно, при разборе входящего запроса, [[yii\web\UrlManager|URL manager]] проверяет все правила в порядке их следования, до *первого* правила, соответствующего запрошенному URL. Найденное правило используется для разбора URL на маршрут и параметры запроса. Аналогично для создания URL компонент [[yii\web\UrlManager|URL manager]] ищет первое правило, соответствующее заданному маршруту и параметрам и использует его для создания URL.
[[yii\web\UrlManager::rules|Правила]] задаются ассоциативным массивом, где ключи определяют шаблоны, а значения соответствующие маршруты. Каждая пара шаблон-маршрут составляет правило разбора URL. Например, следующие [[yii\web\UrlManager::rules|правила]] определяют два правила разбора URL. Первое правило задает соответствие URL `post` маршруту `post/index`. Второе правило задает соответствие URL, соответствующего регулярному выражению `post/(\d+)` маршруту `post/view` и параметру `id`.
[[yii\web\UrlManager::rules|Правила]] задаются ассоциативным массивом, где ключи определяют шаблоны, а значения соответствующие маршруты. Каждая пара шаблон-маршрут составляет правило разбора URL. Например, следующие [[yii\web\UrlManager::rules|правила]] определяют два правила разбора URL. Первое правило задает соответствие URL `posts` маршруту `post/index`. Второе правило задает соответствие URL, соответствующего регулярному выражению `post/(\d+)` маршруту `post/view` и параметру `id`.
```php
[
@ -305,7 +305,7 @@ echo Url::previous();
]
```
Для разбора URL `/index.php/comment/100/create` будет использовано первое правило, которое установит значения параметров `controller` равным `comment` и `action` равным `create`. Таким образом, маршрут `<controller>/<action>` дубет разрешен в `comment/create`.
Для разбора URL `/index.php/comment/100/create` будет использовано первое правило, которое установит значения параметров `controller` равным `comment` и `action` равным `create`. Таким образом, маршрут `<controller>/<action>` будет разрешен в `comment/create`.
Аналогично, для маршрута `comment/index`, при помощи третьего правила, будет создан URL `comment/index`.

2
docs/guide-ru/security-best-practices.md

@ -145,7 +145,7 @@ CSRF - это аббревиатура для межсайтинговой по
`<img src="http://an.example.com/logout">` на форуме с большой посещаемостью. Браузер не делает никаких отличий
между запросом изображения и запросом страницы, так что когда пользователь откроет страницу с таким тегом `img`, браузер отправит GET запрос на указанный адрес, и пользователь будет разлогинен.
Вот основная идея. Можно сказать, что в разлогировании пользователя нет ничего серьёзного, но с помощью этой уязвимости, можно выполнять опасные операции. Представьте, что существует страница http://an.example.com/purse/transfer?to=anotherUser&amout=2000, обращение к которой с помощью GET запроса, приводит к перечислению 2000 единиц валюты со счета авторизированного пользователя на счет пользователя с логином anotherUser. Учитывая, что браузер для загрузки контента отправляет GET запросы, можно подумать, что разрешение на выполнение такой операции только POST запросом на 100% обезопасит от проблем. К сожалению, это не совсем правда. Учитывайте, что вместо тега <img>, злоумышленник может внедрить JavaScript код, который будет отправлять нужные POST запросы на этот URL.
Вот основная идея. Можно сказать, что в разлогировании пользователя нет ничего серьёзного, но с помощью этой уязвимости, можно выполнять опасные операции. Представьте, что существует страница http://an.example.com/purse/transfer?to=anotherUser&amount=2000, обращение к которой с помощью GET запроса, приводит к перечислению 2000 единиц валюты со счета авторизированного пользователя на счет пользователя с логином anotherUser. Учитывая, что браузер для загрузки контента отправляет GET запросы, можно подумать, что разрешение на выполнение такой операции только POST запросом на 100% обезопасит от проблем. К сожалению, это не совсем правда. Учитывайте, что вместо тега <img>, злоумышленник может внедрить JavaScript код, который будет отправлять нужные POST запросы на этот URL.
Для того, чтоб избежать CSRF вы должны всегда:

4
docs/guide-ru/structure-models.md

@ -29,7 +29,7 @@ $model->name = 'example';
echo $model->name;
```
Также возможно получить доступ к атрибутам как к элементам массива, спасибо поддержке [ArrayAccess](http://php.net/manual/en/class.arrayaccess.php) и [ArrayIterator](http://php.net/manual/en/class.arrayiterator.php)
Также возможно получить доступ к атрибутам как к элементам массива, спасибо поддержке [ArrayAccess](http://php.net/manual/ru/class.arrayaccess.php) и [Traversable](http://php.net/manual/ru/class.traversable.php)
в [[yii\base\Model]]:
```php
@ -39,7 +39,7 @@ $model = new \app\models\ContactForm;
$model['name'] = 'example';
echo $model['name'];
// перебор атрибутов
// Модель является обходимой(traversable) с использованием foreach.
foreach ($model as $name => $value) {
echo "$name: $value\n";
}

4
docs/guide/caching-data.md

@ -200,6 +200,10 @@ if ($data === false) {
}
```
Since 2.0.11 you may set [[yii\caching\Cache::$ttl|ttl]] value in your cache component configuration if you prefer a custom cache duration
over the default unlimited duration.
This will allow you not to pass custom `duration` parameter to [[yii\caching\Cache::set()|set()]] each time.
### Cache Dependencies <span id="cache-dependencies"></span>

20
docs/guide/concept-events.md

@ -261,6 +261,8 @@ implement it in classes, where you need it.
For example, we can create the following interface:
```php
namespace app\interfaces;
interface DanceEventInterface
{
const EVENT_DANCE = 'dance';
@ -290,25 +292,29 @@ class Developer extends Component implements DanceEventInterface
```
To handle the `EVENT_DANCE`, triggered by any of these classes, call [[yii\base\Event::on()|Event::on()]] and
pass the interface name as the first argument:
pass the interface class name as the first argument:
```php
Event::on('DanceEventInterface', DanceEventInterface::EVENT_DANCE, function ($event) {
Yii::trace($event->sender->className . ' just danced'); // Will log that Dog or Developer danced
})
Event::on(DanceEventInterface::class, DanceEventInterface::EVENT_DANCE, function ($event) {
Yii::trace(get_class($event->sender) . ' just danced'); // Will log that Dog or Developer danced
});
```
You can trigger the event of those classes:
```php
Event::trigger(DanceEventInterface::class, DanceEventInterface::EVENT_DANCE);
// trigger event for Dog class
Event::trigger(Dog::class, DanceEventInterface::EVENT_DANCE);
// trigger event for Developer class
Event::trigger(Developer::class, DanceEventInterface::EVENT_DANCE);
```
But please notice, that you can not trigger all the classes, that implement the interface:
```php
// DOES NOT WORK
Event::trigger('DanceEventInterface', DanceEventInterface::EVENT_DANCE); // error
// DOES NOT WORK. Classes that implement this interface will NOT be triggered.
Event::trigger(DanceEventInterface::class, DanceEventInterface::EVENT_DANCE);
```
To detach event handler, call [[yii\base\Event::off()|Event::off()]]. For example:

4
docs/guide/helper-array.md

@ -131,7 +131,7 @@ $array = [
$result = ArrayHelper::index($array, 'id');
```
The result will be an associative array, where the key is the value of `id` attribute
The result will be an associative array, where the key is the value of `id` attribute:
```php
[
@ -141,7 +141,7 @@ The result will be an associative array, where the key is the value of `id` attr
]
```
Anonymous function, passed as a `$key`, gives the same result.
Anonymous function, passed as a `$key`, gives the same result:
```php
$result = ArrayHelper::index($array, function ($element) {

372
docs/guide/input-validation.md

@ -442,6 +442,378 @@ class EntryForm extends Model
```
## Multiple Attributes Validation <span id="multiple-attributes-validation"></span>
Sometimes validators involve multiple attributes. Consider the following form:
```php
class MigrationForm extends \yii\base\Model
{
/**
* Minimal funds amount for one adult person
*/
const MIN_ADULT_FUNDS = 3000;
/**
* Minimal funds amount for one child
*/
const MIN_CHILD_FUNDS = 1500;
public $personalSalary;
public $spouseSalary;
public $childrenCount;
public $description;
public function rules()
{
return [
[['personalSalary', 'description'], 'required'],
[['personalSalary', 'spouseSalary'], 'integer', 'min' => self::MIN_ADULT_FUNDS],
['childrenCount', 'integer', 'min' => 0, 'max' => 5],
[['spouseSalary', 'childrenCount'], 'default', 'value' => 0],
['description', 'string'],
];
}
}
```
### Creating validator <span id="multiple-attributes-validator"></span>
Let's say we need to check if the family income is enough for children. We can create inline validator
`validateChildrenFunds` for that which will run only when `childrenCount` is more than 0.
Note that we can't use all validated attributes (`['personalSalary', 'spouseSalary', 'childrenCount']`) when attaching
validator. This is because the same validator will run for each attribute (3 times in total) and we only need to run it
once for the whole attribute set.
You can use any of these attributes instead (or use what you think is the most relevant):
```php
['childrenCount', 'validateChildrenFunds', 'when' => function ($model) {
return $model->childrenCount > 0;
}],
```
Implementation of `validateChildrenFunds` can be like this:
```php
public function validateChildrenFunds($attribute, $params)
{
$totalSalary = $this->personalSalary + $this->spouseSalary;
// Double the minimal adult funds if spouse salary is specified
$minAdultFunds = $this->spouseSalary ? self::MIN_ADULT_FUNDS * 2 : self::MIN_ADULT_FUNDS;
$childFunds = $totalSalary - $minAdultFunds;
if ($childFunds / $this->childrenCount < self::MIN_CHILD_FUNDS) {
$this->addError('childrenCount', 'Your salary is not enough for children.');
}
}
```
You can ignore `$attribute` parameter because validation is not related to just one attribute.
### Adding errors <span id="multiple-attributes-errors"></span>
Adding error in case of multiple attributes can vary depending on desired form design:
- Select the most relevant field in your opinion and add error to it's attribute:
```php
$this->addError('childrenCount', 'Your salary is not enough for children.');
```
- Select multiple important relevant attributes or all attributes and add the same error message to them. We can store
message in separate variable before passing it to `addError` to keep code DRY.
```php
$message = 'Your salary is not enough for children.';
$this->addError('personalSalary', $message);
$this->addError('wifeSalary', $message);
$this->addError('childrenCount', $message);
```
Or use a loop:
```php
$attributes = ['personalSalary, 'wifeSalary', 'childrenCount'];
foreach ($attributes as $attribute) {
$this->addError($attribute, 'Your salary is not enough for children.');
}
```
- Add a common error (not related to particular attribute). We can use the not existing attribute name for adding
error, for example `*`, because attribute existence is not checked at that point.
```php
$this->addError('*', 'Your salary is not enough for children.');
```
As a result, we will not see error message near form fields. To display it, we can include the error summary in view:
```php
<?= $form->errorSummary($model) ?>
```
### Custom validator <span id="multiple-attributes-custom-validator"></span>
If passing one attribute is not acceptable for you (for example it can be hard to choose which one is more relevant or
you consider it misleading in rules), the more advanced solution is to implement `CustomValidator` with support for
validating multiple attributes at once.
By default if multiple attributes are used for validation, the loop will be used to apply the same validation to each
of them. Let's use a separate trait and override [[yii\base\Validator:validateAttributes()]]:
```php
<?php
namespace app\components;
trait BatchValidationTrait
{
/**
* @var bool whether to validate multiple attributes at once
*/
public $batch = false;
/**
* Validates the specified object.
* @param \yii\base\Model $model the data model being validated.
* @param array|null $attributes the list of attributes to be validated.
* Note that if an attribute is not associated with the validator, or is is prefixed with `!` char - it will be
* ignored. If this parameter is null, every attribute listed in [[attributes]] will be validated.
*/
public function validateAttributes($model, $attributes = null)
{
if (is_array($attributes)) {
$newAttributes = [];
foreach ($attributes as $attribute) {
if (in_array($attribute, $this->attributes) || in_array('!' . $attribute, $this->attributes)) {
$newAttributes[] = $attribute;
}
}
$attributes = $newAttributes;
} else {
$attributes = [];
foreach ($this->attributes as $attribute) {
$attributes[] = $attribute[0] === '!' ? substr($attribute, 1) : $attribute;
}
}
foreach ($attributes as $attribute) {
$skip = $this->skipOnError && $model->hasErrors($attribute)
|| $this->skipOnEmpty && $this->isEmpty($model->$attribute);
if ($skip) {
// Skip validation if at least one attribute is empty or already has error
// (according skipOnError and skipOnEmpty options must be set to true
return;
}
}
if ($this->batch) {
// Validate all attributes at once
if ($this->when === null || call_user_func($this->when, $model, $attribute)) {
// Pass array with all attributes instead of one attribute
$this->validateAttribute($model, $attributes);
}
} else {
// Validate each attribute separately using the same validation logic
foreach ($attributes as $attribute) {
if ($this->when === null || call_user_func($this->when, $model, $attribute)) {
$this->validateAttribute($model, $attribute);
}
}
}
}
}
```
Then we need to create custom validator and use the created trait:
```php
<?php
namespace app\components;
use yii\validators\Validator;
class CustomValidator extends Validator
{
use BatchValidationTrait;
}
```
To support inline validation as well we can extend default inline validator and also use this trait:
```php
<?php
namespace app\components;
use yii\validators\InlineValidator;
class CustomInlineValidator extends InlineValidator
{
use BatchValidationTrait;
}
```
Couple more changes are needed.
First to use our `CustomInlineValidator` instead of default `InlineValidator` we need to override
[[\yii\validators\Validator::createValidator()]] method in `CustomValidator`:
```php
public static function createValidator($type, $model, $attributes, $params = [])
{
$params['attributes'] = $attributes;
if ($type instanceof \Closure || $model->hasMethod($type)) {
// method-based validator
// The following line is changed to use our CustomInlineValidator
$params['class'] = __NAMESPACE__ . '\CustomInlineValidator';
$params['method'] = $type;
} else {
if (isset(static::$builtInValidators[$type])) {
$type = static::$builtInValidators[$type];
}
if (is_array($type)) {
$params = array_merge($type, $params);
} else {
$params['class'] = $type;
}
}
return Yii::createObject($params);
}
```
And finally to support our custom validator in model we can create the trait and override
[[\yii\base\Model::createValidators()]] like this:
```php
<?php
namespace app\components;
use yii\base\InvalidConfigException;
trait CustomValidationTrait
{
/**
* Creates validator objects based on the validation rules specified in [[rules()]].
* Unlike [[getValidators()]], each time this method is called, a new list of validators will be returned.
* @return ArrayObject validators
* @throws InvalidConfigException if any validation rule configuration is invalid
*/
public function createValidators()
{
$validators = new ArrayObject;
foreach ($this->rules() as $rule) {
if ($rule instanceof Validator) {
$validators->append($rule);
} elseif (is_array($rule) && isset($rule[0], $rule[1])) { // attributes, validator type
// The following line is changed in order to use our CustomValidator
$validator = CustomValidator::createValidator($rule[1], $this, (array) $rule[0], array_slice($rule, 2));
$validators->append($validator);
} else {
throw new InvalidConfigException('Invalid validation rule: a rule must specify both attribute names and validator type.');
}
}
return $validators;
}
}
```
Now we can implement custom validator by extending from `CustomValidator`:
```php
<?php
namespace app\validators;
use app\components\CustomValidator;
class ChildrenFundsValidator extends CustomValidator
{
public function validateAttribute($model, $attribute)
{
// $attribute here is not a single attribute, it's an array containing all related attributes
$totalSalary = $this->personalSalary + $this->spouseSalary;
// Double the minimal adult funds if spouse salary is specified
$minAdultFunds = $this->spouseSalary ? self::MIN_ADULT_FUNDS * 2 : self::MIN_ADULT_FUNDS;
$childFunds = $totalSalary - $minAdultFunds;
if ($childFunds / $this->childrenCount < self::MIN_CHILD_FUNDS) {
$this->addError('*', 'Your salary is not enough for children.');
}
}
}
```
Because `$attribute` contains the list of all related attributes, we can use loop in case of adding errors for all
attributes is needed:
```php
foreach ($attribute as $singleAttribute) {
$this->addError($attribute, 'Your salary is not enough for children.');
}
```
Now it's possible to specify all related attributes in according validation rule:
```php
[
['personalSalary', 'spouseSalary', 'childrenCount'],
\app\validators\ChildrenFundsValidator::class,
'batch' => `true`,
'when' => function ($model) {
return $model->childrenCount > 0;
}
],
```
For inline validation the rule will be:
```php
[
['personalSalary', 'spouseSalary', 'childrenCount'],
'validateChildrenFunds',
'batch' => `true`,
'when' => function ($model) {
return $model->childrenCount > 0;
}
],
```
And here is according validation method:
```php
public function validateChildrenFunds($attribute, $params)
{
// $attribute here is not a single attribute, it's an array containing all related attributes
$totalSalary = $this->personalSalary + $this->spouseSalary;
// Double the minimal adult funds if spouse salary is specified
$minAdultFunds = $this->spouseSalary ? self::MIN_ADULT_FUNDS * 2 : self::MIN_ADULT_FUNDS;
$childFunds = $totalSalary - $minAdultFunds;
if ($childFunds / $this->childrenCount < self::MIN_CHILD_FUNDS) {
$this->addError('childrenCount', 'Your salary is not enough for children.');
}
}
```
The advantages of this approach:
- It better reflects all attributes that participate in validation (the rules become more readable);
- It respects the options [[yii\validators\Validator::skipOnError]] and [[yii\validators\Validator::skipOnEmpty]] for
**each** used attribute (not only for that you decided to choose as more relevant).
If you have problems with implementing client validation, you can:
- combine [[yii\widgets\ActiveForm::enableAjaxValidation|enableClientValidation]] and
[[yii\widgets\ActiveForm::enableAjaxValidation|enableAjaxValidation]] options, so multiple attributes will be validated
with AJAX without page reload;
- implement validation outside of [[yii\validators\Validator::clientValidateAttribute]] because it's designed to work
with single attribute.
## Client-Side Validation <span id="client-side-validation"></span>
Client-side validation based on JavaScript is desirable when end users provide inputs via HTML forms, because

22
docs/guide/output-data-widgets.md

@ -37,6 +37,28 @@ echo DetailView::widget([
]);
```
Remember that unlike [[yii\widgets\GridView|GridView]] which processes a set of models,
[[yii\widgets\DetailView|DetailView]] processes just one. So most of the times there is no need for using closure since
`$model` is the only one model for display and available in view as variable.
However some cases can make using of closure useful. For example when `visible` is specified and you want to prevent
`value` calculations in case it evaluates to `false`:
```php
echo DetailView::widget([
'model' => $model,
'attributes' => [
[
'attribute' => 'owner',
'value' => function ($model) {
return $model->owner->name;
},
'visible' => \Yii::$app->user->can('posts.owner.view'),
],
],
]);
```
ListView <a name="list-view"></a>
--------

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

@ -141,7 +141,7 @@ as it's requested by the user itself everything is OK but one day bad guys are s
`<img src="http://an.example.com/logout">` on a forum user visits frequently. Browser doesn't make any difference between
requesting an image or requesting a page so when user opens a page with such `img` tag, the browser will send the GET request to that URL, and the user will be logged out from `an.example.com`.
That's the basic idea. One can say that logging user out is nothing serious, but bad guys can do much more, using this idea. Imagine that some website has an URL `http://an.example.com/purse/transfer?to=anotherUser&amout=2000`. Accessing it using GET request, causes transfer of $2000 from authorized user account to user `anotherUser`. We know, that browser will always send GET request to load an image, so we can modify code to accept only POST requests on that URL. Unfortunately, this will not save us, because an attacker can put some JavaScript code instead of `<img>` tag, which allows to send POST requests on that URL.
That's the basic idea. One can say that logging user out is nothing serious, but bad guys can do much more, using this idea. Imagine that some website has an URL `http://an.example.com/purse/transfer?to=anotherUser&amount=2000`. Accessing it using GET request, causes transfer of $2000 from authorized user account to user `anotherUser`. We know, that browser will always send GET request to load an image, so we can modify code to accept only POST requests on that URL. Unfortunately, this will not save us, because an attacker can put some JavaScript code instead of `<img>` tag, which allows to send POST requests on that URL.
In order to avoid CSRF you should always:
@ -221,3 +221,25 @@ provided by H5BP project:
- [Apache](https://github.com/h5bp/server-configs-apache).
- [IIS](https://github.com/h5bp/server-configs-iis).
- [Lighttpd](https://github.com/h5bp/server-configs-lighttpd).
Secure Server configuration
---------------------------
The purpose of this section is to highlight risks that need to be considered when creating a
server configuration for serving a Yii based website. Besides the points covered here there may
be other security related configuration options to be considered, so do not consider this section to
be complete.
### Avoiding `Host`-header attacks
Classes like [[yii\web\UrlManager]] and [[yii\helpers\Url]] may use the [[yii\web\Request::getHostInfo()|currently requested host name]]
for generating links.
If the webserver is configured to serve the same site independent of the value of the `Host` header, this information may not be reliable
and [may be faked by the user sending the HTTP request](https://www.acunetix.com/vulnerabilities/web/host-header-attack).
In such situations you should either fix your webserver configuration to serve the site only for specified host names
or explicitly set or filter the value by setting the [[yii\web\Request::setHostInfo()|hostInfo]] property of the `request` application component.
For more information about the server configuration, please refer to the documentation of your webserver:
- Apache 2: <http://httpd.apache.org/docs/trunk/vhosts/examples.html#defaultallports>
- Nginx: <https://www.nginx.com/resources/wiki/start/topics/examples/server_blocks/>

4
docs/guide/structure-models.md

@ -37,7 +37,7 @@ echo $model->name;
```
You can also access attributes like accessing array elements, thanks to the support for
[ArrayAccess](http://php.net/manual/en/class.arrayaccess.php) and [ArrayIterator](http://php.net/manual/en/class.arrayiterator.php)
[ArrayAccess](http://php.net/manual/en/class.arrayaccess.php) and [Traversable](http://php.net/manual/en/class.traversable.php)
by [[yii\base\Model]]:
```php
@ -47,7 +47,7 @@ $model = new \app\models\ContactForm;
$model['name'] = 'example';
echo $model['name'];
// iterate attributes
// Model is traversable using foreach.
foreach ($model as $name => $value) {
echo "$name: $value\n";
}

4
docs/guide/test-overview.md

@ -2,12 +2,12 @@ Testing
=======
Testing is an important part of software development. Whether we are aware of it or not, we conduct testing continuously.
For example, when we write a class in PHP, we may debug it step by step or simply use echo or die statements to verify
For example, when we write a class in PHP, we may debug it step by step or simply use `echo` or `die` statements to verify
the implementation works according to our initial plan. In the case of a web application, we're entering some test data
in forms to ensure the page interacts with us as expected.
The testing process could be automated so that each time when we need to verify something, we just need to call up the code that does it for us. The code that verifies the result matches
what we've planned is called test and the process of its creation and further execution is known as automated testing,
what we've planned is called *test* and the process of its creation and further execution is known as *automated testing*,
which is the main topic of these testing chapters.

8
docs/guide/tutorial-i18n.md

@ -103,13 +103,17 @@ method to perform the actual translation work. The component can be configured i
In the above code, a message source supported by [[yii\i18n\PhpMessageSource]] is being configured. The pattern
`app*` indicates that all message categories whose names start with `app` should be translated using this
message source. The [[yii\i18n\PhpMessageSource]] class uses PHP files to store message translations. Each
PHP file corresponds to the messages of a single category. By default, the file name should be the same as
message source. The [[yii\i18n\PhpMessageSource]] class uses PHP files to store message translations.
These files contain a simple PHP array that is a map of the messages in source language to the translation in the target language.
Each PHP file corresponds to the messages of a single category. By default, the file name should be the same as
the category name. However, you may configure [[yii\i18n\PhpMessageSource::fileMap|fileMap]] to map a category
to a PHP file with a different naming approach. In the above example, the category `app/error` is mapped to
the PHP file `@app/messages/ru-RU/error.php` (assuming `ru-RU` is the target language). Without this configuration,
the category would be mapped to `@app/messages/ru-RU/app/error.php`, instead.
> Tip: You can automatically generate these PHP files by using the [`message` command](#message-command),
> which will be introduced later in this chapter.
Besides storing the messages in PHP files, you may also use the following message sources to store translated messages
in different storage:

2
docs/internals/design-decisions.md

@ -24,3 +24,5 @@ the core developers.
then it's safer to use bigint or mediumint rather than relying on unsigned.
<https://github.com/yiisoft/yii/pull/1923#issuecomment-11881967>
6. [Helpers vs separate non-static classes](https://github.com/yiisoft/yii2/pull/12661#issuecomment-251599463)
7. **Setters method chaining** should be avoided if there are methods in the classs returning meaningful values. Chaining could be
supported if a class is a builder where all setters are modifying internal state: https://github.com/yiisoft/yii2/issues/13026

28
framework/CHANGELOG.md

@ -21,19 +21,29 @@ 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 #9305: Fixed MSSQL `Schema::TYPE_TIMESTAMP` to be 'datetime' instead of 'timestamp', which is just an incremental number (nkovacs)
- Bug #9796: Initialization of not existing `yii\grid\ActionColumn` default buttons (arogachev)
- Enh #12798: Changed `yii\cache\Dependency::getHasChanged()` (deprecated, to be removed in 2.1) to `yii\cache\Dependency::isChanged()` (dynasource)
- 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 #12791: Fixed `yii\behaviors\AttributeTypecastBehavior` unable to automatically detect `attributeTypes`, triggering PHP Fatal Error (klimov-paul)
- 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)
- Bug #12824: Enabled usage of `yii\mutex\FileMutex` on Windows systems (davidsonalencar)
- Bug #12828: Fixed handling of nested arrays, objects in `\yii\grid\GridView::guessColumns` (githubjeka)
- Bug #12836: Fixed `yii\widgets\GridView::filterUrl` to not ignore `#` part of filter URL (cebe)
- Bug #12856: Fixed `yii\web\XmlResponseFormatter` to use `true` and `false` to represent booleans (samdark)
- Bug #12879: Console progress bar was not working properly in Windows terminals (samdark, kids-return)
- Bug #12880: Fixed `yii\behaviors\AttributeTypecastBehavior` marks attributes with `null` value as 'dirty' (klimov-paul)
- Bug #12904: Fixed lowercase table name in migrations (zlakomanoff)
- Bug #12939: Hard coded table names for MSSQL in RBAC migration (arogachev)
- Bug #12974: Fixed incorrect order of migrations history in case `yii\console\controllers\MigrateController::$migrationNamespaces` is in use (evgen-d, 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 #9162: Added support of closures in `value` for attributes in `yii\widgets\DetailView` (arogachev)
- Enh #11037: `yii.js` and `yii.validation.js` use `Regexp.test()` instead of `String.match()` (arogachev, nkovacs)
- Enh #11756: Added type mapping for `varbinary` data type in MySQL DBMS (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 #12619: Added catch `Throwable` in `yii\base\ErrorHandler::handleException()` (rob006)
@ -42,10 +52,13 @@ Yii Framework 2 Change Log
- 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 #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 #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 #13035: Use ArrayHelper::getValue() in SluggableBehavior::getValue() (thyseus)
- Enh #13020: Added `disabledListItemSubTagOptions` attribute for `yii\widgets\LinkPager` in order to customize the disabled list item sub tag element (nadar)
- Enh: Added constants for specifying `yii\validators\CompareValidator::$type` (cebe)
@ -53,14 +66,14 @@ Yii Framework 2 Change Log
-----------------------
- Bug #7670: Added `yii\web\UrlNormalizer` for normalizing requests with and without trailing slashes (rob006, cronfy, klimov-paul)
- Bug #10358: Fixed race condition in `yii.js` AJAX prefilter (silverfire)
- Bug #7670: Added `UrlNormalizer` for normalizing requests with and without trailing slashes (rob006, cronfy, klimov-paul)
- Bug #9027: Fixed descendant class of `yii\web\UploadedFile` returns parent instances in case invoked after it (andrewnester)
- Bug #9101: Fixed `yii\web\View` to respect `yii\web\AssetManager::appendTimstamp` property (githubjeka, silverfire)
- Bug #9277: Fixed `yii\console\controllers\AssetController` looses custom options of 'target' bundles (petrabarus, klimov-paul)
- Bug #9561: Fixed `canGetProperty()` and `canSetProperty()` returns `false` for `yii\db\BaseActiveRecord` attributes (klimov-paul, Ni-san)
- Bug #10567: Fixed `yii\console\controllers\AssetController` looses bundle override configuration, which makes it external one (klimov-paul)
- Bug #10358: Fixed race condition in `yii.js` AJAX prefilter (silverfire)
- Bug #10563: Fixed forming `Content-Disposition` header for file downloads (samdark)
- Bug #10567: Fixed `yii\console\controllers\AssetController` looses bundle override configuration, which makes it external one (klimov-paul)
- Bug #10587: Latest used controller instance was not available in `Response::EVENT_AFTER_SEND` handler (samdark, andrewnester)
- Bug #10681: Reverted fix of beforeValidate event calling in `yii.activeForm.js` (silverfire)
- Bug #11347: Fixed `yii\widgets\Pjax::registerClientScript()` to pass custom `container` to the PJAX JS plugin (silverfire)
- Bug #11352: Fixed `updateInputs()` method in `yii.activeForm.js` to prevent reading property of undefined (silverfire)
@ -70,6 +83,7 @@ Yii Framework 2 Change Log
- Bug #11726: `yii\web\DbSession` was echoing database errors in production mode (samdark, pastuhov, deadkrolik)
- Bug #11907: Fixed `yii\helpers\Console::getScreenSize()` on Windows was giving out width and height swapped (Spell6inder, samdark, cebe)
- Bug #11912: Fixed PostgreSQL Schema to support negative default values for integer/float/decimal columns (nsknewbie)
- Bug #11921: Fixed URL decoding in `yii.getQueryParams()` to handle `+` (plus) character properly (silverfire)
- Bug #11922: Fixed `yii\log\FileTarget` does not apply `fileMode` for rotated via copy files (klimov-paul)
- Bug #11947: Fixed `gridData` initialization in `yii.gridView.js` (pavlm)
- Bug #11949: Fixed `yii\widgets\ActiveField::end()` generates close tag when it's `option['tag']` is `null` (egorio)
@ -98,8 +112,6 @@ 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 #12828: Fixed handling of nested arrays, objects in `\yii\grid\GridView::guessColumns` (githubjeka)
- Bug #11921: Fixed URL decoding in `yii.getQueryParams()` to handle `+` (plus) character properly (silverfire)
- 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)
@ -126,17 +138,17 @@ Yii Framework 2 Change Log
- Enh #12082: Used `jQuery.on(` instead of event method to ensure forwards compatibility (newerton)
- Enh #12099: `yii\filters\HttpCache` no longer returns 304 HTTP code when callbacks return null (sergeymakinen)
- Enh #12193: Added the ability to suppress the generation of duplicate error messages in `yii\helpers\Html::errorSummary()`. Added the ability to display error messages beyond the first error for each model attribute (PowerGamer1)
- Enh #12198: Added `time` and `datetime` validator short names (nkovacs)
- Enh #12230: Allows BaseHtml::activeListInput to override the field value (RangelReale)
- Enh #12296: Added value validation to `yii\log\Target::setLevels()` (Mak-Di)
- Enh #12376: Added parameter to `yii.activeForm.js` `validate()` method to be able to force validation (DrDeath72)
- Enh #12382: Changed `yii\widgets\MaskedInput` to use `jQuery` instead of `$` to prevent conflicts (samdark)
- 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 #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 #12499: When AJAX validation in enabled, `yii.activeForm.js` will run it forcefully on form submit to display all possible errors (silverfire)
- Enh: Method `yii\console\controllers\AssetController::getAssetManager()` automatically enables `yii\web\AssetManager::forceCopy` in case it is not explicitly specified (pana1990, klimov-paul)
- Enh #12198: Added `time` and `datetime` validator short names (nkovacs)
2.0.9 July 11, 2016
-------------------

2
framework/assets/yii.gridView.js

@ -159,7 +159,7 @@
return;
}
var checkAll = "#" + id + " input[name='" + options.checkAll + "']";
var inputs = options.class ? "input." + options.class : "input[name='" + options.name + "']";
var inputs = options['class'] ? "input." + options['class'] : "input[name='" + options.name + "']";
var inputsEnabled = "#" + id + " " + inputs + ":enabled";
$(document).off('click.yiiGridView', checkAll).on('click.yiiGridView', checkAll, function () {
$grid.find(inputs + ":enabled").prop('checked', this.checked);

2
framework/base/Component.php

@ -13,7 +13,7 @@ use Yii;
* Component is the base class that implements the *property*, *event* and *behavior* features.
*
* Component provides the *event* and *behavior* features, in addition to the *property* feature which is implemented in
* its parent class [[Object]].
* its parent class [[\yii\base\Object|Object]].
*
* Event is a way to "inject" custom code into existing code at certain places. For example, a comment object can trigger
* an "add" event when the user adds a comment. We can write custom code and attach it to this event so that when the event

6
framework/base/Security.php

@ -55,12 +55,12 @@ class Security extends Component
];
/**
* @var string Hash algorithm for key derivation. Recommend sha256, sha384 or sha512.
* @see hash_algos()
* @see [hash_algos()](http://php.net/manual/en/function.hash-algos.php)
*/
public $kdfHash = 'sha256';
/**
* @var string Hash algorithm for message authentication. Recommend sha256, sha384 or sha512.
* @see hash_algos()
* @see [hash_algos()](http://php.net/manual/en/function.hash-algos.php)
*/
public $macHash = 'sha256';
/**
@ -79,7 +79,7 @@ class Security extends Component
* - 'password_hash' - use of PHP `password_hash()` function with PASSWORD_DEFAULT algorithm.
* This option is recommended, but it requires PHP version >= 5.5.0
* - 'crypt' - use PHP `crypt()` function.
* @deprecated Since version 2.0.7, [[generatePasswordHash()]] ignores [[passwordHashStrategy]] and
* @deprecated since version 2.0.7, [[generatePasswordHash()]] ignores [[passwordHashStrategy]] and
* uses `password_hash()` when available or `crypt()` when not.
*/
public $passwordHashStrategy;

2
framework/behaviors/AttributeBehavior.php

@ -67,7 +67,7 @@ class AttributeBehavior extends Behavior
public $attributes = [];
/**
* @var mixed the value that will be assigned to the current attributes. This can be an anonymous function,
* callable in array format (e.g. `[$this, 'methodName']`), an [[Expression]] object representing a DB expression
* callable in array format (e.g. `[$this, 'methodName']`), an [[\yii\db\Expression|Expression]] object representing a DB expression
* (e.g. `new Expression('NOW()')`), scalar, string or an arbitrary value. If the former, the return value of the
* function will be assigned to the attributes.
* The signature of the function should be as follows,

3
framework/behaviors/SluggableBehavior.php

@ -9,6 +9,7 @@ namespace yii\behaviors;
use yii\base\InvalidConfigException;
use yii\db\BaseActiveRecord;
use yii\helpers\ArrayHelper;
use yii\helpers\Inflector;
use yii\validators\UniqueValidator;
use Yii;
@ -139,7 +140,7 @@ class SluggableBehavior extends AttributeBehavior
if ($this->isNewSlugNeeded()) {
$slugParts = [];
foreach ((array) $this->attribute as $attribute) {
$slugParts[] = $this->owner->{$attribute};
$slugParts[] = ArrayHelper::getValue($this->owner, $attribute);
}
$slug = $this->generateSlug($slugParts);

19
framework/caching/Cache.php

@ -71,6 +71,12 @@ abstract class Cache extends Component implements \ArrayAccess
* implementations of the cache can not correctly save and retrieve data different from a string type.
*/
public $serializer;
/**
* @var integer default duration in seconds before a cache entry will expire. Default value is 0, meaning infinity.
* This value is used by [[set()]] if the duration is not explicitly given.
* @since 2.0.11
*/
public $defaultDuration = 0;
/**
@ -185,14 +191,19 @@ abstract class Cache extends Component implements \ArrayAccess
* @param mixed $key a key identifying the value to be cached. This can be a simple string or
* a complex data structure consisting of factors representing the key.
* @param mixed $value the value to be cached
* @param int $duration the number of seconds in which the cached value will expire. 0 means never expire.
* @param int $duration default duration in seconds before the cache will expire. If not set,
* default [[ttl]] 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.
* @return bool whether the value is successfully stored into cache
*/
public function set($key, $value, $duration = 0, $dependency = null)
public function set($key, $value, $duration = null, $dependency = null)
{
if ($duration === null) {
$duration = $this->defaultDuration;
}
if ($dependency !== null && $this->serializer !== false) {
$dependency->evaluateDependency($this);
}
@ -216,7 +227,7 @@ abstract class Cache extends Component implements \ArrayAccess
* @param Dependency $dependency dependency of the cached items. If the dependency changes,
* the corresponding values in the cache will be invalidated when it is fetched via [[get()]].
* This parameter is ignored if [[serializer]] is false.
* @return bool whether the items are successfully stored into cache
* @return array array of failed keys
* @since 2.0.7
*/
public function multiSet($items, $duration = 0, $dependency = null)
@ -249,7 +260,7 @@ abstract class Cache extends Component implements \ArrayAccess
* @param Dependency $dependency dependency of the cached items. If the dependency changes,
* the corresponding values in the cache will be invalidated when it is fetched via [[get()]].
* This parameter is ignored if [[serializer]] is false.
* @return bool whether the items are successfully stored into cache
* @return array array of failed keys
* @since 2.0.7
*/
public function multiAdd($items, $duration = 0, $dependency = null)

4
framework/caching/Dependency.php

@ -58,7 +58,8 @@ abstract class Dependency extends \yii\base\Object
}
/**
* @deprecated since version 2.0.11. Will be removed in version 2.1
* Returns a value indicating whether the dependency has changed.
* @deprecated since version 2.0.11. Will be removed in version 2.1. Use [[isChanged()]] instead.
*/
public function getHasChanged($cache)
{
@ -69,6 +70,7 @@ abstract class Dependency extends \yii\base\Object
* Checks whether the dependency is changed
* @param Cache $cache the cache component that is currently evaluating this dependency
* @return bool whether the dependency has changed.
* @since 2.0.11
*/
public function isChanged($cache)
{

6
framework/caching/MemCache.php

@ -54,7 +54,7 @@ use yii\base\InvalidConfigException;
* each server, such as `persistent`, `weight`, `timeout`. Please see [[MemCacheServer]] for available options.
*
* For more details and usage information on Cache, see the [guide article on caching](guide:caching-overview).
*
*
* @property \Memcache|\Memcached $memcache The memcache (or memcached) object used by this cache component.
* This property is read-only.
* @property MemCacheServer[] $servers List of memcache server configurations. Note that the type of this
@ -286,7 +286,7 @@ class MemCache extends Cache
*
* @param string $key the key identifying the value to be cached
* @param mixed $value the value to be cached.
* @see \MemcachePool::set()
* @see [Memcache::set()](http://php.net/manual/en/memcache.set.php)
* @param int $duration the number of seconds in which the cached value will expire. 0 means never expire.
* @return bool true if the value is successfully stored into cache, false otherwise
*/
@ -327,7 +327,7 @@ class MemCache extends Cache
*
* @param string $key the key identifying the value to be cached
* @param mixed $value the value to be cached
* @see \MemcachePool::set()
* @see [Memcache::set()](http://php.net/manual/en/memcache.set.php)
* @param int $duration the number of seconds in which the cached value will expire. 0 means never expire.
* @return bool true if the value is successfully stored into cache, false otherwise
*/

2
framework/captcha/Captcha.php

@ -46,7 +46,7 @@ use yii\widgets\InputWidget;
* ]);
* ```
*
* You can also use this widget in an [[yii\widgets\ActiveForm|ActiveForm]] using the [[yii\widgets\ActiveField::widget()|widget()]]
* You can also use this widget in an [[\yii\widgets\ActiveForm|ActiveForm]] using the [[\yii\widgets\ActiveField::widget()|widget()]]
* method, for example like this:
*
* ```php

4
framework/captcha/CaptchaValidator.php

@ -84,7 +84,7 @@ class CaptchaValidator extends Validator
/**
* @inheritdoc
*/
public function clientValidateAttribute($object, $attribute, $view)
public function clientValidateAttribute($model, $attribute, $view)
{
$captcha = $this->createCaptchaAction();
$code = $captcha->getVerifyCode(false);
@ -94,7 +94,7 @@ class CaptchaValidator extends Validator
'hashKey' => 'yiiCaptcha/' . $captcha->getUniqueId(),
'caseSensitive' => $this->caseSensitive,
'message' => Yii::$app->getI18n()->format($this->message, [
'attribute' => $object->getAttributeLabel($attribute),
'attribute' => $model->getAttributeLabel($attribute),
], Yii::$app->language),
];
if ($this->skipOnEmpty) {

12
framework/console/Controller.php

@ -274,9 +274,9 @@ class Controller extends \yii\base\Controller
{
if ($this->interactive) {
return Console::prompt($text, $options);
} else {
return isset($options['default']) ? $options['default'] : '';
}
return isset($options['default']) ? $options['default'] : '';
}
/**
@ -291,9 +291,9 @@ class Controller extends \yii\base\Controller
{
if ($this->interactive) {
return Console::confirm($message, $default);
} else {
return true;
}
return true;
}
/**
@ -320,7 +320,7 @@ class Controller extends \yii\base\Controller
* until [[beforeAction()]] is being called.
*
* @param string $actionID the action id of the current request
* @return array the names of the options valid for the action
* @return string[] the names of the options valid for the action
*/
public function options($actionID)
{
@ -336,7 +336,7 @@ class Controller extends \yii\base\Controller
* where the keys is alias name for option and value is option name.
*
* @since 2.0.8
* @see options($actionID)
* @see options()
*/
public function optionAliases()
{

5
framework/console/Markdown.php

@ -54,7 +54,10 @@ class Markdown extends \cebe\markdown\Parser
}
/**
* @inheritdoc
* Render a paragraph block
*
* @param string $block
* @return string
*/
protected function renderParagraph($block)
{

2
framework/console/controllers/CacheController.php

@ -71,7 +71,7 @@ class CacheController extends Controller
public function actionFlush()
{
$cachesInput = func_get_args();
if (empty($cachesInput)) {
throw new Exception('You should specify cache components names');
}

50
framework/console/controllers/MigrateController.php

@ -199,15 +199,49 @@ class MigrateController extends BaseMigrateController
if ($this->db->schema->getTableSchema($this->migrationTable, true) === null) {
$this->createMigrationHistoryTable();
}
$query = new Query;
$rows = $query->select(['version', 'apply_time'])
$query = (new Query())
->select(['version', 'apply_time'])
->from($this->migrationTable)
->orderBy('apply_time DESC, version DESC')
->limit($limit)
->createCommand($this->db)
->queryAll();
$history = ArrayHelper::map($rows, 'version', 'apply_time');
unset($history[self::BASE_MIGRATION]);
->orderBy(['apply_time' => SORT_DESC, 'version' => SORT_DESC]);
if (empty($this->migrationNamespaces)) {
$query->limit($limit);
$rows = $query->all($this->db);
$history = ArrayHelper::map($rows, 'version', 'apply_time');
unset($history[self::BASE_MIGRATION]);
return $history;
}
$rows = $query->all($this->db);
$history = [];
foreach ($rows as $key => $row) {
if ($row['version'] === self::BASE_MIGRATION) {
continue;
}
if (preg_match('/m?(\d{6}_?\d{6})(\D.*)?$/is', $row['version'], $matches)) {
$time = str_replace('_', '', $matches[1]);
$row['canonicalVersion'] = $time;
} else {
$row['canonicalVersion'] = $row['version'];
}
$row['apply_time'] = (int)$row['apply_time'];
$history[] = $row;
}
usort($history, function ($a, $b) {
if ($a['apply_time'] === $b['apply_time']) {
if (($compareResult = strcasecmp($b['canonicalVersion'], $a['canonicalVersion'])) !== 0) {
return $compareResult;
}
return strcasecmp($b['version'], $a['version']);
}
return ($a['apply_time'] > $b['apply_time']) ? -1 : +1;
});
$history = array_slice($history, 0, $limit);
$history = ArrayHelper::map($history, 'version', 'apply_time');
return $history;
}

10
framework/db/ActiveQuery.php

@ -284,10 +284,10 @@ class ActiveQuery extends Query implements ActiveQueryInterface
/**
* Executes query and returns a single row of result.
* @param Connection $db the DB connection used to create the DB command.
* If null, the DB connection returned by [[modelClass]] will be used.
* @param Connection|null $db the DB connection used to create the DB command.
* If `null`, the DB connection returned by [[modelClass]] will be used.
* @return ActiveRecord|array|null a single row of query result. Depending on the setting of [[asArray]],
* the query result may be either an array or an ActiveRecord object. Null will be returned
* the query result may be either an array or an ActiveRecord object. `null` will be returned
* if the query results in nothing.
*/
public function one($db = null)
@ -303,8 +303,8 @@ class ActiveQuery extends Query implements ActiveQueryInterface
/**
* Creates a DB command that can be used to execute this query.
* @param Connection $db the DB connection used to create the DB command.
* If null, the DB connection returned by [[modelClass]] will be used.
* @param Connection|null $db the DB connection used to create the DB command.
* If `null`, the DB connection returned by [[modelClass]] will be used.
* @return Command the created DB command instance.
*/
public function createCommand($db = null)

4
framework/db/ActiveQueryInterface.php

@ -28,11 +28,11 @@ interface ActiveQueryInterface extends QueryInterface
* @return $this the query object itself
*/
public function asArray($value = true);
/**
* Executes query and returns a single row of result.
* @param Connection $db the DB connection used to create the DB command.
* If `null`, the DB connection returned by [[modelClass]] will be used.
* If `null`, the DB connection returned by [[ActiveQueryTrait::$modelClass|modelClass]] will be used.
* @return ActiveRecordInterface|array|null a single row of query result. Depending on the setting of [[asArray]],
* the query result may be either an array or an ActiveRecord object. `null` will be returned
* if the query results in nothing.

6
framework/db/ActiveRecordInterface.php

@ -276,7 +276,7 @@ interface ActiveRecordInterface
* $customer->save();
* ```
*
* @param bool $runValidation whether to perform validation (calling [[Model::validate()|validate()]])
* @param bool $runValidation whether to perform validation (calling [[\yii\base\Model::validate()|validate()]])
* before saving the record. Defaults to `true`. If the validation fails, the record
* will not be saved to the database and this method will return `false`.
* @param array $attributeNames list of attribute names that need to be saved. Defaults to `null`,
@ -297,7 +297,7 @@ interface ActiveRecordInterface
* $customer->insert();
* ```
*
* @param bool $runValidation whether to perform validation (calling [[Model::validate()|validate()]])
* @param bool $runValidation whether to perform validation (calling [[\yii\base\Model::validate()|validate()]])
* before saving the record. Defaults to `true`. If the validation fails, the record
* will not be saved to the database and this method will return `false`.
* @param array $attributes list of attributes that need to be saved. Defaults to `null`,
@ -318,7 +318,7 @@ interface ActiveRecordInterface
* $customer->update();
* ```
*
* @param bool $runValidation whether to perform validation (calling [[Model::validate()|validate()]])
* @param bool $runValidation whether to perform validation (calling [[\yii\base\Model::validate()|validate()]])
* before saving the record. Defaults to `true`. If the validation fails, the record
* will not be saved to the database and this method will return `false`.
* @param array $attributeNames list of attributes that need to be saved. Defaults to `null`,

15
framework/db/ActiveRelationTrait.php

@ -83,15 +83,16 @@ trait ActiveRelationTrait
* Use this method to specify a pivot record/table when declaring a relation in the [[ActiveRecord]] class:
*
* ```php
* public function getOrders()
* class Order extends ActiveRecord
* {
* return $this->hasOne(Order::class, ['id' => 'order_id']);
* }
* public function getOrderItems() {
* return $this->hasMany(OrderItem::class, ['order_id' => 'id']);
* }
*
* public function getOrderItems()
* {
* return $this->hasMany(Item::class, ['id' => 'item_id'])
* ->via('orders');
* public function getItems() {
* return $this->hasMany(Item::class, ['id' => 'item_id'])
* ->via('orderItems');
* }
* }
* ```
*

2
framework/db/BaseActiveRecord.php

@ -947,7 +947,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
* already the new, updated values.
*
* Note that no automatic type conversion performed by default. You may use
* [[yii\behaviors\AttributeTypecastBehavior]] to facilitate attribute typecasting.
* [[\yii\behaviors\AttributeTypecastBehavior]] to facilitate attribute typecasting.
* See http://www.yiiframework.com/doc-2.0/guide-db-active-record.html#attributes-typecasting.
*/
public function afterSave($insert, $changedAttributes)

2
framework/db/Connection.php

@ -283,7 +283,7 @@ class Connection extends Component
'cubrid' => cubrid\Schema::class, // CUBRID
];
/**
* @var string Custom PDO wrapper class. If not set, it will use [[PDO]] or [[yii\db\mssql\PDO]] when MSSQL is used.
* @var string Custom PDO wrapper class. If not set, it will use [[PDO]] or [[\yii\db\mssql\PDO]] when MSSQL is used.
* @see pdo
*/
public $pdoClass;

14
framework/db/mssql/QueryBuilder.php

@ -36,7 +36,7 @@ class QueryBuilder extends \yii\db\QueryBuilder
Schema::TYPE_DOUBLE => 'float',
Schema::TYPE_DECIMAL => 'decimal',
Schema::TYPE_DATETIME => 'datetime',
Schema::TYPE_TIMESTAMP => 'timestamp',
Schema::TYPE_TIMESTAMP => 'datetime',
Schema::TYPE_TIME => 'time',
Schema::TYPE_DATE => 'date',
Schema::TYPE_BINARY => 'varbinary(max)',
@ -65,9 +65,9 @@ class QueryBuilder extends \yii\db\QueryBuilder
/**
* Builds the ORDER BY/LIMIT/OFFSET clauses for SQL SERVER 2012 or newer.
* @param string $sql the existing SQL (without ORDER BY/LIMIT/OFFSET)
* @param array $orderBy the order by columns. See [[Query::orderBy]] for more details on how to specify this parameter.
* @param int $limit the limit number. See [[Query::limit]] for more details.
* @param int $offset the offset number. See [[Query::offset]] for more details.
* @param array $orderBy the order by columns. See [[\yii\db\Query::orderBy]] for more details on how to specify this parameter.
* @param int $limit the limit number. See [[\yii\db\Query::limit]] for more details.
* @param int $offset the offset number. See [[\yii\db\Query::offset]] for more details.
* @param array $params the binding parameters to be populated
* @return string the SQL completed with ORDER BY/LIMIT/OFFSET (if any)
*/
@ -93,9 +93,9 @@ class QueryBuilder extends \yii\db\QueryBuilder
/**
* Builds the ORDER BY/LIMIT/OFFSET clauses for SQL SERVER 2005 to 2008.
* @param string $sql the existing SQL (without ORDER BY/LIMIT/OFFSET)
* @param array $orderBy the order by columns. See [[Query::orderBy]] for more details on how to specify this parameter.
* @param int $limit the limit number. See [[Query::limit]] for more details.
* @param int $offset the offset number. See [[Query::offset]] for more details.
* @param array $orderBy the order by columns. See [[\yii\db\Query::orderBy]] for more details on how to specify this parameter.
* @param int $limit the limit number. See [[\yii\db\Query::limit]] for more details.
* @param int $offset the offset number. See [[\yii\db\Query::offset]] for more details.
* @param array $params the binding parameters to be populated
* @return string the SQL completed with ORDER BY/LIMIT/OFFSET (if any)
*/

1
framework/db/mysql/Schema.php

@ -50,6 +50,7 @@ class Schema extends \yii\db\Schema
'time' => self::TYPE_TIME,
'timestamp' => self::TYPE_TIMESTAMP,
'enum' => self::TYPE_STRING,
'varbinary' => self::TYPE_BINARY,
];

6
framework/di/Instance.php

@ -128,7 +128,11 @@ class Instance
}
if ($reference instanceof self) {
$component = $reference->get($container);
try {
$component = $reference->get($container);
} catch(\ReflectionException $e) {
throw new InvalidConfigException('Failed to instantiate component or class "' . $reference->id . '".', 0, $e);
}
if ($type === null || $component instanceof $type) {
return $component;
} else {

2
framework/filters/Cors.php

@ -207,7 +207,7 @@ class Cors extends ActionFilter
/**
* Adds the CORS headers to the response
* @param Response $response
* @param array CORS headers which have been computed
* @param array $headers CORS headers which have been computed
*/
public function addCorsHeaders($response, $headers)
{

47
framework/helpers/BaseHtml.php

@ -209,7 +209,7 @@ class BaseHtml
/**
* Generates a link tag that refers to an external CSS file.
* @param array|string $url the URL of the external CSS file. This parameter will be processed by [[Url::to()]].
* @param array $options the tag options in terms of name-value pairs. The following option is specially handled:
* @param array $options the tag options in terms of name-value pairs. The following options are specially handled:
*
* - condition: specifies the conditional comments for IE, e.g., `lt IE 9`. When this is specified,
* the generated `link` tag will be enclosed within the conditional comments. This is mainly useful
@ -754,7 +754,13 @@ class BaseHtml
* the labels will also be HTML-encoded.
* @param array $options the tag options in terms of name-value pairs. The following options are specially handled:
*
* - prompt: string, a prompt text to be displayed as the first option;
* - prompt: string, a prompt text to be displayed as the first option. Since version 2.0.11 you can use an array
* to override the value and to set other tag attributes:
*
* ```php
* ['text' => 'Please select', 'options' => ['value' => 'none', 'class' => 'prompt', 'label' => 'Select']],
* ```
*
* - options: array, the attributes for the select option tags. The array keys must be valid option values,
* and the array values are the extra attributes for the corresponding option tags. For example,
*
@ -803,7 +809,13 @@ class BaseHtml
* the labels will also be HTML-encoded.
* @param array $options the tag options in terms of name-value pairs. The following options are specially handled:
*
* - prompt: string, a prompt text to be displayed as the first option;
* - prompt: string, a prompt text to be displayed as the first option. Since version 2.0.11 you can use an array
* to override the value and to set other tag attributes:
*
* ```php
* ['text' => 'Please select', 'options' => ['value' => 'none', 'class' => 'prompt', 'label' => 'Select']],
* ```
*
* - options: array, the attributes for the select option tags. The array keys must be valid option values,
* and the array values are the extra attributes for the corresponding option tags. For example,
*
@ -1477,7 +1489,13 @@ class BaseHtml
* the labels will also be HTML-encoded.
* @param array $options the tag options in terms of name-value pairs. The following options are specially handled:
*
* - prompt: string, a prompt text to be displayed as the first option;
* - prompt: string, a prompt text to be displayed as the first option. Since version 2.0.11 you can use an array
* to override the value and to set other tag attributes:
*
* ```php
* ['text' => 'Please select', 'options' => ['value' => 'none', 'class' => 'prompt', 'label' => 'Select']],
* ```
*
* - options: array, the attributes for the select option tags. The array keys must be valid option values,
* and the array values are the extra attributes for the corresponding option tags. For example,
*
@ -1526,7 +1544,13 @@ class BaseHtml
* the labels will also be HTML-encoded.
* @param array $options the tag options in terms of name-value pairs. The following options are specially handled:
*
* - prompt: string, a prompt text to be displayed as the first option;
* - prompt: string, a prompt text to be displayed as the first option. Since version 2.0.11 you can use an array
* to override the value and to set other tag attributes:
*
* ```php
* ['text' => 'Please select', 'options' => ['value' => 'none', 'class' => 'prompt', 'label' => 'Select']],
* ```
*
* - options: array, the attributes for the select option tags. The array keys must be valid option values,
* and the array values are the extra attributes for the corresponding option tags. For example,
*
@ -1693,11 +1717,18 @@ class BaseHtml
$encodeSpaces = ArrayHelper::remove($tagOptions, 'encodeSpaces', false);
$encode = ArrayHelper::remove($tagOptions, 'encode', true);
if (isset($tagOptions['prompt'])) {
$prompt = $encode ? static::encode($tagOptions['prompt']) : $tagOptions['prompt'];
$promptOptions = ['value' => ''];
if (is_string($tagOptions['prompt'])) {
$promptText = $tagOptions['prompt'];
} else {
$promptText = $tagOptions['prompt']['text'];
$promptOptions = array_merge($promptOptions, $tagOptions['prompt']['options']);
}
$promptText = $encode ? static::encode($promptText) : $promptText;
if ($encodeSpaces) {
$prompt = str_replace(' ', '&nbsp;', $prompt);
$promptText = str_replace(' ', '&nbsp;', $promptText);
}
$lines[] = static::tag('option', $prompt, ['value' => '']);
$lines[] = static::tag('option', $promptText, $promptOptions);
}
$options = isset($tagOptions['options']) ? $tagOptions['options'] : [];

4
framework/helpers/BaseInflector.php

@ -480,8 +480,8 @@ class BaseInflector
* of the helper.
*
* @param string $string input string
* @param string|\Transliterator $transliterator either a [[Transliterator]] or a string
* from which a [[Transliterator]] can be built.
* @param string|\Transliterator $transliterator either a [[\Transliterator]] or a string
* from which a [[\Transliterator]] can be built.
* @return string
* @since 2.0.7 this method is public.
*/

2
framework/helpers/Html.php

@ -11,7 +11,7 @@ namespace yii\helpers;
* Html provides a set of static methods for generating commonly used HTML tags.
*
* Nearly all of the methods in this class allow setting additional html attributes for the html
* tags they generate. You can specify for example. 'class', 'style' or 'id' for an html element
* tags they generate. You can specify, for example, `class`, `style` or `id` for an html element
* using the `$options` parameter. See the documentation of the [[tag()]] method for more details.
*
* For more details and usage information on Html, see the [guide article on html helpers](guide:helper-html).

6
framework/i18n/Formatter.php

@ -672,7 +672,7 @@ class Formatter extends Component
}
try {
if (is_numeric($value)) { // process as unix timestamp, which is always in UTC
$timestamp = new DateTime('@' . $value, new DateTimeZone('UTC'));
$timestamp = new DateTime('@' . (int)$value, new DateTimeZone('UTC'));
return $checkTimeInfo ? [$timestamp, true] : $timestamp;
} elseif (($timestamp = DateTime::createFromFormat('Y-m-d', $value, new DateTimeZone($this->defaultTimeZone))) !== false) { // try Y-m-d format (support invalid dates like 2012-13-01)
return $checkTimeInfo ? [$timestamp, false] : $timestamp;
@ -1131,7 +1131,7 @@ class Formatter extends Component
* @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
* @return string the formatted result.
* @throws InvalidParamException if the input value is not numeric or the formatting failed.
* @see sizeFormat
* @see sizeFormatBase
* @see asSize
*/
public function asShortSize($value, $decimals = null, $options = [], $textOptions = [])
@ -1187,7 +1187,7 @@ class Formatter extends Component
* @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
* @return string the formatted result.
* @throws InvalidParamException if the input value is not numeric or the formatting failed.
* @see sizeFormat
* @see sizeFormatBase
* @see asShortSize
*/
public function asSize($value, $decimals = null, $options = [], $textOptions = [])

4
framework/i18n/MessageFormatter.php

@ -79,7 +79,7 @@ class MessageFormatter extends Component
* @param string $pattern The pattern string to insert parameters into.
* @param array $params The array of name value pairs to insert into the format string.
* @param string $language The locale to use for formatting locale-dependent parts
* @return string|bool The formatted pattern string or `FALSE` if an error occurred
* @return string|false The formatted pattern string or `false` if an error occurred
*/
public function format($pattern, $params, $language)
{
@ -259,7 +259,7 @@ class MessageFormatter extends Component
* @param string $pattern The pattern string to insert things into.
* @param array $args The array of values to insert into the format string
* @param string $locale The locale to use for formatting locale-dependent parts
* @return string|bool The formatted pattern string or `FALSE` if an error occurred
* @return false|string The formatted pattern string or `false` if an error occurred
*/
protected function fallbackFormat($pattern, $args, $locale)
{

2
framework/messages/ja/yii.php

@ -36,7 +36,7 @@ return [
'Please fix the following errors:' => '次のエラーを修正してください :',
'Please upload a file.' => 'ファイルをアップロードしてください。',
'Showing <b>{begin, number}-{end, number}</b> of <b>{totalCount, number}</b> {totalCount, plural, one{item} other{items}}.' => '<b>{totalCount, number}</b> 件中 <b>{begin, number}</b> から <b>{end, number}</b> までを表示しています。',
'The combination {values} of {attributes} has already been taken.' => '',
'The combination {values} of {attributes} has already been taken.' => '{attributes} の {values} という組み合せは既に登録されています。',
'The file "{file}" is not an image.' => 'ファイル "{file}" は画像ではありません。',
'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => 'ファイル "{file}" は大きすぎます。サイズが {formattedLimit} を超えてはいけません。',
'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => 'ファイル "{file}" は小さすぎます。サイズが {formattedLimit} より小さくてはいけません。',

8
framework/rbac/ManagerInterface.php

@ -79,10 +79,10 @@ interface ManagerInterface extends CheckAccessInterface
public function getRolesByUser($userId);
/**
* Returns the roles that are adding to the role via [[addChild()]] by recursive.
* @param string $roleName name of parent Role.
* @return Role[] all roles directly adding to the role. The array is indexed by the role names.
* First element is a Role item that are getting by $roleName.
* Returns child roles of the role specified. Depth isn't limited.
* @param string $roleName name of the role to file child roles for
* @return Role[] Child roles. The array is indexed by the role names.
* First element is an instance of the parent Role itself.
* @throws \yii\base\InvalidParamException if Role was not found that are getting by $roleName
* @since 2.0.10
*/

4
framework/rbac/migrations/m140506_102106_rbac_init.php

@ -53,7 +53,7 @@ class m140506_102106_rbac_init extends \yii\db\Migration
$this->createTable($authManager->ruleTable, [
'name' => $this->string(64)->notNull(),
'data' => $this->text(),
'data' => $this->binary(),
'created_at' => $this->integer(),
'updated_at' => $this->integer(),
'PRIMARY KEY (name)',
@ -64,7 +64,7 @@ class m140506_102106_rbac_init extends \yii\db\Migration
'type' => $this->smallInteger()->notNull(),
'description' => $this->text(),
'rule_name' => $this->string(64),
'data' => $this->text(),
'data' => $this->binary(),
'created_at' => $this->integer(),
'updated_at' => $this->integer(),
'PRIMARY KEY (name)',

6
framework/rbac/migrations/schema-mssql.sql

@ -17,7 +17,7 @@ drop table [auth_rule];
create table [auth_rule]
(
[name] varchar(64) not null,
[data] text,
[data] blob,
[created_at] integer,
[updated_at] integer,
primary key ([name])
@ -29,7 +29,7 @@ create table [auth_item]
[type] smallint not null,
[description] text,
[rule_name] varchar(64),
[data] text,
[data] blob,
[created_at] integer,
[updated_at] integer,
primary key ([name]),
@ -89,4 +89,4 @@ CREATE TRIGGER dbo.trigger_auth_item_child
DELETE FROM dbo.[auth_item_child] WHERE parent IN (SELECT name FROM deleted) OR child IN (SELECT name FROM deleted);
DELETE FROM dbo.[auth_item] WHERE name IN (SELECT name FROM deleted);
END
END;
END;

4
framework/rbac/migrations/schema-mysql.sql

@ -17,7 +17,7 @@ drop table if exists `auth_rule`;
create table `auth_rule`
(
`name` varchar(64) not null,
`data` text,
`data` blob,
`created_at` integer,
`updated_at` integer,
primary key (`name`)
@ -29,7 +29,7 @@ create table `auth_item`
`type` smallint not null,
`description` text,
`rule_name` varchar(64),
`data` text,
`data` blob,
`created_at` integer,
`updated_at` integer,
primary key (`name`),

5
framework/rbac/migrations/schema-oci.sql

@ -18,7 +18,7 @@ drop table "auth_rule";
create table "auth_rule"
(
"name" varchar(64) not null,
"data" varchar(1000),
"data" BYTEA,
"created_at" integer,
"updated_at" integer,
primary key ("name")
@ -31,8 +31,7 @@ create table "auth_item"
"type" smallint not null,
"description" varchar(1000),
"rule_name" varchar(64),
"data" varchar(1000),
"created_at" integer,
"data" BYTEA,
"updated_at" integer,
foreign key ("rule_name") references "auth_rule"("name") on delete set null,
primary key ("name")

4
framework/rbac/migrations/schema-pgsql.sql

@ -17,7 +17,7 @@ drop table if exists "auth_rule";
create table "auth_rule"
(
"name" varchar(64) not null,
"data" text,
"data" bytea,
"created_at" integer,
"updated_at" integer,
primary key ("name")
@ -29,7 +29,7 @@ create table "auth_item"
"type" smallint not null,
"description" text,
"rule_name" varchar(64),
"data" text,
"data" bytea,
"created_at" integer,
"updated_at" integer,
primary key ("name"),

4
framework/rbac/migrations/schema-sqlite.sql

@ -17,7 +17,7 @@ drop table if exists "auth_rule";
create table "auth_rule"
(
"name" varchar(64) not null,
"data" text,
"data" blob,
"created_at" integer,
"updated_at" integer,
primary key ("name")
@ -29,7 +29,7 @@ create table "auth_item"
"type" smallint not null,
"description" text,
"rule_name" varchar(64),
"data" text,
"data" blob,
"created_at" integer,
"updated_at" integer,
primary key ("name"),

2
framework/requirements/YiiRequirementChecker.php

@ -395,6 +395,6 @@ class YiiRequirementChecker
*/
function getNowDate()
{
return @strftime('%Y-%m-%d %H:%M', time());;
return @strftime('%Y-%m-%d %H:%M', time());
}
}

4
framework/validators/EachValidator.php

@ -42,7 +42,7 @@ class EachValidator extends Validator
{
/**
* @var array|Validator definition of the validation rule, which should be used on array values.
* It should be specified in the same format as at [[yii\base\Model::rules()]], except it should not
* It should be specified in the same format as at [[\yii\base\Model::rules()]], except it should not
* contain attribute list as the first element.
* For example:
*
@ -51,7 +51,7 @@ class EachValidator extends Validator
* ['match', 'pattern' => '/[a-z]/is']
* ```
*
* Please refer to [[yii\base\Model::rules()]] for more details.
* Please refer to [[\yii\base\Model::rules()]] for more details.
*/
public $rule;
/**

2
framework/validators/EmailValidator.php

@ -96,7 +96,7 @@ class EmailValidator extends Validator
} else {
$valid = preg_match($this->pattern, $value) || $this->allowName && preg_match($this->fullPattern, $value);
if ($valid && $this->checkDNS) {
$valid = checkdnsrr($matches['domain'], 'MX') || checkdnsrr($matches['domain'], 'A');
$valid = checkdnsrr($matches['domain'] . '.', 'MX') || checkdnsrr($matches['domain'] . '.', 'A');
}
}
}

33
framework/validators/FileValidator.php

@ -222,56 +222,56 @@ class FileValidator extends Validator
/**
* @inheritdoc
*/
protected function validateValue($file)
protected function validateValue($value)
{
if (!$file instanceof UploadedFile || $file->error == UPLOAD_ERR_NO_FILE) {
if (!$value instanceof UploadedFile || $value->error == UPLOAD_ERR_NO_FILE) {
return [$this->uploadRequired, []];
}
switch ($file->error) {
switch ($value->error) {
case UPLOAD_ERR_OK:
if ($this->maxSize !== null && $file->size > $this->getSizeLimit()) {
if ($this->maxSize !== null && $value->size > $this->getSizeLimit()) {
return [
$this->tooBig,
[
'file' => $file->name,
'file' => $value->name,
'limit' => $this->getSizeLimit(),
'formattedLimit' => Yii::$app->formatter->asShortSize($this->getSizeLimit()),
],
];
} elseif ($this->minSize !== null && $file->size < $this->minSize) {
} elseif ($this->minSize !== null && $value->size < $this->minSize) {
return [
$this->tooSmall,
[
'file' => $file->name,
'file' => $value->name,
'limit' => $this->minSize,
'formattedLimit' => Yii::$app->formatter->asShortSize($this->minSize),
],
];
} elseif (!empty($this->extensions) && !$this->validateExtension($file)) {
return [$this->wrongExtension, ['file' => $file->name, 'extensions' => implode(', ', $this->extensions)]];
} elseif (!empty($this->mimeTypes) && !$this->validateMimeType($file)) {
return [$this->wrongMimeType, ['file' => $file->name, 'mimeTypes' => implode(', ', $this->mimeTypes)]];
} elseif (!empty($this->extensions) && !$this->validateExtension($value)) {
return [$this->wrongExtension, ['file' => $value->name, 'extensions' => implode(', ', $this->extensions)]];
} elseif (!empty($this->mimeTypes) && !$this->validateMimeType($value)) {
return [$this->wrongMimeType, ['file' => $value->name, 'mimeTypes' => implode(', ', $this->mimeTypes)]];
}
return null;
case UPLOAD_ERR_INI_SIZE:
case UPLOAD_ERR_FORM_SIZE:
return [$this->tooBig, [
'file' => $file->name,
'file' => $value->name,
'limit' => $this->getSizeLimit(),
'formattedLimit' => Yii::$app->formatter->asShortSize($this->getSizeLimit()),
]];
case UPLOAD_ERR_PARTIAL:
Yii::warning('File was only partially uploaded: ' . $file->name, __METHOD__);
Yii::warning('File was only partially uploaded: ' . $value->name, __METHOD__);
break;
case UPLOAD_ERR_NO_TMP_DIR:
Yii::warning('Missing the temporary folder to store the uploaded file: ' . $file->name, __METHOD__);
Yii::warning('Missing the temporary folder to store the uploaded file: ' . $value->name, __METHOD__);
break;
case UPLOAD_ERR_CANT_WRITE:
Yii::warning('Failed to write the uploaded file to disk: ' . $file->name, __METHOD__);
Yii::warning('Failed to write the uploaded file to disk: ' . $value->name, __METHOD__);
break;
case UPLOAD_ERR_EXTENSION:
Yii::warning('File upload was stopped by some PHP extension: ' . $file->name, __METHOD__);
Yii::warning('File upload was stopped by some PHP extension: ' . $value->name, __METHOD__);
break;
default:
break;
@ -312,6 +312,7 @@ class FileValidator extends Validator
/**
* @inheritdoc
* @param bool $trim
*/
public function isEmpty($value, $trim = false)
{

6
framework/validators/ImageValidator.php

@ -115,11 +115,11 @@ class ImageValidator extends FileValidator
/**
* @inheritdoc
*/
protected function validateValue($file)
protected function validateValue($value)
{
$result = parent::validateValue($file);
$result = parent::validateValue($value);
return empty($result) ? $this->validateImage($file) : $result;
return empty($result) ? $this->validateImage($value) : $result;
}
/**

2
framework/validators/SafeValidator.php

@ -18,6 +18,8 @@ namespace yii\validators;
* does not have a validation rule associated with it - for instance, due to no validation being performed, in which case, you use this class
* as a validation rule for that property. Although it has no functionality, it allows Yii to determine that the property is safe to copy.
*
* > Note: [[when]] property is not supported by SafeValidator.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/

2
framework/validators/UniqueValidator.php

@ -80,7 +80,7 @@ class UniqueValidator extends Validator
/**
* @var string
* @since 2.0.9
* @deprecated Deprecated since version 2.0.10, to be removed in 2.1. Use [[message]] property
* @deprecated since version 2.0.10, to be removed in 2.1. Use [[message]] property
* to setup custom message for multiple target attributes.
*/
public $comboNotUnique;

14
framework/web/AssetManager.php

@ -30,7 +30,7 @@ use yii\helpers\Url;
* ],
* ]
* ```
*
*
* For more details and usage information on AssetManager, see the [guide article on assets](guide:structure-assets).
*
* @property AssetConverterInterface $converter The asset converter. Note that the type of this property
@ -291,9 +291,9 @@ class AssetManager extends Component
/**
* Returns the actual URL for the specified asset.
* The actual URL is obtained by prepending either [[baseUrl]] or [[AssetManager::baseUrl]] to the given asset path.
* The actual URL is obtained by prepending either [[AssetBundle::$baseUrl]] or [[AssetManager::$baseUrl]] to the given asset path.
* @param AssetBundle $bundle the asset bundle which the asset file belongs to
* @param string $asset the asset path. This should be one of the assets listed in [[js]] or [[css]].
* @param string $asset the asset path. This should be one of the assets listed in [[AssetBundle::$js]] or [[AssetBundle::$css]].
* @return string the actual URL for the specified asset.
*/
public function getAssetUrl($bundle, $asset)
@ -327,8 +327,8 @@ class AssetManager extends Component
/**
* Returns the actual file path for the specified asset.
* @param AssetBundle $bundle the asset bundle which the asset file belongs to
* @param string $asset the asset path. This should be one of the assets listed in [[js]] or [[css]].
* @return string|bool the actual file path, or false if the asset is specified as an absolute URL
* @param string $asset the asset path. This should be one of the assets listed in [[AssetBundle::$js]] or [[AssetBundle::$css]].
* @return string|false the actual file path, or `false` if the asset is specified as an absolute URL
*/
public function getAssetPath($bundle, $asset)
{
@ -464,7 +464,7 @@ class AssetManager extends Component
/**
* Publishes a file.
* @param string $src the asset file to be published
* @return array the path and the URL that the asset is published as.
* @return string[] the path and the URL that the asset is published as.
* @throws InvalidParamException if the asset to be published does not exist.
*/
protected function publishFile($src)
@ -509,7 +509,7 @@ class AssetManager extends Component
* it is found in the target directory. This option is used only when publishing a directory.
* This overrides [[forceCopy]] if set.
*
* @return array the path directory and the URL that the asset is published as.
* @return string[] the path directory and the URL that the asset is published as.
* @throws InvalidParamException if the asset to be published does not exist.
*/
protected function publishDirectory($src, $options)

19
framework/web/Request.php

@ -529,9 +529,25 @@ class Request extends \yii\base\Request
/**
* Returns the schema and host part of the current request URL.
*
* The returned URL does not have an ending slash.
* By default this is determined based on the user request information.
*
* By default this value is based on the user request information. This method will
* return the value of `$_SERVER['HTTP_HOST']` if it is available or `$_SERVER['SERVER_NAME']` if not.
* You may want to check out the [PHP documentation](http://php.net/manual/en/reserved.variables.server.php)
* for more information on these variables.
*
* You may explicitly specify it by setting the [[setHostInfo()|hostInfo]] property.
*
* > Warning: Dependent on the server configuration this information may not be
* > reliable and [may be faked by the user sending the HTTP request](https://www.acunetix.com/vulnerabilities/web/host-header-attack).
* > If the webserver is configured to serve the same site independent of the value of
* > the `Host` header, this value is not reliable. In such situations you should either
* > fix your webserver configuration or explicitly set the value by setting the [[setHostInfo()|hostInfo]] property.
*
* @property string|null schema and hostname part (with port number if needed) of the request URL
* (e.g. `http://www.yiiframework.com`), null if can't be obtained from `$_SERVER` and wasn't set.
* See [[getHostInfo()]] for security related notes on this property.
* @return string|null schema and hostname part (with port number if needed) of the request URL
* (e.g. `http://www.yiiframework.com`), null if can't be obtained from `$_SERVER` and wasn't set.
* @see setHostInfo()
@ -560,6 +576,7 @@ class Request extends \yii\base\Request
* This setter is provided in case the schema and hostname cannot be determined
* on certain Web servers.
* @param string|null $value the schema and host part of the application URL. The trailing slashes will be removed.
* @see getHostInfo() for security related notes on this property.
*/
public function setHostInfo($value)
{

46
framework/web/User.php

@ -219,27 +219,20 @@ class User extends Component
/**
* Logs in a user.
*
* After logging in a user, you may obtain the user's identity information from the [[identity]] property.
* If [[enableSession]] is true, you may even get the identity information in the next requests without
* calling this method again.
* After logging in a user:
* - the user's identity information is obtainable from the [[identity]] property
*
* The login status is maintained according to the `$duration` parameter:
* If [[enableSession]] is `true`:
* - the identity information will be stored in session and be available in the next requests
* - in case of `$duration == 0`: as long as the session remains active or till the user closes the browser
* - in case of `$duration > 0`: as long as the session remains active or as long as the cookie
* remains valid by it's `$duration` in seconds when [[enableAutoLogin]] is set `true`.
*
* - `$duration == 0`: the identity information will be stored in session and will be available
* via [[identity]] as long as the session remains active.
* - `$duration > 0`: the identity information will be stored in session. If [[enableAutoLogin]] is true,
* it will also be stored in a cookie which will expire in `$duration` seconds. As long as
* the cookie remains valid or the session is active, you may obtain the user identity information
* via [[identity]].
*
* Note that if [[enableSession]] is false, the `$duration` parameter will be ignored as it is meaningless
* in this case.
* If [[enableSession]] is `false`:
* - the `$duration` parameter will be ignored
*
* @param IdentityInterface $identity the user identity (which should already be authenticated)
* @param int $duration number of seconds that the user can remain in logged-in status.
* Defaults to 0, meaning login till the user closes the browser or the session is manually destroyed.
* If greater than 0 and [[enableAutoLogin]] is true, cookie-based login will be supported.
* Note that if [[enableSession]] is false, this parameter will be ignored.
* @param int $duration number of seconds that the user can remain in logged-in status, defaults to `0`
* @return bool whether the user is logged in
*/
public function login(IdentityInterface $identity, $duration = 0)
@ -413,7 +406,6 @@ class User extends Component
* @return Response the redirection response if [[loginUrl]] is set
* @throws ForbiddenHttpException the "Access Denied" HTTP exception if [[loginUrl]] is not set or a redirect is
* not applicable.
* @see checkAcceptHeader
*/
public function loginRequired($checkAjax = true, $checkAcceptHeader = true)
{
@ -583,7 +575,7 @@ class User extends Component
$this->removeIdentityCookie();
return null;
}
/**
* Removes the identity cookie.
* This method is used when [[enableAutoLogin]] is true.
@ -744,7 +736,21 @@ class User extends Component
}
/**
* Returns the acess checker used for checking access.
* Returns auth manager associated with the user component.
*
* By default this is the `authManager` application component.
* You may override this method to return a different auth manager instance if needed.
* @return \yii\rbac\ManagerInterface
* @since 2.0.6
* @deprecated since version 2.0.9, to be removed in 2.1. Use [[getAccessChecker()]] instead.
*/
protected function getAuthManager()
{
return Yii::$app->getAuthManager();
}
/**
* Returns the access checker used for checking access.
*
* By default this is the `authManager` application component.
*

1
framework/web/ViewAction.php

@ -9,7 +9,6 @@ namespace yii\web;
use Yii;
use yii\base\Action;
use yii\base\InvalidParamException;
use yii\base\ViewNotFoundException;
/**

14
framework/widgets/DetailView.php

@ -73,7 +73,15 @@ class DetailView extends Widget
* - `label`: the label associated with the attribute. If this is not specified, it will be generated from the attribute name.
* - `value`: the value to be displayed. If this is not specified, it will be retrieved from [[model]] using the attribute name
* by calling [[ArrayHelper::getValue()]]. Note that this value will be formatted into a displayable text
* according to the `format` option.
* according to the `format` option. Since version 2.0.11 it can be defined as closure with the following
* parameters:
*
* ```php
* function ($model, $widget)
* ```
*
* `$model` refers to displayed model and `$widget` is an instance of `DetailView` widget.
*
* - `format`: the type of the value that determines how the value would be formatted into a displayable text.
* Please refer to [[Formatter]] for supported types.
* - `visible`: whether the attribute is visible. If set to `false`, the attribute will NOT be displayed.
@ -232,6 +240,10 @@ class DetailView extends Widget
throw new InvalidConfigException('The attribute configuration requires the "attribute" element to determine the value and display label.');
}
if ($attribute['value'] instanceof \Closure) {
$attribute['value'] = call_user_func($attribute['value'], $this->model, $this);
}
$this->attributes[$i] = $attribute;
}
}

18
framework/widgets/LinkPager.php

@ -12,6 +12,7 @@ use yii\base\InvalidConfigException;
use yii\helpers\Html;
use yii\base\Widget;
use yii\data\Pagination;
use yii\helpers\ArrayHelper;
/**
* LinkPager displays a list of hyperlinks that lead to different pages of target.
@ -74,6 +75,18 @@ class LinkPager extends Widget
* @var string the CSS class for the disabled page buttons.
*/
public $disabledPageCssClass = 'disabled';
/**
* @var array the options for the disabled tag to be generated inside the disabled list element.
* In order to customize the html tag, please use the tag key.
*
* ```php
* $disabledListItemSubTagOptions = ['tag' => 'div', 'class' => 'disabled-div'];
* ```
* @since 2.0.11
*/
public $disabledListItemSubTagOptions = [];
/**
* @var int maximum number of page buttons that can be displayed. Defaults to 10.
*/
@ -218,8 +231,9 @@ class LinkPager extends Widget
}
if ($disabled) {
Html::addCssClass($options, $this->disabledPageCssClass);
return Html::tag('li', Html::tag('span', $label), $options);
$tag = ArrayHelper::remove($this->disabledListItemSubTagOptions, 'tag', 'span');
return Html::tag('li', Html::tag($tag, $label, $this->disabledListItemSubTagOptions), $options);
}
$linkOptions = $this->linkOptions;
$linkOptions['data-page'] = $page;

2
framework/widgets/MaskedInput.php

@ -29,7 +29,7 @@ use yii\web\View;
* ]);
* ```
*
* You can also use this widget in an [[yii\widgets\ActiveForm|ActiveForm]] using the [[yii\widgets\ActiveField::widget()|widget()]]
* You can also use this widget in an [[ActiveForm]] using the [[ActiveField::widget()|widget()]]
* method, for example like this:
*
* ```php

41
tests/framework/behaviors/SluggableBehaviorTest.php

@ -44,8 +44,15 @@ class SluggableBehaviorTest extends TestCase
'name' => 'string',
'slug' => 'string',
'category_id' => 'integer',
'belongs_to_id' => 'integer',
];
Yii::$app->getDb()->createCommand()->createTable('test_slug', $columns)->execute();
$columns = [
'id' => 'pk',
'name' => 'string',
];
Yii::$app->getDb()->createCommand()->createTable('test_slug_related', $columns)->execute();
}
public function tearDown()
@ -83,6 +90,25 @@ class SluggableBehaviorTest extends TestCase
/**
* @depends testSlug
*/
public function testSlugRelatedAttribute()
{
$model = new ActiveRecordSluggable();
$model->getBehavior('sluggable')->attribute = 'related.name';
$relatedmodel = new ActiveRecordRelated();
$relatedmodel->name = 'I am an value inside an related activerecord model';
$relatedmodel->save(false);
$model->belongs_to_id = $relatedmodel->id;
$model->validate();
$this->assertEquals('i-am-an-value-inside-an-related-activerecord-model', $model->slug);
}
/**
* @depends testSlug
*/
public function testUniqueByIncrement()
{
$name = 'test name';
@ -176,6 +202,19 @@ class ActiveRecordSluggable extends ActiveRecord
{
return $this->getBehavior('sluggable');
}
public function getRelated()
{
return $this->hasOne(ActiveRecordRelated::class, ['id' => 'belongs_to_id']);
}
}
class ActiveRecordRelated extends ActiveRecord
{
public static function tableName()
{
return 'test_slug_related';
}
}
class ActiveRecordSluggableUnique extends ActiveRecordSluggable
@ -190,4 +229,4 @@ class ActiveRecordSluggableUnique extends ActiveRecordSluggable
],
];
}
}
}

7
tests/framework/caching/CacheTestCase.php

@ -177,6 +177,13 @@ abstract class CacheTestCase extends TestCase
$this->assertEquals(['number_test' => 42, 'non_existent_key' => null], $cache->multiGet(['number_test', 'non_existent_key']));
}
public function testDefaultTtl()
{
$cache = $this->getCacheInstance();
$this->assertSame(0, $cache->defaultDuration);
}
public function testExpire()
{
$cache = $this->getCacheInstance();

56
tests/framework/console/controllers/MigrateControllerTest.php

@ -218,4 +218,60 @@ class MigrateControllerTest extends TestCase
$this->assertCommandCreatedFile('junction_test', $migrationName, 'post_tag');
}
}
/**
* @see https://github.com/yiisoft/yii2/issues/12980
*/
public function testGetMigrationHistory()
{
$controllerConfig = [
'migrationPath' => null,
'migrationNamespaces' => [$this->migrationNamespace]
];
$this->runMigrateControllerAction('history', [], $controllerConfig);
$controller = $this->createMigrateController($controllerConfig);
$controller->db = Yii::$app->db;
Yii::$app->db->createCommand()
->batchInsert(
'migration',
['version', 'apply_time'],
[
['app\migrations\M140506102106One', 10],
['app\migrations\M160909083544Two', 10],
['app\modules\foo\migrations\M161018124749Three', 10],
['app\migrations\M160930135248Four', 20],
['app\modules\foo\migrations\M161025123028Five', 20],
['app\migrations\M161110133341Six', 20],
]
)
->execute();
$rows = $this->invokeMethod($controller, 'getMigrationHistory', [10]);
$this->assertSame(
[
'app\migrations\M161110133341Six',
'app\modules\foo\migrations\M161025123028Five',
'app\migrations\M160930135248Four',
'app\modules\foo\migrations\M161018124749Three',
'app\migrations\M160909083544Two',
'app\migrations\M140506102106One',
],
array_keys($rows)
);
$rows = $this->invokeMethod($controller, 'getMigrationHistory', [4]);
$this->assertSame(
[
'app\migrations\M161110133341Six',
'app\modules\foo\migrations\M161025123028Five',
'app\migrations\M160930135248Four',
'app\modules\foo\migrations\M161018124749Three',
],
array_keys($rows)
);
}
}

8
tests/framework/db/CommandTest.php

@ -138,6 +138,10 @@ abstract class CommandTest extends DatabaseTestCase
public function testBindParamValue()
{
if (defined('HHVM_VERSION') && $this->driverName === 'pgsql') {
$this->markTestSkipped('HHVMs PgSQL implementation has some specific behavior that breaks some parts of this test.');
}
$db = $this->getConnection();
// bindParam
@ -195,12 +199,14 @@ SQL;
$this->assertEquals($floatCol, $row['float_col']);
if ($this->driverName === 'mysql' || $this->driverName === 'sqlite' || $this->driverName === 'oci') {
$this->assertEquals($blobCol, $row['blob_col']);
} elseif (defined('HHVM_VERSION') && $this->driverName === 'pgsql') {
// HHVMs pgsql implementation does not seem to support blob columns correctly.
} else {
$this->assertTrue(is_resource($row['blob_col']));
$this->assertEquals($blobCol, stream_get_contents($row['blob_col']));
}
$this->assertEquals($numericCol, $row['numeric_col']);
if ($this->driverName === 'mysql' || (defined('HHVM_VERSION') && $this->driverName === 'sqlite') || $this->driverName === 'oci') {
if ($this->driverName === 'mysql' || $this->driverName === 'oci' || (defined('HHVM_VERSION') && in_array($this->driverName, ['sqlite', 'pgsql']))) {
$this->assertEquals($boolCol, (int) $row['bool_col']);
} else {
$this->assertEquals($boolCol, $row['bool_col']);

2
tests/framework/db/cubrid/ActiveQueryTest.php

@ -4,7 +4,7 @@ namespace yiiunit\framework\db\cubrid;
/**
* @group db
* @group mysql
* @group cubrid
*/
class ActiveQueryTest extends \yiiunit\framework\db\ActiveQueryTest
{

2
tests/framework/db/mssql/ActiveQueryTest.php

@ -4,7 +4,7 @@ namespace yiiunit\framework\db\mssql;
/**
* @group db
* @group mysql
* @group mssql
*/
class ActiveQueryTest extends \yiiunit\framework\db\ActiveQueryTest
{

2
tests/framework/db/mssql/ExistValidatorTest.php

@ -9,5 +9,5 @@ namespace yiiunit\framework\db\mssql;
*/
class ExistValidatorTest extends \yiiunit\framework\validators\ExistValidatorTest
{
public $driverName = 'mssql';
public $driverName = 'sqlsrv';
}

16
tests/framework/db/mysql/QueryTest.php

@ -1,6 +1,8 @@
<?php
namespace yiiunit\framework\db\mysql;
use yii\db\Expression;
use yii\db\Query;
/**
* @group db
@ -9,4 +11,18 @@ namespace yiiunit\framework\db\mysql;
class QueryTest extends \yiiunit\framework\db\QueryTest
{
protected $driverName = 'mysql';
/**
* Tests MySQL specific syntax for index hints
*/
public function testQueryIndexHint()
{
$db = $this->getConnection();
$query = (new Query)->from([new Expression('{{%customer}} USE INDEX (primary)')]);
$row = $query->one($db);
$this->assertArrayHasKey('id', $row);
$this->assertArrayHasKey('name', $row);
$this->assertArrayHasKey('email', $row);
}
}

12
tests/framework/db/oci/ActiveQueryTest.php

@ -0,0 +1,12 @@
<?php
namespace yiiunit\framework\db\oci;
/**
* @group db
* @group oci
*/
class ActiveQueryTest extends \yiiunit\framework\db\ActiveQueryTest
{
public $driverName = 'oci';
}

2
tests/framework/db/pgsql/ActiveQueryTest.php

@ -4,7 +4,7 @@ namespace yiiunit\framework\db\pgsql;
/**
* @group db
* @group mysql
* @group pgsql
*/
class ActiveQueryTest extends \yiiunit\framework\db\ActiveQueryTest
{

6
tests/framework/db/pgsql/CommandTest.php

@ -74,6 +74,10 @@ class CommandTest extends \yiiunit\framework\db\CommandTest
*/
public function testSaveSerializedObject()
{
if (defined('HHVM_VERSION')) {
$this->markTestSkipped('HHVMs PgSQL implementation does not seem to support blob colums in the way they are used here.');
}
$db = $this->getConnection();
$command = $db->createCommand()->insert('type', [
@ -90,4 +94,4 @@ class CommandTest extends \yiiunit\framework\db\CommandTest
], ['char_col' => 'serialize']);
$this->assertEquals(1, $command->execute());
}
}
}

2
tests/framework/db/sqlite/ActiveQueryTest.php

@ -4,7 +4,7 @@ namespace yiiunit\framework\db\sqlite;
/**
* @group db
* @group mysql
* @group sqlite
*/
class ActiveQueryTest extends \yiiunit\framework\db\ActiveQueryTest
{

24
tests/framework/di/InstanceTest.php

@ -46,7 +46,27 @@ class InstanceTest extends TestCase
$this->assertTrue(Instance::ensure(['class' => 'yii\db\Connection', 'dsn' => 'test'], 'yii\db\Connection', $container) instanceof Connection);
}
public function testEnsureWithoutType()
/**
* ensure an InvalidConfigException is thrown when a component does not exist.
*/
public function testEnsure_NonExistingComponentException()
{
$container = new Container;
$this->setExpectedExceptionRegExp('yii\base\InvalidConfigException', '/^Failed to instantiate component or class/i');
Instance::ensure('cache', 'yii\cache\Cache', $container);
}
/**
* ensure an InvalidConfigException is thrown when a class does not exist.
*/
public function testEnsure_NonExistingClassException()
{
$container = new Container;
$this->setExpectedExceptionRegExp('yii\base\InvalidConfigException', '/^Failed to instantiate component or class/i');
Instance::ensure('yii\cache\DoesNotExist', 'yii\cache\Cache', $container);
}
public function testEnsure_WithoutType()
{
$container = new Container;
$container->set('db', [
@ -59,7 +79,7 @@ class InstanceTest extends TestCase
$this->assertTrue(Instance::ensure(['class' => 'yii\db\Connection', 'dsn' => 'test'], null, $container) instanceof Connection);
}
public function testEnsureMinimalSettings()
public function testEnsure_MinimalSettings()
{
Yii::$container->set('db', [
'class' => 'yii\db\Connection',

18
tests/framework/helpers/HtmlTest.php

@ -603,6 +603,24 @@ EOD;
],
];
$this->assertEqualsWithoutLE(str_replace('&nbsp;', ' ', $expected), Html::renderSelectOptions(['value111', 'value1'], $data, $attributes));
// Attributes for prompt (https://github.com/yiisoft/yii2/issues/7420)
$data = [
'value1' => 'label1',
'value2' => 'label2',
];
$expected = <<<EOD
<option class="prompt" value="-1" label="None">Please select</option>
<option value="value1" selected>label1</option>
<option value="value2">label2</option>
EOD;
$attributes = [
'prompt' => [
'text' => 'Please select', 'options' => ['class' => 'prompt', 'value' => '-1', 'label' => 'None'],
],
];
$this->assertEqualsWithoutLE($expected, Html::renderSelectOptions(['value1'], $data, $attributes));
}
public function testRenderAttributes()

19
tests/framework/i18n/FormatterTest.php

@ -191,4 +191,23 @@ class FormatterTest extends TestCase
// null display
$this->assertSame($this->formatter->nullDisplay, $this->formatter->asBoolean(null));
}
public function testAsTimestamp()
{
$this->assertSame('1451606400', $this->formatter->asTimestamp(1451606400));
$this->assertSame('1451606400', $this->formatter->asTimestamp(1451606400.1234));
$this->assertSame('1451606400', $this->formatter->asTimestamp(1451606400.0000));
$this->assertSame('1451606400', $this->formatter->asTimestamp('1451606400'));
$this->assertSame('1451606400', $this->formatter->asTimestamp('1451606400.1234'));
$this->assertSame('1451606400', $this->formatter->asTimestamp('1451606400.0000'));
$this->assertSame('1451606400', $this->formatter->asTimestamp('2016-01-01 00:00:00'));
$dateTime = new \DateTime('2016-01-01 00:00:00.000');
$this->assertSame('1451606400', $this->formatter->asTimestamp($dateTime));
$dateTime = new \DateTime('2016-01-01 00:00:00.000', new \DateTimeZone('Europe/Berlin'));
$this->assertSame('1451602800', $this->formatter->asTimestamp($dateTime));
}
}

16
tests/framework/rbac/ActionRule.php

@ -2,13 +2,27 @@
namespace yiiunit\framework\rbac;
use yii\rbac\Rule;
/**
* Description of ActionRule
*/
class ActionRule extends \yii\rbac\Rule
class ActionRule extends Rule
{
public $name = 'action_rule';
public $action = 'read';
/**
* Private and protected properties to ensure that serialized object
* does not get corrupted after saving into the DB because of null-bytes
* in the string.
*
* @see https://github.com/yiisoft/yii2/issues/10176
* @see https://github.com/yiisoft/yii2/issues/12681
*/
private $somePrivateProperty;
protected $someProtectedProperty;
public function execute($user, $item, $params)
{
return $this->action === 'all' || $this->action === $params['action'];

18
tests/framework/rbac/ManagerTestCase.php

@ -480,4 +480,22 @@ abstract class ManagerTestCase extends TestCase
$auth->update('Reader', $role);
$this->assertTrue($auth->checkAccess($userId, 'AdminPost', ['action' => 'print']));
}
/**
* https://github.com/yiisoft/yii2/issues/10176
* https://github.com/yiisoft/yii2/issues/12681
*/
public function testRuleWithPrivateFields()
{
$auth = $this->auth;
$auth->removeAll();
$rule = new ActionRule();
$auth->add($rule);
/** @var ActionRule $rule */
$rule = $this->auth->getRule('action_rule');
$this->assertTrue($rule instanceof ActionRule);
}
}

36
tests/framework/validators/UniqueValidatorTest.php

@ -267,4 +267,40 @@ abstract class UniqueValidatorTest extends DatabaseTestCase
$validator->validateAttribute($profileModel, 'description');
$this->assertFalse($profileModel->hasErrors('description'));
}
public function testValidateEmptyAttributeInStringField()
{
ValidatorTestMainModel::deleteAll();
$val = new UniqueValidator();
$m = new ValidatorTestMainModel(['field1' => '']);
$m->id = 1;
$val->validateAttribute($m, 'field1');
$this->assertFalse($m->hasErrors('field1'));
$m->save(false);
$m = new ValidatorTestMainModel(['field1' => '']);
$m->id = 2;
$val->validateAttribute($m, 'field1');
$this->assertTrue($m->hasErrors('field1'));
}
public function testValidateEmptyAttributeInIntField()
{
ValidatorTestRefModel::deleteAll();
$val = new UniqueValidator();
$m = new ValidatorTestRefModel(['ref' => 0]);
$m->id = 1;
$val->validateAttribute($m, 'ref');
$this->assertFalse($m->hasErrors('ref'));
$m->save(false);
$m = new ValidatorTestRefModel(['ref' => 0]);
$m->id = 2;
$val->validateAttribute($m, 'ref');
$this->assertTrue($m->hasErrors('ref'));
}
}

55
tests/framework/validators/ValidatorTest.php

@ -129,40 +129,29 @@ class ValidatorTest extends TestCase
public function testValidateWithEmpty()
{
$val = new TestValidator([
'attributes' => [
'attr_runMe1',
'attr_runMe2',
'attr_empty1',
'attr_empty2'
],
'skipOnEmpty' => true,
]);
$model = $this->getTestModel(['attr_empty1' => '', 'attr_emtpy2' => ' ']);
$val->validateAttributes($model);
$this->assertTrue($val->isAttributeValidated('attr_runMe1'));
$this->assertTrue($val->isAttributeValidated('attr_runMe2'));
$this->assertFalse($val->isAttributeValidated('attr_empty1'));
$this->assertFalse($val->isAttributeValidated('attr_empty2'));
$model = $this->getTestModel(['attr_empty1' => '', 'attr_empty2' => ' ']);
$attributes = ['attr_runMe1', 'attr_runMe2', 'attr_empty1', 'attr_empty2'];
$validator = new TestValidator(['attributes' => $attributes, 'skipOnEmpty' => false]);
$validator->validateAttributes($model);
$this->assertTrue($validator->isAttributeValidated('attr_runMe1'));
$this->assertTrue($validator->isAttributeValidated('attr_runMe2'));
$this->assertTrue($validator->isAttributeValidated('attr_empty1'));
$this->assertTrue($validator->isAttributeValidated('attr_empty2'));
$validator = new TestValidator(['attributes' => $attributes, 'skipOnEmpty' => true]);
$validator->validateAttributes($model);
$this->assertTrue($validator->isAttributeValidated('attr_runMe1'));
$this->assertTrue($validator->isAttributeValidated('attr_runMe2'));
$this->assertFalse($validator->isAttributeValidated('attr_empty1'));
$this->assertTrue($validator->isAttributeValidated('attr_empty2'));
$model->attr_empty1 = 'not empty anymore';
$val->validateAttributes($model);
$this->assertTrue($val->isAttributeValidated('attr_empty1'));
$this->assertFalse($val->isAttributeValidated('attr_empty2'));
$val = new TestValidator([
'attributes' => [
'attr_runMe1',
'attr_runMe2',
'attr_empty1',
'attr_empty2'
],
'skipOnEmpty' => false,
]);
$model = $this->getTestModel(['attr_empty1' => '', 'attr_emtpy2' => ' ']);
$val->validateAttributes($model);
$this->assertTrue($val->isAttributeValidated('attr_runMe1'));
$this->assertTrue($val->isAttributeValidated('attr_runMe2'));
$this->assertTrue($val->isAttributeValidated('attr_empty1'));
$this->assertTrue($val->isAttributeValidated('attr_empty2'));
$validator->validateAttributes($model);
$this->assertTrue($validator->isAttributeValidated('attr_empty1'));
}
public function testIsEmpty()

133
tests/framework/widgets/DetailViewTest.php

@ -2,12 +2,12 @@
/**
* @author Bennet Klarhoelter <boehsermoe@me.com>
*/
namespace yiiunit\framework\widgets;
use yii\base\Arrayable;
use yii\base\ArrayableTrait;
use yii\base\DynamicModel;
use yii\base\Object;
use yii\widgets\ActiveForm;
use yii\widgets\DetailView;
/**
@ -25,6 +25,122 @@ class DetailViewTest extends \yiiunit\TestCase
$this->mockWebApplication();
}
public function testAttributeValue()
{
$model = new ObjectMock();
$model->id = 'id';
$this->detailView = new PublicDetailView([
'model' => $model,
'template' => '{label}:{value}',
'attributes' => [
'id',
[
'attribute' => 'id',
'value' => 1,
],
[
'attribute' => 'id',
'value' => '1',
],
[
'attribute' => 'id',
'value' => $model->getDisplayedId(),
],
[
'attribute' => 'id',
'value' => function ($model) {
return $model->getDisplayedId();
},
],
],
]);
$this->assertEquals('Id:id', $this->detailView->renderAttribute($this->detailView->attributes[0], 0));
$this->assertEquals('Id:1', $this->detailView->renderAttribute($this->detailView->attributes[1], 1));
$this->assertEquals('Id:1', $this->detailView->renderAttribute($this->detailView->attributes[2], 2));
$this->assertEquals('Id:Displayed id', $this->detailView->renderAttribute($this->detailView->attributes[3], 3));
$this->assertEquals('Id:Displayed id', $this->detailView->renderAttribute($this->detailView->attributes[4], 4));
$this->assertEquals(2, $model->getDisplayedIdCallCount());
}
public function testAttributeVisible()
{
$model = new ObjectMock();
$model->id = 'id';
$this->detailView = new PublicDetailView([
'model' => $model,
'template' => '{label}:{value}',
'attributes' => [
[
'attribute' => 'id',
'value' => $model->getDisplayedId(),
],
[
'attribute' => 'id',
'value' => $model->getDisplayedId(),
'visible' => false,
],
[
'attribute' => 'id',
'value' => $model->getDisplayedId(),
'visible' => true,
],
[
'attribute' => 'id',
'value' => function ($model) {
return $model->getDisplayedId();
},
],
[
'attribute' => 'id',
'value' => function ($model) {
return $model->getDisplayedId();
},
'visible' => false,
],
[
'attribute' => 'id',
'value' => function ($model) {
return $model->getDisplayedId();
},
'visible' => true,
],
],
]);
$this->assertEquals([
0 => [
'attribute' => 'id',
'format' => 'text',
'label' => 'Id',
'value' => 'Displayed id',
],
2 => [
'attribute' => 'id',
'format' => 'text',
'label' => 'Id',
'value' => 'Displayed id',
'visible' => true,
],
3 => [
'attribute' => 'id',
'format' => 'text',
'label' => 'Id',
'value' => 'Displayed id',
],
5 => [
'attribute' => 'id',
'format' => 'text',
'label' => 'Id',
'value' => 'Displayed id',
'visible' => true,
]
], $this->detailView->attributes);
$this->assertEquals(5, $model->getDisplayedIdCallCount());
}
public function testRelationAttribute()
{
$model = new ObjectMock();
@ -191,6 +307,7 @@ class ObjectMock extends Object
public $text;
private $_related;
private $_displayedIdCallCount = 0;
public function getRelated()
{
@ -201,6 +318,18 @@ class ObjectMock extends Object
{
$this->_related = $related;
}
public function getDisplayedId()
{
$this->_displayedIdCallCount++;
return "Displayed $this->id";
}
public function getDisplayedIdCallCount()
{
return $this->_displayedIdCallCount;
}
}
class PublicDetailView extends DetailView

30
tests/framework/widgets/LinkPagerTest.php

@ -56,4 +56,34 @@ class LinkPagerTest extends \yiiunit\TestCase
static::assertNotContains('<li class="first">', $output);
static::assertNotContains('<li class="last">', $output);
}
public function testDisabledPageElementOptions()
{
$pagination = new Pagination();
$pagination->setPage(0);
$pagination->totalCount = 50;
$pagination->route = 'test';
$output = LinkPager::widget([
'pagination' => $pagination,
'disabledListItemSubTagOptions' => ['class' => 'foo-bar'],
]);
static::assertContains('<span class="foo-bar">&laquo;</span>', $output);
}
public function testDisabledPageElementOptionsWithTagOption()
{
$pagination = new Pagination();
$pagination->setPage(0);
$pagination->totalCount = 50;
$pagination->route = 'test';
$output = LinkPager::widget([
'pagination' => $pagination,
'disabledListItemSubTagOptions' => ['class' => 'foo-bar', 'tag' => 'div'],
]);
static::assertContains('<div class="foo-bar">&laquo;</div>', $output);
}
}

Loading…
Cancel
Save