diff --git a/apps/advanced/backend/views/layouts/main.php b/apps/advanced/backend/views/layouts/main.php index 9f0280d..fdffc26 100644 --- a/apps/advanced/backend/views/layouts/main.php +++ b/apps/advanced/backend/views/layouts/main.php @@ -13,7 +13,7 @@ AppAsset::register($this); ?> beginPage(); ?> - + <?= Html::encode($this->title) ?> diff --git a/apps/advanced/frontend/config/main.php b/apps/advanced/frontend/config/main.php index 49c4e55..e0fd2de 100644 --- a/apps/advanced/frontend/config/main.php +++ b/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, diff --git a/apps/advanced/frontend/views/layouts/main.php b/apps/advanced/frontend/views/layouts/main.php index 7b2ce6f..febcc5a 100644 --- a/apps/advanced/frontend/views/layouts/main.php +++ b/apps/advanced/frontend/views/layouts/main.php @@ -14,7 +14,7 @@ AppAsset::register($this); ?> beginPage(); ?> - + <?= Html::encode($this->title) ?> diff --git a/apps/advanced/requirements.php b/apps/advanced/requirements.php index 139284f..47bdf37 100644 --- a/apps/advanced/requirements.php +++ b/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' => 'CApcCache', ], // Additional PHP extensions : diff --git a/apps/basic/requirements.php b/apps/basic/requirements.php index 139284f..47bdf37 100644 --- a/apps/basic/requirements.php +++ b/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' => 'CApcCache', ], // Additional PHP extensions : diff --git a/apps/basic/views/layouts/main.php b/apps/basic/views/layouts/main.php index 8489e9c..04acdfb 100644 --- a/apps/basic/views/layouts/main.php +++ b/apps/basic/views/layouts/main.php @@ -13,7 +13,7 @@ AppAsset::register($this); ?> beginPage(); ?> - + <?= Html::encode($this->title) ?> diff --git a/docs/guide/i18n.md b/docs/guide/i18n.md index 6524801..477de0a 100644 --- a/docs/guide/i18n.md +++ b/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', diff --git a/docs/guide/model.md b/docs/guide/model.md index 154fd38..465cb16 100644 --- a/docs/guide/model.md +++ b/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 ---------- diff --git a/docs/guide/performance.md b/docs/guide/performance.md index d16d962..42aa042 100644 --- a/docs/guide/performance.md +++ b/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 diff --git a/docs/guide/view.md b/docs/guide/view.md index 5951a30..ae86809 100644 --- a/docs/guide/view.md +++ b/docs/guide/view.md @@ -256,7 +256,7 @@ use yii\helpers\Html; ?> beginPage(); ?> - + <?= Html::encode($this->title) ?> diff --git a/extensions/debug/Module.php b/extensions/debug/Module.php index b315c6c..ae707da 100644 --- a/extensions/debug/Module.php +++ b/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', [ diff --git a/extensions/debug/panels/DbPanel.php b/extensions/debug/panels/DbPanel.php index e24da93..a487dd4 100644 --- a/extensions/debug/panels/DbPanel.php +++ b/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); diff --git a/extensions/sphinx/ActiveQuery.php b/extensions/sphinx/ActiveQuery.php index aaaf419..a196b56 100644 --- a/extensions/sphinx/ActiveQuery.php +++ b/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 * @since 2.0 diff --git a/extensions/sphinx/ActiveRecord.php b/extensions/sphinx/ActiveRecord.php index a5f8c71..95106a6 100644 --- a/extensions/sphinx/ActiveRecord.php +++ b/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 * @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 */ diff --git a/extensions/sphinx/ActiveRelation.php b/extensions/sphinx/ActiveRelation.php index 15a5ba0..c0dd0ca 100644 --- a/extensions/sphinx/ActiveRelation.php +++ b/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 * @since 2.0 diff --git a/extensions/sphinx/Connection.php b/extensions/sphinx/Connection.php index c0d4562..dbbe27a 100644 --- a/extensions/sphinx/Connection.php +++ b/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: diff --git a/extensions/sphinx/Query.php b/extensions/sphinx/Query.php index 4da378e..ff0dcba 100644 --- a/extensions/sphinx/Query.php +++ b/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; /** diff --git a/extensions/sphinx/README.md b/extensions/sphinx/README.md index e135318..35c400a 100644 --- a/extensions/sphinx/README.md +++ b/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. \ No newline at end of file +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' => '', + ], + ], +]; +``` \ No newline at end of file diff --git a/framework/yii/BaseYii.php b/framework/yii/BaseYii.php index 357a1e7..f11df7f 100644 --- a/framework/yii/BaseYii.php +++ b/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. */ diff --git a/framework/yii/assets/yii.js b/framework/yii/assets/yii.js index add3a02..b9f2cdd 100644 --- a/framework/yii/assets/yii.js +++ b/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); diff --git a/framework/yii/assets/yii.validation.js b/framework/yii/assets/yii.validation.js index 3ce9edb..97074ac 100644 --- a/framework/yii/assets/yii.validation.js +++ b/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); } }, diff --git a/framework/yii/base/Application.php b/framework/yii/base/Application.php index 0af586c..dc363f1 100644 --- a/framework/yii/base/Application.php +++ b/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 */ diff --git a/framework/yii/base/Component.php b/framework/yii/base/Component.php index 48d4f05..9af22a8 100644 --- a/framework/yii/base/Component.php +++ b/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])) { diff --git a/framework/yii/base/Widget.php b/framework/yii/base/Widget.php index c47a89d..4a06c92 100644 --- a/framework/yii/base/Widget.php +++ b/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() diff --git a/framework/yii/caching/ApcCache.php b/framework/yii/caching/ApcCache.php index 688c3f1..8a0d207 100644 --- a/framework/yii/caching/ApcCache.php +++ b/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'); + } } } diff --git a/framework/yii/caching/RedisCache.php b/framework/yii/caching/RedisCache.php index d31d66b..b64f000 100644 --- a/framework/yii/caching/RedisCache.php +++ b/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() { diff --git a/framework/yii/console/controllers/HelpController.php b/framework/yii/console/controllers/HelpController.php index 90e810f..eee1923 100644 --- a/framework/yii/console/controllers/HelpController.php +++ b/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; } } diff --git a/framework/yii/data/ActiveDataProvider.php b/framework/yii/data/ActiveDataProvider.php index 89a7cf6..e9d5403 100644 --- a/framework/yii/data/ActiveDataProvider.php +++ b/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), ]; } diff --git a/framework/yii/data/BaseDataProvider.php b/framework/yii/data/BaseDataProvider.php index db1e057..cf094c7 100644 --- a/framework/yii/data/BaseDataProvider.php +++ b/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; } diff --git a/framework/yii/data/Pagination.php b/framework/yii/data/Pagination.php index dc75294..6a85dd2 100644 --- a/framework/yii/data/Pagination.php +++ b/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); } diff --git a/framework/yii/data/Sort.php b/framework/yii/data/Sort.php index 156b447..7612641 100644 --- a/framework/yii/data/Sort.php +++ b/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); } diff --git a/framework/yii/db/cubrid/Schema.php b/framework/yii/db/cubrid/Schema.php index 3131fa8..458f2e3 100644 --- a/framework/yii/db/cubrid/Schema.php +++ b/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']); diff --git a/framework/yii/db/pgsql/Schema.php b/framework/yii/db/pgsql/Schema.php index 4925984..d7885f2 100644 --- a/framework/yii/db/pgsql/Schema.php +++ b/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 = << 0 + a.attnum > 0 and t.typname != '' and c.relname = {$tableName} and d.nspname = {$schemaName} ORDER BY diff --git a/framework/yii/helpers/BaseArrayHelper.php b/framework/yii/helpers/BaseArrayHelper.php index a5382e7..da63238 100644 --- a/framework/yii/helpers/BaseArrayHelper.php +++ b/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; diff --git a/framework/yii/i18n/I18N.php b/framework/yii/i18n/I18N.php index 5575621..c59a6d2 100644 --- a/framework/yii/i18n/I18N.php +++ b/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) diff --git a/framework/yii/i18n/MissingTranslationEvent.php b/framework/yii/i18n/MissingTranslationEvent.php index 9ac337a..5c8ffd3 100644 --- a/framework/yii/i18n/MissingTranslationEvent.php +++ b/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; } diff --git a/framework/yii/messages/config.php b/framework/yii/messages/config.php index 0f724ba..b707abb 100644 --- a/framework/yii/messages/config.php +++ b/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 diff --git a/framework/yii/requirements/YiiRequirementChecker.php b/framework/yii/requirements/YiiRequirementChecker.php index c8ad45d..586cdce 100644 --- a/framework/yii/requirements/YiiRequirementChecker.php +++ b/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); } diff --git a/framework/yii/requirements/requirements.php b/framework/yii/requirements/requirements.php index 34b556e..916d6aa 100644 --- a/framework/yii/requirements/requirements.php +++ b/framework/yii/requirements/requirements.php @@ -44,7 +44,7 @@ return array( 'condition' => $this->checkPhpExtensionVersion('intl', '1.0.2', '>='), 'by' => 'Internationalization support', 'memo' => 'PHP Intl extension 1.0.2 or higher is required when you want to use advanced parameters formatting - in \Yii::t(), IDN-feature of + in Yii::t(), IDN-feature of EmailValidator or UrlValidator or the yii\i18n\Formatter class.' ), ); diff --git a/framework/yii/validators/EmailValidator.php b/framework/yii/validators/EmailValidator.php index bc105a5..6b5a07f 100644 --- a/framework/yii/validators/EmailValidator.php +++ b/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; } diff --git a/framework/yii/web/Controller.php b/framework/yii/web/Controller.php index 3b08b7e..49e1ba6 100644 --- a/framework/yii/web/Controller.php +++ b/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; diff --git a/framework/yii/web/Request.php b/framework/yii/web/Request.php index 0b49730..7a7cedf 100644 --- a/framework/yii/web/Request.php +++ b/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; } } diff --git a/framework/yii/web/Response.php b/framework/yii/web/Response.php index 8934fa1..1d77d14 100644 --- a/framework/yii/web/Response.php +++ b/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) { diff --git a/tests/unit/data/i18n/messages/de_DE/test.php b/tests/unit/data/i18n/messages/de-DE/test.php similarity index 100% rename from tests/unit/data/i18n/messages/de_DE/test.php rename to tests/unit/data/i18n/messages/de-DE/test.php diff --git a/tests/unit/data/i18n/messages/en_US/test.php b/tests/unit/data/i18n/messages/en-US/test.php similarity index 100% rename from tests/unit/data/i18n/messages/en_US/test.php rename to tests/unit/data/i18n/messages/en-US/test.php diff --git a/tests/unit/framework/caching/ApcCacheTest.php b/tests/unit/framework/caching/ApcCacheTest.php index adda151..b65ec41 100644 --- a/tests/unit/framework/caching/ApcCacheTest.php +++ b/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."); + } } diff --git a/tests/unit/framework/caching/CacheTestCase.php b/tests/unit/framework/caching/CacheTestCase.php index 849cad0..afd514d 100644 --- a/tests/unit/framework/caching/CacheTestCase.php +++ b/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(); diff --git a/tests/unit/framework/caching/DbCacheTest.php b/tests/unit/framework/caching/DbCacheTest.php index c3c0233..c2d03e2 100644 --- a/tests/unit/framework/caching/DbCacheTest.php +++ b/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')); + } } diff --git a/tests/unit/framework/caching/FileCacheTest.php b/tests/unit/framework/caching/FileCacheTest.php index 70271c9..f102614 100644 --- a/tests/unit/framework/caching/FileCacheTest.php +++ b/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')); + } } diff --git a/tests/unit/framework/caching/MemCacheTest.php b/tests/unit/framework/caching/MemCacheTest.php index e489a39..63f8be1 100644 --- a/tests/unit/framework/caching/MemCacheTest.php +++ b/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(); + } } diff --git a/tests/unit/framework/caching/MemCachedTest.php b/tests/unit/framework/caching/MemCachedTest.php index 57ee110..3a9d415 100644 --- a/tests/unit/framework/caching/MemCachedTest.php +++ b/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(); + } } diff --git a/tests/unit/framework/caching/RedisCacheTest.php b/tests/unit/framework/caching/RedisCacheTest.php index b064f78..3201a49 100644 --- a/tests/unit/framework/caching/RedisCacheTest.php +++ b/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 diff --git a/tests/unit/framework/data/SortTest.php b/tests/unit/framework/data/SortTest.php index abe0031..dca2fcb 100644 --- a/tests/unit/framework/data/SortTest.php +++ b/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' => [ diff --git a/tests/unit/framework/helpers/ArrayHelperTest.php b/tests/unit/framework/helpers/ArrayHelperTest.php index e566cec..6aa2a45 100644 --- a/tests/unit/framework/helpers/ArrayHelperTest.php +++ b/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(); diff --git a/tests/unit/framework/i18n/FallbackMessageFormatterTest.php b/tests/unit/framework/i18n/FallbackMessageFormatterTest.php index 6aa8a22..bdc4e43 100644 --- a/tests/unit/framework/i18n/FallbackMessageFormatterTest.php +++ b/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); } -} \ No newline at end of file +} diff --git a/tests/unit/framework/i18n/FormatterTest.php b/tests/unit/framework/i18n/FormatterTest.php index 622c308..ed5ab33 100644 --- a/tests/unit/framework/i18n/FormatterTest.php +++ b/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() diff --git a/tests/unit/framework/i18n/I18NTest.php b/tests/unit/framework/i18n/I18NTest.php index 65d3d11..aa2356b 100644 --- a/tests/unit/framework/i18n/I18NTest.php +++ b/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; -} \ No newline at end of file +} diff --git a/tests/unit/framework/i18n/MessageFormatterTest.php b/tests/unit/framework/i18n/MessageFormatterTest.php index 7bc8047..1d5d007 100644 --- a/tests/unit/framework/i18n/MessageFormatterTest.php +++ b/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()); } -} \ No newline at end of file +} diff --git a/tests/unit/framework/validators/EmailValidatorTest.php b/tests/unit/framework/validators/EmailValidatorTest.php index eee708d..770914d 100644 --- a/tests/unit/framework/validators/EmailValidatorTest.php +++ b/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 ')); + $this->assertFalse($validator->validateValue('"Carsten Brandt" ')); + $this->assertFalse($validator->validateValue('')); + $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 ')); + $this->assertTrue($validator->validateValue('"Carsten Brandt" ')); + $this->assertTrue($validator->validateValue('')); + $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 ')); + $this->assertFalse($validator->validateValue('John Smith ')); + } + + 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 ')); + $this->assertFalse($validator->validateValue('"Carsten Brandt" ')); + $this->assertFalse($validator->validateValue('')); + + $validator->allowName = true; + + $this->assertTrue($validator->validateValue('info@örtliches.de')); + $this->assertTrue($validator->validateValue('Informtation ')); + $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 ')); + $this->assertTrue($validator->validateValue('"Carsten Brandt" ')); + $this->assertTrue($validator->validateValue('')); + $this->assertTrue($validator->validateValue('test@example.com')); + $this->assertTrue($validator->validateValue('John Smith ')); + $this->assertFalse($validator->validateValue('John Smith ')); } 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 ')); - $this->assertFalse($val->validateValue('John Smith ')); - } }