Browse Source

Merge branch 'master' into xml-formatter-root-and-object-tags

tags/2.0.11
Alexander Makarov 8 years ago committed by GitHub
parent
commit
c175c6b73a
  1. 2
      .gitignore
  2. 10
      .travis.yml
  3. 7
      docs/guide/db-active-record.md
  4. 20
      docs/guide/db-dao.md
  5. BIN
      docs/guide/images/start-gii-crud-preview.png
  6. BIN
      docs/guide/images/start-gii-crud.png
  7. BIN
      docs/guide/images/start-gii-model-preview.png
  8. BIN
      docs/guide/images/start-gii-model.png
  9. 3
      docs/guide/input-validation.md
  10. 203
      docs/guide/output-client-scripts.md
  11. 35
      docs/guide/rest-resources.md
  12. 2
      docs/guide/runtime-responses.md
  13. 1
      docs/guide/structure-assets.md
  14. 27
      framework/CHANGELOG.md
  15. 10
      framework/UPGRADE.md
  16. 108
      framework/assets/yii.gridView.js
  17. 9
      framework/assets/yii.js
  18. 2
      framework/caching/ArrayCache.php
  19. 4
      framework/caching/FileDependency.php
  20. 2
      framework/composer.json
  21. 2
      framework/console/Markdown.php
  22. 6
      framework/console/Request.php
  23. 1
      framework/data/ActiveDataProvider.php
  24. 9
      framework/db/ActiveRecord.php
  25. 17
      framework/db/Connection.php
  26. 27
      framework/db/Migration.php
  27. 10
      framework/db/Query.php
  28. 7
      framework/db/Transaction.php
  29. 3
      framework/db/pgsql/Schema.php
  30. 2
      framework/di/ServiceLocator.php
  31. 2
      framework/helpers/BaseFileHelper.php
  32. 9
      framework/helpers/BaseJson.php
  33. 21
      framework/i18n/Formatter.php
  34. 23
      framework/messages/fa/yii.php
  35. 12
      framework/rbac/BaseManager.php
  36. 5
      framework/rbac/DbManager.php
  37. 5
      framework/rbac/PhpManager.php
  38. 2
      framework/rest/UrlRule.php
  39. 2
      framework/validators/InlineValidator.php
  40. 2
      framework/widgets/ActiveField.php
  41. 83
      framework/widgets/ListView.php
  42. 7
      framework/widgets/Menu.php
  43. 9
      framework/widgets/Pjax.php
  44. 5
      tests/data/travis/imagick-setup.sh
  45. 6
      tests/framework/ar/ActiveRecordTestTrait.php
  46. 576
      tests/framework/base/SecurityTest.php
  47. 63
      tests/framework/console/RequestTest.php
  48. 44
      tests/framework/console/controllers/MigrateControllerTestTrait.php
  49. 63
      tests/framework/db/QueryBuilderTest.php
  50. 8
      tests/framework/db/QueryTest.php
  51. 11
      tests/framework/db/mssql/QueryBuilderTest.php
  52. 72
      tests/framework/db/mysql/QueryBuilderTest.php
  53. 11
      tests/framework/db/pgsql/QueryBuilderTest.php
  54. 25
      tests/framework/di/ServiceLocatorTest.php
  55. 26
      tests/framework/helpers/ArrayHelperTest.php
  56. 2
      tests/framework/helpers/ConsoleTest.php
  57. 59
      tests/framework/helpers/FileHelperTest.php
  58. 4
      tests/framework/i18n/FormatterNumberTest.php
  59. 10
      tests/framework/rbac/ManagerTestCase.php
  60. 140
      tests/framework/rest/UrlRuleTest.php
  61. 10
      tests/framework/validators/CompareValidatorTest.php
  62. 2
      tests/framework/web/ResponseTest.php
  63. 8
      tests/framework/widgets/ActiveFieldTest.php
  64. 2
      tests/framework/widgets/LinkPagerTest.php
  65. 33
      tests/framework/widgets/ListViewTest.php
  66. 41
      tests/framework/widgets/MenuTest.php
  67. 37
      tests/framework/widgets/PjaxTest.php
  68. 170
      tests/js/data/yii.gridView.html
  69. 754
      tests/js/tests/yii.gridView.test.js

2
.gitignore vendored

@ -35,7 +35,7 @@ phpunit.phar
# local phpunit config
/phpunit.xml
# ignore sub directory for dev installed apps and extensions
# ignore dev installed apps and extensions
/apps
/extensions

10
.travis.yml

@ -72,7 +72,7 @@ matrix:
- phpenv config-rm xdebug.ini || echo "xdebug is not installed"
- travis_retry composer self-update && composer --version
- travis_retry composer global require "fxp/composer-asset-plugin:^1.2.0" --no-plugins
- travis_retry composer update --prefer-dist --no-interaction
- travis_retry composer install --prefer-dist --no-interaction
before_script:
- node --version
- npm --version
@ -110,13 +110,15 @@ install:
- travis_retry composer self-update && composer --version
- travis_retry composer global require "fxp/composer-asset-plugin:^1.2.0" --no-plugins
- export PATH="$HOME/.composer/vendor/bin:$PATH"
# core framework:
- travis_retry composer update --prefer-dist --no-interaction
# core framework:
- travis_retry composer install --prefer-dist --no-interaction
- tests/data/travis/apc-setup.sh
- tests/data/travis/memcache-setup.sh
# - tests/data/travis/cubrid-setup.sh
- tests/data/travis/imagick-setup.sh
before_script:
# Disable the HHVM JIT for faster Unit Testing
- if [[ $TRAVIS_PHP_VERSION = hhv* ]]; then echo 'hhvm.jit = 0' >> /etc/hhvm/php.ini; fi
# show some versions and env information
- php -r "echo INTL_ICU_VERSION . \"\n\";"
- php -r "echo INTL_ICU_DATA_VERSION . \"\n\";"

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

@ -636,9 +636,16 @@ try {
} catch(\Exception $e) {
$transaction->rollBack();
throw $e;
} catch(\Throwable $e) {
$transaction->rollBack();
throw $e;
}
```
> Note: in the above code we have two catch-blocks for compatibility
> with PHP 5.x and PHP 7.x. `\Exception` implements the [`\Throwable` interface](http://php.net/manual/en/class.throwable.php)
> since PHP 7.0, so you can skip the part with `\Exception` if your app uses only PHP 7.0 and higher.
The second way is to list the DB operations that require transactional support in the [[yii\db\ActiveRecord::transactions()]]
method. For example,

20
docs/guide/db-dao.md

@ -328,18 +328,17 @@ The above code is equivalent to the following, which gives you more control abou
```php
$db = Yii::$app->db;
$transaction = $db->beginTransaction();
try {
$db->createCommand($sql1)->execute();
$db->createCommand($sql2)->execute();
// ... executing other SQL statements ...
$transaction->commit();
} catch(\Exception $e) {
$transaction->rollBack();
throw $e;
} catch(\Throwable $e) {
$transaction->rollBack();
throw $e;
}
```
@ -352,6 +351,10 @@ will be triggered and caught, the [[yii\db\Transaction::rollBack()|rollBack()]]
the changes made by the queries prior to that failed query in the transaction. `throw $e` will then re-throw the
exception as if we had not caught it, so the normal error handling process will take care of it.
> Note: in the above code we have two catch-blocks for compatibility
> with PHP 5.x and PHP 7.x. `\Exception` implements the [`\Throwable` interface](http://php.net/manual/en/class.throwable.php)
> since PHP 7.0, so you can skip the part with `\Exception` if your app uses only PHP 7.0 and higher.
### Specifying Isolation Levels <span id="specifying-isolation-levels"></span>
@ -424,12 +427,18 @@ try {
} catch (\Exception $e) {
$innerTransaction->rollBack();
throw $e;
} catch (\Throwable $e) {
$innerTransaction->rollBack();
throw $e;
}
$outerTransaction->commit();
} catch (\Exception $e) {
$outerTransaction->rollBack();
throw $e;
} catch (\Throwable $e) {
$outerTransaction->rollBack();
throw $e;
}
```
@ -573,6 +582,9 @@ try {
} catch(\Exception $e) {
$transaction->rollBack();
throw $e;
} catch(\Throwable $e) {
$transaction->rollBack();
throw $e;
}
```

BIN
docs/guide/images/start-gii-crud-preview.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 59 KiB

BIN
docs/guide/images/start-gii-crud.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 55 KiB

BIN
docs/guide/images/start-gii-model-preview.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 64 KiB

BIN
docs/guide/images/start-gii-model.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 50 KiB

3
docs/guide/input-validation.md

@ -331,7 +331,8 @@ the method/function is:
/**
* @param string $attribute the attribute currently being validated
* @param mixed $params the value of the "params" given in the rule
* @param \yii\validators\InlineValidator related InlineValidator instance
* @param \yii\validators\InlineValidator related InlineValidator instance.
* This parameter is available since version 2.0.11.
*/
function ($attribute, $params, $validator)
```

203
docs/guide/output-client-scripts.md

@ -1,70 +1,84 @@
Working with Client Scripts
===========================
> Note: This section is under development.
Modern web applications, additionally to static HTML pages that are
rendered and sent to the browser, contain JavaScript that is used
to modify the page in the browser by manipulating existing elements or
loading new content via AJAX.
This section describes methods provided by Yii for adding JavaScript and CSS to a website as well as dynamically adjusting these.
### Registering scripts
## Registering scripts <span id="register-scripts"></span>
With the [[yii\web\View]] object you can register scripts. There are two dedicated methods for it:
[[yii\web\View::registerJs()|registerJs()]] for inline scripts and
[[yii\web\View::registerJsFile()|registerJsFile()]] for external scripts.
Inline scripts are useful for configuration and dynamically generated code.
The method for adding these can be used as follows:
When working with the [[yii\web\View]] object you can dynamically register frontend scripts.
There are two dedicated methods for this:
- [[yii\web\View::registerJs()|registerJs()]] for inline scripts
- [[yii\web\View::registerJsFile()|registerJsFile()]] for external scripts
### Registering inline scripts <span id="inline-scripts"></span>
Inline scripts are useful for configuration, dynamically generated code and small snippets created by reusable frontend code contained in [widgets](structure-widgets.md).
The [[yii\web\View::registerJs()|registerJs()]] method for adding these can be used as follows:
```php
$this->registerJs("var options = ".json_encode($options).";", View::POS_END, 'my-options');
$this->registerJs(
"$('#myButton').on('click', function() { alert('Button clicked!'); });",
View::POS_READY,
'my-button-handler'
);
```
The first argument is the actual JS code we want to insert into the page. The second argument
determines where script should be inserted into the page. Possible values are:
The first argument is the actual JS code we want to insert into the page.
It will be wrapped into a `<script>` tag. The second argument
determines at which position the script should be inserted into the page. Possible values are:
- [[yii\web\View::POS_HEAD|View::POS_HEAD]] for head section.
- [[yii\web\View::POS_BEGIN|View::POS_BEGIN]] for right after opening `<body>`.
- [[yii\web\View::POS_END|View::POS_END]] for right before closing `</body>`.
- [[yii\web\View::POS_READY|View::POS_READY]] for executing code on document `ready` event. This will register [[yii\web\JqueryAsset|jQuery]] automatically.
- [[yii\web\View::POS_LOAD|View::POS_LOAD]] for executing code on document `load` event. This will register [[yii\web\JqueryAsset|jQuery]] automatically.
- [[yii\web\View::POS_READY|View::POS_READY]] for executing code on the [document `ready` event](http://learn.jquery.com/using-jquery-core/document-ready/).
This will automatically register [[yii\web\JqueryAsset|jQuery]] and wrap the code into the appropriate jQuery code. This is the default position.
- [[yii\web\View::POS_LOAD|View::POS_LOAD]] for executing code on the
[document `load` event](http://learn.jquery.com/using-jquery-core/document-ready/). Same as the above, this will also register [[yii\web\JqueryAsset|jQuery]] automatically.
The last argument is a unique script ID that is used to identify code block and replace existing one with the same ID
instead of adding a new one. If you don't provide it, the JS code itself will be used as the ID.
The last argument is a unique script ID that is used to identify the script code block and replace an existing one with the same ID
instead of adding a new one. If you don't provide it, the JS code itself will be used as the ID. It is used to avoid registration of the same code muliple times.
An external script can be added like the following:
```php
$this->registerJsFile('http://example.com/js/main.js', ['depends' => [\yii\web\JqueryAsset::className()]]);
```
### Registering script files <span id="script-files"></span>
The arguments for [[yii\web\View::registerJsFile()|registerJsFile()]] are similar to those for
[[yii\web\View::registerCssFile()|registerCssFile()]]. In the above example,
we register the `main.js` file with the dependency on `JqueryAsset`. This means the `main.js` file
will be added AFTER `jquery.js`. Without this dependency specification, the relative order between
`main.js` and `jquery.js` would be undefined.
Like for [[yii\web\View::registerCssFile()|registerCssFile()]], it is also highly recommended that you use
[asset bundles](structure-assets.md) to register external JS files rather than using [[yii\web\View::registerJsFile()|registerJsFile()]].
we register the `main.js` file with the dependency on the [[yii\web\JqueryAsset]]. It means that the `main.js` file
will be added AFTER `jquery.js`. Without such dependency specification, the relative order between
`main.js` and `jquery.js` would be undefined and the code would not work.
### Registering asset bundles
As was mentioned earlier it's preferred to use asset bundles instead of using CSS and JavaScript directly. You can get
details on how to define asset bundles in [asset manager](structure-assets.md) section of the guide. As for using already defined
asset bundle, it's very straightforward:
An external script can be added like the following:
```php
\frontend\assets\AppAsset::register($this);
$this->registerJsFile(
'@web/js/main.js',
['depends' => [\yii\web\JqueryAsset::className()]]
);
```
This will add a tag for the `/js/main.js` script located under the application [base URL](concept-aliases.md#predefined-aliases).
It is highly recommended to use [asset bundles](structure-assets.md) to register external JS files rather than [[yii\web\View::registerJsFile()|registerJsFile()]] because these allow better flexibility and more granular dependency configuration. Also using asset bundles allows you to combine and compress
multiple JS files, which is desirable for high traffic websites.
## Registering CSS <span id="register-css"></span>
### Registering CSS
Similar to Javascript, you can register CSS using
[[yii\web\View::registerCss()|registerCss()]] or
[[yii\web\View::registerCssFile()|registerCssFile()]].
The former registers a block of CSS code while the latter registers an external CSS file.
You can register CSS using [[yii\web\View::registerCss()|registerCss()]] or [[yii\web\View::registerCssFile()|registerCssFile()]].
The former registers a block of CSS code while the latter registers an external CSS file. For example,
### Registering inline CSS <span id="inline-css"></span>
```php
$this->registerCss("body { background: #f00; }");
```
The code above will result in adding the following to the head section of the page:
The code above will result in adding the following to the `<head>` section of the page:
```html
<style>
@ -73,26 +87,127 @@ body { background: #f00; }
```
If you want to specify additional properties of the style tag, pass an array of name-values to the second argument.
If you need to make sure there's only a single style tag use third argument as was mentioned in meta tags description.
The last argument is a unique ID that is used to identify the style block and make sure it is only added once in case the same style is registered from different places in the code.
### Registering CSS files <span id="css-files"></span>
A CSS file can be registered using the following:
```php
$this->registerCssFile("http://example.com/css/themes/black-and-white.css", [
'depends' => [BootstrapAsset::className()],
$this->registerCssFile("@web/css/themes/black-and-white.css", [
'depends' => [\yii\bootstrap\BootstrapAsset::className()],
'media' => 'print',
], 'css-print-theme');
```
The code above will add a link to CSS file to the head section of the page.
The above code will add a link to the `/css/themes/black-and-white.css` CSS file to the `<head>` section of the page.
* The first argument specifies the CSS file to be registered.
The `@web` in this example is an [alias for the applications base URL](concept-aliases.md#predefined-aliases).
* The second argument specifies the HTML attributes for the resulting `<link>` tag. The option `depends`
is specially handled. It specifies which asset bundles this CSS file depends on. In this case, the dependent
asset bundle is [[yii\bootstrap\BootstrapAsset|BootstrapAsset]]. This means the CSS file will be added
*after* the CSS files in [[yii\bootstrap\BootstrapAsset|BootstrapAsset]].
*after* the CSS files from [[yii\bootstrap\BootstrapAsset|BootstrapAsset]].
* The last argument specifies an ID identifying this CSS file. If it is not provided, the URL of the CSS file will be
used instead.
It is highly recommended that you use [asset bundles](structure-assets.md) to register external CSS files rather than
using [[yii\web\View::registerCssFile()|registerCssFile()]]. Using asset bundles allows you to combine and compress
It is highly recommended to use [asset bundles](structure-assets.md) to register external CSS files rather than
[[yii\web\View::registerCssFile()|registerCssFile()]]. Using asset bundles allows you to combine and compress
multiple CSS files, which is desirable for high traffic websites.
It also provides more flexibility as all asset dependencies of your application are configured in one place.
## Registering asset bundles <span id="asset-bundles"></span>
As was mentioned earlier it's recommended to use asset bundles instead of registering CSS and JavaScript files directly.
You can get details on how to define asset bundles in the
["Assets" section](structure-assets.md).
As for using already defined asset bundles, it's very straightforward:
```php
\frontend\assets\AppAsset::register($this);
```
In the above code, in the context of a view file, the `AppAsset` bundle is registered on the current view (represented by `$this`).
When registering asset bundles from within a widget, you would pass the
[[yii\base\Widget::$view|$view]] of the widget instead (`$this->view`).
## Generating Dynamic Javascript <span id="dynamic-js"></span>
In view files often the HTML code is not written out directly but generated
by some PHP code dependent on the variables of the view.
In order for the generated HTML to be manipulated with Javascript, the JS code has to contain dynamic parts too, for example the IDs of the jQuery selectors.
To insert PHP variables into JS code, their values have to be
escaped properly. Especially when the JS code is inserted into
HTML instead of residing in a dedicated JS file.
Yii provides the [[yii\helpers\Json::htmlEncode()|htmlEncode()]] method of the [[yii\helpers\Json|Json]] helper for this purpose. Its usage will be shown in the following examples.
### Registering a global JavaScript configuration <span id="js-configuration"></span>
In this example we use an array to pass global configuration parameters from
the PHP part of the application to the JS frontend code.
```php
$options = [
'appName' => Yii::$app->name,
'baseUrl' => Yii::$app->request->baseUrl,
'language' => Yii::$app->language,
// ...
];
$this->registerJs(
"var yiiOptions = ".\yii\helpers\Json::htmlEncode($options).";",
View::POS_HEAD,
'yiiOptions'
);
```
The above code will register a `<script>`-tag containing the JavaScript
variable definition, e.g.:
```javascript
var yiiOptions = {"appName":"My Yii Application","baseUrl":"/basic/web","language":"en"};
```
In your Javascript code you can now access these like `yiiOptions.baseUrl` or `yiiOptions.language`.
### Passing translated messages <span id="translated-messages"></span>
You may encounter a case where your JavaScript needs to print a message reacting to some event. In an application that works with multiple languages this string has to be translated to the current application language.
One way to achieve this is to use the
[message translation feature](tutorial-i18n.md#message-translation) provided by Yii and passing the result to the JavaScript code.
```php
$message = \yii\helpers\Json::htmlEncode(
\Yii::t('app', 'Button clicked!')
);
$this->registerJs(<<<JS
$('#myButton').on('click', function() { alert( $message ); });",
JS
);
```
The above example code uses PHP
[Heredoc syntax](http://php.net/manual/de/language.types.string.php#language.types.string.syntax.heredoc) for better readability. This also enables better syntax highlighting in most IDEs so it is the
preferred way of writing inline JavaScript, especially useful for code that is longer than a single line. The variable `$message` is created in PHP and
thanks to [[yii\helpers\Json::htmlEncode|Json::htmlEncode]] it contains the
string in valid JS syntax, which can be inserted into the JavaScript code to place the dynamic string in the function call to `alert()`.
> Note: When using Heredoc, be careful with variable naming in JS code
> as variables beginning with `$` may be interpreted as PHP variables which
> will be replaced by their content.
> The jQuery function in form of `$(` or `$.` is not interpreted
> as a PHP variable and can safely be used.
## The `yii.js` script <span id="yii.js"></span>
> Note: This section has not been written yet. It should contain explanation of the functionality provided by `yii.js`:
>
> - Yii JavaScript Modules
> - CSRF param handling
> - `data-confirm` handler
> - `data-method` handler
> - script filtering
> - redirect handling

35
docs/guide/rest-resources.md

@ -146,23 +146,41 @@ contains a single method [[yii\web\Linkable::getLinks()|getLinks()]] which shoul
Typically, you should return at least the `self` link representing the URL to the resource object itself. For example,
```php
use yii\db\ActiveRecord;
use yii\web\Link;
use yii\base\Model;
use yii\web\Link; // represents a link object as defined in JSON Hypermedia API Language.
use yii\web\Linkable;
use yii\helpers\Url;
class User extends ActiveRecord implements Linkable
class UserResource extends Model implements Linkable
{
public $id;
public $email;
//...
public function fields()
{
return ['id', 'email'];
}
public function extraFields()
{
return ['profile'];
}
public function getLinks()
{
return [
Link::REL_SELF => Url::to(['user/view', 'id' => $this->id], true),
'edit' => Url::to(['user/view', 'id' => $this->id], true),
'profile' => Url::to(['user/profile/view', 'id' => $this->id], true),
'index' => Url::to(['users'], true),
];
}
}
```
When a `User` object is returned in a response, it will contain a `_links` element representing the links related
When a `UserResource` object is returned in a response, it will contain a `_links` element representing the links related
to the user, for example,
```
@ -173,6 +191,15 @@ to the user, for example,
"_links" => {
"self": {
"href": "https://example.com/users/100"
},
"edit": {
"href": "https://example.com/users/100"
},
"profile": {
"href": "https://example.com/users/profile/100"
},
"index": {
"href": "https://example.com/users"
}
}
}

2
docs/guide/runtime-responses.md

@ -195,7 +195,7 @@ redirect the browser accordingly.
> Info: Yii comes with a `yii.js` JavaScript file which provides a set of commonly used JavaScript utilities,
including browser redirection based on the `X-Redirect` header. Therefore, if you are using this JavaScript file
(by registering the [[yii\web\YiiAsset]] asset bundle), you do not need to write anything to support AJAX redirection.
More information about `yii.js` can be found in the [Client Scripts Section](output-client-scripts.md#yii.js).
## Sending Files <span id="sending-files"></span>

1
docs/guide/structure-assets.md

@ -490,6 +490,7 @@ be referenced in your application or extension code.
- [[yii\web\YiiAsset]]: It mainly includes the `yii.js` file which implements a mechanism of organizing JavaScript code
in modules. It also provides special support for `data-method` and `data-confirm` attributes and other useful features.
More information about `yii.js` can be found in the [Client Scripts Section](output-client-scripts.md#yii.js).
- [[yii\web\JqueryAsset]]: It includes the `jquery.js` file from the jQuery Bower package.
- [[yii\bootstrap\BootstrapAsset]]: It includes the CSS file from the Twitter Bootstrap framework.
- [[yii\bootstrap\BootstrapPluginAsset]]: It includes the JavaScript file from the Twitter Bootstrap framework for

27
framework/CHANGELOG.md

@ -5,10 +5,12 @@ Yii Framework 2 Change Log
------------------------
- Bug #4113: Error page stacktrace was generating links to private methods which are not part of the API docs (samdark)
- Bug #7727: Fixed truncateHtml leaving extra tags (developeruz)
- Bug #7727: Fixed `yii\helpers\StringHelper::truncateHtml()` leaving extra tags (developeruz)
- Bug #9305: Fixed MSSQL `Schema::TYPE_TIMESTAMP` to be 'datetime' instead of 'timestamp', which is just an incremental number (nkovacs)
- Bug #9616: Fixed mysql\Schema::loadColumnSchema to set enumValues attribute correctly if enum definition contains commas (fphammerle)
- Bug #9796: Initialization of not existing `yii\grid\ActionColumn` default buttons (arogachev)
- Bug #11122: Fixed can not use `orderBy` with aggregate functions like `count` (Ni-san)
- Bug #11771: Fixed semantics of `yii\di\ServiceLocator::__isset()` to match the behavior of `__get()` which fixes inconsistent behavior on newer PHP versions (cebe)
- Bug #12213: Fixed `yii\db\ActiveRecord::unlinkAll()` to respect `onCondition()` of the relational query (silverfire)
- Bug #12681: Changed `data` column type from `text` to `blob` to handle null-byte (`\0`) in serialized RBAC rule properly (silverfire)
- Bug #12714: Fixed `yii\validation\EmailValidator` to prevent false-positives checks when property `checkDns` is set to `true` (silverfire)
@ -20,7 +22,7 @@ Yii Framework 2 Change Log
- 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 #12836: Fixed `yii\widgets\GridView::filterUrl` to not ignore `#` part of filter URL (cebe, arogachev)
- 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)
@ -30,10 +32,15 @@ Yii Framework 2 Change Log
- Bug #13071: Help option for commands was not working in modules (arogachev, haimanman)
- Bug #13089: Fixed `yii\console\controllers\AssetController::adjustCssUrl()` breaks URL reference specification (`url(#id)`) (vitalyzhakov)
- Bug #13105: Fixed `validate()` method in `yii.activeForm.js` to prevent unexpected form submit when `forceValidate` set to `true` (silverfire)
- Bug #13118: Fixed `handleAction()` function in `yii.js` to handle attribute `data-pjax=0` as disabled PJAX (silverfire)
- Bug #13118: Fixed `handleAction()` function in `yii.js` to handle attribute `data-pjax=0` as disabled PJAX (silverfire, arisk)
- Bug #13128: Fixed incorrect position of {pos} string in ColumnSchemaBuilder `__toString` (df2)
- Bug #13159: Fixed `destroy` method in `yii.captcha.js` which did not work as expected (arogachev)
- Bug #13198: Fixed order of checks in `yii\validators\IpValidator` that sometimes caused wrong error message (silverfire)
- Bug #13200: Creating URLs for routes specified in `yii\rest\UrlRule::$extraPatterns` did not work if no HTTP verb was specified (cebe)
- Bug #13229: Fix fetching table schema for `pgsql` when `PDO::ATTR_CASE` is set (klimov-paul)
- Bug #13231: Fixed `destroy` method in `yii.gridView.js` which did not work as expected (arogachev)
- Bug #13232: Event handlers were not detached with changed selector in `yii.gridView.js` (arogachev)
- Bug #13108: Fix execute command with negative integer parameter (pana1990, uaoleg)
- Enh #475: Added Bash and Zsh completion support for the `./yii` command (cebe, silverfire)
- Enh #6242: Access to validator in inline validation (arogachev)
- Enh #6373: Introduce `yii\db\Query::emulateExecution()` to force returning an empty result for a query (klimov-paul)
@ -57,10 +64,11 @@ Yii Framework 2 Change Log
- Enh #12619: Added catch `Throwable` in `yii\base\ErrorHandler::handleException()` (rob006)
- Enh #12659: Suggest alternatives when console command was not found (mdmunir, cebe)
- Enh #12726: `yii\base\Application::$version` converted to `yii\base\Module::$version` virtual property, allowing to specify version as a PHP callback (klimov-paul)
- Enh #12732: Added `is_dir()` validation to `yii\helpers\BaseFileHelper::findFiles()` method (zalatov, silverfire)
- Enh #12738: Added support for creating protocol-relative URLs in `UrlManager::createAbsoluteUrl()` and `Url` helper methods (rob006)
- Enh #12748: Added Migration tool automatic generation reference column for foreignKey (MKiselev)
- Enh #12748: Migration generator now tries to fetch reference column name for foreignKey from schema if it's not set explicitly (MKiselev)
- Enh #12750: `yii\widgets\ListView::itemOptions` can be a closure now (webdevsega, silverfire)
- Enh #12771: Skip \yii\rbac\PhpManager::checkAccessRecursive and \yii\rbac\DbManager::checkAccessRecursive if role assignments are empty (Ni-san)
- Enh #12790: Added `scrollToErrorOffset` option for `yii\widgets\ActiveForm` which adds ability to specify offset in pixels when scrolling to error (mg-code)
- Enh #12798: Changed `yii\cache\Dependency::getHasChanged()` (deprecated, to be removed in 2.1) to `yii\cache\Dependency::isChanged()` (dynasource)
- Enh #12807: Added console controller checks for `yii\console\controllers\HelpController` (schmunk42)
@ -68,12 +76,13 @@ Yii Framework 2 Change Log
- Enh #12854: Added `RangeNotSatisfiableHttpException` to cover HTTP error 416 file request exceptions (zalatov)
- Enh #12881: Added `removeValue` method to `yii\helpers\BaseArrayHelper` (nilsburg)
- Enh #12901: Added `getDefaultHelpHeader` method to the `yii\console\controllers\HelpController` class to be able to override default help header in a class heir (diezztsk)
- Enh #12988: Changed `textarea` method within the `yii\helpers\BaseHtml` class to allow users to control whether html entities found within `$value` will be double-encoded or not (cyphix333)
- Enh #12988: Changed `textarea` method within the `yii\helpers\BaseHtml` class to allow users to control whether HTML entities found within `$value` will be double-encoded or not (cyphix333)
- Enh #13020: Added `disabledListItemSubTagOptions` attribute for `yii\widgets\LinkPager` in order to customize the disabled list item sub tag element (nadar)
- Enh #13035: Use ArrayHelper::getValue() in SluggableBehavior::getValue() (thyseus)
- Enh #13036: Added shortcut methods `asJson()` and `asXml()` for returning JSON and XML data in web controller actions (cebe)
- Enh #13050: Added `yii\filters\HostControl` allowing protection against 'host header' attacks (klimov-paul, rob006)
- Enh #13074: Improved `yii\log\SyslogTarget` with `$options` to be able to change the default `openlog` options. (timbeks)
- Enh #13074: Improved `yii\log\SyslogTarget` with `$options` to be able to change the default `openlog` options (timbeks)
- Bug: #12969: Improved unique ID generation for `yii\widgets\Pjax` widgets (dynasource, samdark, rob006)
- Enh #13122: Optimized query for information about foreign keys in `yii\db\oci` (zlakomanoff)
- Enh #13202: Refactor validateAttribute method in UniqueValidator (developeruz)
- Enh: Added constants for specifying `yii\validators\CompareValidator::$type` (cebe)
@ -129,6 +138,7 @@ Yii Framework 2 Change Log
- Bug #12605: Make 'safe' validator work on write-only properties (arthibald, CeBe)
- Bug #12629: Fixed `yii\widgets\ActiveField::widget()` to call `adjustLabelFor()` for `InputWidget` descendants (coderlex)
- Bug #12649: Fixed consistency of `indexBy` handling for `yii\db\Query::column()` (silverfire)
- Bug #12713: Fixed `yii\caching\FileDependency` to clear stat cache before reading filemtime (SG5)
- Enh #384: Added ability to run migration from several locations via `yii\console\controllers\BaseMigrateController::$migrationNamespaces` (klimov-paul)
- Enh #6996: Added `yii\web\MultipartFormDataParser`, which allows proper processing of 'multipart/form-data' encoded non POST requests (klimov-paul)
- Enh #8719: Add support for HTML5 attributes on submitbutton (formaction/formmethod...) for ActiveForm (VirtualRJ)
@ -163,11 +173,13 @@ Yii Framework 2 Change Log
- Enh #12440: Added `yii\base\Event::offAll()` method allowing clear all registered class-level event handlers (klimov-paul)
- Enh #12499: When AJAX validation in enabled, `yii.activeForm.js` will run it forcefully on form submit to display all possible errors (silverfire)
- Enh #12580: Make `yii.js` comply with strict and non-strict javascript mode to allow concatenation with external code (mikehaertl)
- Enh #12612: Query conditions added with `yii\db\Query::andWhere()` now get appended to the existing conditions if they were already being joined with the `and` operator (brandonkelly)
- Enh #12664: Added support for wildcards for `optional` at `yii\filters\auth\AuthMethod` (mg-code)
- Enh #12744: Added `afterInit` event to `yii.activeForm.js` (werew01f)
- Enh #12710: Added `beforeItem` and `afterItem` to `yii\widgets\ListView` (mdmunir, silverfire)
- Enh #12727: Enhanced `yii\widgets\Menu` to allow item option `active` be a Closure (voskobovich, silverfire)
- Enh: Method `yii\console\controllers\AssetController::getAssetManager()` automatically enables `yii\web\AssetManager::forceCopy` in case it is not explicitly specified (pana1990, klimov-paul)
2.0.9 July 11, 2016
-------------------
@ -228,6 +240,7 @@ Yii Framework 2 Change Log
- Enh #11857: `yii\filters\AccessRule::$verbs` can now be configured in upper and lowercase (DrDeath72, samdark)
- Chg #11364: Updated jQuery dependency to include versions `1.12.*` (cebe)
- Chg #11683: Fixed fixture command to work with short syntax. `yii fixture "*, -User"` should be used instead of `yii fixture "*" -User` (Faryshta, samdark)
- Chg #11906: Updated `yii\widgets\MaskedInput` inputmask dependency to `~3.3.3` (samdark)
2.0.8 April 28, 2016

10
framework/UPGRADE.md

@ -59,6 +59,16 @@ Upgrade from Yii 2.0.10
* `yii\validators\FileValidator::getClientOptions()` and `yii\validators\ImageValidator::getClientOptions()` are now public.
If you extend from these classes and override these methods, you must make them public as well.
* `yii\widgets\MaskedInput` inputmask dependency was updated to `~3.3.3`.
[See its changelog for details](https://github.com/RobinHerbots/Inputmask/blob/3.x/CHANGELOG.md).
* PJAX: Auto generated IDs of the Pjax widget have been changed to use their own prefix to avoid conflicts.
Auto generated IDs are now prefixed with `p` instead of `w`. This is defined by the `$autoIdPrefix`
property of `yii\widgets\Pjax`. If you have any PHP or Javascript code that depends on autogenerated IDs
you should update these to match this new value. It is not a good idea to rely on auto generated values anyway, so
you better fix these cases by specifying an explicit ID.
Upgrade from Yii 2.0.9
----------------------

108
framework/assets/yii.gridView.js

@ -16,7 +16,7 @@
} else if (typeof method === 'object' || !method) {
return methods.init.apply(this, arguments);
} else {
$.error('Method ' + method + ' does not exist on jQuery.yiiGridView');
$.error('Method ' + method + ' does not exist in jQuery.yiiGridView');
return false;
}
};
@ -50,6 +50,32 @@
afterFilter: 'afterFilter'
};
/**
* Used for storing active event handlers and removing them later.
* The structure of single event handler is:
*
* {
* gridViewId: {
* type: {
* event: '...',
* selector: '...'
* }
* }
* }
*
* Used types:
*
* - filter, used for filtering grid with elements found by filterSelector
* - checkRow, used for checking single row
* - checkAllRows, used for checking all rows with according "Check all" checkbox
*
* event is the name of event, for example: 'change.yiiGridView'
* selector is a jQuery selector for finding elements
*
* @type {{}}
*/
var gridEventHandlers = {};
var methods = {
init: function (options) {
return this.each(function () {
@ -62,32 +88,32 @@
gridData[id] = $.extend(gridData[id], {settings: settings});
var filterEvents = 'change.yiiGridView keydown.yiiGridView';
var enterPressed = false;
$(document).off('change.yiiGridView keydown.yiiGridView', settings.filterSelector)
.on('change.yiiGridView keydown.yiiGridView', settings.filterSelector, function (event) {
if (event.type === 'keydown') {
if (event.keyCode !== 13) {
return; // only react to enter key
} else {
enterPressed = true;
}
initEventHandler($e, 'filter', filterEvents, settings.filterSelector, function (event) {
if (event.type === 'keydown') {
if (event.keyCode !== 13) {
return; // only react to enter key
} else {
// prevent processing for both keydown and change events
if (enterPressed) {
enterPressed = false;
return;
}
enterPressed = true;
}
} else {
// prevent processing for both keydown and change events
if (enterPressed) {
enterPressed = false;
return;
}
}
methods.applyFilter.apply($e);
methods.applyFilter.apply($e);
return false;
});
return false;
});
});
},
applyFilter: function () {
var $grid = $(this), event;
var $grid = $(this);
var settings = gridData[$grid.attr('id')].settings;
var data = {};
$.each($(settings.filterSelector).serializeArray(), function () {
@ -119,8 +145,8 @@
var pos = settings.filterUrl.indexOf('?');
var url = pos < 0 ? settings.filterUrl : settings.filterUrl.substring(0, pos);
var hashPos = settings.filterUrl.indexOf('#');
if (hashPos >= 0) {
url += settings.filterUrl.substring(pos);
if (pos >= 0 && hashPos >= 0) {
url += settings.filterUrl.substring(hashPos);
}
$grid.find('form.gridview-filter-form').remove();
@ -137,7 +163,7 @@
});
});
event = $.Event(gridEvents.beforeFilter);
var event = $.Event(gridEvents.beforeFilter);
$grid.trigger(event);
if (event.result === false) {
return;
@ -161,10 +187,10 @@
var checkAll = "#" + id + " input[name='" + options.checkAll + "']";
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 () {
initEventHandler($grid, 'checkAllRows', 'click.yiiGridView', checkAll, function () {
$grid.find(inputs + ":enabled").prop('checked', this.checked);
});
$(document).off('click.yiiGridView', inputsEnabled).on('click.yiiGridView', inputsEnabled, function () {
initEventHandler($grid, 'checkRow', 'click.yiiGridView', inputsEnabled, function () {
var all = $grid.find(inputs).length == $grid.find(inputs + ":checked").length;
$grid.find("input[name='" + options.checkAll + "']").prop('checked', all);
});
@ -183,10 +209,17 @@
},
destroy: function () {
return this.each(function () {
$(window).unbind('.yiiGridView');
$(this).removeData('yiiGridView');
var events = ['.yiiGridView', gridEvents.beforeFilter, gridEvents.afterFilter].join(' ');
this.off(events);
var id = $(this).attr('id');
$.each(gridEventHandlers[id], function (type, data) {
$(document).off(data.event, data.selector);
});
delete gridData[id];
return this;
},
data: function () {
@ -194,4 +227,27 @@
return gridData[id];
}
};
/**
* Used for attaching event handler and prevent of duplicating them. With each call previously attached handler of
* the same type is removed even selector was changed.
* @param {jQuery} $gridView According jQuery grid view element
* @param {string} type Type of the event which acts like a key
* @param {string} event Event name, for example 'change.yiiGridView'
* @param {string} selector jQuery selector
* @param {function} callback The actual function to be executed with this event
*/
function initEventHandler($gridView, type, event, selector, callback) {
var id = $gridView.attr('id');
var prevHandler = gridEventHandlers[id];
if (prevHandler !== undefined && prevHandler[type] !== undefined) {
var data = prevHandler[type];
$(document).off(data.event, data.selector);
}
if (prevHandler === undefined) {
gridEventHandlers[id] = {};
}
$(document).on(event, selector, callback);
gridEventHandlers[id][type] = {event: event, selector: selector};
}
})(window.jQuery);

9
framework/assets/yii.js

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

2
framework/caching/ArrayCache.php

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

4
framework/caching/FileDependency.php

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

2
framework/composer.json

@ -65,7 +65,7 @@
"ezyang/htmlpurifier": "~4.6",
"cebe/markdown": "~1.0.0 | ~1.1.0",
"bower-asset/jquery": "2.2.*@stable | 2.1.*@stable | 1.11.*@stable | 1.12.*@stable",
"bower-asset/jquery.inputmask": "~3.2.2",
"bower-asset/jquery.inputmask": "~3.2.2 | ~3.3.3",
"bower-asset/punycode": "1.3.*",
"bower-asset/yii2-pjax": "~2.0.1"
},

2
framework/console/Markdown.php

@ -54,7 +54,7 @@ class Markdown extends \cebe\markdown\Parser
}
/**
* Render a paragraph block
* Render a paragraph block
*
* @param string $block
* @return string

6
framework/console/Request.php

@ -73,7 +73,11 @@ class Request extends \yii\base\Request
}
} elseif (preg_match('/^-(\w+)(?:=(.*))?$/', $param, $matches)) {
$name = $matches[1];
$params['_aliases'][$name] = isset($matches[2]) ? $matches[2] : true;
if (is_numeric($name)) {
$params[] = $param;
} else {
$params['_aliases'][$name] = isset($matches[2]) ? $matches[2] : true;
}
} else {
$params[] = $param;
}

1
framework/data/ActiveDataProvider.php

@ -7,7 +7,6 @@
namespace yii\data;
use Yii;
use yii\db\ActiveQueryInterface;
use yii\base\InvalidConfigException;
use yii\base\Model;

9
framework/db/ActiveRecord.php

@ -439,6 +439,9 @@ class ActiveRecord extends BaseActiveRecord
} catch (\Exception $e) {
$transaction->rollBack();
throw $e;
} catch (\Throwable $e) {
$transaction->rollBack();
throw $e;
}
}
@ -545,6 +548,9 @@ class ActiveRecord extends BaseActiveRecord
} catch (\Exception $e) {
$transaction->rollBack();
throw $e;
} catch (\Throwable $e) {
$transaction->rollBack();
throw $e;
}
}
@ -585,6 +591,9 @@ class ActiveRecord extends BaseActiveRecord
} catch (\Exception $e) {
$transaction->rollBack();
throw $e;
} catch (\Throwable $e) {
$transaction->rollBack();
throw $e;
}
}

17
framework/db/Connection.php

@ -420,7 +420,7 @@ class Connection extends Component
* Use 0 to indicate that the cached data will never expire.
* @param \yii\caching\Dependency $dependency the cache dependency associated with the cached query results.
* @return mixed the return result of the callable
* @throws \Exception if there is any exception during query
* @throws \Exception|\Throwable if there is any exception during query
* @see enableQueryCache
* @see queryCache
* @see noCache()
@ -435,6 +435,9 @@ class Connection extends Component
} catch (\Exception $e) {
array_pop($this->_queryCacheInfo);
throw $e;
} catch (\Throwable $e) {
array_pop($this->_queryCacheInfo);
throw $e;
}
}
@ -457,7 +460,7 @@ class Connection extends Component
* @param callable $callable a PHP callable that contains DB queries which should not use query cache.
* The signature of the callable is `function (Connection $db)`.
* @return mixed the return result of the callable
* @throws \Exception if there is any exception during query
* @throws \Exception|\Throwable if there is any exception during query
* @see enableQueryCache
* @see queryCache
* @see cache()
@ -472,6 +475,9 @@ class Connection extends Component
} catch (\Exception $e) {
array_pop($this->_queryCacheInfo);
throw $e;
} catch (\Throwable $e) {
array_pop($this->_queryCacheInfo);
throw $e;
}
}
@ -671,7 +677,7 @@ class Connection extends Component
* @param callable $callback a valid PHP callback that performs the job. Accepts connection instance as parameter.
* @param string|null $isolationLevel The isolation level to use for this transaction.
* See [[Transaction::begin()]] for details.
* @throws \Exception
* @throws \Exception|\Throwable if there is any exception during query. In this case the transaction will be rolled back.
* @return mixed result of callback function
*/
public function transaction(callable $callback, $isolationLevel = null)
@ -689,6 +695,11 @@ class Connection extends Component
$transaction->rollBack();
}
throw $e;
} catch (\Throwable $e) {
if ($transaction->isActive && $transaction->level === $level) {
$transaction->rollBack();
}
throw $e;
}
return $result;

27
framework/db/Migration.php

@ -95,15 +95,16 @@ class Migration extends Component implements MigrationInterface
try {
if ($this->safeUp() === false) {
$transaction->rollBack();
return false;
}
$transaction->commit();
} catch (\Exception $e) {
echo 'Exception: ' . $e->getMessage() . ' (' . $e->getFile() . ':' . $e->getLine() . ")\n";
echo $e->getTraceAsString() . "\n";
$this->printException($e);
$transaction->rollBack();
return false;
} catch (\Throwable $e) {
$this->printException($e);
$transaction->rollBack();
return false;
}
@ -123,15 +124,16 @@ class Migration extends Component implements MigrationInterface
try {
if ($this->safeDown() === false) {
$transaction->rollBack();
return false;
}
$transaction->commit();
} catch (\Exception $e) {
echo 'Exception: ' . $e->getMessage() . ' (' . $e->getFile() . ':' . $e->getLine() . ")\n";
echo $e->getTraceAsString() . "\n";
$this->printException($e);
$transaction->rollBack();
return false;
} catch (\Throwable $e) {
$this->printException($e);
$transaction->rollBack();
return false;
}
@ -139,6 +141,15 @@ class Migration extends Component implements MigrationInterface
}
/**
* @param \Throwable|\Exception $e
*/
private function printException($e)
{
echo 'Exception: ' . $e->getMessage() . ' (' . $e->getFile() . ':' . $e->getLine() . ")\n";
echo $e->getTraceAsString() . "\n";
}
/**
* This method contains the logic to be executed when applying this migration.
* This method differs from [[up()]] in that the DB logic implemented here will
* be enclosed within a DB transaction.

10
framework/db/Query.php

@ -421,7 +421,13 @@ class Query extends Component implements QueryInterface
$this->limit = $limit;
$this->offset = $offset;
if (empty($this->groupBy) && empty($this->having) && empty($this->union) && !$this->distinct) {
if (
!$this->distinct
&& empty($this->groupBy)
&& empty($this->having)
&& empty($this->union)
&& empty($this->orderBy)
) {
return $command->queryScalar();
} else {
return (new Query)->select([$selectExpression])
@ -585,6 +591,8 @@ class Query extends Component implements QueryInterface
{
if ($this->where === null) {
$this->where = $condition;
} elseif (is_array($this->where) && isset($this->where[0]) && strcasecmp($this->where[0], 'and') === 0) {
$this->where[] = $condition;
} else {
$this->where = ['and', $this->where, $condition];
}

7
framework/db/Transaction.php

@ -28,9 +28,16 @@ use yii\base\InvalidConfigException;
* } catch (\Exception $e) {
* $transaction->rollBack();
* throw $e;
* } catch (\Throwable $e) {
* $transaction->rollBack();
* throw $e;
* }
* ```
*
* > Note: in the above code we have two catch-blocks for compatibility
* > with PHP 5.x and PHP 7.x. `\Exception` implements the [`\Throwable` interface](http://php.net/manual/en/class.throwable.php)
* > since PHP 7.0, so you can skip the part with `\Exception` if your app uses only PHP 7.0 and higher.
*
* @property bool $isActive Whether this transaction is active. Only an active transaction can [[commit()]]
* or [[rollBack()]]. This property is read-only.
* @property string $isolationLevel The transaction isolation level to use for this transaction. This can be

3
framework/db/pgsql/Schema.php

@ -454,6 +454,9 @@ SQL;
return false;
}
foreach ($columns as $column) {
if ($this->db->slavePdo->getAttribute(\PDO::ATTR_CASE) === \PDO::CASE_UPPER) {
$column = array_change_key_case($column, CASE_LOWER);
}
$column = $this->loadColumnSchema($column);
$table->columns[$column->name] = $column;
if ($column->isPrimaryKey) {

2
framework/di/ServiceLocator.php

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

2
framework/helpers/BaseFileHelper.php

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

9
framework/helpers/BaseJson.php

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

21
framework/i18n/Formatter.php

@ -920,8 +920,13 @@ class Formatter extends Component
* value is rounded automatically to the defined decimal digits.
*
* @param mixed $value the value to be formatted.
* @param int $decimals the number of digits after the decimal point. If not given the number of digits is determined from the
* [[locale]] and if the [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available defaults to `2`.
* @param int $decimals the number of digits after the decimal point.
* If not given, the number of digits depends in the input value and is determined based on
* `NumberFormatter::MIN_FRACTION_DIGITS` and `NumberFormatter::MAX_FRACTION_DIGITS`, which can be configured
* using [[$numberFormatterOptions]].
* If the [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available, the default value is `2`.
* If you want consistent behavior between environments where intl is available and not, you should explicitly
* specify a value here.
* @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
* @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
* @return string the formatted result.
@ -956,6 +961,12 @@ class Formatter extends Component
*
* @param mixed $value the value to be formatted. It must be a factor e.g. `0.75` will result in `75%`.
* @param int $decimals the number of digits after the decimal point.
* If not given, the number of digits depends in the input value and is determined based on
* `NumberFormatter::MIN_FRACTION_DIGITS` and `NumberFormatter::MAX_FRACTION_DIGITS`, which can be configured
* using [[$numberFormatterOptions]].
* If the [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available, the default value is `0`.
* If you want consistent behavior between environments where intl is available and not, you should explicitly
* specify a value here.
* @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
* @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
* @return string the formatted result.
@ -988,6 +999,12 @@ class Formatter extends Component
*
* @param mixed $value the value to be formatted.
* @param int $decimals the number of digits after the decimal point.
* If not given, the number of digits depends in the input value and is determined based on
* `NumberFormatter::MIN_FRACTION_DIGITS` and `NumberFormatter::MAX_FRACTION_DIGITS`, which can be configured
* using [[$numberFormatterOptions]].
* If the [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available, the default value depends on your PHP configuration.
* If you want consistent behavior between environments where intl is available and not, you should explicitly
* specify a value here.
* @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]].
* @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]].
* @return string the formatted result.

23
framework/messages/fa/yii.php

@ -5,7 +5,7 @@
*
* Message translations.
*
* This file is automatically generated by 'yii message' command.
* This file is automatically generated by 'yii message/extract' command.
* It contains the localizable messages extracted from source code.
* You may modify this file by translating the extracted messages.
*
@ -20,10 +20,11 @@
* NOTE: this file must be saved in UTF-8 encoding.
*/
return [
'The combination {values} of {attributes} has already been taken.' => 'مقدار {values} از {attributes} قبلاً گرفته شده است.',
'Unknown alias: -{name}' => 'نام مستعار ناشناخته: -{name}',
'(not set)' => '(تنظیم نشده)',
'An internal server error occurred.' => 'خطای داخلی سرور رخ داده است.',
'Are you sure you want to delete this item?' => 'آیا اطمینان به حذف این مورد دارید؟',
'Delete' => 'حذف',
'Error' => 'خطا',
'File upload failed.' => 'آپلود فایل ناموفق بود.',
'Home' => 'صفحهاصلی',
@ -38,7 +39,6 @@ return [
'Page not found.' => 'صفحهای یافت نشد.',
'Please fix the following errors:' => 'لطفاً خطاهای زیر را رفع نمائید:',
'Please upload a file.' => 'لطفاً یک فایل آپلود کنید.',
'Powered by {yii}' => 'طراحی شده توسط {yii}',
'Showing <b>{begin, number}-{end, number}</b> of <b>{totalCount, number}</b> {totalCount, plural, one{item} other{items}}.' => 'نمایش <b>{begin, number} تا {end, number}</b> مورد از کل <b>{totalCount, number}</b> مورد.',
'The file "{file}" is not an image.' => 'فایل "{file}" یک تصویر نیست.',
'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => 'حجم فایل "{file}" بسیار بیشتر می باشد. حجم آن نمی تواند از {formattedLimit} بیشتر باشد.',
@ -53,10 +53,7 @@ return [
'Total <b>{count, number}</b> {count, plural, one{item} other{items}}.' => 'مجموع <b>{count, number}</b> مورد.',
'Unable to verify your data submission.' => 'قادر به تأیید اطلاعات ارسالی شما نمیباشد.',
'Unknown option: --{name}' => 'گزینه ناشناخته: --{name}',
'Update' => 'بروزرسانی',
'View' => 'نما',
'Yes' => 'بله',
'Yii Framework' => 'فریم ورک یی',
'You are not allowed to perform this action.' => 'شما برای انجام این عملیات، دسترسی ندارید.',
'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'شما حداکثر {limit, number} فایل را میتوانید آپلود کنید.',
'in {delta, plural, =1{a day} other{# days}}' => '{delta} روز دیگر',
@ -73,25 +70,25 @@ return [
'{attribute} is invalid.' => '{attribute} معتبر نیست.',
'{attribute} is not a valid URL.' => '{attribute} یک URL معتبر نیست.',
'{attribute} is not a valid email address.' => '{attribute} یک آدرس ایمیل معتبر نیست.',
'{attribute} is not in the allowed range.' => '{attribute} در محدوده مجاز نمی باشد.',
'{attribute} is not in the allowed range.' => '{attribute} در محدوده مجاز نمیباشد.',
'{attribute} must be "{requiredValue}".' => '{attribute} باید "{requiredValue}" باشد.',
'{attribute} must be a number.' => '{attribute} باید یک عدد باشد.',
'{attribute} must be a string.' => '{attribute} باید یک رشته باشد.',
'{attribute} must be a valid IP address.' => '{attribute} باید IP صحیح باشد.',
'{attribute} must be an IP address with specified subnet.' => '{attribute} باید یک IP آدرسی با زیرشبکه بخصوص باشد.',
'{attribute} must be a valid IP address.' => '{attribute} باید یک آدرس IP معتبر باشد.',
'{attribute} must be an IP address with specified subnet.' => '{attribute} باید یک IP آدرس با زیرشبکه بخصوص باشد.',
'{attribute} must be an integer.' => '{attribute} باید یک عدد صحیح باشد.',
'{attribute} must be either "{true}" or "{false}".' => '{attribute} باید "{true}" و یا "{false}" باشد.',
'{attribute} must be equal to "{compareValueOrAttribute}".' => '{attribute} باید با "{compareValueOrAttribute}" برابر باشد.',
'{attribute} must be greater than "{compareValueOrAttribute}".' => '{attribute} باید بزرگتر از "{compareValueOrAttribute}" باشد.',
'{attribute} must be greater than or equal to "{compareValueOrAttribute}".' => '{attribute} باید بزرکتر یا برابر با "{compareValueOrAttribute}" باشد.',
'{attribute} must be greater than or equal to "{compareValueOrAttribute}".' => '{attribute} باید بزرگتر یا برابر با "{compareValueOrAttribute}" باشد.',
'{attribute} must be less than "{compareValueOrAttribute}".' => '{attribute} باید کمتر از "{compareValueOrAttribute}" باشد.',
'{attribute} must be less than or equal to "{compareValueOrAttribute}".' => '{attribute} باید کمتر یا برابر با "{compareValueOrAttribute}" باشد.',
'{attribute} must be no greater than {max}.' => '{attribute} نباید بیشتر از "{max}" باشد.',
'{attribute} must be no less than {min}.' => '{attribute} نباید کمتر از "{min}" باشد.',
'{attribute} must not be a subnet.' => '{attribute} نباید یک زیرشبکه باشد.',
'{attribute} must not be an IPv4 address.' => '{attribute} باید آدرس IPv4 نباشد.',
'{attribute} must not be an IPv6 address.' => '{attribute} باید آدرس IPv6 نباشد.',
'{attribute} must not be equal to "{compareValueOrAttribute}".' => '{attribute} باید مانند "{compareValueOrAttribute}" تکرار نشود.',
'{attribute} must not be an IPv4 address.' => '{attribute} نباید آدرس IPv4 باشد.',
'{attribute} must not be an IPv6 address.' => '{attribute} نباید آدرس IPv6 باشد.',
'{attribute} must not be equal to "{compareValueOrAttribute}".' => '{attribute} نباید برابر با "{compareValueOrAttribute}" باشد.',
'{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} حداقل باید شامل {min, number} کارکتر باشد.',
'{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} حداکثر باید شامل {max, number} کارکتر باشد.',
'{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} باید شامل {length, number} کارکتر باشد.',

12
framework/rbac/BaseManager.php

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

5
framework/rbac/DbManager.php

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

5
framework/rbac/PhpManager.php

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

2
framework/rest/UrlRule.php

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

2
framework/validators/InlineValidator.php

@ -35,7 +35,7 @@ class InlineValidator extends Validator
*
* - `$attribute` is the name of the attribute to be validated;
* - `$params` contains the value of [[params]] that you specify when declaring the inline validation rule;
* - `$validator` is a reference to related [[InlineValidator]] object.
* - `$validator` is a reference to related [[InlineValidator]] object. This parameter is available since version 2.0.11.
*/
public $method;
/**

2
framework/widgets/ActiveField.php

@ -714,7 +714,7 @@ class ActiveField extends Component
$config['attribute'] = $this->attribute;
$config['view'] = $this->form->getView();
if (isset($config['options']) && isset(class_parents($class)['yii\widgets\InputWidget'])) {
$this->addAriaAttributes($config['options']);
$this->addAriaAttributes($config['options']);
$this->adjustLabelFor($config['options']);
}
$this->parts['{input}'] = $class::widget($config);

83
framework/widgets/ListView.php

@ -7,7 +7,6 @@
namespace yii\widgets;
use Yii;
use Closure;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;
@ -75,7 +74,36 @@ class ListView extends BaseListView
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
*/
public $options = ['class' => 'list-view'];
/**
* @var Closure an anonymous function that is called once BEFORE rendering each data model.
* It should have the following signature:
*
* ```php
* function ($model, $key, $index, $widget)
* ```
*
* - `$model`: the current data model being rendered
* - `$key`: the key value associated with the current data model
* - `$index`: the zero-based index of the data model in the model array returned by [[dataProvider]]
* - `$widget`: the ListView object
*
* The return result of the function will be rendered directly.
* Note: If the function returns `null`, nothing will be rendered before the item.
* @see renderBeforeItem
* @since 2.0.11
*/
public $beforeItem;
/**
* @var Closure an anonymous function that is called once AFTER rendering each data model.
*
* It should have the same signature as [[beforeItem]].
*
* The return result of the function will be rendered directly.
* Note: If the function returns `null`, nothing will be rendered after the item.
* @see renderAfterItem
* @since 2.0.11
*/
public $afterItem;
/**
* Renders all data models.
@ -87,13 +115,62 @@ class ListView extends BaseListView
$keys = $this->dataProvider->getKeys();
$rows = [];
foreach (array_values($models) as $index => $model) {
$rows[] = $this->renderItem($model, $keys[$index], $index);
$key = $keys[$index];
if (($before = $this->renderBeforeItem($model, $key, $index)) !== null) {
$rows[] = $before;
}
$rows[] = $this->renderItem($model, $key, $index);
if (($after = $this->renderAfterItem($model, $key, $index)) !== null) {
$rows[] = $after;
}
}
return implode($this->separator, $rows);
}
/**
* Calls [[beforeItem]] closure, returns execution result.
* If [[beforeItem]] is not a closure, `null` will be returned.
*
* @param mixed $model the data model to be rendered
* @param mixed $key the key value associated with the data model
* @param int $index the zero-based index of the data model in the model array returned by [[dataProvider]].
* @return string|null [[beforeItem]] call result or `null` when [[beforeItem]] is not a closure
* @see beforeItem
* @since 2.0.11
*/
protected function renderBeforeItem($model, $key, $index)
{
if ($this->beforeItem instanceof Closure) {
return call_user_func($this->beforeItem, $model, $key, $index, $this);
}
return null;
}
/**
* Calls [[afterItem]] closure, returns execution result.
* If [[afterItem]] is not a closure, `null` will be returned.
*
* @param mixed $model the data model to be rendered
* @param mixed $key the key value associated with the data model
* @param int $index the zero-based index of the data model in the model array returned by [[dataProvider]].
* @return string|null [[afterItem]] call result or `null` when [[afterItem]] is not a closure
* @see afterItem
* @since 2.0.11
*/
protected function renderAfterItem($model, $key, $index)
{
if ($this->afterItem instanceof Closure) {
return call_user_func($this->afterItem, $model, $key, $index, $this);
}
return null;
}
/**
* Renders a single data model.
* @param mixed $model the data model to be rendered
* @param mixed $key the key value associated with the data model

7
framework/widgets/Menu.php

@ -7,6 +7,7 @@
namespace yii\widgets;
use Closure;
use Yii;
use yii\base\Widget;
use yii\helpers\ArrayHelper;
@ -60,7 +61,9 @@ class Menu extends Widget
* otherwise, [[labelTemplate]] will be used.
* - visible: boolean, optional, whether this menu item is visible. Defaults to true.
* - items: array, optional, specifies the sub-menu items. Its format is the same as the parent items.
* - active: boolean, optional, whether this menu item is in active state (currently selected).
* - active: boolean or Closure, optional, whether this menu item is in active state (currently selected).
* When using a closure, its signature should be `function ($item, $hasActiveChild, $isItemActive, $widget)`.
* Closure must return `true` if item should be marked as `active`, otherwise - `false`.
* If a menu item is active, its CSS class will be appended with [[activeCssClass]].
* If this option is not set, the menu item will be set active automatically when the current request
* is triggered by `url`. For more details, please refer to [[isItemActive()]].
@ -285,6 +288,8 @@ class Menu extends Widget
} else {
$items[$i]['active'] = false;
}
} elseif ($item['active'] instanceof Closure) {
$active = $items[$i]['active'] = call_user_func($item['active'], $item, $hasActiveChild, $this->isItemActive($item), $this);
} elseif ($item['active']) {
$active = true;
}

9
framework/widgets/Pjax.php

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

5
tests/data/travis/imagick-setup.sh

@ -0,0 +1,5 @@
#!/bin/sh -e
if [ $(phpenv version-name) = '5.4' ] || [ $(phpenv version-name) = '5.5' ] || [ $(phpenv version-name) = '5.6' ]; then
yes '' | pecl install imagick
fi

6
tests/framework/ar/ActiveRecordTestTrait.php

@ -1159,9 +1159,9 @@ trait ActiveRecordTestTrait
$afterRefreshCalls = [];
Event::on(BaseActiveRecord::className(), BaseActiveRecord::EVENT_AFTER_REFRESH, function ($event) use (&$afterRefreshCalls) {
/* @var $ar BaseActiveRecord */
$ar = $event->sender;
$afterRefreshCalls[] = [get_class($ar), $ar->getIsNewRecord(), $ar->getPrimaryKey(), $ar->isRelationPopulated('orders')];
/* @var $ar BaseActiveRecord */
$ar = $event->sender;
$afterRefreshCalls[] = [get_class($ar), $ar->getIsNewRecord(), $ar->getPrimaryKey(), $ar->isRelationPopulated('orders')];
});
$customer = $customerClass::findOne(1);

576
tests/framework/base/SecurityTest.php

@ -391,142 +391,142 @@ TEXT;
'67ae7370e4a144523543f1e3edf35b26',
'30',
'706e1670ec6beb91c565299710e4f43231336530323838313435653933623539
3238313639623066323530646630653236313832333361346664623132393436
34326131663964636634366665306439e6142bf3d0e6a5952231d8bfabbacd83
ff04c61400529385d63a4b8f8696982d',
3238313639623066323530646630653236313832333361346664623132393436
34326131663964636634366665306439e6142bf3d0e6a5952231d8bfabbacd83
ff04c61400529385d63a4b8f8696982d',
],
[
'5d4bae62bea3c8f4c49dd1a38a4e1b2a',
'30313233343536373839',
'4c9a34e1977dd656fd3e18d4bc2b00c762353830326262373135396530643235
3332643937653632353331626532373336653761643866646233336365323233
30633138663034313665653038306161eb77ac98344a0bf946f98e892e6e15db
b9e221a7c25135a36562c3ecb2981fcd',
3332643937653632353331626532373336653761643866646233336365323233
30633138663034313665653038306161eb77ac98344a0bf946f98e892e6e15db
b9e221a7c25135a36562c3ecb2981fcd',
],
[
'6832851af3c63a1e303bdf1bca38dd9c',
'303132333435363738396162636465',
'd09d2faa163e774abd020bf8cd623f8962646233313330626133613964353834
3436663631383139653438326563623537373830663962303531366130383130
3333303531613337386534626136636415db7a6885b67f69d9974f5879ae7497
08811fc036508f23f55f3f9d16b9ff9d',
3436663631383139653438326563623537373830663962303531366130383130
3333303531613337386534626136636415db7a6885b67f69d9974f5879ae7497
08811fc036508f23f55f3f9d16b9ff9d',
],
[
'e8a9799f4254b26484ea447918dcaad4',
'30313233343536373839616263646566',
'1a76a05af572abe9b4e7a67d7f64baff39666636363332323133373034386531
3761353236333263643965313139303666636535343862363737333032323331
313136366562336535663033316231303811542783f13798b17d40aa7ec9b489
34404ab50784ad1722f5f4d4462f702d3a625ef712635b274a970a47b516b137',
3761353236333263643965313139303666636535343862363737333032323331
313136366562336535663033316231303811542783f13798b17d40aa7ec9b489
34404ab50784ad1722f5f4d4462f702d3a625ef712635b274a970a47b516b137',
],
[
'27cbf5a7c4327582393b2ec277cfe957',
'3031323334353637383961626364656630',
'60e2e14b404c5bd55b192ef2f1c7131c32356261636361313131336265323534
3439623033346337346133346239363337633736663831343632626638303038
64653361396366356165633061666538d2137b2defca383668827b6983f86254
bd849c3e8c9a44b6e1ed203491d73b4cb0cb83658240a89baa9261755d707879',
3439623033346337346133346239363337633736663831343632626638303038
64653361396366356165633061666538d2137b2defca383668827b6983f86254
bd849c3e8c9a44b6e1ed203491d73b4cb0cb83658240a89baa9261755d707879',
],
[
'7b157beb08e8a8ac7d74f789ccbacae5',
'4e161a1df6',
'a17ca346c4a88404b8a345b1d075173a66356137326335343137393136353762
3930636535636466336534316438376437376639366535666636313235653262
38393464663134313430333633343232b319ce688676fde03d092d73df75705f
bde594d9b2bfe41580a458555982dc70',
3930636535636466336534316438376437376639366535666636313235653262
38393464663134313430333633343232b319ce688676fde03d092d73df75705f
bde594d9b2bfe41580a458555982dc70',
],
[
'88704df5b4881d2246388dc7c68066cc',
'5862447074634952357761764253733d',
'9069fbdd47ee81faf34eff40f8fee94333393037333936333439653537636132
6561613863336165333336323562353161636330366438633264376461626563
3836363462646536323261333034643070e4b0a3d196f36a4afb92a7ae4ef7b8
eb44e9db29638fa32140e379ae7aa6b7e68f454635ade137165383fd3a5c049b',
6561613863336165333336323562353161636330366438633264376461626563
3836363462646536323261333034643070e4b0a3d196f36a4afb92a7ae4ef7b8
eb44e9db29638fa32140e379ae7aa6b7e68f454635ade137165383fd3a5c049b',
],
[
'1d203fba1cd2a7b92abb8f40eb985538',
'82c2f315c69f20ec7ae391fc6c2de85281d54c97f74981b4efec0f5e4606765c
040f1f45386bed4fdb9f998b0e7027c7414bea667b1205027c55eed927364446
3b1832bc5338e3a7e10bf24d8e69f167b94551b6240f65b416feebd28599334a
a4',
040f1f45386bed4fdb9f998b0e7027c7414bea667b1205027c55eed927364446
3b1832bc5338e3a7e10bf24d8e69f167b94551b6240f65b416feebd28599334a
a4',
'fade5d841aa10a29a2ef5236371ffc2964343165383664636264393666383364
6165646139316438666531633230306537343364336166666463656466306564
63326239653938303332656464346463136bd6dd0b7530490b91024ed944bc3e
3fc4050d20ce05a9ed992ede75f62bdd2523d0cbc93493baf07ef98c895a353b
5baaf26200572aa2e5bd22508db227556c5ee9eb7425418e9852c595e6ac0e61
37c186e04a3f19d855d8c4b8a8e6ad1be179ea5c816fe461a4cec212297873c8
5f96ee5c024cd88d1c32975fd95acd73',
6165646139316438666531633230306537343364336166666463656466306564
63326239653938303332656464346463136bd6dd0b7530490b91024ed944bc3e
3fc4050d20ce05a9ed992ede75f62bdd2523d0cbc93493baf07ef98c895a353b
5baaf26200572aa2e5bd22508db227556c5ee9eb7425418e9852c595e6ac0e61
37c186e04a3f19d855d8c4b8a8e6ad1be179ea5c816fe461a4cec212297873c8
5f96ee5c024cd88d1c32975fd95acd73',
],
[
'1f93719d7a66a724c3841835fbcb33fd',
'badec0c7d9ca734e161a1df6ca4daa8cdbf6b3bbb60ec404b47a23226ec266b1
3837ffc969e9c23e2bbba72facb491a6a3271193a35026a9ebc93698d689bf7b
84fc384f544cc5d71c2945c8c48ae6348c753322fcaf75171b7d8f1e178e8545
3d5c79f03bae6d9705cabbe7004ec81e188812a66313297fcf5d4c61a48614d2
1b5379fae60736f88bc737257bc1cfbef7016108dd1f3a537aaa681544f5bb20
c2d352212d6a2e460311ab7b93b611218b30c7402709bff89ecf814310c2db97
81f4142a54d6d41f82c33208c188a023c70befd697b496efb7c7b8569474a9d5
025a8ea8311c831b3de2b81800f28589b6aef537f6ada2e3d92a1ddd39cfd6b4
8113c8b36be890b099ca4e08655db0e9b5c211e706af052b776924233321412c
b1b540f08d88acd664c55854b05e31a15d4b0dc93b4514f62a88c3d4a451fe34
74d2fbaee742f232959b5f6c2b71a8b6ad95a0d5362f39309aca710f37a4dae2
228e27d35f4d3fc87ee6ece4538587fad7835f9803f5ca64dfd856ae767a3482
c2f315c69f20ec7ae391fc6c2de85281d54c97f74981b4efec0f5e4606765c04
0f1f45386bed4fdb9f998b0e7027c7414bea667b1205027c55eed9273644463b
1832bc5338e3a7e10bf24d8e69f167b94551b6240f65b416feebd28599334aa4
d998a599053cd477f54fc0af62a8ef75eb91096b153751b201b8374c4956508c
6c82ea2ceb0265e74c96032787fe01139b578c3a14fd4e32a85195375b25da60
4f21187ee01df6042971d7c2b9dd8b611527377f28f933559f6b2edfe7bd8896
00627830bcdffcb884989013585cc6da726c7cc57c69e8664a244d234465f2a5
e736512a65e563a2a726915b50247aff165190c75d151139f2b31211a3acf94e
236d27b441fe64e88a443778359d57ba0fed8edbb01cc60a116f9599da4e47bf
e96850ac12d1199a080f1b591c60eae613fba444f9fe31fd42ee333678532556
07fbfeef02cdbb2d0a8d2329adc7fba22aa4cd124c6e66e0b0aca796ba6aeb69
f8e76a9d1aaabe351bc42fefa6054977859b51158ba7143980d76db5b2cc6aec
9f83eac5edf79357632fb60cb4811e71892436e582375860faf8d8c518ee33ca
e08b18af7faa227d97196d7b041bcb366118751fbeaa92d80f86324ce8711cfd
65005cad0ce30878f508eb1ea2735e212ef3e42a78467d4f31b483087c3c63a3
2fa62f2ae92e2acdfc3cf21b3419acc347ff51ccb17161e829cc4a24965fd930
b983d63153b9c13dd85340d7184bf01734d4357c1aabfbdc9711193ffa974832
4f88cf801f35708db0901ffaa55415ca4334678633dda763b5fbfba7cfd51dc5
79149ec9284a4db2349dbc5264d5fca7a72034261716d90a996cb57ba09f7d07
29a2f10cc58eec98d86e9306a3fbf2e711046e86b5a86cc0eed7625be1a01ebf',
3837ffc969e9c23e2bbba72facb491a6a3271193a35026a9ebc93698d689bf7b
84fc384f544cc5d71c2945c8c48ae6348c753322fcaf75171b7d8f1e178e8545
3d5c79f03bae6d9705cabbe7004ec81e188812a66313297fcf5d4c61a48614d2
1b5379fae60736f88bc737257bc1cfbef7016108dd1f3a537aaa681544f5bb20
c2d352212d6a2e460311ab7b93b611218b30c7402709bff89ecf814310c2db97
81f4142a54d6d41f82c33208c188a023c70befd697b496efb7c7b8569474a9d5
025a8ea8311c831b3de2b81800f28589b6aef537f6ada2e3d92a1ddd39cfd6b4
8113c8b36be890b099ca4e08655db0e9b5c211e706af052b776924233321412c
b1b540f08d88acd664c55854b05e31a15d4b0dc93b4514f62a88c3d4a451fe34
74d2fbaee742f232959b5f6c2b71a8b6ad95a0d5362f39309aca710f37a4dae2
228e27d35f4d3fc87ee6ece4538587fad7835f9803f5ca64dfd856ae767a3482
c2f315c69f20ec7ae391fc6c2de85281d54c97f74981b4efec0f5e4606765c04
0f1f45386bed4fdb9f998b0e7027c7414bea667b1205027c55eed9273644463b
1832bc5338e3a7e10bf24d8e69f167b94551b6240f65b416feebd28599334aa4
d998a599053cd477f54fc0af62a8ef75eb91096b153751b201b8374c4956508c
6c82ea2ceb0265e74c96032787fe01139b578c3a14fd4e32a85195375b25da60
4f21187ee01df6042971d7c2b9dd8b611527377f28f933559f6b2edfe7bd8896
00627830bcdffcb884989013585cc6da726c7cc57c69e8664a244d234465f2a5
e736512a65e563a2a726915b50247aff165190c75d151139f2b31211a3acf94e
236d27b441fe64e88a443778359d57ba0fed8edbb01cc60a116f9599da4e47bf
e96850ac12d1199a080f1b591c60eae613fba444f9fe31fd42ee333678532556
07fbfeef02cdbb2d0a8d2329adc7fba22aa4cd124c6e66e0b0aca796ba6aeb69
f8e76a9d1aaabe351bc42fefa6054977859b51158ba7143980d76db5b2cc6aec
9f83eac5edf79357632fb60cb4811e71892436e582375860faf8d8c518ee33ca
e08b18af7faa227d97196d7b041bcb366118751fbeaa92d80f86324ce8711cfd
65005cad0ce30878f508eb1ea2735e212ef3e42a78467d4f31b483087c3c63a3
2fa62f2ae92e2acdfc3cf21b3419acc347ff51ccb17161e829cc4a24965fd930
b983d63153b9c13dd85340d7184bf01734d4357c1aabfbdc9711193ffa974832
4f88cf801f35708db0901ffaa55415ca4334678633dda763b5fbfba7cfd51dc5
79149ec9284a4db2349dbc5264d5fca7a72034261716d90a996cb57ba09f7d07
29a2f10cc58eec98d86e9306a3fbf2e711046e86b5a86cc0eed7625be1a01ebf',
'712929635f5be013159aec81296b96ec36333734373565366166383031313734
3331623863663837343237633863376539643865643339383531363139396566
34313731633765393462663264316130de90698e64fa4abc91639e72baee83bc
2caf85f91318e0cbd0db5fa08c4ffb582ec55ca43de53a43f2844af35d5f87b9
8faa623107aee2e083f1c7aeedcb0472c93bb9eacbd39d839d5bf94c44658d7f
d70817f5d6b120f91ef86880f93e99151bc1ed13ed263a3ccc7243e5ea97f39f
1ce2ce6b05a2b78f05c5d72041e35466068f52fb3d2ba3afa7594d7bc0981c54
8b31ae7e5b7e7e0e6f2fac9a336e6516d7e4b5cc658e1fe634daf9ad097715be
14d54ef19adc381db31db78714a09e997dc7732853d39885566e41a2c0ecb08d
ef8ab56359a0e312446d9f1555539ad29e13080d438d6817280d4dbfe6cd4ab9
4a357dbddcea1bc90e0d0fa6d556b1ba75c23a1d3818ea91e0fa5b8005b8020e
f7a80ed2ee60aa8ee588e101dbf3b64bf6a3dca5b1d5bcb96eed5c594bab1dcf
c1f61d74ad3ff0f5fdf176a327e8de33d123cdd61c6c4dc946429c566ac0f77f
48215d5889365fa664a879babd4758fb4d824cfb9e4bf6500ffec0e4ebefe4f6
2e521a7cd563fc954a9161047461e4054f324c5cf4f9e949566c9ac17c45aac1
abf98caed42242c51aa0d81d732c538e437c4024e8f04eeeab6619aed46599e7
f66c2041e0c346affab1f79cd7352a66686fac2b38615f8fc172ab0967fd4435
9dea9f41b57ba5a752e20456f2254e8eef576867ebda0f48fe47a2e91fc8d8af
dc1bf98a8d3530b4b02996fe4b05ec3dab200f9e79a4261c233caa9a33762f1f
4b3482ab6f16f5dbd7bd87ed17e21c30140ce61fc2468c054ce51dded2683d7c
375d69d662729d6fb8629b8dc25dcc5596f87f627a2138a3ef368a2a1591902f
84ffc457bd556a346815986f153fd3a99ef169444436ac81853b318c0908cb33
cf332edadfb870cab419365312c18aeda418b8b571783a6b2d8c397c33a22b31
55958cef153f4d9cfc3a1c6288ff17bfdd92132e1f1e5fe8041e30a383084811
fe892d7fc33abff10dd20db07277677b16e7f90137adfc3cb36dd85de3618769
bf8f3ce13642f9ea430f455d388281208190b335915e256320274a904ecd0938
d3c34d99c88e3186a132777fc7b74b43efab1a08376035061de56a4fb6de611c
41d2139c77a516c4f8144b123696356b4b9a752a9cd857630af4ef02339172e6
361a2fd6a18e3baaf8a3f7e9811ad5fb61abba7ae893b1e7748df2c5b7704eb9
65606b0253cba6a0561b0e70593c724f99e07d3e9a857aef894a64a35969b354
4726d35504d7b8c0c06cbf9106c5d504674daa879b39328d2c83e0f4e5622ea1
4fb742458214b410e2736d8cefabfb125c4769701711c15ab870b5ff192d4c71
e805ac5100352e33227b162ebae123e20c477719c52c59e192c2e3731806404d
d4359f840b11ad495357210e259e6b9e8fe5e8f600e8746fe1a483d45b694324
0809649ed7320b0022a5ef7b414635933d6d18ec7218f829121d12dcb573ed77
79ab0519d2df17dbd8988b32ac0711ef',
3331623863663837343237633863376539643865643339383531363139396566
34313731633765393462663264316130de90698e64fa4abc91639e72baee83bc
2caf85f91318e0cbd0db5fa08c4ffb582ec55ca43de53a43f2844af35d5f87b9
8faa623107aee2e083f1c7aeedcb0472c93bb9eacbd39d839d5bf94c44658d7f
d70817f5d6b120f91ef86880f93e99151bc1ed13ed263a3ccc7243e5ea97f39f
1ce2ce6b05a2b78f05c5d72041e35466068f52fb3d2ba3afa7594d7bc0981c54
8b31ae7e5b7e7e0e6f2fac9a336e6516d7e4b5cc658e1fe634daf9ad097715be
14d54ef19adc381db31db78714a09e997dc7732853d39885566e41a2c0ecb08d
ef8ab56359a0e312446d9f1555539ad29e13080d438d6817280d4dbfe6cd4ab9
4a357dbddcea1bc90e0d0fa6d556b1ba75c23a1d3818ea91e0fa5b8005b8020e
f7a80ed2ee60aa8ee588e101dbf3b64bf6a3dca5b1d5bcb96eed5c594bab1dcf
c1f61d74ad3ff0f5fdf176a327e8de33d123cdd61c6c4dc946429c566ac0f77f
48215d5889365fa664a879babd4758fb4d824cfb9e4bf6500ffec0e4ebefe4f6
2e521a7cd563fc954a9161047461e4054f324c5cf4f9e949566c9ac17c45aac1
abf98caed42242c51aa0d81d732c538e437c4024e8f04eeeab6619aed46599e7
f66c2041e0c346affab1f79cd7352a66686fac2b38615f8fc172ab0967fd4435
9dea9f41b57ba5a752e20456f2254e8eef576867ebda0f48fe47a2e91fc8d8af
dc1bf98a8d3530b4b02996fe4b05ec3dab200f9e79a4261c233caa9a33762f1f
4b3482ab6f16f5dbd7bd87ed17e21c30140ce61fc2468c054ce51dded2683d7c
375d69d662729d6fb8629b8dc25dcc5596f87f627a2138a3ef368a2a1591902f
84ffc457bd556a346815986f153fd3a99ef169444436ac81853b318c0908cb33
cf332edadfb870cab419365312c18aeda418b8b571783a6b2d8c397c33a22b31
55958cef153f4d9cfc3a1c6288ff17bfdd92132e1f1e5fe8041e30a383084811
fe892d7fc33abff10dd20db07277677b16e7f90137adfc3cb36dd85de3618769
bf8f3ce13642f9ea430f455d388281208190b335915e256320274a904ecd0938
d3c34d99c88e3186a132777fc7b74b43efab1a08376035061de56a4fb6de611c
41d2139c77a516c4f8144b123696356b4b9a752a9cd857630af4ef02339172e6
361a2fd6a18e3baaf8a3f7e9811ad5fb61abba7ae893b1e7748df2c5b7704eb9
65606b0253cba6a0561b0e70593c724f99e07d3e9a857aef894a64a35969b354
4726d35504d7b8c0c06cbf9106c5d504674daa879b39328d2c83e0f4e5622ea1
4fb742458214b410e2736d8cefabfb125c4769701711c15ab870b5ff192d4c71
e805ac5100352e33227b162ebae123e20c477719c52c59e192c2e3731806404d
d4359f840b11ad495357210e259e6b9e8fe5e8f600e8746fe1a483d45b694324
0809649ed7320b0022a5ef7b414635933d6d18ec7218f829121d12dcb573ed77
79ab0519d2df17dbd8988b32ac0711ef',
],
];
@ -557,142 +557,142 @@ TEXT;
'LtogIEhy59ve0Huy',
'30',
'83325c8abe8dc0afd801acb7785dc29c32393439653930663266396466653862
3965303964653935326238343065363734346264633932376364376430653933
37303963376232336634306339396136e6f4e7dc3e2fd23be186f037e4caa6d0
4ae8cb894d80c08bb790417af9cc176f',
3965303964653935326238343065363734346264633932376364376430653933
37303963376232336634306339396136e6f4e7dc3e2fd23be186f037e4caa6d0
4ae8cb894d80c08bb790417af9cc176f',
],
[
'1_VTumNNc7VV463t',
'30313233343536373839',
'a247ac24f3aa60e894904f58954ce8bf39386530366165343538336132616231
6532353066663430646432383531333465373336646333386437323733633763
646665346232623635393962626162323a44a150a556addd97addfb43a32f600
aa2c479664682a308e6cdd523967cb4a',
6532353066663430646432383531333465373336646333386437323733633763
646665346232623635393962626162323a44a150a556addd97addfb43a32f600
aa2c479664682a308e6cdd523967cb4a',
],
[
'DBpoIPndKRm2Rfem',
'303132333435363738396162636465',
'9ed20f30824312032e5e34e2c5ab61e333313930616634396137623261363863
3334323832333664626265663435643633323033383862613865633961356261
30353634393536636465643833633531b62ce156f34b92790b2d26312f3fd7d0
0a646da4d636f6998f1b0d859f255dc6',
3334323832333664626265663435643633323033383862613865633961356261
30353634393536636465643833633531b62ce156f34b92790b2d26312f3fd7d0
0a646da4d636f6998f1b0d859f255dc6',
],
[
's1e1oRE1iM_oortb',
'30313233343536373839616263646566',
'5b03427dba481b5af8760edc3788fa0e66666663623539353163346466663761
6434663337383566366265663931303662366530653633663235303532373763
37346330393537646630616535373230c98134da00d77753741c1f0bb483f109
622a889f950310cd51d7d48d63202b20d378eac85f7d0c851fc9905d322aef96',
6434663337383566366265663931303662366530653633663235303532373763
37346330393537646630616535373230c98134da00d77753741c1f0bb483f109
622a889f950310cd51d7d48d63202b20d378eac85f7d0c851fc9905d322aef96',
],
[
'i1E7JvOQaESAKoeH',
'3031323334353637383961626364656630',
'7cf2ed4612d07de3ecfd54ac0e576e8539613861656439333965623533626238
3361653764663438643330383130313462613961636462383836383862313962
346439313666343336313766366538637bd566b1eecb0b1e0896eb1fd0fd11d9
dcb9eee5cc3d90c4046a6849e8ad152caf85e8f96de3b24b4d523a2d60533e0b',
3361653764663438643330383130313462613961636462383836383862313962
346439313666343336313766366538637bd566b1eecb0b1e0896eb1fd0fd11d9
dcb9eee5cc3d90c4046a6849e8ad152caf85e8f96de3b24b4d523a2d60533e0b',
],
[
'QUN24gpNGodXurMM',
'4e161a1df6',
'49fc2d51b3e6325d86334ea8872699ef31613661333031373065373834643033
3863343734366435343035316562643438636133306233373431323066333330
32613330623364326162326434306637042f9b4a005ed3b9532181d020378800
deefcfa36d77ed4abdf35546c0bb4aec',
3863343734366435343035316562643438636133306233373431323066333330
32613330623364326162326434306637042f9b4a005ed3b9532181d020378800
deefcfa36d77ed4abdf35546c0bb4aec',
],
[
'1COt9D8ZsfclCic2',
'5862447074634952357761764253733d',
'c4d5f90054faf3699d983795f44bdb1430643734643832343265323064323434
6535346565323733323764376266646539366431353266666639386231626438
653237646165393461626435656332332b4244fccb47ad8cdea56109c7d5a417
13b3844d9857507db59d0000037b169f7b67cf0ea793c0254bffc55342d8e4c7',
6535346565323733323764376266646539366431353266666639386231626438
653237646165393461626435656332332b4244fccb47ad8cdea56109c7d5a417
13b3844d9857507db59d0000037b169f7b67cf0ea793c0254bffc55342d8e4c7',
],
[
'eUqmv4chMnO1H5cq',
'82c2f315c69f20ec7ae391fc6c2de85281d54c97f74981b4efec0f5e4606765c
040f1f45386bed4fdb9f998b0e7027c7414bea667b1205027c55eed927364446
3b1832bc5338e3a7e10bf24d8e69f167b94551b6240f65b416feebd28599334a
a4',
040f1f45386bed4fdb9f998b0e7027c7414bea667b1205027c55eed927364446
3b1832bc5338e3a7e10bf24d8e69f167b94551b6240f65b416feebd28599334a
a4',
'31435e0bf8c0be9a395791288f6d058839336130623938343839626436636433
6664373131666361383739633163373130323936653730306331663836373263
656135303466323439326564626162378298df437dab821e8b2f7086962ffeb1
7a674022ee498470e5e8fdff8905aed39e424588ecee69965bb6856f0356860e
aa978ffa42ccfef6d4fb00026014e107736f3ee9b2206a2cd52b18e3068cf6aa
077c7304128a3cd92d4fc29dfa7c180eaf85feec791618db1ed01695536cf8ec
7923c0b3fb974fd0ff92faa62723e94f',
6664373131666361383739633163373130323936653730306331663836373263
656135303466323439326564626162378298df437dab821e8b2f7086962ffeb1
7a674022ee498470e5e8fdff8905aed39e424588ecee69965bb6856f0356860e
aa978ffa42ccfef6d4fb00026014e107736f3ee9b2206a2cd52b18e3068cf6aa
077c7304128a3cd92d4fc29dfa7c180eaf85feec791618db1ed01695536cf8ec
7923c0b3fb974fd0ff92faa62723e94f',
],
[
'3Cl5v2Lmn61PiQ3H',
'badec0c7d9ca734e161a1df6ca4daa8cdbf6b3bbb60ec404b47a23226ec266b1
3837ffc969e9c23e2bbba72facb491a6a3271193a35026a9ebc93698d689bf7b
84fc384f544cc5d71c2945c8c48ae6348c753322fcaf75171b7d8f1e178e8545
3d5c79f03bae6d9705cabbe7004ec81e188812a66313297fcf5d4c61a48614d2
1b5379fae60736f88bc737257bc1cfbef7016108dd1f3a537aaa681544f5bb20
c2d352212d6a2e460311ab7b93b611218b30c7402709bff89ecf814310c2db97
81f4142a54d6d41f82c33208c188a023c70befd697b496efb7c7b8569474a9d5
025a8ea8311c831b3de2b81800f28589b6aef537f6ada2e3d92a1ddd39cfd6b4
8113c8b36be890b099ca4e08655db0e9b5c211e706af052b776924233321412c
b1b540f08d88acd664c55854b05e31a15d4b0dc93b4514f62a88c3d4a451fe34
74d2fbaee742f232959b5f6c2b71a8b6ad95a0d5362f39309aca710f37a4dae2
228e27d35f4d3fc87ee6ece4538587fad7835f9803f5ca64dfd856ae767a3482
c2f315c69f20ec7ae391fc6c2de85281d54c97f74981b4efec0f5e4606765c04
0f1f45386bed4fdb9f998b0e7027c7414bea667b1205027c55eed9273644463b
1832bc5338e3a7e10bf24d8e69f167b94551b6240f65b416feebd28599334aa4
d998a599053cd477f54fc0af62a8ef75eb91096b153751b201b8374c4956508c
6c82ea2ceb0265e74c96032787fe01139b578c3a14fd4e32a85195375b25da60
4f21187ee01df6042971d7c2b9dd8b611527377f28f933559f6b2edfe7bd8896
00627830bcdffcb884989013585cc6da726c7cc57c69e8664a244d234465f2a5
e736512a65e563a2a726915b50247aff165190c75d151139f2b31211a3acf94e
236d27b441fe64e88a443778359d57ba0fed8edbb01cc60a116f9599da4e47bf
e96850ac12d1199a080f1b591c60eae613fba444f9fe31fd42ee333678532556
07fbfeef02cdbb2d0a8d2329adc7fba22aa4cd124c6e66e0b0aca796ba6aeb69
f8e76a9d1aaabe351bc42fefa6054977859b51158ba7143980d76db5b2cc6aec
9f83eac5edf79357632fb60cb4811e71892436e582375860faf8d8c518ee33ca
e08b18af7faa227d97196d7b041bcb366118751fbeaa92d80f86324ce8711cfd
65005cad0ce30878f508eb1ea2735e212ef3e42a78467d4f31b483087c3c63a3
2fa62f2ae92e2acdfc3cf21b3419acc347ff51ccb17161e829cc4a24965fd930
b983d63153b9c13dd85340d7184bf01734d4357c1aabfbdc9711193ffa974832
4f88cf801f35708db0901ffaa55415ca4334678633dda763b5fbfba7cfd51dc5
79149ec9284a4db2349dbc5264d5fca7a72034261716d90a996cb57ba09f7d07
29a2f10cc58eec98d86e9306a3fbf2e711046e86b5a86cc0eed7625be1a01ebf',
3837ffc969e9c23e2bbba72facb491a6a3271193a35026a9ebc93698d689bf7b
84fc384f544cc5d71c2945c8c48ae6348c753322fcaf75171b7d8f1e178e8545
3d5c79f03bae6d9705cabbe7004ec81e188812a66313297fcf5d4c61a48614d2
1b5379fae60736f88bc737257bc1cfbef7016108dd1f3a537aaa681544f5bb20
c2d352212d6a2e460311ab7b93b611218b30c7402709bff89ecf814310c2db97
81f4142a54d6d41f82c33208c188a023c70befd697b496efb7c7b8569474a9d5
025a8ea8311c831b3de2b81800f28589b6aef537f6ada2e3d92a1ddd39cfd6b4
8113c8b36be890b099ca4e08655db0e9b5c211e706af052b776924233321412c
b1b540f08d88acd664c55854b05e31a15d4b0dc93b4514f62a88c3d4a451fe34
74d2fbaee742f232959b5f6c2b71a8b6ad95a0d5362f39309aca710f37a4dae2
228e27d35f4d3fc87ee6ece4538587fad7835f9803f5ca64dfd856ae767a3482
c2f315c69f20ec7ae391fc6c2de85281d54c97f74981b4efec0f5e4606765c04
0f1f45386bed4fdb9f998b0e7027c7414bea667b1205027c55eed9273644463b
1832bc5338e3a7e10bf24d8e69f167b94551b6240f65b416feebd28599334aa4
d998a599053cd477f54fc0af62a8ef75eb91096b153751b201b8374c4956508c
6c82ea2ceb0265e74c96032787fe01139b578c3a14fd4e32a85195375b25da60
4f21187ee01df6042971d7c2b9dd8b611527377f28f933559f6b2edfe7bd8896
00627830bcdffcb884989013585cc6da726c7cc57c69e8664a244d234465f2a5
e736512a65e563a2a726915b50247aff165190c75d151139f2b31211a3acf94e
236d27b441fe64e88a443778359d57ba0fed8edbb01cc60a116f9599da4e47bf
e96850ac12d1199a080f1b591c60eae613fba444f9fe31fd42ee333678532556
07fbfeef02cdbb2d0a8d2329adc7fba22aa4cd124c6e66e0b0aca796ba6aeb69
f8e76a9d1aaabe351bc42fefa6054977859b51158ba7143980d76db5b2cc6aec
9f83eac5edf79357632fb60cb4811e71892436e582375860faf8d8c518ee33ca
e08b18af7faa227d97196d7b041bcb366118751fbeaa92d80f86324ce8711cfd
65005cad0ce30878f508eb1ea2735e212ef3e42a78467d4f31b483087c3c63a3
2fa62f2ae92e2acdfc3cf21b3419acc347ff51ccb17161e829cc4a24965fd930
b983d63153b9c13dd85340d7184bf01734d4357c1aabfbdc9711193ffa974832
4f88cf801f35708db0901ffaa55415ca4334678633dda763b5fbfba7cfd51dc5
79149ec9284a4db2349dbc5264d5fca7a72034261716d90a996cb57ba09f7d07
29a2f10cc58eec98d86e9306a3fbf2e711046e86b5a86cc0eed7625be1a01ebf',
'0c5164ead7f48c5d95f5907399c146a261353462323734323738656231323164
3962333166373062373734636630623932616638626236393339363861646366
35643933393537303331343436303330a927c7a879272f81e1032d57530b5e69
60f70e14b7607e8e17583aa8197f547e375e07b7a9d6c11be406f5e01aac59e2
7e54bb3c33662ea294cfbd0e7bb8e5eb886edee341509a752fc3b9706807a948
00edd0bff8e624275420bff50995de70d2692a052e1710a0b9135f9f44abcb02
24a189f1bb414e4d73fb539728d40b47ebc34fdb5ec2fa61848bb828de5179a1
e799eb09367bda9b59b3bdc51d3f92c1bd7dacf751b9324059869bf6adc3d88d
226a153a75cae1a2426ea187ef62c97bbd35e97450da87d4da9aed08ebf30a3c
a3369cc65a17acebb8a6ff8ff698743a3782990bc5e8cd03a6882c0f7c50868c
6b3ce967c9ea317555eb972e9bb7beb7b3215160a0bcca8c7f92a085beca256e
4484b1cdcaee495917d8aa4dbf7675806f7f57e77a770ea7db6e080150b43f56
15709b371a303e89b032eac7fec3ee954ea52940a2e59343fd0fd59ac2d4f095
74d2863eed13774c63a94e87f7ccdbf0741a56074a7210e2f022809ca48637d8
057d80fb190f339ddee2b6f7aaef3f2c1848026bd33e377ac554e2b29dede4fc
11e040b3837c24e6cd93430c2f9c19138cd1b505681958a09f223f65e6d7b123
43e426e204ee32411b1590133d58fbc4806ff784db93cd0205a7b4a1f4b96ee4
02b06ac00520118c89a7c4f1dfb4ac941a3d6f839e9312ee8cd9c3c02ef5cf2a
29d266fcfd29c24b994bcd3c67000999486bad6b060f87d5c843f3f132be5a7c
6ab2aab2091a83f0673efc4bf658e6c181c800a2eab5272c9cc8e9bdcf4ee061
de613989f107f1755385697ad64cd1c613f1f742a980c48e27638b8423b82eba
555c59020ee5794099377e816e8983a7579fc97178e5ac0f98bdda6dbf3f38f1
3f8b2d12cc792e729468a8408482985ac73e831a6ca5f67176f68bf216b77147
fe4743ae60f628defa590b0b0b7d8c39d24ef6980ffc0c47882fa0e3f04ed3e5
6d8654e11a0f25a2b3dd03e900e3d59922fa7ccd544f98a04828a34826fd9bfe
e8c841ca6146cc96443ab2cbed3f846f5241d27b15f81df80e2b35219044e933
4967334529fe949604a19f7cad76842a16928066a01fb9ec750e78ae68ddcb4f
833b5e89377ea31c7c87666c300f36f7583383f783f979cedbd05585c50caade
73f9f38dfcc5f915106573694ce497a0787ce3fecf9678a75b62f258aea300e8
2176e5c51009ea1fbdf266606cfb93cc62f9abd6c056625df053da8d9e175d63
ca716f148365a41796889e6d24e6c9a6ee6ac57bf7c35b45d4000ec638d191e5
54fe6b031f87b82f6076e9672f7162de1dafedb9fed9d38adeb999c7fc46f801
cd546342404315b06faff51d549b2eb94390d40a9ad826c1595add3f11afb909
16dc20173f13010008bc12a6355d582205897e7eb885f526a08bee072dabbb15
3b1777d8e7d96b31db8b64d64404ae82b3506f10ef198fad6321aa8cf04b5f54
d5236014d4b5ce20fbfba77f090d3573',
3962333166373062373734636630623932616638626236393339363861646366
35643933393537303331343436303330a927c7a879272f81e1032d57530b5e69
60f70e14b7607e8e17583aa8197f547e375e07b7a9d6c11be406f5e01aac59e2
7e54bb3c33662ea294cfbd0e7bb8e5eb886edee341509a752fc3b9706807a948
00edd0bff8e624275420bff50995de70d2692a052e1710a0b9135f9f44abcb02
24a189f1bb414e4d73fb539728d40b47ebc34fdb5ec2fa61848bb828de5179a1
e799eb09367bda9b59b3bdc51d3f92c1bd7dacf751b9324059869bf6adc3d88d
226a153a75cae1a2426ea187ef62c97bbd35e97450da87d4da9aed08ebf30a3c
a3369cc65a17acebb8a6ff8ff698743a3782990bc5e8cd03a6882c0f7c50868c
6b3ce967c9ea317555eb972e9bb7beb7b3215160a0bcca8c7f92a085beca256e
4484b1cdcaee495917d8aa4dbf7675806f7f57e77a770ea7db6e080150b43f56
15709b371a303e89b032eac7fec3ee954ea52940a2e59343fd0fd59ac2d4f095
74d2863eed13774c63a94e87f7ccdbf0741a56074a7210e2f022809ca48637d8
057d80fb190f339ddee2b6f7aaef3f2c1848026bd33e377ac554e2b29dede4fc
11e040b3837c24e6cd93430c2f9c19138cd1b505681958a09f223f65e6d7b123
43e426e204ee32411b1590133d58fbc4806ff784db93cd0205a7b4a1f4b96ee4
02b06ac00520118c89a7c4f1dfb4ac941a3d6f839e9312ee8cd9c3c02ef5cf2a
29d266fcfd29c24b994bcd3c67000999486bad6b060f87d5c843f3f132be5a7c
6ab2aab2091a83f0673efc4bf658e6c181c800a2eab5272c9cc8e9bdcf4ee061
de613989f107f1755385697ad64cd1c613f1f742a980c48e27638b8423b82eba
555c59020ee5794099377e816e8983a7579fc97178e5ac0f98bdda6dbf3f38f1
3f8b2d12cc792e729468a8408482985ac73e831a6ca5f67176f68bf216b77147
fe4743ae60f628defa590b0b0b7d8c39d24ef6980ffc0c47882fa0e3f04ed3e5
6d8654e11a0f25a2b3dd03e900e3d59922fa7ccd544f98a04828a34826fd9bfe
e8c841ca6146cc96443ab2cbed3f846f5241d27b15f81df80e2b35219044e933
4967334529fe949604a19f7cad76842a16928066a01fb9ec750e78ae68ddcb4f
833b5e89377ea31c7c87666c300f36f7583383f783f979cedbd05585c50caade
73f9f38dfcc5f915106573694ce497a0787ce3fecf9678a75b62f258aea300e8
2176e5c51009ea1fbdf266606cfb93cc62f9abd6c056625df053da8d9e175d63
ca716f148365a41796889e6d24e6c9a6ee6ac57bf7c35b45d4000ec638d191e5
54fe6b031f87b82f6076e9672f7162de1dafedb9fed9d38adeb999c7fc46f801
cd546342404315b06faff51d549b2eb94390d40a9ad826c1595add3f11afb909
16dc20173f13010008bc12a6355d582205897e7eb885f526a08bee072dabbb15
3b1777d8e7d96b31db8b64d64404ae82b3506f10ef198fad6321aa8cf04b5f54
d5236014d4b5ce20fbfba77f090d3573',
],
];
@ -702,142 +702,142 @@ TEXT;
'3DZsVH4gt5xBueho',
'30',
'e19edc37d284b77a5a4600334b67317662393266323434393038666330653031
3730313664646137663830626366383363336239313664336637653133313933
306133613836363563643538396163642b6c12f20e7e1ae97422ee659914cc57
e99c7c97c7a4957e78ab957b18be4551',
3730313664646137663830626366383363336239313664336637653133313933
306133613836363563643538396163642b6c12f20e7e1ae97422ee659914cc57
e99c7c97c7a4957e78ab957b18be4551',
],
[
'CVALyUpDdMOSaxJN',
'30313233343536373839',
'a2535797ac0c83932fd9a2b8d6b2602662663439623337616631623434333233
3033343439313063656162373231666330333261383561356632393932386462
3631666364313236303962373636316296930828e0e154a0e8d262846835f242
e42de861cba81df69fe6fe5a5970fdf7',
3033343439313063656162373231666330333261383561356632393932386462
3631666364313236303962373636316296930828e0e154a0e8d262846835f242
e42de861cba81df69fe6fe5a5970fdf7',
],
[
'wBZcBIhNSFiaCWE8',
'303132333435363738396162636465',
'd77b5d334d2820b5b5a54c5f71fce21130373832656561636661623564383766
6338343636626662346566623035333461653566353366656438303063376561
32643033343166376162376333373166c6cf828b68ff940e8de977e3471d1d51
2e51bab7ee0f976dd6d87727b508f7cf',
6338343636626662346566623035333461653566353366656438303063376561
32643033343166376162376333373166c6cf828b68ff940e8de977e3471d1d51
2e51bab7ee0f976dd6d87727b508f7cf',
],
[
'PYeAhK5nWPIxGD2F',
'30313233343536373839616263646566',
'0c2b35c26794c55b94228af98ba7378133326361353835323130363730333039
3531343463653763663366386462363733666537646466326339303439373730
64343038313234393232313362653639dbec4d9abb2dcffbf186366476df34d2
500744fd27eda1e0ea0e54280b091d32ebd1e786402507cb3c591503e27f195b',
3531343463653763663366386462363733666537646466326339303439373730
64343038313234393232313362653639dbec4d9abb2dcffbf186366476df34d2
500744fd27eda1e0ea0e54280b091d32ebd1e786402507cb3c591503e27f195b',
],
[
'B9u8Tl9tRBgmnSHk',
'3031323334353637383961626364656630',
'8401d45dbce698cf9342152c75cd8f5164356231643534643561646636316462
6331373265623831373165363336616165343432383161633962646333373932
3139323034393665303863356331363743e5873d38758489b9d5800aabf31f3e
cf36e1e47955f5c96acdf8dc6cc7280c825949d46546796ec5a114985f5fe598',
6331373265623831373165363336616165343432383161633962646333373932
3139323034393665303863356331363743e5873d38758489b9d5800aabf31f3e
cf36e1e47955f5c96acdf8dc6cc7280c825949d46546796ec5a114985f5fe598',
],
[
'cLHX5BvVQcdlozS6',
'4e161a1df6',
'e04aed1dc385788c3c777bc7b3cfa20c32656633363730393363366138643831
3866303330613461666664623039326437623739613236333566353762333461
34653132366163653031303134353161eefebead8d190e854c05f598adfb8d7b
ef30c86c7cc7003f261b8ce26c62da55',
3866303330613461666664623039326437623739613236333566353762333461
34653132366163653031303134353161eefebead8d190e854c05f598adfb8d7b
ef30c86c7cc7003f261b8ce26c62da55',
],
[
'r8EZMeBVex-LC3c5',
'5862447074634952357761764253733d',
'ae03ee587e7df6407a17d43e3381b76563616366633165613463383063353930
6163613736656635663234613263323833393131363661333737333133356366
3963313236356663373035323264623224b4aa1536a8deba1dfa026efa614fb9
4915763a2629a00fe5ff8f1afc894f1b644f9b08ebc7baefc06229f177b5e446',
6163613736656635663234613263323833393131363661333737333133356366
3963313236356663373035323264623224b4aa1536a8deba1dfa026efa614fb9
4915763a2629a00fe5ff8f1afc894f1b644f9b08ebc7baefc06229f177b5e446',
],
[
'TRS5GvlQ2WCoEzHY',
'82c2f315c69f20ec7ae391fc6c2de85281d54c97f74981b4efec0f5e4606765c
040f1f45386bed4fdb9f998b0e7027c7414bea667b1205027c55eed927364446
3b1832bc5338e3a7e10bf24d8e69f167b94551b6240f65b416feebd28599334a
a4',
040f1f45386bed4fdb9f998b0e7027c7414bea667b1205027c55eed927364446
3b1832bc5338e3a7e10bf24d8e69f167b94551b6240f65b416feebd28599334a
a4',
'f05611e02a2cccbfea4679e8110087a061396332303030396562663732626138
3530363534313362373731316330363964393564653831636566646261363265
38323536366666633438396566323939bce309f535fca3d1fb23e8503f8cf3b8
bca4c4bcfb4021592268f88070b5203f5023a6a39034e34048bc944c22e037fd
e6ca8ca17e2bdc8169d1e714830de6932cfe0dcdd1728e8bf848a6f4f7154d1c
0f8c650e0ef650ba3b90372eb6d13e93e3c79610291a523a3967a3049b04f1d4
c899e1554b04f906c2e6408a16702d19',
3530363534313362373731316330363964393564653831636566646261363265
38323536366666633438396566323939bce309f535fca3d1fb23e8503f8cf3b8
bca4c4bcfb4021592268f88070b5203f5023a6a39034e34048bc944c22e037fd
e6ca8ca17e2bdc8169d1e714830de6932cfe0dcdd1728e8bf848a6f4f7154d1c
0f8c650e0ef650ba3b90372eb6d13e93e3c79610291a523a3967a3049b04f1d4
c899e1554b04f906c2e6408a16702d19',
],
[
'x_a2LHnyqH8WAwkZ',
'badec0c7d9ca734e161a1df6ca4daa8cdbf6b3bbb60ec404b47a23226ec266b1
3837ffc969e9c23e2bbba72facb491a6a3271193a35026a9ebc93698d689bf7b
84fc384f544cc5d71c2945c8c48ae6348c753322fcaf75171b7d8f1e178e8545
3d5c79f03bae6d9705cabbe7004ec81e188812a66313297fcf5d4c61a48614d2
1b5379fae60736f88bc737257bc1cfbef7016108dd1f3a537aaa681544f5bb20
c2d352212d6a2e460311ab7b93b611218b30c7402709bff89ecf814310c2db97
81f4142a54d6d41f82c33208c188a023c70befd697b496efb7c7b8569474a9d5
025a8ea8311c831b3de2b81800f28589b6aef537f6ada2e3d92a1ddd39cfd6b4
8113c8b36be890b099ca4e08655db0e9b5c211e706af052b776924233321412c
b1b540f08d88acd664c55854b05e31a15d4b0dc93b4514f62a88c3d4a451fe34
74d2fbaee742f232959b5f6c2b71a8b6ad95a0d5362f39309aca710f37a4dae2
228e27d35f4d3fc87ee6ece4538587fad7835f9803f5ca64dfd856ae767a3482
c2f315c69f20ec7ae391fc6c2de85281d54c97f74981b4efec0f5e4606765c04
0f1f45386bed4fdb9f998b0e7027c7414bea667b1205027c55eed9273644463b
1832bc5338e3a7e10bf24d8e69f167b94551b6240f65b416feebd28599334aa4
d998a599053cd477f54fc0af62a8ef75eb91096b153751b201b8374c4956508c
6c82ea2ceb0265e74c96032787fe01139b578c3a14fd4e32a85195375b25da60
4f21187ee01df6042971d7c2b9dd8b611527377f28f933559f6b2edfe7bd8896
00627830bcdffcb884989013585cc6da726c7cc57c69e8664a244d234465f2a5
e736512a65e563a2a726915b50247aff165190c75d151139f2b31211a3acf94e
236d27b441fe64e88a443778359d57ba0fed8edbb01cc60a116f9599da4e47bf
e96850ac12d1199a080f1b591c60eae613fba444f9fe31fd42ee333678532556
07fbfeef02cdbb2d0a8d2329adc7fba22aa4cd124c6e66e0b0aca796ba6aeb69
f8e76a9d1aaabe351bc42fefa6054977859b51158ba7143980d76db5b2cc6aec
9f83eac5edf79357632fb60cb4811e71892436e582375860faf8d8c518ee33ca
e08b18af7faa227d97196d7b041bcb366118751fbeaa92d80f86324ce8711cfd
65005cad0ce30878f508eb1ea2735e212ef3e42a78467d4f31b483087c3c63a3
2fa62f2ae92e2acdfc3cf21b3419acc347ff51ccb17161e829cc4a24965fd930
b983d63153b9c13dd85340d7184bf01734d4357c1aabfbdc9711193ffa974832
4f88cf801f35708db0901ffaa55415ca4334678633dda763b5fbfba7cfd51dc5
79149ec9284a4db2349dbc5264d5fca7a72034261716d90a996cb57ba09f7d07
29a2f10cc58eec98d86e9306a3fbf2e711046e86b5a86cc0eed7625be1a01ebf',
3837ffc969e9c23e2bbba72facb491a6a3271193a35026a9ebc93698d689bf7b
84fc384f544cc5d71c2945c8c48ae6348c753322fcaf75171b7d8f1e178e8545
3d5c79f03bae6d9705cabbe7004ec81e188812a66313297fcf5d4c61a48614d2
1b5379fae60736f88bc737257bc1cfbef7016108dd1f3a537aaa681544f5bb20
c2d352212d6a2e460311ab7b93b611218b30c7402709bff89ecf814310c2db97
81f4142a54d6d41f82c33208c188a023c70befd697b496efb7c7b8569474a9d5
025a8ea8311c831b3de2b81800f28589b6aef537f6ada2e3d92a1ddd39cfd6b4
8113c8b36be890b099ca4e08655db0e9b5c211e706af052b776924233321412c
b1b540f08d88acd664c55854b05e31a15d4b0dc93b4514f62a88c3d4a451fe34
74d2fbaee742f232959b5f6c2b71a8b6ad95a0d5362f39309aca710f37a4dae2
228e27d35f4d3fc87ee6ece4538587fad7835f9803f5ca64dfd856ae767a3482
c2f315c69f20ec7ae391fc6c2de85281d54c97f74981b4efec0f5e4606765c04
0f1f45386bed4fdb9f998b0e7027c7414bea667b1205027c55eed9273644463b
1832bc5338e3a7e10bf24d8e69f167b94551b6240f65b416feebd28599334aa4
d998a599053cd477f54fc0af62a8ef75eb91096b153751b201b8374c4956508c
6c82ea2ceb0265e74c96032787fe01139b578c3a14fd4e32a85195375b25da60
4f21187ee01df6042971d7c2b9dd8b611527377f28f933559f6b2edfe7bd8896
00627830bcdffcb884989013585cc6da726c7cc57c69e8664a244d234465f2a5
e736512a65e563a2a726915b50247aff165190c75d151139f2b31211a3acf94e
236d27b441fe64e88a443778359d57ba0fed8edbb01cc60a116f9599da4e47bf
e96850ac12d1199a080f1b591c60eae613fba444f9fe31fd42ee333678532556
07fbfeef02cdbb2d0a8d2329adc7fba22aa4cd124c6e66e0b0aca796ba6aeb69
f8e76a9d1aaabe351bc42fefa6054977859b51158ba7143980d76db5b2cc6aec
9f83eac5edf79357632fb60cb4811e71892436e582375860faf8d8c518ee33ca
e08b18af7faa227d97196d7b041bcb366118751fbeaa92d80f86324ce8711cfd
65005cad0ce30878f508eb1ea2735e212ef3e42a78467d4f31b483087c3c63a3
2fa62f2ae92e2acdfc3cf21b3419acc347ff51ccb17161e829cc4a24965fd930
b983d63153b9c13dd85340d7184bf01734d4357c1aabfbdc9711193ffa974832
4f88cf801f35708db0901ffaa55415ca4334678633dda763b5fbfba7cfd51dc5
79149ec9284a4db2349dbc5264d5fca7a72034261716d90a996cb57ba09f7d07
29a2f10cc58eec98d86e9306a3fbf2e711046e86b5a86cc0eed7625be1a01ebf',
'5d841d0cb575fdb8c7e2cc48be47021d38646237366663323933346163643933
3861336335396666356139383066663033336333303731336263663262626136
643635333333393362306631333266320d73812258a45d90f8240d59c4e39e87
8d4d999f36403a6f0b1d69ab9416792c5e8f2cf427af8423c8213a885fde488c
db95217c64c542f5e2be6b4375ff82e5c72a9f165049546b38295006f50665b1
354350de4a68b5f16a18f7df53e0999f4f7ba5ba0676e416a0444d5b7accda8b
093ae31a23e3cb63b6f404c437071435b8143f282c4cb4403a2b538af5a0d94f
1e582c3bb9d5379ba5d576643854c232d74f303dcac91a711bf440fb24e7ab2a
70ef69008ffb59ad2455d4f1482e77114489a3b5a250384b24062f546f86073a
91fdd85d34bd7814b4ce70ec8d6ea34f98067d7101050f3800f9f1fd92003856
223f8ca142749c2ef4c8d1991a62b0ff86623bf9afce65d55fb5efe80089cee4
e4f12e94e1748c5740f075a94a2290ba2dc892fdfde516ebc190a4db63e77f93
54a3bec5ef695572dddcc9d7c43609724c73bc5bfe79d5f322890e4f39a31e6b
3fb9388c78e133c58e395ca03eca2e8ab9520e4d2e5421e0d9a1f781a564dda7
720d56f413312762da078e0226053672983ecf5bdf18086a6f617071814d61e4
27e6f02167b8d38e381607e4238f21c0b6e6d9222f1cc6348b9f7d6fb084cc3b
306b7acbb94f4b3ab6b66fe539865fb804899d3f64c8bca6bd02ee5509022a50
03d63e259bb414391fc10ae9b2e42c68a2be743488b69ca77c70741820aaeeb0
da2ff00c07787a39ec613e665d78c30b5f57a14fcbe24f00cf55eeb174e23dac
a9eb3587bc8dc8fdf5ef062b7f1659b45c48246055fda699b6ae8b9fcc46a380
ebc6b648662ef5fed1a4fe16c9aa310cc16f5ad642b80549262f5c77335f5435
a43d30459297b754350a9dd635b0ca5342fc798d369225f6d692eea0c901eb72
fd10af1199b7847ffc1a0c5915902fe339772183727c31497c752e3beb1cf010
2c97ab270def6628bdae630172d73a9fc0afed1d893870003828f64518512886
57d62ba52c8d325aa8409b0a40754dc3f84d1c8898e01e20c03464b83d2dae5d
3d9f279778fb1161ac5d8f9c466fd0c6bdd6a21553ea9252ff018ae99b0d4425
0e55d177fb1e0275da97474ea052d85d96b7432c1be840e5994b127b147d1a0c
64f1cea9115a37225c8b49960e0693680ee5593c81784c850811e1f10ce4f9a3
365e3d2d240e0eef8da6404d5a93ebd000a98d5d33dd5a238327e88dfdad2744
ed0c4321a543c1a3231e53550e816c531b73bafec21e32daeecd199c7a2be75f
450ce4d39e10ad81a7ca74877e1661376d7cce557a1d4dde53b503e1512efef6
d607d5074ca8ba29db067454e529aec867907c6eded03ce90835d72974cddcc5
83628bd2948a78a2d666ed89889f59b1dd5b05704b7e68a801ad9f93809e0d8a
ae72a72923883d4de81d867bf639eb5dc581429041ca78763235fe11251254c8
cca8bfe10e8810035e4cce023b6527744d0ea839bb035db99adc3ce742a5491d
c446220a6cb416a0bd3362b424dcdf3e',
3861336335396666356139383066663033336333303731336263663262626136
643635333333393362306631333266320d73812258a45d90f8240d59c4e39e87
8d4d999f36403a6f0b1d69ab9416792c5e8f2cf427af8423c8213a885fde488c
db95217c64c542f5e2be6b4375ff82e5c72a9f165049546b38295006f50665b1
354350de4a68b5f16a18f7df53e0999f4f7ba5ba0676e416a0444d5b7accda8b
093ae31a23e3cb63b6f404c437071435b8143f282c4cb4403a2b538af5a0d94f
1e582c3bb9d5379ba5d576643854c232d74f303dcac91a711bf440fb24e7ab2a
70ef69008ffb59ad2455d4f1482e77114489a3b5a250384b24062f546f86073a
91fdd85d34bd7814b4ce70ec8d6ea34f98067d7101050f3800f9f1fd92003856
223f8ca142749c2ef4c8d1991a62b0ff86623bf9afce65d55fb5efe80089cee4
e4f12e94e1748c5740f075a94a2290ba2dc892fdfde516ebc190a4db63e77f93
54a3bec5ef695572dddcc9d7c43609724c73bc5bfe79d5f322890e4f39a31e6b
3fb9388c78e133c58e395ca03eca2e8ab9520e4d2e5421e0d9a1f781a564dda7
720d56f413312762da078e0226053672983ecf5bdf18086a6f617071814d61e4
27e6f02167b8d38e381607e4238f21c0b6e6d9222f1cc6348b9f7d6fb084cc3b
306b7acbb94f4b3ab6b66fe539865fb804899d3f64c8bca6bd02ee5509022a50
03d63e259bb414391fc10ae9b2e42c68a2be743488b69ca77c70741820aaeeb0
da2ff00c07787a39ec613e665d78c30b5f57a14fcbe24f00cf55eeb174e23dac
a9eb3587bc8dc8fdf5ef062b7f1659b45c48246055fda699b6ae8b9fcc46a380
ebc6b648662ef5fed1a4fe16c9aa310cc16f5ad642b80549262f5c77335f5435
a43d30459297b754350a9dd635b0ca5342fc798d369225f6d692eea0c901eb72
fd10af1199b7847ffc1a0c5915902fe339772183727c31497c752e3beb1cf010
2c97ab270def6628bdae630172d73a9fc0afed1d893870003828f64518512886
57d62ba52c8d325aa8409b0a40754dc3f84d1c8898e01e20c03464b83d2dae5d
3d9f279778fb1161ac5d8f9c466fd0c6bdd6a21553ea9252ff018ae99b0d4425
0e55d177fb1e0275da97474ea052d85d96b7432c1be840e5994b127b147d1a0c
64f1cea9115a37225c8b49960e0693680ee5593c81784c850811e1f10ce4f9a3
365e3d2d240e0eef8da6404d5a93ebd000a98d5d33dd5a238327e88dfdad2744
ed0c4321a543c1a3231e53550e816c531b73bafec21e32daeecd199c7a2be75f
450ce4d39e10ad81a7ca74877e1661376d7cce557a1d4dde53b503e1512efef6
d607d5074ca8ba29db067454e529aec867907c6eded03ce90835d72974cddcc5
83628bd2948a78a2d666ed89889f59b1dd5b05704b7e68a801ad9f93809e0d8a
ae72a72923883d4de81d867bf639eb5dc581429041ca78763235fe11251254c8
cca8bfe10e8810035e4cce023b6527744d0ea839bb035db99adc3ce742a5491d
c446220a6cb416a0bd3362b424dcdf3e',
],
];

63
tests/framework/console/RequestTest.php

@ -0,0 +1,63 @@
<?php
use yii\console\Request;
use yiiunit\TestCase;
/**
* @group console
*/
class RequestTest extends TestCase
{
public function provider()
{
return [
[
'params' => [
'controller',
],
'expected' => [
'route' => 'controller',
'params' => [
]
]
],
[
'params' => [
'controller/route',
'param1',
'-12345',
'--option1',
'--option2=testValue',
'-alias1',
'-alias2=testValue'
],
'expected' => [
'route' => 'controller/route',
'params' => [
'param1',
'-12345',
'option1' => '1',
'option2' => 'testValue',
'_aliases' => [
'alias1' => true,
'alias2' => 'testValue'
]
]
]
]
];
}
/**
* @dataProvider provider
*/
public function testResolve($params, $expected)
{
$request = new Request();
$request->setParams($params);
list($route, $params) = $request->resolve();
$this->assertEquals($expected['route'], $route);
$this->assertEquals($expected['params'], $params);
}
}

44
tests/framework/console/controllers/MigrateControllerTestTrait.php

@ -210,12 +210,12 @@ CODE;
public function testUp()
{
$this->createMigration('test1');
$this->createMigration('test2');
$this->createMigration('test_up1');
$this->createMigration('test_up2');
$this->runMigrateControllerAction('up');
$this->assertMigrationHistory(['m*_base', 'm*_test1', 'm*_test2']);
$this->assertMigrationHistory(['m*_base', 'm*_test_up1', 'm*_test_up2']);
}
/**
@ -223,12 +223,12 @@ CODE;
*/
public function testUpCount()
{
$this->createMigration('test1');
$this->createMigration('test2');
$this->createMigration('test_down1');
$this->createMigration('test_down2');
$this->runMigrateControllerAction('up', [1]);
$this->assertMigrationHistory(['m*_base', 'm*_test1']);
$this->assertMigrationHistory(['m*_base', 'm*_test_down1']);
}
/**
@ -236,13 +236,13 @@ CODE;
*/
public function testDownCount()
{
$this->createMigration('test1');
$this->createMigration('test2');
$this->createMigration('test_down_count1');
$this->createMigration('test_down_count2');
$this->runMigrateControllerAction('up');
$this->runMigrateControllerAction('down', [1]);
$this->assertMigrationHistory(['m*_base', 'm*_test1']);
$this->assertMigrationHistory(['m*_base', 'm*_test_down_count1']);
}
/**
@ -250,8 +250,8 @@ CODE;
*/
public function testDownAll()
{
$this->createMigration('test1');
$this->createMigration('test2');
$this->createMigration('test_down_all1');
$this->createMigration('test_down_all2');
$this->runMigrateControllerAction('up');
$this->runMigrateControllerAction('down', ['all']);
@ -267,13 +267,13 @@ CODE;
$output = $this->runMigrateControllerAction('history');
$this->assertContains('No migration', $output);
$this->createMigration('test1');
$this->createMigration('test2');
$this->createMigration('test_history1');
$this->createMigration('test_history2');
$this->runMigrateControllerAction('up');
$output = $this->runMigrateControllerAction('history');
$this->assertContains('_test1', $output);
$this->assertContains('_test2', $output);
$this->assertContains('_test_history1', $output);
$this->assertContains('_test_history2', $output);
}
/**
@ -281,25 +281,25 @@ CODE;
*/
public function testNew()
{
$this->createMigration('test1');
$this->createMigration('test_new1');
$output = $this->runMigrateControllerAction('new');
$this->assertContains('_test1', $output);
$this->assertContains('_test_new1', $output);
$this->runMigrateControllerAction('up');
$output = $this->runMigrateControllerAction('new');
$this->assertNotContains('_test1', $output);
$this->assertNotContains('_test_new1', $output);
}
public function testMark()
{
$version = '010101_000001';
$this->createMigration('test1', $version);
$this->createMigration('test_mark1', $version);
$this->runMigrateControllerAction('mark', [$version]);
$this->assertMigrationHistory(['m*_base', 'm*_test1']);
$this->assertMigrationHistory(['m*_base', 'm*_test_mark1']);
}
public function testTo()
@ -317,12 +317,12 @@ CODE;
*/
public function testRedo()
{
$this->createMigration('test1');
$this->createMigration('test_redo1');
$this->runMigrateControllerAction('up');
$this->runMigrateControllerAction('redo');
$this->assertMigrationHistory(['m*_base', 'm*_test1']);
$this->assertMigrationHistory(['m*_base', 'm*_test_redo1']);
}
// namespace :

63
tests/framework/db/QueryBuilderTest.php

@ -1582,10 +1582,65 @@ abstract class QueryBuilderTest extends DatabaseTestCase
// // TODO implement
// }
//
// public function testBatchInsert()
// {
// // TODO implement
// }
public function batchInsertProvider()
{
return [
[
'customer',
['email', 'name', 'address'],
[['test@example.com', 'silverfire', 'Kyiv {{city}}, Ukraine']],
$this->replaceQuotes("INSERT INTO [[customer]] ([[email]], [[name]], [[address]]) VALUES ('test@example.com', 'silverfire', 'Kyiv {{city}}, Ukraine')")
],
'escape-danger-chars' => [
'customer',
['address'],
[["SQL-danger chars are escaped: '); --"]],
'expected' => $this->replaceQuotes("INSERT INTO [[customer]] ([[address]]) VALUES ('SQL-danger chars are escaped: \'); --')")
],
[
'customer',
['address'],
[],
''
],
[
'customer',
[],
[["no columns passed"]],
$this->replaceQuotes("INSERT INTO [[customer]] () VALUES ('no columns passed')")
],
'bool-false, bool2-null' => [
'type',
['bool_col', 'bool_col2'],
[[false, null]],
'expected' => $this->replaceQuotes("INSERT INTO [[type]] ([[bool_col]], [[bool_col2]]) VALUES (0, NULL)")
],
[
'{{%type}}',
['{{%type}}.[[float_col]]', '[[time]]'],
[[null, new Expression('now()')]],
"INSERT INTO {{%type}} ({{%type}}.[[float_col]], [[time]]) VALUES (NULL, now())"
],
'bool-false, time-now()' => [
'{{%type}}',
['{{%type}}.[[bool_col]]', '[[time]]'],
[[false, new Expression('now()')]],
'expected' => "INSERT INTO {{%type}} ({{%type}}.[[bool_col]], [[time]]) VALUES (0, now())"
],
];
}
/**
* @dataProvider batchInsertProvider
*/
public function testBatchInsert($table, $columns, $value, $expected)
{
$queryBuilder = $this->getQueryBuilder();
$sql = $queryBuilder->batchInsert($table, $columns, $value);
$this->assertEquals($expected, $sql);
}
//
// public function testUpdate()
// {

8
tests/framework/db/QueryTest.php

@ -319,6 +319,10 @@ abstract class QueryTest extends DatabaseTestCase
$count = (new Query)->select('[[status]], COUNT([[id]])')->from('customer')->groupBy('status')->count('*', $db);
$this->assertEquals(2, $count);
// testing that orderBy() should be ignored here as it does not affect the count anyway.
$count = (new Query)->from('customer')->orderBy('status')->count('*', $db);
$this->assertEquals(3, $count);
}
/**
@ -344,11 +348,11 @@ abstract class QueryTest extends DatabaseTestCase
$query->andFilterCompare('name', 'Doe', 'like');
$this->assertEquals($condition, $query->where);
$condition = ['and', $condition, ['>', 'rating', '9']];
$condition[] = ['>', 'rating', '9'];
$query->andFilterCompare('rating', '>9');
$this->assertEquals($condition, $query->where);
$condition = ['and', $condition, ['<=', 'value', '100']];
$condition[] = ['<=', 'value', '100'];
$query->andFilterCompare('value', '<=100');
$this->assertEquals($condition, $query->where);
}

11
tests/framework/db/mssql/QueryBuilderTest.php

@ -89,4 +89,15 @@ class QueryBuilderTest extends \yiiunit\framework\db\QueryBuilderTest
{
return array_merge(parent::columnTypes(), []);
}
public function batchInsertProvider()
{
$data = parent::batchInsertProvider();
$data['escape-danger-chars']['expected'] = 'INSERT INTO [customer] ([address]) VALUES ("SQL-danger chars are escaped: \'); --")';
$data['bool-false, bool2-null']['expected'] = 'INSERT INTO [type] ([bool_col], [bool_col2]) VALUES (FALSE, NULL)';
$data['bool-false, time-now()']['expected'] = "INSERT INTO {{%type}} ({{%type}}.[[bool_col]], [[time]]) VALUES (FALSE, now())";
return $data;
}
}

72
tests/framework/db/mysql/QueryBuilderTest.php

@ -10,7 +10,7 @@ use yii\db\Schema;
*/
class QueryBuilderTest extends \yiiunit\framework\db\QueryBuilderTest
{
protected $driverName = 'mysql';
protected $driverName = 'mysql';
/**
* this is not used as a dataprovider for testGetColumnType to speed up the test
@ -19,41 +19,41 @@ class QueryBuilderTest extends \yiiunit\framework\db\QueryBuilderTest
public function columnTypes()
{
return array_merge(parent::columnTypes(), [
[
Schema::TYPE_PK . ' AFTER `col_before`',
$this->primaryKey()->after('col_before'),
'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY AFTER `col_before`'
],
[
Schema::TYPE_PK . ' FIRST',
$this->primaryKey()->first(),
'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST'
],
[
Schema::TYPE_PK . ' FIRST',
$this->primaryKey()->first()->after('col_before'),
'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST'
],
[
Schema::TYPE_PK . '(8) AFTER `col_before`',
$this->primaryKey(8)->after('col_before'),
'int(8) NOT NULL AUTO_INCREMENT PRIMARY KEY AFTER `col_before`'
],
[
Schema::TYPE_PK . '(8) FIRST',
$this->primaryKey(8)->first(),
'int(8) NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST'
],
[
Schema::TYPE_PK . '(8) FIRST',
$this->primaryKey(8)->first()->after('col_before'),
'int(8) NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST'
],
[
Schema::TYPE_PK . " COMMENT 'test' AFTER `col_before`",
$this->primaryKey()->comment('test')->after('col_before'),
"int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'test' AFTER `col_before`"
],
[
Schema::TYPE_PK . ' AFTER `col_before`',
$this->primaryKey()->after('col_before'),
'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY AFTER `col_before`'
],
[
Schema::TYPE_PK . ' FIRST',
$this->primaryKey()->first(),
'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST'
],
[
Schema::TYPE_PK . ' FIRST',
$this->primaryKey()->first()->after('col_before'),
'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST'
],
[
Schema::TYPE_PK . '(8) AFTER `col_before`',
$this->primaryKey(8)->after('col_before'),
'int(8) NOT NULL AUTO_INCREMENT PRIMARY KEY AFTER `col_before`'
],
[
Schema::TYPE_PK . '(8) FIRST',
$this->primaryKey(8)->first(),
'int(8) NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST'
],
[
Schema::TYPE_PK . '(8) FIRST',
$this->primaryKey(8)->first()->after('col_before'),
'int(8) NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST'
],
[
Schema::TYPE_PK . " COMMENT 'test' AFTER `col_before`",
$this->primaryKey()->comment('test')->after('col_before'),
"int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'test' AFTER `col_before`"
],
]);
}
}

11
tests/framework/db/pgsql/QueryBuilderTest.php

@ -120,4 +120,15 @@ class QueryBuilderTest extends \yiiunit\framework\db\QueryBuilderTest
$sql = $qb->dropCommentFromTable('comment');
$this->assertEquals($this->replaceQuotes($expected), $sql);
}
public function batchInsertProvider()
{
$data = parent::batchInsertProvider();
$data['escape-danger-chars']['expected'] = "INSERT INTO \"customer\" (\"address\") VALUES ('SQL-danger chars are escaped: ''); --')";
$data['bool-false, bool2-null']['expected'] = 'INSERT INTO "type" ("bool_col", "bool_col2") VALUES (FALSE, NULL)';
$data['bool-false, time-now()']['expected'] = "INSERT INTO {{%type}} ({{%type}}.[[bool_col]], [[time]]) VALUES (FALSE, now())";
return $data;
}
}

25
tests/framework/di/ServiceLocatorTest.php

@ -86,4 +86,29 @@ class ServiceLocatorTest extends TestCase
$this->assertTrue($object2 instanceof $className);
$this->assertTrue($object === $object2);
}
/**
* https://github.com/yiisoft/yii2/issues/11771
*/
public function testModulePropertyIsset()
{
$config = [
'components' => [
'captcha' => [
'name' => 'foo bar',
'class' => 'yii\captcha\Captcha',
],
],
];
$app = new ServiceLocator($config);
$this->assertTrue(isset($app->captcha->name));
$this->assertFalse(empty($app->captcha->name));
$this->assertEquals('foo bar', $app->captcha->name);
$this->assertTrue(isset($app->captcha->name));
$this->assertFalse(empty($app->captcha->name));
}
}

26
tests/framework/helpers/ArrayHelperTest.php

@ -298,6 +298,32 @@ class ArrayHelperTest extends TestCase
$this->assertEquals(['name' => 'b', 'age' => 3], $array[2]);
}
public function testMultisortClosure()
{
$changelog = [
'- Enh #123: test1',
'- Bug #125: test2',
'- Bug #123: test2',
'- Enh: test3',
'- Bug: test4',
];
$i = 0;
ArrayHelper::multisort($changelog, function($line) use (&$i) {
if (preg_match('/^- (Enh|Bug)( #\d+)?: .+$/', $line, $m)) {
$o = ['Bug' => 'C', 'Enh' => 'D'];
return $o[$m[1]] . ' ' . (!empty($m[2]) ? $m[2] : 'AAAA' . $i++);
}
return 'B' . $i++;
}, SORT_ASC, SORT_NATURAL);
$this->assertEquals([
'- Bug #123: test2',
'- Bug #125: test2',
'- Bug: test4',
'- Enh #123: test1',
'- Enh: test3',
], $changelog);
}
public function testMerge()
{
$a = [

2
tests/framework/helpers/ConsoleTest.php

@ -71,7 +71,7 @@ class ConsoleTest extends TestCase
$this->assertEquals(str_repeat('a', 25), $output);
}
/* public function testScreenSize()
/*public function testScreenSize()
{
for ($i = 1; $i < 20; $i++) {
echo implode(', ', Console::getScreenSize(true)) . "\n";

59
tests/framework/helpers/FileHelperTest.php

@ -61,10 +61,11 @@ class FileHelperTest extends TestCase
if ($handle = opendir($dirName)) {
while (false !== ($entry = readdir($handle))) {
if ($entry != '.' && $entry != '..') {
if (is_dir($dirName . DIRECTORY_SEPARATOR . $entry) === true) {
$this->removeDir($dirName . DIRECTORY_SEPARATOR . $entry);
$item = $dirName . DIRECTORY_SEPARATOR . $entry;
if (is_dir($item) === true && !is_link($item)) {
$this->removeDir($item);
} else {
unlink($dirName . DIRECTORY_SEPARATOR . $entry);
unlink($item);
}
}
}
@ -515,6 +516,58 @@ class FileHelperTest extends TestCase
/**
* @depends testFindFiles
*/
public function testFindFilesRecursiveWithSymLink()
{
$dirName = 'test_dir';
$this->createFileStructure([
$dirName => [
'theDir' => [
'file1' => 'abc',
'file2' => 'def',
],
'symDir' => ['symlink', 'theDir'],
],
]);
$dirName = $this->testFilePath . DIRECTORY_SEPARATOR . $dirName;
$expected = [
$dirName . DIRECTORY_SEPARATOR . 'symDir' . DIRECTORY_SEPARATOR . 'file1',
$dirName . DIRECTORY_SEPARATOR . 'symDir' . DIRECTORY_SEPARATOR . 'file2',
$dirName . DIRECTORY_SEPARATOR . 'theDir' . DIRECTORY_SEPARATOR . 'file1',
$dirName . DIRECTORY_SEPARATOR . 'theDir' . DIRECTORY_SEPARATOR . 'file2',
];
$result = FileHelper::findFiles($dirName);
sort($result);
$this->assertEquals($expected, $result);
}
/**
* @depends testFindFiles
*/
public function testFindFilesNotRecursive()
{
$dirName = 'test_dir';
$this->createFileStructure([
$dirName => [
'theDir' => [
'file1' => 'abc',
'file2' => 'def',
],
'symDir' => ['symlink', 'theDir'],
'file3' => 'root'
],
]);
$dirName = $this->testFilePath . DIRECTORY_SEPARATOR . $dirName;
$expected = [
$dirName . DIRECTORY_SEPARATOR . 'file3',
];
$this->assertEquals($expected, FileHelper::findFiles($dirName, ['recursive' => false]));
}
/**
* @depends testFindFiles
*/
public function testFindFilesExclude()
{
$basePath = $this->testFilePath . DIRECTORY_SEPARATOR;

4
tests/framework/i18n/FormatterNumberTest.php

@ -239,8 +239,8 @@ class FormatterNumberTest extends TestCase
$this->assertSame('$0.00', $this->formatter->asCurrency('0'));
// Starting from ICU 52.1, negative currency value will be formatted as -$123,456.12
// see: http://source.icu-project.org/repos/icu/icu/tags/release-52-1/source/data/locales/en.txt
// $value = '-123456.123';
// $this->assertSame("($123,456.12)", $this->formatter->asCurrency($value));
//$value = '-123456.123';
//$this->assertSame("($123,456.12)", $this->formatter->asCurrency($value));
$this->formatter->locale = 'de-DE';
$this->formatter->currencyCode = null;

10
tests/framework/rbac/ManagerTestCase.php

@ -182,6 +182,16 @@ abstract class ManagerTestCase extends TestCase
'blablabla' => false,
null => false,
],
'guest' => [
// all actions denied for guest (user not exists)
'createPost' => false,
'readPost' => false,
'updatePost' => false,
'deletePost' => false,
'updateAnyPost' => false,
'blablabla' => false,
null => false,
],
];
$params = ['authorID' => 'author B'];

140
tests/framework/rest/UrlRuleTest.php

@ -200,8 +200,9 @@ class UrlRuleTest extends TestCase
* Proviedes test cases for createUrl() method
*
* - first param are properties of the UrlRule
* - second param is the route to create
* - third param is the expected URL
* - second param is an array of test cases, containing two element arrays:
* - first element is the route to create
* - second element is the expected URL
*/
public function createUrlDataProvider()
{
@ -212,40 +213,46 @@ class UrlRuleTest extends TestCase
'controller' => 'v1/channel',
'pluralize' => true,
],
['v1/channel/index'], // route
'v1/channels', // expected
[ // test cases: route, expected
[ ['v1/channel/index'], 'v1/channels' ],
[ ['v1/channel/index', 'offset' => 1], 'v1/channels?offset=1' ],
[ ['v1/channel/view', 'id' => 42], 'v1/channels/42' ],
[ ['v1/channel/options'], 'v1/channels' ],
[ ['v1/channel/options', 'id' => 42], 'v1/channels/42' ],
[ ['v1/channel/delete'], false ],
],
],
[
[ // Rule properties
'controller' => ['v1/channel'],
'pluralize' => true,
],
['v1/channel/index'], // route
'v1/channels', // expected
],
[
[ // Rule properties
'controller' => ['v1/channel', 'v1/u' => 'v1/user'],
'pluralize' => true,
[ // test cases: route, expected
[ ['v1/channel/index'], 'v1/channels' ],
[ ['v1/channel/index', 'offset' => 1], 'v1/channels?offset=1' ],
[ ['v1/channel/view', 'id' => 42], 'v1/channels/42' ],
[ ['v1/channel/options'], 'v1/channels' ],
[ ['v1/channel/options', 'id' => 42], 'v1/channels/42' ],
[ ['v1/channel/delete'], false ],
],
['v1/channel/index'], // route
'v1/channels', // expected
],
[
[ // Rule properties
'controller' => ['v1/channel', 'v1/u' => 'v1/user'],
'pluralize' => true,
],
['v1/user/index'], // route
'v1/u', // expected
],
[
[ // Rule properties
'controller' => 'v1/channel',
'pluralize' => true,
[ // test cases: route, expected
[ ['v1/channel/index'], 'v1/channels' ],
[ ['v1/channel/view', 'id' => 42], 'v1/channels/42' ],
[ ['v1/channel/options'], 'v1/channels' ],
[ ['v1/channel/options', 'id' => 42], 'v1/channels/42' ],
[ ['v1/channel/delete'], false ],
[ ['v1/user/index'], 'v1/u' ],
[ ['v1/user/view', 'id' => 1], 'v1/u/1' ],
[ ['v1/channel/options'], 'v1/channels' ],
[ ['v1/channel/options', 'id' => 42], 'v1/channels/42' ],
[ ['v1/user/delete'], false ],
],
['v1/channel/index', 'offset' => 1], // route
'v1/channels?offset=1', // expected
],
@ -255,60 +262,97 @@ class UrlRuleTest extends TestCase
'controller' => 'v1/channel',
'pluralize' => false,
],
['v1/channel/index'], // route
'v1/channel', // expected
[ // test cases: route, expected
[ ['v1/channel/index'], 'v1/channel' ],
[ ['v1/channel/index', 'offset' => 1], 'v1/channel?offset=1' ],
[ ['v1/channel/view', 'id' => 42], 'v1/channel/42' ],
[ ['v1/channel/options'], 'v1/channel' ],
[ ['v1/channel/options', 'id' => 42], 'v1/channel/42' ],
[ ['v1/channel/delete'], false ],
],
],
[
[ // Rule properties
'controller' => ['v1/channel'],
'pluralize' => false,
],
['v1/channel/index'], // route
'v1/channel', // expected
],
[
[ // Rule properties
'controller' => ['v1/channel', 'v1/u' => 'v1/user'],
'pluralize' => false,
[ // test cases: route, expected
[ ['v1/channel/index'], 'v1/channel' ],
[ ['v1/channel/index', 'offset' => 1], 'v1/channel?offset=1' ],
[ ['v1/channel/view', 'id' => 42], 'v1/channel/42' ],
[ ['v1/channel/options'], 'v1/channel' ],
[ ['v1/channel/options', 'id' => 42], 'v1/channel/42' ],
[ ['v1/channel/delete'], false ],
],
['v1/channel/index'], // route
'v1/channel', // expected
],
[
[ // Rule properties
'controller' => ['v1/channel', 'v1/u' => 'v1/user'],
'pluralize' => false,
],
['v1/user/index'], // route
'v1/u', // expected
[ // test cases: route, expected
[ ['v1/channel/index'], 'v1/channel' ],
[ ['v1/channel/view', 'id' => 42], 'v1/channel/42' ],
[ ['v1/channel/options'], 'v1/channel' ],
[ ['v1/channel/options', 'id' => 42], 'v1/channel/42' ],
[ ['v1/channel/delete'], false ],
[ ['v1/user/index'], 'v1/u' ],
[ ['v1/user/view', 'id' => 1], 'v1/u/1' ],
[ ['v1/user/options'], 'v1/u' ],
[ ['v1/user/options', 'id' => 42], 'v1/u/42' ],
[ ['v1/user/delete'], false ],
],
],
// using extra patterns
[
[ // Rule properties
'controller' => 'v1/channel',
'pluralize' => false,
'pluralize' => true,
'extraPatterns' => [
'{id}/my' => 'my',
'my' => 'my',
// this should not create a URL, no GET definition
'POST {id}/my2' => 'my2',
],
],
[ // test cases: route, expected
// normal actions should behave as before
[ ['v1/channel/index'], 'v1/channels' ],
[ ['v1/channel/index', 'offset' => 1], 'v1/channels?offset=1' ],
[ ['v1/channel/view', 'id' => 42], 'v1/channels/42' ],
[ ['v1/channel/options'], 'v1/channels' ],
[ ['v1/channel/options', 'id' => 42], 'v1/channels/42' ],
[ ['v1/channel/delete'], false ],
[ ['v1/channel/my'], 'v1/channels/my' ],
[ ['v1/channel/my', 'id' => 42], 'v1/channels/42/my' ],
[ ['v1/channel/my2'], false ],
[ ['v1/channel/my2', 'id' => 42], false ],
],
['v1/channel/index', 'offset' => 1], // route
'v1/channel?offset=1', // expected
],
// ---
];
}
/**
* @dataProvider createUrlDataProvider
*/
public function testCreateUrl($rule, $params, $expected)
public function testCreateUrl($rule, $tests)
{
$this->mockWebApplication();
Yii::$app->set('request', new Request(['hostInfo' => 'http://api.example.com', 'scriptUrl' => '/index.php']));
$route = array_shift($params);
foreach($tests as $test) {
list($params, $expected) = $test;
$this->mockWebApplication();
Yii::$app->set('request', new Request(['hostInfo' => 'http://api.example.com', 'scriptUrl' => '/index.php']));
$route = array_shift($params);
$manager = new UrlManager([
'cache' => null,
]);
$rule = new UrlRule($rule);
$this->assertEquals($expected, $rule->createUrl($manager, $route, $params));
$manager = new UrlManager([
'cache' => null,
]);
$rule = new UrlRule($rule);
$this->assertEquals($expected, $rule->createUrl($manager, $route, $params));
}
}
}

10
tests/framework/validators/CompareValidatorTest.php

@ -83,11 +83,11 @@ class CompareValidatorTest extends TestCase
[$value + 1, false],
[$value - 1, true],
],
//'non-op' => [
// [$value, false],
// [$value + 1, false],
// [$value - 1, false],
//],
/*'non-op' => [
[$value, false],
[$value + 1, false],
[$value - 1, false],
],*/
];
}

2
tests/framework/web/ResponseTest.php

@ -42,7 +42,7 @@ class ResponseTest extends \yiiunit\TestCase
$fullContent = file_get_contents($dataFile);
$_SERVER['HTTP_RANGE'] = 'bytes=' . $rangeHeader;
ob_start();
$this->response->sendFile($dataFile)->send( );
$this->response->sendFile($dataFile)->send();
$content = ob_get_clean();
$this->assertEquals($expectedContent, $content);

8
tests/framework/widgets/ActiveFieldTest.php

@ -497,6 +497,14 @@ EOD;
$this->assertEqualsWithoutLE($expectedValue, $actualValue);
}
public function testEmptyTag()
{
$this->activeField->options = ['tag' => false];
$expectedValue = '<input type="hidden" id="activefieldtestmodel-attributename" class="form-control" name="ActiveFieldTestModel[attributeName]">';
$actualValue = $this->activeField->hiddenInput()->label(false)->error(false)->hint(false)->render();
$this->assertEqualsWithoutLE($expectedValue, trim($actualValue));
}
/**
* Helper methods
*/

2
tests/framework/widgets/LinkPagerTest.php

@ -72,7 +72,7 @@ class LinkPagerTest extends \yiiunit\TestCase
static::assertContains('<span class="foo-bar">&laquo;</span>', $output);
}
public function testDisabledPageElementOptionsWithTagOption()
public function testDisabledPageElementOptionsWithTagOption()
{
$pagination = new Pagination();
$pagination->setPage(0);

33
tests/framework/widgets/ListViewTest.php

@ -170,4 +170,37 @@ HTML
$this->getListView(['itemOptions' => $itemOptions])->run();
$this->expectOutputString($expected);
}
public function testBeforeAndAfterItem()
{
$before = function ($model, $key, $index, $widget) {
$widget = get_class($widget);
return "<!-- before: {$model['id']}, key: $key, index: $index, widget: $widget -->";
};
$after = function ($model, $key, $index, $widget) {
if ($model['id'] === 1) {
return null;
}
$widget = get_class($widget);
return "<!-- after: {$model['id']}, key: $key, index: $index, widget: $widget -->";
};
$this->getListView([
'beforeItem' => $before,
'afterItem' => $after
])->run();
$this->expectOutputString(<<<HTML
<div id="w0" class="list-view"><div class="summary">Showing <b>1-3</b> of <b>3</b> items.</div>
<!-- before: 1, key: 0, index: 0, widget: yii\widgets\ListView -->
<div data-key="0">0</div>
<!-- before: 2, key: 1, index: 1, widget: yii\widgets\ListView -->
<div data-key="1">1</div>
<!-- after: 2, key: 1, index: 1, widget: yii\widgets\ListView -->
<!-- before: 3, key: 2, index: 2, widget: yii\widgets\ListView -->
<div data-key="2">2</div>
<!-- after: 3, key: 2, index: 2, widget: yii\widgets\ListView -->
</div>
HTML
);
}
}

41
tests/framework/widgets/MenuTest.php

@ -2,6 +2,7 @@
namespace yiiunit\framework\widgets;
use Yii;
use yii\widgets\Menu;
/**
@ -158,5 +159,45 @@ HTML;
$this->assertEqualsWithoutLE($expected, $output);
}
public function testActiveItemClosure()
{
$output = Menu::widget([
'route' => 'test/test',
'params' => [],
'linkTemplate' => '',
'labelTemplate' => '',
'items' => [
[
'label' => 'item1',
'url' => '#',
'template' => 'label: {label}; url: {url}',
'active' => function ($item, $hasActiveChild, $isItemActive, $widget) {
return isset($item, $hasActiveChild, $isItemActive, $widget);
}
],
[
'label' => 'item2',
'template' => 'label: {label}',
'active' => false
],
[
'label' => 'item3 (no template)',
'active' => 'somestring'
],
]
]);
$expected = <<<HTML
<ul><li class="active">label: item1; url: #</li>
<li>label: item2</li>
<li class="active"></li></ul>
HTML;
$this->assertEqualsWithoutLE($expected, $output);
}
public function testIsItemActive()
{
// TODO: implement test of protected method isItemActive()
}
}

37
tests/framework/widgets/PjaxTest.php

@ -0,0 +1,37 @@
<?php
namespace yiiunit\framework\widgets;
use yii\data\ArrayDataProvider;
use yii\widgets\ListView;
use yii\widgets\Pjax;
use yiiunit\TestCase;
class PjaxTest extends TestCase
{
public function testGeneratedIdByPjaxWidget()
{
ListView::$counter = 0;
Pjax::$counter = 0;
$nonPjaxWidget1 = new ListView(['dataProvider' => new ArrayDataProvider()]);
ob_start();
$pjax1 = new Pjax();
ob_end_clean();
$nonPjaxWidget2 = new ListView(['dataProvider' => new ArrayDataProvider()]);
ob_start();
$pjax2 = new Pjax();
ob_end_clean();
$this->assertEquals('w0', $nonPjaxWidget1->options['id']);
$this->assertEquals('w1', $nonPjaxWidget2->options['id']);
$this->assertEquals('p0', $pjax1->options['id']);
$this->assertEquals('p1', $pjax2->options['id']);
}
protected function setUp()
{
parent::setUp();
$this->mockWebApplication();
}
}

170
tests/js/data/yii.gridView.html

@ -0,0 +1,170 @@
<!-- Filters for testing of multiple grid views -->
<div id="w-common-filters">
<input name="PostSearch[id]" type="text">
<input name="PostSearch[name]" type="text">
</div>
<!-- The main setup -->
<div id="w0" class="grid-view">
<table>
<thead>
<tr>
<th><input id="w0-check-all" name="selection_all" value="1" type="checkbox"></th>
<th>Name</th>
<th>Category</th>
<th>Tags</th>
</tr>
<tr id="w0-filters">
<td>&nbsp;</td>
<td><input id="w0-name" name="PostSearch[name]" type="text"></td>
<td>
<select id="w0-category" name="PostSearch[category_id]">
<option value="" selected>None</option>
<option value="1">Programming</option>
<option value="2">Traveling</option>
</select>
</td>
<td>
<select id="w0-tags" name="PostSearch[tags][]" multiple>
<option value="1">html</option>
<option value="2">css</option>
<option value="3">js</option>
<option value="4">php</option>
</select>
</td>
</tr>
</thead>
<tbody>
<tr data-key="1">
<td><input class="w0-check-row" name="selection[]" value="1" type="checkbox"></td>
<td>Name 1</td>
<td>Programming</td>
<td>html, css</td>
</tr>
<tr data-key="2">
<td><input class="w0-check-row" name="selection[]" value="2" type="checkbox"></td>
<td>Name 2</td>
<td>Programming</td>
<td>js</td>
</tr>
<tr data-key="3">
<td><input class="w0-check-row" name="selection[]" value="3" type="checkbox"></td>
<td>Name 3</td>
<td>Programming</td>
<td>php</td>
</tr>
</tbody>
</table>
</div>
<!-- The basic setup, used for testing of multiple grid views -->
<div id="w1" class="grid-view">
<table>
<thead>
<tr>
<th><input name="selection_all" value="1" type="checkbox"></th>
<th>ID</th>
<th>Name</th>
</tr>
<tr id="w1-filters">
<td>&nbsp;</td>
<td><input name="PostSearch[id]" type="text"></td>
<td><input name="PostSearch[name]" type="text"></td>
</tr>
</thead>
<tbody>
<tr data-key="1">
<td><input name="selection[]" value="1" type="checkbox"></td>
<td>1</td>
<td>Name 1</td>
</tr>
<tr data-key="2">
<td><input name="selection[]" value="2" type="checkbox"></td>
<td>2</td>
<td>Name 2</td>
</tr>
</tbody>
</table>
</div>
<!-- https://github.com/yiisoft/yii2/pull/10284 -->
<div id="w2">
<table>
<thead>
<tr>
<th>Name</th>
<th>Tags</th>
</tr>
<tr id="w2-filters">
<td><input name="PostSearch[name]" type="text"></td>
<td>
<input type="hidden" name="PostSearch[tags]" value="-1">
<select id="w2-tags" name="PostSearch[tags][]" multiple>
<option value="1">html</option>
<option value="2">css</option>
<option value="3">js</option>
<option value="4">php</option>
</select>
</td>
</tr>
</thead>
<tbody>
<tr data-key="1">
<td>Name 1</td>
<td>html, css</td>
</tr>
<tr data-key="2">
<td>Name 2</td>
<td>js</td>
</tr>
<tr data-key="3">
<td>Name 3</td>
<td>php</td>
</tr>
</tbody>
</table>
</div>
<!-- Setup for testing that event handlers are correctly removed with new selectors -->
<div id="w3">
<table>
<thead>
<tr>
<th>
<input name="selection_all" value="1" type="checkbox">
<input name="selection_all2" value="1" type="checkbox">
</th>
<th>ID</th>
<th>Name</th>
</tr>
<tr id="w3-filters">
<td>&nbsp;</td>
<td><input name="PostSearch[id]" type="text"></td>
<td><input name="PostSearch[name]" type="text"></td>
</tr>
</thead>
<tbody>
<tr data-key="1">
<td>
<input class="w3-check-row" name="selection[]" value="1" type="checkbox">
<input name="selection2[]" value="1" type="checkbox">
</td>
<td>1</td>
<td>Name 1</td>
</tr>
<tr data-key="2">
<td>
<input class="w3-check-row" name="selection[]" value="2" type="checkbox">
<input name="selection2[]" value="2" type="checkbox">
</td>
<td>2</td>
<td>Name 2</td>
</tr>
</tbody>
</table>
</div>

754
tests/js/tests/yii.gridView.test.js

@ -0,0 +1,754 @@
var assert = require('chai').assert;
var sinon;
var withData = require('leche').withData;
var jsdom = require('mocha-jsdom');
var fs = require('fs');
var vm = require('vm');
describe('yii.gridView', function () {
var yiiGridViewPath = 'framework/assets/yii.gridView.js';
var yiiPath = 'framework/assets/yii.js';
var jQueryPath = 'vendor/bower/jquery/dist/jquery.js';
var $;
var $gridView;
var settings = {
filterUrl: '/posts/index',
filterSelector: '#w0-filters input, #w0-filters select'
};
var commonSettings = {
filterUrl: '/posts/index',
filterSelector: '#w-common-filters input, #w-common-filters select'
};
var $textInput;
var $select;
var $multipleSelect;
var $listBox;
var $checkAllCheckbox;
var $checkRowCheckboxes;
function registerYii() {
var code = fs.readFileSync(yiiPath);
var script = new vm.Script(code);
var sandbox = {window: window, jQuery: $};
var context = new vm.createContext(sandbox);
script.runInContext(context);
return sandbox.window.yii;
}
function registerTestableCode() {
var yii = registerYii();
var code = fs.readFileSync(yiiGridViewPath);
var script = new vm.Script(code);
var context = new vm.createContext({window: window, document: window.document, yii: yii});
script.runInContext(context);
}
var gridViewHtml = fs.readFileSync('tests/js/data/yii.gridView.html', 'utf-8');
var html = '<!doctype html><html><head><meta charset="utf-8"></head><body>' + gridViewHtml + '</body></html>';
jsdom({
html: html,
src: fs.readFileSync(jQueryPath, 'utf-8')
});
before(function () {
$ = window.$;
registerTestableCode();
sinon = require('sinon');
});
beforeEach(function () {
$textInput = $('#w0-name');
$select = $('#w0-category');
$multipleSelect = $('#w0-tags');
$listBox = $('#w2-tags');
$checkAllCheckbox = $('#w0-check-all');
$checkRowCheckboxes = $('.w0-check-row');
});
afterEach(function () {
if ($gridView.length) {
$gridView.yiiGridView('destroy');
}
$textInput.val('');
$select.val('');
$multipleSelect.find('option:selected').prop('selected', false);
$listBox.find('option:selected').prop('selected', false);
$checkAllCheckbox.prop('checked', false);
$checkRowCheckboxes.prop('checked', false);
});
/**
* Simulate pressing "Enter" button while focused on some element
* @param $el
*/
function pressEnter($el) {
var e = $.Event('keydown', {keyCode: 13});
$el.trigger(e);
}
/**
* Simulate pressing keyboard button while focused on the text input. For simplicity, intended to use with letter
* buttons, such as "a", "b", etc. Case insensitive.
* @param $el
* @param buttonName
*/
function pressButton($el, buttonName) {
$el.val(buttonName);
var keyCode = buttonName.charCodeAt(0);
var e = $.Event('keydown', {keyCode: keyCode});
$el.trigger(e);
}
/**
* Simulate changing value in the select
* @param $el
* @param value
*/
function changeValue($el, value) {
$el.val(value);
var e = $.Event('change');
$el.trigger(e);
}
/**
* Simulate losing focus of the element after the value was changed
* @param $el
*/
function loseFocus($el) {
var e = $.Event('change');
$el.trigger(e);
}
/**
* Simulate click in the checkbox
* @param $el
*/
function click($el) {
var e = $.Event('click');
$el.trigger(e);
}
/**
* Simulate hovering on the new value and pressing "Enter" button in the select
* @param $el
*/
function hoverAndPressEnter($el) {
pressEnter($el);
// After pressing enter while hovering the value will be immediately changed as well like with losing focus
loseFocus($el);
}
describe('init', function () {
var customSettings = {
filterUrl: '/posts/filter',
filterSelector: '#w-common-filters input'
};
withData({
'no method specified': [function () {
$gridView = $('.grid-view').yiiGridView(commonSettings);
}, commonSettings],
'no method specified, custom settings': [function () {
$gridView = $('.grid-view').yiiGridView(customSettings);
}, customSettings],
'manual method call': [function () {
$gridView = $('.grid-view').yiiGridView('init', commonSettings);
}, commonSettings]
}, function (initFunction, expectedSettings) {
it('should save settings for all elements', function () {
initFunction();
assert.deepEqual($('#w0').yiiGridView('data'), {settings: expectedSettings});
assert.deepEqual($('#w1').yiiGridView('data'), {settings: expectedSettings});
});
});
describe('with repeated call', function () {
var jQuerySubmitStub;
before(function () {
jQuerySubmitStub = sinon.stub($.fn, 'submit');
});
after(function () {
jQuerySubmitStub.restore();
});
it('should remove "filter" event handler', function () {
$gridView = $('#w0').yiiGridView(settings);
$gridView.yiiGridView(settings);
// Change selector to make sure event handlers are removed regardless of the selector
$gridView.yiiGridView({
filterUrl: '/posts/index',
filterSelector: '#w0-filters select'
});
pressEnter($textInput);
assert.isFalse(jQuerySubmitStub.called);
changeValue($select, 1);
assert.isTrue(jQuerySubmitStub.calledOnce);
});
});
});
describe('applyFilter', function () {
var jQuerySubmit = function () {
};
var jQuerySubmitStub;
beforeEach(function () {
jQuerySubmitStub = sinon.stub($.fn, 'submit', jQuerySubmit);
});
afterEach(function () {
jQuerySubmitStub.restore();
});
describe('with beforeFilter returning not false', function () {
var calledMethods = []; // For testing the order of called methods
var beforeFilterSpy;
var afterFilterSpy;
before(function () {
jQuerySubmit = function () {
calledMethods.push('submit');
return this;
};
beforeFilterSpy = sinon.spy(function () {
calledMethods.push('beforeFilter');
});
afterFilterSpy = sinon.spy(function () {
calledMethods.push('afterFilter');
});
});
after(function () {
jQuerySubmit = function () {
};
beforeFilterSpy.reset();
afterFilterSpy.reset();
calledMethods = [];
});
var message = 'should send the request to correct url with correct parameters and apply events in ' +
'correct order';
it(message, function () {
$gridView = $('#w0').yiiGridView(settings)
.on('beforeFilter', beforeFilterSpy)
.on('afterFilter', afterFilterSpy);
$textInput.val('a');
$select.val(1);
$multipleSelect.find('option[value="1"]').prop('selected', true);
$multipleSelect.find('option[value="2"]').prop('selected', true);
$gridView.yiiGridView('applyFilter');
var expectedHtml = '<form action="/posts/index" method="get" class="gridview-filter-form" ' +
'style="display:none" data-pjax="">' +
'<input type="hidden" name="PostSearch[name]" value="a">' +
'<input type="hidden" name="PostSearch[category_id]" value="1">' +
'<input type="hidden" name="PostSearch[tags][]" value="1">' +
'<input type="hidden" name="PostSearch[tags][]" value="2">' +
'</form>';
var $form = $('.grid-view .gridview-filter-form');
assert.equal($form.get(0).outerHTML, expectedHtml);
assert.isTrue(beforeFilterSpy.calledOnce);
assert.instanceOf(beforeFilterSpy.getCall(0).args[0], $.Event);
assert.equal($(beforeFilterSpy.getCall(0).args[0].target).attr('id'), $gridView.attr('id'));
assert.isTrue(jQuerySubmitStub.calledOnce);
assert.equal(jQuerySubmitStub.returnValues[0].attr('class'), 'gridview-filter-form');
assert.isTrue(afterFilterSpy.calledOnce);
assert.instanceOf(afterFilterSpy.getCall(0).args[0], $.Event);
assert.equal($(afterFilterSpy.getCall(0).args[0].target).attr('id'), $gridView.attr('id'));
assert.deepEqual(calledMethods, ['beforeFilter', 'submit', 'afterFilter']);
});
});
describe('with beforeFilter returning false', function () {
var beforeFilterSpy;
var afterFilterSpy;
before(function () {
beforeFilterSpy = sinon.spy(function () {
return false;
});
afterFilterSpy = sinon.spy();
});
after(function () {
beforeFilterSpy.reset();
afterFilterSpy.reset();
});
it('should prevent from sending request and triggering "afterFilter" event', function () {
$gridView = $('#w0').yiiGridView(settings)
.on('beforeFilter', beforeFilterSpy)
.on('afterFilter', afterFilterSpy);
$gridView.yiiGridView('applyFilter');
assert.isTrue(beforeFilterSpy.calledOnce);
assert.isFalse(jQuerySubmitStub.called);
assert.isFalse(afterFilterSpy.called);
});
});
describe('with different urls', function () {
describe('with no filter data sent', function () {
withData({
'query parameters': [
'/posts/index?foo=1&bar=2',
'/posts/index',
'PostSearch[name]=&PostSearch[category_id]=&foo=1&bar=2'
],
// https://github.com/yiisoft/yii2/pull/10302
'query parameter with multiple values (not array)': [
'/posts/index?foo=1&foo=2',
'/posts/index',
'PostSearch[name]=&PostSearch[category_id]=&foo=1&foo=2'
],
'query parameter with multiple values (array)': [
'/posts/index?foo[]=1&foo[]=2',
'/posts/index',
'PostSearch[name]=&PostSearch[category_id]=&foo[]=1&foo[]=2'
],
// https://github.com/yiisoft/yii2/issues/12836
'anchor': [
'/posts/index#post',
'/posts/index#post',
'PostSearch[name]=&PostSearch[category_id]='
],
'query parameters, anchor': [
'/posts/index?foo=1&bar=2#post',
'/posts/index#post',
'PostSearch[name]=&PostSearch[category_id]=&foo=1&bar=2'
],
'relative url, query parameters': [
'?foo=1&bar=2',
'',
'PostSearch[name]=&PostSearch[category_id]=&foo=1&bar=2'
],
'relative url, anchor': [
'#post',
'#post',
'PostSearch[name]=&PostSearch[category_id]='
],
'relative url, query parameters, anchor': [
'?foo=1&bar=2#post',
'#post',
'PostSearch[name]=&PostSearch[category_id]=&foo=1&bar=2'
]
}, function (filterUrl, expectedUrl, expectedQueryString) {
it('should send the request to correct url with correct parameters', function () {
var customSettings = $.extend({}, settings, {filterUrl: filterUrl});
$gridView = $('#w0').yiiGridView(customSettings);
$gridView.yiiGridView('applyFilter');
var $form = $gridView.find('.gridview-filter-form');
assert.isTrue(jQuerySubmitStub.calledOnce);
assert.equal($form.attr('action'), expectedUrl);
assert.equal(decodeURIComponent($form.serialize()), expectedQueryString);
});
});
});
// https://github.com/yiisoft/yii2/pull/10302
describe('with filter data sent', function () {
it('should send the request to correct url with new parameter values', function () {
var filterUrl = '/posts/index?CategorySearch[id]=5&CategorySearch[name]=c' +
'&PostSearch[name]=a&PostSearch[category_id]=1&PostSearch[tags][]=1&PostSearch[tags][]=2' +
'&foo[]=1&foo[]=2&bar=1#post';
var customSettings = $.extend({}, settings, {filterUrl: filterUrl});
$gridView = $('#w0').yiiGridView(customSettings);
$textInput.val('b');
$select.val('1'); // Leave value as is (simulate setting "selected" in HTML)
$multipleSelect.find('option[value="2"]').prop('selected', true);
$multipleSelect.find('option[value="3"]').prop('selected', true);
$gridView.yiiGridView('applyFilter');
var $form = $gridView.find('.gridview-filter-form');
assert.isTrue(jQuerySubmitStub.calledOnce);
assert.equal($form.attr('action'), '/posts/index#post');
// Parameters not related with current filter are appended to the end
var expectedQueryString = 'PostSearch[name]=b&PostSearch[category_id]=1' +
'&PostSearch[tags][]=2&PostSearch[tags][]=3' +
'&CategorySearch[id]=5&CategorySearch[name]=c' +
'&foo[]=1&foo[]=2&bar=1';
assert.equal(decodeURIComponent($form.serialize()), expectedQueryString);
});
});
});
// https://github.com/yiisoft/yii2/pull/10284
describe('with list box', function () {
var queryString = 'PostSearch[name]=&PostSearch[tags]=-1&PostSearch[tags][]=1&PostSearch[tags][]=2';
beforeEach(function () {
$listBox.find('option[value="1"]').prop('selected', true);
$listBox.find('option[value="2"]').prop('selected', true);
});
describe('with values selected', function () {
it('should send the request to correct url with correct parameters', function () {
$gridView = $('#w2').yiiGridView({
filterUrl: '/posts/index',
filterSelector: '#w2-filters input, #w2-filters select'
});
$gridView.yiiGridView('applyFilter');
var $form = $gridView.find('.gridview-filter-form');
assert.equal($form.attr('action'), '/posts/index');
assert.equal(decodeURIComponent($form.serialize()), queryString);
});
});
describe('with unselected values after applied filter', function () {
it('should send the request to correct url with correct parameters', function () {
$gridView = $('#w2').yiiGridView({
filterUrl: '/posts/index/?' + queryString,
filterSelector: '#w2-filters input, #w2-filters select'
});
$listBox.find('option:selected').prop('selected', false);
$gridView.yiiGridView('applyFilter');
var $form = $gridView.find('.gridview-filter-form');
assert.equal($form.attr('action'), '/posts/index/');
assert.equal(decodeURIComponent($form.serialize()), 'PostSearch[name]=&PostSearch[tags]=-1');
});
});
});
describe('with repeated method call', function () {
it('should delete the hidden form', function () {
$gridView = $('#w0').yiiGridView(settings);
$gridView.yiiGridView('applyFilter');
$gridView.yiiGridView('applyFilter');
var $form = $gridView.find('.gridview-filter-form');
assert.lengthOf($form, 1);
});
});
describe('with filter event handlers', function () {
beforeEach(function () {
$gridView = $('#w0').yiiGridView(settings);
});
describe('with text entered in the text input', function () {
it('should not submit form', function () {
pressButton($textInput, 'a');
assert.isFalse(jQuerySubmitStub.called);
});
});
describe('with "Enter" pressed in the text input', function () {
it('should submit form once', function () {
pressEnter($textInput);
assert.isTrue(jQuerySubmitStub.calledOnce);
});
});
describe('with text entered in the text input and lost focus', function () {
it('should submit form once', function () {
pressButton($textInput, 'a');
loseFocus($textInput);
assert.isTrue(jQuerySubmitStub.calledOnce);
});
});
describe('with value changed in the select', function () {
it('should submit form once', function () {
changeValue($select, 1);
assert.isTrue(jQuerySubmitStub.calledOnce);
});
});
describe('with hover on different value and "Enter" pressed in select', function () {
it('should submit form once', function () {
// Simulate hovering on new value and pressing "Enter"
$select.val(1);
hoverAndPressEnter($select);
assert.isTrue(jQuerySubmitStub.calledOnce);
});
});
});
});
describe('setSelectionColumn method', function () {
describe('with name option and', function () {
withData({
'nothing else': [{}],
'checkAll option': [{checkAll: 'selection_all'}],
'multiple option set to true': [{multiple: true}],
'multiple and checkAll options, multiple set to false': [{multiple: false, checkAll: 'selection_all'}]
}, function (customOptions) {
it('should update data and do not activate "check all" functionality', function () {
$gridView = $('#w0').yiiGridView(settings);
var defaultOptions = {name: 'selection[]'};
var options = $.extend({}, defaultOptions, customOptions);
$gridView.yiiGridView('setSelectionColumn', options);
assert.equal($gridView.yiiGridView('data').selectionColumn, 'selection[]');
click($checkAllCheckbox);
assert.lengthOf($checkRowCheckboxes.filter(':checked'), 0);
click($checkAllCheckbox); // Back to initial condition
click($checkRowCheckboxes);
assert.isFalse($checkAllCheckbox.prop('checked'));
});
});
});
describe('with name, multiple and checkAll options, multiple set to true and', function () {
withData({
'nothing else': [{}],
// https://github.com/yiisoft/yii2/pull/11729
'class option': [{'class': 'w0-check-row'}]
}, function (customOptions) {
it('should update data and "check all" functionality should work', function () {
$gridView = $('#w0').yiiGridView(settings);
var defaultOptions = {name: 'selection[]', multiple: true, checkAll: 'selection_all'};
var options = $.extend({}, defaultOptions, customOptions);
$gridView.yiiGridView('setSelectionColumn', options);
assert.equal($gridView.yiiGridView('data').selectionColumn, 'selection[]');
var $checkFirstRowCheckbox = $checkRowCheckboxes.filter('[value="1"]');
// Check all
click($checkAllCheckbox);
assert.lengthOf($checkRowCheckboxes.filter(':checked'), 3);
assert.isTrue($checkAllCheckbox.prop('checked'));
// Uncheck all
click($checkAllCheckbox);
assert.lengthOf($checkRowCheckboxes.filter(':checked'), 0);
assert.isFalse($checkAllCheckbox.prop('checked'));
// Check all manually
click($checkRowCheckboxes);
assert.lengthOf($checkRowCheckboxes.filter(':checked'), 3);
assert.isTrue($checkAllCheckbox.prop('checked'));
// Uncheck all manually
click($checkRowCheckboxes);
assert.lengthOf($checkRowCheckboxes.filter(':checked'), 0);
assert.isFalse($checkAllCheckbox.prop('checked'));
// Check first row
click($checkFirstRowCheckbox);
assert.isTrue($checkFirstRowCheckbox.prop('checked'));
assert.lengthOf($checkRowCheckboxes.filter(':checked'), 1);
assert.isFalse($checkAllCheckbox.prop('checked'));
// Then check all
click($checkAllCheckbox);
assert.lengthOf($checkRowCheckboxes.filter(':checked'), 3);
assert.isTrue($checkAllCheckbox.prop('checked'));
// Uncheck first row
click($checkFirstRowCheckbox);
assert.isFalse($checkFirstRowCheckbox.prop('checked'));
assert.lengthOf($checkRowCheckboxes.filter(':checked'), 2);
assert.isFalse($checkAllCheckbox.prop('checked'));
});
});
});
describe('with repeated calls', function () {
var jQueryPropStub;
before(function () {
jQueryPropStub = sinon.stub($, 'prop');
});
after(function () {
jQueryPropStub.restore();
});
it('should not duplicate event handler calls', function () {
$gridView = $('#w3').yiiGridView({
filterUrl: '/posts/index',
filterSelector: '#w3-filters input, #w3-filters select'
});
$gridView.yiiGridView('setSelectionColumn', {
name: 'selection[]',
multiple: true,
checkAll: 'selection_all'
});
// Change selectors to make sure event handlers are removed regardless of the selector
$gridView.yiiGridView('setSelectionColumn', {
name: 'selection2[]',
multiple: true,
checkAll: 'selection_all2'
});
$gridView.yiiGridView('setSelectionColumn', {
name: 'selection[]',
multiple: true,
checkAll: 'selection_all'
});
$gridView.yiiGridView('setSelectionColumn', {
'class': 'w3-check-row',
multiple: true,
checkAll: 'selection_all'
});
// Check first row ("prop" should be called once)
click($gridView.find('input[name="selection[]"][value="1"]'));
// Check all rows ("prop" should be called 2 times, 1 time for each row)
click($gridView.find('input[name="selection_all"]'));
assert.equal(jQueryPropStub.callCount, 3);
});
});
});
describe('getSelectedRows method', function () {
withData({
'selectionColumn not set, no rows selected': [undefined, [], false, []],
'selectionColumn not set, 1st and 2nd rows selected': [undefined, [1, 2], false, []],
'selectionColumn set, no rows selected': ['selection[]', [], false, []],
'selectionColumn set, 1st row selected': ['selection[]', [1], false, [1]],
'selectionColumn set, 1st and 2nd rows selected': ['selection[]', [1, 2], false, [1, 2]],
'selectionColumn set, all rows selected, "Check all" checkbox checked': [
'selection[]', [1, 2, 3], true, [1, 2, 3]
]
}, function (selectionColumn, selectedRows, checkAll, expectedSelectedRows) {
it('should return array with ids of selected rows', function () {
$gridView = $('#w0').yiiGridView(settings);
$gridView.yiiGridView('setSelectionColumn', {name: selectionColumn});
for (var i = 0; i < selectedRows.length; i++) {
$checkRowCheckboxes.filter('[value="' + selectedRows[i] + '"]').prop('checked', true);
}
if (checkAll) {
$checkAllCheckbox.prop('checked', true);
}
assert.deepEqual($gridView.yiiGridView('getSelectedRows'), expectedSelectedRows);
});
});
});
describe('destroy method', function () {
var jQuerySubmitStub;
var jQueryPropStub;
var beforeFilterSpy;
var afterFilterSpy;
beforeEach(function () {
jQuerySubmitStub = sinon.stub($.fn, 'submit');
jQueryPropStub = sinon.stub($, 'prop');
beforeFilterSpy = sinon.spy();
afterFilterSpy = sinon.spy();
});
afterEach(function () {
jQuerySubmitStub.restore();
jQueryPropStub.restore();
beforeFilterSpy.reset();
afterFilterSpy.reset();
});
it('should remove saved settings for destroyed element only and return initial jQuery object', function () {
$gridView = $('.grid-view').yiiGridView(commonSettings);
var $gridView1 = $('#w0');
var $gridView2 = $('#w1');
var destroyResult = $gridView1.yiiGridView('destroy');
assert.strictEqual(destroyResult, $gridView1);
assert.isUndefined($gridView1.yiiGridView('data'));
assert.deepEqual($gridView2.yiiGridView('data'), {settings: commonSettings});
});
it('should remove "beforeFilter" and "afterFilter" event handlers for destroyed element only', function () {
$gridView = $('.grid-view').yiiGridView(commonSettings)
.on('beforeFilter', beforeFilterSpy)
.on('afterFilter', afterFilterSpy);
var $gridView1 = $('#w0');
var $gridView2 = $('#w1');
$gridView1.yiiGridView('destroy');
assert.throws(function () {
$gridView1.yiiGridView('applyFilter');
}, "Cannot read property 'settings' of undefined");
$gridView1.yiiGridView(settings); // Reinitialize without "beforeFilter" and "afterFilter" event handlers
$gridView1.yiiGridView('applyFilter');
assert.isTrue(jQuerySubmitStub.calledOnce);
assert.isFalse(beforeFilterSpy.called);
assert.isFalse(afterFilterSpy.called);
$gridView2.yiiGridView('applyFilter');
assert.isTrue(jQuerySubmitStub.calledTwice);
assert.isTrue(beforeFilterSpy.calledOnce);
assert.isTrue(afterFilterSpy.calledOnce);
});
it('should remove "filter" event handler for destroyed element only', function () {
var $gridView1 = $('#w0');
var $gridView2 = $('#w1');
$gridView1.yiiGridView(settings);
$gridView2.yiiGridView({
filterUrl: '/posts/index',
filterSelector: '#w1-filters input, #w1-filters select'
});
$gridView2.yiiGridView('destroy');
pressEnter($gridView2.find('input[name="PostSearch[id]"]'));
assert.isFalse(jQuerySubmitStub.called);
pressEnter($textInput);
assert.isTrue(jQuerySubmitStub.calledOnce);
});
it('should remove "checkRow" and "checkAllRows" filter event handlers for destroyed element only', function () {
$gridView = $('.grid-view').yiiGridView(commonSettings);
var options = {name: 'selection[]', multiple: true, checkAll: 'selection_all'};
var $gridView1 = $('#w0');
var $gridView2 = $('#w1');
$gridView1.yiiGridView('setSelectionColumn', options);
$gridView2.yiiGridView('setSelectionColumn', options);
$gridView2.yiiGridView('destroy');
click($gridView2.find('input[name="selection_all"]'));
click($gridView2.find('input[name="selection[]"][value="1"]'));
assert.equal(jQueryPropStub.callCount, 0);
click($checkRowCheckboxes.filter('[value="1"]')); // Check first row ("prop" should be called once)
click($checkAllCheckbox); // Check all rows ("prop" should be called 3 times, 1 time for each row)
assert.equal(jQueryPropStub.callCount, 4);
});
});
describe('data method', function () {
it('should return saved settings', function () {
$gridView = $('#w0').yiiGridView(settings);
assert.deepEqual($gridView.yiiGridView('data'), {settings: settings});
});
});
describe('call of not existing method', function () {
it('should throw according error', function () {
$gridView = $('#w0').yiiGridView(settings);
assert.throws(function () {
$gridView.yiiGridView('foobar');
}, 'Method foobar does not exist in jQuery.yiiGridView');
});
});
});
Loading…
Cancel
Save