Browse Source

Merge branch 'master' of github.com:yiisoft/yii2 into sphinx

tags/2.0.0-alpha
Klimov Paul 11 years ago
parent
commit
7fa572460e
  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. 2
      framework/yii/BaseYii.php
  14. 110
      framework/yii/assets/yii.js
  15. 6
      framework/yii/assets/yii.validation.js
  16. 4
      framework/yii/base/Application.php
  17. 2
      framework/yii/base/Component.php
  18. 4
      framework/yii/base/Widget.php
  19. 6
      framework/yii/caching/ApcCache.php
  20. 85
      framework/yii/caching/RedisCache.php
  21. 2
      framework/yii/console/controllers/HelpController.php
  22. 4
      framework/yii/data/ActiveDataProvider.php
  23. 5
      framework/yii/data/BaseDataProvider.php
  24. 2
      framework/yii/data/Pagination.php
  25. 68
      framework/yii/data/Sort.php
  26. 2
      framework/yii/db/cubrid/Schema.php
  27. 26
      framework/yii/db/pgsql/Schema.php
  28. 30
      framework/yii/helpers/BaseArrayHelper.php
  29. 8
      framework/yii/i18n/I18N.php
  30. 2
      framework/yii/i18n/MissingTranslationEvent.php
  31. 2
      framework/yii/messages/config.php
  32. 3
      framework/yii/requirements/YiiRequirementChecker.php
  33. 2
      framework/yii/requirements/requirements.php
  34. 30
      framework/yii/validators/EmailValidator.php
  35. 2
      framework/yii/web/Controller.php
  36. 8
      framework/yii/web/Request.php
  37. 20
      framework/yii/web/Response.php
  38. 0
      tests/unit/data/i18n/messages/de-DE/test.php
  39. 0
      tests/unit/data/i18n/messages/en-US/test.php
  40. 5
      tests/unit/framework/caching/ApcCacheTest.php
  41. 26
      tests/unit/framework/caching/CacheTestCase.php
  42. 12
      tests/unit/framework/caching/DbCacheTest.php
  43. 12
      tests/unit/framework/caching/FileCacheTest.php
  44. 8
      tests/unit/framework/caching/MemCacheTest.php
  45. 8
      tests/unit/framework/caching/MemCachedTest.php
  46. 11
      tests/unit/framework/caching/RedisCacheTest.php
  47. 42
      tests/unit/framework/data/SortTest.php
  48. 8
      tests/unit/framework/helpers/ArrayHelperTest.php
  49. 8
      tests/unit/framework/i18n/FallbackMessageFormatterTest.php
  50. 2
      tests/unit/framework/i18n/FormatterTest.php
  51. 24
      tests/unit/framework/i18n/I18NTest.php
  52. 14
      tests/unit/framework/i18n/MessageFormatterTest.php
  53. 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(); ?> <?php $this->beginPage(); ?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="<?= Yii::$app->language ?>">
<head> <head>
<meta charset="<?= Yii::$app->charset ?>"/> <meta charset="<?= Yii::$app->charset ?>"/>
<title><?= Html::encode($this->title) ?></title> <title><?= Html::encode($this->title) ?></title>

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

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

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

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

2
apps/advanced/requirements.php

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

2
apps/basic/requirements.php

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

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

@ -13,7 +13,7 @@ AppAsset::register($this);
?> ?>
<?php $this->beginPage(); ?> <?php $this->beginPage(); ?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="<?= Yii::$app->language ?>">
<head> <head>
<meta charset="<?= Yii::$app->charset ?>"/> <meta charset="<?= Yii::$app->charset ?>"/>
<title><?= Html::encode($this->title) ?></title> <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*' => [ 'app*' => [
'class' => 'yii\i18n\PhpMessageSource', 'class' => 'yii\i18n\PhpMessageSource',
//'basePath' => '@app/messages', //'basePath' => '@app/messages',
//'sourceLanguage' => 'en_US', //'sourceLanguage' => 'en-US',
'fileMap' => [ 'fileMap' => [
'app' => 'app.php', 'app' => 'app.php',
'app/error' => 'error.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. - Massive attribute assignment: the ability to populate multiple model attributes in one step.
- Scenario-based data validation. - 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. 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 for more advanced models with additional functionality such as [Active Record](active-record.md). The Model class is also the base class for more advanced models with additional functionality, such as [Active Record](active-record.md).
Attributes Attributes
---------- ----------
Attributes store the actual data represented by a model and can The actual data represented by a model is stored in the model's *attributes*. Model attributes can
be accessed like object member variables. For example, a `Post` model be accessed like the member variables of any object. For example, a `Post` model
may contain a `title` attribute and a `content` attribute which may be may contain a `title` attribute and a `content` attribute, accessible as follows:
accessed as follows:
```php ```php
$post = new Post; $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, 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 ```php
$post = new Post; $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()]] 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 as the column names of the database table method. For example, [[\yii\db\ActiveRecord]] defines attributes using the column names of the database table
that is associated with the class. 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 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()]]. 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 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
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. In many cases, [[\yii\base\Model::generateAttributeLabel()]] will generate reasonable labels (e.g. `username` to `Username`, `orderNumber` to `Order Number`).
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`).
```php ```php
// LoginForm has two attributes: username and password // LoginForm has two attributes: username and password
@ -88,17 +85,19 @@ class LoginForm extends \yii\base\Model
Scenarios Scenarios
--------- ---------
A model may be used in different scenarios. For example, a `User` model may be used to collect user login inputs, 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` 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.
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. 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, 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, in the `login` scenario, only the `username` and `password` attributes are active; while in the `register` scenario,
additional attributes such as `email` are *active*. 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 Possible scenarios should be listed in the `scenarios()` method. This method returns an array whose keys are the scenario
names and whose values are the corresponding active attribute lists. Below is an example: names and whose values are lists of attributes that should be active in that scenario:
```php ```php
class User extends \yii\db\ActiveRecord 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). 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, We may do so by prefixing an exclamation character to the attribute name when declaring it in `scenarios()`. For example:
```php ```php
['username', 'password', '!secret'] ['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 ```php
class EmployeeController extends \yii\web\Controller 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 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.
use scenarios since form model is typically used for a single form.
Validation Validation
---------- ----------

2
docs/guide/performance.md

@ -1,7 +1,7 @@
Performance Tuning 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 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 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 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(); ?> <?php $this->beginPage(); ?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="<?= Yii::$app->charset ?>"> <html lang="<?= Yii::$app->language ?>">
<head> <head>
<meta charset="<?= Yii::$app->charset ?>"/> <meta charset="<?= Yii::$app->charset ?>"/>
<title><?= Html::encode($this->title) ?></title> <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) public function renderToolbar($event)
{ {
if (!$this->checkAccess()) { if (!$this->checkAccess() || Yii::$app->getRequest()->getIsAjax()) {
return; return;
} }
$url = Yii::$app->getUrlManager()->createUrl($this->id . '/default/toolbar', [ $url = Yii::$app->getUrlManager()->createUrl($this->id . '/default/toolbar', [

2
extensions/debug/panels/DbPanel.php

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

2
framework/yii/BaseYii.php

@ -498,7 +498,7 @@ class BaseYii
* @param string $category the message category. * @param string $category the message category.
* @param string $message the message to be translated. * @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 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. * [[\yii\base\Application::language|application language]] will be used.
* @return string the translated message. * @return string the translated message.
*/ */

110
framework/yii/assets/yii.js

@ -44,6 +44,11 @@
yii = (function ($) { yii = (function ($) {
var pub = { 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. * 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"]', clickableSelector: 'a, button, input[type="submit"], input[type="button"], input[type="reset"], input[type="image"]',
@ -161,46 +166,77 @@ yii = (function ($) {
}, },
init: function () { init: function () {
var $document = $(document); initCsrfHandler();
initRedirectHandler();
initScriptFilter();
initDataMethods();
}
};
// automatically send CSRF token for all AJAX requests function initRedirectHandler() {
$.ajaxPrefilter(function (options, originalOptions, xhr) { // handle AJAX redirection
if (!options.crossDomain && pub.getCsrfVar()) { $(document).ajaxComplete(function (event, xhr, settings) {
xhr.setRequestHeader('X-CSRF-Token', pub.getCsrfToken()); 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; return pub;
})(jQuery); })(jQuery);

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

@ -117,16 +117,16 @@ yii.validation = (function ($) {
var valid = true; var valid = true;
if (options.enableIDN) { if (options.enableIDN) {
var regexp = /^(.*)@(.*)$/, var regexp = /^(.*<?)(.*)@(.*)(>?)$/,
matches = regexp.exec(value); matches = regexp.exec(value);
if (matches === null) { if (matches === null) {
valid = false; valid = false;
} else { } 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); 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. * @var string the language that is meant to be used for end users.
* @see sourceLanguage * @see sourceLanguage
*/ */
public $language = 'en_US'; public $language = 'en-US';
/** /**
* @var string the language that the application is written in. This mainly refers to * @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. * the language that the messages and view files are written in.
* @see language * @see language
*/ */
public $sourceLanguage = 'en_US'; public $sourceLanguage = 'en-US';
/** /**
* @var Controller the currently active controller instance * @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 string $name the event name
* @param Event $event the event parameter. If not set, a default [[Event]] object will be created. * @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(); $this->ensureBehaviors();
if (!empty($this->_events[$name])) { 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 * 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. * 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 * @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 = []) public static function begin($config = [])
{ {
@ -55,7 +55,7 @@ class Widget extends Component implements ViewContextInterface
/** /**
* Ends a widget. * Ends a widget.
* Note that the rendering result of the widget is directly echoed out. * 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 * @throws InvalidCallException if [[begin()]] and [[end()]] calls are not properly nested
*/ */
public static function end() public static function end()

6
framework/yii/caching/ApcCache.php

@ -124,6 +124,10 @@ class ApcCache extends Cache
*/ */
protected function flushValues() 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; 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 * 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 * 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. * @inheritDocs
* 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.
*/ */
protected function getValue($key) protected function getValue($key)
{ {
@ -130,9 +127,7 @@ class RedisCache extends Cache
} }
/** /**
* Retrieves multiple values from cache with the specified keys. * @inheritDocs
* @param array $keys a list of keys identifying the cached values
* @return array a list of cached values indexed by the keys
*/ */
protected function getValues($keys) protected function getValues($keys)
{ {
@ -146,55 +141,67 @@ class RedisCache extends Cache
} }
/** /**
* Stores a value identified by a key in cache. * @inheritDocs
* 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
*/ */
protected function setValue($key,$value,$expire) protected function setValue($key, $value, $expire)
{ {
if ($expire == 0) { if ($expire == 0) {
return (bool) $this->_connection->executeCommand('SET', [$key, $value]); return (bool) $this->_connection->executeCommand('SET', [$key, $value]);
} else { } else {
$expire = (int) ($expire * 1000); $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. * @inheritDocs
* 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
*/ */
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) { if ($expire == 0) {
return (bool) $this->_connection->executeCommand('SETNX', [$key, $value]); $this->_connection->executeCommand('MSET', $args);
} else { } else {
// TODO consider requiring redis version >= 2.6.12 that supports this in one command
$expire = (int) ($expire * 1000); $expire = (int) ($expire * 1000);
$this->_connection->executeCommand('MULTI'); $this->_connection->executeCommand('MULTI');
$this->_connection->executeCommand('SETNX', [$key, $value]); $this->_connection->executeCommand('MSET', $args);
$this->_connection->executeCommand('PEXPIRE', [$key, $expire]); $index = [];
$response = $this->_connection->executeCommand('EXEC'); foreach ($data as $key => $value) {
return (bool) $response[0]; $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 * @inheritDocs
* 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
*/ */
protected function deleteValue($key) protected function deleteValue($key)
{ {
@ -202,9 +209,7 @@ class RedisCache extends Cache
} }
/** /**
* Deletes all values from cache. * @inheritDocs
* This is the implementation of the method declared in the parent class.
* @return boolean whether the flush operation was successful.
*/ */
protected function flushValues() protected function flushValues()
{ {

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

@ -124,7 +124,7 @@ class HelpController extends Controller
continue; continue;
} }
foreach ($this->getModuleCommands($child) as $command) { 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; $model = new $this->query->modelClass;
foreach ($model->attributes() as $attribute) { foreach ($model->attributes() as $attribute) {
$sort->attributes[$attribute] = [ $sort->attributes[$attribute] = [
'asc' => [$attribute => Sort::ASC], 'asc' => [$attribute => SORT_ASC],
'desc' => [$attribute => Sort::DESC], 'desc' => [$attribute => SORT_DESC],
'label' => $model->getAttributeLabel($attribute), 'label' => $model->getAttributeLabel($attribute),
]; ];
} }

5
framework/yii/data/BaseDataProvider.php

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

2
framework/yii/data/Pagination.php

@ -182,7 +182,7 @@ class Pagination extends Object
} else { } else {
unset($params[$this->pageVar]); 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); return Yii::$app->getUrlManager()->createUrl($route, $params);
} }

68
framework/yii/data/Sort.php

@ -29,9 +29,9 @@ use yii\helpers\Inflector;
* 'attributes' => [ * 'attributes' => [
* 'age', * 'age',
* 'name' => [ * 'name' => [
* 'asc' => ['first_name' => Sort::ASC, 'last_name' => Sort::ASC], * 'asc' => ['first_name' => SORT_ASC, 'last_name' => SORT_ASC],
* 'desc' => ['first_name' => Sort::DESC, 'last_name' => Sort::DESC], * 'desc' => ['first_name' => SORT_DESC, 'last_name' => SORT_DESC],
* 'default' => Sort::DESC, * 'default' => SORT_DESC,
* 'label' => 'Name', * 'label' => 'Name',
* ], * ],
* ], * ],
@ -66,7 +66,7 @@ use yii\helpers\Inflector;
* that can lead to pages with the data sorted by the corresponding attributes. * 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 * @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 * @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. * 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 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. * @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. * 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', * 'age',
* 'name' => [ * 'name' => [
* 'asc' => ['first_name' => Sort::ASC, 'last_name' => Sort::ASC], * 'asc' => ['first_name' => SORT_ASC, 'last_name' => SORT_ASC],
* 'desc' => ['first_name' => Sort::DESC, 'last_name' => Sort::DESC], * 'desc' => ['first_name' => SORT_DESC, 'last_name' => SORT_DESC],
* 'default' => Sort::DESC, * 'default' => SORT_DESC,
* 'label' => 'Name', * 'label' => 'Name',
* ], * ],
* ] * ]
@ -112,9 +102,9 @@ class Sort extends Object
* *
* ~~~ * ~~~
* 'age' => [ * 'age' => [
* 'asc' => ['age' => Sort::ASC], * 'asc' => ['age' => SORT_ASC],
* 'desc' => ['age' => Sort::DESC], * 'desc' => ['age' => SORT_DESC],
* 'default' => Sort::ASC, * 'default' => SORT_ASC,
* 'label' => Inflector::camel2words('age'), * 'label' => Inflector::camel2words('age'),
* ] * ]
* ~~~ * ~~~
@ -153,8 +143,8 @@ class Sort extends Object
* *
* ~~~ * ~~~
* [ * [
* 'name' => Sort::ASC, * 'name' => SORT_ASC,
* 'create_time' => Sort::DESC, * 'create_time' => SORT_DESC,
* ] * ]
* ~~~ * ~~~
* *
@ -199,13 +189,13 @@ class Sort extends Object
foreach ($this->attributes as $name => $attribute) { foreach ($this->attributes as $name => $attribute) {
if (!is_array($attribute)) { if (!is_array($attribute)) {
$attributes[$attribute] = [ $attributes[$attribute] = [
'asc' => [$attribute => self::ASC], 'asc' => [$attribute => SORT_ASC],
'desc' => [$attribute => self::DESC], 'desc' => [$attribute => SORT_DESC],
]; ];
} elseif (!isset($attribute['asc'], $attribute['desc'])) { } elseif (!isset($attribute['asc'], $attribute['desc'])) {
$attributes[$name] = array_merge([ $attributes[$name] = array_merge([
'asc' => [$name => self::ASC], 'asc' => [$name => SORT_ASC],
'desc' => [$name => self::DESC], 'desc' => [$name => SORT_DESC],
], $attribute); ], $attribute);
} else { } else {
$attributes[$name] = $attribute; $attributes[$name] = $attribute;
@ -226,7 +216,7 @@ class Sort extends Object
$orders = []; $orders = [];
foreach ($attributeOrders as $attribute => $direction) { foreach ($attributeOrders as $attribute => $direction) {
$definition = $this->attributes[$attribute]; $definition = $this->attributes[$attribute];
$columns = $definition[$direction === self::ASC ? 'asc' : 'desc']; $columns = $definition[$direction === SORT_ASC ? 'asc' : 'desc'];
foreach ($columns as $name => $dir) { foreach ($columns as $name => $dir) {
$orders[$name] = $dir; $orders[$name] = $dir;
} }
@ -243,8 +233,8 @@ class Sort extends Object
* Returns the currently requested sort information. * Returns the currently requested sort information.
* @param boolean $recalculate whether to recalculate the sort directions * @param boolean $recalculate whether to recalculate the sort directions
* @return array sort directions indexed by attribute names. * @return array sort directions indexed by attribute names.
* Sort direction can be either [[Sort::ASC]] for ascending order or * Sort direction can be either `SORT_ASC` for ascending order or
* [[Sort::DESC]] for descending order. * `SORT_DESC` for descending order.
*/ */
public function getAttributeOrders($recalculate = false) public function getAttributeOrders($recalculate = false)
{ {
@ -262,7 +252,7 @@ class Sort extends Object
} }
if (isset($this->attributes[$attribute])) { if (isset($this->attributes[$attribute])) {
$this->_attributeOrders[$attribute] = $descending; $this->_attributeOrders[$attribute] = $descending ? SORT_DESC : SORT_ASC;
if (!$this->enableMultiSort) { if (!$this->enableMultiSort) {
return $this->_attributeOrders; return $this->_attributeOrders;
} }
@ -279,8 +269,8 @@ class Sort extends Object
/** /**
* Returns the sort direction of the specified attribute in the current request. * Returns the sort direction of the specified attribute in the current request.
* @param string $attribute the attribute name * @param string $attribute the attribute name
* @return boolean|null Sort direction of the attribute. Can be either [[Sort::ASC]] * @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 * for ascending order or `SORT_DESC` for descending order. Null is returned
* if the attribute is invalid or does not need to be sorted. * if the attribute is invalid or does not need to be sorted.
*/ */
public function getAttributeOrder($attribute) public function getAttributeOrder($attribute)
@ -305,7 +295,7 @@ class Sort extends Object
public function link($attribute, $options = []) public function link($attribute, $options = [])
{ {
if (($direction = $this->getAttributeOrder($attribute)) !== null) { if (($direction = $this->getAttributeOrder($attribute)) !== null) {
$class = $direction ? 'desc' : 'asc'; $class = $direction === SORT_DESC ? 'desc' : 'asc';
if (isset($options['class'])) { if (isset($options['class'])) {
$options['class'] .= ' ' . $class; $options['class'] .= ' ' . $class;
} else { } else {
@ -365,21 +355,21 @@ class Sort extends Object
$definition = $this->attributes[$attribute]; $definition = $this->attributes[$attribute];
$directions = $this->getAttributeOrders(); $directions = $this->getAttributeOrders();
if (isset($directions[$attribute])) { if (isset($directions[$attribute])) {
$descending = !$directions[$attribute]; $direction = $directions[$attribute] === SORT_DESC ? SORT_ASC : SORT_DESC;
unset($directions[$attribute]); unset($directions[$attribute]);
} else { } else {
$descending = !empty($definition['default']); $direction = isset($definition['default']) ? $definition['default'] : SORT_ASC;
} }
if ($this->enableMultiSort) { if ($this->enableMultiSort) {
$directions = array_merge([$attribute => $descending], $directions); $directions = array_merge([$attribute => $direction], $directions);
} else { } else {
$directions = [$attribute => $descending]; $directions = [$attribute => $direction];
} }
$sorts = []; $sorts = [];
foreach ($directions as $attribute => $descending) { foreach ($directions as $attribute => $direction) {
$sorts[] = $descending ? $attribute . $this->separators[1] . $this->descTag : $attribute; $sorts[] = $direction === SORT_DESC ? $attribute . $this->separators[1] . $this->descTag : $attribute;
} }
return implode($this->separators[0], $sorts); 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->name = $info['Field'];
$column->allowNull = $info['Null'] === 'YES'; $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->autoIncrement = stripos($info['Extra'], 'auto_increment') !== false;
$column->dbType = strtolower($info['Type']); $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, 'inet' => self::TYPE_STRING,
'smallint' => self::TYPE_SMALLINT, 'smallint' => self::TYPE_SMALLINT,
'int4' => self::TYPE_INTEGER, 'int4' => self::TYPE_INTEGER,
'int8' => self::TYPE_BIGINT,
'integer' => self::TYPE_INTEGER, 'integer' => self::TYPE_INTEGER,
'bigint' => self::TYPE_BIGINT, 'bigint' => self::TYPE_BIGINT,
'interval' => self::TYPE_STRING, 'interval' => self::TYPE_STRING,
@ -241,18 +242,17 @@ SQL;
$schemaName = $this->db->quoteValue($table->schemaName); $schemaName = $this->db->quoteValue($table->schemaName);
$sql = <<<SQL $sql = <<<SQL
SELECT SELECT
current_database() as table_catalog, d.nspname AS table_schema,
d.nspname AS table_schema, c.relname AS table_name,
c.relname AS table_name, a.attname AS column_name,
a.attname AS column_name, t.typname AS data_type,
t.typname AS data_type, a.attlen AS character_maximum_length,
a.attlen AS character_maximum_length, pg_catalog.col_description(c.oid, a.attnum) AS column_comment,
pg_catalog.col_description(c.oid, a.attnum) AS column_comment, a.atttypmod AS modifier,
a.atttypmod AS modifier, a.attnotnull = false AS is_nullable,
a.attnotnull = false AS is_nullable, CAST(pg_get_expr(ad.adbin, ad.adrelid) AS varchar) AS column_default,
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,
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,
array_to_string((select array_agg(enumlabel) from pg_enum where enumtypid=a.atttypid)::varchar[],',') as enum_values,
CASE atttypid CASE atttypid
WHEN 21 /*int2*/ THEN 16 WHEN 21 /*int2*/ THEN 16
WHEN 23 /*int4*/ THEN 32 WHEN 23 /*int4*/ THEN 32
@ -288,7 +288,7 @@ FROM
LEFT JOIN pg_namespace d ON d.oid = c.relnamespace LEFT JOIN pg_namespace d ON d.oid = c.relnamespace
LEFT join pg_constraint ct on ct.conrelid=c.oid and ct.contype='p' LEFT join pg_constraint ct on ct.conrelid=c.oid and ct.contype='p'
WHERE WHERE
a.attnum > 0 a.attnum > 0 and t.typname != ''
and c.relname = {$tableName} and c.relname = {$tableName}
and d.nspname = {$schemaName} and d.nspname = {$schemaName}
ORDER BY 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 * 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)`. * purpose. The anonymous function signature should be: `function($item)`.
* To sort by multiple keys, provide an array of keys here. * To sort by multiple keys, provide an array of keys here.
* @param boolean|array $descending whether to sort in descending or ascending order. When * @param integer|array $direction the sorting direction. It can be either `SORT_ASC` or `SORT_DESC`.
* sorting by multiple keys with different descending orders, use an array of descending flags. * 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 * @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`. * `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) * 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. * 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 * @throws InvalidParamException if the $descending or $sortFlag parameters do not have
* correct number of elements as that of $key. * 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]; $keys = is_array($key) ? $key : [$key];
if (empty($keys) || empty($array)) { if (empty($keys) || empty($array)) {
return; return;
} }
$n = count($keys); $n = count($keys);
if (is_scalar($descending)) { if (is_scalar($direction)) {
$descending = array_fill(0, $n, $descending); $direction = array_fill(0, $n, $direction);
} elseif (count($descending) !== $n) { } elseif (count($direction) !== $n) {
throw new InvalidParamException('The length of $descending parameter must be the same as that of $keys.'); throw new InvalidParamException('The length of $descending parameter must be the same as that of $keys.');
} }
if (is_scalar($sortFlag)) { if (is_scalar($sortFlag)) {
@ -362,22 +359,11 @@ class BaseArrayHelper
} elseif (count($sortFlag) !== $n) { } elseif (count($sortFlag) !== $n) {
throw new InvalidParamException('The length of $sortFlag parameter must be the same as that of $keys.'); 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 = []; $args = [];
foreach ($keys as $i => $key) { foreach ($keys as $i => $key) {
$flag = $sortFlag[$i]; $flag = $sortFlag[$i];
$cs = $caseSensitive[$i]; $args[] = static::getColumn($array, $key);
if (!$cs && ($flag === SORT_STRING)) { $args[] = $direction[$i];
$flag = $flag | SORT_FLAG_CASE;
$args[] = static::getColumn($array, $key);
} else {
$args[] = static::getColumn($array, $key);
}
$args[] = $descending[$i] ? SORT_DESC : SORT_ASC;
$args[] = $flag; $args[] = $flag;
} }
$args[] = &$array; $args[] = &$array;

8
framework/yii/i18n/I18N.php

@ -53,14 +53,14 @@ class I18N extends Component
if (!isset($this->translations['yii'])) { if (!isset($this->translations['yii'])) {
$this->translations['yii'] = [ $this->translations['yii'] = [
'class' => 'yii\i18n\PhpMessageSource', 'class' => 'yii\i18n\PhpMessageSource',
'sourceLanguage' => 'en_US', 'sourceLanguage' => 'en-US',
'basePath' => '@yii/messages', 'basePath' => '@yii/messages',
]; ];
} }
if (!isset($this->translations['app'])) { if (!isset($this->translations['app'])) {
$this->translations['app'] = [ $this->translations['app'] = [
'class' => 'yii\i18n\PhpMessageSource', 'class' => 'yii\i18n\PhpMessageSource',
'sourceLanguage' => 'en_US', 'sourceLanguage' => 'en-US',
'basePath' => '@app/messages', 'basePath' => '@app/messages',
]; ];
} }
@ -75,7 +75,7 @@ class I18N extends Component
* @param string $category the message category. * @param string $category the message category.
* @param string $message the message to be translated. * @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 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. * @return string the translated and formatted message.
*/ */
public function translate($category, $message, $params, $language) public function translate($category, $message, $params, $language)
@ -89,7 +89,7 @@ class I18N extends Component
* *
* @param string $message the message to be formatted. * @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 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. * @return string the formatted message.
*/ */
public function format($message, $params, $language) public function format($message, $params, $language)

2
framework/yii/i18n/MissingTranslationEvent.php

@ -27,7 +27,7 @@ class MissingTranslationEvent extends Event
*/ */
public $category; 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; public $language;
} }

2
framework/yii/messages/config.php

@ -6,7 +6,7 @@ return [
// string, required, root directory containing message translations. // string, required, root directory containing message translations.
'messagePath' => __DIR__, 'messagePath' => __DIR__,
// array, required, list of language codes that the extracted messages // 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'], 'languages' => ['de'],
// string, the name of the function for translating messages. // 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 // 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)) { if (empty($extensionVersion)) {
return false; return false;
} }
if (strncasecmp($extensionVersion, 'PECL-', 5) == 0) {
$extensionVersion = substr($extensionVersion, 5);
}
return version_compare($extensionVersion, $version, $compare); 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', '>='), 'condition' => $this->checkPhpExtensionVersion('intl', '1.0.2', '>='),
'by' => '<a href="http://www.php.net/manual/en/book.intl.php">Internationalization</a> support', '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 '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.' <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; public $allowName = false;
/** /**
* @var boolean whether to check the MX record for the email address. * @var boolean whether to check whether the emails domain exists and has either an A or MX record.
* Defaults to false. To enable it, you need to make sure the PHP function 'checkdnsrr' * Be aware of the fact that this check can fail due to temporary DNS problems even if the email address is
* exists in your PHP installation. * valid and an email would be deliverable. Defaults to false.
*/ */
public $checkMX = false; public $checkDNS = false;
/**
* @var boolean whether to check port 25 for the email address.
* Defaults to false.
*/
public $checkPort = false;
/** /**
* @var boolean whether validation process should take into account IDN (internationalized domain * @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. * 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) public function validateValue($value)
{ {
// make sure string length is limited to avoid DOS attacks // 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; return false;
} }
if (($atPosition = strpos($value, '@')) === false) { if (!preg_match('/^(.*<?)(.*)@(.*)(>?)$/', $value, $matches)) {
return false; return false;
} }
$domain = rtrim(substr($value, $atPosition + 1), '>'); $domain = $matches[3];
if ($this->enableIDN) { 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); $valid = preg_match($this->pattern, $value) || $this->allowName && preg_match($this->fullPattern, $value);
if ($valid) { if ($valid && $this->checkDNS) {
if ($this->checkMX && function_exists('checkdnsrr')) { $valid = checkdnsrr($domain, 'MX') || checkdnsrr($domain, 'A');
$valid = checkdnsrr($domain, 'MX');
}
if ($valid && $this->checkPort && function_exists('fsockopen')) {
$valid = fsockopen($domain, 25) !== false;
}
} }
return $valid; return $valid;
} }

2
framework/yii/web/Controller.php

@ -92,6 +92,8 @@ class Controller extends \yii\base\Controller
{ {
if (parent::beforeAction($action)) { if (parent::beforeAction($action)) {
if ($this->enableCsrfValidation && !Yii::$app->getRequest()->validateCsrfToken()) { 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.')); throw new HttpException(400, Yii::t('yii', 'Unable to verify your data submission.'));
} }
return true; 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; return isset($acceptedLanguages[0]) ? $acceptedLanguages[0] : null;
} }
foreach ($acceptedLanguages as $acceptedLanguage) { foreach ($acceptedLanguages as $acceptedLanguage) {
$acceptedLanguage = str_replace('-', '_', strtolower($acceptedLanguage)); $acceptedLanguage = str_replace('_', '-', strtolower($acceptedLanguage));
foreach ($languages as $language) { foreach ($languages as $language) {
$language = str_replace('-', '_', strtolower($language)); $language = str_replace('_', '-', strtolower($language));
// en_us==en_us, en==en_us, en_us==en // en-us==en-us, en==en-us, en-us==en
if ($language === $acceptedLanguage || strpos($acceptedLanguage, $language . '_') === 0 || strpos($language, $acceptedLanguage . '_') === 0) { if ($language === $acceptedLanguage || strpos($acceptedLanguage, $language . '-') === 0 || strpos($language, $acceptedLanguage . '-') === 0) {
return $language; return $language;
} }
} }

20
framework/yii/web/Response.php

@ -126,9 +126,10 @@ class Response extends \yii\base\Response
*/ */
public $charset; 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']`, * @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. * 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. * @var integer the HTTP status code to send with the response.
*/ */
private $_statusCode; private $_statusCode = 200;
/** /**
* @var HeaderCollection * @var HeaderCollection
*/ */
@ -248,11 +249,6 @@ class Response extends \yii\base\Response
*/ */
public function setStatusCode($value, $text = null) public function setStatusCode($value, $text = null)
{ {
if ($value === null) {
$this->_statusCode = null;
$this->statusText = null;
return;
}
$this->_statusCode = (int)$value; $this->_statusCode = (int)$value;
if ($this->getIsInvalid()) { if ($this->getIsInvalid()) {
throw new InvalidParamException("The HTTP status code is invalid: $value"); throw new InvalidParamException("The HTTP status code is invalid: $value");
@ -297,10 +293,10 @@ class Response extends \yii\base\Response
{ {
$this->_headers = null; $this->_headers = null;
$this->_cookies = null; $this->_cookies = null;
$this->_statusCode = null; $this->_statusCode = 200;
$this->statusText = 'OK';
$this->data = null; $this->data = null;
$this->content = null; $this->content = null;
$this->statusText = null;
} }
/** /**
@ -312,9 +308,7 @@ class Response extends \yii\base\Response
return; return;
} }
$statusCode = $this->getStatusCode(); $statusCode = $this->getStatusCode();
if ($statusCode !== null) { header("HTTP/{$this->version} $statusCode {$this->statusText}");
header("HTTP/{$this->version} $statusCode {$this->statusText}");
}
if ($this->_headers) { if ($this->_headers) {
$headers = $this->getHeaders(); $headers = $this->getHeaders();
foreach ($headers as $name => $values) { 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."); $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']); $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 = $this->getCacheInstance();
$cache->flush(); $cache->flush();
@ -100,7 +111,7 @@ abstract class CacheTestCase extends TestCase
'string_test' => 'string_test', 'string_test' => 'string_test',
'number_test' => 42, 'number_test' => 42,
'array_test' => ['array_test' => 'array_test'], 'array_test' => ['array_test' => 'array_test'],
]); ], $expiry);
$this->assertEquals('string_test', $cache->get('string_test')); $this->assertEquals('string_test', $cache->get('string_test'));
@ -170,6 +181,17 @@ abstract class CacheTestCase extends TestCase
$this->assertFalse($cache->get('expire_test')); $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() public function testAdd()
{ {
$cache = $this->prepare(); $cache = $this->prepare();

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

@ -83,4 +83,16 @@ class DbCacheTest extends CacheTestCase
static::$time++; static::$time++;
$this->assertFalse($cache->get('expire_test')); $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++; static::$time++;
$this->assertFalse($cache->get('expire_test')); $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(); 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(); 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')); $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 * Store a value that is 2 times buffer size big
* https://github.com/yiisoft/yii2/issues/743 * https://github.com/yiisoft/yii2/issues/743

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

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

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

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

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

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

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

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

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

@ -250,7 +250,7 @@ _MSG_
1 => 123, 1 => 123,
2 => 37.073 2 => 37.073
], ],
'en_US' 'en-US'
], ],
[ [
@ -272,7 +272,7 @@ _MSG_
'trees' => 123, 'trees' => 123,
'monkeysPerTree' => 37.073 'monkeysPerTree' => 37.073
], ],
'en_US' 'en-US'
], ],
[ [
@ -297,14 +297,14 @@ _MSG_
$this->markTestSkipped($skipMessage); $this->markTestSkipped($skipMessage);
} }
$formatter = new MessageFormatter(); $formatter = new MessageFormatter();
$result = $formatter->format($pattern, $args, 'en_US'); $result = $formatter->format($pattern, $args, 'en-US');
$this->assertEquals($expected, $result, $formatter->getErrorMessage()); $this->assertEquals($expected, $result, $formatter->getErrorMessage());
} }
/** /**
* @dataProvider parsePatterns * @dataProvider parsePatterns
*/ */
public function testParseNamedArguments($pattern, $expected, $args, $locale = 'en_US') public function testParseNamedArguments($pattern, $expected, $args, $locale = 'en-US')
{ {
if (!extension_loaded("intl")) { if (!extension_loaded("intl")) {
$this->markTestSkipped("intl not installed. Skipping."); $this->markTestSkipped("intl not installed. Skipping.");
@ -322,7 +322,7 @@ _MSG_
$formatter = new MessageFormatter(); $formatter = new MessageFormatter();
$result = $formatter->format('{'.self::SUBJECT.'} is {'.self::N.', number}', [ $result = $formatter->format('{'.self::SUBJECT.'} is {'.self::N.', number}', [
self::N => self::N_VALUE, self::N => self::N_VALUE,
], 'en_US'); ], 'en-US');
$this->assertEquals($expected, $result, $formatter->getErrorMessage()); $this->assertEquals($expected, $result, $formatter->getErrorMessage());
} }
@ -331,7 +331,7 @@ _MSG_
{ {
$pattern = '{'.self::SUBJECT.'} is '.self::N; $pattern = '{'.self::SUBJECT.'} is '.self::N;
$formatter = new MessageFormatter(); $formatter = new MessageFormatter();
$result = $formatter->format($pattern, [], 'en_US'); $result = $formatter->format($pattern, [], 'en-US');
$this->assertEquals($pattern, $result, $formatter->getErrorMessage()); $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('sam@rmcreative.ru'));
$this->assertTrue($validator->validateValue('5011@gmail.com')); $this->assertTrue($validator->validateValue('5011@gmail.com'));
$this->assertFalse($validator->validateValue('rmcreative.ru')); $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() public function testValidateValueMx()
{ {
$validator = new EmailValidator(); $validator = new EmailValidator();
$validator->checkMX = true;
$validator->checkDNS = true;
$this->assertTrue($validator->validateValue('5011@gmail.com')); $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() public function testValidateAttribute()
@ -43,24 +105,4 @@ class EmailValidatorTest extends TestCase
$val->validateAttribute($model, 'attr_email'); $val->validateAttribute($model, 'attr_email');
$this->assertFalse($model->hasErrors('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