Browse Source

Merge branch 'sphinx' of github.com:klimov-paul/yii2 into sphinx

tags/2.0.0-beta
Paul Klimov 11 years ago
parent
commit
18af6984fc
  1. 2
      apps/advanced/backend/views/layouts/main.php
  2. 4
      apps/advanced/frontend/config/main.php
  3. 2
      apps/advanced/frontend/views/layouts/main.php
  4. 2
      apps/advanced/requirements.php
  5. 2
      apps/basic/requirements.php
  6. 2
      apps/basic/views/layouts/main.php
  7. 2
      docs/guide/i18n.md
  8. 48
      docs/guide/model.md
  9. 2
      docs/guide/performance.md
  10. 2
      docs/guide/view.md
  11. 2
      extensions/debug/Module.php
  12. 2
      extensions/debug/panels/DbPanel.php
  13. 42
      extensions/sphinx/ActiveQuery.php
  14. 88
      extensions/sphinx/ActiveRecord.php
  15. 2
      extensions/sphinx/ActiveRelation.php
  16. 4
      extensions/sphinx/Connection.php
  17. 4
      extensions/sphinx/Query.php
  18. 18
      extensions/sphinx/README.md
  19. 2
      framework/yii/BaseYii.php
  20. 110
      framework/yii/assets/yii.js
  21. 6
      framework/yii/assets/yii.validation.js
  22. 4
      framework/yii/base/Application.php
  23. 2
      framework/yii/base/Component.php
  24. 4
      framework/yii/base/Widget.php
  25. 6
      framework/yii/caching/ApcCache.php
  26. 85
      framework/yii/caching/RedisCache.php
  27. 2
      framework/yii/console/controllers/HelpController.php
  28. 4
      framework/yii/data/ActiveDataProvider.php
  29. 5
      framework/yii/data/BaseDataProvider.php
  30. 2
      framework/yii/data/Pagination.php
  31. 68
      framework/yii/data/Sort.php
  32. 2
      framework/yii/db/cubrid/Schema.php
  33. 26
      framework/yii/db/pgsql/Schema.php
  34. 30
      framework/yii/helpers/BaseArrayHelper.php
  35. 8
      framework/yii/i18n/I18N.php
  36. 2
      framework/yii/i18n/MissingTranslationEvent.php
  37. 2
      framework/yii/messages/config.php
  38. 3
      framework/yii/requirements/YiiRequirementChecker.php
  39. 2
      framework/yii/requirements/requirements.php
  40. 30
      framework/yii/validators/EmailValidator.php
  41. 2
      framework/yii/web/Controller.php
  42. 8
      framework/yii/web/Request.php
  43. 20
      framework/yii/web/Response.php
  44. 0
      tests/unit/data/i18n/messages/de-DE/test.php
  45. 0
      tests/unit/data/i18n/messages/en-US/test.php
  46. 5
      tests/unit/framework/caching/ApcCacheTest.php
  47. 26
      tests/unit/framework/caching/CacheTestCase.php
  48. 12
      tests/unit/framework/caching/DbCacheTest.php
  49. 12
      tests/unit/framework/caching/FileCacheTest.php
  50. 8
      tests/unit/framework/caching/MemCacheTest.php
  51. 8
      tests/unit/framework/caching/MemCachedTest.php
  52. 11
      tests/unit/framework/caching/RedisCacheTest.php
  53. 42
      tests/unit/framework/data/SortTest.php
  54. 8
      tests/unit/framework/helpers/ArrayHelperTest.php
  55. 8
      tests/unit/framework/i18n/FallbackMessageFormatterTest.php
  56. 2
      tests/unit/framework/i18n/FormatterTest.php
  57. 24
      tests/unit/framework/i18n/I18NTest.php
  58. 14
      tests/unit/framework/i18n/MessageFormatterTest.php
  59. 86
      tests/unit/framework/validators/EmailValidatorTest.php

2
apps/advanced/backend/views/layouts/main.php

@ -13,7 +13,7 @@ AppAsset::register($this);
?>
<?php $this->beginPage(); ?>
<!DOCTYPE html>
<html lang="en">
<html lang="<?= Yii::$app->language ?>">
<head>
<meta charset="<?= Yii::$app->charset ?>"/>
<title><?= Html::encode($this->title) ?></title>

4
apps/advanced/frontend/config/main.php

@ -11,12 +11,12 @@ $params = array_merge(
return [
'id' => 'app-frontend',
'basePath' => dirname(__DIR__),
'vendorPath' => dirname(dirname(__DIR__)) . '/vendor',
'vendorPath' => $rootDir . '/vendor',
'controllerNamespace' => 'frontend\controllers',
'modules' => [
'gii' => 'yii\gii\Module'
],
'extensions' => require(__DIR__ . '/../../vendor/yiisoft/extensions.php'),
'extensions' => require($rootDir . '/vendor/yiisoft/extensions.php'),
'components' => [
'request' => [
'enableCsrfValidation' => true,

2
apps/advanced/frontend/views/layouts/main.php

@ -14,7 +14,7 @@ AppAsset::register($this);
?>
<?php $this->beginPage(); ?>
<!DOCTYPE html>
<html lang="en">
<html lang="<?= Yii::$app->language ?>">
<head>
<meta charset="<?= Yii::$app->charset ?>"/>
<title><?= Html::encode($this->title) ?></title>

2
apps/advanced/requirements.php

@ -59,7 +59,7 @@ $requirements = [
[
'name' => 'APC extension',
'mandatory' => false,
'condition' => extension_loaded('apc') || extension_loaded('apc'),
'condition' => extension_loaded('apc'),
'by' => '<a href="http://www.yiiframework.com/doc/api/CApcCache">CApcCache</a>',
],
// Additional PHP extensions :

2
apps/basic/requirements.php

@ -59,7 +59,7 @@ $requirements = [
[
'name' => 'APC extension',
'mandatory' => false,
'condition' => extension_loaded('apc') || extension_loaded('apc'),
'condition' => extension_loaded('apc'),
'by' => '<a href="http://www.yiiframework.com/doc/api/CApcCache">CApcCache</a>',
],
// Additional PHP extensions :

2
apps/basic/views/layouts/main.php

@ -13,7 +13,7 @@ AppAsset::register($this);
?>
<?php $this->beginPage(); ?>
<!DOCTYPE html>
<html lang="en">
<html lang="<?= Yii::$app->language ?>">
<head>
<meta charset="<?= Yii::$app->charset ?>"/>
<title><?= Html::encode($this->title) ?></title>

2
docs/guide/i18n.md

@ -57,7 +57,7 @@ Yii tries to load approprite translation from one of the message sources defined
'app*' => [
'class' => 'yii\i18n\PhpMessageSource',
//'basePath' => '@app/messages',
//'sourceLanguage' => 'en_US',
//'sourceLanguage' => 'en-US',
'fileMap' => [
'app' => 'app.php',
'app/error' => 'error.php',

48
docs/guide/model.md

@ -8,16 +8,15 @@ In keeping with the MVC approach, a model in Yii is intended for storing or temp
- Massive attribute assignment: the ability to populate multiple model attributes in one step.
- Scenario-based data validation.
Models in Yii extend from the [[\yii\base\Model]] class. Models are typically used to both hold data and define the validation rules for that data. The validation rules greatly simply the generation of models from complex web forms.
The Model class is also the base for more advanced models with additional functionality such as [Active Record](active-record.md).
Models in Yii extend from the [[\yii\base\Model]] class. Models are typically used to both hold data and define the validation rules for that data (aka, the business logic). The business logic greatly simplifies the generation of models from complex web forms by providing validation and error reporting.
The Model class is also the base class for more advanced models with additional functionality, such as [Active Record](active-record.md).
Attributes
----------
Attributes store the actual data represented by a model and can
be accessed like object member variables. For example, a `Post` model
may contain a `title` attribute and a `content` attribute which may be
accessed as follows:
The actual data represented by a model is stored in the model's *attributes*. Model attributes can
be accessed like the member variables of any object. For example, a `Post` model
may contain a `title` attribute and a `content` attribute, accessible as follows:
```php
$post = new Post;
@ -28,7 +27,7 @@ echo $post->content;
```
Since [[\yii\base\Model|Model]] implements the [ArrayAccess](http://php.net/manual/en/class.arrayaccess.php) interface,
you can also access the attributes like accessing array elements:
you can also access the attributes as if they were array elements:
```php
$post = new Post;
@ -51,8 +50,8 @@ class LoginForm extends \yii\base\Model
}
```
Derived model classes may use different ways to declare attributes by overriding the [[\yii\base\Model::attributes()|attributes()]]
method. For example, [[\yii\db\ActiveRecord]] defines attributes as the column names of the database table
Derived model classes may declare attributes in different ways, by overriding the [[\yii\base\Model::attributes()|attributes()]]
method. For example, [[\yii\db\ActiveRecord]] defines attributes using the column names of the database table
that is associated with the class.
@ -60,13 +59,11 @@ Attribute Labels
----------------
Attribute labels are mainly used for display purpose. For example, given an attribute `firstName`, we can declare
a label `First Name` which is more user-friendly and can be displayed to end users in places such as form labels,
a label `First Name` that is more user-friendly when displayed to end users in places such as form labels and
error messages. Given an attribute name, you can obtain its label by calling [[\yii\base\Model::getAttributeLabel()]].
To declare attribute labels, you should override the [[\yii\base\Model::attributeLabels()]] method and return
a mapping from attribute names to attribute labels, like shown in the example below. If an attribute is not found
in this mapping, its label will be generated using the [[\yii\base\Model::generateAttributeLabel()]] method, which
in many cases, will generate reasonable labels (e.g. `username` to `Username`, `orderNumber` to `Order Number`).
To declare attribute labels, override the [[\yii\base\Model::attributeLabels()]] method. The overridden method returns a mapping of attribute names to attribute labels, as shown in the example below. If an attribute is not found
in this mapping, its label will be generated using the [[\yii\base\Model::generateAttributeLabel()]] method. In many cases, [[\yii\base\Model::generateAttributeLabel()]] will generate reasonable labels (e.g. `username` to `Username`, `orderNumber` to `Order Number`).
```php
// LoginForm has two attributes: username and password
@ -88,17 +85,19 @@ class LoginForm extends \yii\base\Model
Scenarios
---------
A model may be used in different scenarios. For example, a `User` model may be used to collect user login inputs,
and it may also be used for user registration purpose. For this reason, each model has a property named `scenario`
which stores the name of the scenario that the model is currently being used in. As we will explain in the next
few sections, the concept of scenario is mainly used for data validation and massive attribute assignment.
A model may be used in different *scenarios*. For example, a `User` model may be used to collect user login inputs,
but it may also be used for user registration purposes. In the one scenario, every piece of data is required; in the other, only the username and password would be.
To easily implement the business logic for different scenarios, each model has a property named `scenario`
that stores the name of the scenario that the model is currently being used in. As will be explained in the next
few sections, the concept of scenarios is mainly used for data validation and massive attribute assignment.
Associated with each scenario is a list of attributes that are *active* in that particular scenario. For example,
in the `login` scenario, only the `username` and `password` attributes are active; while in the `register` scenario,
additional attributes such as `email` are *active*.
Possible scenarios should be listed in the `scenarios()` method which returns an array whose keys are the scenario
names and whose values are the corresponding active attribute lists. Below is an example:
Possible scenarios should be listed in the `scenarios()` method. This method returns an array whose keys are the scenario
names and whose values are lists of attributes that should be active in that scenario:
```php
class User extends \yii\db\ActiveRecord
@ -113,14 +112,14 @@ class User extends \yii\db\ActiveRecord
}
```
Sometimes, we want to mark an attribute as not safe for massive assignment (but we still want it to be validated).
We may do so by prefixing an exclamation character to the attribute name when declaring it in `scenarios()`. For example,
Sometimes, we want to mark an attribute as not safe for massive assignment (but we still want the attribute to be validated).
We may do so by prefixing an exclamation character to the attribute name when declaring it in `scenarios()`. For example:
```php
['username', 'password', '!secret']
```
Active model scenario could be set using one of the following ways:
Identifying the active model scenario can be done using one of the following approaches:
```php
class EmployeeController extends \yii\web\Controller
@ -143,8 +142,7 @@ class EmployeeController extends \yii\web\Controller
}
```
In the example above we are using [Active Record](active-record.md). For basic form models it's rarely needed to
use scenarios since form model is typically used for a single form.
The example above presumes that the model is based upon [Active Record](active-record.md). For basic form models, scenarios are rarely needed, as the basic form model is normally tied directly to a single form.
Validation
----------

2
docs/guide/performance.md

@ -1,7 +1,7 @@
Performance Tuning
==================
Application performance consists of two parts. First is the framework performance
The performance of your web application is based upon two parts. First is the framework performance
and the second is the application itself. Yii has a pretty low performance impact
on your application out of the box and can be fine-tuned further for production
environment. As for the application, we'll provide some of the best practices

2
docs/guide/view.md

@ -256,7 +256,7 @@ use yii\helpers\Html;
?>
<?php $this->beginPage(); ?>
<!DOCTYPE html>
<html lang="<?= Yii::$app->charset ?>">
<html lang="<?= Yii::$app->language ?>">
<head>
<meta charset="<?= Yii::$app->charset ?>"/>
<title><?= Html::encode($this->title) ?></title>

2
extensions/debug/Module.php

@ -85,7 +85,7 @@ class Module extends \yii\base\Module
public function renderToolbar($event)
{
if (!$this->checkAccess()) {
if (!$this->checkAccess() || Yii::$app->getRequest()->getIsAjax()) {
return;
}
$url = Yii::$app->getUrlManager()->createUrl($this->id . '/default/toolbar', [

2
extensions/debug/panels/DbPanel.php

@ -48,7 +48,7 @@ EOD;
public function getDetail()
{
$timings = $this->calculateTimings();
ArrayHelper::multisort($timings, 3, true);
ArrayHelper::multisort($timings, 3, SORT_DESC);
$rows = [];
foreach ($timings as $timing) {
$duration = sprintf('%.1f ms', $timing[3] * 1000);

42
extensions/sphinx/ActiveQuery.php

@ -11,7 +11,47 @@ use yii\db\ActiveQueryInterface;
use yii\db\ActiveQueryTrait;
/**
* Class ActiveQuery
* ActiveQuery represents a Sphinx query associated with an Active Record class.
*
* ActiveQuery instances are usually created by [[ActiveRecord::find()]] and [[ActiveRecord::findBySql()]].
*
* Because ActiveQuery extends from [[Query]], one can use query methods, such as [[where()]],
* [[orderBy()]] to customize the query options.
*
* ActiveQuery also provides the following additional query options:
*
* - [[with()]]: list of relations that this query should be performed with.
* - [[indexBy()]]: the name of the column by which the query result should be indexed.
* - [[asArray()]]: whether to return each record as an array.
*
* These options can be configured using methods of the same name. For example:
*
* ~~~
* $articles = Article::find()->with('source')->asArray()->all();
* ~~~
*
* ActiveQuery allows to build the snippets using sources provided by ActiveRecord.
* You can use [[snippetByModel()]] method to enable this.
* For example:
*
* ~~~
* class Article extends ActiveRecord
* {
* public function getSource()
* {
* return $this->hasOne('db', ArticleDb::className(), ['id' => 'id']);
* }
*
* public function getSnippetSource()
* {
* return $this->source->content;
* }
*
* ...
* }
*
* $articles = Article::find()->with('source')->snippetByModel()->all();
* ~~~
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0

88
extensions/sphinx/ActiveRecord.php

@ -30,6 +30,7 @@ use Yii;
* @property array $populatedRelations An array of relation data indexed by relation names. This property is
* read-only.
* @property integer $primaryKey The primary key value. This property is read-only.
* @property string $snippet current snippet value for this Active Record instance..
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0
@ -103,7 +104,9 @@ class ActiveRecord extends Model
*/
private $_related = [];
/**
* @var string snippet value for this Active Record instance.
* @var string current snippet value for this Active Record instance.
* It will be filled up automatically when instance found using [[Query::snippetCallback]]
* or [[ActiveQuery::snippetByModel()]].
*/
private $_snippet;
@ -307,27 +310,28 @@ class ActiveRecord extends Model
}
/**
* @param string $query snippet source query
* Returns current snippet value or generates new one from given match.
* @param string $match snippet source query
* @param array $options list of options in format: optionName => optionValue
* @return string snippet value
*/
public function getSnippet($query = null, $options = [])
public function getSnippet($match = null, $options = [])
{
if ($query !== null) {
$this->_snippet = $this->fetchSnippet($query, $options);
if ($match !== null) {
$this->_snippet = $this->fetchSnippet($match, $options);
}
return $this->_snippet;
}
/**
* Builds up the snippet value from the given query.
* @param string $query the full-text query to build snippets for.
* @param string $match the full-text query to build snippets for.
* @param array $options list of options in format: optionName => optionValue
* @return string snippet value.
*/
protected function fetchSnippet($query, $options = [])
protected function fetchSnippet($match, $options = [])
{
return static::callSnippets($this->getSnippetSource(), $query, $options);
return static::callSnippets($this->getSnippetSource(), $match, $options);
}
/**
@ -335,12 +339,12 @@ class ActiveRecord extends Model
* Active Record instance.
* Child classes must implement this method to return the actual snippet source text.
* For example:
* ```php
* ~~~
* public function getSnippetSource()
* {
* return $this->snippetSourceRelation->content;
* }
* ```
* ~~~
* @return string snippet source string.
* @throws \yii\base\NotSupportedException if this is not supported by the Active Record class
*/
@ -369,6 +373,9 @@ class ActiveRecord extends Model
* and implement necessary business logic (e.g. merging the changes, prompting stated data)
* to resolve the conflict.
*
* Warning: optimistic lock will NOT work in case of updating fields (not attributes) for the
* runtime indexes!
*
* @return string the column name that stores the lock version of a table row.
* If null is returned (default implemented), optimistic locking will not be supported.
*/
@ -378,10 +385,10 @@ class ActiveRecord extends Model
}
/**
* Declares which DB operations should be performed within a transaction in different scenarios.
* Declares which operations should be performed within a transaction in different scenarios.
* The supported DB operations are: [[OP_INSERT]], [[OP_UPDATE]] and [[OP_DELETE]],
* which correspond to the [[insert()]], [[update()]] and [[delete()]] methods, respectively.
* By default, these methods are NOT enclosed in a DB transaction.
* By default, these methods are NOT enclosed in a transaction.
*
* In some scenarios, to ensure data consistency, you may want to enclose some or all of them
* in transactions. You can do so by overriding this method and returning the operations
@ -768,20 +775,21 @@ class ActiveRecord extends Model
* This method will call [[insert()]] when [[isNewRecord]] is true, or [[update()]]
* when [[isNewRecord]] is false.
*
* For example, to save a customer record:
* For example, to save an article record:
*
* ~~~
* $customer = new Customer; // or $customer = Customer::find($id);
* $customer->name = $name;
* $customer->email = $email;
* $customer = new Article; // or $customer = Article::find(['id' => $id]);
* $customer->id = $id;
* $customer->genre_id = $genreId;
* $customer->content = $email;
* $customer->save();
* ~~~
*
*
* @param boolean $runValidation whether to perform validation before saving the record.
* If the validation fails, the record will not be saved to database.
* If the validation fails, the record will not be saved.
* @param array $attributes list of attributes that need to be saved. Defaults to null,
* meaning all attributes that are loaded from DB will be saved.
* meaning all attributes that are loaded from index will be saved.
* @return boolean whether the saving succeeds
*/
public function save($runValidation = true, $attributes = null)
@ -794,7 +802,7 @@ class ActiveRecord extends Model
}
/**
* Inserts a row into the associated database table using the attribute values of this record.
* Inserts a row into the associated Sphinx index using the attribute values of this record.
*
* This method performs the following steps in order:
*
@ -803,31 +811,29 @@ class ActiveRecord extends Model
* 2. call [[afterValidate()]] when `$runValidation` is true.
* 3. call [[beforeSave()]]. If the method returns false, it will skip the
* rest of the steps;
* 4. insert the record into database. If this fails, it will skip the rest of the steps;
* 4. insert the record into index. If this fails, it will skip the rest of the steps;
* 5. call [[afterSave()]];
*
* In the above step 1, 2, 3 and 5, events [[EVENT_BEFORE_VALIDATE]],
* [[EVENT_BEFORE_INSERT]], [[EVENT_AFTER_INSERT]] and [[EVENT_AFTER_VALIDATE]]
* will be raised by the corresponding methods.
*
* Only the [[changedAttributes|changed attribute values]] will be inserted into database.
*
* If the table's primary key is auto-incremental and is null during insertion,
* it will be populated with the actual value after insertion.
* Only the [[changedAttributes|changed attribute values]] will be inserted.
*
* For example, to insert a customer record:
* For example, to insert an article record:
*
* ~~~
* $customer = new Customer;
* $customer->name = $name;
* $customer->email = $email;
* $customer->insert();
* $article = new Article;
* $article->id = $id;
* $article->genre_id = $genreId;
* $article->content = $content;
* $article->insert();
* ~~~
*
* @param boolean $runValidation whether to perform validation before saving the record.
* If the validation fails, the record will not be inserted into the database.
* If the validation fails, the record will not be inserted.
* @param array $attributes list of attributes that need to be saved. Defaults to null,
* meaning all attributes that are loaded from DB will be saved.
* meaning all attributes that are loaded from index will be saved.
* @return boolean whether the attributes are valid and the record is inserted successfully.
* @throws \Exception in case insert failed.
*/
@ -883,7 +889,7 @@ class ActiveRecord extends Model
}
/**
* Saves the changes to this active record into the associated database table.
* Saves the changes to this active record into the associated Sphinx index.
*
* This method performs the following steps in order:
*
@ -892,7 +898,7 @@ class ActiveRecord extends Model
* 2. call [[afterValidate()]] when `$runValidation` is true.
* 3. call [[beforeSave()]]. If the method returns false, it will skip the
* rest of the steps;
* 4. save the record into database. If this fails, it will skip the rest of the steps;
* 4. save the record into index. If this fails, it will skip the rest of the steps;
* 5. call [[afterSave()]];
*
* In the above step 1, 2, 3 and 5, events [[EVENT_BEFORE_VALIDATE]],
@ -901,13 +907,13 @@ class ActiveRecord extends Model
*
* Only the [[changedAttributes|changed attribute values]] will be saved into database.
*
* For example, to update a customer record:
* For example, to update an article record:
*
* ~~~
* $customer = Customer::find($id);
* $customer->name = $name;
* $customer->email = $email;
* $customer->update();
* $article = Article::find(['id' => $id]);
* $article->genre_id = $genreId;
* $article->group_id = $groupId;
* $article->update();
* ~~~
*
* Note that it is possible the update does not affect any row in the table.
@ -1018,13 +1024,13 @@ class ActiveRecord extends Model
}
/**
* Deletes the table row corresponding to this active record.
* Deletes the index entry corresponding to this active record.
*
* This method performs the following steps in order:
*
* 1. call [[beforeDelete()]]. If the method returns false, it will skip the
* rest of the steps;
* 2. delete the record from the database;
* 2. delete the record from the index;
* 3. call [[afterDelete()]].
*
* In the above step 1 and 3, events named [[EVENT_BEFORE_DELETE]] and [[EVENT_AFTER_DELETE]]
@ -1310,8 +1316,6 @@ class ActiveRecord extends Model
* This method is called by [[create()]].
* You may override this method if the instance being created
* depends on the row data to be populated into the record.
* For example, by creating a record based on the value of a column,
* you may implement the so-called single-table inheritance mapping.
* @param array $row row data to be populated into the record.
* @return ActiveRecord the newly created active record
*/

2
extensions/sphinx/ActiveRelation.php

@ -11,7 +11,7 @@ use yii\db\ActiveRelationInterface;
use yii\db\ActiveRelationTrait;
/**
* Class ActiveRelation
* ActiveRelation represents a relation to Sphinx Active Record class.
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0

4
extensions/sphinx/Connection.php

@ -14,13 +14,13 @@ use yii\base\NotSupportedException;
* Note: although PDO supports numerous database drivers, this class supports only MySQL.
*
* In order to setup Sphinx "searchd" to support MySQL protocol following configuration should be added:
* ```
* ~~~
* searchd
* {
* listen = localhost:9306:mysql41
* ...
* }
* ```
* ~~~
*
* The following example shows how to create a Connection instance and establish
* the Sphinx connection:

4
extensions/sphinx/Query.php

@ -101,7 +101,7 @@ class Query extends Component implements QueryInterface
* Such callback will receive array of query result rows as an argument and must return the
* array of snippet source strings in the order, which match one of incoming rows.
* For example:
* ```php
* ~~~
* $query = new Query;
* $query->from('idx_item')
* ->match('pencil')
@ -113,7 +113,7 @@ class Query extends Component implements QueryInterface
* return $result;
* })
* ->all();
* ```
* ~~~
*/
public $snippetCallback;
/**

18
extensions/sphinx/README.md

@ -50,4 +50,20 @@ searchd
This extension supports all Sphinx features including [Runtime Indexes](http://sphinxsearch.com/docs/current.html#rt-indexes).
Since this extension uses MySQL protocol to access Sphinx, it shares base approach and much code from the
regular "yii\db" package.
regular "yii\db" package.
To use this extension, simply add the following code in your application configuration:
```php
return [
//....
'components' => [
'sphinx' => [
'class' => 'yii\sphinx\Connection',
'dsn' => 'mysql:host=127.0.0.1;port=9306;',
'username' => '',
'password' => '',
],
],
];
```

2
framework/yii/BaseYii.php

@ -498,7 +498,7 @@ class BaseYii
* @param string $category the message category.
* @param string $message the message to be translated.
* @param array $params the parameters that will be used to replace the corresponding placeholders in the message.
* @param string $language the language code (e.g. `en_US`, `en`). If this is null, the current
* @param string $language the language code (e.g. `en-US`, `en`). If this is null, the current
* [[\yii\base\Application::language|application language]] will be used.
* @return string the translated message.
*/

110
framework/yii/assets/yii.js

@ -44,6 +44,11 @@
yii = (function ($) {
var pub = {
/**
* List of scripts that can be loaded multiple times via AJAX requests. Each script can be represented
* as either an absolute URL or a relative one.
*/
reloadableScripts: [],
/**
* The selector for clickable elements that need to support confirmation and form submission.
*/
clickableSelector: 'a, button, input[type="submit"], input[type="button"], input[type="reset"], input[type="image"]',
@ -161,46 +166,77 @@ yii = (function ($) {
},
init: function () {
var $document = $(document);
initCsrfHandler();
initRedirectHandler();
initScriptFilter();
initDataMethods();
}
};
// automatically send CSRF token for all AJAX requests
$.ajaxPrefilter(function (options, originalOptions, xhr) {
if (!options.crossDomain && pub.getCsrfVar()) {
xhr.setRequestHeader('X-CSRF-Token', pub.getCsrfToken());
function initRedirectHandler() {
// handle AJAX redirection
$(document).ajaxComplete(function (event, xhr, settings) {
var url = xhr.getResponseHeader('X-Redirect');
if (url) {
window.location = url;
}
});
}
function initCsrfHandler() {
// automatically send CSRF token for all AJAX requests
$.ajaxPrefilter(function (options, originalOptions, xhr) {
if (!options.crossDomain && pub.getCsrfVar()) {
xhr.setRequestHeader('X-CSRF-Token', pub.getCsrfToken());
}
});
}
function initDataMethods() {
var $document = $(document);
// handle data-confirm and data-method for clickable elements
$document.on('click.yii', pub.clickableSelector, function (event) {
var $this = $(this);
if (pub.allowAction($this)) {
return pub.handleAction($this);
} else {
event.stopImmediatePropagation();
return false;
}
});
// handle data-confirm and data-method for changeable elements
$document.on('change.yii', pub.changeableSelector, function (event) {
var $this = $(this);
if (pub.allowAction($this)) {
return pub.handleAction($this);
} else {
event.stopImmediatePropagation();
return false;
}
});
}
function initScriptFilter() {
var hostInfo = location.protocol + '//' + location.host;
var loadedScripts = $('script[src]').map(function () {
return this.src.charAt(0) === '/' ? hostInfo + this.src : this.src;
}).toArray();
$.ajaxPrefilter('script', function (options, originalOptions, xhr) {
var url = options.url.charAt(0) === '/' ? hostInfo + options.url : options.url;
if ($.inArray(url, loadedScripts) === -1) {
loadedScripts.push(url);
} else {
var found = $.inArray(url, $.map(pub.reloadableScripts, function (script) {
return script.charAt(0) === '/' ? hostInfo + script : script;
})) !== -1;
if (!found) {
xhr.abort();
}
});
}
});
}
// handle AJAX redirection
$document.ajaxComplete(function (event, xhr, settings) {
var url = xhr.getResponseHeader('X-Redirect');
if (url) {
window.location = url;
}
});
// handle data-confirm and data-method for clickable elements
$document.on('click.yii', pub.clickableSelector, function (event) {
var $this = $(this);
if (pub.allowAction($this)) {
return pub.handleAction($this);
} else {
event.stopImmediatePropagation();
return false;
}
});
// handle data-confirm and data-method for changeable elements
$document.on('change.yii', pub.changeableSelector, function (event) {
var $this = $(this);
if (pub.allowAction($this)) {
return pub.handleAction($this);
} else {
event.stopImmediatePropagation();
return false;
}
});
}
};
return pub;
})(jQuery);

6
framework/yii/assets/yii.validation.js

@ -117,16 +117,16 @@ yii.validation = (function ($) {
var valid = true;
if (options.enableIDN) {
var regexp = /^(.*)@(.*)$/,
var regexp = /^(.*<?)(.*)@(.*)(>?)$/,
matches = regexp.exec(value);
if (matches === null) {
valid = false;
} else {
value = punycode.toASCII(matches[1]) + '@' + punycode.toASCII(matches[2]);
value = matches[1] + punycode.toASCII(matches[2]) + '@' + punycode.toASCII(matches[3]) + matches[4];
}
}
if (!valid || !(value.match(options.pattern) && (!options.allowName || value.match(options.fullPattern)))) {
if (!valid || !(value.match(options.pattern) || (options.allowName && value.match(options.fullPattern)))) {
addMessage(messages, options.message, value);
}
},

4
framework/yii/base/Application.php

@ -79,13 +79,13 @@ abstract class Application extends Module
* @var string the language that is meant to be used for end users.
* @see sourceLanguage
*/
public $language = 'en_US';
public $language = 'en-US';
/**
* @var string the language that the application is written in. This mainly refers to
* the language that the messages and view files are written in.
* @see language
*/
public $sourceLanguage = 'en_US';
public $sourceLanguage = 'en-US';
/**
* @var Controller the currently active controller instance
*/

2
framework/yii/base/Component.php

@ -436,7 +436,7 @@ class Component extends Object
* @param string $name the event name
* @param Event $event the event parameter. If not set, a default [[Event]] object will be created.
*/
public function trigger($name, $event = null)
public function trigger($name, Event $event = null)
{
$this->ensureBehaviors();
if (!empty($this->_events[$name])) {

4
framework/yii/base/Widget.php

@ -41,7 +41,7 @@ class Widget extends Component implements ViewContextInterface
* This method creates an instance of the calling class. It will apply the configuration
* to the created instance. A matching [[end()]] call should be called later.
* @param array $config name-value pairs that will be used to initialize the object properties
* @return Widget the newly created widget instance
* @return static the newly created widget instance
*/
public static function begin($config = [])
{
@ -55,7 +55,7 @@ class Widget extends Component implements ViewContextInterface
/**
* Ends a widget.
* Note that the rendering result of the widget is directly echoed out.
* @return Widget the widget instance that is ended.
* @return static the widget instance that is ended.
* @throws InvalidCallException if [[begin()]] and [[end()]] calls are not properly nested
*/
public static function end()

6
framework/yii/caching/ApcCache.php

@ -124,6 +124,10 @@ class ApcCache extends Cache
*/
protected function flushValues()
{
return apc_clear_cache('user');
if (extension_loaded('apcu')) {
return apc_clear_cache();
} else {
return apc_clear_cache('user');
}
}
}

85
framework/yii/caching/RedisCache.php

@ -10,7 +10,7 @@ namespace yii\caching;
use yii\redis\Connection;
/**
* RedisCache implements a cache application component based on [redis](http://redis.io/) version 2.6 or higher.
* RedisCache implements a cache application component based on [redis](http://redis.io/) version 2.6.12 or higher.
*
* RedisCache needs to be configured with [[hostname]], [[port]] and [[database]] of the server
* to connect to. By default RedisCache assumes there is a redis server running on localhost at
@ -119,10 +119,7 @@ class RedisCache extends Cache
}
/**
* Retrieves a value from cache with a specified key.
* This is the implementation of the method declared in the parent class.
* @param string $key a unique key identifying the cached value
* @return string|boolean the value stored in cache, false if the value is not in the cache or expired.
* @inheritDocs
*/
protected function getValue($key)
{
@ -130,9 +127,7 @@ class RedisCache extends Cache
}
/**
* Retrieves multiple values from cache with the specified keys.
* @param array $keys a list of keys identifying the cached values
* @return array a list of cached values indexed by the keys
* @inheritDocs
*/
protected function getValues($keys)
{
@ -146,55 +141,67 @@ class RedisCache extends Cache
}
/**
* Stores a value identified by a key in cache.
* This is the implementation of the method declared in the parent class.
*
* @param string $key the key identifying the value to be cached
* @param string $value the value to be cached
* @param float $expire the number of seconds in which the cached value will expire. 0 means never expire.
* This can be a floating point number to specify the time in milliseconds.
* @return boolean true if the value is successfully stored into cache, false otherwise
* @inheritDocs
*/
protected function setValue($key,$value,$expire)
protected function setValue($key, $value, $expire)
{
if ($expire == 0) {
return (bool) $this->_connection->executeCommand('SET', [$key, $value]);
} else {
$expire = (int) ($expire * 1000);
return (bool) $this->_connection->executeCommand('PSETEX', [$key, $expire, $value]);
return (bool) $this->_connection->executeCommand('SET', [$key, $value, 'PX', $expire]);
}
}
/**
* Stores a value identified by a key into cache if the cache does not contain this key.
* This is the implementation of the method declared in the parent class.
*
* @param string $key the key identifying the value to be cached
* @param string $value the value to be cached
* @param float $expire the number of seconds in which the cached value will expire. 0 means never expire.
* This can be a floating point number to specify the time in milliseconds.
* @return boolean true if the value is successfully stored into cache, false otherwise
* @inheritDocs
*/
protected function addValue($key,$value,$expire)
protected function setValues($data, $expire)
{
$args = [];
foreach($data as $key => $value) {
$args[] = $key;
$args[] = $value;
}
$failedKeys = [];
if ($expire == 0) {
return (bool) $this->_connection->executeCommand('SETNX', [$key, $value]);
$this->_connection->executeCommand('MSET', $args);
} else {
// TODO consider requiring redis version >= 2.6.12 that supports this in one command
$expire = (int) ($expire * 1000);
$this->_connection->executeCommand('MULTI');
$this->_connection->executeCommand('SETNX', [$key, $value]);
$this->_connection->executeCommand('PEXPIRE', [$key, $expire]);
$response = $this->_connection->executeCommand('EXEC');
return (bool) $response[0];
$this->_connection->executeCommand('MSET', $args);
$index = [];
foreach ($data as $key => $value) {
$this->_connection->executeCommand('PEXPIRE', [$key, $expire]);
$index[] = $key;
}
$result = $this->_connection->executeCommand('EXEC');
array_shift($result);
foreach($result as $i => $r) {
if ($r != 1) {
$failedKeys[] = $index[$i];
}
}
}
return $failedKeys;
}
/**
* @inheritDocs
*/
protected function addValue($key, $value, $expire)
{
if ($expire == 0) {
return (bool) $this->_connection->executeCommand('SET', [$key, $value, 'NX']);
} else {
$expire = (int) ($expire * 1000);
return (bool) $this->_connection->executeCommand('SET', [$key, $value, 'PX', $expire, 'NX']);
}
}
/**
* Deletes a value with the specified key from cache
* This is the implementation of the method declared in the parent class.
* @param string $key the key of the value to be deleted
* @return boolean if no error happens during deletion
* @inheritDocs
*/
protected function deleteValue($key)
{
@ -202,9 +209,7 @@ class RedisCache extends Cache
}
/**
* Deletes all values from cache.
* This is the implementation of the method declared in the parent class.
* @return boolean whether the flush operation was successful.
* @inheritDocs
*/
protected function flushValues()
{

2
framework/yii/console/controllers/HelpController.php

@ -124,7 +124,7 @@ class HelpController extends Controller
continue;
}
foreach ($this->getModuleCommands($child) as $command) {
$commands[] = $prefix . $id . '/' . $command;
$commands[] = $command;
}
}

4
framework/yii/data/ActiveDataProvider.php

@ -172,8 +172,8 @@ class ActiveDataProvider extends BaseDataProvider
$model = new $this->query->modelClass;
foreach ($model->attributes() as $attribute) {
$sort->attributes[$attribute] = [
'asc' => [$attribute => Sort::ASC],
'desc' => [$attribute => Sort::DESC],
'asc' => [$attribute => SORT_ASC],
'desc' => [$attribute => SORT_DESC],
'label' => $model->getAttributeLabel($attribute),
];
}

5
framework/yii/data/BaseDataProvider.php

@ -165,10 +165,7 @@ abstract class BaseDataProvider extends Component implements DataProviderInterfa
public function getPagination()
{
if ($this->_pagination === null) {
$this->_pagination = new Pagination;
if ($this->id !== null) {
$this->_pagination->pageVar = $this->id . '-page';
}
$this->setPagination([]);
}
return $this->_pagination;
}

2
framework/yii/data/Pagination.php

@ -182,7 +182,7 @@ class Pagination extends Object
} else {
unset($params[$this->pageVar]);
}
$route = $this->route === null ? Yii::$app->controller->route : $this->route;
$route = $this->route === null ? Yii::$app->controller->getRoute() : $this->route;
return Yii::$app->getUrlManager()->createUrl($route, $params);
}

68
framework/yii/data/Sort.php

@ -29,9 +29,9 @@ use yii\helpers\Inflector;
* 'attributes' => [
* 'age',
* 'name' => [
* 'asc' => ['first_name' => Sort::ASC, 'last_name' => Sort::ASC],
* 'desc' => ['first_name' => Sort::DESC, 'last_name' => Sort::DESC],
* 'default' => Sort::DESC,
* 'asc' => ['first_name' => SORT_ASC, 'last_name' => SORT_ASC],
* 'desc' => ['first_name' => SORT_DESC, 'last_name' => SORT_DESC],
* 'default' => SORT_DESC,
* 'label' => 'Name',
* ],
* ],
@ -66,7 +66,7 @@ use yii\helpers\Inflector;
* that can lead to pages with the data sorted by the corresponding attributes.
*
* @property array $attributeOrders Sort directions indexed by attribute names. Sort direction can be either
* [[Sort::ASC]] for ascending order or [[Sort::DESC]] for descending order. This property is read-only.
* `SORT_ASC` for ascending order or `SORT_DESC` for descending order. This property is read-only.
* @property array $orders The columns (keys) and their corresponding sort directions (values). This can be
* passed to [[\yii\db\Query::orderBy()]] to construct a DB query. This property is read-only.
*
@ -76,16 +76,6 @@ use yii\helpers\Inflector;
class Sort extends Object
{
/**
* Sort ascending
*/
const ASC = false;
/**
* Sort descending
*/
const DESC = true;
/**
* @var boolean whether the sorting can be applied to multiple attributes simultaneously.
* Defaults to false, which means each time the data can only be sorted by one attribute.
*/
@ -99,9 +89,9 @@ class Sort extends Object
* [
* 'age',
* 'name' => [
* 'asc' => ['first_name' => Sort::ASC, 'last_name' => Sort::ASC],
* 'desc' => ['first_name' => Sort::DESC, 'last_name' => Sort::DESC],
* 'default' => Sort::DESC,
* 'asc' => ['first_name' => SORT_ASC, 'last_name' => SORT_ASC],
* 'desc' => ['first_name' => SORT_DESC, 'last_name' => SORT_DESC],
* 'default' => SORT_DESC,
* 'label' => 'Name',
* ],
* ]
@ -112,9 +102,9 @@ class Sort extends Object
*
* ~~~
* 'age' => [
* 'asc' => ['age' => Sort::ASC],
* 'desc' => ['age' => Sort::DESC],
* 'default' => Sort::ASC,
* 'asc' => ['age' => SORT_ASC],
* 'desc' => ['age' => SORT_DESC],
* 'default' => SORT_ASC,
* 'label' => Inflector::camel2words('age'),
* ]
* ~~~
@ -153,8 +143,8 @@ class Sort extends Object
*
* ~~~
* [
* 'name' => Sort::ASC,
* 'create_time' => Sort::DESC,
* 'name' => SORT_ASC,
* 'create_time' => SORT_DESC,
* ]
* ~~~
*
@ -199,13 +189,13 @@ class Sort extends Object
foreach ($this->attributes as $name => $attribute) {
if (!is_array($attribute)) {
$attributes[$attribute] = [
'asc' => [$attribute => self::ASC],
'desc' => [$attribute => self::DESC],
'asc' => [$attribute => SORT_ASC],
'desc' => [$attribute => SORT_DESC],
];
} elseif (!isset($attribute['asc'], $attribute['desc'])) {
$attributes[$name] = array_merge([
'asc' => [$name => self::ASC],
'desc' => [$name => self::DESC],
'asc' => [$name => SORT_ASC],
'desc' => [$name => SORT_DESC],
], $attribute);
} else {
$attributes[$name] = $attribute;
@ -226,7 +216,7 @@ class Sort extends Object
$orders = [];
foreach ($attributeOrders as $attribute => $direction) {
$definition = $this->attributes[$attribute];
$columns = $definition[$direction === self::ASC ? 'asc' : 'desc'];
$columns = $definition[$direction === SORT_ASC ? 'asc' : 'desc'];
foreach ($columns as $name => $dir) {
$orders[$name] = $dir;
}
@ -243,8 +233,8 @@ class Sort extends Object
* Returns the currently requested sort information.
* @param boolean $recalculate whether to recalculate the sort directions
* @return array sort directions indexed by attribute names.
* Sort direction can be either [[Sort::ASC]] for ascending order or
* [[Sort::DESC]] for descending order.
* Sort direction can be either `SORT_ASC` for ascending order or
* `SORT_DESC` for descending order.
*/
public function getAttributeOrders($recalculate = false)
{
@ -262,7 +252,7 @@ class Sort extends Object
}
if (isset($this->attributes[$attribute])) {
$this->_attributeOrders[$attribute] = $descending;
$this->_attributeOrders[$attribute] = $descending ? SORT_DESC : SORT_ASC;
if (!$this->enableMultiSort) {
return $this->_attributeOrders;
}
@ -279,8 +269,8 @@ class Sort extends Object
/**
* Returns the sort direction of the specified attribute in the current request.
* @param string $attribute the attribute name
* @return boolean|null Sort direction of the attribute. Can be either [[Sort::ASC]]
* for ascending order or [[Sort::DESC]] for descending order. Null is returned
* @return boolean|null Sort direction of the attribute. Can be either `SORT_ASC`
* for ascending order or `SORT_DESC` for descending order. Null is returned
* if the attribute is invalid or does not need to be sorted.
*/
public function getAttributeOrder($attribute)
@ -305,7 +295,7 @@ class Sort extends Object
public function link($attribute, $options = [])
{
if (($direction = $this->getAttributeOrder($attribute)) !== null) {
$class = $direction ? 'desc' : 'asc';
$class = $direction === SORT_DESC ? 'desc' : 'asc';
if (isset($options['class'])) {
$options['class'] .= ' ' . $class;
} else {
@ -365,21 +355,21 @@ class Sort extends Object
$definition = $this->attributes[$attribute];
$directions = $this->getAttributeOrders();
if (isset($directions[$attribute])) {
$descending = !$directions[$attribute];
$direction = $directions[$attribute] === SORT_DESC ? SORT_ASC : SORT_DESC;
unset($directions[$attribute]);
} else {
$descending = !empty($definition['default']);
$direction = isset($definition['default']) ? $definition['default'] : SORT_ASC;
}
if ($this->enableMultiSort) {
$directions = array_merge([$attribute => $descending], $directions);
$directions = array_merge([$attribute => $direction], $directions);
} else {
$directions = [$attribute => $descending];
$directions = [$attribute => $direction];
}
$sorts = [];
foreach ($directions as $attribute => $descending) {
$sorts[] = $descending ? $attribute . $this->separators[1] . $this->descTag : $attribute;
foreach ($directions as $attribute => $direction) {
$sorts[] = $direction === SORT_DESC ? $attribute . $this->separators[1] . $this->descTag : $attribute;
}
return implode($this->separators[0], $sorts);
}

2
framework/yii/db/cubrid/Schema.php

@ -180,7 +180,7 @@ class Schema extends \yii\db\Schema
$column->name = $info['Field'];
$column->allowNull = $info['Null'] === 'YES';
$column->isPrimaryKey = strpos($info['Key'], 'PRI') !== false;
$column->isPrimaryKey = false; // primary key will be set by loadTableSchema() later
$column->autoIncrement = stripos($info['Extra'], 'auto_increment') !== false;
$column->dbType = strtolower($info['Type']);

26
framework/yii/db/pgsql/Schema.php

@ -49,6 +49,7 @@ class Schema extends \yii\db\Schema
'inet' => self::TYPE_STRING,
'smallint' => self::TYPE_SMALLINT,
'int4' => self::TYPE_INTEGER,
'int8' => self::TYPE_BIGINT,
'integer' => self::TYPE_INTEGER,
'bigint' => self::TYPE_BIGINT,
'interval' => self::TYPE_STRING,
@ -241,18 +242,17 @@ SQL;
$schemaName = $this->db->quoteValue($table->schemaName);
$sql = <<<SQL
SELECT
current_database() as table_catalog,
d.nspname AS table_schema,
c.relname AS table_name,
a.attname AS column_name,
t.typname AS data_type,
a.attlen AS character_maximum_length,
pg_catalog.col_description(c.oid, a.attnum) AS column_comment,
a.atttypmod AS modifier,
a.attnotnull = false AS is_nullable,
CAST(pg_get_expr(ad.adbin, ad.adrelid) AS varchar) AS column_default,
coalesce(pg_get_expr(ad.adbin, ad.adrelid) ~ 'nextval',false) AS is_autoinc,
array_to_string((select array_agg(enumlabel) from pg_enum where enumtypid=a.atttypid)::varchar[],',') as enum_values,
d.nspname AS table_schema,
c.relname AS table_name,
a.attname AS column_name,
t.typname AS data_type,
a.attlen AS character_maximum_length,
pg_catalog.col_description(c.oid, a.attnum) AS column_comment,
a.atttypmod AS modifier,
a.attnotnull = false AS is_nullable,
CAST(pg_get_expr(ad.adbin, ad.adrelid) AS varchar) AS column_default,
coalesce(pg_get_expr(ad.adbin, ad.adrelid) ~ 'nextval',false) AS is_autoinc,
array_to_string((select array_agg(enumlabel) from pg_enum where enumtypid=a.atttypid)::varchar[],',') as enum_values,
CASE atttypid
WHEN 21 /*int2*/ THEN 16
WHEN 23 /*int4*/ THEN 32
@ -288,7 +288,7 @@ FROM
LEFT JOIN pg_namespace d ON d.oid = c.relnamespace
LEFT join pg_constraint ct on ct.conrelid=c.oid and ct.contype='p'
WHERE
a.attnum > 0
a.attnum > 0 and t.typname != ''
and c.relname = {$tableName}
and d.nspname = {$schemaName}
ORDER BY

30
framework/yii/helpers/BaseArrayHelper.php

@ -333,28 +333,25 @@ class BaseArrayHelper
* elements, a property name of the objects, or an anonymous function returning the values for comparison
* purpose. The anonymous function signature should be: `function($item)`.
* To sort by multiple keys, provide an array of keys here.
* @param boolean|array $descending whether to sort in descending or ascending order. When
* sorting by multiple keys with different descending orders, use an array of descending flags.
* @param integer|array $direction the sorting direction. It can be either `SORT_ASC` or `SORT_DESC`.
* When sorting by multiple keys with different sorting directions, use an array of sorting directions.
* @param integer|array $sortFlag the PHP sort flag. Valid values include
* `SORT_REGULAR`, `SORT_NUMERIC`, `SORT_STRING`, `SORT_LOCALE_STRING`, `SORT_NATURAL` and `SORT_FLAG_CASE`.
* Please refer to [PHP manual](http://php.net/manual/en/function.sort.php)
* for more details. When sorting by multiple keys with different sort flags, use an array of sort flags.
* @param boolean|array $caseSensitive whether to sort string in case-sensitive manner. This parameter
* is used only when `$sortFlag` is `SORT_STRING`.
* When sorting by multiple keys with different case sensitivities, use an array of boolean values.
* @throws InvalidParamException if the $descending or $sortFlag parameters do not have
* correct number of elements as that of $key.
*/
public static function multisort(&$array, $key, $descending = false, $sortFlag = SORT_REGULAR, $caseSensitive = true)
public static function multisort(&$array, $key, $direction = SORT_ASC, $sortFlag = SORT_REGULAR)
{
$keys = is_array($key) ? $key : [$key];
if (empty($keys) || empty($array)) {
return;
}
$n = count($keys);
if (is_scalar($descending)) {
$descending = array_fill(0, $n, $descending);
} elseif (count($descending) !== $n) {
if (is_scalar($direction)) {
$direction = array_fill(0, $n, $direction);
} elseif (count($direction) !== $n) {
throw new InvalidParamException('The length of $descending parameter must be the same as that of $keys.');
}
if (is_scalar($sortFlag)) {
@ -362,22 +359,11 @@ class BaseArrayHelper
} elseif (count($sortFlag) !== $n) {
throw new InvalidParamException('The length of $sortFlag parameter must be the same as that of $keys.');
}
if (is_scalar($caseSensitive)) {
$caseSensitive = array_fill(0, $n, $caseSensitive);
} elseif (count($caseSensitive) !== $n) {
throw new InvalidParamException('The length of $caseSensitive parameter must be the same as that of $keys.');
}
$args = [];
foreach ($keys as $i => $key) {
$flag = $sortFlag[$i];
$cs = $caseSensitive[$i];
if (!$cs && ($flag === SORT_STRING)) {
$flag = $flag | SORT_FLAG_CASE;
$args[] = static::getColumn($array, $key);
} else {
$args[] = static::getColumn($array, $key);
}
$args[] = $descending[$i] ? SORT_DESC : SORT_ASC;
$args[] = static::getColumn($array, $key);
$args[] = $direction[$i];
$args[] = $flag;
}
$args[] = &$array;

8
framework/yii/i18n/I18N.php

@ -53,14 +53,14 @@ class I18N extends Component
if (!isset($this->translations['yii'])) {
$this->translations['yii'] = [
'class' => 'yii\i18n\PhpMessageSource',
'sourceLanguage' => 'en_US',
'sourceLanguage' => 'en-US',
'basePath' => '@yii/messages',
];
}
if (!isset($this->translations['app'])) {
$this->translations['app'] = [
'class' => 'yii\i18n\PhpMessageSource',
'sourceLanguage' => 'en_US',
'sourceLanguage' => 'en-US',
'basePath' => '@app/messages',
];
}
@ -75,7 +75,7 @@ class I18N extends Component
* @param string $category the message category.
* @param string $message the message to be translated.
* @param array $params the parameters that will be used to replace the corresponding placeholders in the message.
* @param string $language the language code (e.g. `en_US`, `en`).
* @param string $language the language code (e.g. `en-US`, `en`).
* @return string the translated and formatted message.
*/
public function translate($category, $message, $params, $language)
@ -89,7 +89,7 @@ class I18N extends Component
*
* @param string $message the message to be formatted.
* @param array $params the parameters that will be used to replace the corresponding placeholders in the message.
* @param string $language the language code (e.g. `en_US`, `en`).
* @param string $language the language code (e.g. `en-US`, `en`).
* @return string the formatted message.
*/
public function format($message, $params, $language)

2
framework/yii/i18n/MissingTranslationEvent.php

@ -27,7 +27,7 @@ class MissingTranslationEvent extends Event
*/
public $category;
/**
* @var string the language ID (e.g. en_US) that the message is to be translated to
* @var string the language ID (e.g. en-US) that the message is to be translated to
*/
public $language;
}

2
framework/yii/messages/config.php

@ -6,7 +6,7 @@ return [
// string, required, root directory containing message translations.
'messagePath' => __DIR__,
// array, required, list of language codes that the extracted messages
// should be translated to. For example, ['zh_cn', 'de'].
// should be translated to. For example, ['zh-CN', 'de'].
'languages' => ['de'],
// string, the name of the function for translating messages.
// Defaults to 'Yii::t'. This is used as a mark to find the messages to be

3
framework/yii/requirements/YiiRequirementChecker.php

@ -178,6 +178,9 @@ class YiiRequirementChecker
if (empty($extensionVersion)) {
return false;
}
if (strncasecmp($extensionVersion, 'PECL-', 5) == 0) {
$extensionVersion = substr($extensionVersion, 5);
}
return version_compare($extensionVersion, $version, $compare);
}

2
framework/yii/requirements/requirements.php

@ -44,7 +44,7 @@ return array(
'condition' => $this->checkPhpExtensionVersion('intl', '1.0.2', '>='),
'by' => '<a href="http://www.php.net/manual/en/book.intl.php">Internationalization</a> support',
'memo' => 'PHP Intl extension 1.0.2 or higher is required when you want to use advanced parameters formatting
in <code>\Yii::t()</code>, <abbr title="Internationalized domain names">IDN</abbr>-feature of
in <code>Yii::t()</code>, <abbr title="Internationalized domain names">IDN</abbr>-feature of
<code>EmailValidator</code> or <code>UrlValidator</code> or the <code>yii\i18n\Formatter</code> class.'
),
);

30
framework/yii/validators/EmailValidator.php

@ -38,16 +38,11 @@ class EmailValidator extends Validator
*/
public $allowName = false;
/**
* @var boolean whether to check the MX record for the email address.
* Defaults to false. To enable it, you need to make sure the PHP function 'checkdnsrr'
* exists in your PHP installation.
* @var boolean whether to check whether the emails domain exists and has either an A or MX record.
* Be aware of the fact that this check can fail due to temporary DNS problems even if the email address is
* valid and an email would be deliverable. Defaults to false.
*/
public $checkMX = false;
/**
* @var boolean whether to check port 25 for the email address.
* Defaults to false.
*/
public $checkPort = false;
public $checkDNS = false;
/**
* @var boolean whether validation process should take into account IDN (internationalized domain
* names). Defaults to false meaning that validation of emails containing IDN will always fail.
@ -93,24 +88,19 @@ class EmailValidator extends Validator
public function validateValue($value)
{
// make sure string length is limited to avoid DOS attacks
if (!is_string($value) || strlen($value) >= 255) {
if (!is_string($value) || strlen($value) >= 320) {
return false;
}
if (($atPosition = strpos($value, '@')) === false) {
if (!preg_match('/^(.*<?)(.*)@(.*)(>?)$/', $value, $matches)) {
return false;
}
$domain = rtrim(substr($value, $atPosition + 1), '>');
$domain = $matches[3];
if ($this->enableIDN) {
$value = idn_to_ascii(ltrim(substr($value, 0, $atPosition), '<')) . '@' . idn_to_ascii($domain);
$value = $matches[1] . idn_to_ascii($matches[2]) . '@' . idn_to_ascii($domain) . $matches[4];
}
$valid = preg_match($this->pattern, $value) || $this->allowName && preg_match($this->fullPattern, $value);
if ($valid) {
if ($this->checkMX && function_exists('checkdnsrr')) {
$valid = checkdnsrr($domain, 'MX');
}
if ($valid && $this->checkPort && function_exists('fsockopen')) {
$valid = fsockopen($domain, 25) !== false;
}
if ($valid && $this->checkDNS) {
$valid = checkdnsrr($domain, 'MX') || checkdnsrr($domain, 'A');
}
return $valid;
}

2
framework/yii/web/Controller.php

@ -92,6 +92,8 @@ class Controller extends \yii\base\Controller
{
if (parent::beforeAction($action)) {
if ($this->enableCsrfValidation && !Yii::$app->getRequest()->validateCsrfToken()) {
// avoid checking again if errorAction is called to display exception
Yii::$app->getRequest()->enableCsrfValidation = false;
throw new HttpException(400, Yii::t('yii', 'Unable to verify your data submission.'));
}
return true;

8
framework/yii/web/Request.php

@ -908,11 +908,11 @@ class Request extends \yii\base\Request
return isset($acceptedLanguages[0]) ? $acceptedLanguages[0] : null;
}
foreach ($acceptedLanguages as $acceptedLanguage) {
$acceptedLanguage = str_replace('-', '_', strtolower($acceptedLanguage));
$acceptedLanguage = str_replace('_', '-', strtolower($acceptedLanguage));
foreach ($languages as $language) {
$language = str_replace('-', '_', strtolower($language));
// en_us==en_us, en==en_us, en_us==en
if ($language === $acceptedLanguage || strpos($acceptedLanguage, $language . '_') === 0 || strpos($language, $acceptedLanguage . '_') === 0) {
$language = str_replace('_', '-', strtolower($language));
// en-us==en-us, en==en-us, en-us==en
if ($language === $acceptedLanguage || strpos($acceptedLanguage, $language . '-') === 0 || strpos($language, $acceptedLanguage . '-') === 0) {
return $language;
}
}

20
framework/yii/web/Response.php

@ -126,9 +126,10 @@ class Response extends \yii\base\Response
*/
public $charset;
/**
* @var string
* @var string the HTTP status description that comes together with the status code.
* @see [[httpStatuses]]
*/
public $statusText;
public $statusText = 'OK';
/**
* @var string the version of the HTTP protocol to use. If not set, it will be determined via `$_SERVER['SERVER_PROTOCOL']`,
* or '1.1' if that is not available.
@ -208,7 +209,7 @@ class Response extends \yii\base\Response
/**
* @var integer the HTTP status code to send with the response.
*/
private $_statusCode;
private $_statusCode = 200;
/**
* @var HeaderCollection
*/
@ -248,11 +249,6 @@ class Response extends \yii\base\Response
*/
public function setStatusCode($value, $text = null)
{
if ($value === null) {
$this->_statusCode = null;
$this->statusText = null;
return;
}
$this->_statusCode = (int)$value;
if ($this->getIsInvalid()) {
throw new InvalidParamException("The HTTP status code is invalid: $value");
@ -297,10 +293,10 @@ class Response extends \yii\base\Response
{
$this->_headers = null;
$this->_cookies = null;
$this->_statusCode = null;
$this->_statusCode = 200;
$this->statusText = 'OK';
$this->data = null;
$this->content = null;
$this->statusText = null;
}
/**
@ -312,9 +308,7 @@ class Response extends \yii\base\Response
return;
}
$statusCode = $this->getStatusCode();
if ($statusCode !== null) {
header("HTTP/{$this->version} $statusCode {$this->statusText}");
}
header("HTTP/{$this->version} $statusCode {$this->statusText}");
if ($this->_headers) {
$headers = $this->getHeaders();
foreach ($headers as $name => $values) {

0
tests/unit/data/i18n/messages/de_DE/test.php → tests/unit/data/i18n/messages/de-DE/test.php

0
tests/unit/data/i18n/messages/en_US/test.php → tests/unit/data/i18n/messages/en-US/test.php

5
tests/unit/framework/caching/ApcCacheTest.php

@ -37,4 +37,9 @@ class ApcCacheTest extends CacheTestCase
{
$this->markTestSkipped("APC keys are expiring only on the next request.");
}
public function testExpireAdd()
{
$this->markTestSkipped("APC keys are expiring only on the next request.");
}
}

26
tests/unit/framework/caching/CacheTestCase.php

@ -91,7 +91,18 @@ abstract class CacheTestCase extends TestCase
$this->assertEquals('array_test', $array['array_test']);
}
public function testMset()
/**
* @return array testing mset with and without expiry
*/
public function msetExpiry()
{
return [[0], [2]];
}
/**
* @dataProvider msetExpiry
*/
public function testMset($expiry)
{
$cache = $this->getCacheInstance();
$cache->flush();
@ -100,7 +111,7 @@ abstract class CacheTestCase extends TestCase
'string_test' => 'string_test',
'number_test' => 42,
'array_test' => ['array_test' => 'array_test'],
]);
], $expiry);
$this->assertEquals('string_test', $cache->get('string_test'));
@ -170,6 +181,17 @@ abstract class CacheTestCase extends TestCase
$this->assertFalse($cache->get('expire_test'));
}
public function testExpireAdd()
{
$cache = $this->getCacheInstance();
$this->assertTrue($cache->add('expire_testa', 'expire_testa', 2));
usleep(500000);
$this->assertEquals('expire_testa', $cache->get('expire_testa'));
usleep(2500000);
$this->assertFalse($cache->get('expire_testa'));
}
public function testAdd()
{
$cache = $this->prepare();

12
tests/unit/framework/caching/DbCacheTest.php

@ -83,4 +83,16 @@ class DbCacheTest extends CacheTestCase
static::$time++;
$this->assertFalse($cache->get('expire_test'));
}
public function testExpireAdd()
{
$cache = $this->getCacheInstance();
static::$time = \time();
$this->assertTrue($cache->add('expire_testa', 'expire_testa', 2));
static::$time++;
$this->assertEquals('expire_testa', $cache->get('expire_testa'));
static::$time++;
$this->assertFalse($cache->get('expire_testa'));
}
}

12
tests/unit/framework/caching/FileCacheTest.php

@ -33,4 +33,16 @@ class FileCacheTest extends CacheTestCase
static::$time++;
$this->assertFalse($cache->get('expire_test'));
}
public function testExpireAdd()
{
$cache = $this->getCacheInstance();
static::$time = \time();
$this->assertTrue($cache->add('expire_testa', 'expire_testa', 2));
static::$time++;
$this->assertEquals('expire_testa', $cache->get('expire_testa'));
static::$time++;
$this->assertFalse($cache->get('expire_testa'));
}
}

8
tests/unit/framework/caching/MemCacheTest.php

@ -34,4 +34,12 @@ class MemCacheTest extends CacheTestCase
}
parent::testExpire();
}
public function testExpireAdd()
{
if (getenv('TRAVIS') == 'true') {
$this->markTestSkipped('Can not reliably test memcache expiry on travis-ci.');
}
parent::testExpireAdd();
}
}

8
tests/unit/framework/caching/MemCachedTest.php

@ -34,4 +34,12 @@ class MemCachedTest extends CacheTestCase
}
parent::testExpire();
}
public function testExpireAdd()
{
if (getenv('TRAVIS') == 'true') {
$this->markTestSkipped('Can not reliably test memcached expiry on travis-ci.');
}
parent::testExpireAdd();
}
}

11
tests/unit/framework/caching/RedisCacheTest.php

@ -45,6 +45,17 @@ class RedisCacheTest extends CacheTestCase
$this->assertFalse($cache->get('expire_test_ms'));
}
public function testExpireAddMilliseconds()
{
$cache = $this->getCacheInstance();
$this->assertTrue($cache->add('expire_testa_ms', 'expire_testa_ms', 0.2));
usleep(100000);
$this->assertEquals('expire_testa_ms', $cache->get('expire_testa_ms'));
usleep(300000);
$this->assertFalse($cache->get('expire_testa_ms'));
}
/**
* Store a value that is 2 times buffer size big
* https://github.com/yiisoft/yii2/issues/743

42
tests/unit/framework/data/SortTest.php

@ -25,8 +25,8 @@ class SortTest extends TestCase
'attributes' => [
'age',
'name' => [
'asc' => ['first_name' => Sort::ASC, 'last_name' => Sort::ASC],
'desc' => ['first_name' => Sort::DESC, 'last_name' => Sort::DESC],
'asc' => ['first_name' => SORT_ASC, 'last_name' => SORT_ASC],
'desc' => ['first_name' => SORT_DESC, 'last_name' => SORT_DESC],
],
],
'params' => [
@ -37,14 +37,14 @@ class SortTest extends TestCase
$orders = $sort->getOrders();
$this->assertEquals(3, count($orders));
$this->assertEquals(Sort::ASC, $orders['age']);
$this->assertEquals(Sort::DESC, $orders['first_name']);
$this->assertEquals(Sort::DESC, $orders['last_name']);
$this->assertEquals(SORT_ASC, $orders['age']);
$this->assertEquals(SORT_DESC, $orders['first_name']);
$this->assertEquals(SORT_DESC, $orders['last_name']);
$sort->enableMultiSort = false;
$orders = $sort->getOrders(true);
$this->assertEquals(1, count($orders));
$this->assertEquals(Sort::ASC, $orders['age']);
$this->assertEquals(SORT_ASC, $orders['age']);
}
public function testGetAttributeOrders()
@ -53,8 +53,8 @@ class SortTest extends TestCase
'attributes' => [
'age',
'name' => [
'asc' => ['first_name' => Sort::ASC, 'last_name' => Sort::ASC],
'desc' => ['first_name' => Sort::DESC, 'last_name' => Sort::DESC],
'asc' => ['first_name' => SORT_ASC, 'last_name' => SORT_ASC],
'desc' => ['first_name' => SORT_DESC, 'last_name' => SORT_DESC],
],
],
'params' => [
@ -65,13 +65,13 @@ class SortTest extends TestCase
$orders = $sort->getAttributeOrders();
$this->assertEquals(2, count($orders));
$this->assertEquals(Sort::ASC, $orders['age']);
$this->assertEquals(Sort::DESC, $orders['name']);
$this->assertEquals(SORT_ASC, $orders['age']);
$this->assertEquals(SORT_DESC, $orders['name']);
$sort->enableMultiSort = false;
$orders = $sort->getAttributeOrders(true);
$this->assertEquals(1, count($orders));
$this->assertEquals(Sort::ASC, $orders['age']);
$this->assertEquals(SORT_ASC, $orders['age']);
}
public function testGetAttributeOrder()
@ -80,8 +80,8 @@ class SortTest extends TestCase
'attributes' => [
'age',
'name' => [
'asc' => ['first_name' => Sort::ASC, 'last_name' => Sort::ASC],
'desc' => ['first_name' => Sort::DESC, 'last_name' => Sort::DESC],
'asc' => ['first_name' => SORT_ASC, 'last_name' => SORT_ASC],
'desc' => ['first_name' => SORT_DESC, 'last_name' => SORT_DESC],
],
],
'params' => [
@ -90,8 +90,8 @@ class SortTest extends TestCase
'enableMultiSort' => true,
]);
$this->assertEquals(Sort::ASC, $sort->getAttributeOrder('age'));
$this->assertEquals(Sort::DESC, $sort->getAttributeOrder('name'));
$this->assertEquals(SORT_ASC, $sort->getAttributeOrder('age'));
$this->assertEquals(SORT_DESC, $sort->getAttributeOrder('name'));
$this->assertNull($sort->getAttributeOrder('xyz'));
}
@ -101,8 +101,8 @@ class SortTest extends TestCase
'attributes' => [
'age',
'name' => [
'asc' => ['first_name' => Sort::ASC, 'last_name' => Sort::ASC],
'desc' => ['first_name' => Sort::DESC, 'last_name' => Sort::DESC],
'asc' => ['first_name' => SORT_ASC, 'last_name' => SORT_ASC],
'desc' => ['first_name' => SORT_DESC, 'last_name' => SORT_DESC],
],
],
'params' => [
@ -127,8 +127,8 @@ class SortTest extends TestCase
'attributes' => [
'age',
'name' => [
'asc' => ['first_name' => Sort::ASC, 'last_name' => Sort::ASC],
'desc' => ['first_name' => Sort::DESC, 'last_name' => Sort::DESC],
'asc' => ['first_name' => SORT_ASC, 'last_name' => SORT_ASC],
'desc' => ['first_name' => SORT_DESC, 'last_name' => SORT_DESC],
],
],
'params' => [
@ -155,8 +155,8 @@ class SortTest extends TestCase
'attributes' => [
'age',
'name' => [
'asc' => ['first_name' => Sort::ASC, 'last_name' => Sort::ASC],
'desc' => ['first_name' => Sort::DESC, 'last_name' => Sort::DESC],
'asc' => ['first_name' => SORT_ASC, 'last_name' => SORT_ASC],
'desc' => ['first_name' => SORT_DESC, 'last_name' => SORT_DESC],
],
],
'params' => [

8
tests/unit/framework/helpers/ArrayHelperTest.php

@ -129,13 +129,13 @@ class ArrayHelperTest extends TestCase
['name' => 'A', 'age' => 1],
];
ArrayHelper::multisort($array, ['name', 'age'], false, [SORT_STRING, SORT_REGULAR]);
ArrayHelper::multisort($array, ['name', 'age'], SORT_ASC, [SORT_STRING, SORT_REGULAR]);
$this->assertEquals(['name' => 'A', 'age' => 1], $array[0]);
$this->assertEquals(['name' => 'B', 'age' => 4], $array[1]);
$this->assertEquals(['name' => 'a', 'age' => 3], $array[2]);
$this->assertEquals(['name' => 'b', 'age' => 2], $array[3]);
ArrayHelper::multisort($array, ['name', 'age'], false, [SORT_STRING, SORT_REGULAR], false);
ArrayHelper::multisort($array, ['name', 'age'], SORT_ASC, [SORT_STRING | SORT_FLAG_CASE, SORT_REGULAR]);
$this->assertEquals(['name' => 'A', 'age' => 1], $array[0]);
$this->assertEquals(['name' => 'a', 'age' => 3], $array[1]);
$this->assertEquals(['name' => 'b', 'age' => 2], $array[2]);
@ -147,7 +147,7 @@ class ArrayHelperTest extends TestCase
// single key
$sort = new Sort([
'attributes' => ['name', 'age'],
'defaultOrder' => ['name' => Sort::ASC],
'defaultOrder' => ['name' => SORT_ASC],
]);
$orders = $sort->getOrders();
@ -164,7 +164,7 @@ class ArrayHelperTest extends TestCase
// multiple keys
$sort = new Sort([
'attributes' => ['name', 'age'],
'defaultOrder' => ['name' => Sort::ASC, 'age' => Sort::DESC],
'defaultOrder' => ['name' => SORT_ASC, 'age' => SORT_DESC],
]);
$orders = $sort->getOrders();

8
tests/unit/framework/i18n/FallbackMessageFormatterTest.php

@ -136,7 +136,7 @@ _MSG_
public function testNamedArguments($pattern, $expected, $args)
{
$formatter = new FallbackMessageFormatter();
$result = $formatter->fallbackFormat($pattern, $args, 'en_US');
$result = $formatter->fallbackFormat($pattern, $args, 'en-US');
$this->assertEquals($expected, $result, $formatter->getErrorMessage());
}
@ -147,7 +147,7 @@ _MSG_
$formatter = new FallbackMessageFormatter();
$result = $formatter->fallbackFormat('{'.self::SUBJECT.'} is {'.self::N.'}', [
self::N => self::N_VALUE,
], 'en_US');
], 'en-US');
$this->assertEquals($expected, $result);
}
@ -157,7 +157,7 @@ _MSG_
$pattern = '{'.self::SUBJECT.'} is '.self::N;
$formatter = new FallbackMessageFormatter();
$result = $formatter->fallbackFormat($pattern, [], 'en_US');
$result = $formatter->fallbackFormat($pattern, [], 'en-US');
$this->assertEquals($pattern, $result, $formatter->getErrorMessage());
}
}
@ -168,4 +168,4 @@ class FallbackMessageFormatter extends MessageFormatter
{
return parent::fallbackFormat($pattern, $args, $locale);
}
}
}

2
tests/unit/framework/i18n/FormatterTest.php

@ -29,7 +29,7 @@ class FormatterTest extends TestCase
$this->markTestSkipped('intl extension is required.');
}
$this->mockApplication();
$this->formatter = new Formatter(['locale' => 'en_US']);
$this->formatter = new Formatter(['locale' => 'en-US']);
}
protected function tearDown()

24
tests/unit/framework/i18n/I18NTest.php

@ -40,16 +40,16 @@ class I18NTest extends TestCase
public function testTranslate()
{
$msg = 'The dog runs fast.';
$this->assertEquals('The dog runs fast.', $this->i18n->translate('test', $msg, [], 'en_US'));
$this->assertEquals('Der Hund rennt schnell.', $this->i18n->translate('test', $msg, [], 'de_DE'));
$this->assertEquals('The dog runs fast.', $this->i18n->translate('test', $msg, [], 'en-US'));
$this->assertEquals('Der Hund rennt schnell.', $this->i18n->translate('test', $msg, [], 'de-DE'));
}
public function testTranslateParams()
{
$msg = 'His speed is about {n} km/h.';
$params = ['n' => 42];
$this->assertEquals('His speed is about 42 km/h.', $this->i18n->translate('test', $msg, $params, 'en_US'));
$this->assertEquals('Seine Geschwindigkeit beträgt 42 km/h.', $this->i18n->translate('test', $msg, $params, 'de_DE'));
$this->assertEquals('His speed is about 42 km/h.', $this->i18n->translate('test', $msg, $params, 'en-US'));
$this->assertEquals('Seine Geschwindigkeit beträgt 42 km/h.', $this->i18n->translate('test', $msg, $params, 'de-DE'));
}
public function testTranslateParams2()
@ -62,22 +62,22 @@ class I18NTest extends TestCase
'n' => 42,
'name' => 'DA VINCI', // http://petrix.com/dognames/d.html
];
$this->assertEquals('His name is DA VINCI and his speed is about 42 km/h.', $this->i18n->translate('test', $msg, $params, 'en_US'));
$this->assertEquals('Er heißt DA VINCI und ist 42 km/h schnell.', $this->i18n->translate('test', $msg, $params, 'de_DE'));
$this->assertEquals('His name is DA VINCI and his speed is about 42 km/h.', $this->i18n->translate('test', $msg, $params, 'en-US'));
$this->assertEquals('Er heißt DA VINCI und ist 42 km/h schnell.', $this->i18n->translate('test', $msg, $params, 'de-DE'));
}
public function testSpecialParams()
{
$msg = 'His speed is about {0} km/h.';
$this->assertEquals('His speed is about 0 km/h.', $this->i18n->translate('test', $msg, 0, 'en_US'));
$this->assertEquals('His speed is about 42 km/h.', $this->i18n->translate('test', $msg, 42, 'en_US'));
$this->assertEquals('His speed is about {0} km/h.', $this->i18n->translate('test', $msg, null, 'en_US'));
$this->assertEquals('His speed is about {0} km/h.', $this->i18n->translate('test', $msg, [], 'en_US'));
$this->assertEquals('His speed is about 0 km/h.', $this->i18n->translate('test', $msg, 0, 'en-US'));
$this->assertEquals('His speed is about 42 km/h.', $this->i18n->translate('test', $msg, 42, 'en-US'));
$this->assertEquals('His speed is about {0} km/h.', $this->i18n->translate('test', $msg, null, 'en-US'));
$this->assertEquals('His speed is about {0} km/h.', $this->i18n->translate('test', $msg, [], 'en-US'));
$msg = 'His name is {name} and he is {age} years old.';
$model = new ParamModel();
$this->assertEquals('His name is peer and he is 5 years old.', $this->i18n->translate('test', $msg, $model, 'en_US'));
$this->assertEquals('His name is peer and he is 5 years old.', $this->i18n->translate('test', $msg, $model, 'en-US'));
}
}
@ -85,4 +85,4 @@ class ParamModel extends Model
{
public $name = 'peer';
public $age = 5;
}
}

14
tests/unit/framework/i18n/MessageFormatterTest.php

@ -250,7 +250,7 @@ _MSG_
1 => 123,
2 => 37.073
],
'en_US'
'en-US'
],
[
@ -272,7 +272,7 @@ _MSG_
'trees' => 123,
'monkeysPerTree' => 37.073
],
'en_US'
'en-US'
],
[
@ -297,14 +297,14 @@ _MSG_
$this->markTestSkipped($skipMessage);
}
$formatter = new MessageFormatter();
$result = $formatter->format($pattern, $args, 'en_US');
$result = $formatter->format($pattern, $args, 'en-US');
$this->assertEquals($expected, $result, $formatter->getErrorMessage());
}
/**
* @dataProvider parsePatterns
*/
public function testParseNamedArguments($pattern, $expected, $args, $locale = 'en_US')
public function testParseNamedArguments($pattern, $expected, $args, $locale = 'en-US')
{
if (!extension_loaded("intl")) {
$this->markTestSkipped("intl not installed. Skipping.");
@ -322,7 +322,7 @@ _MSG_
$formatter = new MessageFormatter();
$result = $formatter->format('{'.self::SUBJECT.'} is {'.self::N.', number}', [
self::N => self::N_VALUE,
], 'en_US');
], 'en-US');
$this->assertEquals($expected, $result, $formatter->getErrorMessage());
}
@ -331,7 +331,7 @@ _MSG_
{
$pattern = '{'.self::SUBJECT.'} is '.self::N;
$formatter = new MessageFormatter();
$result = $formatter->format($pattern, [], 'en_US');
$result = $formatter->format($pattern, [], 'en-US');
$this->assertEquals($pattern, $result, $formatter->getErrorMessage());
}
}
}

86
tests/unit/framework/validators/EmailValidatorTest.php

@ -24,15 +24,77 @@ class EmailValidatorTest extends TestCase
$this->assertTrue($validator->validateValue('sam@rmcreative.ru'));
$this->assertTrue($validator->validateValue('5011@gmail.com'));
$this->assertFalse($validator->validateValue('rmcreative.ru'));
$this->assertFalse($validator->validateValue('Carsten Brandt <mail@cebe.cc>'));
$this->assertFalse($validator->validateValue('"Carsten Brandt" <mail@cebe.cc>'));
$this->assertFalse($validator->validateValue('<mail@cebe.cc>'));
$this->assertFalse($validator->validateValue('info@örtliches.de'));
$this->assertFalse($validator->validateValue('sam@рмкреатиф.ru'));
$validator->allowName = true;
$this->assertTrue($validator->validateValue('sam@rmcreative.ru'));
$this->assertTrue($validator->validateValue('5011@gmail.com'));
$this->assertFalse($validator->validateValue('rmcreative.ru'));
$this->assertTrue($validator->validateValue('Carsten Brandt <mail@cebe.cc>'));
$this->assertTrue($validator->validateValue('"Carsten Brandt" <mail@cebe.cc>'));
$this->assertTrue($validator->validateValue('<mail@cebe.cc>'));
$this->assertFalse($validator->validateValue('info@örtliches.de'));
$this->assertFalse($validator->validateValue('sam@рмкреатиф.ru'));
$this->assertFalse($validator->validateValue('Informtation info@oertliches.de'));
$this->assertTrue($validator->validateValue('test@example.com'));
$this->assertTrue($validator->validateValue('John Smith <john.smith@example.com>'));
$this->assertFalse($validator->validateValue('John Smith <example.com>'));
}
public function testValidateValueIdn()
{
if (!function_exists('idn_to_ascii')) {
$this->markTestSkipped('Intl extension required');
return;
}
$validator = new EmailValidator();
$validator->enableIDN = true;
$this->assertTrue($validator->validateValue('5011@example.com'));
$this->assertTrue($validator->validateValue('example@äüößìà.de'));
$this->assertTrue($validator->validateValue('example@xn--zcack7ayc9a.de'));
$this->assertTrue($validator->validateValue('info@örtliches.de'));
$this->assertTrue($validator->validateValue('sam@рмкреатиф.ru'));
$this->assertTrue($validator->validateValue('sam@rmcreative.ru'));
$this->assertTrue($validator->validateValue('5011@gmail.com'));
$this->assertFalse($validator->validateValue('rmcreative.ru'));
$this->assertFalse($validator->validateValue('Carsten Brandt <mail@cebe.cc>'));
$this->assertFalse($validator->validateValue('"Carsten Brandt" <mail@cebe.cc>'));
$this->assertFalse($validator->validateValue('<mail@cebe.cc>'));
$validator->allowName = true;
$this->assertTrue($validator->validateValue('info@örtliches.de'));
$this->assertTrue($validator->validateValue('Informtation <info@örtliches.de>'));
$this->assertFalse($validator->validateValue('Informtation info@örtliches.de'));
$this->assertTrue($validator->validateValue('sam@рмкреатиф.ru'));
$this->assertTrue($validator->validateValue('sam@rmcreative.ru'));
$this->assertTrue($validator->validateValue('5011@gmail.com'));
$this->assertFalse($validator->validateValue('rmcreative.ru'));
$this->assertTrue($validator->validateValue('Carsten Brandt <mail@cebe.cc>'));
$this->assertTrue($validator->validateValue('"Carsten Brandt" <mail@cebe.cc>'));
$this->assertTrue($validator->validateValue('<mail@cebe.cc>'));
$this->assertTrue($validator->validateValue('test@example.com'));
$this->assertTrue($validator->validateValue('John Smith <john.smith@example.com>'));
$this->assertFalse($validator->validateValue('John Smith <example.com>'));
}
public function testValidateValueMx()
{
$validator = new EmailValidator();
$validator->checkMX = true;
$validator->checkDNS = true;
$this->assertTrue($validator->validateValue('5011@gmail.com'));
$this->assertFalse($validator->validateValue('test@example.com'));
$validator->checkDNS = false;
$this->assertTrue($validator->validateValue('test@nonexistingsubdomain.example.com'));
$validator->checkDNS = true;
$this->assertFalse($validator->validateValue('test@nonexistingsubdomain.example.com'));
}
public function testValidateAttribute()
@ -43,24 +105,4 @@ class EmailValidatorTest extends TestCase
$val->validateAttribute($model, 'attr_email');
$this->assertFalse($model->hasErrors('attr_email'));
}
public function testValidateValueIdn()
{
if (!function_exists('idn_to_ascii')) {
$this->markTestSkipped('Intl extension required');
return;
}
$val = new EmailValidator(['enableIDN' => true]);
$this->assertTrue($val->validateValue('5011@example.com'));
$this->assertTrue($val->validateValue('example@äüößìà.de'));
$this->assertTrue($val->validateValue('example@xn--zcack7ayc9a.de'));
}
public function testValidateValueWithName()
{
$val = new EmailValidator(['allowName' => true]);
$this->assertTrue($val->validateValue('test@example.com'));
$this->assertTrue($val->validateValue('John Smith <john.smith@example.com>'));
$this->assertFalse($val->validateValue('John Smith <example.com>'));
}
}

Loading…
Cancel
Save