diff --git a/docs/guide/db-query-builder.md b/docs/guide/db-query-builder.md
index b997a65..c87c795 100644
--- a/docs/guide/db-query-builder.md
+++ b/docs/guide/db-query-builder.md
@@ -1,65 +1,88 @@
-Query Builder and Query
-=======================
+Query Builder
+=============
> Note: This section is under development.
-Yii provides a basic database access layer as described in the [Database basics](db-dao.md) section.
-The database access layer provides a low-level way to interact with the database. While useful in some situations,
-it can be tedious and error-prone to write raw SQLs. An alternative approach is to use the Query Builder.
-The Query Builder provides an object-oriented vehicle for generating queries to be executed.
+Built on top of [Database Access Objects](db-dao.md), query builder allows you to construct a SQL statement
+in a programmatic way. Compared to writing raw SQLs, using query builder will help you write more readable
+SQL-related code and generate more secure SQL statements.
-A typical usage of the query builder looks like the following:
+Using query builder usually involves two steps:
+
+1. Build a [[yii\db\Query]] object to represent different parts (e.g. `SELECT`, `FROM`) of a SELECT SQL statement.
+2. Execute a query method (e.g. `all()`) of [[yii\db\Query]].
+
+Behind the scene, [[yii\db\QueryBuilder]] is invoked in the second step to convert a [[yii\db\Query]] object into
+a SQL statement for the particular DBMS.
+
+The following code shows a typical use case of query builder:
```php
$rows = (new \yii\db\Query())
- ->select('id, name')
+ ->select('id, email')
->from('user')
+ ->where(['name' => 'Smith'])
->limit(10)
->all();
+```
-// which is equivalent to the following code:
-
-$query = (new \yii\db\Query())
- ->select('id, name')
- ->from('user')
- ->limit(10);
-
-// Create a command. You can get the actual SQL using $command->sql
-$command = $query->createCommand();
+It generates and executes the following SQL statement:
-// Execute the command:
-$rows = $command->queryAll();
+```sql
+SELECT `id`, `email`
+FROM `user`
+WHERE `name` = :name
+LIMIT 10
```
-Query Methods
--------------
+where the `:name` parameter is bound with the string `'Smith'`. Depending on the DBMS being used, query builder
+will properly quote the column and table names and bind parameter values to the generated SQL statement.
+
-As you can see, [[yii\db\Query]] is the main player that you need to deal with. Behind the scenes,
-`Query` is actually only responsible for representing various query information. The actual query
-building logic is done by [[yii\db\QueryBuilder]] when you call the `createCommand()` method,
-and the query execution is done by [[yii\db\Command]].
+## Query Methods
-For convenience, [[yii\db\Query]] provides a set of commonly used query methods that will build
-the query, execute it, and return the result. For example,
+[[yii\db\Query]] provides a whole set of methods for different query purposes:
-- [[yii\db\Query::all()|all()]]: builds the query, executes it and returns all results as an array.
+- [[yii\db\Query::all()|all()]]: returns an array of rows with each row being an associative array of name-value pairs.
- [[yii\db\Query::one()|one()]]: returns the first row of the result.
- [[yii\db\Query::column()|column()]]: returns the first column of the result.
-- [[yii\db\Query::scalar()|scalar()]]: returns the first column in the first row of the result.
-- [[yii\db\Query::exists()|exists()]]: returns a value indicating whether the query results in anything.
-- [[yii\db\Query::count()|count()]]: returns the result of a `COUNT` query. Other similar methods
- include `sum($q)`, `average($q)`, `max($q)` and `min($q)`, which support the so-called aggregational data query. The `$q`
- parameter is mandatory for these methods and can be either the column name or expression.
+- [[yii\db\Query::scalar()|scalar()]]: returns a scalar value located at the first row and first column of the result.
+- [[yii\db\Query::exists()|exists()]]: returns a value indicating whether the query contains any result.
+- [[yii\db\Query::count()|count()]]: returns the result of a `COUNT` query.
+- Other aggregation query methods, including [[yii\db\Query::sum()|sum($q)]], [[yii\db\Query::average()|average($q)]],
+ [[yii\db\Query::max()|max($q)]], [[yii\db\Query::min()|min($q)]]. The `$q` parameter is mandatory for these methods
+ and can be either a column name or a DB expression.
+All of the above methods take an optional `$db` parameter representing the [[yii\db\Connection|DB connection]] that
+should be used to perform a DB query. If you omit this parameter, the `db` application component will be used
+as the DB connection.
-Building Query
---------------
+Given a [[yii\db\Query]], you can create a [[yii\db\Command]] and further work with this command object. For example,
-In the following, we will explain how to build various clauses in a SQL statement. For simplicity,
-we use `$query` to represent a [[yii\db\Query]] object.
+```php
+$command = (new \yii\db\Query())
+ ->select('id, email')
+ ->from('user')
+ ->where(['name' => 'Smith'])
+ ->limit(10)
+ ->createCommand();
+
+// show the SQL statement
+echo $command->sql;
+// returns all rows of the query result
+$rows= $command->queryAll();
+```
-### `SELECT`
+## Building Queries
+
+To build a [[yii\db\Query]] object, you call different query building methods to specify different parts of
+a SQL statement. The names of these methods resemble the SQL keywords used in the corresponding parts of the SQL
+statement. For example, to specify the `FROM` part, you would call the `from()` method. In the following, we will
+describe in detail the usage of each query building method.
+
+
+### `SELECT`
In order to form a basic `SELECT` query, you need to specify what columns to select and from what table:
@@ -100,7 +123,7 @@ To select distinct rows, you may call `distinct()`, like the following:
$query->select('user_id')->distinct()->from('post');
```
-### `FROM`
+### `FROM`
To specify which table(s) to select data from, call `from()`:
@@ -133,7 +156,7 @@ $query->select('*')->from(['u' => $subQuery]);
```
-### `WHERE`
+### `WHERE`
Usually data is selected based upon certain criteria. Query Builder has some useful methods to specify these, the most powerful of which being `where`. It can be used in multiple ways.
@@ -321,7 +344,7 @@ A value is *empty* if it is null, an empty string, a string consisting of whites
You may also use `andFilterWhere()` and `orFilterWhere()` to append more filter conditions.
-### `ORDER BY`
+### `ORDER BY`
For ordering results `orderBy` and `addOrderBy` could be used:
@@ -420,9 +443,11 @@ $anotherQuery->select('id, type, name')->from('user')->limit(10);
$query->union($anotherQuery);
```
+## Indexing Query Results
+
+TBD
-Batch Query
------------
+## Batch Query
When working with large amounts of data, methods such as [[yii\db\Query::all()]] are not suitable
because they require loading all data into the memory. To keep the memory requirement low, Yii
diff --git a/docs/guide/input-file-upload.md b/docs/guide/input-file-upload.md
index 76f386e..eb89dc6 100644
--- a/docs/guide/input-file-upload.md
+++ b/docs/guide/input-file-upload.md
@@ -141,18 +141,24 @@ It is wise to validate the type of file uploaded. FileValidator has the property
public function rules()
{
return [
- [['file'], 'file', 'extensions' => 'gif, jpg',],
+ [['file'], 'file', 'extensions' => 'gif, jpg'],
];
}
```
-Keep in mind that only the file extension will be validated, but not the actual file content. In order to validate the content as well, use the `mimeTypes` property of `FileValidator`:
+By default it will validate against file content mime type corresponding to extension specified. For gif it will be
+`image/gif`, for `jpg` it will be `image/jpeg`.
+
+
+Note that some mime types can't be detected properly by PHP's fileinfo extension that is used by `file` validator. For
+example, `csv` files are detected as `text/plain` instead of `text/csv`. You can turn off such behavior by setting
+`checkExtensionByMimeType` to `false` and specifying mime types manually:
```php
public function rules()
{
return [
- [['file'], 'file', 'extensions' => 'jpg, png', 'mimeTypes' => 'image/jpeg, image/png',],
+ [['file'], 'file', 'checkExtensionByMimeType' => false, 'extensions' => 'csv', 'mimeTypes' => 'text/plain'],
];
}
```
diff --git a/docs/guide/runtime-routing.md b/docs/guide/runtime-routing.md
index 5283dc8..2745a65 100644
--- a/docs/guide/runtime-routing.md
+++ b/docs/guide/runtime-routing.md
@@ -42,7 +42,7 @@ The [[yii\web\UrlManager|URL manager]] supports two URL formats: the default URL
The default URL format uses a query parameter named `r` to represent the route and normal query parameters
to represent the query parameters associated with the route. For example, the URL `/index.php?r=post/view&id=100` represents
-the route `post/view` and the `id` query parameter 100. The default URL format does not require any configuration about
+the route `post/view` and the `id` query parameter 100. The default URL format does not require any configuration of
the [[yii\web\UrlManager|URL manager]] and works in any Web server setup.
The pretty URL format uses the extra path following the entry script name to represent the route and the associated
@@ -275,7 +275,7 @@ The rest of the properties are optional. However, their configuration shown abov
A URL rule is an instance of [[yii\web\UrlRule]] or its child class. Each URL rule consists of a pattern used
for matching the path info part of URLs, a route, and a few query parameters. A URL rule can be used to parse a request
-if its pattern matches the requested URL and a URL rule can be used to create a URL if its route and query parameter
+if its pattern matches the requested URL. A URL rule can be used to create a URL if its route and query parameter
names match those that are given.
When the pretty URL format is enabled, the [[yii\web\UrlManager|URL manager]] uses the URL rules declared in its
diff --git a/docs/guide/start-installation.md b/docs/guide/start-installation.md
index 265e30b..6897bcc 100644
--- a/docs/guide/start-installation.md
+++ b/docs/guide/start-installation.md
@@ -185,7 +185,7 @@ server {
root /path/to/basic/web;
index index.php;
- access_log /path/to/basic/log/access.log main;
+ access_log /path/to/basic/log/access.log;
error_log /path/to/basic/log/error.log;
location / {
diff --git a/docs/internals/versions.md b/docs/internals/versions.md
index 6e25d38..12730f5 100644
--- a/docs/internals/versions.md
+++ b/docs/internals/versions.md
@@ -1,10 +1,22 @@
Yii Versioning
==============
-This document summarizes the versioning policy of Yii. In general, Yii follows the [Semantic Versioning](http://semver.org/).
+This document summarizes the versioning policy of Yii. Our current versioning strategy can be
+described as [ferver](https://github.com/jonathanong/ferver), which we considered is more practical
+and reasonable than [Semantic Versioning](http://semver.org/) (See [#7408](https://github.com/yiisoft/yii2/issues/7408) for more references).
+
+Within the core developer team, we have emphasized several times that it is important to keep 2.0.x releases 100% BC-compatible.
+But this is an ideal plan. The ferver article has given out a real world example that this is hard to achieve in practice,
+regardless you are using semver or not.
+
+In summary, our versioning policy is as follows:
## Patch Releases `2.x.Y`
-
+
+Patch releases, which should be 100% BC-compatible. Ideally, we hope they contain bug fixes only so that it reduces
+the chance of breaking BC. Practically, since 2.0.x is released more frequently, we are also adding minor features
+to it so that users can enjoy them earlier.
+
* Maintained on a branch named `2.x`
* Mainly contain bug fixes and minor feature enhancements
* No major features.
@@ -16,6 +28,9 @@ This document summarizes the versioning policy of Yii. In general, Yii follows t
## Minor Releases `2.X.0`
+BC-breaking releases, which contains major features and changes that may break BC. Upgrading from earlier versions may
+not be trivial, but a complete upgrade guide or even script will be available.
+
* Developed on master branch
* Mainly contain new features and bug fixes
* Contain minor features and bug fixes merged from patch releases
@@ -27,4 +42,5 @@ This document summarizes the versioning policy of Yii. In general, Yii follows t
## Major Releases `X.0.0`
-None in plan.
+It's like 2.0 over 1.0. We expect this only happens every 3 to 5 years, depending on external technology advancement
+(such as PHP upgraded from 5.0 to 5.4).
diff --git a/extensions/jui/InputWidget.php b/extensions/jui/InputWidget.php
index fdff0a4..eaee003 100644
--- a/extensions/jui/InputWidget.php
+++ b/extensions/jui/InputWidget.php
@@ -8,7 +8,6 @@
namespace yii\jui;
use yii\base\Model;
-use yii\base\InvalidConfigException;
use yii\helpers\Html;
/**
diff --git a/extensions/jui/README.md b/extensions/jui/README.md
index bd2a0a8..7917f44 100644
--- a/extensions/jui/README.md
+++ b/extensions/jui/README.md
@@ -30,7 +30,7 @@ to the require section of your `composer.json` file.
Usage
-----
-Fhe following
+The following
single line of code in a view file would render a [JQuery UI DatePicker](http://api.jqueryui.com/datepicker/) widget:
```php
diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md
index 5bb53cb..24d44fa 100644
--- a/framework/CHANGELOG.md
+++ b/framework/CHANGELOG.md
@@ -4,7 +4,10 @@ Yii Framework 2 Change Log
2.0.4 under development
-----------------------
-- no changes in this release.
+- Bug #7529: Fixed `yii\web\Response::sendContentAsFile()` that was broken in 2.0.3 (samdark)
+- Enh #7488: Added `StringHelper::explode` to perform explode with trimming and skipping of empty elements (SilverFire, nineinchnick, creocoder, samdark)
+- Enh #7562: `yii help` now lists all sub-commands by default (callmez)
+- Enh: Added `yii\helper\Console::wrapText()` method to wrap indented text by console window width and used it in `yii help` command (cebe)
2.0.3 March 01, 2015
diff --git a/framework/base/Component.php b/framework/base/Component.php
index 72ea35c..3039fa6 100644
--- a/framework/base/Component.php
+++ b/framework/base/Component.php
@@ -199,16 +199,18 @@ class Component extends Object
}
/**
- * Checks if a property value is null.
+ * Checks if a property is set, i.e. defined and not null.
* This method will check in the following order and act accordingly:
*
- * - a property defined by a setter: return whether the property value is null
- * - a property of a behavior: return whether the property value is null
+ * - a property defined by a setter: return whether the property is set
+ * - a property of a behavior: return whether the property is set
+ * - return `false` for non existing properties
*
* Do not call this method directly as it is a PHP magic method that
* will be implicitly called when executing `isset($component->property)`.
* @param string $name the property name or the event name
- * @return boolean whether the named property is null
+ * @return boolean whether the named property is set
+ * @see http://php.net/manual/en/function.isset.php
*/
public function __isset($name)
{
@@ -238,6 +240,7 @@ class Component extends Object
* will be implicitly called when executing `unset($component->property)`.
* @param string $name the property name
* @throws InvalidCallException if the property is read only.
+ * @see http://php.net/manual/en/function.unset.php
*/
public function __unset($name)
{
diff --git a/framework/base/Object.php b/framework/base/Object.php
index 9b7efe2..0545711 100644
--- a/framework/base/Object.php
+++ b/framework/base/Object.php
@@ -163,7 +163,7 @@ class Object implements Configurable
}
/**
- * Checks if the named property is set (not null).
+ * Checks if a property is set, i.e. defined and not null.
*
* Do not call this method directly as it is a PHP magic method that
* will be implicitly called when executing `isset($object->property)`.
@@ -171,6 +171,7 @@ class Object implements Configurable
* Note that if the property is not defined, false will be returned.
* @param string $name the property name or the event name
* @return boolean whether the named property is set (not null).
+ * @see http://php.net/manual/en/function.isset.php
*/
public function __isset($name)
{
@@ -192,6 +193,7 @@ class Object implements Configurable
* If the property is read-only, it will throw an exception.
* @param string $name the property name
* @throws InvalidCallException if the property is read only.
+ * @see http://php.net/manual/en/function.unset.php
*/
public function __unset($name)
{
diff --git a/framework/console/controllers/HelpController.php b/framework/console/controllers/HelpController.php
index c24cf97..977a596 100644
--- a/framework/console/controllers/HelpController.php
+++ b/framework/console/controllers/HelpController.php
@@ -187,14 +187,56 @@ class HelpController extends Controller
$this->stdout("\nThe following commands are available:\n\n", Console::BOLD);
$len = 0;
foreach ($commands as $command => $description) {
- if (($l = strlen($command)) > $len) {
+ $result = Yii::$app->createController($command);
+ if ($result !== false) {
+ /** @var $controller Controller */
+ list($controller, $actionID) = $result;
+ $actions = $this->getActions($controller);
+ if (!empty($actions)) {
+ $prefix = $controller->getUniqueId();
+ foreach ($actions as $action) {
+ $string = $prefix . '/' . $action;
+ if ($action === $controller->defaultAction) {
+ $string .= ' (default)';
+ }
+ if (($l = strlen($string)) > $len) {
+ $len = $l;
+ }
+ }
+ }
+ } elseif (($l = strlen($command)) > $len) {
$len = $l;
}
}
foreach ($commands as $command => $description) {
- $this->stdout("- " . $this->ansiFormat($command, Console::FG_YELLOW));
- $this->stdout(str_repeat(' ', $len + 3 - strlen($command)) . $description);
+ $this->stdout('- ' . $this->ansiFormat($command, Console::FG_YELLOW));
+ $this->stdout(str_repeat(' ', $len + 4 - strlen($command)));
+ $this->stdout(Console::wrapText($description, $len + 4 + 2), Console::BOLD);
$this->stdout("\n");
+
+ $result = Yii::$app->createController($command);
+ if ($result !== false) {
+ list($controller, $actionID) = $result;
+ $actions = $this->getActions($controller);
+ if (!empty($actions)) {
+ $prefix = $controller->getUniqueId();
+ foreach ($actions as $action) {
+ $string = ' ' . $prefix . '/' . $action;
+ $this->stdout(' ' . $this->ansiFormat($string, Console::FG_GREEN));
+ if ($action === $controller->defaultAction) {
+ $string .= ' (default)';
+ $this->stdout(' (default)', Console::FG_YELLOW);
+ }
+ $summary = $controller->getActionHelpSummary($controller->createAction($action));
+ if ($summary !== '') {
+ $this->stdout(str_repeat(' ', $len + 4 - strlen($string)));
+ $this->stdout(Console::wrapText($summary, $len + 4 + 2));
+ }
+ $this->stdout("\n");
+ }
+ }
+ $this->stdout("\n");
+ }
}
$scriptName = $this->getScriptName();
$this->stdout("\nTo see the help of each command, enter:\n", Console::BOLD);
@@ -223,14 +265,24 @@ class HelpController extends Controller
if (!empty($actions)) {
$this->stdout("\nSUB-COMMANDS\n\n", Console::BOLD);
$prefix = $controller->getUniqueId();
+
+ $maxlen = 5;
+ foreach ($actions as $action) {
+ $len = strlen($prefix.'/'.$action) + 2 + ($action === $controller->defaultAction ? 10 : 0);
+ if ($maxlen < $len) {
+ $maxlen = $len;
+ }
+ }
foreach ($actions as $action) {
$this->stdout('- ' . $this->ansiFormat($prefix.'/'.$action, Console::FG_YELLOW));
+ $len = strlen($prefix.'/'.$action) + 2;
if ($action === $controller->defaultAction) {
$this->stdout(' (default)', Console::FG_GREEN);
+ $len += 10;
}
$summary = $controller->getActionHelpSummary($controller->createAction($action));
if ($summary !== '') {
- $this->stdout(': ' . $summary);
+ $this->stdout(str_repeat(' ', $maxlen - $len + 2) . Console::wrapText($summary, $maxlen + 2));
}
$this->stdout("\n");
}
diff --git a/framework/helpers/BaseConsole.php b/framework/helpers/BaseConsole.php
index 366cee1..b12e852 100644
--- a/framework/helpers/BaseConsole.php
+++ b/framework/helpers/BaseConsole.php
@@ -636,6 +636,46 @@ class BaseConsole
}
/**
+ * Word wrap text with indentation to fit the screen size
+ *
+ * If screen size could not be detected, or the indentation is greater than the screen size, the text will not be wrapped.
+ *
+ * The first line will **not** be indented, so `Console::wrapText("Lorem ipsum dolor sit amet.", 4)` will result in the
+ * following output, given the screen width is 16 characters:
+ *
+ * ```
+ * Lorem ipsum
+ * dolor sit
+ * amet.
+ * ```
+ *
+ * @param string $text the text to be wrapped
+ * @param integer $indent number of spaces to use for indentation.
+ * @param boolean $refresh whether to force refresh of screen size.
+ * This will be passed to [[getScreenSize()]].
+ * @return string the wrapped text.
+ * @since 2.0.3
+ */
+ public static function wrapText($text, $indent = 0, $refresh = false)
+ {
+ $size = static::getScreenSize($refresh);
+ if ($size === false || $size[0] <= $indent) {
+ return $text;
+ }
+ $pad = str_repeat(' ', $indent);
+ $lines = explode("\n", wordwrap($text, $size[0] - $indent, "\n", true));
+ $first = true;
+ foreach($lines as $i => $line) {
+ if ($first) {
+ $first = false;
+ continue;
+ }
+ $lines[$i] = $pad . $line;
+ }
+ return implode("\n", $lines);
+ }
+
+ /**
* Gets input from STDIN and returns a string right-trimmed for EOLs.
*
* @param boolean $raw If set to true, returns the raw string without trimming
diff --git a/framework/helpers/BaseHtml.php b/framework/helpers/BaseHtml.php
index ca0d785..49dd28b 100644
--- a/framework/helpers/BaseHtml.php
+++ b/framework/helpers/BaseHtml.php
@@ -839,7 +839,7 @@ class BaseHtml
* - encode: boolean, whether to HTML-encode the checkbox labels. Defaults to true.
* This option is ignored if `item` option is set.
* - separator: string, the HTML code that separates items.
- * - itemOptions: array, the options for generating the radio button tag using [[checkbox()]].
+ * - itemOptions: array, the options for generating the checkbox tag using [[checkbox()]].
* - item: callable, a callback that can be used to customize the generation of the HTML code
* corresponding to a single item in $items. The signature of this callback must be:
*
@@ -903,8 +903,10 @@ class BaseHtml
* @param string|array $selection the selected value(s).
* @param array $items the data item used to generate the radio buttons.
* The array keys are the radio button values, while the array values are the corresponding labels.
- * @param array $options options (name => config) for the radio button list. The following options are supported:
+ * @param array $options options (name => config) for the radio button list container tag.
+ * The following options are specially handled:
*
+ * - tag: string, the tag name of the container element.
* - unselect: string, the value that should be submitted when none of the radio buttons is selected.
* By setting this option, a hidden input will be generated.
* - encode: boolean, whether to HTML-encode the checkbox labels. Defaults to true.
@@ -1287,7 +1289,6 @@ class BaseHtml
*
* The rest of the options will be rendered as the attributes of the resulting tag. The values will
* be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered.
- *
* See [[renderTagAttributes()]] for details on how attributes are being rendered.
*
* @return string the generated radio button tag
@@ -1473,12 +1474,17 @@ class BaseHtml
* @param array $items the data item used to generate the checkboxes.
* The array keys are the checkbox values, and the array values are the corresponding labels.
* Note that the labels will NOT be HTML-encoded, while the values will.
- * @param array $options options (name => config) for the checkbox list. The following options are specially handled:
+ * @param array $options options (name => config) for the checkbox list container tag.
+ * The following options are specially handled:
*
+ * - tag: string, the tag name of the container element.
* - unselect: string, the value that should be submitted when none of the checkboxes is selected.
* You may set this option to be null to prevent default value submission.
* If this option is not set, an empty string will be submitted.
+ * - encode: boolean, whether to HTML-encode the checkbox labels. Defaults to true.
+ * This option is ignored if `item` option is set.
* - separator: string, the HTML code that separates items.
+ * - itemOptions: array, the options for generating the checkbox tag using [[checkbox()]].
* - item: callable, a callback that can be used to customize the generation of the HTML code
* corresponding to a single item in $items. The signature of this callback must be:
*
@@ -1509,12 +1515,17 @@ class BaseHtml
* @param array $items the data item used to generate the radio buttons.
* The array keys are the radio values, and the array values are the corresponding labels.
* Note that the labels will NOT be HTML-encoded, while the values will.
- * @param array $options options (name => config) for the radio button list. The following options are specially handled:
+ * @param array $options options (name => config) for the radio button list container tag.
+ * The following options are specially handled:
*
+ * - tag: string, the tag name of the container element.
* - unselect: string, the value that should be submitted when none of the radio buttons is selected.
* You may set this option to be null to prevent default value submission.
* If this option is not set, an empty string will be submitted.
+ * - encode: boolean, whether to HTML-encode the checkbox labels. Defaults to true.
+ * This option is ignored if `item` option is set.
* - separator: string, the HTML code that separates items.
+ * - itemOptions: array, the options for generating the radio button tag using [[radio()]].
* - item: callable, a callback that can be used to customize the generation of the HTML code
* corresponding to a single item in $items. The signature of this callback must be:
*
diff --git a/framework/helpers/BaseStringHelper.php b/framework/helpers/BaseStringHelper.php
index f9a4607..3e3e683 100644
--- a/framework/helpers/BaseStringHelper.php
+++ b/framework/helpers/BaseStringHelper.php
@@ -234,4 +234,35 @@ class BaseStringHelper
return mb_strtolower(mb_substr($string, -$bytes, null, '8bit'), Yii::$app->charset) === mb_strtolower($with, Yii::$app->charset);
}
}
+
+ /**
+ * Explodes string into array, optionally trims values and skips empty ones
+ *
+ * @param string $string String to be exploded.
+ * @param string $delimiter Delimiter. Default is ','.
+ * @param mixed $trim Whether to trim each element. Can be:
+ * - boolean - to trim normally;
+ * - string - custom characters to trim. Will be passed as a second argument to `trim()` function.
+ * - callable - will be called for each value instead of trim. Takes the only argument - value.
+ * @param boolean $skipEmpty Whether to skip empty strings between delimiters. Default is false.
+ * @return array
+ */
+ public static function explode($string, $delimiter = ',', $trim = true, $skipEmpty = false) {
+ $result = explode($delimiter, $string);
+ if ($trim) {
+ if ($trim === true) {
+ $trim = 'trim';
+ } elseif (!is_callable($trim)) {
+ $trim = function($v) use ($trim) {
+ return trim($v, $trim);
+ };
+ }
+ $result = array_map($trim, $result);
+ }
+ if ($skipEmpty) {
+ // Wrapped with array_values to make array keys sequential after empty values removing
+ $result = array_values(array_filter($result));
+ }
+ return $result;
+ }
}
diff --git a/framework/messages/el/yii.php b/framework/messages/el/yii.php
index ce53d7b..5edd152 100644
--- a/framework/messages/el/yii.php
+++ b/framework/messages/el/yii.php
@@ -17,49 +17,9 @@
* NOTE: this file must be saved in UTF-8 encoding.
*/
return [
- 'Are you sure you want to delete this item?' => 'Είστε σίγουροι για τη διαγραφή του αντικειμένου;',
- 'Only files with these MIME types are allowed: {mimeTypes}.' => 'Επιτρέπονται μόνο αρχεία με τους ακόλουθους τύπους MIME: {mimeTypes}.',
- 'The requested view "{name}" was not found.' => 'Δε βρέθηκε η αιτούμενη όψη "{name}".',
- 'in {delta, plural, =1{a day} other{# days}}' => 'σε {delta, plural, =1{μία ημέρα} other{# ημέρες}}',
- 'in {delta, plural, =1{a minute} other{# minutes}}' => 'σε {delta, plural, =1{ένα λεπτό} other{# λεπτά}}',
- 'in {delta, plural, =1{a month} other{# months}}' => 'σε {delta, plural, =1{ένα μήνα} other{# μήνες}}',
- 'in {delta, plural, =1{a second} other{# seconds}}' => 'σε {delta, plural, =1{ένα δευτερόλεπτο} other{# δευτερόλεπτα}}',
- 'in {delta, plural, =1{a year} other{# years}}' => 'σε {delta, plural, =1{ένα έτος} other{# έτη}}',
- 'in {delta, plural, =1{an hour} other{# hours}}' => 'σε {delta, plural, =1{μία ώρα} other{# ώρες}}',
- 'just now' => 'μόλις τώρα',
- '{delta, plural, =1{a day} other{# days}} ago' => 'πριν {delta, plural, =1{μία ημέρα} other{# ημέρες}}',
- '{delta, plural, =1{a minute} other{# minutes}} ago' => 'πριν {delta, plural, =1{ένα λεπτό} other{# λεπτά}}',
- '{delta, plural, =1{a month} other{# months}} ago' => 'πριν {delta, plural, =1{ένα μήνα} other{# μήνες}}',
- '{delta, plural, =1{a second} other{# seconds}} ago' => 'πριν {delta, plural, =1{ένα δευτερόλεπτο} other{# δευτερόλεπτα}}',
- '{delta, plural, =1{a year} other{# years}} ago' => 'πριν {delta, plural, =1{ένα έτος} other{# έτη}}',
- '{delta, plural, =1{an hour} other{# hours}} ago' => 'πριν {delta, plural, =1{μία ώρα} other{# ώρες}}',
- '{nFormatted} B' => '{nFormatted} B',
- '{nFormatted} GB' => '{nFormatted} GB',
- '{nFormatted} GiB' => '{nFormatted} GiB',
- '{nFormatted} KB' => '{nFormatted} KB',
- '{nFormatted} KiB' => '{nFormatted} KiB',
- '{nFormatted} MB' => '{nFormatted} MB',
- '{nFormatted} MiB' => '{nFormatted} MiB',
- '{nFormatted} PB' => '{nFormatted} PB',
- '{nFormatted} PiB' => '{nFormatted} PiB',
- '{nFormatted} TB' => '{nFormatted} TB',
- '{nFormatted} TiB' => '{nFormatted} TiB',
- '{nFormatted} {n, plural, =1{byte} other{bytes}}' => '{nFormatted} {n, plural, =1{byte} other{bytes}}',
- '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}' => '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}',
- '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}' => '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}',
- '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}' => '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}',
- '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}' => '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}',
- '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}' => '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}',
- '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}' => '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}',
- '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}' => '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}',
- '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}' => '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}',
- '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}' => '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}',
- '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}' => '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}',
- 'No help for unknown command "{command}".' => 'Δεν υπάρχει βοήθεια για την άγνωστη εντολή "{command}".',
- 'No help for unknown sub-command "{command}".' => 'Δεν υπάρχει βοήθεια για την άγνωστη υπό-εντολή "{command}".',
- 'Unknown command "{command}".' => 'Άγνωστη εντολή "{command}".',
'(not set)' => '(μη ορισμένο)',
'An internal server error occurred.' => 'Υπήρξε ένα εσωτερικό σφάλμα του διακομιστή.',
+ 'Are you sure you want to delete this item?' => 'Είστε σίγουροι για τη διαγραφή του αντικειμένου;',
'Delete' => 'Διαγραφή',
'Error' => 'Σφάλμα',
'File upload failed.' => 'Η μεταφόρτωση απέτυχε.',
@@ -70,6 +30,7 @@ return [
'Missing required parameters: {params}' => 'Απουσιάζουν απαραίτητες παράμετροι: {params}',
'No' => 'Όχι',
'No results found.' => 'Δε βρέθηκαν αποτελέσματα.',
+ 'Only files with these MIME types are allowed: {mimeTypes}.' => 'Επιτρέπονται μόνο αρχεία με τους ακόλουθους τύπους MIME: {mimeTypes}.',
'Only files with these extensions are allowed: {extensions}.' => 'Επιτρέπονται αρχεία μόνο με καταλήξεις: {extensions}.',
'Page not found.' => 'Η σελίδα δε βρέθηκε.',
'Please fix the following errors:' => 'Παρακαλώ διορθώστε τα παρακάτω σφάλματα:',
@@ -83,6 +44,7 @@ return [
'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Η εικόνα «{file}» είναι πολύ μεγάλη. Το πλάτος δεν μπορεί να είναι μεγαλύτερο από {limit, number} {limit, plural, one{pixel} few{pixels} many{pixels} other{pixels}}.',
'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Η εικόνα «{file}» είναι πολύ μικρή. To ύψος δεν μπορεί να είναι μικρότερο από {limit, number} {limit, plural, one{pixel} few{pixels} many{pixels} other{pixels}}.',
'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Η εικόνα «{file}» είναι πολύ μικρή. Το πλάτος του δεν μπορεί να είναι μικρότερο από {limit, number} {limit, plural, one{pixel} few{pixels} many{pixels} other{pixels}}.',
+ 'The requested view "{name}" was not found.' => 'Δε βρέθηκε η αιτούμενη όψη "{name}".',
'The verification code is incorrect.' => 'Ο κωδικός επαλήθευσης είναι εσφαλμένος.',
'Total {count, number} {count, plural, one{item} other{items}}.' => 'Συνολικά {count, number} {count, plural, one{αντικείμενο} few{αντικείμενα} many{αντικείμενα} other{αντικείμενα}}.',
'Unable to verify your data submission.' => 'Δεν ήταν δυνατή η επαλήθευση των απεσταλμένων δεδομένων.',
@@ -92,6 +54,13 @@ return [
'Yes' => 'Ναι',
'You are not allowed to perform this action.' => 'Δεν επιτρέπεται να εκτελέσετε αυτή την ενέργεια.',
'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Μπορείτε να ανεβάσετε το πολύ {limit, number} {limit, plural, one{αρχείο} few{αρχεία} many{αρχεία} other{αρχεία}}.',
+ 'in {delta, plural, =1{a day} other{# days}}' => 'σε {delta, plural, =1{μία ημέρα} other{# ημέρες}}',
+ 'in {delta, plural, =1{a minute} other{# minutes}}' => 'σε {delta, plural, =1{ένα λεπτό} other{# λεπτά}}',
+ 'in {delta, plural, =1{a month} other{# months}}' => 'σε {delta, plural, =1{ένα μήνα} other{# μήνες}}',
+ 'in {delta, plural, =1{a second} other{# seconds}}' => 'σε {delta, plural, =1{ένα δευτερόλεπτο} other{# δευτερόλεπτα}}',
+ 'in {delta, plural, =1{a year} other{# years}}' => 'σε {delta, plural, =1{ένα έτος} other{# έτη}}',
+ 'in {delta, plural, =1{an hour} other{# hours}}' => 'σε {delta, plural, =1{μία ώρα} other{# ώρες}}',
+ 'just now' => 'μόλις τώρα',
'the input value' => 'η τιμή εισόδου',
'{attribute} "{value}" has already been taken.' => 'Το {attribute} «{value}» έχει ήδη καταχωρηθεί.',
'{attribute} cannot be blank.' => 'Το «{attribute}» δεν μπορεί να είναι κενό.',
@@ -114,4 +83,32 @@ return [
'{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => 'Το «{attribute}» πρέπει να περιέχει το λιγότερο {min, number} {min, plural, one{χαρακτήρα} few{χαρακτήρες} many{χαρακτήρες} other{χαρακτήρες}}.',
'{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => 'Το «{attribute}» πρέπει να περιέχει το πολύ {max, number} {max, plural, one{χαρακτήρα} few{χαρακτήρες} many{χαρακτήρες} other{χαρακτήρες}}.',
'{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => 'Το «{attribute}» πρέπει να περιέχει {length, number} {length, plural, one{χαρακτήρα} few{χαρακτήρες} many{χαρακτήρες} other{χαρακτήρες}}.',
+ '{delta, plural, =1{a day} other{# days}} ago' => 'πριν {delta, plural, =1{μία ημέρα} other{# ημέρες}}',
+ '{delta, plural, =1{a minute} other{# minutes}} ago' => 'πριν {delta, plural, =1{ένα λεπτό} other{# λεπτά}}',
+ '{delta, plural, =1{a month} other{# months}} ago' => 'πριν {delta, plural, =1{ένα μήνα} other{# μήνες}}',
+ '{delta, plural, =1{a second} other{# seconds}} ago' => 'πριν {delta, plural, =1{ένα δευτερόλεπτο} other{# δευτερόλεπτα}}',
+ '{delta, plural, =1{a year} other{# years}} ago' => 'πριν {delta, plural, =1{ένα έτος} other{# έτη}}',
+ '{delta, plural, =1{an hour} other{# hours}} ago' => 'πριν {delta, plural, =1{μία ώρα} other{# ώρες}}',
+ '{nFormatted} B' => '{nFormatted} B',
+ '{nFormatted} GB' => '{nFormatted} GB',
+ '{nFormatted} GiB' => '{nFormatted} GiB',
+ '{nFormatted} KB' => '{nFormatted} KB',
+ '{nFormatted} KiB' => '{nFormatted} KiB',
+ '{nFormatted} MB' => '{nFormatted} MB',
+ '{nFormatted} MiB' => '{nFormatted} MiB',
+ '{nFormatted} PB' => '{nFormatted} PB',
+ '{nFormatted} PiB' => '{nFormatted} PiB',
+ '{nFormatted} TB' => '{nFormatted} TB',
+ '{nFormatted} TiB' => '{nFormatted} TiB',
+ '{nFormatted} {n, plural, =1{byte} other{bytes}}' => '{nFormatted} {n, plural, =1{byte} other{bytes}}',
+ '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}' => '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}',
+ '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}' => '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}',
+ '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}' => '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}',
+ '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}' => '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}',
+ '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}' => '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}',
+ '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}' => '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}',
+ '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}' => '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}',
+ '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}' => '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}',
+ '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}' => '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}',
+ '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}' => '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}',
];
diff --git a/framework/web/AssetManager.php b/framework/web/AssetManager.php
index a67ac93..61f417c 100644
--- a/framework/web/AssetManager.php
+++ b/framework/web/AssetManager.php
@@ -64,11 +64,11 @@ class AssetManager extends Component
*/
public $bundles = [];
/**
- * @return string the root directory storing the published asset files.
+ * @var string the root directory storing the published asset files.
*/
public $basePath = '@webroot/assets';
/**
- * @return string the base URL through which the published asset files can be accessed.
+ * @var string the base URL through which the published asset files can be accessed.
*/
public $baseUrl = '@web/assets';
/**
diff --git a/framework/web/Response.php b/framework/web/Response.php
index 675a997..bb5ec8a 100644
--- a/framework/web/Response.php
+++ b/framework/web/Response.php
@@ -934,7 +934,9 @@ class Response extends \yii\base\Response
throw new InvalidConfigException("The '{$this->format}' response formatter is invalid. It must implement the ResponseFormatterInterface.");
}
} elseif ($this->format === self::FORMAT_RAW) {
- $this->content = $this->data;
+ if ($this->data !== null) {
+ $this->content = $this->data;
+ }
} else {
throw new InvalidConfigException("Unsupported response format: {$this->format}");
}
diff --git a/framework/widgets/ActiveField.php b/framework/widgets/ActiveField.php
index 23f06dd..860b655 100644
--- a/framework/widgets/ActiveField.php
+++ b/framework/widgets/ActiveField.php
@@ -550,24 +550,9 @@ class ActiveField extends Component
*
* Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in
* the labels will also be HTML-encoded.
- * @param array $options the tag options in terms of name-value pairs. The following options are specially handled:
- *
- * - prompt: string, a prompt text to be displayed as the first option;
- * - options: array, the attributes for the select option tags. The array keys must be valid option values,
- * and the array values are the extra attributes for the corresponding option tags. For example,
+ * @param array $options the tag options in terms of name-value pairs.
*
- * ~~~
- * [
- * 'value1' => ['disabled' => true],
- * 'value2' => ['label' => 'value 2'],
- * ];
- * ~~~
- *
- * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options',
- * except that the array keys represent the optgroup labels specified in $items.
- *
- * The rest of the options will be rendered as the attributes of the resulting tag. The values will
- * be HTML-encoded using [[Html::encode()]]. If a value is null, the corresponding attribute will not be rendered.
+ * For the list of available options please refer to the `$options` parameter of [[\yii\helpers\Html::activeDropDownList()]].
*
* If you set a custom `id` for the input element, you may need to adjust the [[$selectors]] accordingly.
*
@@ -593,28 +578,9 @@ class ActiveField extends Component
*
* Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in
* the labels will also be HTML-encoded.
- * @param array $options the tag options in terms of name-value pairs. The following options are specially handled:
- *
- * - prompt: string, a prompt text to be displayed as the first option;
- * - options: array, the attributes for the select option tags. The array keys must be valid option values,
- * and the array values are the extra attributes for the corresponding option tags. For example,
- *
- * ~~~
- * [
- * 'value1' => ['disabled' => true],
- * 'value2' => ['label' => 'value 2'],
- * ];
- * ~~~
+ * @param array $options the tag options in terms of name-value pairs.
*
- * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options',
- * except that the array keys represent the optgroup labels specified in $items.
- * - unselect: string, the value that will be submitted when no option is selected.
- * When this attribute is set, a hidden field will be generated so that if no option is selected in multiple
- * mode, we can still obtain the posted unselect value. If you do not want any hidden input,
- * you should explicitly set this option as null.
- *
- * The rest of the options will be rendered as the attributes of the resulting tag. The values will
- * be HTML-encoded using [[Html::encode()]]. If a value is null, the corresponding attribute will not be rendered.
+ * For the list of available options please refer to the `$options` parameter of [[\yii\helpers\Html::activeListBox()]].
*
* If you set a custom `id` for the input element, you may need to adjust the [[$selectors]] accordingly.
*
@@ -636,23 +602,8 @@ class ActiveField extends Component
* The selection of the checkbox list is taken from the value of the model attribute.
* @param array $items the data item used to generate the checkboxes.
* The array values are the labels, while the array keys are the corresponding checkbox values.
- * Note that the labels will NOT be HTML-encoded, while the values will.
- * @param array $options options (name => config) for the checkbox list. The following options are specially handled:
- *
- * - unselect: string, the value that should be submitted when none of the checkboxes is selected.
- * By setting this option, a hidden input will be generated. If you do not want any hidden input,
- * you should explicitly set this option as null.
- * - separator: string, the HTML code that separates items.
- * - item: callable, a callback that can be used to customize the generation of the HTML code
- * corresponding to a single item in $items. The signature of this callback must be:
- *
- * ~~~
- * function ($index, $label, $name, $checked, $value)
- * ~~~
- *
- * where $index is the zero-based index of the checkbox in the whole list; $label
- * is the label for the checkbox; and $name, $value and $checked represent the name,
- * value and the checked status of the checkbox input.
+ * @param array $options options (name => config) for the checkbox list.
+ * For the list of available options please refer to the `$options` parameter of [[\yii\helpers\Html::activeCheckboxList()]].
* @return static the field object itself
*/
public function checkboxList($items, $options = [])
@@ -669,23 +620,8 @@ class ActiveField extends Component
* The selection of the radio buttons is taken from the value of the model attribute.
* @param array $items the data item used to generate the radio buttons.
* The array values are the labels, while the array keys are the corresponding radio values.
- * Note that the labels will NOT be HTML-encoded, while the values will.
- * @param array $options options (name => config) for the radio button list. The following options are specially handled:
- *
- * - unselect: string, the value that should be submitted when none of the radio buttons is selected.
- * By setting this option, a hidden input will be generated. If you do not want any hidden input,
- * you should explicitly set this option as null.
- * - separator: string, the HTML code that separates items.
- * - item: callable, a callback that can be used to customize the generation of the HTML code
- * corresponding to a single item in $items. The signature of this callback must be:
- *
- * ~~~
- * function ($index, $label, $name, $checked, $value)
- * ~~~
- *
- * where $index is the zero-based index of the radio button in the whole list; $label
- * is the label for the radio button; and $name, $value and $checked represent the name,
- * value and the checked status of the radio button input.
+ * @param array $options options (name => config) for the radio button list.
+ * For the list of available options please refer to the `$options` parameter of [[\yii\helpers\Html::activeRadioList()]].
* @return static the field object itself
*/
public function radioList($items, $options = [])
diff --git a/tests/unit/data/postgres.sql b/tests/unit/data/postgres.sql
index 7aa7b9b..b3f19c8 100644
--- a/tests/unit/data/postgres.sql
+++ b/tests/unit/data/postgres.sql
@@ -125,7 +125,7 @@ CREATE TABLE "bool_values" (
);
-CREATE TABLE `animal` (
+CREATE TABLE "animal" (
id serial primary key,
type varchar(255) not null
);
diff --git a/tests/unit/framework/helpers/StringHelperTest.php b/tests/unit/framework/helpers/StringHelperTest.php
index bed726f..b305aaf 100644
--- a/tests/unit/framework/helpers/StringHelperTest.php
+++ b/tests/unit/framework/helpers/StringHelperTest.php
@@ -227,4 +227,13 @@ class StringHelperTest extends TestCase
$this->assertTrue(StringHelper::endsWith('string', 'nG', false));
$this->assertTrue(StringHelper::endsWith('BüЯйΨ', 'ÜяЙΨ', false));
}
+
+ public function testExplode()
+ {
+ $this->assertEquals(['It', 'is', 'a first', 'test'], StringHelper::explode("It, is, a first, test"));
+ $this->assertEquals(['It', 'is', 'a second', 'test'], StringHelper::explode("It+ is+ a second+ test", '+'));
+ $this->assertEquals(['Save', '', '', 'empty trimmed string'], StringHelper::explode("Save, ,, empty trimmed string", ','));
+ $this->assertEquals(['Здесь', 'multibyte', 'строка'], StringHelper::explode("Здесь我 multibyte我 строка", '我'));
+ $this->assertEquals(['Disable', ' trim ', 'here but ignore empty'], StringHelper::explode("Disable, trim ,,,here but ignore empty", ',', false, true));
+ }
}
diff --git a/tests/unit/framework/web/ResponseTest.php b/tests/unit/framework/web/ResponseTest.php
index f0f8bda..8a9c22d 100644
--- a/tests/unit/framework/web/ResponseTest.php
+++ b/tests/unit/framework/web/ResponseTest.php
@@ -82,4 +82,23 @@ class ResponseTest extends \yiiunit\TestCase
{
return '12ёжик3456798áèabcdefghijklmnopqrstuvwxyz!"§$%&/(ёжик)=?';
}
+
+ /**
+ * https://github.com/yiisoft/yii2/issues/7529
+ */
+ public function testSendContentAsFile()
+ {
+ ob_start();
+ $this->response->sendContentAsFile('test', 'test.txt')->send([
+ 'mimeType' => 'text/plain'
+ ]);
+ $content = ob_get_clean();
+
+ static::assertEquals('test', $content);
+ static::assertEquals(200, $this->response->statusCode);
+ $headers = $this->response->headers;
+ static::assertEquals('application/octet-stream', $headers->get('Content-Type'));
+ static::assertEquals('attachment; filename="test.txt"', $headers->get('Content-Disposition'));
+ static::assertEquals(4, $headers->get('Content-Length'));
+ }
}