From 5f8881cfbdb4736a45e8cea7a4a6586831f12013 Mon Sep 17 00:00:00 2001 From: SSiwek Date: Thu, 28 Apr 2016 08:23:20 +0200 Subject: [PATCH 01/90] #11428 (oci) speedup SQL SELECT in findColumns --- framework/CHANGELOG.md | 2 ++ framework/db/oci/Schema.php | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index fc9bc39..d7e567c 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -1,6 +1,8 @@ Yii Framework 2 Change Log ========================== +- Enh #11428: Speedup SQL SELECT in yii\db\oci\Schema\findColumns + 2.0.8 under development ----------------------- diff --git a/framework/db/oci/Schema.php b/framework/db/oci/Schema.php index 48998d8..8dd687a 100644 --- a/framework/db/oci/Schema.php +++ b/framework/db/oci/Schema.php @@ -125,9 +125,9 @@ SELECT a.column_name, a.data_type, a.data_precision, a.data_scale, a.data_length a.nullable, a.data_default, ( SELECT D.constraint_type FROM ALL_CONS_COLUMNS C - inner join ALL_constraints D on D.OWNER = C.OWNER and D.constraint_name = C.constraint_name - WHERE C.OWNER = B.OWNER - and C.table_name = B.object_name + inner join ALL_constraints D on D.OWNER = C.OWNER and d.TABLE_NAME=c.TABLE_NAME and D.constraint_name = C.constraint_name + WHERE C.OWNER = A.OWNER + and C.table_name = A.table_name and C.column_name = A.column_name and D.constraint_type = 'P') as Key, com.comments as column_comment From 752d537998f71a568a035a3ac69e69f2c4a8f22b Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sat, 30 Apr 2016 17:35:55 +0300 Subject: [PATCH 02/90] Fixes #11367: mentioned securing connection with TLS in security best practices --- docs/guide/security-best-practices.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/guide/security-best-practices.md b/docs/guide/security-best-practices.md index abdaa86..3067c00 100644 --- a/docs/guide/security-best-practices.md +++ b/docs/guide/security-best-practices.md @@ -170,3 +170,17 @@ simply rewrite code with what's generated by Gii. Debug toolbar should be avoided at production unless really necessary. It exposes all the application and config details possible. If you absolutely need it check twice that access is properly restricted to your IP only. + +Using secure connection over TLS +-------------------------------- + +Yii provides features that rely on cookies and/or PHP sessions. These can be vulnerable in case your connection is +compromised. The vulnerability is reduced if the app uses secure connection via TLS. + +Please refer to your webserver documentation for instructions on how to configure it. You may also check example configs +provided by H5BP project: + +- [Nginx](https://github.com/h5bp/server-configs-nginx) +- [Apache](https://github.com/h5bp/server-configs-apache). +- [IIS](https://github.com/h5bp/server-configs-iis). +- [Lighttpd](https://github.com/h5bp/server-configs-lighttpd). From 325e416ab9c14c5a85c85c93f3497f76c5c4199b Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sat, 30 Apr 2016 17:54:15 +0300 Subject: [PATCH 03/90] Fixes #9735: minor corrections in nginx config example (cherry picked from commit 26a18aaa8cca60eee83b773dca6a6558c27c361c) --- docs/guide/start-installation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guide/start-installation.md b/docs/guide/start-installation.md index 1b09eeb..d6c7359 100644 --- a/docs/guide/start-installation.md +++ b/docs/guide/start-installation.md @@ -208,7 +208,7 @@ server { location / { # Redirect everything that isn't a real file to index.php - try_files $uri $uri/ /index.php?$args; + try_files $uri $uri/ /index.php$is_args$args; } # uncomment to avoid processing of calls to non-existing static files by Yii @@ -219,7 +219,7 @@ server { location ~ \.php$ { include fastcgi_params; - fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_pass 127.0.0.1:9000; #fastcgi_pass unix:/var/run/php5-fpm.sock; try_files $uri =404; From fc987f48de57cfc490e5330611a86a1e7347c856 Mon Sep 17 00:00:00 2001 From: cartmanchen Date: Fri, 29 Apr 2016 21:11:01 +0800 Subject: [PATCH 04/90] Fixes #11459: Fixed flash messages not destroyed when `session.auto_start = 1` set in php.ini --- framework/CHANGELOG.md | 1 + framework/web/Session.php | 1 + 2 files changed, 2 insertions(+) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 9b1843b..3e818cc 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -4,6 +4,7 @@ Yii Framework 2 Change Log 2.0.9 under development ----------------------- +- Bug #11459: Fixed flash messages not destroyed when `session.auto_start = 1` set in php.ini (cartmanchen) - Enh #11414: Files specified as `null` in `yii\web\AssetBundle` won't be registered (Razzwan) diff --git a/framework/web/Session.php b/framework/web/Session.php index 6af1fb0..41658db 100644 --- a/framework/web/Session.php +++ b/framework/web/Session.php @@ -98,6 +98,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co parent::init(); register_shutdown_function([$this, 'close']); if ($this->getIsActive()) { + $this->updateFlashCounters(); Yii::warning("Session is already started", __METHOD__); } } From 9a842d25e5b87bda74c9f6e376fda3632857b7da Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sat, 30 Apr 2016 19:18:59 +0300 Subject: [PATCH 05/90] Better docs wording as suggested in 752d537998f71a568a035a3ac69e69f2c4a8f22b --- docs/guide/security-best-practices.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/security-best-practices.md b/docs/guide/security-best-practices.md index 3067c00..736a08e 100644 --- a/docs/guide/security-best-practices.md +++ b/docs/guide/security-best-practices.md @@ -175,7 +175,7 @@ Using secure connection over TLS -------------------------------- Yii provides features that rely on cookies and/or PHP sessions. These can be vulnerable in case your connection is -compromised. The vulnerability is reduced if the app uses secure connection via TLS. +compromised. The risk is reduced if the app uses secure connection via TLS. Please refer to your webserver documentation for instructions on how to configure it. You may also check example configs provided by H5BP project: From 707ec2b48cbe7bae16218d72aba037217b67b708 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sat, 30 Apr 2016 20:33:12 +0300 Subject: [PATCH 06/90] Fixes #11243: added note about naming validation rules for later use to validation guide --- docs/guide/input-validation.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/guide/input-validation.md b/docs/guide/input-validation.md index 5a69690..95f6255 100644 --- a/docs/guide/input-validation.md +++ b/docs/guide/input-validation.md @@ -96,6 +96,27 @@ According to the above validation steps, an attribute will be validated if and o an active attribute declared in `scenarios()` and is associated with one or multiple active rules declared in `rules()`. +> Note: It is handy to give names to rules i.e. +> ```php +> public function rules() +> { +> return [ +> // ... +> 'password' => [['password'], 'string', 'max' => 60], +> ]; +> } +> ``` +> +> TYou can use it in a child model: +> +> ```php +> public function rules() +> { +> $rules = parent::rules(); +> unset($rules['passoword']); +> return $rules; +> } + ### Customizing Error Messages From 929d802059bc184cd8c7ec0ad6608c705b9eaa58 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sat, 30 Apr 2016 20:54:15 +0300 Subject: [PATCH 07/90] Added note about where to read/send cookie to the guide --- docs/guide/runtime-sessions-cookies.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/guide/runtime-sessions-cookies.md b/docs/guide/runtime-sessions-cookies.md index 606450a..6c2b3b4 100644 --- a/docs/guide/runtime-sessions-cookies.md +++ b/docs/guide/runtime-sessions-cookies.md @@ -247,6 +247,8 @@ maintain a collection of cookies via the property named `cookies`. The cookie co the cookies submitted in a request, while the cookie collection in the latter represents the cookies that are to be sent to the user. +The part of the application dealing with request and response directly is controller. Therefore, cookies should be +read and sent in controller. ### Reading Cookies From 73803ff28ba9076848df25596c3bcf3316357461 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sat, 30 Apr 2016 21:05:33 +0300 Subject: [PATCH 08/90] Fixes #8422: added note to behaviors explaining that there's no sense in validating attributes there are set automatically --- framework/behaviors/AttributeBehavior.php | 3 +++ framework/behaviors/BlameableBehavior.php | 9 +++++++-- framework/behaviors/SluggableBehavior.php | 8 ++++++-- framework/behaviors/TimestampBehavior.php | 3 +++ 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/framework/behaviors/AttributeBehavior.php b/framework/behaviors/AttributeBehavior.php index f24d306..9931583 100644 --- a/framework/behaviors/AttributeBehavior.php +++ b/framework/behaviors/AttributeBehavior.php @@ -42,6 +42,9 @@ use yii\db\ActiveRecord; * } * ``` * + * Because attribute values will be set automatically, it's a good idea to make sure attribute names aren't + * in `rules()` method of the model. + * * @author Luciano Baraglia * @author Qiang Xue * @since 2.0 diff --git a/framework/behaviors/BlameableBehavior.php b/framework/behaviors/BlameableBehavior.php index 76574e7..0fb5a11 100644 --- a/framework/behaviors/BlameableBehavior.php +++ b/framework/behaviors/BlameableBehavior.php @@ -28,8 +28,13 @@ use yii\db\BaseActiveRecord; * * By default, BlameableBehavior will fill the `created_by` and `updated_by` attributes with the current user ID * when the associated AR object is being inserted; it will fill the `updated_by` attribute - * with the current user ID when the AR object is being updated. If your attribute names are different, you may configure - * the [[createdByAttribute]] and [[updatedByAttribute]] properties like the following: + * with the current user ID when the AR object is being updated. + * + * Because attribute values will be set automatically, it's a good idea to make sure `created_by` and `updated_by` aren't + * in `rules()` method of the model. + * + * If your attribute names are different, you may configure the [[createdByAttribute]] and [[updatedByAttribute]] + * properties like the following: * * ```php * public function behaviors() diff --git a/framework/behaviors/SluggableBehavior.php b/framework/behaviors/SluggableBehavior.php index 3b91e1a..f08ef23 100644 --- a/framework/behaviors/SluggableBehavior.php +++ b/framework/behaviors/SluggableBehavior.php @@ -34,8 +34,12 @@ use Yii; * ``` * * By default, SluggableBehavior will fill the `slug` attribute with a value that can be used a slug in a URL - * when the associated AR object is being validated. If your attribute name is different, you may configure - * the [[slugAttribute]] property like the following: + * when the associated AR object is being validated. + * + * Because attribute value will be set automatically, it's a good idea to make sure `slug` isn't + * in `rules()` method of the model. + * + * If your attribute name is different, you may configure the [[slugAttribute]] property like the following: * * ```php * public function behaviors() diff --git a/framework/behaviors/TimestampBehavior.php b/framework/behaviors/TimestampBehavior.php index e8d7817..5d74518 100644 --- a/framework/behaviors/TimestampBehavior.php +++ b/framework/behaviors/TimestampBehavior.php @@ -30,6 +30,9 @@ use yii\db\BaseActiveRecord; * when the associated AR object is being inserted; it will fill the `updated_at` attribute * with the timestamp when the AR object is being updated. The timestamp value is obtained by `time()`. * + * Because attribute values will be set automatically, it's a good idea to make sure `created_at` and `updated_at` aren't + * in `rules()` method of the model. + * * For the above implementation to work with MySQL database, please declare the columns(`created_at`, `updated_at`) as int(11) for being UNIX timestamp. * * If your attribute names are different or you want to use a different way of calculating the timestamp, From cc40c55a6ba14b30be69e340cb415939981c0070 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sat, 30 Apr 2016 21:23:28 +0300 Subject: [PATCH 09/90] Fixes #6160: Referred to Yii 2.0 cookbook for low level validation-related JavaScript methods reference --- docs/guide/input-validation.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/guide/input-validation.md b/docs/guide/input-validation.md index 95f6255..7f18140 100644 --- a/docs/guide/input-validation.md +++ b/docs/guide/input-validation.md @@ -577,6 +577,10 @@ JS; > ] > ``` +> Tip: If you need to work with client validation manually i.e. dynamically add fields or do some custom UI logic, refer +> to [Working with ActiveForm via JavaScript](https://github.com/samdark/yii2-cookbook/blob/master/book/forms-activeform-js.md) +> in Yii 2.0 Cookbook. + ### Deferred Validation If you need to perform asynchronous client-side validation, you can create [Deferred objects](http://api.jquery.com/category/deferred-object/). From fae0e9430be183f1c868b99ad9e71a4d3cae23f9 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sat, 30 Apr 2016 21:30:54 +0300 Subject: [PATCH 10/90] Fixes #8398: added info about how to deal with twoWord namespaces --- docs/internals/core-code-style.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/internals/core-code-style.md b/docs/internals/core-code-style.md index 4cb1c45..4f50a91 100644 --- a/docs/internals/core-code-style.md +++ b/docs/internals/core-code-style.md @@ -486,3 +486,6 @@ Properties allowing to configure component not to do something should accept val - use lower case - use plural form for nouns which represent objects (e.g. validators) - use singular form for names representing relevant functionality/features (e.g. web) +- prefer single word namespaces +- if single word isn't suitable, use camelCase + From 315d730b475ae591829f4d154e6365b7faa3bfc7 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sat, 30 Apr 2016 21:44:49 +0300 Subject: [PATCH 11/90] Fixes #6922: Added note about overriding widget options specified via DI container --- docs/guide/concept-di-container.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/guide/concept-di-container.md b/docs/guide/concept-di-container.md index 6aa59cf..4b0c201 100644 --- a/docs/guide/concept-di-container.md +++ b/docs/guide/concept-di-container.md @@ -303,6 +303,8 @@ You can still override the value set via DI container, though: echo \yii\widgets\LinkPager::widget(['maxButtonCount' => 20]); ``` +> Tip: no matter which value type it is, it will be overwritten so be careful with option arrays. They won't be merged. + Another example is to take advantage of the automatic constructor injection of the DI container. Assume your controller class depends on some other objects, such as a hotel booking service. You can declare the dependency through a constructor parameter and let the DI container to resolve it for you. From f288747e958f0f0eaf457766710562a7d98089d9 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sat, 30 Apr 2016 21:49:54 +0300 Subject: [PATCH 12/90] Fixes #6904: Added reference to webshell extension from mirations guide --- docs/guide/db-migrations.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/guide/db-migrations.md b/docs/guide/db-migrations.md index 2933f72..d7e59e6 100644 --- a/docs/guide/db-migrations.md +++ b/docs/guide/db-migrations.md @@ -715,6 +715,9 @@ these migrations, it will run the `up()` or `safeUp()` method in every new migra in the order of their timestamp values. If any of the migrations fails, the command will quit without applying the rest of the migrations. +> Tip: In case you don't have command line at your server you may try [web shell](https://github.com/samdark/yii2-webshell) +> extension. + For each migration that has been successfully applied, the command will insert a row into a database table named `migration` to record the successful application of the migration. This will allow the migration tool to identify which migrations have been applied and which have not. From 9f1c7ba5326223f67c522cd17287b031cd92b9d4 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sat, 30 Apr 2016 22:17:00 +0300 Subject: [PATCH 13/90] Fixed typo --- docs/guide/input-validation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/input-validation.md b/docs/guide/input-validation.md index 7f18140..68f05aa 100644 --- a/docs/guide/input-validation.md +++ b/docs/guide/input-validation.md @@ -107,7 +107,7 @@ declared in `rules()`. > } > ``` > -> TYou can use it in a child model: +> You can use it in a child model: > > ```php > public function rules() From 63cac32fbc881985d171a89204318a573b4d265d Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sun, 1 May 2016 00:19:55 +0300 Subject: [PATCH 14/90] Cleaned up security guide --- docs/guide/README.md | 3 + docs/guide/security-best-practices.md | 37 ++++++++++ docs/guide/security-cryptography.md | 66 ++++++++++++++++++ docs/guide/security-overview.md | 14 ++++ docs/guide/security-passwords.md | 125 +++------------------------------- 5 files changed, 128 insertions(+), 117 deletions(-) create mode 100644 docs/guide/security-cryptography.md create mode 100644 docs/guide/security-overview.md diff --git a/docs/guide/README.md b/docs/guide/README.md index 4d34f79..a7d886a 100644 --- a/docs/guide/README.md +++ b/docs/guide/README.md @@ -109,9 +109,12 @@ Displaying Data Security -------- +* [Overview](security-overview.md) * [Authentication](security-authentication.md) * [Authorization](security-authorization.md) * [Working with Passwords](security-passwords.md) +* [Cryptography](security-cryptography.md) +* [Views security](structure-views.md#security) * [Auth Clients](https://github.com/yiisoft/yii2-authclient/blob/master/docs/guide/README.md) * [Best Practices](security-best-practices.md) diff --git a/docs/guide/security-best-practices.md b/docs/guide/security-best-practices.md index 736a08e..87a3231 100644 --- a/docs/guide/security-best-practices.md +++ b/docs/guide/security-best-practices.md @@ -148,6 +148,43 @@ In order to avoid CSRF you should always: 1. Follow HTTP specification i.e. GET should not change application state. 2. Keep Yii CSRF protection enabled. +Sometimes you need to disable CSRF validation per controller and/or action. It could be achieved by setting its property: + +```php +namespace app\controllers; + +use yii\web\Controller; + +class SiteController extends Controller +{ + public $enableCsrfValidation = false; + + public function actionIndex() + { + // CSRF validation will not be applied to this and other actions + } + +} +``` + +To disable CSRF validation per custom actions you can do: + +```php +namespace app\controllers; + +use yii\web\Controller; + +class SiteController extends Controller +{ + public function beforeAction($action) + { + // ...set `$this->enableCsrfValidation` here based on some conditions... + // call parent method that will check CSRF if such property is true. + return parent::beforeAction($action); + } +} +``` + Avoiding file exposure ---------------------- diff --git a/docs/guide/security-cryptography.md b/docs/guide/security-cryptography.md new file mode 100644 index 0000000..b5818f9 --- /dev/null +++ b/docs/guide/security-cryptography.md @@ -0,0 +1,66 @@ +Cryptography +============ + +In this section we'll review the following security aspects: + +- Generating random data +- Encryption and Decryption +- Confirming Data Integrity + +Generating Pseudorandom Data +---------------------------- + +Pseudorandom data is useful in many situations. For example when resetting a password via email you need to generate a +token, save it to the database, and send it via email to end user which in turn will allow them to prove ownership of +that account. It is very important that this token be unique and hard to guess, else there is a possibility that attacker +can predict the token's value and reset the user's password. + +Yii security helper makes generating pseudorandom data simple: + + +```php +$key = Yii::$app->getSecurity()->generateRandomString(); +``` + +Encryption and Decryption +------------------------- + +Yii provides convenient helper functions that allow you to encrypt/decrypt data using a secret key. The data is passed through the encryption function so that only the person which has the secret key will be able to decrypt it. +For example, we need to store some information in our database but we need to make sure only the user who has the secret key can view it (even if the application database is compromised): + + +```php +// $data and $secretKey are obtained from the form +$encryptedData = Yii::$app->getSecurity()->encryptByPassword($data, $secretKey); +// store $encryptedData to database +``` + +Subsequently when user wants to read the data: + +```php +// $secretKey is obtained from user input, $encryptedData is from the database +$data = Yii::$app->getSecurity()->decryptByPassword($encryptedData, $secretKey); +``` + +It's also possible to use key instead of password via [[\yii\base\Security::encryptByKey()]] and +[[\yii\base\Security::decryptByKey()]]. + +Confirming Data Integrity +------------------------- + +There are situations in which you need to verify that your data hasn't been tampered with by a third party or even corrupted in some way. Yii provides an easy way to confirm data integrity in the form of two helper functions. + +Prefix the data with a hash generated from the secret key and data + + +```php +// $secretKey our application or user secret, $genuineData obtained from a reliable source +$data = Yii::$app->getSecurity()->hashData($genuineData, $secretKey); +``` + +Checks if the data integrity has been compromised + +```php +// $secretKey our application or user secret, $data obtained from an unreliable source +$data = Yii::$app->getSecurity()->validateData($data, $secretKey); +``` diff --git a/docs/guide/security-overview.md b/docs/guide/security-overview.md new file mode 100644 index 0000000..7903a10 --- /dev/null +++ b/docs/guide/security-overview.md @@ -0,0 +1,14 @@ +Security +======== + +Good security is vital to the health and success of any application. Unfortunately, many developers cut corners when it +comes to security, either due to a lack of understanding or because implementation is too much of a hurdle. To make your +Yii powered application as secure as possible, Yii has included several excellent and easy to use security features. + +* [Authentication](security-authentication.md) +* [Authorization](security-authorization.md) +* [Working with Passwords](security-passwords.md) +* [Cryptography](security-cryptography.md) +* [Views security](structure-views.md#security) +* [Auth Clients](https://github.com/yiisoft/yii2-authclient/blob/master/docs/guide/README.md) +* [Best Practices](security-best-practices.md) diff --git a/docs/guide/security-passwords.md b/docs/guide/security-passwords.md index 5adef68..2c18ed7 100644 --- a/docs/guide/security-passwords.md +++ b/docs/guide/security-passwords.md @@ -1,17 +1,14 @@ Working with Passwords -======== +====================== -> Note: This section is under development. +Most developers know that passwords cannot be stored in plain text, but many developers believe it's still safe to hash +passwords using `md5` or `sha1`. There was a time when using the aforementioned hashing algorithms was sufficient, +but modern hardware makes it possible to reverse such hashes and even stronger ones very quickly using brute force attacks. -Good security is vital to the health and success of any application. Unfortunately, many developers cut corners when it comes to security, either due to a lack of understanding or because implementation is too much of a hurdle. To make your Yii powered application as secure as possible, Yii has included several excellent and easy to use security features. - - -Hashing and Verifying Passwords -------------------------------- - -Most developers know that passwords cannot be stored in plain text, but many developers believe it's still safe to hash passwords using `md5` or `sha1`. There was a time when using the aforementioned hashing algorithms was sufficient, but modern hardware makes it possible to reverse such hashes very quickly using brute force attacks. - -In order to provide increased security for user passwords, even in the worst case scenario (your application is breached), you need to use a hashing algorithm that is resilient against brute force attacks. The best current choice is `bcrypt`. In PHP, you can create a `bcrypt` hash using the [crypt function](http://php.net/manual/en/function.crypt.php). Yii provides two helper functions which make using `crypt` to securely generate and verify hashes easier. +In order to provide increased security for user passwords, even in the worst case scenario (your application is breached), +you need to use a hashing algorithm that is resilient against brute force attacks. The best current choice is `bcrypt`. +In PHP, you can create a `bcrypt` hash using the [crypt function](http://php.net/manual/en/function.crypt.php). Yii provides +two helper functions which make using `crypt` to securely generate and verify hashes easier. When a user provides a password for the first time (e.g., upon registration), the password needs to be hashed: @@ -32,109 +29,3 @@ if (Yii::$app->getSecurity()->validatePassword($password, $hash)) { // wrong password } ``` - -Generating Pseudorandom Data ------------ - -Pseudorandom data is useful in many situations. For example when resetting a password via email you need to generate a token, save it to the database, and send it via email to end user which in turn will allow them to prove ownership of that account. It is very important that this token be unique and hard to guess, else there is a possibility that attacker can predict the token's value and reset the user's password. - -Yii security helper makes generating pseudorandom data simple: - - -```php -$key = Yii::$app->getSecurity()->generateRandomString(); -``` - -Note that you need to have the `openssl` extension installed in order to generate cryptographically secure random data. - -Encryption and Decryption -------------------------- - -Yii provides convenient helper functions that allow you to encrypt/decrypt data using a secret key. The data is passed through the encryption function so that only the person which has the secret key will be able to decrypt it. -For example, we need to store some information in our database but we need to make sure only the user who has the secret key can view it (even if the application database is compromised): - - -```php -// $data and $secretKey are obtained from the form -$encryptedData = Yii::$app->getSecurity()->encryptByPassword($data, $secretKey); -// store $encryptedData to database -``` - -Subsequently when user wants to read the data: - -```php -// $secretKey is obtained from user input, $encryptedData is from the database -$data = Yii::$app->getSecurity()->decryptByPassword($encryptedData, $secretKey); -``` - -Confirming Data Integrity --------------------------------- - -There are situations in which you need to verify that your data hasn't been tampered with by a third party or even corrupted in some way. Yii provides an easy way to confirm data integrity in the form of two helper functions. - -Prefix the data with a hash generated from the secret key and data - - -```php -// $secretKey our application or user secret, $genuineData obtained from a reliable source -$data = Yii::$app->getSecurity()->hashData($genuineData, $secretKey); -``` - -Checks if the data integrity has been compromised - -```php -// $secretKey our application or user secret, $data obtained from an unreliable source -$data = Yii::$app->getSecurity()->validateData($data, $secretKey); -``` - - -todo: XSS prevention, CSRF prevention, cookie protection, refer to 1.1 guide - -You also can disable CSRF validation per controller and/or action, by setting its property: - -```php -namespace app\controllers; - -use yii\web\Controller; - -class SiteController extends Controller -{ - public $enableCsrfValidation = false; - - public function actionIndex() - { - // CSRF validation will not be applied to this and other actions - } - -} -``` - -To disable CSRF validation per custom actions you can do: - -```php -namespace app\controllers; - -use yii\web\Controller; - -class SiteController extends Controller -{ - public function beforeAction($action) - { - // ...set `$this->enableCsrfValidation` here based on some conditions... - // call parent method that will check CSRF if such property is true. - return parent::beforeAction($action); - } -} -``` - -Securing Cookies ----------------- - -- validation -- httpOnly is default - -See also --------- - -- [Views security](structure-views.md#security) - From 9e7d5041cc0f98d135959cd0b46e0aa2331d124f Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Sun, 1 May 2016 00:51:19 +0300 Subject: [PATCH 15/90] Fixes #2022: Added info about `yii\db\Query::andFilterCompare()` to guide --- docs/guide/db-query-builder.md | 14 ++++++++++++++ docs/guide/output-data-widgets.md | 4 +++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/docs/guide/db-query-builder.md b/docs/guide/db-query-builder.md index 44b6123..cbd921e 100644 --- a/docs/guide/db-query-builder.md +++ b/docs/guide/db-query-builder.md @@ -351,6 +351,20 @@ Like [[yii\db\Query::andWhere()|andWhere()]] and [[yii\db\Query::orWhere()|orWhe [[yii\db\Query::andFilterWhere()|andFilterWhere()]] and [[yii\db\Query::orFilterWhere()|orFilterWhere()]] to append additional filter conditions to the existing one. +Additionally, there is [[yii\db\Query::andFilterCompare()]] that can intelligently determine operator based on what's +in the value: + +```php +$query->andFilterCompare('name', 'John Doe'); +$query->andFilterCompare('rating', '>9'); +$query->andFilterCompare('value', '<=100'); +``` + +You can also specify operator explicitly: + +```php +$query->andFilterCompare('name', 'Doe', 'like'); +``` ### [[yii\db\Query::orderBy()|orderBy()]] diff --git a/docs/guide/output-data-widgets.md b/docs/guide/output-data-widgets.md index 2827fde..bfae3c9 100644 --- a/docs/guide/output-data-widgets.md +++ b/docs/guide/output-data-widgets.md @@ -395,9 +395,11 @@ class PostSearch extends Post return $dataProvider; } } - ``` +> Tip: See [Query Builder](db-query-builder.md) and especially [Filter Conditions](db-query-builder.md#filter-conditions) +> to learn how to build filtering query. + You can use this function in the controller to get the dataProvider for the GridView: ```php From 91734ae83bb7d5afae143c1f569fa8cabcd7a802 Mon Sep 17 00:00:00 2001 From: dasmfm <2@borisklimenko.ru> Date: Wed, 27 Apr 2016 14:03:55 +0300 Subject: [PATCH 16/90] Fixes #11432: Added HTTP status 421 "Misdirected Request" to list of statuses in `yii\web\Response` Source: http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml --- framework/CHANGELOG.md | 1 + framework/web/Response.php | 1 + 2 files changed, 2 insertions(+) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 3e818cc..265cfc5 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -6,6 +6,7 @@ Yii Framework 2 Change Log - Bug #11459: Fixed flash messages not destroyed when `session.auto_start = 1` set in php.ini (cartmanchen) - Enh #11414: Files specified as `null` in `yii\web\AssetBundle` won't be registered (Razzwan) +- Eng #11432: Added HTTP status 421 "Misdirected Request" to list of statuses in `yii\web\Response` (dasmfm) 2.0.8 April 28, 2016 diff --git a/framework/web/Response.php b/framework/web/Response.php index 63f842f..2cb5de6 100644 --- a/framework/web/Response.php +++ b/framework/web/Response.php @@ -204,6 +204,7 @@ class Response extends \yii\base\Response 416 => 'Requested range unsatisfiable', 417 => 'Expectation failed', 418 => 'I\'m a teapot', + 421 => 'Misdirected Request', 422 => 'Unprocessable entity', 423 => 'Locked', 424 => 'Method failure', From e0fd874992c838364ac18ca140ab81c2a22aeff0 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Sun, 1 May 2016 04:42:29 +0200 Subject: [PATCH 17/90] cleanup CHANGELOG --- framework/CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 265cfc5..548a50d 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -4,9 +4,9 @@ Yii Framework 2 Change Log 2.0.9 under development ----------------------- -- Bug #11459: Fixed flash messages not destroyed when `session.auto_start = 1` set in php.ini (cartmanchen) - Enh #11414: Files specified as `null` in `yii\web\AssetBundle` won't be registered (Razzwan) -- Eng #11432: Added HTTP status 421 "Misdirected Request" to list of statuses in `yii\web\Response` (dasmfm) +- Enh #11432: Added HTTP status 421 "Misdirected Request" to list of statuses in `yii\web\Response` (dasmfm) +- Bug #11459: Fixed flash messages not destroyed when `session.auto_start = 1` set in php.ini (cartmanchen) 2.0.8 April 28, 2016 From deb89177eb66c894a4c342da5ce8831419c37d44 Mon Sep 17 00:00:00 2001 From: SilverFire - Dmitry Naumenko Date: Sun, 1 May 2016 10:02:35 +0300 Subject: [PATCH 18/90] Fixed docs typo --- docs/guide/input-validation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/input-validation.md b/docs/guide/input-validation.md index 68f05aa..6893ad9 100644 --- a/docs/guide/input-validation.md +++ b/docs/guide/input-validation.md @@ -113,7 +113,7 @@ declared in `rules()`. > public function rules() > { > $rules = parent::rules(); -> unset($rules['passoword']); +> unset($rules['password']); > return $rules; > } From 37380f785d72b9068b5661be77be08619fc796c1 Mon Sep 17 00:00:00 2001 From: SilverFire - Dmitry Naumenko Date: Sun, 1 May 2016 15:52:32 +0300 Subject: [PATCH 19/90] Updated CHANGELOG --- framework/CHANGELOG.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index f8c3a8b..57ccc4b 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -1,12 +1,11 @@ Yii Framework 2 Change Log ========================== -2.0.9 under development -- Enh #11428: Speedup SQL SELECT in yii\db\oci\Schema\findColumns -2.0.8 under development +2.0.9 under development ----------------------- +- Enh #11428: Speedup SQL query in `yii\db\oci\Schema::findColumns()` (SSiwek) - Enh #11414: Files specified as `null` in `yii\web\AssetBundle` won't be registered (Razzwan) - Enh #11432: Added HTTP status 421 "Misdirected Request" to list of statuses in `yii\web\Response` (dasmfm) - Bug #11459: Fixed flash messages not destroyed when `session.auto_start = 1` set in php.ini (cartmanchen) From 69af09f2a4579d3371789b711248f0064b00014c Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Sun, 1 May 2016 15:06:57 +0200 Subject: [PATCH 20/90] added a note about the scope of indexBy() close #11446 --- docs/guide/db-query-builder.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/guide/db-query-builder.md b/docs/guide/db-query-builder.md index cbd921e..9117be6 100644 --- a/docs/guide/db-query-builder.md +++ b/docs/guide/db-query-builder.md @@ -634,6 +634,12 @@ $query = (new \yii\db\Query()) The anonymous function takes a parameter `$row` which contains the current row data and should return a scalar value which will be used as the index value for the current row. +> Note: In contrast to query methods like [[yii\db\Query::groupBy()|groupBy()]] or [[yii\db\Query::orderBy()|orderBy()]] +> which are converted to SQL and are part of the query, this method works after the data has been fetched from the database. +> That means that only those column names can be used that have been part of SELECT in your query. +> Also if you selected a column with table prefix, e.g. `customer.id`, the result set will only contain `id` so you have to call +> `->indexBy('id')` without table prefix. + ### Batch Query From 5fc46d900b12b5e5ad4defaf0cf8ba890399d6bb Mon Sep 17 00:00:00 2001 From: SSiwek Date: Tue, 3 May 2016 08:03:33 +0200 Subject: [PATCH 21/90] Moved setting of isPrimaryKey #11484 --- framework/CHANGELOG.md | 1 + framework/db/oci/Schema.php | 34 ++++++++++++++++------------------ 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 57ccc4b..b4fe9ce 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -5,6 +5,7 @@ Yii Framework 2 Change Log 2.0.9 under development ----------------------- +- Enh #11484: moved "P" "isPrimaryKey" in `yii\db\oci\Schema` to findConstraints (SSiwek) - Enh #11428: Speedup SQL query in `yii\db\oci\Schema::findColumns()` (SSiwek) - Enh #11414: Files specified as `null` in `yii\web\AssetBundle` won't be registered (Razzwan) - Enh #11432: Added HTTP status 421 "Misdirected Request" to list of statuses in `yii\web\Response` (dasmfm) diff --git a/framework/db/oci/Schema.php b/framework/db/oci/Schema.php index 8dd687a..3481122 100644 --- a/framework/db/oci/Schema.php +++ b/framework/db/oci/Schema.php @@ -123,13 +123,6 @@ class Schema extends \yii\db\Schema $sql = <<createColumn($column); $table->columns[$c->name] = $c; - if ($c->isPrimaryKey) { - $table->primaryKey[] = $c->name; - $table->sequenceName = $this->getTableSequenceName($table->name); - } } return true; } @@ -223,9 +212,8 @@ SQL; $c = $this->createColumnSchema(); $c->name = $column['COLUMN_NAME']; $c->allowNull = $column['NULLABLE'] === 'Y'; - $c->isPrimaryKey = strpos($column['KEY'], 'P') !== false; $c->comment = $column['COLUMN_COMMENT'] === null ? '' : $column['COLUMN_COMMENT']; - + $c->isPrimaryKey = false; $this->extractColumnType($c, $column['DATA_TYPE'], $column['DATA_PRECISION'], $column['DATA_SCALE'], $column['DATA_LENGTH']); $this->extractColumnSize($c, $column['DATA_TYPE'], $column['DATA_PRECISION'], $column['DATA_SCALE'], $column['DATA_LENGTH']); @@ -283,11 +271,21 @@ SQL; if ($this->db->slavePdo->getAttribute(\PDO::ATTR_CASE) === \PDO::CASE_LOWER) { $row = array_change_key_case($row, CASE_UPPER); } - if ($row['CONSTRAINT_TYPE'] !== 'R') { - // this condition is not checked in SQL WHERE because of an Oracle Bug: - // see https://github.com/yiisoft/yii2/pull/8844 - continue; + + if ($row['CONSTRAINT_TYPE'] === 'P') { + $table->columns[$row['COLUMN_NAME']]->isPrimaryKey = true; + $table->primaryKey[] = $row['COLUMN_NAME']; + if (empty($table->sequenceName )) { + $table->sequenceName = $this->getTableSequenceName($table->name); + } } + + if ($row['CONSTRAINT_TYPE'] !== 'R') { + // this condition is not checked in SQL WHERE because of an Oracle Bug: + // see https://github.com/yiisoft/yii2/pull/8844 + continue; + } + $name = $row['CONSTRAINT_NAME']; if (!isset($constraints[$name])) { $constraints[$name] = [ @@ -494,4 +492,4 @@ SQL; return $result; } -} +} \ No newline at end of file From 7dc9d07bcd4ebf4562bddae793b66011c9418850 Mon Sep 17 00:00:00 2001 From: mdmunir Date: Thu, 28 Apr 2016 07:25:54 +0700 Subject: [PATCH 22/90] Changable default flavor for Markdown helper close #11438 --- framework/CHANGELOG.md | 1 + framework/helpers/BaseMarkdown.php | 12 +++++++++--- tests/framework/helpers/MarkdownTest.php | 31 +++++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 tests/framework/helpers/MarkdownTest.php diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 57ccc4b..fb415b4 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -8,6 +8,7 @@ Yii Framework 2 Change Log - Enh #11428: Speedup SQL query in `yii\db\oci\Schema::findColumns()` (SSiwek) - Enh #11414: Files specified as `null` in `yii\web\AssetBundle` won't be registered (Razzwan) - Enh #11432: Added HTTP status 421 "Misdirected Request" to list of statuses in `yii\web\Response` (dasmfm) +- Enh #11438: Configurable `yii\helpers\Markdown` default flavor (mdmunir) - Bug #11459: Fixed flash messages not destroyed when `session.auto_start = 1` set in php.ini (cartmanchen) diff --git a/framework/helpers/BaseMarkdown.php b/framework/helpers/BaseMarkdown.php index 07ed299..0c50213 100644 --- a/framework/helpers/BaseMarkdown.php +++ b/framework/helpers/BaseMarkdown.php @@ -55,10 +55,11 @@ class BaseMarkdown * * @param string $markdown the markdown text to parse * @param string $flavor the markdown flavor to use. See [[$flavors]] for available values. + * Defaults to [[$defaultFlavor]], if not set. * @return string the parsed HTML output * @throws \yii\base\InvalidParamException when an undefined flavor is given. */ - public static function process($markdown, $flavor = 'original') + public static function process($markdown, $flavor = null) { $parser = static::getParser($flavor); @@ -72,10 +73,11 @@ class BaseMarkdown * * @param string $markdown the markdown text to parse * @param string $flavor the markdown flavor to use. See [[$flavors]] for available values. + * Defaults to [[$defaultFlavor]], if not set. * @return string the parsed HTML output * @throws \yii\base\InvalidParamException when an undefined flavor is given. */ - public static function processParagraph($markdown, $flavor = 'original') + public static function processParagraph($markdown, $flavor = null) { $parser = static::getParser($flavor); @@ -83,12 +85,16 @@ class BaseMarkdown } /** - * @param string $flavor + * @param string $flavor the markdown flavor to use. See [[$flavors]] for available values. + * Defaults to [[$defaultFlavor]], if not set. * @return \cebe\markdown\Parser * @throws \yii\base\InvalidParamException when an undefined flavor is given. */ protected static function getParser($flavor) { + if ($flavor === null) { + $flavor = static::$defaultFlavor; + } /* @var $parser \cebe\markdown\Markdown */ if (!isset(static::$flavors[$flavor])) { throw new InvalidParamException("Markdown flavor '$flavor' is not defined.'"); diff --git a/tests/framework/helpers/MarkdownTest.php b/tests/framework/helpers/MarkdownTest.php new file mode 100644 index 0000000..af83c62 --- /dev/null +++ b/tests/framework/helpers/MarkdownTest.php @@ -0,0 +1,31 @@ + + * @group helpers + */ +class MarkdownTest extends TestCase +{ + public function testOriginalFlavor() + { + $text = <<assertEquals(Markdown::process($text), Markdown::process($text, 'original')); + + Markdown::$defaultFlavor = 'gfm-comment'; + $this->assertNotEquals(Markdown::process($text), Markdown::process($text, 'original')); + $this->assertEquals(Markdown::process($text), Markdown::process($text, 'gfm-comment')); + } +} From 7f3511a908b333be4382b77e4b8022c30a88dd9c Mon Sep 17 00:00:00 2001 From: PowerGamer1 Date: Tue, 3 May 2016 12:29:25 +0300 Subject: [PATCH 23/90] Update ArrayDataProvider.php Update DataColumn.php Update CHANGELOG.md --- framework/CHANGELOG.md | 1 + framework/data/ArrayDataProvider.php | 6 +++++- framework/grid/DataColumn.php | 5 +++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index fb415b4..3966300 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -5,6 +5,7 @@ Yii Framework 2 Change Log 2.0.9 under development ----------------------- +- Enh #4146: Added `yii\data\ArrayDataProvider::$modelClass` property to specify a model used to provide column labels even when data array is empty (PowerGamer1) - Enh #11428: Speedup SQL query in `yii\db\oci\Schema::findColumns()` (SSiwek) - Enh #11414: Files specified as `null` in `yii\web\AssetBundle` won't be registered (Razzwan) - Enh #11432: Added HTTP status 421 "Misdirected Request" to list of statuses in `yii\web\Response` (dasmfm) diff --git a/framework/data/ArrayDataProvider.php b/framework/data/ArrayDataProvider.php index f26023a..5fdc75a 100644 --- a/framework/data/ArrayDataProvider.php +++ b/framework/data/ArrayDataProvider.php @@ -63,7 +63,11 @@ class ArrayDataProvider extends BaseDataProvider * The array elements must use zero-based integer keys. */ public $allModels; - + /** + * @var string the name of the \yii\base\Model based class used to provide column labels. + * @since 2.0.9 + */ + public $modelClass; /** * @inheritdoc diff --git a/framework/grid/DataColumn.php b/framework/grid/DataColumn.php index e10234c..9524ac3 100644 --- a/framework/grid/DataColumn.php +++ b/framework/grid/DataColumn.php @@ -9,6 +9,7 @@ namespace yii\grid; use yii\base\Model; use yii\data\ActiveDataProvider; +use yii\data\ArrayDataProvider; use yii\db\ActiveQueryInterface; use yii\helpers\ArrayHelper; use yii\helpers\Html; @@ -143,6 +144,10 @@ class DataColumn extends Column /* @var $model Model */ $model = new $provider->query->modelClass; $label = $model->getAttributeLabel($this->attribute); + } else if($provider instanceof ArrayDataProvider && $provider->modelClass !== null) { + /* @var $model Model */ + $model = new $provider->modelClass; + $label = $model->getAttributeLabel($this->attribute); } else { $models = $provider->getModels(); if (($model = reset($models)) instanceof Model) { From aaf6c844fb3f64155b81896769b351eec7726636 Mon Sep 17 00:00:00 2001 From: SilverFire - Dmitry Naumenko Date: Tue, 3 May 2016 14:46:45 +0300 Subject: [PATCH 24/90] Added DataColumnTest --- tests/framework/grid/DataColumnTest.php | 44 +++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 tests/framework/grid/DataColumnTest.php diff --git a/tests/framework/grid/DataColumnTest.php b/tests/framework/grid/DataColumnTest.php new file mode 100644 index 0000000..b9eb04e --- /dev/null +++ b/tests/framework/grid/DataColumnTest.php @@ -0,0 +1,44 @@ + + * + * @group grid + */ +class DataColumnTest extends \yiiunit\TestCase +{ + public function setUp() + { + parent::setUp(); + $this->mockApplication(); + } + + public function testColumnLabelsOnEmptyProvider() + { + $grid = new GridView([ + 'dataProvider' => new ArrayDataProvider([ + 'allModels' => [], + 'totalCount' => 0, + 'modelClass' => Order::className() + ]), + 'columns' => ['customer_id', 'total'] + ]); + + $labels = []; + foreach ($grid->columns as $column) { + $method = new \ReflectionMethod($column, 'getHeaderCellLabel'); + $method->setAccessible(true); + $labels[] = $method->invoke($column); + $method->setAccessible(false); + } + + $this->assertEquals(['Customer', 'Invoice Total'], $labels); + } +} From 030913e7f0a69925a1ad60bcc245f6d0716b2b8a Mon Sep 17 00:00:00 2001 From: SilverFire - Dmitry Naumenko Date: Tue, 3 May 2016 14:50:59 +0300 Subject: [PATCH 25/90] Fixed code style and indentations --- framework/data/ArrayDataProvider.php | 11 ++++++----- framework/grid/DataColumn.php | 8 ++++---- tests/framework/data/ArrayDataProviderTest.php | 2 +- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/framework/data/ArrayDataProvider.php b/framework/data/ArrayDataProvider.php index 5fdc75a..4e3a333 100644 --- a/framework/data/ArrayDataProvider.php +++ b/framework/data/ArrayDataProvider.php @@ -63,11 +63,12 @@ class ArrayDataProvider extends BaseDataProvider * The array elements must use zero-based integer keys. */ public $allModels; - /** - * @var string the name of the \yii\base\Model based class used to provide column labels. - * @since 2.0.9 - */ - public $modelClass; + /** + * @var string the name of the [[yii\base\Model|Model]] class that will be represented. + * This property is used to get columns' names. + * @since 2.0.9 + */ + public $modelClass; /** * @inheritdoc diff --git a/framework/grid/DataColumn.php b/framework/grid/DataColumn.php index 9524ac3..8eeea14 100644 --- a/framework/grid/DataColumn.php +++ b/framework/grid/DataColumn.php @@ -144,10 +144,10 @@ class DataColumn extends Column /* @var $model Model */ $model = new $provider->query->modelClass; $label = $model->getAttributeLabel($this->attribute); - } else if($provider instanceof ArrayDataProvider && $provider->modelClass !== null) { - /* @var $model Model */ - $model = new $provider->modelClass; - $label = $model->getAttributeLabel($this->attribute); + } else if ($provider instanceof ArrayDataProvider && $provider->modelClass !== null) { + /* @var $model Model */ + $model = new $provider->modelClass; + $label = $model->getAttributeLabel($this->attribute); } else { $models = $provider->getModels(); if (($model = reset($models)) instanceof Model) { diff --git a/tests/framework/data/ArrayDataProviderTest.php b/tests/framework/data/ArrayDataProviderTest.php index 2a6290d..e883784 100644 --- a/tests/framework/data/ArrayDataProviderTest.php +++ b/tests/framework/data/ArrayDataProviderTest.php @@ -179,4 +179,4 @@ class ArrayDataProviderTest extends TestCase $dataProvider = new ArrayDataProvider(['allModels' => $mixedArray, 'pagination' => $pagination]); $this->assertEquals(['key1', 9], $dataProvider->getKeys()); } -} \ No newline at end of file +} From 53e1018648af6b79d429687ce685c5d7eb6fd054 Mon Sep 17 00:00:00 2001 From: SilverFire - Dmitry Naumenko Date: Tue, 3 May 2016 15:01:33 +0300 Subject: [PATCH 26/90] Updated `yii\grid\DataColumn::getHeaderCellLabel()` to extract attribute label from the `filterModel` of Grid Closes #9950 --- framework/CHANGELOG.md | 3 ++- framework/grid/DataColumn.php | 2 ++ tests/framework/grid/DataColumnTest.php | 24 +++++++++++++++++++++++- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 3966300..b7668f8 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -5,7 +5,8 @@ Yii Framework 2 Change Log 2.0.9 under development ----------------------- -- Enh #4146: Added `yii\data\ArrayDataProvider::$modelClass` property to specify a model used to provide column labels even when data array is empty (PowerGamer1) +- Enh #11490: Added `yii\data\ArrayDataProvider::$modelClass` property to specify a model used to provide column labels even when data array is empty (PowerGamer1) +- Bug #9950: Updated `yii\grid\DataColumn::getHeaderCellLabel()` to extract attribute label from the `filterModel` of Grid (silverfire) - Enh #11428: Speedup SQL query in `yii\db\oci\Schema::findColumns()` (SSiwek) - Enh #11414: Files specified as `null` in `yii\web\AssetBundle` won't be registered (Razzwan) - Enh #11432: Added HTTP status 421 "Misdirected Request" to list of statuses in `yii\web\Response` (dasmfm) diff --git a/framework/grid/DataColumn.php b/framework/grid/DataColumn.php index 8eeea14..dd06283 100644 --- a/framework/grid/DataColumn.php +++ b/framework/grid/DataColumn.php @@ -148,6 +148,8 @@ class DataColumn extends Column /* @var $model Model */ $model = new $provider->modelClass; $label = $model->getAttributeLabel($this->attribute); + } else if ($this->grid->filterModel !== null && $this->grid->filterModel instanceof Model) { + $label = $this->grid->filterModel->getAttributeLabel($this->attribute); } else { $models = $provider->getModels(); if (($model = reset($models)) instanceof Model) { diff --git a/tests/framework/grid/DataColumnTest.php b/tests/framework/grid/DataColumnTest.php index b9eb04e..f39286b 100644 --- a/tests/framework/grid/DataColumnTest.php +++ b/tests/framework/grid/DataColumnTest.php @@ -20,7 +20,7 @@ class DataColumnTest extends \yiiunit\TestCase $this->mockApplication(); } - public function testColumnLabelsOnEmptyProvider() + public function testColumnLabelsOnEmptyArrayProvider() { $grid = new GridView([ 'dataProvider' => new ArrayDataProvider([ @@ -41,4 +41,26 @@ class DataColumnTest extends \yiiunit\TestCase $this->assertEquals(['Customer', 'Invoice Total'], $labels); } + + public function testColumnLabelsOnEmptyArrayProviderWithFilterModel() + { + $grid = new GridView([ + 'dataProvider' => new ArrayDataProvider([ + 'allModels' => [], + 'totalCount' => 0, + ]), + 'columns' => ['customer_id', 'total'], + 'filterModel' => new Order + ]); + + $labels = []; + foreach ($grid->columns as $column) { + $method = new \ReflectionMethod($column, 'getHeaderCellLabel'); + $method->setAccessible(true); + $labels[] = $method->invoke($column); + $method->setAccessible(false); + } + + $this->assertEquals(['Customer', 'Invoice Total'], $labels); + } } From 622676b9f91e5f63bf37a034ad3c376f60d8d39c Mon Sep 17 00:00:00 2001 From: Giorgio Giudetti Date: Thu, 5 May 2016 14:32:46 +0200 Subject: [PATCH 27/90] Updated translation (#11510) --- framework/messages/it/yii.php | 108 +++++++++++++++++++++++++----------------- 1 file changed, 64 insertions(+), 44 deletions(-) diff --git a/framework/messages/it/yii.php b/framework/messages/it/yii.php index 453c9e9..524a539 100644 --- a/framework/messages/it/yii.php +++ b/framework/messages/it/yii.php @@ -2,7 +2,7 @@ /** * Message translations. * - * This file is automatically generated by 'yii message' command. + * This file is automatically generated by 'yii message/extract' command. * It contains the localizable messages extracted from source code. * You may modify this file by translating the extracted messages. * @@ -17,43 +17,32 @@ * NOTE: this file must be saved in UTF-8 encoding. */ return [ - 'Only files with these MIME types are allowed: {mimeTypes}.' => 'Solo i file con questi tipi MIME sono consentiti: {mimeTypes}.', - 'The requested view "{name}" was not found.' => 'La vista "{name}" richiesta non è stata trovata.', - 'in {delta, plural, =1{a day} other{# days}}' => 'in {delta, plural, =1{un giorno} other{# giorni}}', - 'in {delta, plural, =1{a minute} other{# minutes}}' => 'in {delta, plural, =1{un minuto} other{# minuti}}', - 'in {delta, plural, =1{a month} other{# months}}' => 'in {delta, plural, =1{un mese} other{# mesi}}', - 'in {delta, plural, =1{a second} other{# seconds}}' => 'in {delta, plural, =1{un secondo} other{# secondi}}', - 'in {delta, plural, =1{a year} other{# years}}' => 'in {delta, plural, =1{un anno} other{# anni}}', - 'in {delta, plural, =1{an hour} other{# hours}}' => 'in {delta, plural, =1{un\'ora} other{# ore}}', - 'just now' => 'proprio ora', - '{delta, plural, =1{a day} other{# days}} ago' => '{delta, plural, =1{un giorno} other{# giorni}} fa', - '{delta, plural, =1{a minute} other{# minutes}} ago' => '{delta, plural, =1{un minuto} other{# minuti}} fa', - '{delta, plural, =1{a month} other{# months}} ago' => '{delta, plural, =1{un mese} other{# mesi}} fa', - '{delta, plural, =1{a second} other{# seconds}} ago' => '{delta, plural, =1{un secondo} other{# secondi}} fa', - '{delta, plural, =1{a year} other{# years}} ago' => '{delta, plural, =1{un anno} other{# anni}} fa', - '{delta, plural, =1{an hour} other{# hours}} ago' => '{delta, plural, =1{un\'ora} other{# ore}} fa', - '{nFormatted} B' => '{nFormatted} B', - '{nFormatted} GB' => '{nFormatted} GB', - '{nFormatted} GiB' => '{nFormatted} GiB', - '{nFormatted} KB' => '{nFormatted} KB', - '{nFormatted} KiB' => '{nFormatted} KiB', - '{nFormatted} MB' => '{nFormatted} MB', - '{nFormatted} MiB' => '{nFormatted} MiB', - '{nFormatted} PB' => '{nFormatted} PB', - '{nFormatted} PiB' => '{nFormatted} PiB', - '{nFormatted} TB' => '{nFormatted} TB', - '{nFormatted} TiB' => '{nFormatted} TiB', - '{nFormatted} {n, plural, =1{byte} other{bytes}}' => '{nFormatted} {n, plural, =1{byte} other{byte}}', - '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}' => '{nFormatted} {n, plural, =1{gibibyte} other{gibibyte}}', - '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}' => '{nFormatted} {n, plural, =1{gigabyte} other{gigabyte}}', - '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}' => '{nFormatted} {n, plural, =1{kibibyte} other{kibibyte}}', - '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}' => '{nFormatted} {n, plural, =1{kilobyte} other{kilobyte}}', - '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}' => '{nFormatted} {n, plural, =1{mebibyte} other{mebibyte}}', - '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}' => '{nFormatted} {n, plural, =1{megabyte} other{megabyte}}', - '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}' => '{nFormatted} {n, plural, =1{pebibyte} other{pebibyte}}', - '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}' => '{nFormatted} {n, plural, =1{petabyte} other{petabyte}}', - '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}' => '{nFormatted} {n, plural, =1{tebibyte} other{tebibyte}}', - '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}' => '{nFormatted} {n, plural, =1{terabyte} other{terabyte}}', + 'Unknown alias: -{name}' => 'Alias sconosciuto: -{name}', + '{attribute} contains wrong subnet mask.' => '{attribute} contiene una subnet mask errata.', + '{attribute} is not in the allowed range.' => '{attribute} non rientra nell\'intervallo permesso', + '{attribute} must be a valid IP address.' => '{attribute} deve essere un indirizzo IP valido.', + '{attribute} must be an IP address with specified subnet.' => '{attribute} deve essere un indirizzo IP valido con subnet specificata.', + '{attribute} must be equal to "{compareValueOrAttribute}".' => '{attribute} deve essere uguale a "{compareValueOrAttribute}".', + '{attribute} must be greater than "{compareValueOrAttribute}".' => '{attribute} deve essere maggiore di "{compareValueOrAttribute}".', + '{attribute} must be greater than or equal to "{compareValueOrAttribute}".' => '{attribute} deve essere maggiore o uguale a "{compareValueOrAttribute}".', + '{attribute} must be less than "{compareValueOrAttribute}".' => '{attribute} deve essere minore di "{compareValueOrAttribute}".', + '{attribute} must be less than or equal to "{compareValueOrAttribute}".' => '{attribute} deve essere minore o uguale a "{compareValueOrAttribute}".', + '{attribute} must not be a subnet.' => '{attribute} non deve essere una subnet.', + '{attribute} must not be an IPv4 address.' => '{attribute} non deve essere un indirizzo IPv4.', + '{attribute} must not be an IPv6 address.' => '{attribute} non deve essere un indirizzo IPv6.', + '{attribute} must not be equal to "{compareValueOrAttribute}".' => '{attribute} non deve essere uguale a "{compareValueOrAttribute}".', + '{delta, plural, =1{1 day} other{# days}}' => '{delta, plural, =1{1 giorno} other{# giorni}}', + '{delta, plural, =1{1 hour} other{# hours}}' => '{delta, plural, =1{1 ora} other{# ore}}', + '{delta, plural, =1{1 minute} other{# minutes}}' => '{delta, plural, =1{1 minuto} other{# minuti}}', + '{delta, plural, =1{1 month} other{# months}}' => '{delta, plural, =1{1 mese} other{# mesi}}', + '{delta, plural, =1{1 second} other{# seconds}}' => '{delta, plural, =1{1 secondo} other{# secondi}}', + '{delta, plural, =1{1 year} other{# years}}' => '{delta, plural, =1{1 anno} other{# anni}}', + '{attribute} must be greater than "{compareValue}".' => '@@{attribute} deve essere maggiore di "{compareValue}".@@', + '{attribute} must be greater than or equal to "{compareValue}".' => '@@{attribute} deve essere maggiore o uguale a "{compareValue}".@@', + '{attribute} must be less than "{compareValue}".' => '@@{attribute} deve essere minore di "{compareValue}".@@', + '{attribute} must be less than or equal to "{compareValue}".' => '@@{attribute} deve essere minore o uguale a "{compareValue}".@@', + '{attribute} must be repeated exactly.' => '@@{attribute} deve essere ripetuto esattamente.@@', + '{attribute} must not be equal to "{compareValue}".' => '@@{attribute} non deve essere uguale a "{compareValue}".@@', '(not set)' => '(nessun valore)', 'An internal server error occurred.' => 'Si è verificato un errore interno', 'Are you sure you want to delete this item?' => 'Sei sicuro di voler eliminare questo elemento?', @@ -67,6 +56,7 @@ return [ 'Missing required parameters: {params}' => 'Il seguente parametro è mancante: {params}', 'No' => 'No', 'No results found.' => 'Nessun risultato trovato', + 'Only files with these MIME types are allowed: {mimeTypes}.' => 'Solo i file con questi tipi MIME sono consentiti: {mimeTypes}.', 'Only files with these extensions are allowed: {extensions}.' => 'Solo i file con queste estensioni sono permessi: {extensions}.', 'Page not found.' => 'Pagina non trovata.', 'Please fix the following errors:' => 'Per favore correggi i seguenti errori:', @@ -80,6 +70,7 @@ return [ 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'L immagine "{file}" è troppo grande. La sua larghezza non può essere maggiore di {limit, number} {limit, plural, one{pixel} other{pixel}}.', 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'L immagine "{file}" è troppo piccola. La sua altezza non può essere minore di {limit, number} {limit, plural, one{pixel} other{pixel}}.', 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'L immagine "{file}" è troppo piccola. La sua larghezza non può essere minore di {limit, number} {limit, plural, one{pixel} other{pixel}}.', + 'The requested view "{name}" was not found.' => 'La vista "{name}" richiesta non è stata trovata.', 'The verification code is incorrect.' => 'Il codice di verifica non è corretto.', 'Total {count, number} {count, plural, one{item} other{items}}.' => '{count, plural, one{Elementi} other{Elementi}} totali {count, number}.', 'Unable to verify your data submission.' => 'Impossibile verificare i dati inviati.', @@ -89,6 +80,13 @@ return [ 'Yes' => 'Si', 'You are not allowed to perform this action.' => 'Non sei autorizzato ad eseguire questa operazione.', 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Puoi caricare al massimo {limit, number} {limit, plural, one{file} other{file}}.', + 'in {delta, plural, =1{a day} other{# days}}' => 'in {delta, plural, =1{un giorno} other{# giorni}}', + 'in {delta, plural, =1{a minute} other{# minutes}}' => 'in {delta, plural, =1{un minuto} other{# minuti}}', + 'in {delta, plural, =1{a month} other{# months}}' => 'in {delta, plural, =1{un mese} other{# mesi}}', + 'in {delta, plural, =1{a second} other{# seconds}}' => 'in {delta, plural, =1{un secondo} other{# secondi}}', + 'in {delta, plural, =1{a year} other{# years}}' => 'in {delta, plural, =1{un anno} other{# anni}}', + 'in {delta, plural, =1{an hour} other{# hours}}' => 'in {delta, plural, =1{un\'ora} other{# ore}}', + 'just now' => 'proprio ora', 'the input value' => 'il valore del campo', '{attribute} "{value}" has already been taken.' => '{attribute} "{value}" è già presente.', '{attribute} cannot be blank.' => '{attribute} non può essere vuoto.', @@ -100,15 +98,37 @@ return [ '{attribute} must be a string.' => '{attribute} deve essere una stringa.', '{attribute} must be an integer.' => '{attribute} deve essere un numero intero.', '{attribute} must be either "{true}" or "{false}".' => '{attribute} deve essere "{true}" oppure "{false}".', - '{attribute} must be greater than "{compareValue}".' => '{attribute} deve essere maggiore di "{compareValue}".', - '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute} deve essere maggiore o uguale a "{compareValue}".', - '{attribute} must be less than "{compareValue}".' => '{attribute} deve essere minore di "{compareValue}".', - '{attribute} must be less than or equal to "{compareValue}".' => '{attribute} deve essere minore o uguale a "{compareValue}".', '{attribute} must be no greater than {max}.' => '{attribute} non deve essere maggiore di {max}.', '{attribute} must be no less than {min}.' => '{attribute} non deve essere minore di {min}.', - '{attribute} must be repeated exactly.' => '{attribute} deve essere ripetuto esattamente.', - '{attribute} must not be equal to "{compareValue}".' => '{attribute} non deve essere uguale a "{compareValue}".', '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} dovrebbe contenere almeno {min, number} {min, plural, one{carattere} other{caratteri}}.', '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} dovrebbe contenere al massimo {max, number} {max, plural, one{carattere} other{caratteri}}.', '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} dovrebbe contenere {length, number} {length, plural, one{carattere} other{caratteri}}.', + '{delta, plural, =1{a day} other{# days}} ago' => '{delta, plural, =1{un giorno} other{# giorni}} fa', + '{delta, plural, =1{a minute} other{# minutes}} ago' => '{delta, plural, =1{un minuto} other{# minuti}} fa', + '{delta, plural, =1{a month} other{# months}} ago' => '{delta, plural, =1{un mese} other{# mesi}} fa', + '{delta, plural, =1{a second} other{# seconds}} ago' => '{delta, plural, =1{un secondo} other{# secondi}} fa', + '{delta, plural, =1{a year} other{# years}} ago' => '{delta, plural, =1{un anno} other{# anni}} fa', + '{delta, plural, =1{an hour} other{# hours}} ago' => '{delta, plural, =1{un\'ora} other{# ore}} fa', + '{nFormatted} B' => '{nFormatted} B', + '{nFormatted} GB' => '{nFormatted} GB', + '{nFormatted} GiB' => '{nFormatted} GiB', + '{nFormatted} KB' => '{nFormatted} KB', + '{nFormatted} KiB' => '{nFormatted} KiB', + '{nFormatted} MB' => '{nFormatted} MB', + '{nFormatted} MiB' => '{nFormatted} MiB', + '{nFormatted} PB' => '{nFormatted} PB', + '{nFormatted} PiB' => '{nFormatted} PiB', + '{nFormatted} TB' => '{nFormatted} TB', + '{nFormatted} TiB' => '{nFormatted} TiB', + '{nFormatted} {n, plural, =1{byte} other{bytes}}' => '{nFormatted} {n, plural, =1{byte} other{byte}}', + '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}' => '{nFormatted} {n, plural, =1{gibibyte} other{gibibyte}}', + '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}' => '{nFormatted} {n, plural, =1{gigabyte} other{gigabyte}}', + '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}' => '{nFormatted} {n, plural, =1{kibibyte} other{kibibyte}}', + '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}' => '{nFormatted} {n, plural, =1{kilobyte} other{kilobyte}}', + '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}' => '{nFormatted} {n, plural, =1{mebibyte} other{mebibyte}}', + '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}' => '{nFormatted} {n, plural, =1{megabyte} other{megabyte}}', + '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}' => '{nFormatted} {n, plural, =1{pebibyte} other{pebibyte}}', + '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}' => '{nFormatted} {n, plural, =1{petabyte} other{petabyte}}', + '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}' => '{nFormatted} {n, plural, =1{tebibyte} other{tebibyte}}', + '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}' => '{nFormatted} {n, plural, =1{terabyte} other{terabyte}}', ]; From 6b607d078f415ae1eb5fb12305f6577044b83370 Mon Sep 17 00:00:00 2001 From: Paul Klimov Date: Fri, 6 May 2016 11:04:27 +0200 Subject: [PATCH 28/90] #11498: Fixed unable to saved serialized object into PostgreSQL binary column (#11499) #11498: Fixed unable to saved serialized object into PostgreSQL binary column --- framework/CHANGELOG.md | 1 + framework/db/pgsql/QueryBuilder.php | 36 ++++++++++++++++++++++ tests/framework/db/pgsql/PostgreSQLCommandTest.php | 22 +++++++++++++ 3 files changed, 59 insertions(+) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index b7668f8..c0813f2 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -12,6 +12,7 @@ Yii Framework 2 Change Log - Enh #11432: Added HTTP status 421 "Misdirected Request" to list of statuses in `yii\web\Response` (dasmfm) - Enh #11438: Configurable `yii\helpers\Markdown` default flavor (mdmunir) - Bug #11459: Fixed flash messages not destroyed when `session.auto_start = 1` set in php.ini (cartmanchen) +- Bug #11498: Fixed inability to save serialized object into PostgreSQL binary column (klimov-paul) 2.0.8 April 28, 2016 diff --git a/framework/db/pgsql/QueryBuilder.php b/framework/db/pgsql/QueryBuilder.php index 3085c6d..7498ee2 100644 --- a/framework/db/pgsql/QueryBuilder.php +++ b/framework/db/pgsql/QueryBuilder.php @@ -227,6 +227,42 @@ class QueryBuilder extends \yii\db\QueryBuilder /** * @inheritdoc */ + public function insert($table, $columns, &$params) + { + return parent::insert($table, $this->normalizeTableRowData($table, $columns), $params); + } + + /** + * @inheritdoc + */ + public function update($table, $columns, $condition, &$params) + { + return parent::update($table, $this->normalizeTableRowData($table, $columns), $condition, $params); + } + + /** + * Normalizes data to be saved into the table, performing extra preparations and type converting, if necessary. + * @param string $table the table that data will be saved into. + * @param array $columns the column data (name => value) to be saved into the table. + * @return array normalized columns + * @since 2.0.9 + */ + private function normalizeTableRowData($table, $columns) + { + if (($tableSchema = $this->db->getSchema()->getTableSchema($table)) !== null) { + $columnSchemas = $tableSchema->columns; + foreach ($columns as $name => $value) { + if (isset($columnSchemas[$name]) && $columnSchemas[$name]->type === Schema::TYPE_BINARY && is_string($value)) { + $columns[$name] = [$value, \PDO::PARAM_LOB]; // explicitly setup PDO param type for binary column + } + } + } + return $columns; + } + + /** + * @inheritdoc + */ public function batchInsert($table, $columns, $rows) { $schema = $this->db->getSchema(); diff --git a/tests/framework/db/pgsql/PostgreSQLCommandTest.php b/tests/framework/db/pgsql/PostgreSQLCommandTest.php index 5a15185..50dff3a 100644 --- a/tests/framework/db/pgsql/PostgreSQLCommandTest.php +++ b/tests/framework/db/pgsql/PostgreSQLCommandTest.php @@ -69,4 +69,26 @@ class PostgreSQLCommandTest extends CommandTest $command->execute(); $this->assertEquals(3, $db->getSchema()->getLastInsertID('schema1.profile_id_seq')); } + + /** + * @see https://github.com/yiisoft/yii2/issues/11498 + */ + public function testSaveSerializedObject() + { + $db = $this->getConnection(); + + $command = $db->createCommand()->insert('type', [ + 'int_col' => 1, + 'char_col' => 'serialize', + 'float_col' => 5.6, + 'bool_col' => true, + 'blob_col' => serialize($db), + ]); + $this->assertEquals(1, $command->execute()); + + $command = $db->createCommand()->update('type', [ + 'blob_col' => serialize($db), + ], ['char_col' => 'serialize']); + $this->assertEquals(1, $command->execute()); + } } \ No newline at end of file From abb408fc1d6477e3980ddab616dea534b013e05e Mon Sep 17 00:00:00 2001 From: Klimov Paul Date: Fri, 6 May 2016 12:18:03 +0300 Subject: [PATCH 29/90] pgsql migrations fixed --- framework/caching/migrations/schema-pgsql.sql | 2 +- framework/web/migrations/schema-pgsql.sql | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/caching/migrations/schema-pgsql.sql b/framework/caching/migrations/schema-pgsql.sql index 63349d3..8d510b9 100644 --- a/framework/caching/migrations/schema-pgsql.sql +++ b/framework/caching/migrations/schema-pgsql.sql @@ -15,6 +15,6 @@ create table "cache" ( "id" varchar(128) not null, "expire" integer, - "data" BLOB, + "data" bytea, primary key ("id") ); diff --git a/framework/web/migrations/schema-pgsql.sql b/framework/web/migrations/schema-pgsql.sql index bdbfd46..8e8674f 100644 --- a/framework/web/migrations/schema-pgsql.sql +++ b/framework/web/migrations/schema-pgsql.sql @@ -15,6 +15,6 @@ create table "session" ( "id" varchar(256) not null, "expire" integer, - "data" BLOB, + "data" bytea, primary key ("id") ); From 2eb90f82fe6eed1274b80c6cb0e4c8d093da6b46 Mon Sep 17 00:00:00 2001 From: Salem Ouerdani Date: Sat, 7 May 2016 17:38:07 +0100 Subject: [PATCH 30/90] removes nonexistent yii/web/rawCsrfToken from docs (#11521) * removes nonexistent yii/web/rawCsrfToken from docs * maj --- framework/web/Request.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/framework/web/Request.php b/framework/web/Request.php index de81447..6398eb7 100644 --- a/framework/web/Request.php +++ b/framework/web/Request.php @@ -1266,9 +1266,8 @@ class Request extends \yii\base\Request /** * Returns the token used to perform CSRF validation. * - * This token is a masked version of [[rawCsrfToken]] to prevent [BREACH attacks](http://breachattack.com/). - * This token may be passed along via a hidden field of an HTML form or an HTTP header value - * to support CSRF validation. + * This token is generated in a way to prevent [BREACH attacks](http://breachattack.com/). It may be passed + * along via a hidden field of an HTML form or an HTTP header value to support CSRF validation. * @param boolean $regenerate whether to regenerate CSRF token. When this parameter is true, each time * this method is called, a new CSRF token will be generated and persisted (in session or cookie). * @return string the token used to perform CSRF validation. From e96f956f8ab8b57310975a4f39b443a262c04722 Mon Sep 17 00:00:00 2001 From: Nobuo Kihara Date: Wed, 11 May 2016 08:46:07 +0900 Subject: [PATCH 31/90] docs/guide-ja updated [ci skip] (#11537) * docs/guide-ja translations updated [skip ci] * docs/guide-ja security-* updated [ci skip] --- docs/guide-ja/README.md | 3 + docs/guide-ja/concept-di-container.md | 4 +- docs/guide-ja/db-migrations.md | 7 ++ docs/guide-ja/db-query-builder.md | 21 +++++ docs/guide-ja/input-forms.md | 4 + docs/guide-ja/input-validation.md | 24 ++++++ docs/guide-ja/output-data-widgets.md | 3 + docs/guide-ja/runtime-sessions-cookies.md | 2 + docs/guide-ja/security-cryptography.md | 68 +++++++++++++++++ docs/guide-ja/security-overview.md | 14 ++++ docs/guide-ja/security-passwords.md | 122 ------------------------------ docs/guide-ja/start-installation.md | 4 +- 12 files changed, 151 insertions(+), 125 deletions(-) create mode 100644 docs/guide-ja/security-cryptography.md create mode 100644 docs/guide-ja/security-overview.md diff --git a/docs/guide-ja/README.md b/docs/guide-ja/README.md index 30319bf..67cf9a4 100644 --- a/docs/guide-ja/README.md +++ b/docs/guide-ja/README.md @@ -109,9 +109,12 @@ All Rights Reserved. セキュリティ ------------ +* [概要](security-overview.md) * [認証](security-authentication.md) * [権限付与](security-authorization.md) * [パスワードを扱う](security-passwords.md) +* [暗号化](security-cryptography.md) +* [ビューのセキュリティ](structure-views.md#security) * [認証クライアント](https://github.com/yiisoft/yii2-authclient/blob/master/docs/guide-ja/README.md) * [ベストプラクティス](security-best-practices.md) diff --git a/docs/guide-ja/concept-di-container.md b/docs/guide-ja/concept-di-container.md index 909b537..73840a8 100644 --- a/docs/guide-ja/concept-di-container.md +++ b/docs/guide-ja/concept-di-container.md @@ -287,7 +287,7 @@ Yii は、新しいオブジェクトを作成するさい、そのコアコー \Yii::$container->set('yii\widgets\LinkPager', ['maxButtonCount' => 5]); ``` -次のコードでビューでウィジェットを使用すれば、 `maxButtonCount` プロパティは、 +そして、次のコードでビューでウィジェットを使用すれば、`maxButtonCount` プロパティは、 クラスで定義されているデフォルト値 10 の代わりに 5 で初期化されます。 ```php @@ -300,6 +300,8 @@ echo \yii\widgets\LinkPager::widget(); echo \yii\widgets\LinkPager::widget(['maxButtonCount' => 20]); ``` +> Tip: どのような型の値であろうとも上書きされますので、オプションの配列の指定には気を付けてください。オプションの配列はマージされません。 + DI コンテナの自動コンストラクタ・インジェクションの利点を活かす別の例です。 あなたのコントローラクラスが、ホテル予約サービスのような、いくつかの他のオブジェクトに依存するとします。 あなたは、コンストラクタパラメータを通して依存関係を宣言して、DI コンテナにあなたの課題を解決させることができます。 diff --git a/docs/guide-ja/db-migrations.md b/docs/guide-ja/db-migrations.md index fcf10c5..9bb6340 100644 --- a/docs/guide-ja/db-migrations.md +++ b/docs/guide-ja/db-migrations.md @@ -668,6 +668,10 @@ class m150101_185401_create_news_table extends Migration * [[yii\db\Migration::dropForeignKey()|dropForeignKey()]]: 外部キーを削除 * [[yii\db\Migration::createIndex()|createIndex()]]: インデックスを作成 * [[yii\db\Migration::dropIndex()|dropIndex()]]: インデックスを削除 +* [[yii\db\Migration::addCommentOnColumn()|addCommentOnColumn()]: カラムにコメントを追加 +* [[yii\db\Migration::dropCommentFromColumn()|dropCommentFromColumn()]: カラムからコメントを削除 +* [[yii\db\Migration::addCommentOnTable()|addCommentOnTable()]: テーブルにコメントを追加 +* [[yii\db\Migration::dropCommentFromTable()|dropCommentFromTable()]: テーブルからコメントを削除 > Info: [[yii\db\Migration]] は、データベースクエリメソッドを提供しません。 これは、通常、データベースからのデータ取得については、メッセージを追加して表示する必要がないからです。 @@ -692,6 +696,9 @@ yii migrate リストされたマイグレーションを適用することをあなたが確認すると、タイムスタンプの値の順に、一つずつ、すべての新しいマイグレーションクラスの `up()` または `safeUp()` メソッドが実行されます。 マイグレーションのどれかが失敗した場合は、コマンドは残りのマイグレーションを適用せずに終了します。 +> Tip: あなたのサーバでコマンドラインを使用できない場合は +> [web shell](https://github.com/samdark/yii2-webshell) エクステンションを使ってみてください。 + 適用が成功したマイグレーションの一つ一つについて、`migration` という名前のデータベーステーブルに行が挿入されて、マイグレーションの成功が記録されます。 この記録によって、マイグレーションツールは、どのマイグレーションが適用され、どのマイグレーションが適用されていないかを特定することが出来ます。 diff --git a/docs/guide-ja/db-query-builder.md b/docs/guide-ja/db-query-builder.md index f92434d..97626e3 100644 --- a/docs/guide-ja/db-query-builder.md +++ b/docs/guide-ja/db-query-builder.md @@ -332,6 +332,20 @@ $query->filterWhere([ [[yii\db\Query::andWhere()|andWhere()]] または [[yii\db\Query::orWhere()|orWhere()]] と同じように、[[yii\db\Query::andFilterWhere()|andFilterWhere()]] または [[yii\db\Query::orFilterWhere()|orFilterWhere()]] を使って、既存の条件に別のフィルタ条件を追加することも出来ます。 +さらに加えて、値の方に含まれている比較演算子を適切に判断してくれる [[yii\db\Query::andFilterCompare()]] があります。 + +```php +$query->andFilterCompare('name', 'John Doe'); +$query->andFilterCompare('rating', '>9'); +$query->andFilterCompare('value', '<=100'); +``` + +比較演算子を明示的に指定することも可能です。 + +```php +$query->andFilterCompare('name', 'Doe', 'like'); +``` + ### [[yii\db\Query::orderBy()|orderBy()]] [[yii\db\Query::orderBy()|orderBy()]] メソッドは SQL クエリの `ORDER BY` 句を指定します。例えば、 @@ -580,6 +594,13 @@ $query = (new \yii\db\Query()) この無名関数は、現在の行データを含む `$row` というパラメータを取り、現在の行のインデックス値として使われるスカラ値を返さなくてはなりません。 +> Note: [[yii\db\Query::groupBy()|groupBy()]] や [[yii\db\Query::orderBy()|orderBy()]] +> のようなクエリメソッドが SQL に変換されてクエリの一部となるのとは対照的に、 +> このメソッドはデータベースからデータが取得された後で動作します。 +> このことは、クエリの SELECT に含まれるカラム名だけを使うことが出来る、ということを意味します。 +> また、テーブルプレフィックスを付けてカラムを選択した場合、例えば `customer.id` を選択した場合は、 +> リザルトセットのカラム名は `id` しか含みませんので、テーブルプレフィックス無しで `->indexBy('id')` と呼ぶ必要があります。 + ## バッチクエリ diff --git a/docs/guide-ja/input-forms.md b/docs/guide-ja/input-forms.md index 15c5d3c..8f91abb 100644 --- a/docs/guide-ja/input-forms.md +++ b/docs/guide-ja/input-forms.md @@ -76,6 +76,10 @@ $form = ActiveForm::begin([ 例えば、上記の例における `username` 属性のインプットフィールドの名前は `LoginForm[username]` となります。 この命名規則の結果として、ログインフォームの全ての属性が配列として、サーバ側においては `$_POST['LoginForm']` に格納されて利用できることになります。 +> Tip: 一つのフォームに一つのモデルだけがある場合、インプットの名前を単純化したいときは、 +> モデルの [[yii\base\Model::formName()|formName()]] メソッドをオーバーライドして空文字列を返すようにして、配列の部分をスキップすることが出来ます。 +> この方法を使えば、[GridView](output-data-widgets.md#grid-view) で使われるフィルターモデルで、もっと見栄えの良い URL を生成させることが出来ます。 + モデルの属性を指定するために、もっと洗練された方法を使うことも出来ます。 例えば、複数のファイルをアップロードしたり、複数の項目を選択したりする場合に、属性の名前に `[]` を付けて、属性が配列の値を取り得ることを指定することが出来ます。 diff --git a/docs/guide-ja/input-validation.md b/docs/guide-ja/input-validation.md index 76573cc..20f08c0 100644 --- a/docs/guide-ja/input-validation.md +++ b/docs/guide-ja/input-validation.md @@ -90,6 +90,27 @@ public function rules() 属性は、上記の検証のステップに従って、`scenarios()` でアクティブな属性であると宣言されており、かつ、`rules()` で宣言された一つまたは複数のアクティブな規則と関連付けられている場合に、また、その場合に限って、検証されます。 +> Note: 規則に名前を付けると便利です。すなわち、 +> ```php +> public function rules() +> { +> return [ +> // ... +> 'password' => [['password'], 'string', 'max' => 60], +> ]; +> } +> ``` +> +> これを子のモデルで使うことが出来ます。 +> +> ```php +> public function rules() +> { +> $rules = parent::rules(); +> unset($rules['password']); +> return $rules; +> } + ### エラーメッセージをカスタマイズする @@ -526,6 +547,9 @@ JS; > ] > ``` +> Tip: クライアント側の検証を手動で操作する必要がある場合、すなわち、動的にフィールドを追加したり、何か特殊な UI ロジックを実装する場合は、 +> Yii 2.0 Cookbook の [Working with ActiveForm via JavaScript](https://github.com/samdark/yii2-cookbook/blob/master/book/forms-activeform-js.md) を参照してください。 + ### Deferred 検証 非同期のクライアント側の検証をサポートする必要がある場合は、[Defered オブジェクト](http://api.jquery.com/category/deferred-object/) を作成することが出来ます。 diff --git a/docs/guide-ja/output-data-widgets.md b/docs/guide-ja/output-data-widgets.md index 3671957..1d26a1a 100644 --- a/docs/guide-ja/output-data-widgets.md +++ b/docs/guide-ja/output-data-widgets.md @@ -392,6 +392,9 @@ class PostSearch extends Post ``` +> Tip: フィルタのクエリを構築する方法を学ぶためには、[クエリビルダ](db-query-builder.md)、 +> 中でも特に [フィルタ条件](db-query-builder.md#filter-conditions) を参照してください。 + この `search()` メソッドをコントローラで使用して、GridView のためのデータプロバイダを取得することが出来ます。 ```php diff --git a/docs/guide-ja/runtime-sessions-cookies.md b/docs/guide-ja/runtime-sessions-cookies.md index 5bdc1af..5aa9890 100644 --- a/docs/guide-ja/runtime-sessions-cookies.md +++ b/docs/guide-ja/runtime-sessions-cookies.md @@ -234,6 +234,8 @@ Yii は個々のクッキーを [[yii\web\Cookie]] のオブジェクトとし [[yii\web\Request]] と [[yii\web\Response]] は、ともに、`cookies` という名前のプロパティによって、クッキーのコレクションを保持します。 後者のクッキーコレクションはリクエストの中で送信されてきたクッキーを表し、一方、後者のクッキーコレクションは、これからユーザに送信されるクッキーを表します。 +アプリケーションで、リクエストとレスポンスを直接に操作する部分は、コントローラです。 +従って、クッキーの読み出しと送信はコントローラで実行されるべきです。 ### クッキーを読み出す diff --git a/docs/guide-ja/security-cryptography.md b/docs/guide-ja/security-cryptography.md new file mode 100644 index 0000000..fdba965 --- /dev/null +++ b/docs/guide-ja/security-cryptography.md @@ -0,0 +1,68 @@ +暗号化 +====== + +個の節では、セキュリティの以下の側面について見ていきます。 + +- 乱数データの生成 +- 暗号化と複合化 +- データの完全性の確認 + + +擬似乱数データを生成する +------------------------ + +擬似乱数データはさまざまな状況で役に立ちます。 +例えば、メール経由でパスワードをリセットするときは、トークンを生成してデータベースに保存し、それをユーザにメールで送信します。 +そして、ユーザはこのトークンを自分がアカウントの所有者であることの証拠として使用します。 +このトークンがユニークかつ推測困難なものであることは非常に重要なことです。 +さもなくば、攻撃者がトークンの値を推測してユーザのパスワードをリセットする可能性があります。 + +Yii のセキュリティヘルパは擬似乱数データの生成を単純な作業にしてくれます。 + + +```php +$key = Yii::$app->getSecurity()->generateRandomString(); +``` + +暗号化と復号化 +-------------- + +Yii は秘密鍵を使ってデータを暗号化/復号化することを可能にする便利なヘルパ関数を提供しています。 +データを暗号化関数に渡して、秘密鍵を持つ者だけが復号化することが出来るようにすることが出来ます。 +例えば、何らかの情報をデータベースに保存する必要があるけれども、(たとえアプリケーションのデータベースが第三者に漏洩した場合でも) 秘密鍵を持つユーザだけがそれを見ることが出来るようにする必要がある、という場合には次のようにします。 + +```php +// $data と $secretKey はフォームから取得する +$encryptedData = Yii::$app->getSecurity()->encryptByPassword($data, $secretKey); +// $encryptedData をデータベースに保存する +``` + +そして、後でユーザがデータを読みたいときは、次のようにします。 + +```php +// $secretKey はユーザ入力から取得、$encryptedData はデータベースから取得 +$data = Yii::$app->getSecurity()->decryptByPassword($encryptedData, $secretKey); +``` + +[[\yii\base\Security::encryptByKey()]] と [[\yii\base\Security::decryptByKey()]] によって、パスワードの代わりにキーを使うことも可能です。 + + +データの完全性を確認する +------------------------ + +データが第三者によって改竄されたり、更には何らかの形で毀損されたりしていないことを確認する必要がある、という場合があります。 +Yii は二つのヘルパ関数の形で、データの完全性を確認するための簡単な方法を提供しています。 + +秘密鍵とデータから生成されたハッシュをデータにプレフィクスします。 + +```php +// $secretKey はアプリケーションまたはユーザの秘密、$genuineData は信頼できるソースから取得 +$data = Yii::$app->getSecurity()->hashData($genuineData, $secretKey); +``` + +データの完全性が毀損されていないかチェックします。 + +```php +// $secretKey はアプリケーションまたはユーザの秘密、$data は信頼できないソースから取得 +$data = Yii::$app->getSecurity()->validateData($data, $secretKey); +``` diff --git a/docs/guide-ja/security-overview.md b/docs/guide-ja/security-overview.md new file mode 100644 index 0000000..29c7d80 --- /dev/null +++ b/docs/guide-ja/security-overview.md @@ -0,0 +1,14 @@ +セキュリティ +============ + +十分なセキュリティは、すべてのアプリケーションの健全さと成功のために欠くことが出来ないものです。 +不幸なことに、理解が不足しているためか、実装の難易度が高すぎるためか、セキュリティのことになると手を抜く開発者がたくさんいます。 +Yii によって駆動されるあなたのアプリケーションを可能な限り安全にするために、Yii はいくつかの優秀な使いやすいセキュリティ機能を内蔵しています。 + +* [認証](security-authentication.md) +* [権限付与](security-authorization.md) +* [パスワードを扱う](security-passwords.md) +* [暗号化](security-cryptography.md) +* [ビューのセキュリティ](structure-views.md#security) +* [認証クライアント](https://github.com/yiisoft/yii2-authclient/blob/master/docs/guide-ja/README.md) +* [ベストプラクティス](security-best-practices.md) diff --git a/docs/guide-ja/security-passwords.md b/docs/guide-ja/security-passwords.md index c681c6d..2ff8563 100644 --- a/docs/guide-ja/security-passwords.md +++ b/docs/guide-ja/security-passwords.md @@ -1,16 +1,6 @@ パスワードを扱う ================ -> Note: この節はまだ執筆中です。 - -十分なセキュリティは、すべてのアプリケーションの健全さと成功のために欠くことが出来ないものです。 -不幸なことに、理解が不足しているためか、実装の難易度が高すぎるためか、セキュリティのことになると手を抜く開発者がたくさんいます。 -Yii によって駆動されるあなたのアプリケーションを可能な限り安全にするために、Yii はいくつかの優秀な使いやすいセキュリティ機能を内蔵しています。 - - -ハッシュとパスワードの検証 --------------------------- - ほとんどの開発者はパスワードを平文テキストで保存してはいけないということを知っていますが、パスワードを `md5` や `sha1` でハッシュしてもまだ安全だと思っている開発者がたくさんいます。 かつては、前述のハッシュアルゴリズムを使えば十分であった時もありましたが、現代のハードウェアをもってすれば、そのようなハッシュはブルートフォースアタックを使って非常に簡単に復元することが可能です。 @@ -38,115 +28,3 @@ if (Yii::$app->getSecurity()->validatePassword($password, $hash)) { // パスワードが違う } ``` - -擬似乱数データを生成する ------------------------- - -擬似乱数データはさまざまな状況で役に立ちます。 -例えば、メール経由でパスワードをリセットするときは、トークンを生成してデータベースに保存し、それをユーザにメールで送信します。 -そして、ユーザはこのトークンを自分がアカウントの所有者であることの証拠として使用します。 -このトークンがユニークかつ推測困難なものであることは非常に重要なことです。 -さもなくば、攻撃者がトークンの値を推測してユーザのパスワードをリセットする可能性があります。 - -Yii のセキュリティヘルパは擬似乱数データの生成を単純な作業にしてくれます。 - - -```php -$key = Yii::$app->getSecurity()->generateRandomString(); -``` - -暗号論的に安全な乱数データを生成するためには、`openssl` 拡張をインストールしている必要があることに注意してください。 - -暗号化と復号化 --------------- - -Yii は秘密鍵を使ってデータを暗号化/復号化することを可能にする便利なヘルパ関数を提供しています。 -データを暗号化関数に渡して、秘密鍵を持つ者だけが復号化することが出来るようにすることが出来ます。 -例えば、何らかの情報をデータベースに保存する必要があるけれども、(たとえアプリケーションのデータベースが第三者に漏洩した場合でも) 秘密鍵を持つユーザだけがそれを見ることが出来るようにする必要がある、という場合には次のようにします。 - -```php -// $data と $secretKey はフォームから取得する -$encryptedData = Yii::$app->getSecurity()->encryptByPassword($data, $secretKey); -// $encryptedData をデータベースに保存する -``` - -そして、後でユーザがデータを読みたいときは、次のようにします。 - -```php -// $secretKey はユーザ入力から取得、$encryptedData はデータベースから取得 -$data = Yii::$app->getSecurity()->decryptByPassword($encryptedData, $secretKey); -``` - -データの完全性を確認する ------------------------- - -データが第三者によって改竄されたり、更には何らかの形で毀損されたりしていないことを確認する必要がある、という場合があります。 -Yii は二つのヘルパ関数の形で、データの完全性を確認するための簡単な方法を提供しています。 - -秘密鍵とデータから生成されたハッシュをデータにプレフィクスします。 - -```php -// $secretKey はアプリケーションまたはユーザの秘密、$genuineData は信頼できるソースから取得 -$data = Yii::$app->getSecurity()->hashData($genuineData, $secretKey); -``` - -データの完全性が毀損されていないかチェックします。 - -```php -// $secretKey はアプリケーションまたはユーザの秘密、$data は信頼できないソースから取得 -$data = Yii::$app->getSecurity()->validateData($data, $secretKey); -``` - - -todo: XSS prevention, CSRF prevention, cookie protection, refer to 1.1 guide - -プロパティを設定することによって、CSRF バリデーションをコントローラ および/または アクション単位で無効にすることも出来ます。 - -```php -namespace app\controllers; - -use yii\web\Controller; - -class SiteController extends Controller -{ - public $enableCsrfValidation = false; - - public function actionIndex() - { - // CSRF バリデーションはこのアクションおよびその他のアクションに適用されない - } - -} -``` - -特定のアクションに対して CSRF バリデーションを無効にするためには、次のようにします。 - -```php -namespace app\controllers; - -use yii\web\Controller; - -class SiteController extends Controller -{ - public function beforeAction($action) - { - // ... ここで、何らかの条件に基づいて `$this->enableCsrfValidation` をセットする ... - // 親のメソッドを呼ぶ。プロパティが true なら、その中で CSRF がチェックされる。 - return parent::beforeAction($action); - } -} -``` - -クッキーを安全にする --------------------- - -- validation -- httpOnly is default - -参照 ----- - -以下も参照してください。 - -- [ビューのセキュリティ](structure-views.md#security) - diff --git a/docs/guide-ja/start-installation.md b/docs/guide-ja/start-installation.md index 5839a16..c33b046 100644 --- a/docs/guide-ja/start-installation.md +++ b/docs/guide-ja/start-installation.md @@ -203,7 +203,7 @@ server { location / { # 本当のファイルでないものは全て index.php にリダイレクト - try_files $uri $uri/ /index.php?$args; + try_files $uri $uri/ /index.php$is_args$args; } # 存在しない静的ファイルの呼び出しを Yii に処理させたくない場合はコメントを外す @@ -214,7 +214,7 @@ server { location ~ \.php$ { include fastcgi_params; - fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_pass 127.0.0.1:9000; #fastcgi_pass unix:/var/run/php5-fpm.sock; try_files $uri =404; From 1ed6fc09f6659f4427b6b5a87fe609a59a649a8e Mon Sep 17 00:00:00 2001 From: Sergey Smirnov Date: Wed, 11 May 2016 16:11:32 +0400 Subject: [PATCH 32/90] #10825 Fixed EachValidator does not respect skipOnEmpty when using model (#11507) --- framework/validators/EachValidator.php | 4 +++- tests/framework/validators/EachValidatorTest.php | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/framework/validators/EachValidator.php b/framework/validators/EachValidator.php index 6613161..6773c88 100644 --- a/framework/validators/EachValidator.php +++ b/framework/validators/EachValidator.php @@ -129,7 +129,9 @@ class EachValidator extends Validator $filteredValue = []; foreach ($value as $k => $v) { $model->$attribute = $v; - $validator->validateAttribute($model, $attribute); + if (!$validator->skipOnEmpty || !$validator->isEmpty($v)) { + $validator->validateAttribute($model, $attribute); + } $filteredValue[$k] = $model->$attribute; if ($model->hasErrors($attribute)) { $validationErrors = $model->getErrors($attribute); diff --git a/tests/framework/validators/EachValidatorTest.php b/tests/framework/validators/EachValidatorTest.php index c1ce388..331a7fd 100644 --- a/tests/framework/validators/EachValidatorTest.php +++ b/tests/framework/validators/EachValidatorTest.php @@ -107,6 +107,20 @@ class EachValidatorTest extends TestCase $validator = new EachValidator(['rule' => ['integer', 'skipOnEmpty' => false]]); $this->assertFalse($validator->validate([''])); + + $model = FakedValidationModel::createWithAttributes([ + 'attr_one' => [ + '' + ], + ]); + $validator = new EachValidator(['rule' => ['integer', 'skipOnEmpty' => true]]); + $validator->validateAttribute($model, 'attr_one'); + $this->assertFalse($model->hasErrors('attr_one')); + + $model->clearErrors(); + $validator = new EachValidator(['rule' => ['integer', 'skipOnEmpty' => false]]); + $validator->validateAttribute($model, 'attr_one'); + $this->assertTrue($model->hasErrors('attr_one')); } /** From 7bdd602ad10c33f553a9a8e87a8f77bec3502426 Mon Sep 17 00:00:00 2001 From: Nobuo Kihara Date: Wed, 11 May 2016 21:13:43 +0900 Subject: [PATCH 33/90] docs/guide-ja/security-best-practices.md updated [skip ci] --- docs/guide-ja/security-best-practices.md | 52 ++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/docs/guide-ja/security-best-practices.md b/docs/guide-ja/security-best-practices.md index bf5533c..35ada54 100644 --- a/docs/guide-ja/security-best-practices.md +++ b/docs/guide-ja/security-best-practices.md @@ -152,6 +152,43 @@ CSRF を回避するためには、常に次のことを守らなければなり 1. HTTP の規格、すなわち、GET はアプリケーションの状態を変更すべきではない、という規則に従うこと。 2. Yii の CSRF 保護を有効にしておくこと。 +場合によっては、コントローラやアクションの単位で CSRF 検証を無効化する必要があることがあるでしょう。 +これは、そのプロパティを設定することによって達成することが出来ます。 + +```php +namespace app\controllers; + +use yii\web\Controller; + +class SiteController extends Controller +{ + public $enableCsrfValidation = false; + + public function actionIndex() + { + // CSRF 検証はこのアクションおよびその他のアクションに対して適用されない + } + +} +``` + +特定のアクションに対して CSRF 検証を無効化したいときは、次のようにすることが出来ます。 +```php +namespace app\controllers; + +use yii\web\Controller; + +class SiteController extends Controller +{ + public function beforeAction($action) + { + // ... ここで何らかの条件に従って `$this->enableCsrfValidation` を設定する ... + // 親のメソッドを呼ぶ。プロパティが true であれば、その中で CSRF がチェックされる。 + return parent::beforeAction($action); + } +} +``` + ファイルの曝露を回避する ------------------------ @@ -175,3 +212,18 @@ Gii を使うと、データベース構造とコードに関する情報を得 デバッグツールバーは本当に必要でない限り本番環境では使用を避けるべきです。 これはアプリケーションと構成情報の全ての詳細を曝露することが出来ます。 どうしても必要な場合は、あなたの IP だけに適切にアクセス制限されていることを再度チェックしてください。 + +TLS によるセキュアな接続を使う +------------------------------ + +Yii が提供する機能には、クッキーや PHP セッションに依存するものがあります。 +これらのものは、接続が侵害された場合には、脆弱性となり得ます。 +アプリケーションが TLS によるセキュアな接続を使用している場合は、この危険性を減少させることが出来ます。 + +その設定の仕方については、あなたのウェブサーバのドキュメントの指示を参照してください。 +H5BP プロジェクトが提供する構成例を参考にすることも出来ます。 + +- [Nginx](https://github.com/h5bp/server-configs-nginx) +- [Apache](https://github.com/h5bp/server-configs-apache). +- [IIS](https://github.com/h5bp/server-configs-iis). +- [Lighttpd](https://github.com/h5bp/server-configs-lighttpd). From 5f769b1a7b766a90e08c413aa8b042f9133a4a9d Mon Sep 17 00:00:00 2001 From: Nobuo Kihara Date: Wed, 11 May 2016 21:14:06 +0900 Subject: [PATCH 34/90] docs/internals-ja updated [ci skip] --- docs/internals-ja/core-code-style.md | 6 ++++-- docs/internals-ja/git-workflow.md | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/internals-ja/core-code-style.md b/docs/internals-ja/core-code-style.md index 2f4fc6c..e956878 100644 --- a/docs/internals-ja/core-code-style.md +++ b/docs/internals-ja/core-code-style.md @@ -484,5 +484,7 @@ public function getEventHandlers($name) ### ディレクトリ/名前空間の名前 - 小文字を使います。 -- オブジェクトを表すものには複数形の名詞を使います (例えば、validators) -- 機能や特徴を表す名前には単数形を使います (例えば、web) +- オブジェクトを表すものには複数形の名詞を使います (例えば、validators)。 +- 機能や特徴を表す名前には単数形を使います (例えば、web)。 +- 出来れば単一の語の名前空間にします。 +- 単一の語が適切でない場合は、camelCase を使います。 diff --git a/docs/internals-ja/git-workflow.md b/docs/internals-ja/git-workflow.md index 484dcb6..3df9f1e 100644 --- a/docs/internals-ja/git-workflow.md +++ b/docs/internals-ja/git-workflow.md @@ -131,7 +131,7 @@ git checkout -b 999-name-of-your-branch-goes-here ### 5. CHANGELOG を更新する CHANGELOG ファイルを編集して、あなたの修正を追加します。 -新しい行は、ファイル冒頭の "Work in progress" 見出しの下に挿入してください。 +新しい行は、ファイル冒頭の最初の見出し (現在開発中のバージョン) の下に挿入してください。 チェンジログの行は、下記のどちらかのように書いてください。 ``` From b5ff914d809dea0a532f5fbb4640956f328ab9f9 Mon Sep 17 00:00:00 2001 From: Klimov Paul Date: Wed, 11 May 2016 15:14:36 +0300 Subject: [PATCH 35/90] issue #11507 added to CHANGLOG --- framework/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index c0813f2..48a63a2 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -13,6 +13,7 @@ Yii Framework 2 Change Log - Enh #11438: Configurable `yii\helpers\Markdown` default flavor (mdmunir) - Bug #11459: Fixed flash messages not destroyed when `session.auto_start = 1` set in php.ini (cartmanchen) - Bug #11498: Fixed inability to save serialized object into PostgreSQL binary column (klimov-paul) +- Bug #11507: Fixed `yii\validators\EachValidator::validateAttribute()` does not respect `skipOnEmpty` rule parameter (webdevsega) 2.0.8 April 28, 2016 From f7ff153fce3902407f17677c611cfa5f895033e3 Mon Sep 17 00:00:00 2001 From: SilverFire - Dmitry Naumenko Date: Thu, 12 May 2016 01:30:17 +0300 Subject: [PATCH 36/90] Fixed `yii\i18n\PhpMessageSource::loadFallbackMessages()` not to log error when source and language is same, but locales are different --- framework/CHANGELOG.md | 1 + framework/i18n/GettextMessageSource.php | 6 ++- framework/i18n/PhpMessageSource.php | 6 ++- tests/framework/i18n/DbMessageSourceTest.php | 16 +++++- tests/framework/i18n/I18NTest.php | 74 +++++++++++++++++++++++----- 5 files changed, 88 insertions(+), 15 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 48a63a2..ca467af 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -7,6 +7,7 @@ Yii Framework 2 Change Log - Enh #11490: Added `yii\data\ArrayDataProvider::$modelClass` property to specify a model used to provide column labels even when data array is empty (PowerGamer1) - Bug #9950: Updated `yii\grid\DataColumn::getHeaderCellLabel()` to extract attribute label from the `filterModel` of Grid (silverfire) +- Bug #11429: Fixed `yii\i18n\PhpMessageSource::loadFallbackMessages()` not to log error when source and language is same, but locales are different (silverfire) - Enh #11428: Speedup SQL query in `yii\db\oci\Schema::findColumns()` (SSiwek) - Enh #11414: Files specified as `null` in `yii\web\AssetBundle` won't be registered (Razzwan) - Enh #11432: Added HTTP status 421 "Misdirected Request" to list of statuses in `yii\web\Response` (dasmfm) diff --git a/framework/i18n/GettextMessageSource.php b/framework/i18n/GettextMessageSource.php index 215a977..18981a6 100644 --- a/framework/i18n/GettextMessageSource.php +++ b/framework/i18n/GettextMessageSource.php @@ -103,7 +103,11 @@ class GettextMessageSource extends MessageSource $fallbackMessageFile = $this->getMessageFilePath($fallbackLanguage); $fallbackMessages = $this->loadMessagesFromFile($fallbackMessageFile, $category); - if ($messages === null && $fallbackMessages === null && $fallbackLanguage !== $this->sourceLanguage) { + if ( + $messages === null && $fallbackMessages === null + && $fallbackLanguage !== $this->sourceLanguage + && $fallbackLanguage !== substr($this->sourceLanguage, 0, 2) + ) { Yii::error("The message file for category '$category' does not exist: $originalMessageFile " . "Fallback file does not exist as well: $fallbackMessageFile", __METHOD__); } elseif (empty($messages)) { diff --git a/framework/i18n/PhpMessageSource.php b/framework/i18n/PhpMessageSource.php index 388954a..1aa4a8a 100644 --- a/framework/i18n/PhpMessageSource.php +++ b/framework/i18n/PhpMessageSource.php @@ -105,7 +105,11 @@ class PhpMessageSource extends MessageSource $fallbackMessageFile = $this->getMessageFilePath($category, $fallbackLanguage); $fallbackMessages = $this->loadMessagesFromFile($fallbackMessageFile); - if ($messages === null && $fallbackMessages === null && $fallbackLanguage !== $this->sourceLanguage) { + if ( + $messages === null && $fallbackMessages === null + && $fallbackLanguage !== $this->sourceLanguage + && $fallbackLanguage !== substr($this->sourceLanguage, 0, 2) + ) { Yii::error("The message file for category '$category' does not exist: $originalMessageFile " . "Fallback file does not exist as well: $fallbackMessageFile", __METHOD__); } elseif (empty($messages)) { diff --git a/tests/framework/i18n/DbMessageSourceTest.php b/tests/framework/i18n/DbMessageSourceTest.php index ba6ffa7..8215d73 100644 --- a/tests/framework/i18n/DbMessageSourceTest.php +++ b/tests/framework/i18n/DbMessageSourceTest.php @@ -29,13 +29,19 @@ class DbMessageSourceTest extends I18NTest { $this->i18n = new I18N([ 'translations' => [ - 'test' => new DbMessageSource([ + 'test' => [ + 'class' => $this->getMessageSourceClass(), 'db' => static::$db, - ]) + ] ] ]); } + private function getMessageSourceClass() + { + return DbMessageSource::className(); + } + protected static function runConsoleAction($route, $params = []) { if (Yii::$app === null) { @@ -153,4 +159,10 @@ class DbMessageSourceTest extends I18NTest $this->assertEquals('Hallo Welt!', $this->i18n->translate('test', 'Hello world!', [], 'de-DE')); Event::off(DbMessageSource::className(), DbMessageSource::EVENT_MISSING_TRANSLATION); } + + + public function testIssue11429($sourceLanguage = null) + { + $this->markTestSkipped('DbMessageSource does not produce any errors when messages file is missing.'); + } } diff --git a/tests/framework/i18n/I18NTest.php b/tests/framework/i18n/I18NTest.php index 4b09231..5b558e5 100644 --- a/tests/framework/i18n/I18NTest.php +++ b/tests/framework/i18n/I18NTest.php @@ -7,6 +7,7 @@ namespace yiiunit\framework\i18n; +use Yii; use yii\base\Event; use yii\i18n\I18N; use yii\i18n\PhpMessageSource; @@ -35,13 +36,19 @@ class I18NTest extends TestCase { $this->i18n = new I18N([ 'translations' => [ - 'test' => new PhpMessageSource([ + 'test' => [ + 'class' => $this->getMessageSourceClass(), 'basePath' => '@yiiunit/data/i18n/messages', - ]) + ] ] ]); } + private function getMessageSourceClass() + { + return PhpMessageSource::className(); + } + public function testTranslate() { $msg = 'The dog runs fast.'; @@ -63,13 +70,14 @@ class I18NTest extends TestCase { $i18n = new I18N([ 'translations' => [ - '*' => new PhpMessageSource([ + '*' => [ + 'class' => $this->getMessageSourceClass(), 'basePath' => '@yiiunit/data/i18n/messages', 'fileMap' => [ 'test' => 'test.php', 'foo' => 'test.php', ], - ]) + ] ] ]); @@ -174,15 +182,15 @@ class I18NTest extends TestCase public function testUsingSourceLanguageForMissingTranslation() { - \Yii::$app->sourceLanguage = 'ru'; - \Yii::$app->language = 'en'; + Yii::$app->sourceLanguage = 'ru'; + Yii::$app->language = 'en'; $msg = '{n, plural, =0{Нет комментариев} =1{# комментарий} one{# комментарий} few{# комментария} many{# комментариев} other{# комментария}}'; - $this->assertEquals('5 комментариев', \Yii::t('app', $msg, ['n' => 5])); - $this->assertEquals('3 комментария', \Yii::t('app', $msg, ['n' => 3])); - $this->assertEquals('1 комментарий', \Yii::t('app', $msg, ['n' => 1])); - $this->assertEquals('21 комментарий', \Yii::t('app', $msg, ['n' => 21])); - $this->assertEquals('Нет комментариев', \Yii::t('app', $msg, ['n' => 0])); + $this->assertEquals('5 комментариев', Yii::t('app', $msg, ['n' => 5])); + $this->assertEquals('3 комментария', Yii::t('app', $msg, ['n' => 3])); + $this->assertEquals('1 комментарий', Yii::t('app', $msg, ['n' => 1])); + $this->assertEquals('21 комментарий', Yii::t('app', $msg, ['n' => 21])); + $this->assertEquals('Нет комментариев', Yii::t('app', $msg, ['n' => 0])); } /** @@ -213,6 +221,50 @@ class I18NTest extends TestCase Event::off(PhpMessageSource::className(), PhpMessageSource::EVENT_MISSING_TRANSLATION); } + public function sourceLanguageDataProvider() + { + return [ + ['en-GB'], + ['en'] + ]; + } + + /** + * @dataProvider sourceLanguageDataProvider + * @param $sourceLanguage + */ + public function testIssue11429($sourceLanguage) + { + $this->mockApplication(); + $this->setI18N(); + + Yii::$app->sourceLanguage = $sourceLanguage; + $logger = Yii::getLogger(); + $logger->messages = []; + $filter = function ($array) { + // Ensures that error message is related to PhpMessageSource + $className = $this->getMessageSourceClass(); + return substr_compare($array[2], $className, 0, strlen($className)) === 0; + }; + + $this->assertEquals('The dog runs fast.', $this->i18n->translate('test', 'The dog runs fast.', [], 'en-GB')); + $this->assertEquals([], array_filter($logger->messages, $filter)); + + $this->assertEquals('The dog runs fast.', $this->i18n->translate('test', 'The dog runs fast.', [], 'en')); + $this->assertEquals([], array_filter($logger->messages, $filter)); + + $this->assertEquals('The dog runs fast.', $this->i18n->translate('test', 'The dog runs fast.', [], 'en-CA')); + $this->assertEquals([], array_filter($logger->messages, $filter)); + + $this->assertEquals('The dog runs fast.', $this->i18n->translate('test', 'The dog runs fast.', [], 'hz-HZ')); + $this->assertCount(1, array_filter($logger->messages, $filter)); + $logger->messages = []; + + $this->assertEquals('The dog runs fast.', $this->i18n->translate('test', 'The dog runs fast.', [], 'hz')); + $this->assertCount(1, array_filter($logger->messages, $filter)); + $logger->messages = []; + } + /** * Formatting a message that contains params but they are not provided. * https://github.com/yiisoft/yii2/issues/10884 From e9abf085e1fc44ec1ba1c896d201b72d38beb238 Mon Sep 17 00:00:00 2001 From: Shirshov Alexander Date: Thu, 12 May 2016 13:19:16 +0400 Subject: [PATCH 37/90] Update start-workflow.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit исправление опечатки --- docs/guide-ru/start-workflow.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide-ru/start-workflow.md b/docs/guide-ru/start-workflow.md index b448bc4..cbd51d5 100644 --- a/docs/guide-ru/start-workflow.md +++ b/docs/guide-ru/start-workflow.md @@ -56,7 +56,7 @@ basic/ корневой каталог приложения В целом, приложение Yii можно разделить на две категории файлов: расположенные в `basic/web` и расположенные в других директориях. Первая категория доступна через Web (например, браузером), вторая не доступна из вне и не должна быть доступной т.к. содержит служебную информацию. -В Yii реализована [архитектурный паттерн MVC](http://ru.wikipedia.org/wiki/Model-View-Controller), +В Yii реализован [архитектурный паттерн MVC](http://ru.wikipedia.org/wiki/Model-View-Controller), которая соответствует структуре директорий приложения. В директории `models` находятся [Модели](structure-models.md), в `views` расположены [Виды](structure-views.md), а в каталоге `controllers` все [Контроллеры](structure-controllers.md) приложения. From bc63b7607fce5d1282067d0d722c726280acb57a Mon Sep 17 00:00:00 2001 From: SilverFire - Dmitry Naumenko Date: Thu, 12 May 2016 16:33:10 +0300 Subject: [PATCH 38/90] Fixed PHPDoc for BaseArrayHelper::index() Closes #11556 --- framework/helpers/BaseArrayHelper.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/framework/helpers/BaseArrayHelper.php b/framework/helpers/BaseArrayHelper.php index ba269a5..aa6c554 100644 --- a/framework/helpers/BaseArrayHelper.php +++ b/framework/helpers/BaseArrayHelper.php @@ -265,6 +265,7 @@ class BaseArrayHelper * ``` * * The result will be an associative array, where the key is the value of `id` attribute + * * ```php * [ * '123' => ['id' => '123', 'data' => 'abc', 'device' => 'laptop'], @@ -274,6 +275,7 @@ class BaseArrayHelper * ``` * * An anonymous function can be used in the grouping array as well. + * * ```php * $result = ArrayHelper::index($array, function ($element) { * return $element['id']; @@ -281,12 +283,14 @@ class BaseArrayHelper * ``` * * Passing `id` as a third argument will group `$array` by `id`: + * * ```php * $result = ArrayHelper::index($array, null, 'id'); * ``` * * The result will be a multidimensional array grouped by `id` on the first level, by `device` on the second level * and indexed by `data` on the third level: + * * ```php * [ * '123' => [ @@ -300,6 +304,7 @@ class BaseArrayHelper * ``` * * The anonymous function can be used in the array of grouping keys as well: + * * ```php * $result = ArrayHelper::index($array, 'data', [function ($element) { * return $element['id']; @@ -308,6 +313,7 @@ class BaseArrayHelper * * The result will be a multidimensional array grouped by `id` on the first level, by the `device` on the second one * and indexed by the `data` on the third level: + * * ```php * [ * '123' => [ From 449a572be5add6e837a55df1ebe70f99566ea582 Mon Sep 17 00:00:00 2001 From: Misbahul D Munir Date: Fri, 13 May 2016 17:36:52 +0700 Subject: [PATCH 39/90] translate guide to bahasa (#11563) --- docs/guide-id/start-hello.md | 143 ++++++++++++++++++++++ docs/guide-id/start-installation.md | 238 ++++++++++++++++++++++++++++++++++++ docs/guide-id/start-workflow.md | 102 ++++++++++++++++ 3 files changed, 483 insertions(+) create mode 100644 docs/guide-id/start-hello.md create mode 100644 docs/guide-id/start-installation.md create mode 100644 docs/guide-id/start-workflow.md diff --git a/docs/guide-id/start-hello.md b/docs/guide-id/start-hello.md new file mode 100644 index 0000000..f20a043 --- /dev/null +++ b/docs/guide-id/start-hello.md @@ -0,0 +1,143 @@ +Katakan Hello +============ + +Bagian ini menjelaskan cara membuat halaman "Hello" baru dalam aplikasi Anda. +Untuk mencapai tujuan ini, Anda akan membuat [action](structure-controllers.md#creating-actions) dan +sebuah [view](structure-views.md): + +* Aplikasi ini akan mengirimkan permintaan halaman ke `action`. +* Dan `action` pada gilirannya akan membuat tampilan yang menunjukkan kata "Hello" kepada pengguna akhir. + +Melalui tutorial ini, Anda akan belajar tiga hal: + +1. Cara membuat [action](structure-controllers.md#creating-actions) untuk menanggapi permintaan, +2. Cara membuat [view](structure-views.md) untuk menyusun konten respon, dan +3. bagaimana aplikasi mengirimkan permintaan ke [action](structure-controllers.md#creating-actions). + + +Membuat Action +--------------- + +Untuk tugas "Hello", Anda akan membuat [action](structure-controllers.md#creating-actions) `say` yang membaca +parameter `message` dari request dan menampilkan pesan bahwa kembali ke pengguna. Jika request +tidak memberikan parameter `message`, aksi akan menampilkan pesan "Hello". + +> Info: [Action](structure-controllers.md#creating-actions) adalah objek yang pengguna akhir dapat langsung merujuk ke +  eksekusi. Action dikelompokkan berdasarkan [controllers](structure-controllers.md). Hasil eksekusi +  action adalah respon yang pengguna akhir akan terima. + +Action harus dinyatakan di [controllers](structure-controllers.md). Untuk mempermudah, Anda mungkin +mendeklarasikan action `say` di` SiteController` yang ada. kontroler ini didefinisikan +dalam file kelas `controllers/SiteController.php`. Berikut adalah awal dari action baru: + +```php +render('say', ['message' => $message]); + } +} +``` + +Pada kode di atas, action `say` didefinisikan sebagai metode bernama` actionSay` di kelas `SiteController`. +Yii menggunakan awalan `action` untuk membedakan metode action dari metode non-action dalam kelas controller. +Nama setelah awalan `action` peta untuk ID tindakan ini. + +Untuk sampai pada penamaan action, Anda harus memahami bagaimana Yii memperlakukan ID action. ID action selalu +direferensikan dalam huruf kecil. Jika ID tindakan membutuhkan beberapa kata, mereka akan digabungkan dengan `tanda hubung` +(Mis, `create-comment`). nama metode aksi yang dipetakan ke ID tindakan diperoleh dengan menghapus tanda hubung apapun dari ID, +mengkapitalkan huruf pertama di setiap kata, dan awalan string yang dihasilkan dengan `action`. Sebagai contoh, +ID action `create-comment` sesuai dengan nama method action `actionCreateComment`. + +Metode action dalam contoh kita mengambil parameter `$message`, yang nilai defaultnya adalah `"Hello"` (persis +dengan cara yang sama Anda menetapkan nilai default untuk fungsi atau metode apapun argumen di PHP). Ketika aplikasi +menerima permintaan dan menentukan bahwa action `say` bertanggung jawab untuk penanganan request, aplikasi akan +mengisi parameter ini dengan parameter bernama sama yang ditemukan dalam request. Dengan kata lain, jika permintaan mencakup +a parameter `message` dengan nilai` "Goodbye" `, maka variabel `$message` dalam aksi akan ditugaskan nilai itu. + +Dalam metode action, [[yii\web\Controller::render()|render()]] dipanggil untuk membuat +sebuah [view](structure-views.md) dari file bernama `say`. Parameter `message` juga diteruskan ke view +sehingga dapat digunakan di sana. Hasil render dikembalikan dengan metode tindakan. Hasil yang akan diterima +oleh aplikasi dan ditampilkan kepada pengguna akhir di browser (sebagai bagian dari halaman HTML yang lengkap). + + +Membuat View +--------------- + +[View](structure-views.md) adalah skrip yang Anda tulis untuk menghasilkan konten respon. +Untuk "Hello" tugas, Anda akan membuat view `say` yang mencetak parameter `message` yang diterima dari metode aksi: + +```php + + +``` + +View `say` harus disimpan dalam file `views/site/say.php`. Ketika metode [[yii\web\Controller::render()|render()]] +disebut dalam tindakan, itu akan mencari file PHP bernama `views/ControllerID/ViewName.php`. + +Perhatikan bahwa dalam kode di atas, parameter `message` adalah di-[[yii\helpers\Html::encode()|HTML-encoded]] +sebelum dicetak. Hal ini diperlukan karena sebagai parameter yang berasal dari pengguna akhir, sangat rentan terhadap +[serangan Cross-site scripting (XSS)](http://en.wikipedia.org/wiki/Cross-site_scripting) dengan melekatkan +kode JavaScript berbahaya dalam parameter. + +Tentu, Anda dapat menempatkan lebih banyak konten di view `say`. konten dapat terdiri dari tag HTML, teks biasa, dan bahkan pernyataan PHP. +Nyatanya, view `say` hanyalah sebuah script PHP yang dijalankan oleh metode [[yii\web\Controller::render()|render()]]. +Isi dicetak oleh skrip view akan dikembalikan ke aplikasi sebagai hasil respon ini. Aplikasi ini pada gilirannya akan mengeluarkan hasil ini kepada pengguna akhir. + + +Trying it Out +------------- + +Setelah membuat action dan view, Anda dapat mengakses halaman baru dengan mengakses URL berikut: + +``` +http://hostname/index.php?r=site%2Fsay&message=Hello+World +``` + +![Hello World](images/start-hello-world.png) + +URL ini akan menghasilkan halaman yang menampilkan "Hello World". Halaman yang berbagi header dan footer yang sama dengan halaman aplikasi lainnya. + +Jika Anda menghilangkan parameter `message` dalam URL, Anda akan melihat tampilan halaman "Hello". Hal ini karena `message` dilewatkan sebagai parameter untuk metode `actionSay()`, dan ketika itu dihilangkan, +nilai default `"Hello"` akan digunakan sebagai gantinya. + +> Info: Halaman baru berbagi header dan footer yang sama dengan halaman lain karena metode [[yii\web\Controller::render()|render()]] +  otomatis akan menanamkan hasil view `say` kedalam apa yang disebut [layout](structure-views.md#layouts) yang dalam hal ini +  Kasus terletak di `views/layouts/main.php`. + +Parameter `r` di URL di atas memerlukan penjelasan lebih lanjut. Ini adalah singkatan dari [route](runtime-routing.md), sebuah ID unik aplikasi +yang mengacu pada action. format rute ini adalah `ControllerID/ActionID`. Ketika aplikasi menerima +permintaan, itu akan memeriksa parameter ini, menggunakan bagian `ControllerID` untuk menentukan kontroler +kelas harus dipakai untuk menangani permintaan. Kemudian, controller akan menggunakan bagian `ActionID` +untuk menentukan action yang harus dipakai untuk melakukan pekerjaan yang sebenarnya. Dalam contoh kasus ini, rute `site/say` +akan diselesaikan dengan kontroler kelas `SiteController` dan action `say`. Sebagai hasilnya, +metode `SiteController::actionSay()` akan dipanggil untuk menangani permintaan. + +> Info: Seperti action, kontroler juga memiliki ID yang unik mengidentifikasi mereka dalam sebuah aplikasi. +  ID kontroler menggunakan aturan penamaan yang sama seperti ID tindakan. nama kelas controller yang berasal dari +  kontroler ID dengan menghapus tanda hubung dari ID, memanfaatkan huruf pertama di setiap kata, +  dan suffixing string yang dihasilkan dengan kata `Controller`. Misalnya, controller ID `post-comment` berkorespondensi +  dengan nama kelas controller `PostCommentController`. + + +Ringkasan +------- + +Pada bagian ini, Anda telah menyentuh controller dan melihat bagian dari pola arsitektur MVC. +Anda menciptakan sebuah action sebagai bagian dari controller untuk menangani permintaan khusus. Dan Anda juga menciptakan view +untuk menulis konten respon ini. Dalam contoh sederhana ini, tidak ada model yang terlibat. Satu-satunya data yang digunakan adalah parameter `message`. + +Anda juga telah belajar tentang rute di Yii, yang bertindak sebagai jembatan antara permintaan pengguna dan tindakan controller. + +Pada bagian berikutnya, Anda akan belajar cara membuat model, dan menambahkan halaman baru yang berisi bentuk HTML. diff --git a/docs/guide-id/start-installation.md b/docs/guide-id/start-installation.md new file mode 100644 index 0000000..d79556a --- /dev/null +++ b/docs/guide-id/start-installation.md @@ -0,0 +1,238 @@ +Instalasi Yii +============== + +Anda dapat menginstal Yii dalam dua cara, menggunakan [Composer](https://getcomposer.org/) paket manager atau dengan mengunduh file arsip. +Yang pertama adalah cara yang lebih disukai, karena memungkinkan Anda untuk menginstal [ekstensi](structure-extensions.md) baru atau memperbarui Yii dengan hanya menjalankan *command line*. + +Hasil instalasi standar Yii baik framework maupun template proyek keduanya akan terunduh dan terpasang. +Sebuah template proyek adalah proyek Yii yang menerapkan beberapa fitur dasar, seperti login, formulir kontak, dll. +Kode diatur dalam cara yang direkomendasikan. Oleh karena itu, dapat berfungsi sebagai titik awal yang baik untuk proyek-proyek Anda. +     +Dalam hal ini dan beberapa bagian berikutnya, kita akan menjelaskan cara menginstal Yii dengan apa yang disebut *Template Proyek Dasar* dan +bagaimana menerapkan fitur baru di atas template ini. Yii juga menyediakan template lain yang disebut +yang [Template Proyek Lanjutan](https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide/README.md) yang lebih baik digunakan dalam lingkungan pengembangan tim +untuk mengembangkan aplikasi dengan beberapa tingkatan. + +> Info: Template Proyek Dasar ini cocok untuk mengembangkan 90 persen dari aplikasi Web. Ini berbeda +  dari Template Proyek Lanjutan terutama dalam bagaimana kode mereka diatur. Jika Anda baru untuk Yii, kami sangat +  merekomendasikan Anda tetap pada Template Proyek Dasar untuk kesederhanaan dan fungsi yang cukup. + + +Menginstal melalui Komposer +----------------------- + +Jika Anda belum memiliki Composer terinstal, Anda dapat melakukannya dengan mengikuti petunjuk di +[getcomposer.org] (https://getcomposer.org/download/). Pada Linux dan Mac OS X, Anda akan menjalankan perintah berikut: + +```bash +curl -sS https://getcomposer.org/installer | php +mv composer.phar /usr/local/bin/composer +``` + +Pada Windows, Anda akan mengunduh dan menjalankan [Composer-Setup.exe](https://getcomposer.org/Composer-Setup.exe). + +Silakan merujuk ke [Dokumentasi Composer](https://getcomposer.org/doc/) jika Anda menemukan +masalah atau ingin mempelajari lebih lanjut tentang penggunaan Composer. + +Jika Composer sudah terinstal sebelumnya, pastikan Anda menggunakan versi terbaru. Anda dapat memperbarui Komposer +dengan menjalankan `composer self-update`. + +Dengan Komposer diinstal, Anda dapat menginstal Yii dengan menjalankan perintah berikut di bawah folder yang terakses web: + +```bash +composer global require "fxp/composer-asset-plugin:~1.1.1" +composer create-project --prefer-dist yiisoft/yii2-app-basic basic +``` + +Perintah pertama menginstal [komposer aset Plugin](https://github.com/francoispluchino/composer-asset-plugin/) +yang memungkinkan mengelola bower dan paket npm melalui Composer. Anda hanya perlu menjalankan perintah ini +sekali untuk semua. Perintah kedua menginstal Yii dalam sebuah direktori bernama `basic`. Anda dapat memilih nama direktori yang berbeda jika Anda ingin. + +> Catatan: Selama instalasi, Composer dapat meminta login Github Anda. Ini normal karena Komposer +> Perlu mendapatkan cukup API rate-limit untuk mengambil informasi paket dari Github. Untuk lebih jelasnya, +> Silahkan lihat [Documentation Composer](https://getcomposer.org/doc/articles/troubleshooting.md#api-rate-limit-and-oauth-tokens). + +> Tip: Jika Anda ingin menginstal versi pengembangan terbaru dari Yii, Anda dapat menggunakan perintah berikut sebagai gantinya, +> Yang menambahkan [opsi stabilitas](https://getcomposer.org/doc/04-schema.md#minimum-stability): +> +> ```bash +> composer create-project --prefer-dist --stability=dev yiisoft/yii2-app-basic basic +> ``` +> +> Perhatikan bahwa versi pengembangan dari Yii tidak boleh digunakan untuk produksi karena kemungkinan dapat *merusak* kode Anda yang sedang berjalan. + + +Instalasi dari file Arsip +------------------------------- + +Instalasi Yii dari file arsip melibatkan tiga langkah: + +1. Download file arsip dari [yiiframework.com](http://www.yiiframework.com/download/). +2. Uraikan file yang didownload ke folder yang bisa diakses web. +3. Memodifikasi `config/web.php` dengan memasukkan kunci rahasia untuk `cookieValidationKey`. +   (Ini dilakukan secara otomatis jika Anda menginstal Yii menggunakan Composer): + + ```php + // !!! Isikan nilai key jika kosong - ini diperlukan oleh cookie validation + 'cookieValidationKey' => 'enter your secret key here', + ``` + + +Pilihan Instalasi lainnya +-------------------------- + +Petunjuk instalasi di atas menunjukkan cara menginstal Yii, yang juga menciptakan aplikasi Web dasar yang bekerja di luar kotak. +Pendekatan ini adalah titik awal yang baik untuk sebagian besar proyek, baik kecil atau besar. Hal ini terutama cocok jika Anda hanya +mulai belajar Yii. + +Tetapi ada pilihan instalasi lain yang tersedia: + +* Jika Anda hanya ingin menginstal kerangka inti dan ingin membangun seluruh aplikasi dari awal, +  Anda dapat mengikuti petunjuk seperti yang dijelaskan dalam [Membangun Aplikasi dari Scratch](tutorial-start-from-scratch.md). +* Jika Anda ingin memulai dengan aplikasi yang lebih canggih, lebih cocok untuk tim lingkungan pengembangan, +  Anda dapat mempertimbangkan memasang [Template Lanjutan Proyek] (https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide/README.md). + + +Memverifikasi Instalasi +-------------------------- + +Setelah instalasi selesai, baik mengkonfigurasi web server Anda (lihat bagian berikutnya) atau menggunakan +[Built-in web server PHP] (https://secure.php.net/manual/en/features.commandline.webserver.php) dengan menjalankan berikut +konsol perintah sementara dalam proyek `web` direktori: + +```bash +php yii serve +``` + +> Catatan: Secara default HTTP-server akan mendengarkan port 8080. Namun jika port yang sudah digunakan atau Anda ingin +melayani beberapa aplikasi dengan cara ini, Anda mungkin ingin menentukan port apa yang harus digunakan. Cukup tambahkan argumen --port: + +```bash +php yii serve --port = 8888 +``` + +Anda dapat menggunakan browser untuk mengakses aplikasi Yii yang diinstal dengan URL berikut: + +``` +http://localhost:8080/ +``` + +![Instalasi Sukses dari Yii](images/start-app-installed.png) + +Anda seharusnya melihat halaman "Congratulations!" di browser Anda. Jika tidak, periksa apakah instalasi PHP Anda memenuhi +persyaratan Yii. Anda dapat memeriksa apakah persyaratan minimumnya cocok dengan menggunakan salah satu pendekatan berikut: + +* Copy `/requirements.php` ke `/web/requirements.php` kemudian gunakan browser untuk mengakses melalui `http://localhost/requirements.php` +* Jalankan perintah berikut: + +  ```bash +  cd basic +  php requirements.php +  ``` + +Anda harus mengkonfigurasi instalasi PHP Anda sehingga memenuhi persyaratan minimal Yii. Yang paling penting, Anda +harus memiliki PHP versi 5.4 atau lebih. Anda juga harus menginstal [PDO PHP Ekstensi](http://www.php.net/manual/en/pdo.installation.php) +dan driver database yang sesuai (seperti `pdo_mysql` untuk database MySQL), jika aplikasi Anda membutuhkan database. + + +Konfigurasi Web Server +----------------------- + +> Info: Anda dapat melewati seksi ini untuk saat ini jika Anda hanya menguji sebuah Yii dengan niat +  penggelaran itu untuk server produksi. + +Aplikasi yang diinstal sesuai dengan petunjuk di atas seharusnya bekerja dengan baik +pada [Apache HTTP server](http://httpd.apache.org/) atau [Nginx HTTP server](http://nginx.org/), pada +Windows, Mac OS X, atau Linux yang menjalankan PHP 5.4 atau lebih tinggi. Yii 2.0 juga kompatibel dengan facebook +[HHVM](http://hhvm.com/). Namun, ada beberapa kasus di mana HHVM berperilaku berbeda dari PHP asli, +sehingga Anda harus mengambil beberapa perlakuan ekstra ketika menggunakan HHVM. + +Pada server produksi, Anda mungkin ingin mengkonfigurasi server Web Anda sehingga aplikasi dapat diakses +melalui URL `http://www.example.com/index.php` bukannya `http://www.example.com/dasar/web/index.php`. konfigurasi seperti itu +membutuhkan root dokumen server Web Anda menunjuk ke folder `basic/web`. Anda mungkin juga +ingin menyembunyikan `index.php` dari URL, seperti yang dijelaskan pada bagian [Routing dan Penciptaan URL](runtime-routing.md). +Dalam bagian ini, Anda akan belajar bagaimana untuk mengkonfigurasi Apache atau Nginx server Anda untuk mencapai tujuan tersebut. + +> Info: Dengan menetapkan `basic/web` sebagai akar dokumen, Anda juga mencegah pengguna akhir mengakses +kode private aplikasi Anda dan file data sensitif yang disimpan dalam direktori sejajar +dari `basic/web`. Mencegah akses ke folder lainnya adalah sebuah peningkatan keamanan. + +> Info: Jika aplikasi Anda akan berjalan di lingkungan shared hosting di mana Anda tidak memiliki izin +untuk memodifikasi konfigurasi server Web-nya, Anda mungkin masih menyesuaikan struktur aplikasi Anda untuk keamanan yang lebih baik. Silakan merujuk ke +yang lebih baik. Lihat bagian [Shared Hosting Lingkungan](tutorial-shared-hosting.md) untuk rincian lebih lanjut. + + +### Konfigurasi Apache yang Direkomendasikan + +Gunakan konfigurasi berikut di file `httpd.conf` Apache atau dalam konfigurasi virtual host. Perhatikan bahwa Anda +harus mengganti `path/to/basic/web` dengan path ` dasar/web` yang sebenarnya. + +```apache +# Set document root to be "basic/web" +DocumentRoot "path/to/basic/web" + + + # use mod_rewrite for pretty URL support + RewriteEngine on + # If a directory or a file exists, use the request directly + RewriteCond %{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_FILENAME} !-d + # Otherwise forward the request to index.php + RewriteRule . index.php + + # ...other settings... + +``` + + +### Konfigurasi Nginx yang Direkomendasikan + +Untuk menggunakan [Nginx](http://wiki.nginx.org/), Anda harus menginstal PHP sebagai [FPM SAPI](http://php.net/install.fpm). +Anda dapat menggunakan konfigurasi Nginx berikut, menggantikan `path/to/basic/web` dengan path yang sebenarnya untuk +`basic/web` dan `mysite.local` dengan hostname yang sebenarnya untuk server. + +```nginx +server { + charset utf-8; + client_max_body_size 128M; + + listen 80; ## listen for ipv4 + #listen [::]:80 default_server ipv6only=on; ## listen for ipv6 + + server_name mysite.local; + root /path/to/basic/web; + index index.php; + + access_log /path/to/basic/log/access.log; + error_log /path/to/basic/log/error.log; + + location / { + # Redirect everything that isn't a real file to index.php + try_files $uri $uri/ /index.php$is_args$args; + } + + # uncomment to avoid processing of calls to non-existing static files by Yii + #location ~ \.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ { + # try_files $uri =404; + #} + #error_page 404 /404.html; + + location ~ \.php$ { + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_pass 127.0.0.1:9000; + #fastcgi_pass unix:/var/run/php5-fpm.sock; + try_files $uri =404; + } + + location ~ /\.(ht|svn|git) { + deny all; + } +} +``` + +Bila menggunakan konfigurasi ini, Anda juga harus menetapkan `cgi.fix_pathinfo=0` di file` php.ini` +untuk menghindari banyak panggilan `stat()` sistem yang tidak perlu. + +Sekalian catat bahwa ketika menjalankan server HTTPS, Anda perlu menambahkan `fastcgi_param HTTPS on;` sehingga Yii +benar dapat mendeteksi jika sambungan aman. \ No newline at end of file diff --git a/docs/guide-id/start-workflow.md b/docs/guide-id/start-workflow.md new file mode 100644 index 0000000..eaa19b5 --- /dev/null +++ b/docs/guide-id/start-workflow.md @@ -0,0 +1,102 @@ +Menjalankan Aplikasi +==================== + +Setelah menginstal Yii, Anda memiliki aplikasi Yii yang dapat diakses melalui +URL `http://hostname/basic/web/index.php` atau `http://hostname/index.php`, tergantung +pada konfigurasi Anda. Bagian ini akan memperkenalkan fungsi built-in aplikasi, +bagaimana kode ini disusun, dan bagaimana aplikasi menangani permintaan secara umum. + +> Info: Untuk mempermudah, selama tutorial "Mulai", itu diasumsikan bahwa Anda telah menetapkan `basic/web` +  sebagai root dokumen server Web Anda, dan URL dikonfigurasi untuk mengakses +  aplikasi Anda untuk menjadi `http://hostname/index.php` atau sesuatu yang serupa. +  Untuk kebutuhan Anda, silakan menyesuaikan URL sesuai deskripsi kami. +   +Perhatikan bahwa tidak seperti framework itu sendiri, setelah template proyek diinstal, itu semua milikmu. Anda bebas untuk menambah atau menghapus +kode dan memodifikasi keseluruhannya sesuai yang Anda butuhkan. + + +Fungsi +------------- + +Aplikasi dasar diinstal berisi empat halaman: + +* Homepage, ditampilkan saat Anda mengakses URL `http://hostname/index.php`, +* Halaman "About", +* Halaman "Contact", yang menampilkan formulir kontak yang memungkinkan pengguna akhir untuk menghubungi Anda melalui email, +* Dan halaman "Login", yang menampilkan form login yang dapat digunakan untuk otentikasi pengguna akhir. Cobalah masuk +  dengan "admin/admin", dan Anda akan menemukan item "Login" di menu utama akan berubah menjadi "Logout". + +Halaman ini berbagi header umum dan footer. header berisi menu bar utama untuk memungkinkan navigasi +antara halaman yang berbeda. + +Anda juga harus melihat toolbar di bagian bawah jendela browser. +Ini adalah [debugger tool](https://github.com/yiisoft/yii2-debug/blob/master/docs/guide/README.md) yang disediakan oleh Yii +untuk merekam dan menampilkan banyak informasi debug, seperti log pesan, status respon, query database berjalan, dan sebagainya. + +Selain itu untuk aplikasi web, ada script konsol yang disebut `yii`, yang terletak di direktori aplikasi dasar. +Script ini dapat digunakan untuk menjalankan aplikasi background dan tugas pemeliharaan untuk aplikasi, yang diuraikan +di bagian [Console Application](tutorial-console.md). + + +Struktur aplikasi +--------------------- + +Direktori yang paling penting dan file dalam aplikasi Anda (dengan asumsi direktori root aplikasi adalah `basic`): + +``` +basic/ path aplikasi dasar + composer.json digunakan oleh Composer, package information + config/ berisi konfigurasi aplikasi dan yang lain + console.php konfigurasi aplikasi konsole + web.php konfigurasi aplikasi web + commands/ contains console command classes + controllers/ contains controller classes + models/ contains model classes + runtime/ contains files generated by Yii during runtime, such as logs and cache files + vendor/ contains the installed Composer packages, including the Yii framework itself + views/ contains view files + web/ application Web root, contains Web accessible files + assets/ contains published asset files (javascript and css) by Yii + index.php the entry (or bootstrap) script for the application + yii the Yii console command execution script +``` + +Secara umum, file dalam aplikasi dapat dibagi menjadi dua jenis: mereka yang di bawah `basic/web` dan mereka yang +di bawah direktori lain. Yang pertama dapat langsung diakses melalui HTTP (yaitu, di browser), sedangkan yang kedua tidak dapat dan tidak seharusnya boleh. + +Yii mengimplementasikan pola arsitektur [model-view-controller (MVC)](http://wikipedia.org/wiki/Model-view-controller), +yang tercermin dalam organisasi direktori di atas. Direktori `models` berisi semua [Model kelas](structure-models.md), +direktori `views` berisi semua [view script] structure-views.md), dan direktori `controllers` mengandung +semua [kelas kontroler](structure-controllers.md). + +Diagram berikut memperlihatkan struktur statis dari sebuah aplikasi. + +![Struktur statis aplikasi](images/application-structure.png) + +Setiap aplikasi memiliki naskah entri `web/index.php` yang merupakan satu-satunya PHP skrip dalam aplikasi yang dapat diakses web. +Naskah entri mengambil permintaan masuk dan menciptakan [aplikasi](structure-applications.md) untuk menanganinya. +[Aplikasi](structure-applications.md) menyelesaikan permintaan dengan bantuan [komponen](concept-components.md)nya, +dan mengirimkan permintaan ke elemen MVC. [Widget](structure-widgets.md) digunakan dalam [view](structure-views.md) +untuk membantu membangun elemen antarmuka pengguna yang kompleks dan dinamis. + + +Daur Hidup Request +----------------- + +Diagram berikut menunjukkan bagaimana aplikasi menangani permintaan. + +![Request Lifecycle](images/request-lifecycle.png) + +1. Pengguna membuat permintaan ke [skrip entri](structure-entry-scripts.md) `web/index.php`. +2. Naskah entri memuat [konfigurasi](concept-configurations.md) aplikasi dan menciptakan +   [aplikasi](structure-applications.md) untuk menangani permintaan. +3. Aplikasi menyelesaikan [route](runtime-routing.md) yang diminta dengan bantuan +   komponen [request](runtime-requests.md) aplikasi. +4. Aplikasi ini menciptakan [kontroler](structure-controllers.md) untuk menangani permintaan. +5. Controller menciptakan [action](structure-controllers.md) dan melakukan filter untuk action. +6. Jika filter gagal, aksi dibatalkan. +7. Jika semua filter lulus, aksi dieksekusi. +8. Action memuat model data, mungkin dari database. +9. Aksi meyiapkan view, menyediakannya dengan model data. +10. Hasilnya diberikan dikembalikan ke komponen aplikasi [respon](runtime-responses.md). +11. Komponen respon mengirimkan hasil yang diberikan ke browser pengguna. \ No newline at end of file From d0a68883c73b0fc81d48ea6db7824164184cc94c Mon Sep 17 00:00:00 2001 From: Evgeniy Tkachenko Date: Fri, 13 May 2016 14:18:19 +0300 Subject: [PATCH 40/90] phpDoc of $layout updated --- framework/base/Controller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/base/Controller.php b/framework/base/Controller.php index 0dba943..aba43c7 100644 --- a/framework/base/Controller.php +++ b/framework/base/Controller.php @@ -50,7 +50,7 @@ class Controller extends Component implements ViewContextInterface */ public $defaultAction = 'index'; /** - * @var null|string|boolean the name of the layout to be applied to this controller's views. + * @var null|string|false the name of the layout to be applied to this controller's views. * This property mainly affects the behavior of [[render()]]. * Defaults to null, meaning the actual layout value should inherit that from [[module]]'s layout value. * If false, no layout will be applied. From 5210fc8e424e93424dc3605ce02f79276744c83d Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Fri, 13 May 2016 15:33:56 +0300 Subject: [PATCH 41/90] Fixes #11532: Fixed casting of empty char value to `null` resulting in integrity constraint violation for not null columns --- framework/CHANGELOG.md | 1 + framework/db/ColumnSchema.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 48a63a2..206a56c 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -14,6 +14,7 @@ Yii Framework 2 Change Log - Bug #11459: Fixed flash messages not destroyed when `session.auto_start = 1` set in php.ini (cartmanchen) - Bug #11498: Fixed inability to save serialized object into PostgreSQL binary column (klimov-paul) - Bug #11507: Fixed `yii\validators\EachValidator::validateAttribute()` does not respect `skipOnEmpty` rule parameter (webdevsega) +- Bug #11532: Fixed casting of empty char value to `null` resulting in integrity constraint violation for not null columns (samdark) 2.0.8 April 28, 2016 diff --git a/framework/db/ColumnSchema.php b/framework/db/ColumnSchema.php index 28041f3..64d007f 100644 --- a/framework/db/ColumnSchema.php +++ b/framework/db/ColumnSchema.php @@ -113,7 +113,7 @@ class ColumnSchema extends Object */ protected function typecast($value) { - if ($value === '' && $this->type !== Schema::TYPE_TEXT && $this->type !== Schema::TYPE_STRING && $this->type !== Schema::TYPE_BINARY) { + if ($value === '' && $this->type !== Schema::TYPE_TEXT && $this->type !== Schema::TYPE_STRING && $this->type !== Schema::TYPE_BINARY && $this->type !== Schema::TYPE_CHAR) { return null; } if ($value === null || gettype($value) === $this->phpType || $value instanceof Expression) { From 66eabda770c60e389d5a0a61de907d2162173106 Mon Sep 17 00:00:00 2001 From: Chris Harris Date: Sat, 26 Mar 2016 13:44:40 -0700 Subject: [PATCH 42/90] Fixes #11195: Added ability to append custom string to schema builder column definition --- framework/db/ColumnSchemaBuilder.php | 32 +++++++++++++++++++++++++++-- framework/db/cubrid/ColumnSchemaBuilder.php | 6 +++--- framework/db/mysql/ColumnSchemaBuilder.php | 6 +++--- framework/db/oci/ColumnSchemaBuilder.php | 6 +++--- framework/db/sqlite/ColumnSchemaBuilder.php | 6 +++--- 5 files changed, 42 insertions(+), 14 deletions(-) diff --git a/framework/db/ColumnSchemaBuilder.php b/framework/db/ColumnSchemaBuilder.php index e50b6c8..83e2c78 100644 --- a/framework/db/ColumnSchemaBuilder.php +++ b/framework/db/ColumnSchemaBuilder.php @@ -56,6 +56,11 @@ class ColumnSchemaBuilder extends Object */ protected $default; /** + * @var mixed SQL string to be appended to column schema string. + * @since 2.0.8 + */ + protected $plus; + /** * @var boolean whether the column values should be unsigned. If this is `true`, an `UNSIGNED` keyword will be added. * @since 2.0.7 */ @@ -237,6 +242,18 @@ class ColumnSchemaBuilder extends Object } /** + * Specify additional SQL to be appended to schema string. + * @param string $sql the SQL string to be appended. + * @return $this + * @since 2.0.8 + */ + public function plus($sql) + { + $this->plus = $sql; + return $this; + } + + /** * Builds the full string for the column's schema * @return string */ @@ -244,10 +261,10 @@ class ColumnSchemaBuilder extends Object { switch ($this->getTypeCategory()) { case self::CATEGORY_PK: - $format = '{type}{check}{comment}'; + $format = '{type}{check}{comment}{plus}'; break; default: - $format = '{type}{length}{notnull}{unique}{default}{check}{comment}'; + $format = '{type}{length}{notnull}{unique}{default}{check}{comment}{plus}'; } return $this->buildCompleteString($format); } @@ -357,6 +374,16 @@ class ColumnSchemaBuilder extends Object } /** + * Builds the first constraint for the column. Defaults to unsupported. + * @return string a string containing the FIRST constraint. + * @since 2.0.8 + */ + protected function buildPlusString() + { + return $this->plus !== null ? ' ' . $this->plus : ''; + } + + /** * Returns the category of the column type. * @return string a string containing the column type category name. * @since 2.0.8 @@ -396,6 +423,7 @@ class ColumnSchemaBuilder extends Object '{pos}' => ($this->isFirst) ? $this->buildFirstString() : $this->buildAfterString(), + '{plus}' => $this->buildPlusString(), ]; return strtr($format, $placeholderValues); } diff --git a/framework/db/cubrid/ColumnSchemaBuilder.php b/framework/db/cubrid/ColumnSchemaBuilder.php index c39e4b7..5e97c46 100644 --- a/framework/db/cubrid/ColumnSchemaBuilder.php +++ b/framework/db/cubrid/ColumnSchemaBuilder.php @@ -58,13 +58,13 @@ class ColumnSchemaBuilder extends AbstractColumnSchemaBuilder { switch ($this->getTypeCategory()) { case self::CATEGORY_PK: - $format = '{type}{check}{pos}{comment}'; + $format = '{type}{check}{pos}{comment}{plus}'; break; case self::CATEGORY_NUMERIC: - $format = '{type}{length}{unsigned}{notnull}{unique}{default}{check}{comment}{pos}'; + $format = '{type}{length}{unsigned}{notnull}{unique}{default}{check}{comment}{pos}{plus}'; break; default: - $format = '{type}{length}{notnull}{unique}{default}{check}{comment}{pos}'; + $format = '{type}{length}{notnull}{unique}{default}{check}{comment}{pos}{plus}'; } return $this->buildCompleteString($format); } diff --git a/framework/db/mysql/ColumnSchemaBuilder.php b/framework/db/mysql/ColumnSchemaBuilder.php index 8c338ec..fb8c6de 100644 --- a/framework/db/mysql/ColumnSchemaBuilder.php +++ b/framework/db/mysql/ColumnSchemaBuilder.php @@ -58,13 +58,13 @@ class ColumnSchemaBuilder extends AbstractColumnSchemaBuilder { switch ($this->getTypeCategory()) { case self::CATEGORY_PK: - $format = '{type}{length}{check}{comment}{pos}'; + $format = '{type}{length}{check}{comment}{pos}{plus}'; break; case self::CATEGORY_NUMERIC: - $format = '{type}{length}{unsigned}{notnull}{unique}{default}{check}{comment}{pos}'; + $format = '{type}{length}{unsigned}{notnull}{unique}{default}{check}{comment}{pos}{plus}'; break; default: - $format = '{type}{length}{notnull}{unique}{default}{check}{comment}{pos}'; + $format = '{type}{length}{notnull}{unique}{default}{check}{comment}{pos}{plus}'; } return $this->buildCompleteString($format); } diff --git a/framework/db/oci/ColumnSchemaBuilder.php b/framework/db/oci/ColumnSchemaBuilder.php index b44665b..383c009 100644 --- a/framework/db/oci/ColumnSchemaBuilder.php +++ b/framework/db/oci/ColumnSchemaBuilder.php @@ -51,13 +51,13 @@ class ColumnSchemaBuilder extends AbstractColumnSchemaBuilder { switch ($this->getTypeCategory()) { case self::CATEGORY_PK: - $format = '{type}{length}{check}{pos}'; + $format = '{type}{length}{check}{pos}{plus}'; break; case self::CATEGORY_NUMERIC: - $format = '{type}{length}{unsigned}{default}{notnull}{check}{pos}'; + $format = '{type}{length}{unsigned}{default}{notnull}{check}{pos}{plus}'; break; default: - $format = '{type}{length}{default}{notnull}{check}{pos}'; + $format = '{type}{length}{default}{notnull}{check}{pos}{plus}'; } return $this->buildCompleteString($format); } diff --git a/framework/db/sqlite/ColumnSchemaBuilder.php b/framework/db/sqlite/ColumnSchemaBuilder.php index 956170a..c5e797d 100644 --- a/framework/db/sqlite/ColumnSchemaBuilder.php +++ b/framework/db/sqlite/ColumnSchemaBuilder.php @@ -33,13 +33,13 @@ class ColumnSchemaBuilder extends AbstractColumnSchemaBuilder { switch ($this->getTypeCategory()) { case self::CATEGORY_PK: - $format = '{type}{check}'; + $format = '{type}{check}{plus}'; break; case self::CATEGORY_NUMERIC: - $format = '{type}{length}{unsigned}{notnull}{unique}{check}{default}'; + $format = '{type}{length}{unsigned}{notnull}{unique}{check}{default}{plus}'; break; default: - $format = '{type}{length}{notnull}{unique}{check}{default}'; + $format = '{type}{length}{notnull}{unique}{check}{default}{plus}'; } return $this->buildCompleteString($format); From 27866bf9d6bd27b52eae97bf33dba2228058ac88 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Fri, 13 May 2016 15:56:33 +0300 Subject: [PATCH 43/90] Changed name to append(), fixed phpdoc, added changelog --- framework/db/ColumnSchemaBuilder.php | 28 +++++++++++++--------------- framework/db/cubrid/ColumnSchemaBuilder.php | 6 +++--- framework/db/mysql/ColumnSchemaBuilder.php | 6 +++--- framework/db/oci/ColumnSchemaBuilder.php | 6 +++--- framework/db/sqlite/ColumnSchemaBuilder.php | 6 +++--- 5 files changed, 25 insertions(+), 27 deletions(-) diff --git a/framework/db/ColumnSchemaBuilder.php b/framework/db/ColumnSchemaBuilder.php index 83e2c78..f0b5b3d 100644 --- a/framework/db/ColumnSchemaBuilder.php +++ b/framework/db/ColumnSchemaBuilder.php @@ -56,10 +56,10 @@ class ColumnSchemaBuilder extends Object */ protected $default; /** - * @var mixed SQL string to be appended to column schema string. + * @var mixed SQL string to be appended to column schema definition. * @since 2.0.8 */ - protected $plus; + protected $append; /** * @var boolean whether the column values should be unsigned. If this is `true`, an `UNSIGNED` keyword will be added. * @since 2.0.7 @@ -245,11 +245,11 @@ class ColumnSchemaBuilder extends Object * Specify additional SQL to be appended to schema string. * @param string $sql the SQL string to be appended. * @return $this - * @since 2.0.8 + * @since 2.0.9 */ public function plus($sql) { - $this->plus = $sql; + $this->append = $sql; return $this; } @@ -261,10 +261,10 @@ class ColumnSchemaBuilder extends Object { switch ($this->getTypeCategory()) { case self::CATEGORY_PK: - $format = '{type}{check}{comment}{plus}'; + $format = '{type}{check}{comment}{append}'; break; default: - $format = '{type}{length}{notnull}{unique}{default}{check}{comment}{plus}'; + $format = '{type}{length}{notnull}{unique}{default}{check}{comment}{append}'; } return $this->buildCompleteString($format); } @@ -374,13 +374,13 @@ class ColumnSchemaBuilder extends Object } /** - * Builds the first constraint for the column. Defaults to unsupported. - * @return string a string containing the FIRST constraint. - * @since 2.0.8 + * Builds the custom string that's appended to column definition. + * @return string custom string to append. + * @since 2.0.9 */ - protected function buildPlusString() + protected function buildAppendString() { - return $this->plus !== null ? ' ' . $this->plus : ''; + return $this->append !== null ? ' ' . $this->append : ''; } /** @@ -420,10 +420,8 @@ class ColumnSchemaBuilder extends Object '{default}' => $this->buildDefaultString(), '{check}' => $this->buildCheckString(), '{comment}' => $this->buildCommentString(), - '{pos}' => ($this->isFirst) ? - $this->buildFirstString() : - $this->buildAfterString(), - '{plus}' => $this->buildPlusString(), + '{pos}' => $this->isFirst ? $this->buildFirstString() : $this->buildAfterString(), + '{append}' => $this->buildAppendString(), ]; return strtr($format, $placeholderValues); } diff --git a/framework/db/cubrid/ColumnSchemaBuilder.php b/framework/db/cubrid/ColumnSchemaBuilder.php index 5e97c46..d418e80 100644 --- a/framework/db/cubrid/ColumnSchemaBuilder.php +++ b/framework/db/cubrid/ColumnSchemaBuilder.php @@ -58,13 +58,13 @@ class ColumnSchemaBuilder extends AbstractColumnSchemaBuilder { switch ($this->getTypeCategory()) { case self::CATEGORY_PK: - $format = '{type}{check}{pos}{comment}{plus}'; + $format = '{type}{check}{pos}{comment}{append}'; break; case self::CATEGORY_NUMERIC: - $format = '{type}{length}{unsigned}{notnull}{unique}{default}{check}{comment}{pos}{plus}'; + $format = '{type}{length}{unsigned}{notnull}{unique}{default}{check}{comment}{pos}{append}'; break; default: - $format = '{type}{length}{notnull}{unique}{default}{check}{comment}{pos}{plus}'; + $format = '{type}{length}{notnull}{unique}{default}{check}{comment}{pos}{append}'; } return $this->buildCompleteString($format); } diff --git a/framework/db/mysql/ColumnSchemaBuilder.php b/framework/db/mysql/ColumnSchemaBuilder.php index fb8c6de..e740c78 100644 --- a/framework/db/mysql/ColumnSchemaBuilder.php +++ b/framework/db/mysql/ColumnSchemaBuilder.php @@ -58,13 +58,13 @@ class ColumnSchemaBuilder extends AbstractColumnSchemaBuilder { switch ($this->getTypeCategory()) { case self::CATEGORY_PK: - $format = '{type}{length}{check}{comment}{pos}{plus}'; + $format = '{type}{length}{check}{comment}{pos}{append}'; break; case self::CATEGORY_NUMERIC: - $format = '{type}{length}{unsigned}{notnull}{unique}{default}{check}{comment}{pos}{plus}'; + $format = '{type}{length}{unsigned}{notnull}{unique}{default}{check}{comment}{pos}{append}'; break; default: - $format = '{type}{length}{notnull}{unique}{default}{check}{comment}{pos}{plus}'; + $format = '{type}{length}{notnull}{unique}{default}{check}{comment}{pos}{append}'; } return $this->buildCompleteString($format); } diff --git a/framework/db/oci/ColumnSchemaBuilder.php b/framework/db/oci/ColumnSchemaBuilder.php index 383c009..6893d4c 100644 --- a/framework/db/oci/ColumnSchemaBuilder.php +++ b/framework/db/oci/ColumnSchemaBuilder.php @@ -51,13 +51,13 @@ class ColumnSchemaBuilder extends AbstractColumnSchemaBuilder { switch ($this->getTypeCategory()) { case self::CATEGORY_PK: - $format = '{type}{length}{check}{pos}{plus}'; + $format = '{type}{length}{check}{pos}{append}'; break; case self::CATEGORY_NUMERIC: - $format = '{type}{length}{unsigned}{default}{notnull}{check}{pos}{plus}'; + $format = '{type}{length}{unsigned}{default}{notnull}{check}{pos}{append}'; break; default: - $format = '{type}{length}{default}{notnull}{check}{pos}{plus}'; + $format = '{type}{length}{default}{notnull}{check}{pos}{append}'; } return $this->buildCompleteString($format); } diff --git a/framework/db/sqlite/ColumnSchemaBuilder.php b/framework/db/sqlite/ColumnSchemaBuilder.php index c5e797d..1c2da63 100644 --- a/framework/db/sqlite/ColumnSchemaBuilder.php +++ b/framework/db/sqlite/ColumnSchemaBuilder.php @@ -33,13 +33,13 @@ class ColumnSchemaBuilder extends AbstractColumnSchemaBuilder { switch ($this->getTypeCategory()) { case self::CATEGORY_PK: - $format = '{type}{check}{plus}'; + $format = '{type}{check}{append}'; break; case self::CATEGORY_NUMERIC: - $format = '{type}{length}{unsigned}{notnull}{unique}{check}{default}{plus}'; + $format = '{type}{length}{unsigned}{notnull}{unique}{check}{default}{append}'; break; default: - $format = '{type}{length}{notnull}{unique}{check}{default}{plus}'; + $format = '{type}{length}{notnull}{unique}{check}{default}{append}'; } return $this->buildCompleteString($format); From 94d0bd9ae7c7b24fd2077bf913d04448ba01675a Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Fri, 13 May 2016 16:05:38 +0300 Subject: [PATCH 44/90] Fixed method name --- framework/CHANGELOG.md | 1 + framework/db/ColumnSchemaBuilder.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 206a56c..b192097 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -5,6 +5,7 @@ Yii Framework 2 Change Log 2.0.9 under development ----------------------- +- Enh #11195: Added ability to append custom string to schema builder column definition (df2, samdark) - Enh #11490: Added `yii\data\ArrayDataProvider::$modelClass` property to specify a model used to provide column labels even when data array is empty (PowerGamer1) - Bug #9950: Updated `yii\grid\DataColumn::getHeaderCellLabel()` to extract attribute label from the `filterModel` of Grid (silverfire) - Enh #11428: Speedup SQL query in `yii\db\oci\Schema::findColumns()` (SSiwek) diff --git a/framework/db/ColumnSchemaBuilder.php b/framework/db/ColumnSchemaBuilder.php index f0b5b3d..8db5cb8 100644 --- a/framework/db/ColumnSchemaBuilder.php +++ b/framework/db/ColumnSchemaBuilder.php @@ -247,7 +247,7 @@ class ColumnSchemaBuilder extends Object * @return $this * @since 2.0.9 */ - public function plus($sql) + public function append($sql) { $this->append = $sql; return $this; From 515732c7b8c5527720592fc87747657de659f1f5 Mon Sep 17 00:00:00 2001 From: Oleg Balykin Date: Thu, 12 May 2016 16:06:31 +0300 Subject: [PATCH 45/90] Test for bug dbTypecast with empty char #11548 --- tests/framework/db/ColumnSchemaTest.php | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 tests/framework/db/ColumnSchemaTest.php diff --git a/tests/framework/db/ColumnSchemaTest.php b/tests/framework/db/ColumnSchemaTest.php new file mode 100644 index 0000000..1a32a91 --- /dev/null +++ b/tests/framework/db/ColumnSchemaTest.php @@ -0,0 +1,25 @@ + Schema::TYPE_CHAR]); + $this->assertSame('', $columnSchema->dbTypecast('')); + } +} From 4d809af0ffc1e6b6f742866e4985c7adb382f186 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Fri, 13 May 2016 17:29:25 +0300 Subject: [PATCH 46/90] Fixed `@since` tag --- framework/db/ColumnSchemaBuilder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/db/ColumnSchemaBuilder.php b/framework/db/ColumnSchemaBuilder.php index 8db5cb8..6404552 100644 --- a/framework/db/ColumnSchemaBuilder.php +++ b/framework/db/ColumnSchemaBuilder.php @@ -57,7 +57,7 @@ class ColumnSchemaBuilder extends Object protected $default; /** * @var mixed SQL string to be appended to column schema definition. - * @since 2.0.8 + * @since 2.0.9 */ protected $append; /** From 6bd9881eaf11566898b963d157dee5e3beb14464 Mon Sep 17 00:00:00 2001 From: TonisOrmisson Date: Sun, 15 May 2016 13:30:14 +0300 Subject: [PATCH 47/90] Minor changes in Estonian translation - more fluent. (#11568) [skip ci] --- framework/messages/et/yii.php | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/framework/messages/et/yii.php b/framework/messages/et/yii.php index 69116b9..cf362f6 100644 --- a/framework/messages/et/yii.php +++ b/framework/messages/et/yii.php @@ -24,13 +24,13 @@ return array ( 'Error' => 'Viga', 'File upload failed.' => 'Faili üleslaadimine ebaõnnestus.', 'Home' => 'Avaleht', - 'Invalid data received for parameter "{param}".' => 'Vastu võeti vigased andmed parameetri "{param}" jaoks.', - 'Login Required' => 'Vajab sisselogimist', + 'Invalid data received for parameter "{param}".' => 'Parameetri "{param}" jaoks võeti vastu vigased andmed.', + 'Login Required' => 'Vajalik on sisse logimine', 'Missing required arguments: {params}' => 'Puuduvad nõutud argumendid: {params}', 'Missing required parameters: {params}' => 'Puuduvad nõutud parameetrid: {params}', 'No' => 'Ei', - 'No help for unknown command "{command}".' => 'Abi puudub tundmatu käsu "{command}" jaoks.', - 'No help for unknown sub-command "{command}".' => 'Abi puudub tundmatu alamkäsu "{command}" jaoks.', + 'No help for unknown command "{command}".' => 'Tundmatu käsu "{command}" jaoks puudub abi.', + 'No help for unknown sub-command "{command}".' => 'Tundmatu alamkäsu "{command}" jaoks puudub abi.', 'No results found.' => 'Ei leitud ühtegi tulemust.', 'Only files with these MIME types are allowed: {mimeTypes}.' => 'Lubatud on ainult nende MIME tüüpidega failid: {mimeTypes}.', 'Only files with these extensions are allowed: {extensions}.' => 'Lubatud on ainult nende faililaienditega failid: {extensions}.', @@ -38,18 +38,18 @@ return array ( 'Please fix the following errors:' => 'Palun parandage järgnevad vead:', 'Please upload a file.' => 'Palun laadige fail üles.', 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Näitan {totalCount, number} {totalCount, plural, one{üksusest} other{üksusest}} {begin, number}-{end, number}.', - 'The file "{file}" is not an image.' => 'Fail "{file}" ei ole pilt.', - 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => 'Fail "{file}" on liiga suur. Suurus ei tohi ületada {formattedLimit}.', - 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => 'Fail "{file}" on liiga väike. Suurus ei tohi olla väiksem kui {formattedLimit}.', + 'The file "{file}" is not an image.' => 'See fail "{file}" ei ole pilt.', + 'The file "{file}" is too big. Its size cannot exceed {formattedLimit}.' => 'See fail "{file}" on liiga suur. Suurus ei tohi ületada {formattedLimit}.', + 'The file "{file}" is too small. Its size cannot be smaller than {formattedLimit}.' => 'See fail "{file}" on liiga väike. Suurus ei tohi olla väiksem kui {formattedLimit}.', 'The format of {attribute} is invalid.' => '{attribute} on sobimatus vormingus.', 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Pilt "{file}" on liiga suur. Kõrgus ei tohi olla suurem kui {limit, number} {limit, plural, one{piksel} other{pikslit}}.', 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Pilt "{file}" on liiga suur. Laius ei tohi olla suurem kui {limit, number} {limit, plural, one{piksel} other{pikslit}}.', 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Pilt "{file}" on liiga väike. Kõrgus ei tohi olla väiksem kui {limit, number} {limit, plural, one{piksel} other{pikslit}}.', 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Pilt "{file}" on liiga väike. Laius ei tohi olla väiksem kui {limit, number} {limit, plural, one{piksel} other{pikslit}}.', 'The requested view "{name}" was not found.' => 'Soovitud vaadet "{name}" ei leitud.', - 'The verification code is incorrect.' => 'Kontrollkood ei ole õige.', + 'The verification code is incorrect.' => 'Kontrollkood on vale.', 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Kokku {count, number} {count, plural, one{üksus} other{üksust}}.', - 'Unable to verify your data submission.' => 'Ei suuda teie andmesööte õigsuses veenduda.', + 'Unable to verify your data submission.' => 'Ei suuda edastatud andmete õigsuses veenduda.', 'Unknown command "{command}".' => 'Tundmatu käsklus "{command}".', 'Unknown option: --{name}' => 'Tundmatu valik: --{name}', 'Update' => 'Muuda', @@ -71,7 +71,7 @@ return array ( '{attribute} is not a valid email address.' => '{attribute} ei ole korrektne e-posti aadress.', '{attribute} must be "{requiredValue}".' => '{attribute} peab olema "{requiredValue}".', '{attribute} must be a number.' => '{attribute} peab olema number.', - '{attribute} must be a string.' => '{attribute} peab olema sõne.', + '{attribute} must be a string.' => '{attribute} peab olema tekst.', '{attribute} must be an integer.' => '{attribute} peab olema täisarv.', '{attribute} must be either "{true}" or "{false}".' => '{attribute} peab olema kas "{true}" või "{false}".', '{attribute} must be greater than "{compareValue}".' => '{attribute} peab olema suurem kui "{compareValue}".', @@ -82,9 +82,9 @@ return array ( '{attribute} must be no less than {min}.' => '{attribute} ei tohi olla väiksem kui {min}.', '{attribute} must be repeated exactly.' => '{attribute} peab täpselt kattuma.', '{attribute} must not be equal to "{compareValue}".' => '{attribute} ei tohi olla "{compareValue}".', - '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} peab sisaldama vähemalt {min, number} {min, plural, one{märki} other{märki}}.', - '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} tohib sisaldada maksimaalselt {max, number} {max, plural, one{märki} other{märki}}.', - '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} peab sisaldama {length, number} {length, plural, one{märki} other{märki}}.', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} peab sisaldama vähemalt {min, number} {min, plural, one{tähemärki} other{tähemärki}}.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} tohib sisaldada maksimaalselt {max, number} {max, plural, one{tähemärki} other{tähemärki}}.', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} peab sisaldama {length, number} {length, plural, one{tähemärki} other{tähemärki}}.', '{delta, plural, =1{a day} other{# days}} ago' => '{delta, plural, =1{üks päev} other{# päeva}} tagasi', '{delta, plural, =1{a minute} other{# minutes}} ago' => '{delta, plural, =1{üks minut} other{# minutit}} tagasi', '{delta, plural, =1{a month} other{# months}} ago' => '{delta, plural, =1{kuu aega} other{# kuud}} tagasi', From 9f499eb51e7eeaf134bba7c82d86dcf54174e4d5 Mon Sep 17 00:00:00 2001 From: SilverFire - Dmitry Naumenko Date: Sun, 15 May 2016 22:27:40 +0300 Subject: [PATCH 48/90] Fixed `yii\web\User::checkRedirectAcceptable()` to treat acceptable content type `*/*` as `*` Closes #11523 --- framework/CHANGELOG.md | 1 + framework/web/User.php | 2 +- tests/framework/web/UserTest.php | 7 +++++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index e65e56f0..59f9b8f 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -16,6 +16,7 @@ Yii Framework 2 Change Log - Bug #11459: Fixed flash messages not destroyed when `session.auto_start = 1` set in php.ini (cartmanchen) - Bug #11498: Fixed inability to save serialized object into PostgreSQL binary column (klimov-paul) - Bug #11507: Fixed `yii\validators\EachValidator::validateAttribute()` does not respect `skipOnEmpty` rule parameter (webdevsega) +- Bug #11523: Fixed `yii\web\User::checkRedirectAcceptable()` to treat acceptable content type `*/*` as `*` (silverfire) - Bug #11532: Fixed casting of empty char value to `null` resulting in integrity constraint violation for not null columns (samdark) diff --git a/framework/web/User.php b/framework/web/User.php index 0418ac5..1bd07b2 100644 --- a/framework/web/User.php +++ b/framework/web/User.php @@ -702,7 +702,7 @@ class User extends Component } foreach ($acceptableTypes as $type => $params) { - if ($type === '*' || in_array($type, $this->acceptableRedirectTypes, true)) { + if ($type === '*' || $type === '*/*' || in_array($type, $this->acceptableRedirectTypes, true)) { return true; } } diff --git a/tests/framework/web/UserTest.php b/tests/framework/web/UserTest.php index 7e048d9..79b91aa 100644 --- a/tests/framework/web/UserTest.php +++ b/tests/framework/web/UserTest.php @@ -216,6 +216,13 @@ class UserTest extends TestCase $this->assertTrue(Yii::$app->response->getIsRedirection()); $this->reset(); + Yii::$app->request->setUrl('accept-all'); + $_SERVER['HTTP_ACCEPT'] = '*/*;q=0.1'; + $user->loginRequired(); + $this->assertEquals('accept-all', $user->getReturnUrl()); + $this->assertTrue(Yii::$app->response->getIsRedirection()); + + $this->reset(); Yii::$app->request->setUrl('accept-html-json'); $_SERVER['HTTP_ACCEPT'] = 'text/json; q=1, text/html; q=0.1'; $user->loginRequired(); From b976f638d8f7e133d0f742cb5de1cf0bb6799aa6 Mon Sep 17 00:00:00 2001 From: SilverFire - Dmitry Naumenko Date: Sun, 15 May 2016 23:05:30 +0300 Subject: [PATCH 49/90] Added test for PgSQL BIGINT column to ensure typecasting works OK Closes #11286 --- tests/data/postgres.sql | 3 +- tests/framework/db/pgsql/PostgreSQLSchemaTest.php | 50 +++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/tests/data/postgres.sql b/tests/data/postgres.sql index a35c188..a15adac 100644 --- a/tests/data/postgres.sql +++ b/tests/data/postgres.sql @@ -128,7 +128,8 @@ CREATE TABLE "type" ( bool_col boolean NOT NULL, bool_col2 boolean DEFAULT TRUE, ts_default TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - bit_col BIT(8) NOT NULL DEFAULT B'10000010' + bit_col BIT(8) NOT NULL DEFAULT B'10000010', + bigint_col BIGINT ); CREATE TABLE "bool_values" ( diff --git a/tests/framework/db/pgsql/PostgreSQLSchemaTest.php b/tests/framework/db/pgsql/PostgreSQLSchemaTest.php index 7b8e9bb..c811847 100644 --- a/tests/framework/db/pgsql/PostgreSQLSchemaTest.php +++ b/tests/framework/db/pgsql/PostgreSQLSchemaTest.php @@ -4,6 +4,8 @@ namespace yiiunit\framework\db\pgsql; use yii\db\Expression; use yii\db\pgsql\Schema; +use yiiunit\data\ar\ActiveRecord; +use yiiunit\data\ar\Type; use yiiunit\framework\db\SchemaTest; /** @@ -64,6 +66,19 @@ class PostgreSQLSchemaTest extends SchemaTest $columns['bit_col']['dbType'] = 'bit'; $columns['bit_col']['size'] = 8; $columns['bit_col']['precision'] = null; + $columns['bigint_col'] = [ + 'type' => 'bigint', + 'dbType' => 'int8', + 'phpType' => 'integer', + 'allowNull' => true, + 'autoIncrement' => false, + 'enumValues' => null, + 'size' => null, + 'precision' => 64, + 'scale' => 0, + 'defaultValue' => null, + ]; + return $columns; } @@ -106,4 +121,39 @@ class PostgreSQLSchemaTest extends SchemaTest $this->assertEquals(3, count($schema->getSchemaNames())); } + + public function bigintValueProvider() + { + return [ + [8817806877], + [3797444208], + [3199585540], + [1389831585], + [922337203685477580], + [9223372036854775807], + [-9223372036854775808] + ]; + } + + /** + * @dataProvider bigintValueProvider + */ + public function testBigintValue($bigint) + { + $this->mockApplication(); + ActiveRecord::$db = $this->getConnection(); + + Type::deleteAll(); + + $type = new Type(); + $type->setAttributes([ + 'bigint_col' => $bigint, + // whatever just to satisfy NOT NULL columns + 'int_col' => 1, 'char_col' => 'a', 'float_col' => 0.1, 'bool_col' => true, + ], false); + $type->save(false); + + $actual = Type::find()->one(); + $this->assertEquals($bigint, $actual->bigint_col); + } } From 0ff6eeba7df57472fe744de9e8bd0cd19ebc397c Mon Sep 17 00:00:00 2001 From: SilverFire - Dmitry Naumenko Date: Mon, 16 May 2016 01:11:47 +0300 Subject: [PATCH 50/90] Enhanced 9f499eb: `yii\web\User::checkRedirectAcceptable()` removed check for "*" type (invalid in accept header) --- framework/web/User.php | 4 ++-- tests/framework/web/UserTest.php | 13 +++++++------ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/framework/web/User.php b/framework/web/User.php index 1bd07b2..61bce7f 100644 --- a/framework/web/User.php +++ b/framework/web/User.php @@ -697,12 +697,12 @@ class User extends Component protected function checkRedirectAcceptable() { $acceptableTypes = Yii::$app->getRequest()->getAcceptableContentTypes(); - if (empty($acceptableTypes)) { + if (empty($acceptableTypes) || count($acceptableTypes) === 1 && array_keys($acceptableTypes)[0] === '*/*') { return true; } foreach ($acceptableTypes as $type => $params) { - if ($type === '*' || $type === '*/*' || in_array($type, $this->acceptableRedirectTypes, true)) { + if (in_array($type, $this->acceptableRedirectTypes, true)) { return true; } } diff --git a/tests/framework/web/UserTest.php b/tests/framework/web/UserTest.php index 79b91aa..5789d42 100644 --- a/tests/framework/web/UserTest.php +++ b/tests/framework/web/UserTest.php @@ -210,17 +210,18 @@ class UserTest extends TestCase $this->reset(); Yii::$app->request->setUrl('accept-all'); - $_SERVER['HTTP_ACCEPT'] = '*;q=0.1'; + $_SERVER['HTTP_ACCEPT'] = '*/*;q=0.1'; $user->loginRequired(); $this->assertEquals('accept-all', $user->getReturnUrl()); $this->assertTrue(Yii::$app->response->getIsRedirection()); $this->reset(); - Yii::$app->request->setUrl('accept-all'); - $_SERVER['HTTP_ACCEPT'] = '*/*;q=0.1'; - $user->loginRequired(); - $this->assertEquals('accept-all', $user->getReturnUrl()); - $this->assertTrue(Yii::$app->response->getIsRedirection()); + Yii::$app->request->setUrl('json-and-accept-all'); + $_SERVER['HTTP_ACCEPT'] = 'text/json, */*; q=0.1'; + try { + $user->loginRequired(); + } catch (ForbiddenHttpException $e) {} + $this->assertFalse(Yii::$app->response->getIsRedirection()); $this->reset(); Yii::$app->request->setUrl('accept-html-json'); From 0c4a16fc8989544faba8867acd5ca2cf98e0b369 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Tue, 17 May 2016 12:09:30 +0300 Subject: [PATCH 51/90] Fixes #11572: updated Codeception version in the guide --- docs/guide/test-environment-setup.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guide/test-environment-setup.md b/docs/guide/test-environment-setup.md index 637f344..0287191 100644 --- a/docs/guide/test-environment-setup.md +++ b/docs/guide/test-environment-setup.md @@ -20,7 +20,7 @@ You can install it either locally - for particular project only, or globally - f For the local installation use following commands: ``` -composer require "codeception/codeception=2.0.*" +composer require "codeception/codeception=2.1.*" composer require "codeception/specify=*" composer require "codeception/verify=*" ``` @@ -28,7 +28,7 @@ composer require "codeception/verify=*" For the global installation you will need to use `global` directive: ``` -composer global require "codeception/codeception=2.0.*" +composer global require "codeception/codeception=2.1.*" composer global require "codeception/specify=*" composer global require "codeception/verify=*" ``` From fd6f536eaf1a577ed38e1cdd5f6698151c3aed2d Mon Sep 17 00:00:00 2001 From: DrDeath72 Date: Wed, 18 May 2016 10:35:00 +0500 Subject: [PATCH 52/90] YUI Compressor fix --- framework/assets/yii.gridView.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/assets/yii.gridView.js b/framework/assets/yii.gridView.js index 8306666..c201652 100644 --- a/framework/assets/yii.gridView.js +++ b/framework/assets/yii.gridView.js @@ -123,7 +123,7 @@ var $form = $('
', { action: url, method: 'get', - class: 'gridview-filter-form', + 'class': 'gridview-filter-form', style: 'display:none', 'data-pjax': '' }).appendTo($grid); From f1c150580894b0055a07ea37d966ff594d6d5612 Mon Sep 17 00:00:00 2001 From: Luciano Baraglia Date: Wed, 18 May 2016 18:03:03 -0300 Subject: [PATCH 53/90] Added glossary to Spanish translation (#11587) * Added glossary to Spanish translation * Some spanish translation updates --- docs/guide-es/glossary.md | 67 ++++++++++++++++++++ docs/guide-es/helper-array.md | 142 +++++++++++++++++++++++++++++++++--------- docs/guide-es/start-hello.md | 2 +- 3 files changed, 180 insertions(+), 31 deletions(-) create mode 100644 docs/guide-es/glossary.md diff --git a/docs/guide-es/glossary.md b/docs/guide-es/glossary.md new file mode 100644 index 0000000..4e26210 --- /dev/null +++ b/docs/guide-es/glossary.md @@ -0,0 +1,67 @@ +# A + +## alias + +Alias es un string utilizado por Yii para referirse a una clase o directorio tal como `@app/vendor`. + +## aplicación + +La aplicación es el objeto central durante la solicitud HTTP. Contiene un número de componentes con los que toma información de la solicitud y la envía al controlador apropiado para posterior procesamiento. + +El objeto de la aplicación es instanciado como un singleton por el script de entrada. El singleton de la aplicación puede ser accedido desde cualquier lugar a través de `\Yii::$app`. + +## assets + +Asset se refiere a un archivo de recurso. Típicamente contiene JavaScript o CSS pero puede ser cualquier otra cosa que sea accesible vía HTTP. + +## atributo + +Un atributo es una propiedad de un modelo (una variable miembro de clase o una propiedad mágica definida vía `__get()`/`__set()`) que almacena **datos de negocio**. + +# B + +## bundle + +Bundle, conocido como paquete en Yii 1.1, se refiere a un número de recursos y un archivo de configuración que describe dependencias y lista recursos. + +# C + +## configuración + +Configuración puede referirse tanto al proceso de establecer propiedades de un objeto como a un archivo de configuración que almacena la definición de propiedades para un objeto o clase de objetos. + +# E + +## extensión + +Extensión es un grupo de clases, paquete de recursos y configuraciones que agrega más características a la aplicación. + +# I + +## instalación + +Instalación es el proceso de preparar algo para trabajar, desde seguir un archivo léame hasta ejecutar un script preparado especialmente para tal fin. En el caso de Yii, define permisos y chequea los requerimientos para el funcionamiento del software. + +# M + +## módulo + +Módulo es una sub-aplicación que contiene elementos MVC en sí mismo, como modelos, vistas, controladores, etc. y puede ser utilizado dentro de la aplicación principal. Típicamente remitiendo las solicitudes al módulo en vez de manejándolo desde controladores. + +# N + +## namespace + +Namespace (espacio de nombres) se refiere a una [característica de PHP](http://php.net/manual/es/language.namespaces.php) activamente utilizada en Yii 2. + +# P + +## paquete + +[Ver bundle](#bundle). + +# V + +## vendor + +Vendor (proveedor) es una organización o un desarrollador individual que provee código en forma de extensiones, módulos o librerías. diff --git a/docs/guide-es/helper-array.md b/docs/guide-es/helper-array.md index 0bfb01c..5c8fe62 100644 --- a/docs/guide-es/helper-array.md +++ b/docs/guide-es/helper-array.md @@ -1,7 +1,7 @@ ArrayHelper =========== -Adicionalmente al [rico conjunto de funciones para arrays de PHP](http://php.net/manual/es/book.array.php) Yii array helper proporciona +Adicionalmente al [rico conjunto de funciones para arrays de PHP](http://php.net/manual/es/book.array.php), el array helper de Yii proporciona métodos estáticos adicionales permitiendo trabajar con arrays de manera más eficiente. @@ -109,30 +109,94 @@ $result = ArrayHelper::getColumn($array, function ($element) { ## Re-indexar Arrays -Con el fin de indexar un array según una clave especificada, se puede usar el método `index`. La entrada del array debe ser -multidimensional o un array de objetos. La clave puede ser un nombre clave del sub-array, un nombre de una propiedad del objeto, o -una función anónima que retorne el valor de la clave dado el elemento del array. +Con el fin de indexar un array según una clave especificada, se puede usar el método `index`. La entrada debería ser +un array multidimensional o un array de objetos. `$key` puede ser tanto una clave del sub-array, un nombre de una propiedad +del objeto, o una función anónima que debe devolver el valor que será utilizado como clave. -Si el valor de la clave es null, el correspondiente elemento del array será desechado y no se pondrá en el resultado. Por ejemplo, +El atributo `$groups` es un array de claves, que será utilizado para agrupar el array de entrada en uno o más sub-arrays +basado en la clave especificada. + +Si el atributo `$key` o su valor por el elemento en particular es null y `$groups` no está definido, dicho elemento del array +será descartado. De otro modo, si `$groups` es especificado, el elemento del array será agregado al array resultante +sin una clave. + +Por ejemplo: ```php $array = [ - ['id' => '123', 'data' => 'abc'], - ['id' => '345', 'data' => 'def'], + ['id' => '123', 'data' => 'abc', 'device' => 'laptop'], + ['id' => '345', 'data' => 'def', 'device' => 'tablet'], + ['id' => '345', 'data' => 'hgi', 'device' => 'smartphone'], ]; -$result = ArrayHelper::index($array, 'id'); -// el resultado es: -// [ -// '123' => ['id' => '123', 'data' => 'abc'], -// '345' => ['id' => '345', 'data' => 'def'], -// ] +$result = ArrayHelper::index($array, 'id');'); +``` + +El resultado será un array asociativo, donde la clave es el valor del atributo `id` + +```php +[ + '123' => ['id' => '123', 'data' => 'abc', 'device' => 'laptop'], + '345' => ['id' => '345', 'data' => 'hgi', 'device' => 'smartphone'] + // El segundo elemento del array original es sobrescrito por el último elemento debido a que tiene el mismo id +] +``` + +Pasando una función anónima en `$key`, da el mismo resultado. -// usando función anónima +```php $result = ArrayHelper::index($array, function ($element) { return $element['id']; }); ``` +Pasando `id` como tercer argumento, agrupará `$array` mediante `id`: + +```php +$result = ArrayHelper::index($array, null, 'id'); +``` + +El resultado será un array multidimensional agrupado por `id` en su primer nivel y no indexado en su segundo nivel: + +```php +[ + '123' => [ + ['id' => '123', 'data' => 'abc', 'device' => 'laptop'] + ], + '345' => [ // todos los elementos con este índice están presentes en el array resultante + ['id' => '345', 'data' => 'def', 'device' => 'tablet'], + ['id' => '345', 'data' => 'hgi', 'device' => 'smartphone'], + ] +] +``` + +Una función anónima puede ser usada también en el array agrupador: + +```php +$result = ArrayHelper::index($array, 'data', [function ($element) { + return $element['id']; +}, 'device']); +``` + +El resultado será un array multidimensional agrupado por `id` en su primer nivel, por `device` en su segundo nivel e +indexado por `data` en su tercer nivel: + +```php +[ + '123' => [ + 'laptop' => [ + 'abc' => ['id' => '123', 'data' => 'abc', 'device' => 'laptop'] + ] + ], + '345' => [ + 'tablet' => [ + 'def' => ['id' => '345', 'data' => 'def', 'device' => 'tablet'] + ], + 'smartphone' => [ + 'hgi' => ['id' => '345', 'data' => 'hgi', 'device' => 'smartphone'] + ] + ] +] +``` ## Construyendo Mapas (Maps) @@ -241,31 +305,30 @@ La codificación utilizará el charset de la aplicación y podría ser cambiado ```php /** - * Merges two or more arrays into one recursively. - * If each array has an element with the same string key value, the latter - * will overwrite the former (different from array_merge_recursive). - * Recursive merging will be conducted if both arrays have an element of array - * type and are having the same key. - * For integer-keyed elements, the elements from the latter array will - * be appended to the former array. - * @param array $a array to be merged to - * @param array $b array to be merged from. You can specify additional - * arrays via third argument, fourth argument etc. - * @return array the merged array (the original arrays are not changed.) - */ + * Fusiona recursivamente dos o más arrays en uno. + * Si cada array tiene un elemento con el mismo valor string de clave, el último + * sobrescribirá el anterior (difiere de array_merge_recursive). + * Se llegará a una fusión recursiva si ambos arrays tienen un elemento tipo array + * y comparten la misma clave. + * Para elementos cuyas claves son enteros, los elementos del array final + * serán agregados al array anterior. + * @param array $a array al que se va a fusionar + * @param array $b array desde el cual fusionar. Puedes especificar + * arrays adicionales mediante el tercer argumento, cuarto argumento, etc. + * @return array el array fusionado (los arrays originales no sufren cambios) + */ public static function merge($a, $b) ``` ## Convirtiendo Objetos a Arrays -A menudo necesitas convertir un objeto o un array de objetos a un array. El caso más común es convertir los modelos de -active record con el fin de servir los arrays de datos vía API REST o utilizarlos de otra manera. El siguiente código -se podría utilizar para hacerlo: +A menudo necesitas convertir un objeto o un array de objetos a un array. El caso más común es convertir los modelos de active record +con el fin de servir los arrays de datos vía API REST o utilizarlos de otra manera. El siguiente código se podría utilizar para hacerlo: ```php $posts = Post::find()->limit(10)->all(); -$data = ArrayHelper::toArray($post, [ +$data = ArrayHelper::toArray($posts, [ 'app\models\Post' => [ 'id', 'title', @@ -302,3 +365,22 @@ El resultado de la conversión anterior será: Es posible proporcionar una manera predeterminada de convertir un objeto a un array para una clase especifica mediante la implementación de la interfaz [[yii\base\Arrayable|Arrayable]] en esa clase. + +## Haciendo pruebas con Arrays + +A menudo necesitarás comprobar está en un array o un grupo de elementos es un sub-grupo de otro. +A pesar de que PHP ofrece `in_array()`, este no soporta sub-grupos u objetos de tipo `\Traversable`. + +Para ayudar en este tipo de pruebas, [[yii\base\ArrayHelper]] provee [[yii\base\ArrayHelper::isIn()|isIn()]] +y [[yii\base\ArrayHelper::isSubset()|isSubset()]] con la misma firma del método [[in_array()]]. + +```php +// true +ArrayHelper::isIn('a', ['a']); +// true +ArrayHelper::isIn('a', new(ArrayObject['a'])); + +// true +ArrayHelper::isSubset(new(ArrayObject['a', 'c']), new(ArrayObject['a', 'b', 'c']) + +``` diff --git a/docs/guide-es/start-hello.md b/docs/guide-es/start-hello.md index 2204249..a75f400 100644 --- a/docs/guide-es/start-hello.md +++ b/docs/guide-es/start-hello.md @@ -101,7 +101,7 @@ Probándolo Después de crear la acción y la vista, puedes acceder a la nueva página abriendo el siguiente URL: ``` -http://hostname/index.php?r=site/say&message=Hello+World +http://hostname/index.php?r=site%2Fsay&message=Hello+World ``` ![Hello World](images/start-hello-world.png) From 48bcee6fc3797c27c6402e432f400b55796245ff Mon Sep 17 00:00:00 2001 From: Luciano Baraglia Date: Thu, 19 May 2016 17:44:45 -0300 Subject: [PATCH 54/90] Doc translation es (#11597) * Added glossary to Spanish translation * Some spanish translation updates * DB Migrations Spanish Docs [skip ci] * Fix line break in english doc [skip ci] --- docs/guide-es/db-migrations.md | 939 +++++++++++++++++++++++++++++++++++++++++ docs/guide/db-migrations.md | 3 +- 2 files changed, 940 insertions(+), 2 deletions(-) create mode 100644 docs/guide-es/db-migrations.md diff --git a/docs/guide-es/db-migrations.md b/docs/guide-es/db-migrations.md new file mode 100644 index 0000000..6d6ee75 --- /dev/null +++ b/docs/guide-es/db-migrations.md @@ -0,0 +1,939 @@ +Migración de Base de Datos +========================== + +Durante el curso de desarrollo y mantenimiento de una aplicación con base de datos, la estructura de dicha base de datos +evoluciona tanto como el código fuente. Por ejemplo, durante el desarrollo de una aplicación, +una nueva tabla podría ser necesaria; una vez que la aplicación se encuentra en producción, podría descrubrirse +que debería crearse un índice para mejorar el tiempo de ejecución de una consulta; y así sucesivamente. Debido a los cambios en la estructura de la base de datos +a menudo se requieren cambios en el código, Yii soporta la característica llamada *migración de base de datos*, la cual permite +tener un seguimiento de esos cambios en término de *migración de base de datos*, cuyo versionado es controlado +junto al del código fuente. + +Los siguientes pasos muestran cómo una migración puede ser utilizada por un equipo durante el desarrollo: + +1. Tim crea una nueva migración (por ej. crea una nueva table, cambia la definición de una columna, etc.). +2. Tim hace un commit con la nueva migración al sistema de control de versiones (por ej. Git, Mercurial). +3. Doug actualiza su repositorio desde el sistema de control de versiones y recibe la nueva migración. +4. Doug aplica dicha migración a su base de datos local de desarrollo, de ese modo sincronizando su base de datos + y reflejando los cambios que hizo Tim. + +Los siguientes pasos muestran cómo hacer una puesta en producción con una migración de base de datos: + +1. Scott crea un tag de lanzamiento en el repositorio del proyecto que contiene algunas migraciones de base de datos. +2. Scott actualiza el código fuente en el servidor de producción con el tag de lanzamiento. +3. Scott aplica cualquier migración de base de datos acumulada a la base de datos de producción. + +Yii provee un grupo de herramientas de línea de comandos que te permite: + +* crear nuevas migraciones; +* aplicar migraciones; +* revertir migraciones; +* re-aplicar migraciones; +* mostrar el historial y estado de migraciones. + +Todas esas herramientas son accesibles a través del comando `yii migrate`. En esta sección describiremos en detalle +cómo lograr varias tareas utilizando dichas herramientas. Puedes a su vez ver el uso de cada herramienta a través del comando +de ayuda `yii help migrate`. + +> Tip: las migraciones pueden no sólo afectar un esquema de base de datos sino también ajustar datos existentes para que encajen en el nuevo esquema, crear herencia RBAC + o también limpiar el cache. + + +## Creando Migraciones + +Para crear una nueva migración, ejecuta el siguiente comando: + +``` +yii migrate/create +``` + +El argumento requerido `name` da una pequeña descripción de la nueva migración. Por ejemplo, si +la migración se trata acerca de crear una nueva tabla llamada *news*, podrías utilizar el nombre `create_news_table` +y ejecutar el siguiente comando: + +``` +yii migrate/create create_news_table +``` + +> Note: Debido a que el argumento `name` será utilizado como parte del nombre de clase de la migración generada, + sólo debería contener letras, dígitos, y/o guines bajos. + +El comando anterior un nuevo archivo de clase PHP llamado `m150101_185401_create_news_table.php` +en el directorio `@app/migrations`. El archivo contendrá el siguiente código, que principalmente declara +una clase de tipo migración `m150101_185401_create_news_table` con el siguiente esqueleto de código: + +```php +_`, donde + +* `` se refiere a la marca de tiempo UTC en la cual el comando de migración fue ejecutado. +* `` es el mismo valor del argumento `name` provisto al ejecutar el comando. + +En la clase de la migración, se espera que tu escribas código en el método `up()`, que realiza los cambios en la base de datos. +Podrías también querer introducir código en el método `down()`, que debería revertir los cambios realizados por `up()`. El método `up()` es llamado +cuando actualizas la base de datos con esta migración, mientras que el método `down()` es llamado cuando reviertes dicha migración. +El siguiente código muestra cómo podrías implementar la clase de migración para crear la tabla `news`: + +```php +createTable('news', [ + 'id' => Schema::TYPE_PK, + 'title' => Schema::TYPE_STRING . ' NOT NULL', + 'content' => Schema::TYPE_TEXT, + ]); + } + + public function down() + { + $this->dropTable('news'); + } +} +``` + +> Info: No todas las migraciones son reversibles. Por ejemplo, si el método `up()` elimina un registro en una tabla, podrías + no ser capáz de recuperarla en el método `down()`. A veces, podrías ser simplemente demasiado perezoso para implementar + el método `down()`, debido a que no es muy común revertir migraciones de base de datos. En este caso, deberías devolver + `false` en el método `down()` para indicar que dicha migración no es reversible. + +La clase de migración de base de datos [[yii\db\Migration]] expone una conexión a la base de datos mediante la propiedad [[yii\db\Migration::db|db]]. +Puedes utilizar esto para manipular el esquema de la base de datos utilizando métodos como se describen en +[Trabajando con Esquemas de Base de Datos](db-dao.md#working-with-database-schema-). + +En vez de utilizar tipos físicos, al crear tablas o columnas deberías utilizar los *tipos abstractos* +así las migraciones son independientes de algún DBMS específico. La clase [[yii\db\Schema]] define +un grupo de constantes que representan los tipos abstractos soportados. Dichas constantes son llamadas utilizando el formato +de `TYPE_`. Por ejemplo, `TYPE_PK` se refiere al tipo clave primaria auto-incremental; `TYPE_STRING` +se refiere al tipo string. Cuando se aplica una migración a una base de datos en particular, los tipos abstractos +serán traducidos a los tipos físicos correspondientes. En el caso de MySQL, `TYPE_PK` será transformado +en `int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY`, mientras `TYPE_STRING` se vuelve `varchar(255)`. + +Puedes agregar restricciones adicionales al utilizar tipos abstractos. En el ejemplo anterior, ` NOT NULL` es agregado +a `Schema::TYPE_STRING` para especificar que la columna no puede ser null. + +> Info: El mapeo entre tipos abstractos y tipos físicos es especificado en + la propiedad [[yii\db\QueryBuilder::$typeMap|$typeMap]] en cada clase concreta `QueryBuilder`. + +Desde la versión 2.0.6, puedes hacer uso del recientemente introducido generador de esquemas, el cual provee una forma más conveniente de definir las columnas. +De esta manera, la migración anterior podría ser escrita así: + +```php +createTable('news', [ + 'id' => $this->primaryKey(), + 'title' => $this->string()->notNull(), + 'content' => $this->text(), + ]); + } + + public function down() + { + $this->dropTable('news'); + } +} +``` + +Existe una lista de todos los métodos disponibles para la definición de tipos de columna en la API de la documentación de [[yii\db\SchemaBuilderTrait]]. + + +## Generar Migraciones + +Desde la versión 2.0.7 la consola provee una manera muy conveniente de generar migraciones. + +Si el nombre de la migración tiene una forma especial, por ejemplo `create_xxx` o `drop_xxx` entonces el archivo de la migración generada +contendrá código extra, en este caso para crear/eliminar tablas. +A continuación se describen todas estas variantes. + +### Crear Tabla + +```php +yii migrate/create create_post +``` + +esto genera + +```php +/** + * Handles the creation for table `post`. + */ +class m150811_220037_create_post extends Migration +{ + /** + * @inheritdoc + */ + public function up() + { + $this->createTable('post', [ + 'id' => $this->primaryKey() + ]); + } + + /** + * @inheritdoc + */ + public function down() + { + $this->dropTable('post'); + } +} +``` + +Para crear las columnas en ese momento, las puedes especificar vía la opción `--fields`. + +```php +yii migrate/create create_post --fields="title:string,body:text" +``` + +genera + +```php +/** + * Handles the creation for table `post`. + */ +class m150811_220037_create_post extends Migration +{ + /** + * @inheritdoc + */ + public function up() + { + $this->createTable('post', [ + 'id' => $this->primaryKey(), + 'title' => $this->string(), + 'body' => $this->text(), + ]); + } + + /** + * @inheritdoc + */ + public function down() + { + $this->dropTable('post'); + } +} + +``` + +Puedes especificar más parámetros para las columnas. + +```php +yii migrate/create create_post --fields="title:string(12):notNull:unique,body:text" +``` + +genera + +```php +/** + * Handles the creation for table `post`. + */ +class m150811_220037_create_post extends Migration +{ + /** + * @inheritdoc + */ + public function up() + { + $this->createTable('post', [ + 'id' => $this->primaryKey(), + 'title' => $this->string(12)->notNull()->unique(), + 'body' => $this->text() + ]); + } + + /** + * @inheritdoc + */ + public function down() + { + $this->dropTable('post'); + } +} +``` + +> Note: la clave primaria es automáticamente agragada y llamada `id` por defecto. Si quieres utilizar otro nombre puedes +> especificarlo así `--fields="name:primaryKey"`. + +#### Claves Foráneas + +Desde 2.0.8 el generador soporta claves foráneas utilizando la palabra clave `foreignKey`. + +```php +yii migrate/create create_post --fields="author_id:integer:notNull:foreignKey(user),category_id:integer:defaultValue(1):foreignKey,title:string,body:text" +``` + +genera + +```php +/** + * Handles the creation for table `post`. + * Has foreign keys to the tables: + * + * - `user` + * - `category` + */ +class m160328_040430_create_post extends Migration +{ + /** + * @inheritdoc + */ + public function up() + { + $this->createTable('post', [ + 'id' => $this->primaryKey(), + 'author_id' => $this->integer()->notNull(), + 'category_id' => $this->integer()->defaultValue(1), + 'title' => $this->string(), + 'body' => $this->text(), + ]); + + // creates index for column `author_id` + $this->createIndex( + 'idx-post-author_id', + 'post', + 'author_id' + ); + + // add foreign key for table `user` + $this->addForeignKey( + 'fk-post-author_id', + 'post', + 'author_id', + 'user', + 'id', + 'CASCADE' + ); + + // creates index for column `category_id` + $this->createIndex( + 'idx-post-category_id', + 'post', + 'category_id' + ); + + // add foreign key for table `category` + $this->addForeignKey( + 'fk-post-category_id', + 'post', + 'category_id', + 'category', + 'id', + 'CASCADE' + ); + } + + /** + * @inheritdoc + */ + public function down() + { + // drops foreign key for table `user` + $this->dropForeignKey( + 'fk-post-author_id', + 'post' + ); + + // drops index for column `author_id` + $this->dropIndex( + 'idx-post-author_id', + 'post' + ); + + // drops foreign key for table `category` + $this->dropForeignKey( + 'fk-post-category_id', + 'post' + ); + + // drops index for column `category_id` + $this->dropIndex( + 'idx-post-category_id', + 'post' + ); + + $this->dropTable('post'); + } +} +``` + +La posición de la palabra clave `foreignKey` en la descripción de la columna +no cambia el código generado. Esto significa: + +- `author_id:integer:notNull:foreignKey(user)` +- `author_id:integer:foreignKey(user):notNull` +- `author_id:foreignKey(user):integer:notNull` + +Todas generan el mismo código. + +La palabra clave `foreignKey` puede tomar un parámetro entre paréntesis el cual +será el nombre de la tabla relacionada por la clave foránea generada. Si no se pasa ningún parámetro +el nombre de la tabla será deducido en base al nombre de la columna. + +En el ejemplo anterior `author_id:integer:notNull:foreignKey(user)` generará +una columna llamada `author_id` con una clave foránea a la tabla `user` mientras +`category_id:integer:defaultValue(1):foreignKey` generará +`category_id` con una clave foránea a la tabla `category`. + +### Eliminar Tabla + +```php +yii migrate/create drop_post --fields="title:string(12):notNull:unique,body:text" +``` + +genera + +```php +class m150811_220037_drop_post extends Migration +{ + public function up() + { + $this->dropTable('post'); + } + + public function down() + { + $this->createTable('post', [ + 'id' => $this->primaryKey(), + 'title' => $this->string(12)->notNull()->unique(), + 'body' => $this->text() + ]); + } +} +``` + +### Agregar Columna + +Si el nombre de la migración está en la forma `add_xxx_to_yyy` entonces el archivo generado contendrá +las declaraciones `addColumn` y `dropColumn` necesarias. + +Para agregar una columna: + +```php +yii migrate/create add_position_to_post --fields="position:integer" +``` + +genera + +```php +class m150811_220037_add_position_to_post extends Migration +{ + public function up() + { + $this->addColumn('post', 'position', $this->integer()); + } + + public function down() + { + $this->dropColumn('post', 'position'); + } +} +``` + +### Eliminar Columna + +Si el nombre de la migración está en la forma `drop_xxx_from_yyy` entonces el archivo generado contendrá +las declaraciones `addColumn` y `dropColumn` necesarias. + +```php +yii migrate/create drop_position_from_post --fields="position:integer" +``` + +genera + +```php +class m150811_220037_drop_position_from_post extends Migration +{ + public function up() + { + $this->dropColumn('post', 'position'); + } + + public function down() + { + $this->addColumn('post', 'position', $this->integer()); + } +} +``` + +### Agregar Tabla de Unión + +Si el nombre de la migración está en la forma `create_junction_xxx_and_yyy` entonces se generará el código necesario +para una tabla de unión. + +```php +yii migrate/create create_junction_post_and_tag --fields="created_at:dateTime" +``` + +genera + +```php +/** + * Handles the creation for table `post_tag`. + * Has foreign keys to the tables: + * + * - `post` + * - `tag` + */ +class m160328_041642_create_junction_post_and_tag extends Migration +{ + /** + * @inheritdoc + */ + public function up() + { + $this->createTable('post_tag', [ + 'post_id' => $this->integer(), + 'tag_id' => $this->integer(), + 'created_at' => $this->dateTime(), + 'PRIMARY KEY(post_id, tag_id)', + ]); + + // creates index for column `post_id` + $this->createIndex( + 'idx-post_tag-post_id', + 'post_tag', + 'post_id' + ); + + // add foreign key for table `post` + $this->addForeignKey( + 'fk-post_tag-post_id', + 'post_tag', + 'post_id', + 'post', + 'id', + 'CASCADE' + ); + + // creates index for column `tag_id` + $this->createIndex( + 'idx-post_tag-tag_id', + 'post_tag', + 'tag_id' + ); + + // add foreign key for table `tag` + $this->addForeignKey( + 'fk-post_tag-tag_id', + 'post_tag', + 'tag_id', + 'tag', + 'id', + 'CASCADE' + ); + } + + /** + * @inheritdoc + */ + public function down() + { + // drops foreign key for table `post` + $this->dropForeignKey( + 'fk-post_tag-post_id', + 'post_tag' + ); + + // drops index for column `post_id` + $this->dropIndex( + 'idx-post_tag-post_id', + 'post_tag' + ); + + // drops foreign key for table `tag` + $this->dropForeignKey( + 'fk-post_tag-tag_id', + 'post_tag' + ); + + // drops index for column `tag_id` + $this->dropIndex( + 'idx-post_tag-tag_id', + 'post_tag' + ); + + $this->dropTable('post_tag'); + } +} +``` + +### Migraciones Transaccionales + +Al ejecutar migraciones complejas de BD, es importante asegurarse que todas las migraciones funcionen o fallen como una unidad +así la base de datos puede mantener integridad y consistencia. Para alcanzar este objetivo, se recomienda que +encierres las operación de la BD de cada migración en una [transacción](db-dao.md#performing-transactions). + +Una manera simple de implementar migraciones transaccionales es poniendo el código de las migraciones en los métodos `safeUp()` y `safeDown()`. +Estos métodos se diferencias con `up()` y `down()` en que son encerrados implícitamente en una transacción. +Como resultado, si alguna de las operaciones dentro de estos métodos falla, todas las operaciones previas son automáticamente revertidas. + +En el siguiente ejemplo, además de crear la tabla `news` también insertamos un registro inicial dentro de la dicha tabla. + +```php +createTable('news', [ + 'id' => $this->primaryKey(), + 'title' => $this->string()->notNull(), + 'content' => $this->text(), + ]); + + $this->insert('news', [ + 'title' => 'test 1', + 'content' => 'content 1', + ]); + } + + public function safeDown() + { + $this->delete('news', ['id' => 1]); + $this->dropTable('news'); + } +} +``` + +Ten en cuenta que usualmente cuando ejecutas múltiples operaciones en la BD en `safeUp()`, deberías revertir su orden de ejecución +en `safeDown()`. En el ejemplo anterior primero creamos la tabla y luego insertamos la finla en `safeUp()`; mientras +que en `safeDown()` primero eliminamos el registro y posteriormente eliminamos la tabla. + +> Note: No todos los DBMS soportan transacciones. Y algunas consultas a la BD no pueden ser puestas en transacciones. Para algunos ejemplos, + por favor lee acerca de [commits implícitos](http://dev.mysql.com/doc/refman/5.7/en/implicit-commit.html). En estos casos, + deberías igualmente implementar `up()` y `down()`. + + +### Métodos de Acceso a la Base de Datos + +La clase base [[yii\db\Migration]] provee un grupo de métodos que te permiten acceder y manipular bases de datos. +Podrías encontrar que estos métodos son nombrados de forma similar a los [métodos DAO](db-dao.md) provistos por la clase [[yii\db\Command]]. +Por ejemplo, el método [[yii\db\Migration::createTable()]] te permite crear una nueva tabla, +tal como lo hace [[yii\db\Command::createTable()]]. + +El beneficio de utilizar lo métodos provistos por [[yii\db\Migration]] es que no necesitas explícitamente +crear instancias de [[yii\db\Command]], y la ejecución de cada método mostrará automáticamente mensajes útiles +diciéndote qué operaciones de la base de datos se realizaron y cuánto tiempo tomaron. + +Debajo hay una lista de todos los métodos de acceso a la base de datos: + +* [[yii\db\Migration::execute()|execute()]]: ejecuta una declaración SQL +* [[yii\db\Migration::insert()|insert()]]: inserta un único registro +* [[yii\db\Migration::batchInsert()|batchInsert()]]: inserta múltiples registros +* [[yii\db\Migration::update()|update()]]: actualiza registros +* [[yii\db\Migration::delete()|delete()]]: elimina registros +* [[yii\db\Migration::createTable()|createTable()]]: crea una nueva tabla +* [[yii\db\Migration::renameTable()|renameTable()]]: renombra una tabla +* [[yii\db\Migration::dropTable()|dropTable()]]: elimina una tabla +* [[yii\db\Migration::truncateTable()|truncateTable()]]: elimina todos los registros de una tabla +* [[yii\db\Migration::addColumn()|addColumn()]]: agrega una columna +* [[yii\db\Migration::renameColumn()|renameColumn()]]: renombra una columna +* [[yii\db\Migration::dropColumn()|dropColumn()]]: elimina una columna +* [[yii\db\Migration::alterColumn()|alterColumn()]]: modifica una columna +* [[yii\db\Migration::addPrimaryKey()|addPrimaryKey()]]: agrega una clave primaria +* [[yii\db\Migration::dropPrimaryKey()|dropPrimaryKey()]]: elimina una clave primaria +* [[yii\db\Migration::addForeignKey()|addForeignKey()]]: agrega una clave foránea +* [[yii\db\Migration::dropForeignKey()|dropForeignKey()]]: elimina una clave foránea +* [[yii\db\Migration::createIndex()|createIndex()]]: crea un índice +* [[yii\db\Migration::dropIndex()|dropIndex()]]: elimina un índice +* [[yii\db\Migration::addCommentOnColumn()|addCommentOnColumn()]: agrega un comentario a una columna +* [[yii\db\Migration::dropCommentFromColumn()|dropCommentFromColumn()]: elimina un comentario de una columna +* [[yii\db\Migration::addCommentOnTable()|addCommentOnTable()]: agrega un comentario a una tabla +* [[yii\db\Migration::dropCommentFromTable()|dropCommentFromTable()]: elimina un comentario de una tabla + +> Info: [[yii\db\Migration]] no provee un método de consulta a la base de datos. Esto es porque normalmente no necesitas + mostrar mensajes detallados al traer datos de una base de datos. También se debe a que puedes utilizar el poderoso + [Query Builder](db-query-builder.md) para generar y ejecutar consultas complejas. + +> Note: Al manipular datos utilizando una migración podrías encontrar que utilizando tus clases [Active Record](db-active-record.md) +> para esto podría ser útil ya que algo de la lógica ya está implementada ahí. Ten en cuenta de todos modos, que en contraste con +> el código escrito en las migraciones, cuya naturaleza es permanecer constante por siempre, la lógica de la aplicación está sujeta a cambios. +> Entonces al utilizar Active Record en migraciones, los cambios en la lógica en la capa Active Record podrían accidentalmente romper +> migraciones existentes. Por esta razón, el código de las migraciones debería permanecer independiente de determinada lógica de la aplicación +> tal como clases Active Record. + + +## Aplicar Migraciones + +To upgrade a database to its latest structure, you should apply all available new migrations using the following command: +Para actualizar una base de datos a su última estructura, deberías aplicar todas las nuevas migraciones utilizando el siguiente comando: + +``` +yii migrate +``` + +Este comando listará todas las migraciones que no han sido aplicadas hasta el momento. Si confirmas que quieres aplicar +dichas migraciones, se correrá el método `up()` o `safeUp()` en cada clase de migración nueva, una tras otra, +en el orden de su valor de marca temporal. Si alguna de las migraciones falla, el comando terminará su ejecución sin aplicar +el resto de las migraciones. + +> Tip: En caso de no disponer de la línea de comandos en el servidor, podrías intentar utilizar +> la extensión [web shell](https://github.com/samdark/yii2-webshell). + +Por cada migración aplicada correctamente, el comando insertará un registro en la base de datos, en la tabla llamada +`migration` para registrar la correcta aplicación de la migración. Esto permitirá a la herramienta de migración identificar +cuáles migraciones han sido aplicadas y cuáles no. + +> Info: La herramienta de migración creará automáticamente la tabla `migration` en la base de datos especificada + en la opción [[yii\console\controllers\MigrateController::db|db]] del comando. Por defecto, la base de datos + es especificada en el [componente de aplicación](structure-application-components.md) `db`. + +A veces, podrías sólo querer aplicar una o algunas pocas migraciones, en vez de todas las migraciones disponibles. +Puedes hacer esto el número de migraciones que quieres aplicar al ejecutar el comando. +Por ejemplo, el siguiente comando intentará aplicar las tres siguientes migraciones disponibles: + +``` +yii migrate 3 +``` + +Puedes además explícitamente especificar una migración en particular a la cual la base de datos debería migrar +utilizando el comando `migrate/to` de acuerdo a uno de los siguientes formatos: + +``` +yii migrate/to 150101_185401 # utiliza la marca temporal para especificar la migración +yii migrate/to "2015-01-01 18:54:01" # utiliza un string que puede ser analizado por strtotime() +yii migrate/to m150101_185401_create_news_table # utiliza el nombre completo +yii migrate/to 1392853618 # utiliza el tiempo UNIX +``` + +Si hubiera migraciones previas a la especificada sin aplicar, estas serán aplicadas antes de que la migración especificada +sea aplicada. + +Si la migración especificada ha sido aplicada previamente, cualquier migración aplicada posteriormente será revertida. + + +## Revertir Migraciones + +Para revertir (deshacer) una o varias migraciones ya aplicadas, puedes ejecutar el siguiente comando: + +``` +yii migrate/down # revierte la más reciente migración aplicada +yii migrate/down 3 # revierte las 3 últimas migraciones aplicadas +``` + +> Note: No todas las migraciones son reversibles. Intentar revertir tales migraciones producirá un error y detendrá + completamente el proceso de reversión. + + +## Rehacer Migraciones + +Rehacer (re-ejecutar) migraciones significa primero revertir las migraciones especificadas y luego aplicarlas nuevamente. Esto puede hacerse +de esta manera: + +``` +yii migrate/redo # rehace la más reciente migración aplicada +yii migrate/redo 3 # rehace las 3 últimas migraciones aplicadas +``` + +> Note: Si una migración no es reversible, no tendrás posibilidades de rehacerla. + + +## Listar Migraciones + +Para listar cuáles migraciones han sido aplicadas y cuáles no, puedes utilizar los siguientes comandos: + +``` +yii migrate/history # muestra las últimas 10 migraciones aplicadas +yii migrate/history 5 # muestra las últimas 5 migraciones aplicadas +yii migrate/history all # muestra todas las migraciones aplicadas + +yii migrate/new # muestra las primeras 10 nuevas migraciones +yii migrate/new 5 # muestra las primeras 5 nuevas migraciones +yii migrate/new all # muestra todas las nuevas migraciones +``` + + +## Modificar el Historial de Migraciones + +En vez de aplicar o revertir migraciones, a veces simplemente quieres marcar que tu base de datos +ha sido actualizada a una migración en particular. Esto sucede normalmente cuando cambias manualmente la base de datos +a un estado particular y no quieres que la/s migración/es de ese cambio sean re-aplicadas posteriormente. Puedes alcanzar este objetivo +con el siguiente comando: + +``` +yii migrate/mark 150101_185401 # utiliza la marca temporal para especificar la migración +yii migrate/mark "2015-01-01 18:54:01" # utiliza un string que puede ser analizado por strtotime() +yii migrate/mark m150101_185401_create_news_table # utiliza el nombre completo +yii migrate/mark 1392853618 # utiliza el tiempo UNIX +``` + +El comando modificará la tabla `migration` agregando o eliminado ciertos registros para indicar que en la base de datos +han sido aplicadas las migraciones hasta la especificada. Ninguna migración será aplicada ni revertida por este comando. + + +## Personalizar Migraciones + +Hay varias maneras de personalizar el comando de migración. + + +### Utilizar Opciones de la Línea de Comandos + +El comando de migración trae algunas opciones de línea de comandos que pueden ser utilizadas para personalizar su comportamiento: + +* `interactive`: boolean (por defecto true), especificar si se debe ejecutar la migración en modo interactivo. + Cuando se indica true, se le pedirá confirmación al usuario antes de ejecutar ciertas acciones. + Puedes querer definirlo como false si el comando está siendo utilizado como un proceso de fondo. + +* `migrationPath`: string (por defecto `@app/migrations`), especifica el directorio que contiene todos los archivos + de clase de las migraciones. Este puede ser especificado tanto como una ruta a un directorio un [alias](concept-aliases.md) de ruta. + Ten en cuenta que el directorio debe existir, o el comando disparará un error. + +* `migrationTable`: string (por defecto `migration`), especifica el nombre de la tabla de la base de datos que almacena + información del historial de migraciones. Dicha tabla será creada por el comando en caso de que no exista. + Puedes también crearla manualmente utilizando la estructura `version varchar(255) primary key, apply_time integer`. + +* `db`: string (por defecto `db`), especifica el ID del [componente de aplicación](structure-application-components.md) de la base de datos. + Esto representa la base de datos que será migrada en este comando. + +* `templateFile`: string (por defecto `@yii/views/migration.php`), especifica la ruta al template + utilizado para generar el esqueleto de los archivos de clases de migración. Puede ser especificado tanto como una ruta a un archivo + como una [alias](concept-aliases.md) de una ruta. El template es un archivo PHP en el cual puedes utilizar una variable predefinida + llamada `$className` para obtener el nombre de clase de la migración. + +* `generatorTemplateFiles`: array (por defecto `[ + 'create_table' => '@yii/views/createTableMigration.php', + 'drop_table' => '@yii/views/dropTableMigration.php', + 'add_column' => '@yii/views/addColumnMigration.php', + 'drop_column' => '@yii/views/dropColumnMigration.php', + 'create_junction' => '@yii/views/createJunctionMigration.php' + ]`), especifica los templates utilizados para generar las migraciones. Ver "[Generar Migraciones](#generating-migrations)" + para más detalles. + +* `fields`: array de strings de definiciones de columna utilizado por el código de migración. Por defecto `[]`. El formato de cada + definición es `COLUMN_NAME:COLUMN_TYPE:COLUMN_DECORATOR`. Por ejemplo, `--fields=name:string(12):notNull` produce + una columna string de tamaño 12 que es not null. + +El siguiente ejemplo muestra cómo se pueden utilizar estas opciones. + +Por ejemplo, si queremos migrar un módulo `forum` cuyos arhivos de migración +están ubicados dentro del directorio `migrations` del módulo, podemos utilizar el siguientedocs/guide-es/db-migrations.md +comando: + +``` +# realiza las migraciones de un módulo forum sin interacción del usuario +yii migrate --migrationPath=@app/modules/forum/migrations --interactive=0 +``` + + +### Configurar el Comando Globalmente + +En vez de introducir los valores de las opciones cada vez que ejecutas un comandod e migración, podrías configurarlos +de una vez por todas en la configuración de la aplicación como se muestra a continuación: + +```php +return [ + 'controllerMap' => [ + 'migrate' => [ + 'class' => 'yii\console\controllers\MigrateController', + 'migrationTable' => 'backend_migration', + ], + ], +]; +``` + +Con esta configuración, cada vez que ejecutes un comando de migración, la tabla `backend_migration` +será utilizada para registrar el historial de migraciones. No necesitarás volver a especificarla con la opción `migrationTable` +de la línea de comandos. + + +## Migrar Múltiples Bases de Datos + +Por defecto, las migraciones son aplicadas en la misma base de datos especificada en el [componente de aplicación](structure-application-components.md) `db`. +Si quieres que sean aplicadas en una base de datos diferente, puedes especificar la opción `db` como se muestra a continuación, + +``` +yii migrate --db=db2 +``` + +El comando anterior aplicará las migraciones en la base de datos `db2`. + +A veces puede suceder que quieras aplicar *algunas* de las migraciones a una base de datos, mientras algunas otras +a una base de datos distinta. Para lograr esto, al implementar una clase de migración debes especificar explícitamente el ID del componente DB +que la migración debe utilizar, como a continuación: + +```php +db = 'db2'; + parent::init(); + } +} +``` + +La migración anterior se aplicará a `db2`, incluso si especificas una base de datos diferente en la opción `db` de la +línea de comandos. Ten en cuenta que el historial aún será registrado in la base de datos especificada en la opción `db` de la línea de comandos. + +Si tienes múltiples migraciones que utilizan la misma base de datos, es recomandable que crees una clase base de migración +con el código `init()` mostrado. Entonces cada clase de migración puede extender de esa clase base. + +> Tip: Aparte de definir la propiedad [[yii\db\Migration::db|db]], puedes también operar en diferentes bases de datos + creando nuevas conexiones de base de datos en tus clases de migración. También puedes utilizar [métodos DAO](db-dao.md) + con esas conexiones para manipular diferentes bases de datos. + +Another strategy that you can take to migrate multiple databases is to keep migrations for different databases in +different migration paths. Then you can migrate these databases in separate commands like the following: +Otra estrategia que puedes seguir para migrar múltiples bases de datos es mantener las migraciones para diferentes bases de datos en +distintas rutas de migración. Entonces podrías migrar esas bases de datos en comandos separados como a continuación: + +``` +yii migrate --migrationPath=@app/migrations/db1 --db=db1 +yii migrate --migrationPath=@app/migrations/db2 --db=db2 +... +``` + +El primer comando aplicará las migraciones que se encuentran en `@app/migrations/db1` en la base de datos `db1`, el segundo comando +aplicará las migraciones que se encuentran en `@app/migrations/db2` en `db2`, y así sucesivamente. diff --git a/docs/guide/db-migrations.md b/docs/guide/db-migrations.md index d7e59e6..b24e346 100644 --- a/docs/guide/db-migrations.md +++ b/docs/guide/db-migrations.md @@ -414,8 +414,7 @@ The `foreignKey` keyword can take a parameter between parenthesis which will be the name of the related table for the generated foreign key. If no parameter is passed then the table name will be deduced from the column name. -In the -example above `author_id:integer:notNull:foreignKey(user)` will generate a +In the example above `author_id:integer:notNull:foreignKey(user)` will generate a column named `author_id` with a foreign key to the `user` table while `category_id:integer:defaultValue(1):foreignKey` will generate a column `category_id` with a foreign key to the `category` table. From 7e7db38011b4a6f6880843b0aa60d2f4ebf5111b Mon Sep 17 00:00:00 2001 From: Paul Klimov Date: Fri, 20 May 2016 11:04:17 +0200 Subject: [PATCH 55/90] Fix #11591 `ActionFilter` wildcard (#11594) * Added support for wildcards for `only` and `except` at `yii\base\ActionFilter` --- framework/CHANGELOG.md | 1 + framework/base/ActionFilter.php | 25 ++++++++++++++++++++++++- tests/framework/base/ActionFilterTest.php | 24 ++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 59f9b8f..7c83caf 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -7,6 +7,7 @@ Yii Framework 2 Change Log - Enh #11195: Added ability to append custom string to schema builder column definition (df2, samdark) - Enh #11490: Added `yii\data\ArrayDataProvider::$modelClass` property to specify a model used to provide column labels even when data array is empty (PowerGamer1) +- Enh #11591: Added support for wildcards for `only` and `except` at `yii\base\ActionFilter` (klimov-paul) - Bug #9950: Updated `yii\grid\DataColumn::getHeaderCellLabel()` to extract attribute label from the `filterModel` of Grid (silverfire) - Bug #11429: Fixed `yii\i18n\PhpMessageSource::loadFallbackMessages()` not to log error when source and language is same, but locales are different (silverfire) - Enh #11428: Speedup SQL query in `yii\db\oci\Schema::findColumns()` (SSiwek) diff --git a/framework/base/ActionFilter.php b/framework/base/ActionFilter.php index 2d1b3a9..67186f4 100644 --- a/framework/base/ActionFilter.php +++ b/framework/base/ActionFilter.php @@ -28,6 +28,8 @@ class ActionFilter extends Behavior * Note that if the filter is attached to a module, the action IDs should also include child module IDs (if any) * and controller IDs. * + * @since 2.0.9 action IDs can be specified as wildcards, e.g. `site/*`. + * * @see except */ public $only; @@ -138,6 +140,27 @@ class ActionFilter extends Behavior protected function isActive($action) { $id = $this->getActionId($action); - return !in_array($id, $this->except, true) && (empty($this->only) || in_array($id, $this->only, true)); + + if (empty($this->only)) { + $onlyMatch = true; + } else { + $onlyMatch = false; + foreach ($this->only as $pattern) { + if (fnmatch($pattern, $id)) { + $onlyMatch = true; + break; + } + } + } + + $exceptMatch = false; + foreach ($this->except as $pattern) { + if (fnmatch($pattern, $id)) { + $exceptMatch = true; + break; + } + } + + return !$exceptMatch && $onlyMatch; } } diff --git a/tests/framework/base/ActionFilterTest.php b/tests/framework/base/ActionFilterTest.php index 464868d..0b81ae3 100644 --- a/tests/framework/base/ActionFilterTest.php +++ b/tests/framework/base/ActionFilterTest.php @@ -135,6 +135,30 @@ class ActionFilterTest extends TestCase $this->assertEquals(false, $method->invokeArgs($filter, [new Action('view', $controller)])); } + /** + * @depends testActive + */ + public function testActiveWildcard() + { + $this->mockWebApplication(); + + $filter = new ActionFilter(); + $reflection = new \ReflectionClass($filter); + $method = $reflection->getMethod('isActive'); + $method->setAccessible(true); + + $controller = new \yii\web\Controller('test', Yii::$app); + + $filter->only = ['test/*']; + $filter->except = []; + $this->assertFalse($method->invokeArgs($filter, [new Action('index', $controller)])); + $this->assertTrue($method->invokeArgs($filter, [new Action('test/index', $controller)])); + + $filter->only = []; + $filter->except = ['test/*']; + $this->assertTrue($method->invokeArgs($filter, [new Action('index', $controller)])); + $this->assertFalse($method->invokeArgs($filter, [new Action('test/index', $controller)])); + } } class FakeController extends Controller From e49bc29aa80a382a7de22835a76693bc7d25790e Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Fri, 20 May 2016 13:13:42 +0200 Subject: [PATCH 56/90] Update ActionFilter.php --- framework/base/ActionFilter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/base/ActionFilter.php b/framework/base/ActionFilter.php index 67186f4..8f33f4e 100644 --- a/framework/base/ActionFilter.php +++ b/framework/base/ActionFilter.php @@ -28,7 +28,7 @@ class ActionFilter extends Behavior * Note that if the filter is attached to a module, the action IDs should also include child module IDs (if any) * and controller IDs. * - * @since 2.0.9 action IDs can be specified as wildcards, e.g. `site/*`. + * Since version 2.0.9 action IDs can be specified as wildcards, e.g. `site/*`. * * @see except */ From b4ed34558fc5403df69466247d6b7642e79a32b9 Mon Sep 17 00:00:00 2001 From: Luciano Baraglia Date: Fri, 20 May 2016 16:27:23 -0300 Subject: [PATCH 57/90] Some spanish doc translations [skip ci] --- docs/guide-es/test-environment-setup.md | 49 ++++ docs/guide-es/test-fixtures.md | 389 ++++++++++++++++++++++++++++++++ 2 files changed, 438 insertions(+) create mode 100644 docs/guide-es/test-environment-setup.md create mode 100644 docs/guide-es/test-fixtures.md diff --git a/docs/guide-es/test-environment-setup.md b/docs/guide-es/test-environment-setup.md new file mode 100644 index 0000000..631f96a --- /dev/null +++ b/docs/guide-es/test-environment-setup.md @@ -0,0 +1,49 @@ +Preparación del entorno de test +=============================== + +> Note: Esta sección se encuentra en desarrollo. + +Yii 2 ha mantenido integración oficial con el framework de testing [`Codeception`](https://github.com/Codeception/Codeception), +que te permite crear los siguientes tipos de tests: + +- [Test de unidad](test-unit.md) - verifica que una unidad simple de código funciona como se espera; +- [Test funcional](test-functional.md) - verifica escenarios desde la perspectiva de un usuario a través de la emulación de un navegador; +- [Test de aceptación](test-acceptance.md) - verifica escenarios desde la perspectiva de un usuario en un navegador. + +Yii provee grupos de pruebas listos para utilizar en ambos +[`yii2-basic`](https://github.com/yiisoft/yii2-app-basic) y +[`yii2-advanced`](https://github.com/yiisoft/yii2-app-advanced) templates de proyectos. + +Para poder ejecutar estos tests es necesario instalar [Codeception](https://github.com/Codeception/Codeception). +Puedes instalarlo tanto localmente - únicamente para un proyecto en particular, o globalmente - para tu máquina de desarrollo. + +Para la instalación local utiliza los siguientes comandos: + +``` +composer require "codeception/codeception=2.1.*" +composer require "codeception/specify=*" +composer require "codeception/verify=*" +``` + +Para la instalación global necesitarás la directiva `global`: + +``` +composer global require "codeception/codeception=2.1.*" +composer global require "codeception/specify=*" +composer global require "codeception/verify=*" +``` + +En caso de que nunca hayas utilizado Composer para paquetes globales, ejecuta `composer global status`. Esto debería mostrar la salida: + +``` +Changed current directory to +``` + +Entonces agrega `/vendor/bin` a tu variable de entorno `PATH`. Ahora podrás utilizar el `codecept` en la línea +de comandos a nivel global. + +> Note: la instalación global te permite usar Codeception para todos los proyectos en los que trabajes en tu máquina de desarrollo y + te permite ejecutar el comando `codecept` globalmente sin especificar su ruta. De todos modos, ese acercamiento podría ser inapropiado, + por ejemplo, si 2 proyectos diferentes requieren diferentes versiones de Codeception instaladas. + Por simplicidad, todos los comandos relacionados a tests en esta guía están escritos asumiendo que Codeception + ha sido instalado en forma global. diff --git a/docs/guide-es/test-fixtures.md b/docs/guide-es/test-fixtures.md new file mode 100644 index 0000000..f7822e6 --- /dev/null +++ b/docs/guide-es/test-fixtures.md @@ -0,0 +1,389 @@ +Fixtures +======== + +Los fixtures son una parte importante de los tests. Su propósito principal es el de preparar el entorno en una estado fijado/conocido +de manera que los tests sean repetibles y corran de la manera esperada. Yii provee un framework de fixtures que te permite +dichos fixtures de manera precisa y usarlo de forma simple. + +Un concepto clave en el framework de fixtures de Yii es el llamado *objeto fixture*. Un objeto fixture representa +un aspecto particular de un entorno de pruebas y es una instancia de [[yii\test\Fixture]] o heredada de esta. Por ejemplo, +puedes utilizar `UserFixture` para asegurarte de que la tabla de usuarios de la BD contiene un grupo de datos fijos. Entonces cargas uno o varios +objetos fixture antes de correr un test y lo descargas cuando el test ha concluido. + +Un fixture puede depender de otros fixtures, especificándolo en su propiedad [[yii\test\Fixture::depends]]. +Cuando un fixture está siendo cargado, los fixtures de los que depende serán cargados automáticamente ANTES que él; +y cuando el fixture está siendo descargado, los fixtures dependientes serán descargados DESPUÉS de él. + + +Definir un Fixture +------------------ + +Para definir un fixture, crea una nueva clase que extienda de [[yii\test\Fixture]] o [[yii\test\ActiveFixture]]. +El primero es más adecuado para fixtures de propósito general, mientras que el último tiene características mejoradas específicamente +diseñadas para trabajar con base de datos y ActiveRecord. + +El siguiente código define un fixture acerca del ActiveRecord `User` y su correspondiente tabla user. + +```php + Tip: Cada `ActiveFixture` se encarga de preparar la tabla de la DB para los tests. Puedes especificar la tabla +> definiendo tanto la propiedad [[yii\test\ActiveFixture::tableName]] o la propiedad [[yii\test\ActiveFixture::modelClass]]. +> Haciéndolo como el último, el nombre de la tabla será tomado de la clase `ActiveRecord` especificada en `modelClass`. + +> Note: [[yii\test\ActiveFixture]] es sólo adecualdo para bases de datos SQL. Para bases de datos NoSQL, Yii provee +> las siguientes clases `ActiveFixture`: +> +> - Mongo DB: [[yii\mongodb\ActiveFixture]] +> - Elasticsearch: [[yii\elasticsearch\ActiveFixture]] (desde la versión 2.0.2) + + +Los datos para un fixture `ActiveFixture` son usualmente provistos en un archivo ubicado en `FixturePath/data/TableName.php`, +donde `FixturePath` correponde al directorio conteniendo el archivo de clase del fixture, y `TableName` +es el nombre de la tabla asociada al fixture. En el ejemplo anterior, el archivo debería ser +`@app/tests/fixtures/data/user.php`. El archivo de datos debe devolver un array de registros +a ser insertados en la tabla user. Por ejemplo, + +```php + [ + 'username' => 'lmayert', + 'email' => 'strosin.vernice@jerde.com', + 'auth_key' => 'K3nF70it7tzNsHddEiq0BZ0i-OU8S3xV', + 'password' => '$2y$13$WSyE5hHsG1rWN2jV8LRHzubilrCLI5Ev/iK0r3jRuwQEs2ldRu.a2', + ], + 'user2' => [ + 'username' => 'napoleon69', + 'email' => 'aileen.barton@heaneyschumm.com', + 'auth_key' => 'dZlXsVnIDgIzFgX4EduAqkEPuphhOh9q', + 'password' => '$2y$13$kkgpvJ8lnjKo8RuoR30ay.RjDf15bMcHIF7Vz1zz/6viYG5xJExU6', + ], +]; +``` + +Puedes dar un alias al registro tal que más tarde en tu test, puedas referirte a ese registra a través de dicho alias. En el ejemplo anterior, +los dos registros tienen como alias `user1` y `user2`, respectivamente. + +Además, no necesitas especificar los datos de columnas auto-incrementales. Yii automáticamente llenará esos valores +dentro de los registros cuando el fixture está siendo cargado. + +> Tip: Puedes personalizar la ubicación del archivo de datos definiendo la propiedad [[yii\test\ActiveFixture::dataFile]]. +> Puedes también sobrescribir [[yii\test\ActiveFixture::getData()]] para obtener los datos. + +Como se describió anteriormente, un fixture puede depender de otros fixtures. Por ejemplo, un `UserProfileFixture` puede necesitar depender de `UserFixture` +porque la table de perfiles de usuarios contiene una cláve foránea a la tabla user. +La dependencia es especificada vía la propiedad [[yii\test\Fixture::depends]], como a continuación, + +```php +namespace app\tests\fixtures; + +use yii\test\ActiveFixture; + +class UserProfileFixture extends ActiveFixture +{ + public $modelClass = 'app\models\UserProfile'; + public $depends = ['app\tests\fixtures\UserFixture']; +} +``` + +The dependency also ensures, that the fixtures are loaded and unloaded in a well defined order. In the above example `UserFixture` will +always be loaded before `UserProfileFixture` to ensure all foreign key references exist and will be unloaded after `UserProfileFixture` +has been unloaded for the same reason. +La dependencia también asegura que los fixtures son cargados y descargados en un orden bien definido. En el ejemplo `UserFixture` +será siempre cargado antes de `UserProfileFixture` para asegurar que todas las referencias de las claves foráneas existan y será siempre descargado después de `UserProfileFixture` +por la misma razón. + +Arriba te mostramos cómo definir un fixture de BD. Para definir un fixture no relacionado a BD +(por ej. un fixture acerca de archivos y directorios), puedes extender de la clase base más general +[[yii\test\Fixture]] y sobrescribir los métodos [[yii\test\Fixture::load()|load()]] y [[yii\test\Fixture::unload()|unload()]]. + + +Utilizar Fixtures +----------------- + +Si estás utilizando [Codeception](http://codeception.com/) para hacer tests de tu código, deberías considerar el utilizar +la extensión `yii2-codeception`, que tiene soporte incorporado para la carga y acceso a fixtures. +En caso de que utilices otros frameworks de testing, puedes usar [[yii\test\FixtureTrait]] en tus casos de tests +para alcanzar el mismo objetivo. + +A continuación describiremos cómo escribir una clase de test de unidad `UserProfile` utilizando `yii2-codeception`. + +En tu clase de test de unidad que extiende de [[yii\codeception\DbTestCase]] o [[yii\codeception\TestCase]], +indica cuáles fixtures quieres utilizar en el método [[yii\test\FixtureTrait::fixtures()|fixtures()]]. Por ejemplo, + +```php +namespace app\tests\unit\models; + +use yii\codeception\DbTestCase; +use app\tests\fixtures\UserProfileFixture; + +class UserProfileTest extends DbTestCase +{ + public function fixtures() + { + return [ + 'profiles' => UserProfileFixture::className(), + ]; + } + + // ...métodos de test... +} +``` + +Los fixtures listados en el método `fixtures()` serán automáticamente cargados antes de correr cada método de test +en el caso de test y descargado al finalizar cada uno. También, como describimos antes, cuando un fixture está +siendo cargado, todos sus fixtures dependientes serán cargados primero. En el ejemplo de arriba, debido a que +`UserProfileFixture` depende de `UserFixture`, cuando ejecutas cualquier método de test en la clase, +dos fixtures serán cargados secuencialmente: `UserFixture` y `UserProfileFixture`. + +Al especificar fixtures en `fixtures()`, puedes utilizar tanto un nombre de clase o un array de configuración para referirte a +un fixture. El array de configuración te permitirá personalizar las propiedades del fixture cuando este es cargado. + +Puedes también asignarles alias a los fixtures. En el ejemplo anterior, el `UserProfileFixture` tiene como alias `profiles`. +En los métodos de test, puedes acceder a un objeto fixture utilizando su alias. Por ejemplo, `$this->profiles` +devolverá el objeto `UserProfileFixture`. + +Dado que `UserProfileFixture` extiende de `ActiveFixture`, puedes por lo tanto usar la siguiente sintáxis para acceder +a los datos provistos por el fixture: + +```php +// devuelve el registro del fixture cuyo alias es 'user1' +$row = $this->profiles['user1']; +// devuelve el modelo UserProfile correspondiente al registro cuyo alias es 'user1' +$profile = $this->profiles('user1'); +// recorre cada registro en el fixture +foreach ($this->profiles as $row) ... +``` + +> Info: `$this->profiles` es todavía del tipo `UserProfileFixture`. Las características de acceso mostradas arriba son implementadas +> a través de métodos mágicos de PHP. + + +Definir y Utilizar Fixtures Globales +------------------------------------ + +The fixtures described above are mainly used by individual test cases. In most cases, you also need some global +fixtures that are applied to ALL or many test cases. An example is [[yii\test\InitDbFixture]] which does +two things: +Los fixtures descritos arriba son principalmente utilizados para casos de tests individuales. En la mayoría de los casos, puedes necesitar algunos +fixtures globales que sean aplicados a TODOS o muchos casos de test. Un ejemplo sería [[yii\test\InitDbFixture]], que hace +dos cosas: + +* Realiza alguna tarea de inicialización común al ejectutar un script ubicado en `@app/tests/fixtures/initdb.php`; +* Deshabilita la comprobación de integridad antes de cargar otros fixtures de BD, y la rehabilita después de que todos los fixtures son descargados. + +Utilizar fixtures globales es similar a utilizar los no-globales. La única diferencia es que declaras estos fixtures +en [[yii\codeception\TestCase::globalFixtures()]] en vez de en `fixtures()`. Cuando un caso de test carga fixtures, +primero carga los globales y luego los no-globales. + +By default, [[yii\codeception\DbTestCase]] already declares `InitDbFixture` in its `globalFixtures()` method. +This means you only need to work with `@app/tests/fixtures/initdb.php` if you want to do some initialization work +before each test. You may otherwise simply focus on developing each individual test case and the corresponding fixtures. +Por defecto, [[yii\codeception\DbTestCase]] ya declara `InitDbFixture` en su método `globalFixtures()`. +Esto significa que sólo necesitas trabajar con `@app/tests/fixtures/initdb.php` si quieres realizar algún trabajo de inicialización +antes de cada test. Sino puedes simplemente enfocarte en desarrollar cada caso de test individual y sus fixtures correspondientes. + + +Organizar Clases de Fixtures y Archivos de Datos +------------------------------------------------ + +Por defecto, las clases de fixtures busca los archivos de datos correspondientes dentro de la carpeta `data`, que es una subcarpeta +de la carpeta conteniendo los archivos de clases de fixtures. Puedes seguir esta convención al trabajar en proyectos simples. +Para proyectos más grandes, es probable que a menudo necesites intercambiar entre diferentes archivos de datos para la misma clase de fixture +en diferentes tests. Recomendamos que organices los archivos de datos en forma jerárquica similar +a tus espacios de nombre de clases. Por ejemplo, + +``` +# bajo la carpeta tests\unit\fixtures + +data\ + components\ + fixture_data_file1.php + fixture_data_file2.php + ... + fixture_data_fileN.php + models\ + fixture_data_file1.php + fixture_data_file2.php + ... + fixture_data_fileN.php +# y así sucesivamente +``` + +In this way you will avoid collision of fixture data files between tests and use them as you need. +De esta manera evitarás la colisión de archivos de datos de fixtures entre tests y podrás utlilizarlos como necesites. + +> Note: In the example above fixture files are named only for example purpose. In real life you should name them +> according to which fixture class your fixture classes are extending from. For example, if you are extending +> from [[yii\test\ActiveFixture]] for DB fixtures, you should use DB table names as the fixture data file names; +> If you are extending from [[yii\mongodb\ActiveFixture]] for MongoDB fixtures, you should use collection names as the file names. + +The similar hierarchy can be used to organize fixture class files. Instead of using `data` as the root directory, you may +want to use `fixtures` as the root directory to avoid conflict with the data files. + + +Summary +------- + +> Note: This section is under development. + +In the above, we have described how to define and use fixtures. Below we summarize the typical workflow +of running unit tests related with DB: + +1. Use `yii migrate` tool to upgrade your test database to the latest version; +2. Run a test case: + - Load fixtures: clean up the relevant DB tables and populate them with fixture data; + - Perform the actual test; + - Unload fixtures. +3. Repeat Step 2 until all tests finish. + + +**To be cleaned up below** + +Managing Fixtures +================= + +> Note: This section is under development. +> +> todo: this tutorial may be merged with the above part of test-fixtures.md + +Fixtures are important part of testing. Their main purpose is to populate you with data that needed by testing +different cases. With this data using your tests becoming more efficient and useful. + +Yii supports fixtures via the `yii fixture` command line tool. This tool supports: + +* Loading fixtures to different storage such as: RDBMS, NoSQL, etc; +* Unloading fixtures in different ways (usually it is clearing storage); +* Auto-generating fixtures and populating it with random data. + +Fixtures format +--------------- + +Fixtures are objects with different methods and configurations, refer to official [documentation](https://github.com/yiisoft/yii2/blob/master/docs/guide/test-fixtures.md) on them. +Lets assume we have fixtures data to load: + +``` +#users.php file under fixtures data path, by default @tests\unit\fixtures\data + +return [ + [ + 'name' => 'Chase', + 'login' => 'lmayert', + 'email' => 'strosin.vernice@jerde.com', + 'auth_key' => 'K3nF70it7tzNsHddEiq0BZ0i-OU8S3xV', + 'password' => '$2y$13$WSyE5hHsG1rWN2jV8LRHzubilrCLI5Ev/iK0r3jRuwQEs2ldRu.a2', + ], + [ + 'name' => 'Celestine', + 'login' => 'napoleon69', + 'email' => 'aileen.barton@heaneyschumm.com', + 'auth_key' => 'dZlXsVnIDgIzFgX4EduAqkEPuphhOh9q', + 'password' => '$2y$13$kkgpvJ8lnjKo8RuoR30ay.RjDf15bMcHIF7Vz1zz/6viYG5xJExU6', + ], +]; +``` +If we are using fixture that loads data into database then these rows will be applied to `users` table. If we are using nosql fixtures, for example `mongodb` +fixture, then this data will be applied to `users` mongodb collection. In order to learn about implementing various loading strategies and more, refer to official [documentation](https://github.com/yiisoft/yii2/blob/master/docs/guide/test-fixtures.md). +Above fixture example was auto-generated by `yii2-faker` extension, read more about it in these [section](#auto-generating-fixtures). +Fixture classes name should not be plural. + +Loading fixtures +---------------- + +Fixture classes should be suffixed by `Fixture` class. By default fixtures will be searched under `tests\unit\fixtures` namespace, you can +change this behavior with config or command options. You can exclude some fixtures due load or unload by specifying `-` before its name like `-User`. + +To load fixture, run the following command: + +``` +yii fixture/load +``` + +The required `fixture_name` parameter specifies a fixture name which data will be loaded. You can load several fixtures at once. +Below are correct formats of this command: + +``` +// load `User` fixture +yii fixture/load User + +// same as above, because default action of "fixture" command is "load" +yii fixture User + +// load several fixtures +yii fixture User UserProfile + +// load all fixtures +yii fixture/load "*" + +// same as above +yii fixture "*" + +// load all fixtures except ones +yii fixture "*" -DoNotLoadThisOne + +// load fixtures, but search them in different namespace. By default namespace is: tests\unit\fixtures. +yii fixture User --namespace='alias\my\custom\namespace' + +// load global fixture `some\name\space\CustomFixture` before other fixtures will be loaded. +// By default this option is set to `InitDbFixture` to disable/enable integrity checks. You can specify several +// global fixtures separated by comma. +yii fixture User --globalFixtures='some\name\space\Custom' +``` + +Unloading fixtures +------------------ + +To unload fixture, run the following command: + +``` +// unload Users fixture, by default it will clear fixture storage (for example "users" table, or "users" collection if this is mongodb fixture). +yii fixture/unload User + +// Unload several fixtures +yii fixture/unload User,UserProfile + +// unload all fixtures +yii fixture/unload "*" + +// unload all fixtures except ones +yii fixture/unload "*" -DoNotUnloadThisOne + +``` + +Same command options like: `namespace`, `globalFixtures` also can be applied to this command. + +Configure Command Globally +-------------------------- +While command line options allow us to configure the migration command +on-the-fly, sometimes we may want to configure the command once for all. For example you can configure +different migration path as follows: + +``` +'controllerMap' => [ + 'fixture' => [ + 'class' => 'yii\console\controllers\FixtureController', + 'namespace' => 'myalias\some\custom\namespace', + 'globalFixtures' => [ + 'some\name\space\Foo', + 'other\name\space\Bar' + ], + ], +] +``` + +Auto-generating fixtures +------------------------ + +Yii also can auto-generate fixtures for you based on some template. You can generate your fixtures with different data on different languages and formats. +These feature is done by [Faker](https://github.com/fzaninotto/Faker) library and `yii2-faker` extension. +See extension [guide](https://github.com/yiisoft/yii2-faker) for more docs. From 3eb70a724bf500e18b36b5baeeada248198eaa72 Mon Sep 17 00:00:00 2001 From: Luciano Baraglia Date: Fri, 20 May 2016 22:39:09 -0300 Subject: [PATCH 58/90] Test fixtures spanish docs [skip ci] --- docs/guide-es/test-fixtures.md | 152 +++++++++++++++++++---------------------- 1 file changed, 71 insertions(+), 81 deletions(-) diff --git a/docs/guide-es/test-fixtures.md b/docs/guide-es/test-fixtures.md index f7822e6..445617d 100644 --- a/docs/guide-es/test-fixtures.md +++ b/docs/guide-es/test-fixtures.md @@ -48,7 +48,7 @@ class UserFixture extends ActiveFixture Los datos para un fixture `ActiveFixture` son usualmente provistos en un archivo ubicado en `FixturePath/data/TableName.php`, -donde `FixturePath` correponde al directorio conteniendo el archivo de clase del fixture, y `TableName` +donde `FixturePath` corresponde al directorio conteniendo el archivo de clase del fixture, y `TableName` es el nombre de la tabla asociada al fixture. En el ejemplo anterior, el archivo debería ser `@app/tests/fixtures/data/user.php`. El archivo de datos debe devolver un array de registros a ser insertados en la tabla user. Por ejemplo, @@ -81,7 +81,7 @@ dentro de los registros cuando el fixture está siendo cargado. > Puedes también sobrescribir [[yii\test\ActiveFixture::getData()]] para obtener los datos. Como se describió anteriormente, un fixture puede depender de otros fixtures. Por ejemplo, un `UserProfileFixture` puede necesitar depender de `UserFixture` -porque la table de perfiles de usuarios contiene una cláve foránea a la tabla user. +porque la table de perfiles de usuarios contiene una clave foránea a la tabla user. La dependencia es especificada vía la propiedad [[yii\test\Fixture::depends]], como a continuación, ```php @@ -96,9 +96,6 @@ class UserProfileFixture extends ActiveFixture } ``` -The dependency also ensures, that the fixtures are loaded and unloaded in a well defined order. In the above example `UserFixture` will -always be loaded before `UserProfileFixture` to ensure all foreign key references exist and will be unloaded after `UserProfileFixture` -has been unloaded for the same reason. La dependencia también asegura que los fixtures son cargados y descargados en un orden bien definido. En el ejemplo `UserFixture` será siempre cargado antes de `UserProfileFixture` para asegurar que todas las referencias de las claves foráneas existan y será siempre descargado después de `UserProfileFixture` por la misma razón. @@ -172,9 +169,6 @@ foreach ($this->profiles as $row) ... Definir y Utilizar Fixtures Globales ------------------------------------ -The fixtures described above are mainly used by individual test cases. In most cases, you also need some global -fixtures that are applied to ALL or many test cases. An example is [[yii\test\InitDbFixture]] which does -two things: Los fixtures descritos arriba son principalmente utilizados para casos de tests individuales. En la mayoría de los casos, puedes necesitar algunos fixtures globales que sean aplicados a TODOS o muchos casos de test. Un ejemplo sería [[yii\test\InitDbFixture]], que hace dos cosas: @@ -186,9 +180,6 @@ Utilizar fixtures globales es similar a utilizar los no-globales. La única dife en [[yii\codeception\TestCase::globalFixtures()]] en vez de en `fixtures()`. Cuando un caso de test carga fixtures, primero carga los globales y luego los no-globales. -By default, [[yii\codeception\DbTestCase]] already declares `InitDbFixture` in its `globalFixtures()` method. -This means you only need to work with `@app/tests/fixtures/initdb.php` if you want to do some initialization work -before each test. You may otherwise simply focus on developing each individual test case and the corresponding fixtures. Por defecto, [[yii\codeception\DbTestCase]] ya declara `InitDbFixture` en su método `globalFixtures()`. Esto significa que sólo necesitas trabajar con `@app/tests/fixtures/initdb.php` si quieres realizar algún trabajo de inicialización antes de cada test. Sino puedes simplemente enfocarte en desarrollar cada caso de test individual y sus fixtures correspondientes. @@ -220,60 +211,59 @@ data\ # y así sucesivamente ``` -In this way you will avoid collision of fixture data files between tests and use them as you need. De esta manera evitarás la colisión de archivos de datos de fixtures entre tests y podrás utlilizarlos como necesites. -> Note: In the example above fixture files are named only for example purpose. In real life you should name them -> according to which fixture class your fixture classes are extending from. For example, if you are extending -> from [[yii\test\ActiveFixture]] for DB fixtures, you should use DB table names as the fixture data file names; -> If you are extending from [[yii\mongodb\ActiveFixture]] for MongoDB fixtures, you should use collection names as the file names. +> Note: En el ejemplo de arriba los archivos de fixtures son nombrados así sólo como ejemplo. En la vida real deberías nombrarlos +> de acuerdo a qué clase de fixture extienden tus clases de fixtures. Por ejemplo, si estás extendiendo +> de [[yii\test\ActiveFixture]] para fixtures de BD, deberías utilizar nombres de tabla de la BD como nombres de los archivos de fixtures; +> Si estás extendiendo de [[yii\mongodb\ActiveFixture]] para fixtures de MongoDB, deberías utilizar nombres de colecciones para los nombres de archivo. -The similar hierarchy can be used to organize fixture class files. Instead of using `data` as the root directory, you may -want to use `fixtures` as the root directory to avoid conflict with the data files. +Se puede utilizar una jerarquía similar para organizar archivos de clases de fixtures. En vez de utilizar `data` como directorio raíz, podrías +querer utilizar `fixtures` como directorio raíz para evitar conflictos con los archivos de datos. -Summary +Resumen ------- -> Note: This section is under development. +> Note: Esta sección se encuentra en desarrollo. -In the above, we have described how to define and use fixtures. Below we summarize the typical workflow -of running unit tests related with DB: +Arriba, definimos cómo definir y utilizar fixtures. Abajo resumiremos el típico flujo de trabajo +de correr tests de unidad relacionados a BD: -1. Use `yii migrate` tool to upgrade your test database to the latest version; -2. Run a test case: - - Load fixtures: clean up the relevant DB tables and populate them with fixture data; - - Perform the actual test; - - Unload fixtures. -3. Repeat Step 2 until all tests finish. +1. Usa la herramienta `yii migrate` para actualizar tu base de datos de prueba a la última versión; +2. Corre el caso de test: + - Carga los fixtures: limpia las tablas de la BD relevantes y cargala con los datos de los fixtures; + - Realiza el test en sí; + - Descarga los fixtures. +3. Repite el Paso 2 hasta que todos los tests terminen. -**To be cleaned up below** +**Lo siguiente, a ser limpiado** -Managing Fixtures -================= +Administrar Fixtures +==================== -> Note: This section is under development. +> Note: Esta sección está en desarrollo. > -> todo: this tutorial may be merged with the above part of test-fixtures.md +> todo: este tutorial podría ser unificado con la parte de arriba en test-fixtures.md -Fixtures are important part of testing. Their main purpose is to populate you with data that needed by testing -different cases. With this data using your tests becoming more efficient and useful. +Los fixtures son una parte importante del testing. Su principal propósito es el de poblarte con datos necesarios para el test +de diferentes casos. Con estos datos. utilizar tests se vuelve más eficiente y útil. -Yii supports fixtures via the `yii fixture` command line tool. This tool supports: +Yii soporta fixtures a través de la herramienta de línea de comandos `yii fixture`. Esta herramienta soporta: -* Loading fixtures to different storage such as: RDBMS, NoSQL, etc; -* Unloading fixtures in different ways (usually it is clearing storage); -* Auto-generating fixtures and populating it with random data. +* Cargar fixtures a diferentes almacenamientos: RDBMS, NoSQL, etc; +* Descargar fixtures de diferentes maneras (usualmente limpiando el almacenamiento); +* Auto-generar fixtures y poblarlos con datos al azar. -Fixtures format ---------------- +Formato de Fixtures +------------------- -Fixtures are objects with different methods and configurations, refer to official [documentation](https://github.com/yiisoft/yii2/blob/master/docs/guide/test-fixtures.md) on them. -Lets assume we have fixtures data to load: +Los fixtures son objetos con diferentes métodos y configuraciones, inspecciónalos en la [documentación oficial](https://github.com/yiisoft/yii2/blob/master/docs/guide-es/test-fixtures.md). +Asumamos que tenemos datos de fixtures a cargar: ``` -#users.php file under fixtures data path, by default @tests\unit\fixtures\data +#archivo users.php bajo la ruta de los fixtures, por defecto @tests\unit\fixtures\data return [ [ @@ -292,81 +282,81 @@ return [ ], ]; ``` -If we are using fixture that loads data into database then these rows will be applied to `users` table. If we are using nosql fixtures, for example `mongodb` -fixture, then this data will be applied to `users` mongodb collection. In order to learn about implementing various loading strategies and more, refer to official [documentation](https://github.com/yiisoft/yii2/blob/master/docs/guide/test-fixtures.md). -Above fixture example was auto-generated by `yii2-faker` extension, read more about it in these [section](#auto-generating-fixtures). -Fixture classes name should not be plural. +Si estamos utilizando un fixture que carga datos en la base de datos, entonces esos registros serán insertados en la tabla `users`. Si estamos utilizando fixtures no sql, por ejemplo de `mongodb`, +entonces estos datos serán aplicados a la colección mongodb `users`. Para aprender cómo implementar varias estrategias de carga y más, visita la [documentación oficial](https://github.com/yiisoft/yii2/blob/master/docs/guide-es/test-fixtures.md). +El fixture de ejemplo de arriba fue autogenerado por la extensión `yii2-faker`, lee más acerca de esto en su [sección](#auto-generating-fixtures). +Los nombres de clase de fixtures no deberían ser en plural. -Loading fixtures +Cargar fixtures ---------------- -Fixture classes should be suffixed by `Fixture` class. By default fixtures will be searched under `tests\unit\fixtures` namespace, you can -change this behavior with config or command options. You can exclude some fixtures due load or unload by specifying `-` before its name like `-User`. +Las clases de fixture deberían tener el prefijo `Fixture`. Por defecto los fixtures serán buscados bajo el espacio de nombre `tests\unit\fixtures`, puedes +modificar este comportamiento con opciones de comando o configuración. Puedes excluir algunos fixtures para carga o descarga especificando `-` antes de su nombre, por ejemplo `-User`. -To load fixture, run the following command: +Para cargar un fixture, ejecuta el siguiente comando: ``` yii fixture/load ``` -The required `fixture_name` parameter specifies a fixture name which data will be loaded. You can load several fixtures at once. -Below are correct formats of this command: +El parámetro requerido `fixture_name` especifica un nombre de fixture cuyos datos serán cargados. Puedes cargar varios fixtures de una sola vez. +Abajo se muestran formatos correctos de este comando: ``` -// load `User` fixture +// carga el fixture `User` yii fixture/load User -// same as above, because default action of "fixture" command is "load" +// lo mismo que arriba, dado que la acción por defecto del comando "fixture" es "load" yii fixture User -// load several fixtures +// carga varios fixtures yii fixture User UserProfile -// load all fixtures +// carga todos los fixtures yii fixture/load "*" -// same as above +// lo mismo que arriba yii fixture "*" -// load all fixtures except ones +// carga todos los fixtures excepto uno yii fixture "*" -DoNotLoadThisOne -// load fixtures, but search them in different namespace. By default namespace is: tests\unit\fixtures. +// carga fixtures, pero los busca en diferente espacio de nombre. El espacio de nombre por defecto es: tests\unit\fixtures. yii fixture User --namespace='alias\my\custom\namespace' -// load global fixture `some\name\space\CustomFixture` before other fixtures will be loaded. -// By default this option is set to `InitDbFixture` to disable/enable integrity checks. You can specify several -// global fixtures separated by comma. +// carga el fixture global `some\name\space\CustomFixture` antes de que otros fixtures sean cargados. +// Por defecto está opción se define como `InitDbFixture` para habilitar/deshabilitar la comprobación de integridad. Puedes especificar varios +// fixtures globales separados por coma. yii fixture User --globalFixtures='some\name\space\Custom' ``` -Unloading fixtures +Descargar fixtures ------------------ -To unload fixture, run the following command: +Para descargar un fixture, ejecuta el siguiente comando: ``` -// unload Users fixture, by default it will clear fixture storage (for example "users" table, or "users" collection if this is mongodb fixture). +// descarga el fixture Users, por defecto limpiará el almacenamiento del fixture (por ejemplo la tabla "users", o la colección "users" si es un fixture mongodb). yii fixture/unload User -// Unload several fixtures +// descarga varios fixtures yii fixture/unload User,UserProfile -// unload all fixtures +// descarga todos los fixtures yii fixture/unload "*" -// unload all fixtures except ones +// descarga todos los fixtures excepto uno yii fixture/unload "*" -DoNotUnloadThisOne ``` -Same command options like: `namespace`, `globalFixtures` also can be applied to this command. +Opciones de comando similares como: `namespace`, `globalFixtures` también pueden ser aplicadas a este comando. -Configure Command Globally --------------------------- -While command line options allow us to configure the migration command -on-the-fly, sometimes we may want to configure the command once for all. For example you can configure -different migration path as follows: +Configurar el Comando Globalmente +--------------------------------- +Mientras que las opciones de línea de comandos nos permiten configurar el comando de migración +en el momento, a veces queremos configurar el comando de una vez y para siempre. Por ejemplo puedes configurar +diferentes rutas de migración como a continuación: ``` 'controllerMap' => [ @@ -381,9 +371,9 @@ different migration path as follows: ] ``` -Auto-generating fixtures ------------------------- +Autogenerando fixtures +---------------------- -Yii also can auto-generate fixtures for you based on some template. You can generate your fixtures with different data on different languages and formats. -These feature is done by [Faker](https://github.com/fzaninotto/Faker) library and `yii2-faker` extension. -See extension [guide](https://github.com/yiisoft/yii2-faker) for more docs. +Yii puede también autogenerar fixtures por tí basándose en algún template. Puedes generar tus fixtures con distintos datos en diferentes lenguajes y formatos. +Esta característica es realizada por la librería [Faker](https://github.com/fzaninotto/Faker) y la extensión `yii2-faker`. +Visita la [guía de la extensión](https://github.com/yiisoft/yii2-faker) para mayor documentación. From bb35fda6e91d3fa72d7ef261667e683b3ad162e6 Mon Sep 17 00:00:00 2001 From: mshutov Date: Sat, 21 May 2016 15:32:34 +0300 Subject: [PATCH 59/90] Update Migration.php (#11605) Typo in doc for safeDown --- framework/db/Migration.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/db/Migration.php b/framework/db/Migration.php index 58bf2ca..5d17489 100644 --- a/framework/db/Migration.php +++ b/framework/db/Migration.php @@ -152,7 +152,7 @@ class Migration extends Component implements MigrationInterface * This method contains the logic to be executed when removing this migration. * This method differs from [[down()]] in that the DB logic implemented here will * be enclosed within a DB transaction. - * Child classes may implement this method instead of [[up()]] if the DB logic + * Child classes may implement this method instead of [[down()]] if the DB logic * needs to be within a transaction. * @return boolean return a false value to indicate the migration fails * and should not proceed further. All other return values mean the migration succeeds. From bde00be202510559ce3bcca25d4a6155cafa02b4 Mon Sep 17 00:00:00 2001 From: andrey-mokhov Date: Mon, 16 May 2016 16:30:20 +0600 Subject: [PATCH 60/90] ColumnSchemaBuilder can not work with custom types In our project we use trait with custom types, example: ```php trait MigrationToolTrait { protected function dateTimeWithTZ ($precision = null) { if ('pgsql' === $this->db->driverName) { return $this->getDb()->getSchema()->createColumnSchemaBuilder(sprintf('timestamp(%d) with time zone', $precision)); } return $this->dateTime($precision); } } ``` usage: ```php class m160516_161900_init { use MigrationToolTrait; public function safeUp () { $this->createTable('testing', [ 'id' => $this->primaryKey(), 'startDate' => $this->dateTimeWithTZ()->notNull()->defaultExpression('now()'), ]); } public function safeDown () { $this->dropTable('testing'); } } ``` In version 2.0.8 MigrateController generate notice: PHP Notice 'yii\base\ErrorException' with message 'Undefined index: timestamp(0) with time zone' in vendor/yiisoft/yii2/db/ColumnSchemaBuilder.php:366 We offer our change to fix it. --- framework/db/ColumnSchemaBuilder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/db/ColumnSchemaBuilder.php b/framework/db/ColumnSchemaBuilder.php index 6404552..d425253 100644 --- a/framework/db/ColumnSchemaBuilder.php +++ b/framework/db/ColumnSchemaBuilder.php @@ -390,7 +390,7 @@ class ColumnSchemaBuilder extends Object */ protected function getTypeCategory() { - return $this->categoryMap[$this->type]; + return isset($this->categoryMap[$this->type]) ? $this->categoryMap[$this->type] : null; } /** From 77b29bf927f78b2ffa6e1f310c2bfea59a22931c Mon Sep 17 00:00:00 2001 From: SilverFire - Dmitry Naumenko Date: Sat, 21 May 2016 09:40:30 +0300 Subject: [PATCH 61/90] Added tests to ColumnSchemaBuilderTest in order to verify custom column types work OK --- tests/framework/db/ColumnSchemaBuilderTest.php | 14 +++++++++++--- tests/framework/db/cubrid/ColumnSchemaBuilderTest.php | 4 ++-- tests/framework/db/mysql/ColumnSchemaBuilderTest.php | 4 ++-- tests/framework/db/oci/ColumnSchemaBuilderTest.php | 4 ++-- tests/framework/db/sqlite/ColumnSchemaBuilderTest.php | 4 ++-- 5 files changed, 19 insertions(+), 11 deletions(-) diff --git a/tests/framework/db/ColumnSchemaBuilderTest.php b/tests/framework/db/ColumnSchemaBuilderTest.php index ee2f2a6..5358cec 100644 --- a/tests/framework/db/ColumnSchemaBuilderTest.php +++ b/tests/framework/db/ColumnSchemaBuilderTest.php @@ -9,6 +9,8 @@ namespace yiiunit\framework\db; use yii\db\ColumnSchemaBuilder; +use yii\db\Exception; +use yii\db\Expression; use yii\db\Schema; use yiiunit\TestCase; @@ -30,7 +32,7 @@ class ColumnSchemaBuilderTest extends TestCase /** * @return array */ - public function unsignedProvider() + public function typesProvider() { return [ ['integer', Schema::TYPE_INTEGER, null, [ @@ -39,13 +41,19 @@ class ColumnSchemaBuilderTest extends TestCase ['integer(10)', Schema::TYPE_INTEGER, 10, [ ['unsigned'], ]], + ['timestamp() WITH TIME ZONE NOT NULL', 'timestamp() WITH TIME ZONE', null, [ + ['notNull'] + ]], + ['timestamp() WITH TIME ZONE DEFAULT NOW()', 'timestamp() WITH TIME ZONE', null, [ + ['defaultValue', new Expression('NOW()')] + ]], ]; } /** - * @dataProvider unsignedProvider + * @dataProvider typesProvider */ - public function testUnsigned($expected, $type, $length, $calls) + public function testCustomTypes($expected, $type, $length, $calls) { $this->checkBuildString($expected, $type, $length, $calls); } diff --git a/tests/framework/db/cubrid/ColumnSchemaBuilderTest.php b/tests/framework/db/cubrid/ColumnSchemaBuilderTest.php index 392198e..c9f0cf8 100644 --- a/tests/framework/db/cubrid/ColumnSchemaBuilderTest.php +++ b/tests/framework/db/cubrid/ColumnSchemaBuilderTest.php @@ -23,7 +23,7 @@ class ColumnSchemaBuilderTest extends BaseColumnSchemaBuilderTest /** * @return array */ - public function unsignedProvider() + public function typesProvider() { return [ ['integer UNSIGNED', Schema::TYPE_INTEGER, null, [ @@ -34,4 +34,4 @@ class ColumnSchemaBuilderTest extends BaseColumnSchemaBuilderTest ]], ]; } -} \ No newline at end of file +} diff --git a/tests/framework/db/mysql/ColumnSchemaBuilderTest.php b/tests/framework/db/mysql/ColumnSchemaBuilderTest.php index 02077fe..7723bec 100644 --- a/tests/framework/db/mysql/ColumnSchemaBuilderTest.php +++ b/tests/framework/db/mysql/ColumnSchemaBuilderTest.php @@ -23,7 +23,7 @@ class ColumnSchemaBuilderTest extends BaseColumnSchemaBuilderTest /** * @return array */ - public function unsignedProvider() + public function typesProvider() { return [ ['integer UNSIGNED', Schema::TYPE_INTEGER, null, [ @@ -34,4 +34,4 @@ class ColumnSchemaBuilderTest extends BaseColumnSchemaBuilderTest ]], ]; } -} \ No newline at end of file +} diff --git a/tests/framework/db/oci/ColumnSchemaBuilderTest.php b/tests/framework/db/oci/ColumnSchemaBuilderTest.php index 3994dd9..bcfba01 100644 --- a/tests/framework/db/oci/ColumnSchemaBuilderTest.php +++ b/tests/framework/db/oci/ColumnSchemaBuilderTest.php @@ -23,7 +23,7 @@ class ColumnSchemaBuilderTest extends BaseColumnSchemaBuilderTest /** * @return array */ - public function unsignedProvider() + public function typesProvider() { return [ ['integer UNSIGNED', Schema::TYPE_INTEGER, null, [ @@ -34,4 +34,4 @@ class ColumnSchemaBuilderTest extends BaseColumnSchemaBuilderTest ]], ]; } -} \ No newline at end of file +} diff --git a/tests/framework/db/sqlite/ColumnSchemaBuilderTest.php b/tests/framework/db/sqlite/ColumnSchemaBuilderTest.php index ef7aef7..a9cf6d4 100644 --- a/tests/framework/db/sqlite/ColumnSchemaBuilderTest.php +++ b/tests/framework/db/sqlite/ColumnSchemaBuilderTest.php @@ -23,7 +23,7 @@ class ColumnSchemaBuilderTest extends BaseColumnSchemaBuilderTest /** * @return array */ - public function unsignedProvider() + public function typesProvider() { return [ ['integer UNSIGNED', Schema::TYPE_INTEGER, null, [ @@ -34,4 +34,4 @@ class ColumnSchemaBuilderTest extends BaseColumnSchemaBuilderTest ]], ]; } -} \ No newline at end of file +} From 36be16c86524d68cae3fd7dafbda9e6a07476d53 Mon Sep 17 00:00:00 2001 From: SilverFire - Dmitry Naumenko Date: Sun, 22 May 2016 12:03:29 +0300 Subject: [PATCH 62/90] Updated CHANGELOG --- framework/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 7c83caf..e7dd1df 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -19,6 +19,7 @@ Yii Framework 2 Change Log - Bug #11507: Fixed `yii\validators\EachValidator::validateAttribute()` does not respect `skipOnEmpty` rule parameter (webdevsega) - Bug #11523: Fixed `yii\web\User::checkRedirectAcceptable()` to treat acceptable content type `*/*` as `*` (silverfire) - Bug #11532: Fixed casting of empty char value to `null` resulting in integrity constraint violation for not null columns (samdark) +- Bug #11571: Fixed `yii\db\ColumnSchemaBuilder` to work with custom column types (andrey-mokhov, silverfire) 2.0.8 April 28, 2016 From 7249a6c99e96cafae522da379fc7c609ce910bc3 Mon Sep 17 00:00:00 2001 From: maine-mike Date: Thu, 12 May 2016 18:00:58 -0400 Subject: [PATCH 63/90] Move Identity Cookie code into separate functions --- framework/CHANGELOG.md | 5 +++ framework/web/User.php | 72 ++++++++++++++++++++++++++-------------- tests/framework/web/UserTest.php | 6 ++++ 3 files changed, 59 insertions(+), 24 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index e7dd1df..a66ed0c 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -6,6 +6,11 @@ Yii Framework 2 Change Log ----------------------- - Enh #11195: Added ability to append custom string to schema builder column definition (df2, samdark) +- Enh #8795: Move Identity Cookie code into separate functions and cleanup invalid identity cookies +- Enh #8795: Move Identity Cookie code into separate functions and cleanup invalid identity cookies (maine-mike) +- Enh #8795 Move Identity Cookie code into separate functions and cleanup invalid identity cookies (maine-mike) +-•Enh #11195: Added ability to append custom string to schema builder column definition (df2, samdark) +- Enh #11195: Added ability to append custom string to schema builder column definition (df2, samdark) - Enh #11490: Added `yii\data\ArrayDataProvider::$modelClass` property to specify a model used to provide column labels even when data array is empty (PowerGamer1) - Enh #11591: Added support for wildcards for `only` and `except` at `yii\base\ActionFilter` (klimov-paul) - Bug #9950: Updated `yii\grid\DataColumn::getHeaderCellLabel()` to extract attribute label from the `filterModel` of Grid (silverfire) diff --git a/framework/web/User.php b/framework/web/User.php index 61bce7f..1a53cf5 100644 --- a/framework/web/User.php +++ b/framework/web/User.php @@ -284,35 +284,17 @@ class User extends Component */ protected function loginByCookie() { - $value = Yii::$app->getRequest()->getCookies()->getValue($this->identityCookie['name']); - if ($value === null) { - return; - } - - $data = json_decode($value, true); - if (count($data) !== 3 || !isset($data[0], $data[1], $data[2])) { - return; - } - - list ($id, $authKey, $duration) = $data; - /* @var $class IdentityInterface */ - $class = $this->identityClass; - $identity = $class::findIdentity($id); - if ($identity === null) { - return; - } elseif (!$identity instanceof IdentityInterface) { - throw new InvalidValueException("$class::findIdentity() must return an object implementing IdentityInterface."); - } - - if ($identity->validateAuthKey($authKey)) { + $data = $this->getIdentityAndDurationFromCookie(); + if (isset($data['identity'], $data['duration'])) { + $identity = $data['identity']; + $duration = $data['duration']; if ($this->beforeLogin($identity, true, $duration)) { $this->switchIdentity($identity, $this->autoRenewCookie ? $duration : 0); + $id = $identity->getId(); $ip = Yii::$app->getRequest()->getUserIP(); Yii::info("User '$id' logged in from $ip via cookie.", __METHOD__); $this->afterLogin($identity, true, $duration); } - } else { - Yii::warning("Invalid auth key attempted for user '$id': $authKey", __METHOD__); } } @@ -562,6 +544,48 @@ class User extends Component } /** + * Determines if an identity cookie has a valid format and contains a valid auth key. + * This method is used when [[enableAutoLogin]] is true. + * This method attempts to authenticate a user using the information in the identity cookie. + * @return array|null Returns an array of 'identity' and 'duration' if valid, otherwise null. + * @see loginByCookie() + */ + protected function getIdentityAndDurationFromCookie() + { + $value = Yii::$app->getRequest()->getCookies()->getValue($this->identityCookie['name']); + if ($value === null) { + return; + } + $data = json_decode($value, true); + if (count($data) == 3) { + list ($id, $authKey, $duration) = $data; + /* @var $class IdentityInterface */ + $class = $this->identityClass; + $identity = $class::findIdentity($id); + if ($identity !== null) { + if (!$identity instanceof IdentityInterface) { + throw new InvalidValueException("$class::findIdentity() must return an object implementing IdentityInterface."); + } elseif (!$identity->validateAuthKey($authKey)) { + Yii::warning("Invalid auth key attempted for user '$id': $authKey", __METHOD__); + } else { + return ['identity' => $identity, 'duration' => $duration]; + } + } + } + $this->removeIdentityCookie(); + return; + } + + /** + * Removes the identity cookie. + * This method is used when [[enableAutoLogin]] is true. + */ + protected function removeIdentityCookie() + { + Yii::$app->getResponse()->getCookies()->remove(new Cookie($this->identityCookie)); + } + + /** * Switches to a new identity for the current user. * * When [[enableSession]] is true, this method may use session and/or cookie to store the user identity information, @@ -585,7 +609,7 @@ class User extends Component /* Ensure any existing identity cookies are removed. */ if ($this->enableAutoLogin) { - Yii::$app->getResponse()->getCookies()->remove(new Cookie($this->identityCookie)); + $this->removeIdentityCookie(); } $session = Yii::$app->getSession(); diff --git a/tests/framework/web/UserTest.php b/tests/framework/web/UserTest.php index 5789d42..d6c1b34 100644 --- a/tests/framework/web/UserTest.php +++ b/tests/framework/web/UserTest.php @@ -122,6 +122,12 @@ class UserTest extends TestCase $this->mockWebApplication($appConfig); Yii::$app->session->removeAll(); + $cookie = new Cookie(Yii::$app->user->identityCookie); + $cookie->value = 'junk'; + $cookiesMock->add($cookie); + Yii::$app->user->getIdentity(); + $this->assertTrue(strlen($cookiesMock->getValue(Yii::$app->user->identityCookie['name'])) == 0); + Yii::$app->user->login(UserIdentity::findIdentity('user1'),3600); $this->assertFalse(Yii::$app->user->isGuest); $this->assertSame(Yii::$app->user->id, 'user1'); From 38be74446e943f238f13be02ac93f1880451548c Mon Sep 17 00:00:00 2001 From: SilverFire - Dmitry Naumenko Date: Sun, 22 May 2016 12:54:20 +0300 Subject: [PATCH 64/90] Update PHPDoc, CHANGELOG, UPGRADE.md --- framework/CHANGELOG.md | 3 ++- framework/UPGRADE.md | 6 ++++++ framework/web/User.php | 6 ++++-- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index a66ed0c..d69f2e4 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -5,11 +5,12 @@ Yii Framework 2 Change Log 2.0.9 under development ----------------------- +- Enh #8795: Refactored `yii\web\User::loginByCookie()` in order to make it easier to override (maine-mike, silverfire) - Enh #11195: Added ability to append custom string to schema builder column definition (df2, samdark) - Enh #8795: Move Identity Cookie code into separate functions and cleanup invalid identity cookies - Enh #8795: Move Identity Cookie code into separate functions and cleanup invalid identity cookies (maine-mike) - Enh #8795 Move Identity Cookie code into separate functions and cleanup invalid identity cookies (maine-mike) --•Enh #11195: Added ability to append custom string to schema builder column definition (df2, samdark) +- Enh #11195: Added ability to append custom string to schema builder column definition (df2, samdark) - Enh #11195: Added ability to append custom string to schema builder column definition (df2, samdark) - Enh #11490: Added `yii\data\ArrayDataProvider::$modelClass` property to specify a model used to provide column labels even when data array is empty (PowerGamer1) - Enh #11591: Added support for wildcards for `only` and `except` at `yii\base\ActionFilter` (klimov-paul) diff --git a/framework/UPGRADE.md b/framework/UPGRADE.md index 188341f..d28ae89 100644 --- a/framework/UPGRADE.md +++ b/framework/UPGRADE.md @@ -15,6 +15,12 @@ php composer.phar self-update php composer.phar global require "fxp/composer-asset-plugin:~1.1.1" ``` +Upgrade from Yii 2.0.8 +---------------------- + +* Part of code from `yii\web\User::loginByCookie()` method was moved to new `getIdentityAndDurationFromCookie()` + and `removeIdentityCookie()` methods. If you override `loginByCookie()` method, update it in order use new methods. + Upgrade from Yii 2.0.7 ---------------------- diff --git a/framework/web/User.php b/framework/web/User.php index 1a53cf5..36687e6 100644 --- a/framework/web/User.php +++ b/framework/web/User.php @@ -549,12 +549,13 @@ class User extends Component * This method attempts to authenticate a user using the information in the identity cookie. * @return array|null Returns an array of 'identity' and 'duration' if valid, otherwise null. * @see loginByCookie() + * @since 2.0.9 */ protected function getIdentityAndDurationFromCookie() { $value = Yii::$app->getRequest()->getCookies()->getValue($this->identityCookie['name']); if ($value === null) { - return; + return null; } $data = json_decode($value, true); if (count($data) == 3) { @@ -573,12 +574,13 @@ class User extends Component } } $this->removeIdentityCookie(); - return; + return null; } /** * Removes the identity cookie. * This method is used when [[enableAutoLogin]] is true. + * @since 2.0.9 */ protected function removeIdentityCookie() { From e6c01ab081fd98dd0335cb458457613d7fb07451 Mon Sep 17 00:00:00 2001 From: SilverFire - Dmitry Naumenko Date: Sun, 22 May 2016 13:01:53 +0300 Subject: [PATCH 65/90] Updated CHANGELOG --- framework/CHANGELOG.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 3e232ef..b7d2b9b 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -7,16 +7,11 @@ Yii Framework 2 Change Log - Enh #8795: Refactored `yii\web\User::loginByCookie()` in order to make it easier to override (maine-mike, silverfire) - Enh #11195: Added ability to append custom string to schema builder column definition (df2, samdark) -- Enh #8795: Move Identity Cookie code into separate functions and cleanup invalid identity cookies -- Enh #8795: Move Identity Cookie code into separate functions and cleanup invalid identity cookies (maine-mike) -- Enh #8795 Move Identity Cookie code into separate functions and cleanup invalid identity cookies (maine-mike) -- Enh #11195: Added ability to append custom string to schema builder column definition (df2, samdark) -- Enh #11195: Added ability to append custom string to schema builder column definition (df2, samdark) - Enh #11490: Added `yii\data\ArrayDataProvider::$modelClass` property to specify a model used to provide column labels even when data array is empty (PowerGamer1) - Enh #11591: Added support for wildcards for `only` and `except` at `yii\base\ActionFilter` (klimov-paul) - Bug #9950: Updated `yii\grid\DataColumn::getHeaderCellLabel()` to extract attribute label from the `filterModel` of Grid (silverfire) - Bug #11429: Fixed `yii\i18n\PhpMessageSource::loadFallbackMessages()` not to log error when source and language is same, but locales are different (silverfire) -- Enh #11484: moved "P" "isPrimaryKey" in `yii\db\oci\Schema` to findConstraints (SSiwek) +- Enh #11484: Speed up `yii\db\oci\Schema::loadTableSchema()` for Oracle DBMS (SSiwek) - Enh #11428: Speedup SQL query in `yii\db\oci\Schema::findColumns()` (SSiwek) - Enh #11414: Files specified as `null` in `yii\web\AssetBundle` won't be registered (Razzwan) - Enh #11432: Added HTTP status 421 "Misdirected Request" to list of statuses in `yii\web\Response` (dasmfm) From a23cbda185d52feb1648fa9af4695cf70cf3eb92 Mon Sep 17 00:00:00 2001 From: SilverFire - Dmitry Naumenko Date: Sun, 22 May 2016 13:13:24 +0300 Subject: [PATCH 66/90] Code style fixed --- framework/db/oci/Schema.php | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/framework/db/oci/Schema.php b/framework/db/oci/Schema.php index 3481122..c0d3227 100644 --- a/framework/db/oci/Schema.php +++ b/framework/db/oci/Schema.php @@ -8,10 +8,10 @@ namespace yii\db\oci; use yii\base\InvalidCallException; +use yii\db\ColumnSchema; use yii\db\Connection; use yii\db\Expression; use yii\db\TableSchema; -use yii\db\ColumnSchema; /** * Schema is the class for retrieving metadata from an Oracle database @@ -271,21 +271,21 @@ SQL; if ($this->db->slavePdo->getAttribute(\PDO::ATTR_CASE) === \PDO::CASE_LOWER) { $row = array_change_key_case($row, CASE_UPPER); } - + if ($row['CONSTRAINT_TYPE'] === 'P') { - $table->columns[$row['COLUMN_NAME']]->isPrimaryKey = true; - $table->primaryKey[] = $row['COLUMN_NAME']; - if (empty($table->sequenceName )) { - $table->sequenceName = $this->getTableSequenceName($table->name); - } + $table->columns[$row['COLUMN_NAME']]->isPrimaryKey = true; + $table->primaryKey[] = $row['COLUMN_NAME']; + if (empty($table->sequenceName)) { + $table->sequenceName = $this->getTableSequenceName($table->name); + } } if ($row['CONSTRAINT_TYPE'] !== 'R') { - // this condition is not checked in SQL WHERE because of an Oracle Bug: - // see https://github.com/yiisoft/yii2/pull/8844 - continue; - } - + // this condition is not checked in SQL WHERE because of an Oracle Bug: + // see https://github.com/yiisoft/yii2/pull/8844 + continue; + } + $name = $row['CONSTRAINT_NAME']; if (!isset($constraints[$name])) { $constraints[$name] = [ @@ -439,9 +439,9 @@ SQL; */ protected function extractColumnSize($column, $dbType, $precision, $scale, $length) { - $column->size = trim($length) === '' ? null : (int) $length; - $column->precision = trim($precision) === '' ? null : (int) $precision; - $column->scale = trim($scale) === '' ? null : (int) $scale; + $column->size = trim($length) === '' ? null : (int)$length; + $column->precision = trim($precision) === '' ? null : (int)$precision; + $column->scale = trim($scale) === '' ? null : (int)$scale; } /** @@ -457,7 +457,7 @@ SQL; if (!empty($returnColumns)) { $columnSchemas = $tableSchema->columns; $returning = []; - foreach ((array) $returnColumns as $name) { + foreach ((array)$returnColumns as $name) { $phName = QueryBuilder::PARAM_PREFIX . (count($params) + count($returnParams)); $returnParams[$phName] = [ 'column' => $name, @@ -492,4 +492,4 @@ SQL; return $result; } -} \ No newline at end of file +} From 10b5a3a819ee7bee77014d8e876e6c6e6983c28b Mon Sep 17 00:00:00 2001 From: Angel Guevara Date: Sun, 22 May 2016 07:09:37 -0500 Subject: [PATCH 67/90] fix #11609 --- docs/guide/output-data-widgets.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/output-data-widgets.md b/docs/guide/output-data-widgets.md index bfae3c9..1e0b043 100644 --- a/docs/guide/output-data-widgets.md +++ b/docs/guide/output-data-widgets.md @@ -103,7 +103,7 @@ These are then also available as variables in the view. GridView -------- -Data grid or [[yii\widgets\GridView|GridView]] is one of the most powerful Yii widgets. It is extremely useful if you need to quickly build the admin +Data grid or [[yii\grid\GridView|GridView]] is one of the most powerful Yii widgets. It is extremely useful if you need to quickly build the admin section of the system. It takes data from a [data provider](output-data-providers.md) and renders each row using a set of [[yii\grid\GridView::columns|columns]] presenting data in the form of a table. From ae1fbdd737720c614ca3ee7e2d94e0173e40c46d Mon Sep 17 00:00:00 2001 From: SilverFire - Dmitry Naumenko Date: Sun, 22 May 2016 18:40:16 +0300 Subject: [PATCH 68/90] Updated output-data-widgets article. Fixed broken links Closes #11609 --- docs/guide-ja/output-data-widgets.md | 12 ++++++------ docs/guide/output-data-widgets.md | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/guide-ja/output-data-widgets.md b/docs/guide-ja/output-data-widgets.md index 1d26a1a..346707f 100644 --- a/docs/guide-ja/output-data-widgets.md +++ b/docs/guide-ja/output-data-widgets.md @@ -102,7 +102,7 @@ echo ListView::widget([ GridView -------- -データグリッドすなわち [[yii\widgets\GridView|GridView]] は Yii の最も強力なウィジェットの一つです。 +データグリッドすなわち [[yii\grid\GridView|GridView]] は Yii の最も強力なウィジェットの一つです。 これは、システムの管理セクションを素速く作らねばならない時に、この上なく便利なものです。 このウィジェットは [データプロバイダ](output-data-providers.md) からデータを受けて、テーブルの形式で、行ごとに一組の [[yii\grid\GridView::columns|カラム]] を使ってデータを表示します。 @@ -691,11 +691,11 @@ echo GridView::widget([ ### GridView を Pjax とともに使う [[yii\widgets\Pjax|Pjax]] ウィジェットを使うと、ページ全体をリロードせずに、ページの一部分だけを更新することが出来ます。 -これを使うと、フィルタを使うときに、[[yii\widgets\GridView|GridView]] の中身だけを更新することが出来ます。 +これを使うと、フィルタを使うときに、[[yii\grid\GridView|GridView]] の中身だけを更新することが出来ます。 ```php use yii\widgets\Pjax; -use yii\widgets\GridView; +use yii\grid\GridView; Pjax::begin([ // PJax のオプション @@ -707,8 +707,8 @@ Pjax::end(); ``` [[yii\widgets\Pjax|Pjax]] は、[[yii\widgets\Pjax::$linkSelector|Pjax::$linkSelector]] の指定に従って、リンクに対しても動作します。 -これは [[yii\data\ActionColumn|ActionColumn]] を使う場合には問題となり得ます。 -この問題を防止するためには、[[yii\data\ActionColumn::$buttons|ActionColumn::$buttons]] +これは [[yii\grid\ActionColumn|ActionColumn]] を使う場合には問題となり得ます。 +この問題を防止するためには、[[yii\grid\ActionColumn::$buttons|ActionColumn::$buttons]] プロパティを編集して `data-pjax="0"` という HTML 属性を追加します。 #### Gii における Pjax を伴う GridView @@ -721,7 +721,7 @@ yii gii/crud --controllerClass="backend\\controllers\PostController" \ --enablePjax=1 ``` -これによって、[[yii\widgets\GridView|GridView]] または [[yii\widgets\ListView|ListView]] +これによって、[[yii\grid\GridView|GridView]] または [[yii\widgets\ListView|ListView]] を囲む [[yii\widgets\Pjax|Pjax]] ウィジェットが生成されます。 diff --git a/docs/guide/output-data-widgets.md b/docs/guide/output-data-widgets.md index 1e0b043..74a09e4 100644 --- a/docs/guide/output-data-widgets.md +++ b/docs/guide/output-data-widgets.md @@ -701,11 +701,11 @@ echo GridView::widget([ The [[yii\widgets\Pjax|Pjax]] widget allows you to update a certain section of a page instead of reloading the entire page. You can use it to to update only the -[[yii\widgets\GridView|GridView]] content when using filters. +[[yii\grid\GridView|GridView]] content when using filters. ```php use yii\widgets\Pjax; -use yii\widgets\GridView; +use yii\grid\GridView; Pjax::begin([ // PJax options @@ -718,9 +718,9 @@ Pjax::end(); Pjax also works for the links inside the [[yii\widgets\Pjax|Pjax]] widget and for the links specified by [[yii\widgets\Pjax::$linkSelector|Pjax::$linkSelector]]. -But this might be a problem for the links of an [[yii\data\ActionColumn|ActionColumn]]. +But this might be a problem for the links of an [[yii\grid\ActionColumn|ActionColumn]]. To prevent this, add the HTML attribute `data-pjax="0"` to the links when you edit -the [[yii\data\ActionColumn::$buttons|ActionColumn::$buttons]] property. +the [[yii\grid\ActionColumn::$buttons|ActionColumn::$buttons]] property. #### GridView/ListView with Pjax in Gii @@ -734,7 +734,7 @@ yii gii/crud --controllerClass="backend\\controllers\PostController" \ ``` Which generates a [[yii\widgets\Pjax|Pjax]] widget wrapping the -[[yii\widgets\GridView|GridView]] or [[yii\widgets\ListView|ListView]] widgets. +[[yii\grid\GridView|GridView]] or [[yii\widgets\ListView|ListView]] widgets. Further reading --------------- From bd2e78c1db029fe28ed0b6abb270fbc78aedf81a Mon Sep 17 00:00:00 2001 From: Luciano Baraglia Date: Mon, 23 May 2016 17:34:12 -0300 Subject: [PATCH 69/90] Some spanish docs [skip ci] (#11617) --- docs/guide-es/tutorial-start-from-scratch.md | 55 ++++++++++++++++++++++++++++ docs/guide-es/tutorial-template-engines.md | 49 +++++++++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 docs/guide-es/tutorial-start-from-scratch.md create mode 100644 docs/guide-es/tutorial-template-engines.md diff --git a/docs/guide-es/tutorial-start-from-scratch.md b/docs/guide-es/tutorial-start-from-scratch.md new file mode 100644 index 0000000..cd810ff --- /dev/null +++ b/docs/guide-es/tutorial-start-from-scratch.md @@ -0,0 +1,55 @@ +Crear tu propia estructura de Aplicación +======================================== + +> Note: Esta sección se encuentra en desarrollo. + +Mientras que los templates de proyectos [basic](https://github.com/yiisoft/yii2-app-basic) y [advanced](https://github.com/yiisoft/yii2-app-advanced) +son grandiosos para la mayoría de tus necesidades, podrías querer crear tu propio template de proyecto del cual +partir todos tus proyectos. + +Los templates de proyectos en Yii son simplemente repositorios conteniendo un archivo `composer.json`, y registrado como un paquete de Composer. +Cualquier repositorio puede ser identificado como paquete Composer, haciéndolo instalable a través del comando de Composer `create-project`. + +Dado que es un poco demasiado comenzar tu template de proyecto desde cero, es mejor utilizar uno de los +templates incorporados como una base. Utilicemos el template básico aquí. + +Clonar el Template Básico +------------------------- + +El primer paso es clonar el template básico de Yii desde su repositorio Git: + +```bash +git clone git@github.com:yiisoft/yii2-app-basic.git +``` + +Entonces espera que el repositorio sea descargado a tu computadora. Dado que los cambios realizados al template no serán enviados al repositorio, puedes eliminar el directorio `.git` +y todo su contenido de la descarga. + +Modificar los Archivos +---------------------- + +A continuación, querrás modificar el archivo `composer.json` para que refleje tu template. Cambia los valores de `name`, `description`, `keywords`, `homepage`, `license`, y `support` +de forma que describa tu nuevo template. También ajusta las opciones `require`, `require-dev`, `suggest`, y demás para que encajen con los requerimientos de tu template. + +> Note: En el archivo `composer.json`, utiliza el parámetro `writable` (bajo `extra`) para especificar +> permisos-por-archivo a ser definidos después de que la aplicación es creada a partir del template. + +Luego, pasa a modificar la estructura y contenido de la aplicación como te gustaría que sea por defecto. Finalmente, actualiza el archivo README para que sea aplicable a tu template. + +Hacer un Paquete +---------------- + +Con el template definido, crea un repositorio Git a partir de él, y sube tus archivos ahí. Si tu template va a ser de código abierto, [Github](http://github.com) es el mejor lugar para alojarlo. Si tu intención es que el template no sea colaborativo, cualquier sitio de repositorios Git servirá. + +Ahora, necesitas registrar tu paquete para Composer. Para templates públicos, el paquete debe ser registrado en [Packagist](https://packagist.org/). +Para templates privados, es un poco más complicado registrarlo. Puedes ver instrucciones para hacerlo en la [documentación de Composer](https://getcomposer.org/doc/05-repositories.md#hosting-your-own). + +Utilizar el Template +-------------------- + +Eso es todo lo que se necesita para crear un nuevo template de proyecto Yii. Ahora puedes crear tus propios proyectos a partir de este template: + +``` +composer global require "fxp/composer-asset-plugin:~1.1.1" +composer create-project --prefer-dist --stability=dev mysoft/yii2-app-coolone new-project +``` diff --git a/docs/guide-es/tutorial-template-engines.md b/docs/guide-es/tutorial-template-engines.md new file mode 100644 index 0000000..addcf8f --- /dev/null +++ b/docs/guide-es/tutorial-template-engines.md @@ -0,0 +1,49 @@ +Usar motores de plantillas +========================== + +Por defecto, Yii utiliza PHP como su lenguaje de plantilla, pero puedes configurar Yii para que soporte otros motores de renderizado, tal como +[Twig](http://twig.sensiolabs.org/) o [Smarty](http://www.smarty.net/), disponibles como extensiones. + +El componente `view` es el responsable de renderizar las vistas. Puedes agregar un motor de plantillas personalizado reconfigurando +el comportamiento (behavior) de este componente: + +```php +[ + 'components' => [ + 'view' => [ + 'class' => 'yii\web\View', + 'renderers' => [ + 'tpl' => [ + 'class' => 'yii\smarty\ViewRenderer', + //'cachePath' => '@runtime/Smarty/cache', + ], + 'twig' => [ + 'class' => 'yii\twig\ViewRenderer', + 'cachePath' => '@runtime/Twig/cache', + // Array de opciones de Twig: + 'options' => [ + 'auto_reload' => true, + ], + 'globals' => ['html' => '\yii\helpers\Html'], + 'uses' => ['yii\bootstrap'], + ], + // ... + ], + ], + ], +] +``` + +En el código de arriba, tanto Smarty como Twig son configurados para ser utilizables por los archivos de vista. Pero para tener ambas extensiones en tu proyecto, también necesitas modificar +tu archivo `composer.json` para incluirlos: + +``` +"yiisoft/yii2-smarty": "*", +"yiisoft/yii2-twig": "*", +``` +Ese código será agregado a la sección `require` de `composer.json`. Después de realizar ese cambio y guardar el archivo, puedes instalar estas extensiones ejecutando `composer update --prefer-dist` en la línea de comandos. + +Para más detalles acerca del uso concreto de cada motor de plantillas, visita su documentación: + +- [Guía de Twig](https://github.com/yiisoft/yii2-twig/tree/master/docs/guide) +- [Guía de Smarty](https://github.com/yiisoft/yii2-smarty/tree/master/docs/guide) From fdbf7d85a1959b60ebae8002151f5eaccae3180d Mon Sep 17 00:00:00 2001 From: Angel Guevara Date: Tue, 24 May 2016 01:35:23 -0500 Subject: [PATCH 70/90] Simple tests (#11606) * method to simplify migrate/create tests * use new assertion for all migration/create tests * move the expected files to test/data/console --- .../data/console/migrate_create/add_columns_fk.php | 128 +++ .../console/migrate_create/add_columns_prefix.php | 128 +++ .../console/migrate_create/add_columns_test.php | 36 + .../data/console/migrate_create/create_fields.php | 35 + .../console/migrate_create/create_foreign_key.php | 128 +++ tests/data/console/migrate_create/create_id_pk.php | 35 + .../data/console/migrate_create/create_prefix.php | 128 +++ tests/data/console/migrate_create/create_test.php | 32 + .../console/migrate_create/create_title_pk.php | 34 + tests/data/console/migrate_create/default.php | 34 + .../console/migrate_create/drop_columns_test.php | 36 + tests/data/console/migrate_create/drop_fields.php | 34 + tests/data/console/migrate_create/drop_test.php | 32 + .../data/console/migrate_create/junction_test.php | 97 ++ .../controllers/MigrateControllerTestTrait.php | 1019 +------------------- 15 files changed, 962 insertions(+), 974 deletions(-) create mode 100644 tests/data/console/migrate_create/add_columns_fk.php create mode 100644 tests/data/console/migrate_create/add_columns_prefix.php create mode 100644 tests/data/console/migrate_create/add_columns_test.php create mode 100644 tests/data/console/migrate_create/create_fields.php create mode 100644 tests/data/console/migrate_create/create_foreign_key.php create mode 100644 tests/data/console/migrate_create/create_id_pk.php create mode 100644 tests/data/console/migrate_create/create_prefix.php create mode 100644 tests/data/console/migrate_create/create_test.php create mode 100644 tests/data/console/migrate_create/create_title_pk.php create mode 100644 tests/data/console/migrate_create/default.php create mode 100644 tests/data/console/migrate_create/drop_columns_test.php create mode 100644 tests/data/console/migrate_create/drop_fields.php create mode 100644 tests/data/console/migrate_create/drop_test.php create mode 100644 tests/data/console/migrate_create/junction_test.php diff --git a/tests/data/console/migrate_create/add_columns_fk.php b/tests/data/console/migrate_create/add_columns_fk.php new file mode 100644 index 0000000..7da3034 --- /dev/null +++ b/tests/data/console/migrate_create/add_columns_fk.php @@ -0,0 +1,128 @@ +addColumn('test', 'user_id', \$this->integer()); + \$this->addColumn('test', 'product_id', \$this->integer()->unsigned()->notNull()); + \$this->addColumn('test', 'order_id', \$this->integer()->notNull()); + \$this->addColumn('test', 'created_at', \$this->dateTime()->notNull()); + + // creates index for column `user_id` + \$this->createIndex( + 'idx-test-user_id', + 'test', + 'user_id' + ); + + // add foreign key for table `user` + \$this->addForeignKey( + 'fk-test-user_id', + 'test', + 'user_id', + 'user', + 'id', + 'CASCADE' + ); + + // creates index for column `product_id` + \$this->createIndex( + 'idx-test-product_id', + 'test', + 'product_id' + ); + + // add foreign key for table `product` + \$this->addForeignKey( + 'fk-test-product_id', + 'test', + 'product_id', + 'product', + 'id', + 'CASCADE' + ); + + // creates index for column `order_id` + \$this->createIndex( + 'idx-test-order_id', + 'test', + 'order_id' + ); + + // add foreign key for table `user_order` + \$this->addForeignKey( + 'fk-test-order_id', + 'test', + 'order_id', + 'user_order', + 'id', + 'CASCADE' + ); + } + + /** + * @inheritdoc + */ + public function down() + { + // drops foreign key for table `user` + \$this->dropForeignKey( + 'fk-test-user_id', + 'test' + ); + + // drops index for column `user_id` + \$this->dropIndex( + 'idx-test-user_id', + 'test' + ); + + // drops foreign key for table `product` + \$this->dropForeignKey( + 'fk-test-product_id', + 'test' + ); + + // drops index for column `product_id` + \$this->dropIndex( + 'idx-test-product_id', + 'test' + ); + + // drops foreign key for table `user_order` + \$this->dropForeignKey( + 'fk-test-order_id', + 'test' + ); + + // drops index for column `order_id` + \$this->dropIndex( + 'idx-test-order_id', + 'test' + ); + + \$this->dropColumn('test', 'user_id'); + \$this->dropColumn('test', 'product_id'); + \$this->dropColumn('test', 'order_id'); + \$this->dropColumn('test', 'created_at'); + } +} + +CODE; diff --git a/tests/data/console/migrate_create/add_columns_prefix.php b/tests/data/console/migrate_create/add_columns_prefix.php new file mode 100644 index 0000000..c9fb959 --- /dev/null +++ b/tests/data/console/migrate_create/add_columns_prefix.php @@ -0,0 +1,128 @@ +addColumn('{{%test}}', 'user_id', \$this->integer()); + \$this->addColumn('{{%test}}', 'product_id', \$this->integer()->unsigned()->notNull()); + \$this->addColumn('{{%test}}', 'order_id', \$this->integer()->notNull()); + \$this->addColumn('{{%test}}', 'created_at', \$this->dateTime()->notNull()); + + // creates index for column `user_id` + \$this->createIndex( + '{{%idx-test-user_id}}', + '{{%test}}', + 'user_id' + ); + + // add foreign key for table `{{%user}}` + \$this->addForeignKey( + '{{%fk-test-user_id}}', + '{{%test}}', + 'user_id', + '{{%user}}', + 'id', + 'CASCADE' + ); + + // creates index for column `product_id` + \$this->createIndex( + '{{%idx-test-product_id}}', + '{{%test}}', + 'product_id' + ); + + // add foreign key for table `{{%product}}` + \$this->addForeignKey( + '{{%fk-test-product_id}}', + '{{%test}}', + 'product_id', + '{{%product}}', + 'id', + 'CASCADE' + ); + + // creates index for column `order_id` + \$this->createIndex( + '{{%idx-test-order_id}}', + '{{%test}}', + 'order_id' + ); + + // add foreign key for table `{{%user_order}}` + \$this->addForeignKey( + '{{%fk-test-order_id}}', + '{{%test}}', + 'order_id', + '{{%user_order}}', + 'id', + 'CASCADE' + ); + } + + /** + * @inheritdoc + */ + public function down() + { + // drops foreign key for table `{{%user}}` + \$this->dropForeignKey( + '{{%fk-test-user_id}}', + '{{%test}}' + ); + + // drops index for column `user_id` + \$this->dropIndex( + '{{%idx-test-user_id}}', + '{{%test}}' + ); + + // drops foreign key for table `{{%product}}` + \$this->dropForeignKey( + '{{%fk-test-product_id}}', + '{{%test}}' + ); + + // drops index for column `product_id` + \$this->dropIndex( + '{{%idx-test-product_id}}', + '{{%test}}' + ); + + // drops foreign key for table `{{%user_order}}` + \$this->dropForeignKey( + '{{%fk-test-order_id}}', + '{{%test}}' + ); + + // drops index for column `order_id` + \$this->dropIndex( + '{{%idx-test-order_id}}', + '{{%test}}' + ); + + \$this->dropColumn('{{%test}}', 'user_id'); + \$this->dropColumn('{{%test}}', 'product_id'); + \$this->dropColumn('{{%test}}', 'order_id'); + \$this->dropColumn('{{%test}}', 'created_at'); + } +} + +CODE; diff --git a/tests/data/console/migrate_create/add_columns_test.php b/tests/data/console/migrate_create/add_columns_test.php new file mode 100644 index 0000000..147e10d --- /dev/null +++ b/tests/data/console/migrate_create/add_columns_test.php @@ -0,0 +1,36 @@ +addColumn('test', 'title', \$this->string(10)->notNull()); + \$this->addColumn('test', 'body', \$this->text()->notNull()); + \$this->addColumn('test', 'price', \$this->money(11,2)->notNull()); + \$this->addColumn('test', 'created_at', \$this->dateTime()); + } + + /** + * @inheritdoc + */ + public function down() + { + \$this->dropColumn('test', 'title'); + \$this->dropColumn('test', 'body'); + \$this->dropColumn('test', 'price'); + \$this->dropColumn('test', 'created_at'); + } +} + +CODE; diff --git a/tests/data/console/migrate_create/create_fields.php b/tests/data/console/migrate_create/create_fields.php new file mode 100644 index 0000000..b18a5dd --- /dev/null +++ b/tests/data/console/migrate_create/create_fields.php @@ -0,0 +1,35 @@ +createTable('test', [ + 'id' => \$this->primaryKey(), + 'title' => \$this->string(10)->notNull()->unique()->defaultValue("test"), + 'body' => \$this->text()->notNull(), + 'price' => \$this->money(11,2)->notNull(), + ]); + } + + /** + * @inheritdoc + */ + public function down() + { + \$this->dropTable('test'); + } +} + +CODE; diff --git a/tests/data/console/migrate_create/create_foreign_key.php b/tests/data/console/migrate_create/create_foreign_key.php new file mode 100644 index 0000000..6f5b40c --- /dev/null +++ b/tests/data/console/migrate_create/create_foreign_key.php @@ -0,0 +1,128 @@ +createTable('test', [ + 'id' => \$this->primaryKey(), + 'user_id' => \$this->integer(), + 'product_id' => \$this->integer()->unsigned()->notNull(), + 'order_id' => \$this->integer()->notNull(), + 'created_at' => \$this->dateTime()->notNull(), + ]); + + // creates index for column `user_id` + \$this->createIndex( + 'idx-test-user_id', + 'test', + 'user_id' + ); + + // add foreign key for table `user` + \$this->addForeignKey( + 'fk-test-user_id', + 'test', + 'user_id', + 'user', + 'id', + 'CASCADE' + ); + + // creates index for column `product_id` + \$this->createIndex( + 'idx-test-product_id', + 'test', + 'product_id' + ); + + // add foreign key for table `product` + \$this->addForeignKey( + 'fk-test-product_id', + 'test', + 'product_id', + 'product', + 'id', + 'CASCADE' + ); + + // creates index for column `order_id` + \$this->createIndex( + 'idx-test-order_id', + 'test', + 'order_id' + ); + + // add foreign key for table `user_order` + \$this->addForeignKey( + 'fk-test-order_id', + 'test', + 'order_id', + 'user_order', + 'id', + 'CASCADE' + ); + } + + /** + * @inheritdoc + */ + public function down() + { + // drops foreign key for table `user` + \$this->dropForeignKey( + 'fk-test-user_id', + 'test' + ); + + // drops index for column `user_id` + \$this->dropIndex( + 'idx-test-user_id', + 'test' + ); + + // drops foreign key for table `product` + \$this->dropForeignKey( + 'fk-test-product_id', + 'test' + ); + + // drops index for column `product_id` + \$this->dropIndex( + 'idx-test-product_id', + 'test' + ); + + // drops foreign key for table `user_order` + \$this->dropForeignKey( + 'fk-test-order_id', + 'test' + ); + + // drops index for column `order_id` + \$this->dropIndex( + 'idx-test-order_id', + 'test' + ); + + \$this->dropTable('test'); + } +} + +CODE; diff --git a/tests/data/console/migrate_create/create_id_pk.php b/tests/data/console/migrate_create/create_id_pk.php new file mode 100644 index 0000000..5173bd6 --- /dev/null +++ b/tests/data/console/migrate_create/create_id_pk.php @@ -0,0 +1,35 @@ +createTable('test', [ + 'id' => \$this->primaryKey(), + 'address' => \$this->string(), + 'address2' => \$this->string(), + 'email' => \$this->string(), + ]); + } + + /** + * @inheritdoc + */ + public function down() + { + \$this->dropTable('test'); + } +} + +CODE; diff --git a/tests/data/console/migrate_create/create_prefix.php b/tests/data/console/migrate_create/create_prefix.php new file mode 100644 index 0000000..04dc575 --- /dev/null +++ b/tests/data/console/migrate_create/create_prefix.php @@ -0,0 +1,128 @@ +createTable('{{%test}}', [ + 'id' => \$this->primaryKey(), + 'user_id' => \$this->integer(), + 'product_id' => \$this->integer()->unsigned()->notNull(), + 'order_id' => \$this->integer()->notNull(), + 'created_at' => \$this->dateTime()->notNull(), + ]); + + // creates index for column `user_id` + \$this->createIndex( + '{{%idx-test-user_id}}', + '{{%test}}', + 'user_id' + ); + + // add foreign key for table `{{%user}}` + \$this->addForeignKey( + '{{%fk-test-user_id}}', + '{{%test}}', + 'user_id', + '{{%user}}', + 'id', + 'CASCADE' + ); + + // creates index for column `product_id` + \$this->createIndex( + '{{%idx-test-product_id}}', + '{{%test}}', + 'product_id' + ); + + // add foreign key for table `{{%product}}` + \$this->addForeignKey( + '{{%fk-test-product_id}}', + '{{%test}}', + 'product_id', + '{{%product}}', + 'id', + 'CASCADE' + ); + + // creates index for column `order_id` + \$this->createIndex( + '{{%idx-test-order_id}}', + '{{%test}}', + 'order_id' + ); + + // add foreign key for table `{{%user_order}}` + \$this->addForeignKey( + '{{%fk-test-order_id}}', + '{{%test}}', + 'order_id', + '{{%user_order}}', + 'id', + 'CASCADE' + ); + } + + /** + * @inheritdoc + */ + public function down() + { + // drops foreign key for table `{{%user}}` + \$this->dropForeignKey( + '{{%fk-test-user_id}}', + '{{%test}}' + ); + + // drops index for column `user_id` + \$this->dropIndex( + '{{%idx-test-user_id}}', + '{{%test}}' + ); + + // drops foreign key for table `{{%product}}` + \$this->dropForeignKey( + '{{%fk-test-product_id}}', + '{{%test}}' + ); + + // drops index for column `product_id` + \$this->dropIndex( + '{{%idx-test-product_id}}', + '{{%test}}' + ); + + // drops foreign key for table `{{%user_order}}` + \$this->dropForeignKey( + '{{%fk-test-order_id}}', + '{{%test}}' + ); + + // drops index for column `order_id` + \$this->dropIndex( + '{{%idx-test-order_id}}', + '{{%test}}' + ); + + \$this->dropTable('{{%test}}'); + } +} + +CODE; diff --git a/tests/data/console/migrate_create/create_test.php b/tests/data/console/migrate_create/create_test.php new file mode 100644 index 0000000..42d445e --- /dev/null +++ b/tests/data/console/migrate_create/create_test.php @@ -0,0 +1,32 @@ +createTable('test', [ + 'id' => \$this->primaryKey(), + ]); + } + + /** + * @inheritdoc + */ + public function down() + { + \$this->dropTable('test'); + } +} + +CODE; diff --git a/tests/data/console/migrate_create/create_title_pk.php b/tests/data/console/migrate_create/create_title_pk.php new file mode 100644 index 0000000..740c163 --- /dev/null +++ b/tests/data/console/migrate_create/create_title_pk.php @@ -0,0 +1,34 @@ +createTable('test', [ + 'title' => \$this->primaryKey(), + 'body' => \$this->text()->notNull(), + 'price' => \$this->money(11,2), + ]); + } + + /** + * @inheritdoc + */ + public function down() + { + \$this->dropTable('test'); + } +} + +CODE; diff --git a/tests/data/console/migrate_create/default.php b/tests/data/console/migrate_create/default.php new file mode 100644 index 0000000..cdef5dc --- /dev/null +++ b/tests/data/console/migrate_create/default.php @@ -0,0 +1,34 @@ +dropColumn('test', 'title'); + \$this->dropColumn('test', 'body'); + \$this->dropColumn('test', 'price'); + \$this->dropColumn('test', 'created_at'); + } + + /** + * @inheritdoc + */ + public function down() + { + \$this->addColumn('test', 'title', \$this->string(10)->notNull()); + \$this->addColumn('test', 'body', \$this->text()->notNull()); + \$this->addColumn('test', 'price', \$this->money(11,2)->notNull()); + \$this->addColumn('test', 'created_at', \$this->dateTime()); + } +} + +CODE; diff --git a/tests/data/console/migrate_create/drop_fields.php b/tests/data/console/migrate_create/drop_fields.php new file mode 100644 index 0000000..5e2fb10 --- /dev/null +++ b/tests/data/console/migrate_create/drop_fields.php @@ -0,0 +1,34 @@ +dropTable('test'); + } + + /** + * @inheritdoc + */ + public function down() + { + \$this->createTable('test', [ + 'id' => \$this->primaryKey(), + 'body' => \$this->text()->notNull(), + 'price' => \$this->money(11,2), + ]); + } +} + +CODE; diff --git a/tests/data/console/migrate_create/drop_test.php b/tests/data/console/migrate_create/drop_test.php new file mode 100644 index 0000000..4f4b166 --- /dev/null +++ b/tests/data/console/migrate_create/drop_test.php @@ -0,0 +1,32 @@ +dropTable('test'); + } + + /** + * @inheritdoc + */ + public function down() + { + \$this->createTable('test', [ + 'id' => \$this->primaryKey(), + ]); + } +} + +CODE; diff --git a/tests/data/console/migrate_create/junction_test.php b/tests/data/console/migrate_create/junction_test.php new file mode 100644 index 0000000..7831a0c --- /dev/null +++ b/tests/data/console/migrate_create/junction_test.php @@ -0,0 +1,97 @@ +createTable('post_tag', [ + 'post_id' => \$this->integer(), + 'tag_id' => \$this->integer(), + 'PRIMARY KEY(post_id, tag_id)', + ]); + + // creates index for column `post_id` + \$this->createIndex( + 'idx-post_tag-post_id', + 'post_tag', + 'post_id' + ); + + // add foreign key for table `post` + \$this->addForeignKey( + 'fk-post_tag-post_id', + 'post_tag', + 'post_id', + 'post', + 'id', + 'CASCADE' + ); + + // creates index for column `tag_id` + \$this->createIndex( + 'idx-post_tag-tag_id', + 'post_tag', + 'tag_id' + ); + + // add foreign key for table `tag` + \$this->addForeignKey( + 'fk-post_tag-tag_id', + 'post_tag', + 'tag_id', + 'tag', + 'id', + 'CASCADE' + ); + } + + /** + * @inheritdoc + */ + public function down() + { + // drops foreign key for table `post` + \$this->dropForeignKey( + 'fk-post_tag-post_id', + 'post_tag' + ); + + // drops index for column `post_id` + \$this->dropIndex( + 'idx-post_tag-post_id', + 'post_tag' + ); + + // drops foreign key for table `tag` + \$this->dropForeignKey( + 'fk-post_tag-tag_id', + 'post_tag' + ); + + // drops index for column `tag_id` + \$this->dropIndex( + 'idx-post_tag-tag_id', + 'post_tag' + ); + + \$this->dropTable('post_tag'); + } +} + +CODE; diff --git a/tests/framework/console/controllers/MigrateControllerTestTrait.php b/tests/framework/console/controllers/MigrateControllerTestTrait.php index 652d856..cce9b6f 100644 --- a/tests/framework/console/controllers/MigrateControllerTestTrait.php +++ b/tests/framework/console/controllers/MigrateControllerTestTrait.php @@ -42,6 +42,27 @@ trait MigrateControllerTestTrait FileHelper::removeDirectory($this->migrationPath); } + public function assertFileContent($expectedFile, $class) + { + $this->assertEqualsWithoutLE( + include Yii::getAlias( + "@yiiunit/data/console/migrate_create/$expectedFile.php" + ), + $this->parseNameClassMigration($class) + ); + } + + public function assertCommandCreatedFile( + $expectedFile, + $migrationName, + $params = [] + ) { + $class = 'm' . gmdate('ymd_His') . '_' . $migrationName; + $params[0] = $migrationName; + $this->runMigrateControllerAction('create', $params); + $this->assertFileContent($expectedFile, $class); + } + /** * @return array applied migration entries */ @@ -168,1048 +189,98 @@ CODE; public function testGenerateDefaultMigration() { - $migrationName = 'DefaultTest'; - $class = 'm' . gmdate('ymd_His') . '_' . $migrationName; - $this->runMigrateControllerAction('create', [$migrationName]); - $file = $this->parseNameClassMigration($class); - - $newLine = '\n'; - $code = <<assertEqualsWithoutLE($code, $file); + $this->assertCommandCreatedFile('default', 'DefaultTest'); } public function testGenerateCreateMigration() { $migrationName = 'create_test'; - $class = 'm' . gmdate('ymd_His') . '_' . $migrationName; - $this->runMigrateControllerAction('create', [ - $migrationName, - 'fields' => 'title:string(10):notNull:unique:defaultValue("test"),body:text:notNull,price:money(11,2):notNull' - ]); - $file = $this->parseNameClassMigration($class); - - $code = <<assertCommandCreatedFile('create_test', $migrationName); -/** - * Handles the creation for table `test`. - */ -class {$class} extends Migration -{ - /** - * @inheritdoc - */ - public function up() - { - \$this->createTable('test', [ - 'id' => \$this->primaryKey(), - 'title' => \$this->string(10)->notNull()->unique()->defaultValue("test"), - 'body' => \$this->text()->notNull(), - 'price' => \$this->money(11,2)->notNull(), + $this->assertCommandCreatedFile('create_fields', $migrationName, [ + 'fields' => 'title:string(10):notNull:unique:defaultValue("test"), + body:text:notNull, + price:money(11,2):notNull' ]); - } - - /** - * @inheritdoc - */ - public function down() - { - \$this->dropTable('test'); - } -} -CODE; - $this->assertEqualsWithoutLE($code, $file); - - $class = 'm' . gmdate('ymd_His') . '_' . $migrationName; - $this->runMigrateControllerAction('create', [ - $migrationName, + $this->assertCommandCreatedFile('create_title_pk', $migrationName, [ 'fields' => 'title:primaryKey,body:text:notNull,price:money(11,2)', ]); - $file = $this->parseNameClassMigration($class); - $code = <<createTable('test', [ - 'title' => \$this->primaryKey(), - 'body' => \$this->text()->notNull(), - 'price' => \$this->money(11,2), + $this->assertCommandCreatedFile('create_id_pk', $migrationName, [ + 'fields' => 'id:primaryKey, + address:string, + address2:string, + email:string', ]); - } - /** - * @inheritdoc - */ - public function down() - { - \$this->dropTable('test'); - } -} - -CODE; - $this->assertEqualsWithoutLE($code, $file); - - $class = 'm' . gmdate('ymd_His') . '_' . $migrationName; - $this->runMigrateControllerAction('create', [ - $migrationName, - ]); - $file = $this->parseNameClassMigration($class); - $code = <<createTable('test', [ - 'id' => \$this->primaryKey(), - ]); - } - - /** - * @inheritdoc - */ - public function down() - { - \$this->dropTable('test'); - } -} - -CODE; - $this->assertEqualsWithoutLE($code, $file); - - $class = 'm' . gmdate('ymd_His') . '_' . $migrationName; - $this->runMigrateControllerAction('create', [ - $migrationName, - 'fields' => 'id:primaryKey,address:string,address2:string,email:string', - ]); - $file = $this->parseNameClassMigration($class); - $code = <<createTable('test', [ - 'id' => \$this->primaryKey(), - 'address' => \$this->string(), - 'address2' => \$this->string(), - 'email' => \$this->string(), - ]); - } - - /** - * @inheritdoc - */ - public function down() - { - \$this->dropTable('test'); - } -} - -CODE; - $this->assertEqualsWithoutLE($code, $file); - - $class = 'm' . gmdate('ymd_His') . '_' . $migrationName; - $this->runMigrateControllerAction('create', [ - $migrationName, + $this->assertCommandCreatedFile('create_foreign_key', $migrationName, [ 'fields' => 'user_id:integer:foreignKey, product_id:foreignKey:integer:unsigned:notNull, order_id:integer:foreignKey(user_order):notNull, created_at:dateTime:notNull', ]); - $file = $this->parseNameClassMigration($class); - $code = <<createTable('test', [ - 'id' => \$this->primaryKey(), - 'user_id' => \$this->integer(), - 'product_id' => \$this->integer()->unsigned()->notNull(), - 'order_id' => \$this->integer()->notNull(), - 'created_at' => \$this->dateTime()->notNull(), - ]); - // creates index for column `user_id` - \$this->createIndex( - 'idx-test-user_id', - 'test', - 'user_id' - ); - - // add foreign key for table `user` - \$this->addForeignKey( - 'fk-test-user_id', - 'test', - 'user_id', - 'user', - 'id', - 'CASCADE' - ); - - // creates index for column `product_id` - \$this->createIndex( - 'idx-test-product_id', - 'test', - 'product_id' - ); - - // add foreign key for table `product` - \$this->addForeignKey( - 'fk-test-product_id', - 'test', - 'product_id', - 'product', - 'id', - 'CASCADE' - ); - - // creates index for column `order_id` - \$this->createIndex( - 'idx-test-order_id', - 'test', - 'order_id' - ); - - // add foreign key for table `user_order` - \$this->addForeignKey( - 'fk-test-order_id', - 'test', - 'order_id', - 'user_order', - 'id', - 'CASCADE' - ); - } - - /** - * @inheritdoc - */ - public function down() - { - // drops foreign key for table `user` - \$this->dropForeignKey( - 'fk-test-user_id', - 'test' - ); - - // drops index for column `user_id` - \$this->dropIndex( - 'idx-test-user_id', - 'test' - ); - - // drops foreign key for table `product` - \$this->dropForeignKey( - 'fk-test-product_id', - 'test' - ); - - // drops index for column `product_id` - \$this->dropIndex( - 'idx-test-product_id', - 'test' - ); - - // drops foreign key for table `user_order` - \$this->dropForeignKey( - 'fk-test-order_id', - 'test' - ); - - // drops index for column `order_id` - \$this->dropIndex( - 'idx-test-order_id', - 'test' - ); - - \$this->dropTable('test'); - } -} - -CODE; - $this->assertEqualsWithoutLE($code, $file); - - $class = 'm' . gmdate('ymd_His') . '_' . $migrationName; - $this->runMigrateControllerAction('create', [ - $migrationName, + $this->assertCommandCreatedFile('create_prefix', $migrationName, [ 'useTablePrefix' => true, 'fields' => 'user_id:integer:foreignKey, product_id:foreignKey:integer:unsigned:notNull, order_id:integer:foreignKey(user_order):notNull, created_at:dateTime:notNull', ]); - $file = $this->parseNameClassMigration($class); - $code = <<createTable('{{%test}}', [ - 'id' => \$this->primaryKey(), - 'user_id' => \$this->integer(), - 'product_id' => \$this->integer()->unsigned()->notNull(), - 'order_id' => \$this->integer()->notNull(), - 'created_at' => \$this->dateTime()->notNull(), - ]); - - // creates index for column `user_id` - \$this->createIndex( - '{{%idx-test-user_id}}', - '{{%test}}', - 'user_id' - ); - - // add foreign key for table `{{%user}}` - \$this->addForeignKey( - '{{%fk-test-user_id}}', - '{{%test}}', - 'user_id', - '{{%user}}', - 'id', - 'CASCADE' - ); - - // creates index for column `product_id` - \$this->createIndex( - '{{%idx-test-product_id}}', - '{{%test}}', - 'product_id' - ); - - // add foreign key for table `{{%product}}` - \$this->addForeignKey( - '{{%fk-test-product_id}}', - '{{%test}}', - 'product_id', - '{{%product}}', - 'id', - 'CASCADE' - ); - - // creates index for column `order_id` - \$this->createIndex( - '{{%idx-test-order_id}}', - '{{%test}}', - 'order_id' - ); - - // add foreign key for table `{{%user_order}}` - \$this->addForeignKey( - '{{%fk-test-order_id}}', - '{{%test}}', - 'order_id', - '{{%user_order}}', - 'id', - 'CASCADE' - ); - } - - /** - * @inheritdoc - */ - public function down() - { - // drops foreign key for table `{{%user}}` - \$this->dropForeignKey( - '{{%fk-test-user_id}}', - '{{%test}}' - ); - - // drops index for column `user_id` - \$this->dropIndex( - '{{%idx-test-user_id}}', - '{{%test}}' - ); - - // drops foreign key for table `{{%product}}` - \$this->dropForeignKey( - '{{%fk-test-product_id}}', - '{{%test}}' - ); - - // drops index for column `product_id` - \$this->dropIndex( - '{{%idx-test-product_id}}', - '{{%test}}' - ); - - // drops foreign key for table `{{%user_order}}` - \$this->dropForeignKey( - '{{%fk-test-order_id}}', - '{{%test}}' - ); - - // drops index for column `order_id` - \$this->dropIndex( - '{{%idx-test-order_id}}', - '{{%test}}' - ); - - \$this->dropTable('{{%test}}'); - } -} - -CODE; - $this->assertEqualsWithoutLE($code, $file); } public function testGenerateDropMigration() { $migrationName = 'drop_test'; - $class = 'm' . gmdate('ymd_His') . '_' . $migrationName; - $this->runMigrateControllerAction('create', [ - $migrationName - ]); - $file = $this->parseNameClassMigration($class); - - $code = <<assertCommandCreatedFile('drop_test', $migrationName); -/** - * Handles the dropping for table `test`. - */ -class {$class} extends Migration -{ - /** - * @inheritdoc - */ - public function up() - { - \$this->dropTable('test'); - } - - /** - * @inheritdoc - */ - public function down() - { - \$this->createTable('test', [ - 'id' => \$this->primaryKey(), - ]); - } -} - -CODE; - $this->assertEqualsWithoutLE($code, $file); - - $class = 'm' . gmdate('ymd_His') . '_' . $migrationName; - $this->runMigrateControllerAction('create', [ - $migrationName, + $this->assertCommandCreatedFile('drop_fields', $migrationName, [ 'fields' => 'body:text:notNull,price:money(11,2)' ]); - $file = $this->parseNameClassMigration($class); - $code = <<dropTable('test'); - } - - /** - * @inheritdoc - */ - public function down() - { - \$this->createTable('test', [ - 'id' => \$this->primaryKey(), - 'body' => \$this->text()->notNull(), - 'price' => \$this->money(11,2), - ]); - } -} - -CODE; - $this->assertEqualsWithoutLE($code, $file); } public function testGenerateAddColumnMigration() { $migrationName = 'add_columns_to_test'; - $class = 'm' . gmdate('ymd_His') . '_' . $migrationName; - $this->runMigrateControllerAction('create', [ - $migrationName, + $this->assertCommandCreatedFile('add_columns_test', $migrationName, [ 'fields' => 'title:string(10):notNull, body:text:notNull, price:money(11,2):notNull, created_at:dateTime' ]); - $file = $this->parseNameClassMigration($class); - - $code = <<addColumn('test', 'title', \$this->string(10)->notNull()); - \$this->addColumn('test', 'body', \$this->text()->notNull()); - \$this->addColumn('test', 'price', \$this->money(11,2)->notNull()); - \$this->addColumn('test', 'created_at', \$this->dateTime()); - } - - /** - * @inheritdoc - */ - public function down() - { - \$this->dropColumn('test', 'title'); - \$this->dropColumn('test', 'body'); - \$this->dropColumn('test', 'price'); - \$this->dropColumn('test', 'created_at'); - } -} - -CODE; - $this->assertEqualsWithoutLE($code, $file); - $migrationName = 'add_columns_to_test'; - $class = 'm' . gmdate('ymd_His') . '_' . $migrationName; - $this->runMigrateControllerAction('create', [ - $migrationName, + $this->assertCommandCreatedFile('add_columns_fk', $migrationName, [ 'fields' => 'user_id:integer:foreignKey, product_id:foreignKey:integer:unsigned:notNull, order_id:integer:foreignKey(user_order):notNull, created_at:dateTime:notNull', ]); - $file = $this->parseNameClassMigration($class); - $code = <<addColumn('test', 'user_id', \$this->integer()); - \$this->addColumn('test', 'product_id', \$this->integer()->unsigned()->notNull()); - \$this->addColumn('test', 'order_id', \$this->integer()->notNull()); - \$this->addColumn('test', 'created_at', \$this->dateTime()->notNull()); - - // creates index for column `user_id` - \$this->createIndex( - 'idx-test-user_id', - 'test', - 'user_id' - ); - - // add foreign key for table `user` - \$this->addForeignKey( - 'fk-test-user_id', - 'test', - 'user_id', - 'user', - 'id', - 'CASCADE' - ); - - // creates index for column `product_id` - \$this->createIndex( - 'idx-test-product_id', - 'test', - 'product_id' - ); - - // add foreign key for table `product` - \$this->addForeignKey( - 'fk-test-product_id', - 'test', - 'product_id', - 'product', - 'id', - 'CASCADE' - ); - - // creates index for column `order_id` - \$this->createIndex( - 'idx-test-order_id', - 'test', - 'order_id' - ); - - // add foreign key for table `user_order` - \$this->addForeignKey( - 'fk-test-order_id', - 'test', - 'order_id', - 'user_order', - 'id', - 'CASCADE' - ); - } - - /** - * @inheritdoc - */ - public function down() - { - // drops foreign key for table `user` - \$this->dropForeignKey( - 'fk-test-user_id', - 'test' - ); - - // drops index for column `user_id` - \$this->dropIndex( - 'idx-test-user_id', - 'test' - ); - - // drops foreign key for table `product` - \$this->dropForeignKey( - 'fk-test-product_id', - 'test' - ); - - // drops index for column `product_id` - \$this->dropIndex( - 'idx-test-product_id', - 'test' - ); - - // drops foreign key for table `user_order` - \$this->dropForeignKey( - 'fk-test-order_id', - 'test' - ); - - // drops index for column `order_id` - \$this->dropIndex( - 'idx-test-order_id', - 'test' - ); - - \$this->dropColumn('test', 'user_id'); - \$this->dropColumn('test', 'product_id'); - \$this->dropColumn('test', 'order_id'); - \$this->dropColumn('test', 'created_at'); - } -} - -CODE; - $this->assertEqualsWithoutLE($code, $file); - - $migrationName = 'add_columns_to_test'; - $class = 'm' . gmdate('ymd_His') . '_' . $migrationName; - $this->runMigrateControllerAction('create', [ - $migrationName, + $this->assertCommandCreatedFile('add_columns_prefix', $migrationName, [ 'useTablePrefix' => true, 'fields' => 'user_id:integer:foreignKey, product_id:foreignKey:integer:unsigned:notNull, order_id:integer:foreignKey(user_order):notNull, created_at:dateTime:notNull', ]); - $file = $this->parseNameClassMigration($class); - - $code = <<addColumn('{{%test}}', 'user_id', \$this->integer()); - \$this->addColumn('{{%test}}', 'product_id', \$this->integer()->unsigned()->notNull()); - \$this->addColumn('{{%test}}', 'order_id', \$this->integer()->notNull()); - \$this->addColumn('{{%test}}', 'created_at', \$this->dateTime()->notNull()); - - // creates index for column `user_id` - \$this->createIndex( - '{{%idx-test-user_id}}', - '{{%test}}', - 'user_id' - ); - - // add foreign key for table `{{%user}}` - \$this->addForeignKey( - '{{%fk-test-user_id}}', - '{{%test}}', - 'user_id', - '{{%user}}', - 'id', - 'CASCADE' - ); - - // creates index for column `product_id` - \$this->createIndex( - '{{%idx-test-product_id}}', - '{{%test}}', - 'product_id' - ); - - // add foreign key for table `{{%product}}` - \$this->addForeignKey( - '{{%fk-test-product_id}}', - '{{%test}}', - 'product_id', - '{{%product}}', - 'id', - 'CASCADE' - ); - - // creates index for column `order_id` - \$this->createIndex( - '{{%idx-test-order_id}}', - '{{%test}}', - 'order_id' - ); - - // add foreign key for table `{{%user_order}}` - \$this->addForeignKey( - '{{%fk-test-order_id}}', - '{{%test}}', - 'order_id', - '{{%user_order}}', - 'id', - 'CASCADE' - ); - } - - /** - * @inheritdoc - */ - public function down() - { - // drops foreign key for table `{{%user}}` - \$this->dropForeignKey( - '{{%fk-test-user_id}}', - '{{%test}}' - ); - - // drops index for column `user_id` - \$this->dropIndex( - '{{%idx-test-user_id}}', - '{{%test}}' - ); - - // drops foreign key for table `{{%product}}` - \$this->dropForeignKey( - '{{%fk-test-product_id}}', - '{{%test}}' - ); - - // drops index for column `product_id` - \$this->dropIndex( - '{{%idx-test-product_id}}', - '{{%test}}' - ); - - // drops foreign key for table `{{%user_order}}` - \$this->dropForeignKey( - '{{%fk-test-order_id}}', - '{{%test}}' - ); - - // drops index for column `order_id` - \$this->dropIndex( - '{{%idx-test-order_id}}', - '{{%test}}' - ); - - \$this->dropColumn('{{%test}}', 'user_id'); - \$this->dropColumn('{{%test}}', 'product_id'); - \$this->dropColumn('{{%test}}', 'order_id'); - \$this->dropColumn('{{%test}}', 'created_at'); - } -} - -CODE; - $this->assertEqualsWithoutLE($code, $file); } public function testGenerateDropColumnMigration() { $migrationName = 'drop_columns_from_test'; - $class = 'm' . gmdate('ymd_His') . '_' . $migrationName; - $this->runMigrateControllerAction('create', [ - $migrationName, - 'fields' => 'title:string(10):notNull,body:text:notNull,price:money(11,2):notNull,created_at:dateTime' + $this->assertCommandCreatedFile('drop_columns_test', $migrationName, [ + 'fields' => 'title:string(10):notNull,body:text:notNull, + price:money(11,2):notNull, + created_at:dateTime' ]); - $file = $this->parseNameClassMigration($class); - - $code = <<dropColumn('test', 'title'); - \$this->dropColumn('test', 'body'); - \$this->dropColumn('test', 'price'); - \$this->dropColumn('test', 'created_at'); - } - - /** - * @inheritdoc - */ - public function down() - { - \$this->addColumn('test', 'title', \$this->string(10)->notNull()); - \$this->addColumn('test', 'body', \$this->text()->notNull()); - \$this->addColumn('test', 'price', \$this->money(11,2)->notNull()); - \$this->addColumn('test', 'created_at', \$this->dateTime()); - } -} - -CODE; - $this->assertEqualsWithoutLE($code, $file); } public function testGenerateCreateJunctionMigration() { $migrationName = 'create_junction_post_and_tag'; - $class = 'm' . gmdate('ymd_His') . '_' . $migrationName; - $this->runMigrateControllerAction('create', [ - $migrationName, - ]); - $file = $this->parseNameClassMigration($class); - - $code = <<createTable('post_tag', [ - 'post_id' => \$this->integer(), - 'tag_id' => \$this->integer(), - 'PRIMARY KEY(post_id, tag_id)', - ]); - - // creates index for column `post_id` - \$this->createIndex( - 'idx-post_tag-post_id', - 'post_tag', - 'post_id' - ); - - // add foreign key for table `post` - \$this->addForeignKey( - 'fk-post_tag-post_id', - 'post_tag', - 'post_id', - 'post', - 'id', - 'CASCADE' - ); - - // creates index for column `tag_id` - \$this->createIndex( - 'idx-post_tag-tag_id', - 'post_tag', - 'tag_id' - ); - - // add foreign key for table `tag` - \$this->addForeignKey( - 'fk-post_tag-tag_id', - 'post_tag', - 'tag_id', - 'tag', - 'id', - 'CASCADE' - ); - } - - /** - * @inheritdoc - */ - public function down() - { - // drops foreign key for table `post` - \$this->dropForeignKey( - 'fk-post_tag-post_id', - 'post_tag' - ); - - // drops index for column `post_id` - \$this->dropIndex( - 'idx-post_tag-post_id', - 'post_tag' - ); - - // drops foreign key for table `tag` - \$this->dropForeignKey( - 'fk-post_tag-tag_id', - 'post_tag' - ); - - // drops index for column `tag_id` - \$this->dropIndex( - 'idx-post_tag-tag_id', - 'post_tag' - ); - - \$this->dropTable('post_tag'); - } -} - -CODE; - $this->assertEqualsWithoutLE($code, $file); + $this->assertCommandCreatedFile('junction_test', $migrationName); } public function testUp() From 04835e5a339e7336fdee020b3624d039e37c5559 Mon Sep 17 00:00:00 2001 From: Luciano Baraglia Date: Tue, 24 May 2016 13:51:55 -0300 Subject: [PATCH 71/90] Some spanish docs updates [skip ci] (#11621) --- docs/guide-es/helper-url.md | 71 +++++++++++------ docs/guide-es/start-installation.md | 155 ++++++++++++++++++++++++------------ 2 files changed, 149 insertions(+), 77 deletions(-) diff --git a/docs/guide-es/helper-url.md b/docs/guide-es/helper-url.md index e834f3f..ad5f923 100644 --- a/docs/guide-es/helper-url.md +++ b/docs/guide-es/helper-url.md @@ -3,8 +3,8 @@ Clase Auxiliar URL (URL Helper) La clase auxiliar URL proporciona un conjunto de métodos estáticos para gestionar URLs. -Obtener URLs Comunes --------------------- + +## Obtener URLs comúnes Se pueden usar dos métodos para obtener URLs comunes: URL de inicio (home URL) y URL base (base URL) de la petición (request) actual. Para obtener la URL de inicio se puede usar el siguiente código: @@ -15,7 +15,7 @@ $absoluteHomeUrl = Url::home(true); $httpsAbsoluteHomeUrl = Url::home('https'); ``` -Si no se pasan parámetros, las URLs generadas son relativas. Se puede pasar `true`para obtener la URL absoluta del +Si no se pasan parámetros, la URL generada es relativa. Se puede pasar `true`para obtener la URL absoluta del esquema actual o especificar el esquema explícitamente (`https`, `http`). Para obtener la URL base de la petición actual, se puede usar el siguiente código: @@ -28,11 +28,11 @@ $httpsAbsoluteBaseUrl = Url::base('https'); El único parámetro del método funciona exactamente igual que para `Url::home()`. -Creación de URLs ----------------- -Para crear una URL para una ruta determinada se puede usar `Url::toRoute()`. El metodo utiliza [[\yii\web\UrlManager]] -para crear una URL: +## Creación de URLs + +Para crear una URL para una ruta determinada se puede usar `Url::toRoute()`. El método utiliza [[\yii\web\UrlManager]] +para crear la URL: ```php $url = Url::toRoute(['product/view', 'id' => 42]); @@ -42,7 +42,7 @@ Se puede especificar la ruta como una cadena de texto, ej. `site/index`. Tambié quieren especificar parámetros para la URL que se esta generando. El formato del array debe ser: ```php -// genera: /index.php?r=site/index¶m1=value1¶m2=value2 +// genera: /index.php?r=site%2Findex¶m1=value1¶m2=value2 ['site/index', 'param1' => 'value1', 'param2' => 'value2'] ``` @@ -53,9 +53,8 @@ Si se quiere crear una URL con un enlace, se puede usar el formato de array con ['site/index', 'param1' => 'value1', '#' => 'name'] ``` -Una ruta puede ser absoluta o relativa. Una ruta absoluta tiene una barra al principio (ej. `/site/index`), -mientras que una ruta relativa no la tiene (ej. `site/index` o `index`). Una ruta relativa se convertirá en una -ruta absoluta siguiendo las siguientes normas: +Una ruta puede ser absoluta o relativa. Una ruta absoluta tiene una barra al principio (ej. `/site/index`), mientras que una ruta relativa +no la tiene (ej. `site/index` o `index`). Una ruta relativa se convertirá en una ruta absoluta siguiendo las siguientes reglas: - Si la ruta es una cadena vacía, se usará la [[\yii\web\Controller::route|route]] actual; - Si la ruta no contiene barras (ej. `index`), se considerará que es el ID de una acción del controlador actual y @@ -63,19 +62,26 @@ ruta absoluta siguiendo las siguientes normas: - Si la ruta no tiene barra inicial (ej. `site/index`), se considerará que es una ruta relativa del modulo actual y se le antepondrá el [[\yii\base\Module::uniqueId|uniqueId]] del modulo. +Desde la versión 2.0.2, puedes especificar una ruta en términos de [alias](concept-aliases.md). Si este es el caso, +el alias será convertido primero en la ruta real, la cual será entonces transformada en una ruta absoluta de acuerdo +a las reglas mostradas arriba. + A continuación se muestran varios ejemplos del uso de este método: ```php -// /index?r=site/index +// /index.php?r=site%2Findex echo Url::toRoute('site/index'); -// /index?r=site/index&src=ref1#name +// /index.php?r=site%2Findex&src=ref1#name echo Url::toRoute(['site/index', 'src' => 'ref1', '#' => 'name']); -// http://www.example.com/index.php?r=site/index +// /index.php?r=post%2Fedit&id=100 asume que el alias "@postEdit" se definió como "post/edit" +echo Url::toRoute(['@postEdit', 'id' => 100]); + +// http://www.example.com/index.php?r=site%2Findex echo Url::toRoute('site/index', true); -// https://www.example.com/index.php?r=site/index +// https://www.example.com/index.php?r=site%2Findex echo Url::toRoute('site/index', 'https'); ``` @@ -99,13 +105,16 @@ el especificado. A continuación se muestran algunos ejemplos de uso: ```php -// /index?r=site/index +// /index.php?r=site%2Findex echo Url::to(['site/index']); -// /index?r=site/index&src=ref1#name +// /index.php?r=site%2Findex&src=ref1#name echo Url::to(['site/index', 'src' => 'ref1', '#' => 'name']); -// la URL solicitada actualmente +// /index.php?r=post%2Fedit&id=100 asume que el alias "@postEdit" se definió como "post/edit" +echo Url::to(['@postEdit', 'id' => 100]); + +// the currently requested URL echo Url::to(); // /images/logo.gif @@ -121,8 +130,24 @@ echo Url::to('@web/images/logo.gif', true); echo Url::to('@web/images/logo.gif', 'https'); ``` -Recordar la URL para utilizarla más adelante --------------------------------------------- +Desde la versión 2.0.3, puedes utilizar [[yii\helpers\Url::current()]] para crear una URL a partir de la ruta +solicitada y los parámetros GET. Puedes modificar o eliminar algunos de los parámetros GET, o también agregar nuevos +pasando un parámetro `$params` al método. Por ejemplo, + +```php +// asume que $_GET = ['id' => 123, 'src' => 'google'], la ruta actual es "post/view" + +// /index.php?r=post%2Fview&id=123&src=google +echo Url::current(); + +// /index.php?r=post%2Fview&id=123 +echo Url::current(['src' => null]); +// /index.php?r=post%2Fview&id=100&src=google +echo Url::current(['id' => 100]); +``` + + +## Recordar URLs Hay casos en que se necesita recordar la URL y después usarla durante el procesamiento de una de las peticiones secuenciales. Se puede logar de la siguiente manera: @@ -145,11 +170,9 @@ $url = Url::previous(); $productUrl = Url::previous('product'); ``` -Reconocer la relatividad de URLs --------------------------------- +## Chequear URLs relativas -Para descubrir si una URL es relativa, es decir, que no contenga información del host, se puede utilizar el siguiente -código: +Para descubrir si una URL es relativa, es decir, que no contenga información del host, se puede utilizar el siguiente código: ```php $isRelative = Url::isRelative('test/it'); diff --git a/docs/guide-es/start-installation.md b/docs/guide-es/start-installation.md index ddf1d82..00799b0 100644 --- a/docs/guide-es/start-installation.md +++ b/docs/guide-es/start-installation.md @@ -1,58 +1,89 @@ -Instalando Yii -============== +Instalar Yii +============ -Yii puede ser instalado de dos maneras, usando [Composer](https://getcomposer.org/) o descargando un archivo comprimido. -Es preferible usar la primera forma, ya que te permite instalar [extensiones](structure-extensions.md) o actualizar Yii ejecutando un simple comando. +Puedes instalar Yii de dos maneras, utilizando el administrador de paquetes [Composer](https://getcomposer.org/) o descargando un archivo comprimido. +La forma recomendada es la primera, ya que te permite instalar nuevas [extensions](structure-extensions.md) o actualizar Yii con sólo ejecutar un comando. -> Note: A diferencia de Yii 1, la instalación estándar de Yii 2 resulta en la descarga e instalación tanto del framework como del esqueleto de la aplicación. +La instalación estándar de Yii cuenta tanto con el framework como un template de proyecto instalados. +Un template de proyecto es un proyecto Yii funcional que implementa algunas características básicas como: login, formulario de contacto, etc. +El código está organizado de una forma recomendada. Por lo tanto, puede servir como un buen punto de partida para tus proyectos. + +En esta y en las próximas secciones, describiremos cómo instalar Yii con el llamado *Template de Proyecto Básico* +y cómo implementar nuevas características por encima del template. Yii también provee otro template llamado +[Template de Proyecto Avanzado](https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide/README.md) qué es mejor para desarrollar aplicaciones con varios niveles +en el entorno de un equipo de desarrollo. +> Info: El Template de Proyecto Básico es adecuado para desarrollar el 90 porciento de las aplicaciones Web. Difiere del + Template de Proyecto Avanzado principalmente en cómo está organizado el código. Si eres nuevo en Yii, te recomendamos + utilizar el Template de Proyecto Básico por su simplicidad pero funcionalidad suficiente. -Instalando a través de Composer + +Installing via Composer ------------------------------- Si aún no tienes Composer instalado, puedes hacerlo siguiendo las instrucciones que se encuentran en [getcomposer.org](https://getcomposer.org/download/). En Linux y Mac OS X, se ejecutan los siguientes comandos: - curl -sS https://getcomposer.org/installer | php - mv composer.phar /usr/local/bin/composer +```bash +curl -sS https://getcomposer.org/installer | php +mv composer.phar /usr/local/bin/composer +``` En Windows, tendrás que descargar y ejecutar [Composer-Setup.exe](https://getcomposer.org/Composer-Setup.exe). Por favor, consulta la [Documentación de Composer](https://getcomposer.org/doc/) si encuentras algún problema o deseas obtener un conocimiento más profundo sobre su utilización. -Si ya tienes composer instalado asegurate que esté actualizado ejecutando `composer self-update` +Si ya tienes composer instalado, asegúrate de tener una versión actualizada. Puedes actualizar Composer +ejecutando el comando `composer self-update` Teniendo Composer instalado, puedes instalar Yii ejecutando los siguientes comandos en un directorio accesible vía Web: -Nota: es posible que en al ejecutar el primer comando te pida tu username - composer global require "fxp/composer-asset-plugin:~1.1.1" - composer create-project --prefer-dist yiisoft/yii2-app-basic basic +```bash +composer global require "fxp/composer-asset-plugin:~1.1.1" +composer create-project --prefer-dist yiisoft/yii2-app-basic basic +``` + +El primer comando instala [composer asset plugin](https://github.com/francoispluchino/composer-asset-plugin/), +que permite administrar dependencias de paquetes bower y npm a través de Composer. Sólo necesitas ejecutar este comando +una vez. El segundo comando instala Yii en un directorio llamado `basic`. Puedes elegir un nombre de directorio diferente si así lo deseas. -El comando anterior instala Yii dentro del directorio `basic`. +> Note: Durante la instalación, Composer puede preguntar por tus credenciales de acceso de Github. Esto es normal ya que Composer +> necesita obtener suficiente límite de acceso de la API para traer la información de dependencias de Github. Para más detalles, +> consulta la [documentación de Composer](https://getcomposer.org/doc/articles/troubleshooting.md#api-rate-limit-and-oauth-tokens). -> Tip: Si quieres instalar la última versión de desarrollo de Yii, puedes utilizar el siguiente comando, -> que añade una [opción de estabilidad mínima](https://getcomposer.org/doc/04-schema.md#minimum-stability): +> Tip: Si quieres instalar la última versión de desarrollo de Yii, puedes utilizar uno de los siguientes comandos, +> que agregan una [opción de estabilidad](https://getcomposer.org/doc/04-schema.md#minimum-stability): > -> composer create-project --prefer-dist --stability=dev yiisoft/yii2-app-basic basic +> ```bash +> composer create-project --prefer-dist --stability=dev yiisoft/yii2-app-basic basic +> ``` > -> Ten en cuenta que la versión de desarrollo de Yii no debería ser usada para producción ya que podría romper el funcionamiento actual de la aplicación. +> Ten en cuenta que la versión de desarrollo de Yii no debería ser utilizada en producción ya que podría romper tu código actual. -Instalando desde un Archivo Comprimido --------------------------------------- +Instalar desde un Archivo Comprimido +------------------------------------ -Instalar Yii desde un archivo comprimido involucra dos pasos: +Instalar Yii desde un archivo comprimido involucra tres pasos: 1. Descargar el archivo desde [yiiframework.com](http://www.yiiframework.com/download/yii2-basic). 2. Descomprimirlo en un directorio accesible vía Web. +3. Modificar el archivo `config/web.php` introduciendo una clave secreta para el ítem de configuración `cookieValidationKey` + (esto se realiza automáticamente si estás instalando Yii a través de Composer): + + ```php + // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation + 'cookieValidationKey' => 'enter your secret key here', + ``` Otras Opciones de Instalación ----------------------------- Las instrucciones anteriores muestran cómo instalar Yii, lo que también crea una aplicación Web lista para ser usada. -Este es un buen punto de partida para pequeñas aplicaciones, o cuando apenas estás aprendiendo a utilizar Yii. +Este es un buen punto de partida para la mayoría de proyectos, tanto grandes como pequeños. Es especialmente adecuado si recién +estás aprendiendo a utilizar Yii. Pero también hay otras opciones de instalación disponibles: @@ -65,45 +96,61 @@ Pero también hay otras opciones de instalación disponibles: Verificando las Instalación --------------------------- -Después de la instalación, puedes acceder a la aplicación instalada a través de la siguiente URL: +Una vez finalizada la instalación, o bien configura tu servidor web (mira la sección siguiente) o utiliza +el [servidor web incluido en PHP](https://secure.php.net/manual/en/features.commandline.webserver.php) ejecutando el siguiente +comando de consola estando parado en el directorio `web` de la aplicación: + +```bash +php yii serve +``` + +> Note: Por defecto el servidor HTTP escuchará en el puerto 8080. De cualquier modo, si el puerto está en uso o deseas +servir varias aplicaciones de esta manera, podrías querer especificar qué puerto utilizar. Sólo agrega el argumento --port: +```bash +php yii serve --port=8888 ``` -http://localhost/basic/web/index.php + +Puedes utilizar tu navegador para acceder a la aplicación instalada de Yii en la siguiente URL: + ``` +http://localhost:8080/. -Esta URL da por hecho que Yii se instaló en un directorio llamado `basic`, directamente bajo el directorio del Servidor Web, -y que el Servidor Web está corriendo en tu máquina local (`localhost`). Sino, podrías necesitar ajustarlo de acuerdo a tu entorno de instalación. ![Instalación Correcta de Yii](images/start-app-installed.png) Deberías ver la página mostrando "Congratulations!" en tu navegador. Si no ocurriera, por favor chequea que la instalación -de PHP satisface los requerimientos de Yii. Esto puedes hacerlo usando cualquiera de los siguientes procedimientos: +de PHP satisfaga los requerimientos de Yii. Esto puedes hacerlo usando cualquiera de los siguientes procedimientos: -* Visitando la URL `http://localhost/basic/requirements.php` en tu navegador +* Copiando `/requirements.php` a `/web/requirements.php` y visitando la URL `http://localhost/basic/requirements.php` en tu navegador * Corriendo los siguientes comandos: - ``` + ```bash cd basic php requirements.php ``` + +Deberías configurar tu instalación de PHP para que satisfaga los requisitos mínimos de Yii. Lo que es más importante, +debes tener PHP 5.4 o mayor. También deberías instalar la [Extensión de PHP PDO](http://www.php.net/manual/es/pdo.installation.php) +y el correspondiente driver de base de datos (como `pdo_mysql` para bases de datos MySQL), si tu aplicación lo necesitara. -Deberías configurar tu instalación de PHP para que satisfaga los requisitos mínimos de Yii. Lo que es más importante, debes tener PHP 5.4 o mayor. -También deberías instalar la [Extensión de PHP PDO](http://www.php.net/manual/es/pdo.installation.php) y el correspondiente driver de base de datos -(como `pdo_mysql` para bases de datos MySQL), si tu aplicación lo necesitara. +Configurar Servidores Web +------------------------- -Configurando Servidores Web ---------------------------- +> Info: Puedes saltear esta sección por ahora si sólo estás probando Yii sin intención + de poner la aplicación en un servidor de producción. -> Info: Puedes saltear esta sección por ahora si sólo estás probando Yii sin intención de poner la aplicación en un servidor de producción. +La aplicación instalada siguiendo las instrucciones mencionadas debería estar lista para usar tanto +con un [servidor HTTP Apache](http://httpd.apache.org/) como con un [servidor HTTP Nginx](http://nginx.org/), +en Windows, Mac OS X, o Linux utilizando PHP 5.4 o mayor. Yii 2.0 también es compatible con [HHVM](http://hhvm.com/) +de Facebook. De todos modos, hay algunos casos donde HHVM se comporta diferente del +PHP oficial, por lo que tendrás que tener cuidados extra al utilizarlo. -La aplicación instalada debería estar lista para usar tanto con un [servidor HTTP Apache](http://httpd.apache.org/) como con un [servidor HTTP Nginx](http://nginx.org/), -en Windows, Mac OS X, o Linux. - -En un servidor de producción, podrías querer configurar el servidor Web para que la aplicación sea accedida a través de la -URL `http://www.example.com/index.php` en vez de `http://www.example.com/basic/web/index.php`. Tal configuración -require apuntar el document root de tu servidor Web al directorio `basic/web`. También podrías querer ocultar `index.php` -de la URL, como se describe en la sección [Parseo y Generación de URLs](runtime-url-handling.md). +En un servidor de producción, podrías querer configurar el servidor Web para que la aplicación sea accedida +a través de la URL `http://www.example.com/index.php` en vez de `http://www.example.com/basic/web/index.php`. Tal configuración +require apuntar el document root de tu servidor Web a la carpeta `basic/web`. También podrías +querer ocultar `index.php` de la URL, como se describe en la sección [Parseo y Generación de URLs](runtime-url-handling.md). En esta sub-sección, aprenderás a configurar tu servidor Apache o Nginx para alcanzar estos objetivos. > Info: Al definir `basic/web` como document root, también previenes que los usuarios finales accedan @@ -120,29 +167,29 @@ la sección [Entorno de Hosting Compartido](tutorial-shared-hosting.md) para má Utiliza la siguiente configuración del archivo `httpd.conf` de Apache dentro de la configuración del virtual host. Ten en cuenta que deberás reemplazar `path/to/basic/web` con la ruta real a `basic/web`. -``` -# Definir el document root de "basic/web" +```apache +# Definir el document root como "basic/web" DocumentRoot "path/to/basic/web" + # utiliza mod_rewrite para soporte de URLs amigables RewriteEngine on - - # Si el directorio o archivo existe, utiliza el request directamente + # Si el directorio o archivo existe, utiliza la petición directamente RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d - # Sino envía el request a index.php + # Sino, redirige la petición a index.php RewriteRule . index.php - # ...más configuraciones... + # ...otras configuraciones... ``` ### Configuración Recomendada de Nginx -Deberías haber instalado PHP como un [FPM SAPI](http://php.net/install.fpm) para utilizar [Nginx](http://wiki.nginx.org/). -Utiliza la siguiente configuración de Nginx, reemplazando `path/to/basic/web` con la ruta real a `basic/web` y `mysite.local` con el -hostname real del servidor. +Para utilizar [Nginx](http://wiki.nginx.org/), debes instalar PHP como un [FPM SAPI](http://php.net/install.fpm). +Utiliza la siguiente configuración de Nginx, reemplazando `path/to/basic/web` con la ruta real a +`basic/web` y `mysite.local` con el hostname real a servir. ``` server { @@ -156,12 +203,12 @@ server { root /path/to/basic/web; index index.php; - access_log /path/to/basic/log/access.log main; + access_log /path/to/basic/log/access.log; error_log /path/to/basic/log/error.log; location / { # Redireccionar a index.php todo lo que no sea un archivo real - try_files $uri $uri/ /index.php?$args; + try_files $uri $uri/ /index.php$is_args$args; } # descomentar para evitar el procesamiento de llamadas de Yii a archivos estáticos no existente @@ -171,9 +218,11 @@ server { #error_page 404 /404.html; location ~ \.php$ { - include fastcgi.conf; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_pass 127.0.0.1:9000; #fastcgi_pass unix:/var/run/php5-fpm.sock; + try_files $uri =404; } location ~ /\.(ht|svn|git) { From dbb54f986c766469b5c8bcc42584d1f843e62c3d Mon Sep 17 00:00:00 2001 From: Evgeniy Tkachenko Date: Wed, 25 May 2016 09:14:31 +0300 Subject: [PATCH 72/90] Updated phpDoc of scalar --- framework/db/Query.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/db/Query.php b/framework/db/Query.php index 2c099c0..2bf9bf1 100644 --- a/framework/db/Query.php +++ b/framework/db/Query.php @@ -252,7 +252,7 @@ class Query extends Component implements QueryInterface * The value returned will be the first column in the first row of the query results. * @param Connection $db the database connection used to generate the SQL statement. * If this parameter is not given, the `db` application component will be used. - * @return string|boolean the value of the first column in the first row of the query result. + * @return string|null|false the value of the first column in the first row of the query result. * False is returned if the query result is empty. */ public function scalar($db = null) From b7b8348c732065aa2c41f0775ed3450afbe9097e Mon Sep 17 00:00:00 2001 From: Klimov Paul Date: Wed, 25 May 2016 12:40:40 +0300 Subject: [PATCH 73/90] `yii\rbac\PhpManager` now invalidates script file cache performed by 'OPCache' or 'APC' on file saving --- framework/CHANGELOG.md | 1 + framework/rbac/PhpManager.php | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index b7d2b9b..fd3181e 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -6,6 +6,7 @@ Yii Framework 2 Change Log ----------------------- - Enh #8795: Refactored `yii\web\User::loginByCookie()` in order to make it easier to override (maine-mike, silverfire) +- Enh #9948: `yii\rbac\PhpManager` now invalidates script file cache performed by 'OPCache' or 'APC' on file saving (klimov-paul) - Enh #11195: Added ability to append custom string to schema builder column definition (df2, samdark) - Enh #11490: Added `yii\data\ArrayDataProvider::$modelClass` property to specify a model used to provide column labels even when data array is empty (PowerGamer1) - Enh #11591: Added support for wildcards for `only` and `except` at `yii\base\ActionFilter` (klimov-paul) diff --git a/framework/rbac/PhpManager.php b/framework/rbac/PhpManager.php index 62e9dec..a68f454 100644 --- a/framework/rbac/PhpManager.php +++ b/framework/rbac/PhpManager.php @@ -764,6 +764,21 @@ class PhpManager extends BaseManager protected function saveToFile($data, $file) { file_put_contents($file, "invalidateScriptCache($file); + } + + /** + * Invalidates precompiled script cache (such as OPCache or APC) for the given file. + * @param string $file the file path. + */ + protected function invalidateScriptCache($file) + { + if (function_exists('opcache_invalidate')) { + opcache_invalidate($file, true); + } + if (function_exists('apc_delete_file')) { + @apc_delete_file($file); + } } /** From c9fa71e5caba0f59f117b85c74aff450e4a7f490 Mon Sep 17 00:00:00 2001 From: Klimov Paul Date: Wed, 25 May 2016 16:34:19 +0300 Subject: [PATCH 74/90] added missing `@since` tag --- framework/rbac/PhpManager.php | 1 + 1 file changed, 1 insertion(+) diff --git a/framework/rbac/PhpManager.php b/framework/rbac/PhpManager.php index a68f454..3abdab5 100644 --- a/framework/rbac/PhpManager.php +++ b/framework/rbac/PhpManager.php @@ -770,6 +770,7 @@ class PhpManager extends BaseManager /** * Invalidates precompiled script cache (such as OPCache or APC) for the given file. * @param string $file the file path. + * @since 2.0.9 */ protected function invalidateScriptCache($file) { From af6df229e8f197bed3ed816e6db792e3436d49f3 Mon Sep 17 00:00:00 2001 From: Luciano Baraglia Date: Wed, 25 May 2016 18:10:49 -0300 Subject: [PATCH 75/90] Output client spanish docs [skip ci] (#11622) --- docs/guide-es/output-client-scripts.md | 98 ++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 docs/guide-es/output-client-scripts.md diff --git a/docs/guide-es/output-client-scripts.md b/docs/guide-es/output-client-scripts.md new file mode 100644 index 0000000..a1125c7 --- /dev/null +++ b/docs/guide-es/output-client-scripts.md @@ -0,0 +1,98 @@ +Trabajar con Scripts del Cliente +================================ + +> Note: Esta sección se encuentra en desarrollo. + +### Registrar scripts + +Con el objeto [[yii\web\View]] puedes registrar scripts. Hay dos métodos dedicados a esto: +[[yii\web\View::registerJs()|registerJs()]] para scripts en línea +[[yii\web\View::registerJsFile()|registerJsFile()]] para scripts externos. +Los scripts en línea son útiles para configuración y código generado dinámicamente. +El método para agregarlos puede ser utilizado así: + +```php +$this->registerJs("var options = ".json_encode($options).";", View::POS_END, 'my-options'); +``` + +El primer argumento es el código JS real que queremos insertar en la página. El segundo argumento +determina en qué parte de la página debería ser insertado el script. Los valores posibles son: + +- [[yii\web\View::POS_HEAD|View::POS_HEAD]] para la sección head. +- [[yii\web\View::POS_BEGIN|View::POS_BEGIN]] justo después de la etiqueta ``. +- [[yii\web\View::POS_END|View::POS_END]] justo antes de cerrar la etiqueta ``. +- [[yii\web\View::POS_READY|View::POS_READY]] para ejecutar código en el evento `ready` del documento. Esto registrará [[yii\web\JqueryAsset|jQuery]] automáticamente. +- [[yii\web\View::POS_LOAD|View::POS_LOAD]] para ejecutar código en el evento `load` del documento. Esto registrará [[yii\web\JqueryAsset|jQuery]] automáticamente. + +El último argumento es un ID único del script, utilizado para identificar el bloque de código y reemplazar otro con el mismo ID +en vez de agregar uno nuevo. En caso de no proveerlo, el código JS en sí será utilizado como ID. + +Un script externo puede ser agregado de esta manera: + +```php +$this->registerJsFile('http://example.com/js/main.js', ['depends' => [\yii\web\JqueryAsset::className()]]); +``` + +Los argumentos para [[yii\web\View::registerJsFile()|registerJsFile()]] son similares a los de +[[yii\web\View::registerCssFile()|registerCssFile()]]. En el ejemplo anterior, +registramos el archivo `main.js` con dependencia de `JqueryAsset`. Esto quiere decir que el archivo `main.js` +será agregado DESPUÉS de `jquery.js`. Si esta especificación de dependencia, el orden relativo entre +`main.js` y `jquery.js` sería indefinido. + +Como para [[yii\web\View::registerCssFile()|registerCssFile()]], es altamente recomendable que utilices +[asset bundles](structure-assets.md) para registrar archivos JS externos más que utilizar [[yii\web\View::registerJsFile()|registerJsFile()]]. + + +### Registrar asset bundles + +Como mencionamos anteriormente, es preferible utilizar asset bundles en vez de usar CSS y JavaScript directamente. Puedes obtener detalles +de cómo definir asset bundles en la sección [gestor de assets](structure-assets.md) de esta guía. Utilizar asset bundles +ya definidos es muy sencillo: + +```php +\frontend\assets\AppAsset::register($this); +``` + + + +### Registrar CSS + +Puedes registrar CSS utilizando [[yii\web\View::registerCss()|registerCss()]] o [[yii\web\View::registerCssFile()|registerCssFile()]]. +El primero registra un bloque de código CSS mientras que el segundo registra un archivo CSS externo. Por ejemplo, + +```php +$this->registerCss("body { background: #f00; }"); +``` + +El código anterior dará como resultado que se agregue lo siguiente a la sección head de la página: + +```html + +``` + +Si quieres especificar propiedades adicionales a la etiqueta style, pasa un array de claves-valores como tercer argumento. +Si necesitas asegurarte que haya sólo una etiqueta style utiliza el cuarto argumento como fue mencionado en las descripciones de meta etiquetas. + +```php +$this->registerCssFile("http://example.com/css/themes/black-and-white.css", [ + 'depends' => [BootstrapAsset::className()], + 'media' => 'print', +], 'css-print-theme'); +``` + +El código de arriba agregará un link al archivo CSS en la sección head de la página. + +* El primer argumento especifica el archivo CSS a ser registrado. +* El segundo argumento especifica los atributos HTML de la etiqueta `` resultante. La opción `depends` + es especialmente tratada. Esta especifica de qué asset bundles depende este archivo CSS. En este caso, depende + del asset bundle [[yii\bootstrap\BootstrapAsset|BootstrapAsset]]. Esto significa que el archivo CSS será agregado + *después* de los archivos CSS de [[yii\bootstrap\BootstrapAsset|BootstrapAsset]]. +* El último argumento especifica un ID que identifica al archivo CSS. Si no es provisto, se utilizará la URL + del archivo. + + +Es altamente recomendable que ustilices [asset bundles](structure-assets.md) para registrar archivos CSS en vez de +utilizar [[yii\web\View::registerCssFile()|registerCssFile()]]. Utilizar asset bundles te permite combinar y comprimir +varios archivos CSS, deseable en sitios web de tráfico alto. From 9d327baa8b2c80b53d4d405678f03e6b89ff6e38 Mon Sep 17 00:00:00 2001 From: Nikola Kovacs Date: Thu, 26 May 2016 11:19:32 +0200 Subject: [PATCH 76/90] coding style fixes --- framework/base/ActionFilter.php | 3 +- framework/base/Configurable.php | 1 - framework/behaviors/SluggableBehavior.php | 2 +- framework/captcha/Captcha.php | 2 +- framework/console/ErrorHandler.php | 2 +- framework/console/controllers/CacheController.php | 4 +- .../console/controllers/FixtureController.php | 3 +- .../console/controllers/MessageController.php | 34 +++++----- .../console/controllers/MigrateController.php | 4 +- framework/console/controllers/ServeController.php | 2 +- framework/db/ActiveQuery.php | 2 +- framework/db/BaseActiveRecord.php | 2 +- framework/db/cubrid/Schema.php | 2 +- framework/db/oci/Schema.php | 2 +- framework/db/pgsql/Schema.php | 2 +- framework/db/sqlite/ColumnSchemaBuilder.php | 1 - framework/grid/DataColumn.php | 4 +- framework/helpers/BaseInflector.php | 2 +- framework/helpers/BaseJson.php | 2 +- framework/i18n/DbMessageSource.php | 4 +- framework/i18n/Formatter.php | 72 ++++++++++++++-------- framework/messages/config.php | 2 +- framework/test/ArrayFixture.php | 1 - framework/validators/FileValidator.php | 13 ++-- framework/validators/ImageValidator.php | 10 +-- framework/validators/IpValidator.php | 8 ++- framework/web/MultiFieldSession.php | 2 +- framework/web/Request.php | 2 +- framework/web/UrlRule.php | 4 +- framework/web/User.php | 4 +- 30 files changed, 110 insertions(+), 88 deletions(-) diff --git a/framework/base/ActionFilter.php b/framework/base/ActionFilter.php index 8f33f4e..1c4c624 100644 --- a/framework/base/ActionFilter.php +++ b/framework/base/ActionFilter.php @@ -118,7 +118,8 @@ class ActionFilter extends Behavior * @return string * @since 2.0.7 */ - protected function getActionId($action) { + protected function getActionId($action) + { if ($this->owner instanceof Module) { $mid = $this->owner->getUniqueId(); $id = $action->getUniqueId(); diff --git a/framework/base/Configurable.php b/framework/base/Configurable.php index 37e7f31..b9a121d 100644 --- a/framework/base/Configurable.php +++ b/framework/base/Configurable.php @@ -29,4 +29,3 @@ namespace yii\base; interface Configurable { } - diff --git a/framework/behaviors/SluggableBehavior.php b/framework/behaviors/SluggableBehavior.php index f08ef23..fa1ad9c 100644 --- a/framework/behaviors/SluggableBehavior.php +++ b/framework/behaviors/SluggableBehavior.php @@ -223,7 +223,7 @@ class SluggableBehavior extends AttributeBehavior /* @var $model BaseActiveRecord */ $validator = Yii::createObject(array_merge( [ - 'class' => UniqueValidator::className() + 'class' => UniqueValidator::className(), ], $this->uniqueValidator )); diff --git a/framework/captcha/Captcha.php b/framework/captcha/Captcha.php index 58a74cc..12ab1c3 100644 --- a/framework/captcha/Captcha.php +++ b/framework/captcha/Captcha.php @@ -150,7 +150,7 @@ class Captcha extends InputWidget $options = [ 'refreshUrl' => Url::toRoute($route), - 'hashKey' => 'yiiCaptcha/' . trim($route[0], '/') + 'hashKey' => 'yiiCaptcha/' . trim($route[0], '/'), ]; return $options; diff --git a/framework/console/ErrorHandler.php b/framework/console/ErrorHandler.php index 712a7a5..c2ef846 100644 --- a/framework/console/ErrorHandler.php +++ b/framework/console/ErrorHandler.php @@ -75,4 +75,4 @@ class ErrorHandler extends \yii\base\ErrorHandler } return $message; } -} \ No newline at end of file +} diff --git a/framework/console/controllers/CacheController.php b/framework/console/controllers/CacheController.php index 9079eec..a2f6fe4 100644 --- a/framework/console/controllers/CacheController.php +++ b/framework/console/controllers/CacheController.php @@ -93,7 +93,7 @@ class CacheController extends Controller $cachesInfo[] = [ 'name' => $name, 'class' => $class, - 'is_flushed' => Yii::$app->get($name)->flush(), + 'is_flushed' => Yii::$app->get($name)->flush(), ]; } @@ -117,7 +117,7 @@ class CacheController extends Controller $cachesInfo[] = [ 'name' => $name, 'class' => $class, - 'is_flushed' => Yii::$app->get($name)->flush(), + 'is_flushed' => Yii::$app->get($name)->flush(), ]; } diff --git a/framework/console/controllers/FixtureController.php b/framework/console/controllers/FixtureController.php index 99ec476..79b7993 100644 --- a/framework/console/controllers/FixtureController.php +++ b/framework/console/controllers/FixtureController.php @@ -78,7 +78,7 @@ class FixtureController extends Controller { return array_merge(parent::optionAliases(), [ 'g' => 'globalFixtures', - 'n' => 'namespace' + 'n' => 'namespace', ]); } @@ -490,5 +490,4 @@ class FixtureController extends Controller { return Yii::getAlias('@' . str_replace('\\', '/', $this->namespace)); } - } diff --git a/framework/console/controllers/MessageController.php b/framework/console/controllers/MessageController.php index 29f30be..980c192 100644 --- a/framework/console/controllers/MessageController.php +++ b/framework/console/controllers/MessageController.php @@ -141,22 +141,22 @@ class MessageController extends Controller public function options($actionID) { return array_merge(parent::options($actionID), [ - 'sourcePath', - 'messagePath', - 'languages', - 'translator', - 'sort', - 'overwrite', - 'removeUnused', - 'markUnused', - 'except', - 'only', - 'format', - 'db', - 'sourceMessageTable', - 'messageTable', - 'catalog', - 'ignoreCategories' + 'sourcePath', + 'messagePath', + 'languages', + 'translator', + 'sort', + 'overwrite', + 'removeUnused', + 'markUnused', + 'except', + 'only', + 'format', + 'db', + 'sourceMessageTable', + 'messageTable', + 'catalog', + 'ignoreCategories', ]); } @@ -180,7 +180,7 @@ class MessageController extends Controller 't' => 'translator', 'm' => 'sourceMessageTable', 's' => 'sourcePath', - 'r' => 'removeUnused' + 'r' => 'removeUnused', ]); } diff --git a/framework/console/controllers/MigrateController.php b/framework/console/controllers/MigrateController.php index 11f5960..01aac89 100644 --- a/framework/console/controllers/MigrateController.php +++ b/framework/console/controllers/MigrateController.php @@ -80,7 +80,7 @@ class MigrateController extends BaseMigrateController 'drop_table' => '@yii/views/dropTableMigration.php', 'add_column' => '@yii/views/addColumnMigration.php', 'drop_column' => '@yii/views/dropColumnMigration.php', - 'create_junction' => '@yii/views/createTableMigration.php' + 'create_junction' => '@yii/views/createTableMigration.php', ]; /** * @var boolean indicates whether the table names generated should consider @@ -295,7 +295,7 @@ class MigrateController extends BaseMigrateController $foreignKeys[$column] = [ 'idx' => $this->generateTableName("idx-$table-$column"), 'fk' => $this->generateTableName("fk-$table-$column"), - 'relatedTable' => $this->generateTableName($relatedTable) + 'relatedTable' => $this->generateTableName($relatedTable), ]; } diff --git a/framework/console/controllers/ServeController.php b/framework/console/controllers/ServeController.php index daee73b..65ca37c 100644 --- a/framework/console/controllers/ServeController.php +++ b/framework/console/controllers/ServeController.php @@ -103,7 +103,7 @@ class ServeController extends Controller return array_merge(parent::optionAliases(), [ 't' => 'docroot', 'p' => 'port', - 'r' => 'router' + 'r' => 'router', ]); } diff --git a/framework/db/ActiveQuery.php b/framework/db/ActiveQuery.php index 49211a6..3a53a7a 100644 --- a/framework/db/ActiveQuery.php +++ b/framework/db/ActiveQuery.php @@ -405,7 +405,7 @@ class ActiveQuery extends Query implements ActiveQueryInterface // relation is defined with an alias, adjust callback to apply alias list(, $relation, $alias) = $matches; $name = $relation; - $callback = function($query) use ($callback, $alias) { + $callback = function ($query) use ($callback, $alias) { /** @var $query ActiveQuery */ $query->alias($alias); if ($callback !== null) { diff --git a/framework/db/BaseActiveRecord.php b/framework/db/BaseActiveRecord.php index 75272f1..3606d5f 100644 --- a/framework/db/BaseActiveRecord.php +++ b/framework/db/BaseActiveRecord.php @@ -912,7 +912,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface public function afterSave($insert, $changedAttributes) { $this->trigger($insert ? self::EVENT_AFTER_INSERT : self::EVENT_AFTER_UPDATE, new AfterSaveEvent([ - 'changedAttributes' => $changedAttributes + 'changedAttributes' => $changedAttributes, ])); } diff --git a/framework/db/cubrid/Schema.php b/framework/db/cubrid/Schema.php index 84885c6..f70063c 100644 --- a/framework/db/cubrid/Schema.php +++ b/framework/db/cubrid/Schema.php @@ -155,7 +155,7 @@ class Schema extends \yii\db\Schema } else { $table->foreignKeys[$key['FK_NAME']] = [ $key['PKTABLE_NAME'], - $key['FKCOLUMN_NAME'] => $key['PKCOLUMN_NAME'] + $key['FKCOLUMN_NAME'] => $key['PKCOLUMN_NAME'], ]; } } diff --git a/framework/db/oci/Schema.php b/framework/db/oci/Schema.php index c0d3227..f65e608 100644 --- a/framework/db/oci/Schema.php +++ b/framework/db/oci/Schema.php @@ -213,7 +213,7 @@ SQL; $c->name = $column['COLUMN_NAME']; $c->allowNull = $column['NULLABLE'] === 'Y'; $c->comment = $column['COLUMN_COMMENT'] === null ? '' : $column['COLUMN_COMMENT']; - $c->isPrimaryKey = false; + $c->isPrimaryKey = false; $this->extractColumnType($c, $column['DATA_TYPE'], $column['DATA_PRECISION'], $column['DATA_SCALE'], $column['DATA_LENGTH']); $this->extractColumnSize($c, $column['DATA_TYPE'], $column['DATA_PRECISION'], $column['DATA_SCALE'], $column['DATA_LENGTH']); diff --git a/framework/db/pgsql/Schema.php b/framework/db/pgsql/Schema.php index b83fdcd..1e7d3b5 100644 --- a/framework/db/pgsql/Schema.php +++ b/framework/db/pgsql/Schema.php @@ -105,7 +105,7 @@ class Schema extends \yii\db\Schema 'uuid' => self::TYPE_STRING, 'json' => self::TYPE_STRING, 'jsonb' => self::TYPE_STRING, - 'xml' => self::TYPE_STRING + 'xml' => self::TYPE_STRING, ]; diff --git a/framework/db/sqlite/ColumnSchemaBuilder.php b/framework/db/sqlite/ColumnSchemaBuilder.php index 1c2da63..8ba7456 100644 --- a/framework/db/sqlite/ColumnSchemaBuilder.php +++ b/framework/db/sqlite/ColumnSchemaBuilder.php @@ -9,7 +9,6 @@ namespace yii\db\sqlite; use yii\db\ColumnSchemaBuilder as AbstractColumnSchemaBuilder; - /** * ColumnSchemaBuilder is the schema builder for Sqlite databases. * diff --git a/framework/grid/DataColumn.php b/framework/grid/DataColumn.php index dd06283..e1402b2 100644 --- a/framework/grid/DataColumn.php +++ b/framework/grid/DataColumn.php @@ -144,11 +144,11 @@ class DataColumn extends Column /* @var $model Model */ $model = new $provider->query->modelClass; $label = $model->getAttributeLabel($this->attribute); - } else if ($provider instanceof ArrayDataProvider && $provider->modelClass !== null) { + } elseif ($provider instanceof ArrayDataProvider && $provider->modelClass !== null) { /* @var $model Model */ $model = new $provider->modelClass; $label = $model->getAttributeLabel($this->attribute); - } else if ($this->grid->filterModel !== null && $this->grid->filterModel instanceof Model) { + } elseif ($this->grid->filterModel !== null && $this->grid->filterModel instanceof Model) { $label = $this->grid->filterModel->getAttributeLabel($this->attribute); } else { $models = $provider->getModels(); diff --git a/framework/helpers/BaseInflector.php b/framework/helpers/BaseInflector.php index 5c513d3..1e19d38 100644 --- a/framework/helpers/BaseInflector.php +++ b/framework/helpers/BaseInflector.php @@ -362,7 +362,7 @@ class BaseInflector $label = trim(strtolower(str_replace([ '-', '_', - '.' + '.', ], ' ', preg_replace('/(?where([ 't1.id' => new Expression('[[t2.id]]'), 't1.category' => $category, - 't2.language' => $language + 't2.language' => $language, ]); $fallbackLanguage = substr($language, 0, 2); @@ -181,7 +181,7 @@ class DbMessageSource extends MessageSource ->where([ 't1.id' => new Expression('[[t2.id]]'), 't1.category' => $category, - 't2.language' => $fallbackLanguage + 't2.language' => $fallbackLanguage, ])->andWhere([ 'NOT IN', 't2.id', (new Query())->select('[[id]]')->from($this->messageTable)->where(['language' => $language]) ]); diff --git a/framework/i18n/Formatter.php b/framework/i18n/Formatter.php index 8de3745..82018c6 100644 --- a/framework/i18n/Formatter.php +++ b/framework/i18n/Formatter.php @@ -1146,21 +1146,33 @@ class Formatter extends Component if ($this->sizeFormatBase == 1024) { switch ($position) { - case 0: return Yii::t('yii', '{nFormatted} B', $params, $this->locale); - case 1: return Yii::t('yii', '{nFormatted} KiB', $params, $this->locale); - case 2: return Yii::t('yii', '{nFormatted} MiB', $params, $this->locale); - case 3: return Yii::t('yii', '{nFormatted} GiB', $params, $this->locale); - case 4: return Yii::t('yii', '{nFormatted} TiB', $params, $this->locale); - default: return Yii::t('yii', '{nFormatted} PiB', $params, $this->locale); + case 0: + return Yii::t('yii', '{nFormatted} B', $params, $this->locale); + case 1: + return Yii::t('yii', '{nFormatted} KiB', $params, $this->locale); + case 2: + return Yii::t('yii', '{nFormatted} MiB', $params, $this->locale); + case 3: + return Yii::t('yii', '{nFormatted} GiB', $params, $this->locale); + case 4: + return Yii::t('yii', '{nFormatted} TiB', $params, $this->locale); + default: + return Yii::t('yii', '{nFormatted} PiB', $params, $this->locale); } } else { switch ($position) { - case 0: return Yii::t('yii', '{nFormatted} B', $params, $this->locale); - case 1: return Yii::t('yii', '{nFormatted} KB', $params, $this->locale); - case 2: return Yii::t('yii', '{nFormatted} MB', $params, $this->locale); - case 3: return Yii::t('yii', '{nFormatted} GB', $params, $this->locale); - case 4: return Yii::t('yii', '{nFormatted} TB', $params, $this->locale); - default: return Yii::t('yii', '{nFormatted} PB', $params, $this->locale); + case 0: + return Yii::t('yii', '{nFormatted} B', $params, $this->locale); + case 1: + return Yii::t('yii', '{nFormatted} KB', $params, $this->locale); + case 2: + return Yii::t('yii', '{nFormatted} MB', $params, $this->locale); + case 3: + return Yii::t('yii', '{nFormatted} GB', $params, $this->locale); + case 4: + return Yii::t('yii', '{nFormatted} TB', $params, $this->locale); + default: + return Yii::t('yii', '{nFormatted} PB', $params, $this->locale); } } } @@ -1190,21 +1202,33 @@ class Formatter extends Component if ($this->sizeFormatBase == 1024) { switch ($position) { - case 0: return Yii::t('yii', '{nFormatted} {n, plural, =1{byte} other{bytes}}', $params, $this->locale); - case 1: return Yii::t('yii', '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}', $params, $this->locale); - case 2: return Yii::t('yii', '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}', $params, $this->locale); - case 3: return Yii::t('yii', '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}', $params, $this->locale); - case 4: return Yii::t('yii', '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}', $params, $this->locale); - default: return Yii::t('yii', '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}', $params, $this->locale); + case 0: + return Yii::t('yii', '{nFormatted} {n, plural, =1{byte} other{bytes}}', $params, $this->locale); + case 1: + return Yii::t('yii', '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}', $params, $this->locale); + case 2: + return Yii::t('yii', '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}', $params, $this->locale); + case 3: + return Yii::t('yii', '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}', $params, $this->locale); + case 4: + return Yii::t('yii', '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}', $params, $this->locale); + default: + return Yii::t('yii', '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}', $params, $this->locale); } } else { switch ($position) { - case 0: return Yii::t('yii', '{nFormatted} {n, plural, =1{byte} other{bytes}}', $params, $this->locale); - case 1: return Yii::t('yii', '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}', $params, $this->locale); - case 2: return Yii::t('yii', '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}', $params, $this->locale); - case 3: return Yii::t('yii', '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}', $params, $this->locale); - case 4: return Yii::t('yii', '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}', $params, $this->locale); - default: return Yii::t('yii', '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}', $params, $this->locale); + case 0: + return Yii::t('yii', '{nFormatted} {n, plural, =1{byte} other{bytes}}', $params, $this->locale); + case 1: + return Yii::t('yii', '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}', $params, $this->locale); + case 2: + return Yii::t('yii', '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}', $params, $this->locale); + case 3: + return Yii::t('yii', '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}', $params, $this->locale); + case 4: + return Yii::t('yii', '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}', $params, $this->locale); + default: + return Yii::t('yii', '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}', $params, $this->locale); } } } diff --git a/framework/messages/config.php b/framework/messages/config.php index fbd456f..4efd6f8 100644 --- a/framework/messages/config.php +++ b/framework/messages/config.php @@ -7,7 +7,7 @@ return [ 'messagePath' => __DIR__, // array, required, list of language codes that the extracted messages // should be translated to. For example, ['zh-CN', 'de']. - 'languages' => ['ar', 'az', 'bg', 'bs', 'ca', 'cs', 'da', 'de', 'el', 'es', 'et', 'fa', 'fi', 'fr', 'he', 'hr', 'hu', 'id', 'it', 'ja', 'ka', 'kk', 'ko', 'lt', 'lv', 'ms', 'nb-NO', 'nl', 'pl', 'pt', 'pt-BR', 'ro', 'ru', 'sk', 'sl', 'sr', 'sr-Latn', 'sv', 'th', 'tj', 'uk', 'vi', 'zh-CN','zh-TW'], + 'languages' => ['ar', 'az', 'bg', 'bs', 'ca', 'cs', 'da', 'de', 'el', 'es', 'et', 'fa', 'fi', 'fr', 'he', 'hr', 'hu', 'id', 'it', 'ja', 'ka', 'kk', 'ko', 'lt', 'lv', 'ms', 'nb-NO', 'nl', 'pl', 'pt', 'pt-BR', 'ro', 'ru', 'sk', 'sl', 'sr', 'sr-Latn', 'sv', 'th', 'tj', 'uk', 'vi', 'zh-CN', 'zh-TW'], // 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 // translated. You may use a string for single function name or an array for diff --git a/framework/test/ArrayFixture.php b/framework/test/ArrayFixture.php index 5109e19..09a6571 100644 --- a/framework/test/ArrayFixture.php +++ b/framework/test/ArrayFixture.php @@ -73,5 +73,4 @@ class ArrayFixture extends Fixture implements \IteratorAggregate, \ArrayAccess, parent::unload(); $this->data = []; } - } diff --git a/framework/validators/FileValidator.php b/framework/validators/FileValidator.php index a427570..5c6c9d1 100644 --- a/framework/validators/FileValidator.php +++ b/framework/validators/FileValidator.php @@ -236,8 +236,8 @@ class FileValidator extends Validator [ 'file' => $file->name, 'limit' => $this->getSizeLimit(), - 'formattedLimit' => Yii::$app->formatter->asShortSize($this->getSizeLimit()) - ] + 'formattedLimit' => Yii::$app->formatter->asShortSize($this->getSizeLimit()), + ], ]; } elseif ($this->minSize !== null && $file->size < $this->minSize) { return [ @@ -245,22 +245,21 @@ class FileValidator extends Validator [ 'file' => $file->name, 'limit' => $this->minSize, - 'formattedLimit' => Yii::$app->formatter->asShortSize($this->minSize) - ] + 'formattedLimit' => Yii::$app->formatter->asShortSize($this->minSize), + ], ]; } elseif (!empty($this->extensions) && !$this->validateExtension($file)) { return [$this->wrongExtension, ['file' => $file->name, 'extensions' => implode(', ', $this->extensions)]]; } elseif (!empty($this->mimeTypes) && !$this->validateMimeType($file)) { return [$this->wrongMimeType, ['file' => $file->name, 'mimeTypes' => implode(', ', $this->mimeTypes)]]; - } else { - return null; } + return null; case UPLOAD_ERR_INI_SIZE: case UPLOAD_ERR_FORM_SIZE: return [$this->tooBig, [ 'file' => $file->name, 'limit' => $this->getSizeLimit(), - 'formattedLimit' => Yii::$app->formatter->asShortSize($this->getSizeLimit()) + 'formattedLimit' => Yii::$app->formatter->asShortSize($this->getSizeLimit()), ]]; case UPLOAD_ERR_PARTIAL: Yii::warning('File was only partially uploaded: ' . $file->name, __METHOD__); diff --git a/framework/validators/ImageValidator.php b/framework/validators/ImageValidator.php index e2fd930..4e72f63 100644 --- a/framework/validators/ImageValidator.php +++ b/framework/validators/ImageValidator.php @@ -180,7 +180,7 @@ class ImageValidator extends FileValidator if ($this->notImage !== null) { $options['notImage'] = Yii::$app->getI18n()->format($this->notImage, [ - 'attribute' => $label + 'attribute' => $label, ], Yii::$app->language); } @@ -188,7 +188,7 @@ class ImageValidator extends FileValidator $options['minWidth'] = $this->minWidth; $options['underWidth'] = Yii::$app->getI18n()->format($this->underWidth, [ 'attribute' => $label, - 'limit' => $this->minWidth + 'limit' => $this->minWidth, ], Yii::$app->language); } @@ -196,7 +196,7 @@ class ImageValidator extends FileValidator $options['maxWidth'] = $this->maxWidth; $options['overWidth'] = Yii::$app->getI18n()->format($this->overWidth, [ 'attribute' => $label, - 'limit' => $this->maxWidth + 'limit' => $this->maxWidth, ], Yii::$app->language); } @@ -204,7 +204,7 @@ class ImageValidator extends FileValidator $options['minHeight'] = $this->minHeight; $options['underHeight'] = Yii::$app->getI18n()->format($this->underHeight, [ 'attribute' => $label, - 'limit' => $this->minHeight + 'limit' => $this->minHeight, ], Yii::$app->language); } @@ -212,7 +212,7 @@ class ImageValidator extends FileValidator $options['maxHeight'] = $this->maxHeight; $options['overHeight'] = Yii::$app->getI18n()->format($this->overHeight, [ 'attribute' => $label, - 'limit' => $this->maxHeight + 'limit' => $this->maxHeight, ], Yii::$app->language); } diff --git a/framework/validators/IpValidator.php b/framework/validators/IpValidator.php index e861565..455788a 100644 --- a/framework/validators/IpValidator.php +++ b/framework/validators/IpValidator.php @@ -455,7 +455,8 @@ class IpValidator extends Validator * - boolean: whether the string is negated * - string: the string without negation (when the negation were present) */ - private function parseNegatedRange ($string) { + private function parseNegatedRange($string) + { $isNegated = strpos($string, static::NEGATION_CHAR) === 0; return [$isNegated, $isNegated ? substr($string, strlen(static::NEGATION_CHAR)) : $string]; } @@ -470,7 +471,8 @@ class IpValidator extends Validator * @return array * @see networks */ - private function prepareRanges($ranges) { + private function prepareRanges($ranges) + { $result = []; foreach ($ranges as $string) { list($isRangeNegated, $range) = $this->parseNegatedRange($string); @@ -607,7 +609,7 @@ class IpValidator extends Validator 'ipv6' => (boolean)$this->ipv6, 'ipParsePattern' => new JsExpression(Html::escapeJsRegularExpression($this->getIpParsePattern())), 'negation' => $this->negation, - 'subnet' => $this->subnet + 'subnet' => $this->subnet, ]; if ($this->skipOnEmpty) { $options['skipOnEmpty'] = 1; diff --git a/framework/web/MultiFieldSession.php b/framework/web/MultiFieldSession.php index 03ed7c4..c43b80a 100644 --- a/framework/web/MultiFieldSession.php +++ b/framework/web/MultiFieldSession.php @@ -138,4 +138,4 @@ abstract class MultiFieldSession extends Session return isset($fields['data']) ? $fields['data'] : ''; } } -} \ No newline at end of file +} diff --git a/framework/web/Request.php b/framework/web/Request.php index 6398eb7..9ead092 100644 --- a/framework/web/Request.php +++ b/framework/web/Request.php @@ -1266,7 +1266,7 @@ class Request extends \yii\base\Request /** * Returns the token used to perform CSRF validation. * - * This token is generated in a way to prevent [BREACH attacks](http://breachattack.com/). It may be passed + * This token is generated in a way to prevent [BREACH attacks](http://breachattack.com/). It may be passed * along via a hidden field of an HTML form or an HTTP header value to support CSRF validation. * @param boolean $regenerate whether to regenerate CSRF token. When this parameter is true, each time * this method is called, a new CSRF token will be generated and persisted (in session or cookie). diff --git a/framework/web/UrlRule.php b/framework/web/UrlRule.php index 73aabce..5a78d1b 100644 --- a/framework/web/UrlRule.php +++ b/framework/web/UrlRule.php @@ -383,8 +383,8 @@ class UrlRule extends Object implements UrlRuleInterface * @see placeholders * @since 2.0.7 */ - protected function substitutePlaceholderNames (array $matches) - { + protected function substitutePlaceholderNames(array $matches) + { foreach ($this->placeholders as $placeholder => $name) { if (isset($matches[$placeholder])) { $matches[$name] = $matches[$placeholder]; diff --git a/framework/web/User.php b/framework/web/User.php index 36687e6..78ec442 100644 --- a/framework/web/User.php +++ b/framework/web/User.php @@ -544,7 +544,7 @@ class User extends Component } /** - * Determines if an identity cookie has a valid format and contains a valid auth key. + * Determines if an identity cookie has a valid format and contains a valid auth key. * This method is used when [[enableAutoLogin]] is true. * This method attempts to authenticate a user using the information in the identity cookie. * @return array|null Returns an array of 'identity' and 'duration' if valid, otherwise null. @@ -609,7 +609,7 @@ class User extends Component return; } - /* Ensure any existing identity cookies are removed. */ + /* Ensure any existing identity cookies are removed. */ if ($this->enableAutoLogin) { $this->removeIdentityCookie(); } From f538878142e9835b4723f4d8c7ec03813e7a1300 Mon Sep 17 00:00:00 2001 From: Nikola Kovacs Date: Thu, 26 May 2016 11:28:42 +0200 Subject: [PATCH 77/90] remove superfluous whitespace --- framework/i18n/PhpMessageSource.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/i18n/PhpMessageSource.php b/framework/i18n/PhpMessageSource.php index 1aa4a8a..0becc55 100644 --- a/framework/i18n/PhpMessageSource.php +++ b/framework/i18n/PhpMessageSource.php @@ -106,7 +106,7 @@ class PhpMessageSource extends MessageSource $fallbackMessages = $this->loadMessagesFromFile($fallbackMessageFile); if ( - $messages === null && $fallbackMessages === null + $messages === null && $fallbackMessages === null && $fallbackLanguage !== $this->sourceLanguage && $fallbackLanguage !== substr($this->sourceLanguage, 0, 2) ) { From 42db3687cb4fff2bef23284463185384cab13b7c Mon Sep 17 00:00:00 2001 From: Luciano Baraglia Date: Fri, 27 May 2016 02:15:39 -0300 Subject: [PATCH 78/90] More spanish docs [skip ci] (#11640) --- docs/guide-es/input-validation.md | 714 ++++++++++++++++++++++++++++++ docs/guide-es/intro-upgrade-from-v1.md | 44 +- docs/guide-es/tutorial-core-validators.md | 6 +- 3 files changed, 742 insertions(+), 22 deletions(-) create mode 100644 docs/guide-es/input-validation.md diff --git a/docs/guide-es/input-validation.md b/docs/guide-es/input-validation.md new file mode 100644 index 0000000..c2653aa --- /dev/null +++ b/docs/guide-es/input-validation.md @@ -0,0 +1,714 @@ +Validación de Entrada +===================== + +Como regla básica, nunca debes confiar en los datos recibidos de un usuario final y deberías validarlo siempre +antes de ponerlo en uso. + +Dado un [modelo](structure-models.md) poblado con entradas de usuarios, puedes validar esas entradas llamando al +método [[yii\base\Model::validate()]]. Dicho método devolverá un valor booleano indicando si la validación +tuvo éxito o no. En caso de que no, puedes obtener los mensajes de error de la propiedad [[yii\base\Model::errors]]. Por ejemplo, + +```php +$model = new \app\models\ContactForm(); + +// poblar los atributos del modelo desde la entrada del usuario +$model->load(\Yii::$app->request->post()); +// lo que es equivalente a: +// $model->attributes = \Yii::$app->request->post('ContactForm'); + +if ($model->validate()) { + // toda la entrada es válida +} else { + // la validación falló: $errors es un array que contienen los mensajes de error + $errors = $model->errors; +} +``` + + +## Declarar Reglas + +Para hacer que `validate()` realmente funcione, debes declarar reglas de validación para los atributos que planeas validar. +Esto debería hacerse sobrescribiendo el método [[yii\base\Model::rules()]]. El siguiente ejemplo muestra cómo +son declaradas las reglas de validación para el modelo `ContactForm`: + +```php +public function rules() +{ + return [ + // los atributos name, email, subject y body son obligatorios + [['name', 'email', 'subject', 'body'], 'required'], + + // el atributo email debe ser una dirección de email válida + ['email', 'email'], + ]; +} +``` + +El método [[yii\base\Model::rules()|rules()]] debe devolver un array de reglas, la cual cada una +tiene el siguiente formato: + +```php +[ + // requerido, especifica qué atributos deben ser validados por esta regla. + // Para un sólo atributo, puedes utilizar su nombre directamente + // sin tenerlo dentro de un array + ['attribute1', 'attribute2', ...], + + // requerido, especifica de qué tipo es la regla. + // Puede ser un nombre de clase, un alias de validador, o el nombre de un método de validación + 'validator', + + // opcional, especifica en qué escenario/s esta regla debe aplicarse + // si no se especifica, significa que la regla se aplica en todos los escenarios + // Puedes también configurar la opción "except" en caso de que quieras aplicar la regla + // en todos los escenarios salvo los listados + 'on' => ['scenario1', 'scenario2', ...], + + // opcional, especifica atributos adicionales para el objeto validador + 'property1' => 'value1', 'property2' => 'value2', ... +] +``` + +Por cada regla debes especificar al menos a cuáles atributos aplica la regla y cuál es el tipo de la regla. +Puedes especificar el tipo de regla de las siguientes maneras: + +* el alias de un validador propio del framework, tal como `required`, `in`, `date`, etc. Por favor consulta + [Validadores del núcleo](tutorial-core-validators.md) para la lista completa de todos los validadores incluidos. +* el nombre de un método de validación en la clase del modelo, o una función anónima. Consulta la + subsección [Validadores en Línea](#inline-validators) para más detalles. +* el nombre completo de una clase de validador. Por favor consulta la subsección [Validadores Independientes](#standalone-validators) + para más detalles. + +Una regla puede ser utilizada para validar uno o varios atributos, y un atributo puede ser validado por una o varias reglas. +Una regla puede ser aplicada en ciertos [escenarios](structure-models.md#scenarios) con tan sólo especificando la opción `on`. +Si no especificas una opción `on`, significa que la regla se aplicará en todos los escenarios. + +Cuando el método `validate()` es llamado, este sigue los siguientes pasos para realiza la validación: + +1. Determina cuáles atributos deberían ser validados obteniendo la lista de atributos de [[yii\base\Model::scenarios()]] + utilizando el [[yii\base\Model::scenario|scenario]] actual. Estos atributos son llamados *atributos activos*. +2. Determina cuáles reglas de validación deberían ser validados obteniendo la lista de reglas de [[yii\base\Model::rules()]] + utilizando el [[yii\base\Model::scenario|scenario]] actual. Estas reglas son llamadas *reglas activas*. +3. Utiliza cada regla activa para validar cada atributo activo que esté asociado a la regla. + Las reglas de validación son evaluadas en el orden en que están listadas. + +De acuerdo a los pasos de validación mostrados arriba, un atributo será validado si y sólo si +es un atributo activo declarado en `scenarios()` y está asociado a una o varias reglas activas +declaradas en `rules()`. + +> Note: Es práctico darle nombre a las reglas, por ej: +> ```php +> public function rules() +> { +> return [ +> // ... +> 'password' => [['password'], 'string', 'max' => 60], +> ]; +> } +> ``` +> +> Puedes utilizarlas en una subclase del modelo: +> +> ```php +> public function rules() +> { +> $rules = parent::rules(); +> unset($rules['password']); +> return $rules; +> } + + +### Personalizar Mensajes de Error + +La mayoría de los validadores tienen mensajes de error por defecto que serán agregados al modelo siendo validado cuando sus atributos +fallan la validación. Por ejemplo, el validador [[yii\validators\RequiredValidator|required]] agregará +el mensaje "Username no puede estar vacío." a un modelo cuando falla la validación del atributo `username` al utilizar esta regla. + +Puedes especificar el mensaje de error de una regla especificado la propiedad `message` al declarar la regla, +como a continuación, + +```php +public function rules() +{ + return [ + ['username', 'required', 'message' => 'Por favor escoge un nombre de usuario.'], + ]; +} +``` + +Algunos validadores pueden soportar mensajes de error adicionales para describir más precisamente las causas +del fallo de validación. Por ejemplo, el validador [[yii\validators\NumberValidator|number]] soporta +[[yii\validators\NumberValidator::tooBig|tooBig]] y [[yii\validators\NumberValidator::tooSmall|tooSmall]] +para describir si el fallo de validación es porque el valor siendo validado es demasiado grande o demasiado pequeño, respectivamente. +Puedes configurar estos mensajes de error tal como cualquier otroa propiedad del validador en una regla de validación. + + +### Eventos de Validación + +Cuando el método [[yii\base\Model::validate()]] es llamado, este llamará a dos métodos que puedes sobrescribir para personalizar +el proceso de validación: + +* [[yii\base\Model::beforeValidate()]]: la implementación por defecto lanzará un evento [[yii\base\Model::EVENT_BEFORE_VALIDATE]]. + Puedes tanto sobrescribir este método o responder a este evento para realizar algún trabajo de pre procesamiento + (por ej. normalizar datos de entrada) antes de que ocurra la validación en sí. El método debe devolver un booleano que indique + si la validación debe continuar o no. +* [[yii\base\Model::afterValidate()]]: la implementación por defecto lanzará un evento [[yii\base\Model::EVENT_AFTER_VALIDATE]]. + uedes tanto sobrescribir este método o responder a este evento para realizar algún trabajo de post procesamiento después + de completada la validación. + + +### Validación Condicional + +Para validar atributos sólo en determinadas condiciones, por ej. la validación de un atributo depende +del valor de otro atributo puedes utilizar la propiedad [[yii\validators\Validator::when|when]] +para definir la condición. Por ejemplo, + +```php + ['state', 'required', 'when' => function($model) { + return $model->country == 'USA'; + }] +``` + +La propiedad [[yii\validators\Validator::when|when]] toma un método invocable PHP con la siguiente firma: + +```php +/** + * @param Model $model el modelo siendo validado + * @param string $attribute al atributo siendo validado + * @return boolean si la regla debe ser aplicada o no + */ +function ($model, $attribute) +``` + +Si también necesitas soportar validación condicional del lado del cliente, debes configurar +la propiedad [[yii\validators\Validator::whenClient|whenClient]], que toma un string que representa una función JavaScript +cuyo valor de retorno determina si debe aplicarse la regla o no. Por ejemplo, + +```php + ['state', 'required', 'when' => function ($model) { + return $model->country == 'USA'; + }, 'whenClient' => "function (attribute, value) { + return $('#country').val() == 'USA'; + }"] +``` + + +### Filtro de Datos + +La entrada del usuario a menudo debe ser filtrada o pre procesada. Por ejemplo, podrías querer eliminar los espacions alrededor +de la entrada `username`. Puedes utilizar reglas de validación para lograrlo. + +Los siguientes ejemplos muestran cómo eliminar esos espacios en la entrada y cómo transformar entradas vacías en null utilizando +los validadores del framework [trim](tutorial-core-validators.md#trim) y [default](tutorial-core-validators.md#default): + +```php +return [ + [['username', 'email'], 'trim'], + [['username', 'email'], 'default'], +]; +``` + +También puedes utilizar el validador más general [filter](tutorial-core-validators.md#filter) para realizar filtros +de datos más complejos. + +Como puedes ver, estas reglas de validación no validan la entrada realmente. En cambio, procesan los valores +y los guardan en el atributo siendo validado. + + +### Manejando Entradas Vacías + +Cuando los datos de entrada son enviados desde formularios HTML, a menudo necesitas asignar algunos valores por defecto a las entradas +si estas están vacías. Puedes hacerlo utilizando el validador [default](tutorial-core-validators.md#default). Por ejemplo, + +```php +return [ + // convierte "username" y "email" en null si estos están vacíos + [['username', 'email'], 'default'], + + // convierte "level" a 1 si está vacío + ['level', 'default', 'value' => 1], +]; +``` + +Por defecto, una entrada se considera vacía si su valor es un string vacío, un array vacío o null. +Puedes personalizar la lógica de detección de valores vacíos configurando la propiedad [[yii\validators\Validator::isEmpty]] +con una función PHP invocable. Por ejemplo, + +```php + ['agree', 'required', 'isEmpty' => function ($value) { + return empty($value); + }] +``` + +> Note: La mayoría de los validadores no manejan entradas vacías si su propiedad [[yii\validators\Validator::skipOnEmpty]] toma + el valor por defecto true. Estas serán simplemente salteadas durante la validación si sus atributos asociados reciben una entrada vacía. + Entre los [validadores del framework](tutorial-core-validators.md), sólo `captcha`, `default`, `filter`, + `required`, y `trim` manejarán entradas vacías. + + +## Validación Ad Hoc + +A veces necesitas realizar *validación ad hoc* para valores que no están ligados a ningún modelo. + +Si sólo necesitas realizar un tipo de validación (por ej: validar direcciones de email), podrías llamar +al método [[yii\validators\Validator::validate()|validate()]] de los validadores deseados, como a continuación: + +```php +$email = 'test@example.com'; +$validator = new yii\validators\EmailValidator(); + +if ($validator->validate($email, $error)) { + echo 'Email válido.'; +} else { + echo $error; +} +``` + +> Note: No todos los validadores soportan este tipo de validación. Un ejemplo es el validador del framework [unique](tutorial-core-validators.md#unique), + que está diseñado para trabajar sólo con un modelo. + +Si necesitas realizar varias validaciones contro varios valores, puedes utilizar [[yii\base\DynamicModel]], +que soporta declarar tanto los atributos como las reglas sobre la marcha. Su uso es como a continuación: + +```php +public function actionSearch($name, $email) +{ + $model = DynamicModel::validateData(compact('name', 'email'), [ + [['name', 'email'], 'string', 'max' => 128], + ['email', 'email'], + ]); + + if ($model->hasErrors()) { + // validación fallida + } else { + // validación exitosa + } +} +``` + +El método [[yii\base\DynamicModel::validateData()]] crea una instancia de `DynamicModel`, define los atributos +utilizando los datos provistos (`name` e `email` en este ejemplo), y entonces llama a [[yii\base\Model::validate()]] +con las reglas provistas. + +Alternativamente, puedes utilizar la sintaxis más "clásica" para realizar la validación ad hoc: + +```php +public function actionSearch($name, $email) +{ + $model = new DynamicModel(compact('name', 'email')); + $model->addRule(['name', 'email'], 'string', ['max' => 128]) + ->addRule('email', 'email') + ->validate(); + + if ($model->hasErrors()) { + // validación fallida + } else { + // validación exitosa + } +} +``` + +Después de la validación, puedes verificar si la validación tuvo éxito o no llamando al +método [[yii\base\DynamicModel::hasErrors()|hasErrors()]], obteniendo así los errores de validación de la +propiedad [[yii\base\DynamicModel::errors|errors]], como haces con un modelo normal. +Puedes también acceder a los atributos dinámicos definidos a través de la instancia del modelo, por ej., +`$model->name` y `$model->email`. + + +## Crear Validadores + +Además de los [validadores del framework](tutorial-core-validators.md) incluidos en los lanzamientos de Yii, puedes también +crear tus propios validadores. Puedes crear validadores en línea o validadores independientes. + + +### Validadores en Línea + +Un validador en línea es uno definido en términos del método de un modelo o una función anónima. La firma +del método/función es: + +```php +/** + * @param string $attribute el atributo siendo validado actualmente + * @param mixed $params el valor de los "parámetros" dados en la regla + */ +function ($attribute, $params) +``` + +Si falla la validación de un atributo, el método/función debería llamar a [[yii\base\Model::addError()]] para guardar +el mensaje de error en el modelo de manera que pueda ser recuperado más tarde y presentado a los usuarios finales. + +Debajo hay algunos ejemplos: + +```php +use yii\base\Model; + +class MyForm extends Model +{ + public $country; + public $token; + + public function rules() + { + return [ + // un validador en línea definido como el método del modelo validateCountry() + ['country', 'validateCountry'], + + // un validador en línea definido como una función anónima + ['token', function ($attribute, $params) { + if (!ctype_alnum($this->$attribute)) { + $this->addError($attribute, 'El token debe contener letras y dígitos.'); + } + }], + ]; + } + + public function validateCountry($attribute, $params) + { + if (!in_array($this->$attribute, ['USA', 'Web'])) { + $this->addError($attribute, 'El país debe ser "USA" o "Web".'); + } + } +} +``` + +> Note: Por defecto, los validadores en línea no serán aplicados si sus atributos asociados reciben entradas vacías + o si alguna de sus reglas de validación ya falló. Si quieres asegurarte de que una regla siempre sea aplicada, + puedes configurar las reglas [[yii\validators\Validator::skipOnEmpty|skipOnEmpty]] y/o [[yii\validators\Validator::skipOnError|skipOnError]] + como false en las declaraciones de las reglas. Por ejemplo: +> +> ```php +> [ +> ['country', 'validateCountry', 'skipOnEmpty' => false, 'skipOnError' => false], +> ] +> ``` + + +### Validadores Independientes + +Un validador independiente es una clase que extiende de [[yii\validators\Validator]] o sus sub clases. Puedes implementar +su lógica de validación sobrescribiendo el método [[yii\validators\Validator::validateAttribute()]]. Si falla la validación +de un atributo, llama a [[yii\base\Model::addError()]] para guardar el mensaje de error en el modelo, tal como haces +con los [validadores en línea](#inline-validators). + + +Por ejemplo, el validador en línea de arriba podría ser movida a una nueva clase [[components/validators/CountryValidator]]. + +```php +namespace app\components; + +use yii\validators\Validator; + +class CountryValidator extends Validator +{ + public function validateAttribute($model, $attribute) + { + if (!in_array($model->$attribute, ['USA', 'Web'])) { + $this->addError($model, $attribute, 'El país debe ser "USA" o "Web".'); + } + } +} +``` + +Si quieres que tu validador soporte la validación de un valor sin modelo, deberías también sobrescribir +el método[[yii\validators\Validator::validate()]]. Puedes también sobrescribir [[yii\validators\Validator::validateValue()]] +en vez de `validateAttribute()` y `validate()` porque por defecto los últimos dos métodos son implementados +llamando a `validateValue()`. + +Debajo hay un ejemplo de cómo podrías utilizar la clase del validador de arriba dentro de tu modelo. + +```php +namespace app\models; + +use Yii; +use yii\base\Model; +use app\components\validators\CountryValidator; + +class EntryForm extends Model +{ + public $name; + public $email; + public $country; + + public function rules() + { + return [ + [['name', 'email'], 'required'], + ['country', CountryValidator::className()], + ['email', 'email'], + ]; + } +} +``` + + +## Validación del Lado del Cliente + +La validación del lado del cliente basada en JavaScript es deseable cuando la entrada del usuario proviene de formularios HTML, dado que +permite a los usuarios encontrar errores más rápido y por lo tanto provee una mejor experiencia. Puedes utilizar o implementar +un validador que soporte validación del lado del cliente *en adición a* validación del lado del servidor. + +> Info: Si bien la validación del lado del cliente es deseable, no es una necesidad. Su principal propósito es proveer al usuario una mejor + experiencia. Al igual que datos de entrada que vienen del los usuarios finales, nunca deberías confiar en la validación del lado del cliente. Por esta razón, + deberías realizar siempre la validación del lado del servidor llamando a [[yii\base\Model::validate()]], como + se describió en las subsecciones previas. + + +### Utilizar Validación del Lado del Cliente + +Varios [validadores del framework](tutorial-core-validators.md) incluyen validación del lado del cliente. Todo lo que necesitas hacer +es solamente utilizar [[yii\widgets\ActiveForm]] para construir tus formularios HTML. Por ejemplo, `LoginForm` mostrado abajo declara dos +reglas: una utiliza el validador del framework [required](tutorial-core-validators.md#required), el cual es soportado tanto en +lado del cliente como del servidor; y el otro usa el validador en línea `validatePassword`, que es sólo soportado de lado +del servidor. + +```php +namespace app\models; + +use yii\base\Model; +use app\models\User; + +class LoginForm extends Model +{ + public $username; + public $password; + + public function rules() + { + return [ + // username y password son ambos requeridos + [['username', 'password'], 'required'], + + // password es validado por validatePassword() + ['password', 'validatePassword'], + ]; + } + + public function validatePassword() + { + $user = User::findByUsername($this->username); + + if (!$user || !$user->validatePassword($this->password)) { + $this->addError('password', 'Username o password incorrecto.'); + } + } +} +``` + +El formulario HTML creado en el siguiente código contiene dos campos de entrada: `username` y `password`. +Si envias el formulario sin escribir nada, encontrarás que los mensajes de error requiriendo que +escribas algo aparecen sin que haya comunicación alguna con el servidor. + +```php + + field($model, 'username') ?> + field($model, 'password')->passwordInput() ?> + + +``` + +Detrás de escena, [[yii\widgets\ActiveForm]] leerá las reglas de validación declaradas en el modelo +y generará el código JavaScript apropiado para los validadores que soportan validación del lado del cliente. Cuando un usuario +cambia el valor de un campo o envia el formulario, se lanzará la validación JavaScript del lado del cliente. + +Si quieres deshabilitar la validación del lado del cliente completamente, puedes configurar +la propiedad [[yii\widgets\ActiveForm::enableClientValidation]] como false. También puedes deshabilitar la validación +del lado del cliente de campos individuales configurando su propiedad [[yii\widgets\ActiveField::enableClientValidation]] +como false. Cuando `enableClientValidation` es configurado tanto a nivel de campo como a nivel de formulario, +tendrá prioridad la primera. + +### Implementar Validación del Lado del Cliente + + +Para crear validadores que soportan validación del lado del cliente, debes implementar +el método [[yii\validators\Validator::clientValidateAttribute()]], que devuelve una pieza de código JavaScript +que realiza dicha validación. Dentro del código JavaScript, puedes utilizar las siguientes +variables predefinidas: + +- `attribute`: el nombre del atributo siendo validado. +- `value`: el valor siendo validado. +- `messages`: un array utilizado para contener los mensajes de error de validación para el atributo. +- `deferred`: un array con objetos diferidos puede ser insertado (explicado en la subsección siguiente). + +En el siguiente ejemplo, creamos un `StatusValidator` que valida si la entrada es un status válido +contra datos de status existentes. El validador soporta tato tanto validación del lado del servidor como del lado del cliente. + +```php +namespace app\components; + +use yii\validators\Validator; +use app\models\Status; + +class StatusValidator extends Validator +{ + public function init() + { + parent::init(); + $this->message = 'Entrada de Status Inválida.'; + } + + public function validateAttribute($model, $attribute) + { + $value = $model->$attribute; + if (!Status::find()->where(['id' => $value])->exists()) { + $model->addError($attribute, $this->message); + } + } + + public function clientValidateAttribute($model, $attribute, $view) + { + $statuses = json_encode(Status::find()->select('id')->asArray()->column()); + $message = json_encode($this->message, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + return << Tip: El código de arriba muestra principalmente cómo soportar validación del lado del cliente. En la práctica, +> puedes utilizar el validador del framework [in](tutorial-core-validators.md#in) para alcanzar el mismo objetivo. Puedes +> escribir la regla de validación como a como a continuación: +> +> ```php +> [ +> ['status', 'in', 'range' => Status::find()->select('id')->asArray()->column()], +> ] +> ``` + +> Tip: Si necesitas trabajar con validación del lado del cliente manualmente, por ejemplo, agregar campos dinámicamente o realizar alguna lógica de UI, +> consulta [Trabajar con ActiveForm vía JavaScript](https://github.com/samdark/yii2-cookbook/blob/master/book/forms-activeform-js.md) +> en el Yii 2.0 Cookbook. + +### Validación Diferida + +Si necesitas realizar validación del lado del cliente asincrónica, puedes crear [Objetos Diferidos](http://api.jquery.com/category/deferred-object/). +Por ejemplo, para realizar validación AJAX personalizada, puedes utilizar el siguiente código: + +```php +public function clientValidateAttribute($model, $attribute, $view) +{ + return << 150) { + messages.push('Imagen demasiado ancha!!'); + } + def.resolve(); + } + var reader = new FileReader(); + reader.onloadend = function() { + img.src = reader.result; + } + reader.readAsDataURL(file); + + deferred.push(def); +JS; +} +``` + +> Note: El método `resolve()` debe ser llamado después de que el atributo ha sido validado. De otra manera la validación + principal del formulario no será completada. + +Por simplicidad, el array `deferred` está equipado con un método de atajo, `add()`, que automáticamente crea un +Objeto Diferido y lo agrega al array `deferred`. Utilizando este método, puedes simplificar el ejemplo de arriba de esta manera, + +```php +public function clientValidateAttribute($model, $attribute, $view) +{ + return << 150) { + messages.push('Imagen demasiado ancha!!'); + } + def.resolve(); + } + var reader = new FileReader(); + reader.onloadend = function() { + img.src = reader.result; + } + reader.readAsDataURL(file); + }); +JS; +} +``` + + +### Validación AJAX + +Algunas validaciones sólo pueden realizarse del lado del servidor, debido a que sólo el servidor tiene la información necesaria. +Por ejemplo, para validar si un nombre de usuario es único o no, es necesario revisar la tabla de usuarios del lado del servidor. +Puedes utilizar validación basada en AJAX en este caso. Esta lanzará una petición AJAX de fondo para validar +la entrada mientras se mantiene la misma experiencia de usuario como en una validación del lado del cliente regular. + +Para habilitar la validación AJAX individualmente un campo de entrada, configura la propiedad [[yii\widgets\ActiveField::enableAjaxValidation|enableAjaxValidation]] +de ese campo como true y especifica un único `id` de formulario: + +```php +use yii\widgets\ActiveForm; + +$form = ActiveForm::begin([ + 'id' => 'registration-form', +]); + +echo $form->field($model, 'username', ['enableAjaxValidation' => true]); + +// ... + +ActiveForm::end(); +``` + +Para habiliar la validación AJAX en el formulario entero, configura [[yii\widgets\ActiveForm::enableAjaxValidation|enableAjaxValidation]] +como true a nivel del formulario: + +```php +$form = ActiveForm::begin([ + 'id' => 'contact-form', + 'enableAjaxValidation' => true, +]); +``` + +> Note: Cuando la propiedad `enableAjaxValidation` es configurada tanto a nivel de campo como a nivel de formulario, + la primera tendrá prioridad. + +Necesitas también preparar el servidor para que pueda manejar las peticiones AJAX. +Esto puede alcanzarse con una porción de código como la siguiente en las acciones del controlador: + +```php +if (Yii::$app->request->isAjax && $model->load(Yii::$app->request->post())) { + Yii::$app->response->format = Response::FORMAT_JSON; + return ActiveForm::validate($model); +} +``` + +El código de arriba chequeará si la petición actual es AJAX o no. Si lo es, responderá +esta petición ejecutando la validación y devolviendo los errores en formato JSON. + +> Info: Puedes también utilizar [Validación Diferida](#deferred-validation) para realizar validación AJAX. + De todos modos, la característica de validación AJAX descrita aquí es más sistemática y requiere menos esfuerzo de escritura de código. + +Cuando tanto `enableClientValidation` como `enableAjaxValidation` son definidas como true, la petición de validación AJAX será lanzada +sólo después de una validación del lado del cliente exitosa. diff --git a/docs/guide-es/intro-upgrade-from-v1.md b/docs/guide-es/intro-upgrade-from-v1.md index bcb4df3..db0124c 100644 --- a/docs/guide-es/intro-upgrade-from-v1.md +++ b/docs/guide-es/intro-upgrade-from-v1.md @@ -1,5 +1,5 @@ -Actualizando desde Yii 1.1 -========================== +Actualizar desde Yii 1.1 +======================== Existen muchas diferencias entre las versiones 1.1 y 2.0 de Yii ya que el framework fue completamente reescrito en su segunda versión. @@ -18,7 +18,8 @@ Instalación Yii 2.0 adopta íntegramente [Composer](https://getcomposer.org/), el administrador de paquetes de facto de PHP. Tanto la instalación del núcleo del framework como las extensiones se manejan a través de Composer. Por favor consulta la sección [Comenzando con la Aplicación Básica](start-installation.md) para aprender a instalar Yii 2.0. Si quieres crear extensiones -o transformar extensiones de Yii 1.1 para que sean compatibles con Yii 2.0, consulta la sección [Creando Extensiones](structure-extensions.md#creating-extensions) de la guía. +o transformar extensiones de Yii 1.1 para que sean compatibles con Yii 2.0, consulta +la sección [Creando Extensiones](structure-extensions.md#creating-extensions) de la guía. Requerimientos de PHP @@ -107,7 +108,7 @@ $object = Yii::createObject([ ], [$param1, $param2]); ``` -Se puede encontrar más detalles acerca del tema en la sección [Configuración de objetos](concept-configurations.md). +Se puede encontrar más detalles acerca del tema en la sección [Configuración](concept-configurations.md). Eventos @@ -142,7 +143,7 @@ están soportados en la mayor parte del núcleo. Por ejemplo, [[yii\caching\File una ruta de directorios normal como un alias. Un alias está estrechamente relacionado con un namespace de la clase. Se recomienda definir un alias -por cada namespace raíz, y así poder utilizar el autolader de Yii sin otra configuración. +por cada namespace raíz, y así poder utilizar el autoloader de Yii sin otra configuración. Por ejemplo, debido a que `@yii` se refiere al directorio de instalación, una clase como `yii\web\Request` puede ser auto-cargada. Si estás utilizando una librería de terceros, como Zend Framework, puedes definir un alias `@Zend` que se refiera al directorio de instalación @@ -155,15 +156,12 @@ Vistas ------ El cambio más significativo con respecto a las vistas en Yii 2 es que la variable especial `$this` dentro de una vista -ya no se refiere al controlador o widget actual. -En vez de eso, `$this` ahora se refiere al objeto de la *vista*, un concepto nuevo introducido en Yii 2.0. -El objeto *vista* es del tipo [[yii\web\View]], que representa la parte de las vistas en el patrón MVC. Si -quieres acceder al controlador o al widget correspondiente desde la propia vista, -puedes utilizar `$this->context`. +ya no se refiere al controlador o widget actual. En vez de eso, `$this` ahora se refiere al objeto de la *vista*, un concepto nuevo +introducido en Yii 2.0. El objeto *vista* es del tipo [[yii\web\View]], que representa la parte de las vistas +en el patrón MVC. Si quieres acceder al controlador o al widget correspondiente desde la propia vista, puedes utilizar `$this->context`. -Para renderizar una vista parcial (partial) dentro de otra vista, se utiliza `$this->render()`, no `$this->renderPartial()`. -La llamada a `render` además tiene que ser mostrada explícitamente a través de `echo`, ya que el método `render()` -devuelve el resultado de la renderización en vez de mostrarlo directamente. Por ejemplo: +Para renderizar una vista parcial (partial) dentro de otra vista, se utiliza `$this->render()`, no `$this->renderPartial()`. La llamada a `render` además tiene que ser mostrada explícitamente a través de `echo`, +ya que el método `render()` devuelve el resultado de la renderización en vez de mostrarlo directamente. Por ejemplo: ```php echo $this->render('_item', ['item' => $item]); @@ -172,7 +170,8 @@ echo $this->render('_item', ['item' => $item]); Además de utilizar PHP como el lenguaje principal de plantillas (templates), Yii 2.0 está también equipado con soporte oficial de otros dos motores de plantillas populares: Smarty y Twig. El motor de plantillas de Prado ya no está soportado. Para utilizar esos motores, necesitas configurar el componente `view` de la aplicación, definiendo la propiedad [[yii\base\View::$renderers|View::$renderers]]. -Por favor consulta la sección [Motores de Plantillas](tutorial-template-engines.md) para más detalles. +Por favor consulta la sección [Motores de Plantillas](tutorial-template-engines.md) +para más detalles. Modelos @@ -431,7 +430,7 @@ class Customer extends \yii\db\ActiveRecord ``` Ahora puedes utilizar `$customer->orders` para acceder a las órdenes de la tabla relacionada. También puedes utilizar el siguiente -código para realizar una consulta relacional 'al-vuelo' con una condición personalizada: +código para realizar una consulta relacional 'sobre la marcha' con una condición personalizada: ```php $orders = $customer->getOrders()->andWhere('status=1')->all(); @@ -504,9 +503,10 @@ User e IdentityInterface La clase `CWebUser` de 1.1 es reemplazada por [[yii\web\User]], y la clase `CUserIdentity` ha dejado de existir. En cambio, ahora debes implementar [[yii\web\IdentityInterface]] el cual es mucho más directo de usar. -La plantilla de Aplicación Avanzada provee un ejemplo así. +El template de proyecto avanzado provee un ejemplo así. + +Consulta las secciones [Autenticación](security-authentication.md), [Autorización](security-authorization.md), y [Template de Proyecto Avanzado](https://github.com/yiisoft/yii2-app-advanced/blob/master/docs/guide-es/README.md) para más detalles. -Consulta las secciones [Autenticación](security-authentication.md), [Autorización](security-authorization.md), y [Plantilla de Aplicación Avanzada](tutorial-advanced-app.md) para más detalles. Manejo de URLs @@ -526,8 +526,14 @@ En 1.1, tendrías que haber creado dos reglas diferentes para obtener el mismo r Por favor, consulta la sección [Documentación del Manejo de URLs](runtime-routing.md) para más detalles. -Utilizando Yii 1.1 y 2.x juntos -------------------------------- +Un cambio importante en la convención de nombres para rutas es que los nombres en CamelCase de controladores +y acciones ahora son convertidos a minúsculas y cada palabra separada por un guión, por ejemplo el id del controlador +`CamelCaseController` será `camel-case`. +Consulta la sección acerca de [IDs de controladores](structure-controllers.md#controller-ids) y [IDs de acciones](structure-controllers.md#action-ids) para más detalles. + + +Utilizar Yii 1.1 y 2.x juntos +----------------------------- Si tienes código en Yii 1.1 que quisieras utilizar junto con Yii 2.0, por favor consulta la sección [Utilizando Yii 1.1 y 2.0 juntos](tutorial-yii-integration.md). diff --git a/docs/guide-es/tutorial-core-validators.md b/docs/guide-es/tutorial-core-validators.md index 32777a4..d56325b 100644 --- a/docs/guide-es/tutorial-core-validators.md +++ b/docs/guide-es/tutorial-core-validators.md @@ -1,7 +1,7 @@ -Validadores del núcleo -====================== +Validadores del framework +========================= -Yii provee en el núcleo un conjunto de validadores de uso común, que se pueden encontrar principalmente bajo el espacio de nombres (namespace) `yii\validators`. +Yii provee en su núcleo un conjunto de validadores de uso común, que se pueden encontrar principalmente bajo el espacio de nombres (namespace) `yii\validators`. En vez de utilizar interminables nombres de clases para los validadores, puedes usar *alias* para especificar el uso de esos validadores del núcleo. Por ejemplo, puedes usar el alias `required` para referirte a la clase [[yii\validators\RequiredValidator]] : ```php From 0a7514eac808f2f258b9f11022b0a6b87e95c42c Mon Sep 17 00:00:00 2001 From: Luciano Baraglia Date: Mon, 30 May 2016 11:09:45 -0300 Subject: [PATCH 79/90] Spanish docs [skip ci] (#11649) --- docs/guide-es/output-pagination.md | 72 ++++++++++++++++++++++++++++++++++++ docs/guide-es/security-passwords.md | 31 ++++++++++++++++ docs/guide-es/start-looking-ahead.md | 53 +++++++++++++------------- docs/guide-es/start-workflow.md | 18 ++++++--- 4 files changed, 143 insertions(+), 31 deletions(-) create mode 100644 docs/guide-es/output-pagination.md create mode 100644 docs/guide-es/security-passwords.md diff --git a/docs/guide-es/output-pagination.md b/docs/guide-es/output-pagination.md new file mode 100644 index 0000000..9f70cb0 --- /dev/null +++ b/docs/guide-es/output-pagination.md @@ -0,0 +1,72 @@ +Paginación +========== + +Cuando hay muchos datos a mostrar en una sola página, una estrategia común es mostrarlos en varias +páginas y en cada una de ellas mostrar sólo una pequeña porción de datos. Esta estrategia es conocida como *paginación*. + +Yii utiliza el objeto [[yii\data\Pagination]] para representar la información acerca del esquema de paginación. En particular, + +* [[yii\data\Pagination::$totalCount|cuenta total]] especifica el número total de ítems de datos. Ten en cuenta que + este es normalmente un número mucho mayor que el número de ítems necesarios a mostrar en una simple página. +* [[yii\data\Pagination::$pageSize|tamaño de página]] especifica cuántos ítems de datos contiene cada página. El valor + por defecto es 20. +* [[yii\data\Pagination::$page|página actual]] da el número de la página actual (comenzando desde 0). El valor + por defecto es 0, lo que sería la primera página. + +Con un objeto [[yii\data\Pagination]] totalmente especificado, puedes obtener y mostrar datos en partes. Por ejemplo, +si estás recuperando datos de una base de datos, puedes especificar las cláusulas `OFFSET` y `LIMIT` de la consulta a la BD +correspondientes a los valores provistos por la paginación. A continuación hay un ejemplo, + +```php +use yii\data\Pagination; + +// construye una consulta a la BD para obtener todos los artículos con status = 1 +$query = Article::find()->where(['status' => 1]); + +// obtiene el número total de artículos (pero no recupera los datos de los artículos todavía) +$count = $query->count(); + +// crea un objeto paginación con dicho total +$pagination = new Pagination(['totalCount' => $count]); + +// limita la consulta utilizando la paginación y recupera los artículos +$articles = $query->offset($pagination->offset) + ->limit($pagination->limit) + ->all(); +``` + +¿Qué página de artículos devolverá el ejemplo de arriba? Depende de si se le es pasado un parámetro llamado `page`. +Por defecto, la paginación intentará definir la [[yii\data\Pagination::$page|página actual]] con +el valor del parámetro `page`. Si el parámetro no es provisto, entonces tomará por defecto el valor 0. + +Para facilitar la construcción de elementos UI que soporten paginación, Yii provee el widget [[yii\widgets\LinkPager]], +que muestra una lista de botones de navegación que el usuario puede presionar para indicar qué página de datos debería mostrarse. +El widget toma un objeto de paginación y tal manera conoce cuál es la página actual y cuántos botones +debe mostrar. Por ejemplo, + +```php +use yii\widgets\LinkPager; + +echo LinkPager::widget([ + 'pagination' => $pagination, +]); +``` + +Si quieres construir los elementos de UI manualmente, puedes utilizar [[yii\data\Pagination::createUrl()]] para generar URLs que +dirigirán a las distintas páginas. El método requiere un parámetro de página y generará una URL apropiadamente formada +contieniendo el parámetro de página. Por ejemplo, + +```php +// especifica la ruta que la URL generada debería utilizar +// Si no lo especificas, se utilizará la ruta de la petición actual +$pagination->route = 'article/index'; + +// muestra: /index.php?r=article%2Findex&page=100 +echo $pagination->createUrl(100); + +// muestra: /index.php?r=article%2Findex&page=101 +echo $pagination->createUrl(101); +``` + +> Tip: puedes personalizar el parámetro `page` de la consulta configurando + la propiedad [[yii\data\Pagination::pageParam|pageParam]] al crear el objeto de la paginación. diff --git a/docs/guide-es/security-passwords.md b/docs/guide-es/security-passwords.md new file mode 100644 index 0000000..7724187 --- /dev/null +++ b/docs/guide-es/security-passwords.md @@ -0,0 +1,31 @@ +Trabajar con Passwords +====================== + +La mayoría de los desarrolladores saben que los passwords no deben ser guardados en texto plano, pero muchos desarrolladores aún creen +que es seguro aplicar a los passowrds hash `md5` o `sha1`. Hubo un tiempo cuando utilizar esos algoritmos de hash mencionados era suficiente, +pero el hardware moderno hace posible que ese tipo de hash e incluso más fuertes, puedan revertirse rápidamente utilizando ataques de fuerza bruta. + +Para poder proveer de una seguridad mayor para los passwords de los usuarios, incluso en el peor de los escenarios (tu aplicación sufre una brecha de seguridad), +necesitas utilizar un algoritmo que resista los ataques de fuerza bruta. La mejor elección actualmente es `bcrypt`. +En PHP, puedes generar un hash `bcrypt` utilizando la [función crypt](http://php.net/manual/en/function.crypt.php). Yii provee +dos funciones auxiliares que hacen que `crypt` genere y verifique los hash más fácilmente. + +Cuando un usuario provee un password por primera vez (por ej., en la registración), dicho password necesita ser pasado por un hash: + + +```php +$hash = Yii::$app->getSecurity()->generatePasswordHash($password); +``` + +El hash puede estar asociado con el atributo del model correspondiente, de manera que pueda ser almacenado en la base de datos para uso posterior. + +Cuando un usuario intenta ingresar al sistema, el password enviado debe ser verificado con el password con hash almacenado previamente: + + +```php +if (Yii::$app->getSecurity()->validatePassword($password, $hash)) { + // todo en orden, dejar ingresar al usuario +} else { + // password erróneo +} +``` diff --git a/docs/guide-es/start-looking-ahead.md b/docs/guide-es/start-looking-ahead.md index 477e495..39129b3 100644 --- a/docs/guide-es/start-looking-ahead.md +++ b/docs/guide-es/start-looking-ahead.md @@ -1,32 +1,35 @@ Mirando Hacia Adelante ====================== -Hasta ahora, has creado una aplicación completa en Yii, y has aprendido cómo implementar algunas de las características más típicas y necesarias, como la de obtener datos de los usuarios a través de un formulario HTML, y traer datos de la base de datos -para mostrarlos en forma paginada. También has aprendido cómo utilizar la herramienta [Gii](tool-gii.md) para generar -código automáticamente, lo que transforma el hecho de programar en una tarea tan simple como la de completar algunos formularios. -En esta sección, resumiremos los recursos acerca de Yii que ayudan a ser más productivos al utilizar la librería. +Si has leído el capítulo "Comenzando con Yii" completo, has creado una aplicación completa en Yii. En el proceso, has aprendido cómo implementar algunas +características comúnmente necesitadas, tales como obtener datos del usuario a través de formularios HTML, traer datos desde la base de datos, +y mostrar datos utilizando paginación. También has aprendido a utilizar [Gii](https://github.com/yiisoft/yii2-gii/blob/master/docs/guide/README.md) para generar +código automáticamente. Utilizar Gii para la generación de código transforma la carga en el proceso de tu desarrollo Web en una tarea tan simple como solamente completar unos formularios. + +Esta sección resumirá los recursos disponibles de Yii que te ayudarán a ser más productivo al utilizar el framework. * Documentación - - La Guía Definitiva: - Como el nombre indica, la guía precisamente define cómo Yii debería trabajar y te da una guía general - acerca de cómo usar la librería. Este es el tutorial simple más importante de Yii que deberías leer - antes de empezar a escribir código. - - La Referencia de Clases: - Este especifica el uso de cada clase provista por Yii. Debería ser utilizado mayormente cuando estés escribiendo - código y quieras entender el funcionamiento de alguna clase, método o propiedad en particular. - - Artículos de la Wiki: - Estos artículos son escritos por usuarios de Yii basado en experiencias propias. En su mayoría son escritos - como recetas que muestran cómo resolver problemas particulares en Yii. Aunque la calidad de estos artículos - puede ser tan buena como la Guía Definitiva, son particularmente útiles al cubrir aspectos más amplios - y puede a menudo ofrecer soluciones listas para usar. - - Libros + - [La Guía Definitiva](http://www.yiiframework.com/doc-2.0/guide-README.html): + Como su nombre lo indica, la guía define precisamente cómo debería trabajar Yii y provee guías generales + acerca de su utilización. Es el tutorial más importante de Yii, y el que deberías leer + antes de escribir cualquier código en Yii. + - [La Referencia de Clases](http://www.yiiframework.com/doc-2.0/index.html): + Esta especifica el uso de cada clase provista por Yii. Debería ser utilizada principalmente cuando estás escribiendo + código y deseas entender el uso de una clase, método o propiedad en particular. El uso de la referencia de clases es mejor luego de un entendimiento contextual del framework. + - [Los Artículos de la Wiki](http://www.yiiframework.com/wiki/?tag=yii2): + Los artículos de la wiki son escritos por usuarios de Yii basados en sus propias experiencias. La mayoría de ellos están escritos + como recetas de cocina, y muestran cómo resolver problemas particulares utilizando Yii. Si bien la calidad de estos + puede no ser tan buena como la de la Guía Definitiva, son útiles ya que cubren un espectro muy amplio + de temas y puede proveer a menudo soluciones listas para usar. + - [Libros](http://www.yiiframework.com/doc/) * [Extensiones](http://www.yiiframework.com/extensions/): - Yii cuenta con una librería de cientos de extensiones que han sido provistas por la comunidad de usuarios que pueden ser fácilmente integradas - en tu aplicación y lograr que sea más simple y rápido desarrollarla. + Yii puede hacer alarde de una librería de miles de extensiones contribuidas por usuarios, que pueden fácilmente conectadas a tu aplicación, haciendo que el desarrollo de la misma sea todavía más fácil y rápido. * Comunidad - - [Foro](http://www.yiiframework.com/forum/) - - [GitHub](https://github.com/yiisoft/yii2) - - [Facebook](https://www.facebook.com/groups/yiitalk/) - - [Twitter](https://twitter.com/yiiframework) - - [LinkedIn](https://www.linkedin.com/groups/yii-framework-1483367) - + - Foro: + - Chat IRC: El canal #yii en la red freenode () + - Chat Gitter: + - GitHub: + - Facebook: + - Twitter: + - LinkedIn: + - Stackoverflow: diff --git a/docs/guide-es/start-workflow.md b/docs/guide-es/start-workflow.md index a85bbc5..2695c3e 100644 --- a/docs/guide-es/start-workflow.md +++ b/docs/guide-es/start-workflow.md @@ -10,6 +10,9 @@ y cómo la aplicación maneja los requests en general. como el document root de tu servidor Web, y configurado la URL de acceso a tu aplicación para que sea `http://hostname/index.php` o similar. Dependiendo de tus necesidades, por favor ajusta dichas URLs. + +Ten en cuenta que a diferencia del framework en sí, después de que el template de proyecto es instalado, este es todo tuyo. Eres libre de agregar o eliminar +código modificar todo según tu necesidad. Funcionalidad @@ -17,10 +20,9 @@ Funcionalidad La aplicación básica contiene 4 páginas: -* Página principal, mostrada cuando se accede a la URL `http://hostname/index.php`, +* página principal, mostrada cuando se accede a la URL `http://hostname/index.php`, * página "Acerca de (About)", -* la página "Contacto (Contact)", que muestra un formulario de contacto que permite a los usuarios - finales contactarse vía email, +* la página "Contacto (Contact)", que muestra un formulario de contacto que permite a los usuarios finales contactarse vía email, * y la página "Login", que muestra un formulario para loguearse que puede usarse para autenticar usuarios. Intenta loguearte con "admin/admin", y verás que el elemento "Login" del menú principal cambiará a "Logout". @@ -28,10 +30,13 @@ Estas páginas comparten un encabezado y un pie. El encabezado contiene una barr la navegación entre las diferentes páginas. También deberías ver una barra en la parte inferior de la ventana del navegador. -Esta es la útil [herramienta de depuración](tool-debugger.md) provista por Yii para registrar y mostrar mucha información de depuración, -tal como los mensajes de log, response status, las consultas ejecutadas a la base de datos, y más. +Esta es la útil [herramienta de depuración](tool-debugger.md) provista por Yii para registrar y mostrar mucha información de depuración, tal como los mensajes de log, response status, las consultas ejecutadas a la base de datos, y más. +Adicionalmente a la aplicación web, hay un script de consola llamado `yii`, localizado en el directorio base de la aplicación. +El script puede ser utilizado para ejecutar tareas de fondo y tareas de mantenimiento de la aplicación, las cuales son descritas +en la [Sección de Aplicación de Consola](tutorial-console.md). + Estructura de la aplicación --------------------------- @@ -71,7 +76,7 @@ Cada aplicación tiene un script de entrada `web/index.php` que es el único scr El script de entrada toma una petición (request) entrante y crea una instancia de una [aplicación](structure-applications.md) para manejarlo. La [aplicación](structure-applications.md) resuelve la petición (request) con la ayuda de sus [componentes](concept-components.md), y la envía al resto de los elementos MVC. Los [widgets](structure-widgets.md) son usados en las [vistas](structure-views.md) -para ayudar a construir elementos de interfáz complejos y dinámicos. +para ayudar a construir elementos de interfaz complejos y dinámicos. Ciclo de Vida de una Petición (Request) @@ -94,3 +99,4 @@ El siguiente diagrama muestra cómo una aplicación maneja una petición. 9. La acción renderiza una vista, pasándole los datos del modelo cargado. 10. El resultado de la renderización es pasado al componente [response](runtime-responses.md) de la aplicación. 11. El componente response envía el resultado de la renderización al navegador del usuario. + From 52bd5790f10aaebe60d54da0d6537eaa41e92830 Mon Sep 17 00:00:00 2001 From: Luciano Baraglia Date: Mon, 30 May 2016 15:21:45 -0300 Subject: [PATCH 80/90] More Spanish Docs [skip ci] --- docs/guide-es/concept-aliases.md | 27 ++- docs/guide-es/input-file-upload.md | 208 ++++++++++++++++ docs/guide-es/rest-error-handling.md | 19 +- docs/guide-es/rest-response-formatting.md | 5 +- docs/guide-es/runtime-handling-errors.md | 55 ++--- docs/guide-es/structure-views.md | 383 ++++++++++++++++++++++-------- docs/guide-es/test-functional.md | 11 + docs/guide-es/tutorial-yii-integration.md | 118 ++++++--- 8 files changed, 646 insertions(+), 180 deletions(-) create mode 100644 docs/guide-es/input-file-upload.md create mode 100644 docs/guide-es/test-functional.md diff --git a/docs/guide-es/concept-aliases.md b/docs/guide-es/concept-aliases.md index 7304f14..743eccf 100644 --- a/docs/guide-es/concept-aliases.md +++ b/docs/guide-es/concept-aliases.md @@ -1,15 +1,17 @@ Alias ===== -Los alias son utilizados para representar las rutas de archivos o URLs para evitar su [hard-coding](http://es.wikipedia.org/wiki/Hard_code) -en tu código. Un alias debe comenzar con un cáracter `@` para que así pueda ser diferenciado de las rutas de archivos y URLs. -Por ejemplo, el alias `@yii` representa la ruta de instalación de la librería Yii, mientras que `@web` representa la -URL base la aplicación que actualmente se está ejecutando. +Loa alias son utilizados para representar rutas o URLs de manera que no tengas que escribir explícitamente rutas absolutas o URLs en tu +proyecto. Un alias debe comenzar con el signo `@` para ser diferenciado de una ruta normal de archivo y de URLs. Los alias definidos +sin el `@` del principio, serán prefijados con el signo `@`. -Definiendo Alias ----------------- +Yii trae disponibles varios alias predefinidos. Por ejemplo, el alias `@yii` representa la ruta de instalación del +framework Yii; `@web` representa la URL base para la aplicación Web ejecutándose. -Puedes llamar a [[Yii::setAlias()]] para definir un alias para una determinada ruta de archivo o URL. Por ejemplo, +Definir Alias +------------- + +Para definir un alias puedes llamar a [[Yii::setAlias()]] para una determinada ruta de archivo o URL. Por ejemplo, ```php // un alias de una ruta de archivos @@ -83,9 +85,10 @@ Si `@foo/bar` no está definido como un alias de raíz, la última declaración Usando Alias ------------ -Los alias son utilizados en muchos lugares en Yii sin necesidad de llamar [[Yii::getAlias()]] para convertirlos en rutas/URLs. -Por ejemplo, [[yii\caching\FileCache::cachePath]] puede aceptar tanto una ruta de archivo como un alias que represente -la ruta de archivo, gracias al prefijo `@` el cual permite diferenciar una ruta de archivo de un alias. +Los alias son utilizados en muchos lugares en Yii sin necesidad de llamar [[Yii::getAlias()]] para convertirlos +en rutas/URLs. Por ejemplo, [[yii\caching\FileCache::cachePath]] puede aceptar tanto una ruta de archivo como un alias +que represente la ruta de archivo, gracias al prefijo `@` el cual permite diferenciar una ruta de archivo +de un alias. ```php use yii\caching\FileCache; @@ -121,10 +124,10 @@ mientras que el resto de los alias están definidos en el constructor de la apli Alias en Extensiones -------------------- -Un alias se define automaticamente por cada [extensión](structure-extensions.md) que ha sido instalada a través de Composer. +Un alias se define automáticamente por cada [extensión](structure-extensions.md) que ha sido instalada a través de Composer. El alias es nombrado tras el `namespace` de raíz de la extensión instalada tal y como está declarada en su archivo `composer.json`, y representa el directorio raíz de la extensión. Por ejemplo, si instalas la extensión `yiisoft/yii2-jui`, tendrás -automaticamente definido el alias `@yii/jui` durante la etapa [bootstrapping](runtime-bootstrapping.md) de la aplicación: +automáticamente definido el alias `@yii/jui` durante la etapa [bootstrapping](runtime-bootstrapping.md) de la aplicación: ```php Yii::setAlias('@yii/jui', 'VendorPath/yiisoft/yii2-jui'); diff --git a/docs/guide-es/input-file-upload.md b/docs/guide-es/input-file-upload.md new file mode 100644 index 0000000..54728b6 --- /dev/null +++ b/docs/guide-es/input-file-upload.md @@ -0,0 +1,208 @@ +Subir Archivos +============== + +Subir archivos en Yii es normalmente realizado con la ayuda de [[yii\web\UploadedFile]], que encapsula cada archivo subido +en un objeto `UploadedFile`. Combinado con [[yii\widgets\ActiveForm]] y [modelos](structure-models.md), +puedes fácilmente implementar un mecanismo seguro de subida de archivos. + + +## Crear Modelos + +Al igual que al trabajar con entradas de texto plano, para subir un archivo debes crear una clase de modelo y utilizar un atributo +de dicho modelo para mantener la instancia del archivo subido. Debes también declarar una regla para validar la subida del archivo. +Por ejemplo, + +```php +namespace app\models; + +use yii\base\Model; +use yii\web\UploadedFile; + +class UploadForm extends Model +{ + /** + * @var UploadedFile + */ + public $imageFile; + + public function rules() + { + return [ + [['imageFile'], 'file', 'skipOnEmpty' => false, 'extensions' => 'png, jpg'], + ]; + } + + public function upload() + { + if ($this->validate()) { + $this->imageFile->saveAs('uploads/' . $this->imageFile->baseName . '.' . $this->imageFile->extension); + return true; + } else { + return false; + } + } +} +``` + +En el código anterior, el atributo `imageFile` es utilizado para mantener una instancia del archivo subido. Este está asociado con +una regla de validación `file`, que utiliza [[yii\validators\FileValidator]] para asegurarse que el archivo a subir tenga extensión `png` o `jpg`. +El método `upload()` realizará la validación y guardará el archivo subido en el servidor. + +El validador `file` te permite chequear las extensiones, el tamaño, el tipo MIME, etc. Por favor consulta +la sección [Validadores del Framework](tutorial-core-validators.md#file) para más detalles. + +> Tip: Si estás subiendo una imagen, podrías considerar el utilizar el validador `image`. El validador `image` es + implementado a través de [[yii\validators\ImageValidator]], que verifica que un atributo haya recibido una imagen válida + que pueda ser tanto guardada como procesada utilizando la [Extensión Imagine](https://github.com/yiisoft/yii2-imagine). + + +## Renderizar Campos de Subida de Archivos + +A continuación, crea un campo de subida de archivo en la vista: + +```php + + + ['enctype' => 'multipart/form-data']]) ?> + + field($model, 'imageFile')->fileInput() ?> + + + + +``` + +Es importante recordad que agregues la opción `enctype` al formulario para que el archivo pueda ser subido apropiadamente. +La llamada a `fileInput()` renderizará un tag `` que le permitirá al usuario seleccionar el archivo a subir. + +> Tip: desde la versión 2.0.8, [[yii\web\widgets\ActiveField::fileInput|fileInput]] agrega la opción `enctype` al formulario + automáticamente cuando se utiliza una campo de subida de archivo. + +## Uniendo Todo + +Ahora, en una acción del controlador, escribe el código que una el modelo y la vista para implementar la subida de archivos: + +```php +namespace app\controllers; + +use Yii; +use yii\web\Controller; +use app\models\UploadForm; +use yii\web\UploadedFile; + +class SiteController extends Controller +{ + public function actionUpload() + { + $model = new UploadForm(); + + if (Yii::$app->request->isPost) { + $model->imageFile = UploadedFile::getInstance($model, 'imageFile'); + if ($model->upload()) { + // el archivo se subió exitosamente + return; + } + } + + return $this->render('upload', ['model' => $model]); + } +} +``` + +En el código anterior, cuando se envía el formulario, el método [[yii\web\UploadedFile::getInstance()]] es llamado +para representar el archivo subido como una instancia de `UploadedFile`. Entonces dependemos de la validación del modelo +para asegurarnos que el archivo subido es válido y entonces subirlo al servidor. + + +## Uploading Multiple Files + +También puedes subir varios archivos a la vez, con algunos ajustes en el código de las subsecciones previas. + +Primero debes ajustar la clase del modelo, agregando la opción `maxFiles` en la regla de validación `file` para limitar +el número máximo de archivos a subir. Definir `maxFiles` como `0` significa que no hay límite en el número de archivos +a subir simultáneamente. El número máximo de archivos permitidos para subir simultáneamente está también limitado +por la directiva PHP [`max_file_uploads`](http://php.net/manual/en/ini.core.php#ini.max-file-uploads), +cuyo valor por defecto es 20. El método `upload()` debería también ser modificado para guardar los archivos uno a uno. + +```php +namespace app\models; + +use yii\base\Model; +use yii\web\UploadedFile; + +class UploadForm extends Model +{ + /** + * @var UploadedFile[] + */ + public $imageFiles; + + public function rules() + { + return [ + [['imageFiles'], 'file', 'skipOnEmpty' => false, 'extensions' => 'png, jpg', 'maxFiles' => 4], + ]; + } + + public function upload() + { + if ($this->validate()) { + foreach ($this->imageFiles as $file) { + $file->saveAs('uploads/' . $file->baseName . '.' . $file->extension); + } + return true; + } else { + return false; + } + } +} +``` + +En el archivo de la vista, debes agregar la opción `multiple` en la llamada a `fileInput()` de manera que el campo +pueda recibir varios archivos: + +```php + + + ['enctype' => 'multipart/form-data']]) ?> + + field($model, 'imageFiles[]')->fileInput(['multiple' => true, 'accept' => 'image/*']) ?> + + + + +``` + +Y finalmente en la acción del controlador, debes llamar `UploadedFile::getInstances()` en vez de +`UploadedFile::getInstance()` para asignar un array de instancias `UploadedFile` a `UploadForm::imageFiles`. + +```php +namespace app\controllers; + +use Yii; +use yii\web\Controller; +use app\models\UploadForm; +use yii\web\UploadedFile; + +class SiteController extends Controller +{ + public function actionUpload() + { + $model = new UploadForm(); + + if (Yii::$app->request->isPost) { + $model->imageFiles = UploadedFile::getInstances($model, 'imageFiles'); + if ($model->upload()) { + // el archivo fue subido exitosamente + return; + } + } + + return $this->render('upload', ['model' => $model]); + } +} +``` diff --git a/docs/guide-es/rest-error-handling.md b/docs/guide-es/rest-error-handling.md index 092a6ab..f5a663e 100644 --- a/docs/guide-es/rest-error-handling.md +++ b/docs/guide-es/rest-error-handling.md @@ -6,7 +6,8 @@ ocurre en el servidor, simplemente puedes lanzar una excepción para notificar a Si puedes identificar la causa del error (p.e., el recurso solicitado no existe), debes considerar lanzar una excepción con el código HTTP de estado apropiado (p.e., [[yii\web\NotFoundHttpException]] representa un código de estado 404). Yii enviará la respuesta a continuación con el correspondiente código de estado HTTP y el texto. Yii puede incluir también -la representación serializada de la excepción en el cuerpo de la respuesta. Por ejemplo: +la representación serializada de la excepción en el cuerpo de la respuesta. +Por ejemplo: ``` HTTP/1.1 404 Not Found @@ -17,7 +18,7 @@ Content-Type: application/json; charset=UTF-8 { "name": "Not Found Exception", - "message": "El recurso solicitado no ha sido encontrado.", + "message": "The requested resource was not found.", "code": 0, "status": 404 } @@ -26,21 +27,23 @@ Content-Type: application/json; charset=UTF-8 La siguiente lista sumariza los códigos de estado HTTP que son usados por el framework REST: * `200`: OK. Todo ha funcionado como se esperaba. -* `201`: El recurso ha creado con éxito en respuesta a la petición `POST`. La cabecera de situación `Location` contiene la URL apuntando al nuevo recurso creado. +* `201`: El recurso ha creado con éxito en respuesta a la petición `POST`. La cabecera de situación `Location` + contiene la URL apuntando al nuevo recurso creado. * `204`: La petición ha sido manejada con éxito y el cuerpo de la respuesta no tiene contenido (como una petición `DELETE`). * `304`: El recurso no ha sido modificado. Puede usar la versión en caché. -* `400`: Petición errónea. Esto puede estar causado por varias acciones de el usuario, como proveer un JSON no válido en el cuerpo de la petición, proveyendo parámetros de acción no válidos, etc. +* `400`: Petición errónea. Esto puede estar causado por varias acciones de el usuario, como proveer un JSON no válido + en el cuerpo de la petición, proveyendo parámetros de acción no válidos, etc. * `401`: Autenticación fallida. * `403`: El usuario autenticado no tiene permitido acceder a la API final. * `404`: El recurso pedido no existe. * `405`: Método no permitido. Por favor comprueba la cabecera `Allow` por los métodos HTTP permitidos. * `415`: Tipo de medio no soportado. El tipo de contenido pedido o el número de versión no es válido. -* `422`: La validación de datos ha fallado (en respuesta a una petición `POST` , por ejemplo). Por favor, comprobad en el cuerpo de la respuesta el mensaje detallado. +* `422`: La validación de datos ha fallado (en respuesta a una petición `POST` , por ejemplo). Por favor, comprueba en el cuerpo de la respuesta el mensaje detallado. * `429`: Demasiadas peticiones. La petición ha sido rechazada debido a un limitación de rango. * `500`: Error interno del servidor. Esto puede estar causado por errores internos del programa. -## Personalizando la Respuesta al Error +## Personalizar la Respuesta al Error A veces puedes querer personalizar el formato de la respuesta del error por defecto . Por ejemplo, en lugar de depender del uso de diferentes estados HTTP para indicar los diferentes errores, puedes querer usar siempre el estado HTTP 200 @@ -64,7 +67,7 @@ Content-Type: application/json; charset=UTF-8 } ``` -Para lograr este objetivo, puedes responder al evento `beforeSend` del componente `response` en la configuración de la aplicación: +Para lograrlo, puedes responder al evento `beforeSend` del componente `response` en la configuración de la aplicación: ```php return [ @@ -74,7 +77,7 @@ return [ 'class' => 'yii\web\Response', 'on beforeSend' => function ($event) { $response = $event->sender; - if ($response->data !== null && !empty(Yii::$app->request->get['suppress_response_code'])) { + if ($response->data !== null && Yii::$app->request->get('suppress_response_code')) { $response->data = [ 'success' => $response->isSuccessful, 'data' => $response->data, diff --git a/docs/guide-es/rest-response-formatting.md b/docs/guide-es/rest-response-formatting.md index 2b8826a..7f967a5 100644 --- a/docs/guide-es/rest-response-formatting.md +++ b/docs/guide-es/rest-response-formatting.md @@ -9,8 +9,9 @@ con el formato de la respuesta: 2. La conversión de objetos recurso en arrays, como está descrito en la sección [Recursos (Resources)](rest-resources.md). Esto es realizado por la clase [[yii\rest\Serializer]]. 3. La conversión de arrays en cadenas con el formato determinado por el paso de negociación de contenido. Esto es - realizado por los [[yii\web\ResponseFormatterInterface|response formatters]] registrados con el - componente de la aplicación [[yii\web\Response::formatters|response]]. + realizado por los [[yii\web\ResponseFormatterInterface|formatos de respuesta]] registrados + con la propiedad [[yii\web\Response::formatters|formatters]] del + [componente de la aplicación](structure-application-components.md) `response`. ## Negociación de contenido (Content Negotiation) diff --git a/docs/guide-es/runtime-handling-errors.md b/docs/guide-es/runtime-handling-errors.md index ea85a53..2571fb2 100644 --- a/docs/guide-es/runtime-handling-errors.md +++ b/docs/guide-es/runtime-handling-errors.md @@ -4,22 +4,20 @@ Gestión de Errores Yii incluye un [[yii\web\ErrorHandler|error handler]] que permite una gestión de errores mucho más práctica que anteriormente. En particular, el gestor de errores de Yii hace lo siguiente para mejorar la gestión de errores: -* Todos los errores no fatales (ej. advertencias (warning), avisos (notices)) se convierten en excepciones - capturables. +* Todos los errores no fatales (ej. advertencias (warning), avisos (notices)) se convierten en excepciones capturables. * Las excepciones y los errores fatales de PHP se muestran con una pila de llamadas (call stack) de información detallada y lineas de código fuente. * Soporta el uso de [acciones de controlador](structure-controllers.md#actions) dedicadas para mostrar errores. * Soporta diferentes formatos de respuesta (response) de errores. El [[yii\web\ErrorHandler|error handler]] esta habilitado de forma predeterminada. Se puede deshabilitar definiendo la -constante `YII_ENABLE_ERROR_HANDLER` con valor false en el -[script de entrada (entry script)](structure-entry-scripts.md) de la aplicación. +constante `YII_ENABLE_ERROR_HANDLER` con valor false en el [script de entrada (entry script)](structure-entry-scripts.md) de la aplicación. + ## Uso del Gestor de Errores -El [[yii\web\ErrorHandler|error handler]] se registra como un -[componente de aplicación](structure-application-components.md) llamado `errorHandler`. Se puede configurar en la -configuración de la aplicación como en el siguiente ejemplo: +El [[yii\web\ErrorHandler|error handler]] se registra como un [componente de aplicación](structure-application-components.md) llamado `errorHandler`. +Se puede configurar en la configuración de la aplicación como en el siguiente ejemplo: ```php return [ @@ -31,8 +29,7 @@ return [ ]; ``` -Con la anterior configuración, el numero del lineas de código fuente que se mostrará en las páginas de excepciones -será como máximo de 20. +Con la anterior configuración, el numero del lineas de código fuente que se mostrará en las páginas de excepciones será como máximo de 20. Como se ha mencionado, el gestor de errores convierte todos los errores de PHP no fatales en excepciones capturables. Esto significa que se puede usar el siguiente código para tratar los errores PHP: @@ -61,29 +58,27 @@ use yii\web\NotFoundHttpException; throw new NotFoundHttpException(); ``` + ## Personalizar la Visualización de Errores -El [[yii\web\ErrorHandler|error handler]] ajusta la visualización del error conforme al valor de la constante -`YII_DEBUG`. Cuando `YII_DEBUG` es `true` (es decir, en modo depuración (debug)), el gestor de errores mostrara las -excepciones con una pila detallada de información y con lineas de código fuente para ayudar a depurar. Y cuando la -variable `YII_DEBUG` es `false`, solo se mostrará el mensaje de error para prevenir la revelación de información -sensible de la aplicación. +El [[yii\web\ErrorHandler|error handler]] ajusta la visualización del error conforme al valor de la constante `YII_DEBUG`. +Cuando `YII_DEBUG` es `true` (es decir, en modo depuración (debug)), el gestor de errores mostrara las +excepciones con una pila detallada de información y con lineas de código fuente para ayudar a depurar. Y cuando la variable `YII_DEBUG` es `false`, +solo se mostrará el mensaje de error para prevenir la revelación de información sensible de la aplicación. > Info: Si una excepción es descendiente de [[yii\base\UserException]], no se mostrará la pila de llamadas - independientemente del valor de `YII_DEBUG`. Esto es debido a que se considera que estas excepciones se deben a - errores cometidos por los usuarios y los desarrolladores no necesitan corregirlas. +independientemente del valor de `YII_DEBUG`. Esto es debido a que se considera que estas excepciones se deben a +errores cometidos por los usuarios y los desarrolladores no necesitan corregirlas. -De forma predeterminada, el [[yii\web\ErrorHandler|error handler]] muestra los errores usando dos -[vistas](structure-views.md): +De forma predeterminada, el [[yii\web\ErrorHandler|error handler]] muestra los errores usando dos [vistas](structure-views.md): * `@yii/views/errorHandler/error.php`: se usa cuando deben mostrarse los errores SIN la información de la pila de llamadas. Cuando `YII_DEBUG` es falos, este es el único error que se mostrara. -* `@yii/views/errorHandler/exception.php`: se usa cuando los errores deben mostrarse CON la información de la pila de - llamadas. +* `@yii/views/errorHandler/exception.php`: se usa cuando los errores deben mostrarse CON la información de la pila de llamadas. + +Se pueden configurar las propiedades [[yii\web\ErrorHandler::errorView|errorView]] y [[yii\web\ErrorHandler::exceptionView|exceptionView]] +el gestor de errores para usar nuestros propias vistas para personalizar la visualización de los errores. -Se pueden configurar las propiedades [[yii\web\ErrorHandler::errorView|errorView]] y -[[yii\web\ErrorHandler::exceptionView|exceptionView]] el gestor de errores para usar nuestros propias vistas para -personalizar la visualización de los errores. ### Uso de Acciones de Error @@ -129,8 +124,7 @@ class SiteController extends Controller El código anterior define la acción `error` usando la clase [[yii\web\ErrorAction]] que renderiza un error usando la vista llamada `error`. -Además, usando [[yii\web\ErrorAction]], también se puede definir la acción `error` usando un método de acción como en -el siguiente ejemplo, +Además, usando [[yii\web\ErrorAction]], también se puede definir la acción `error` usando un método de acción como en el siguiente ejemplo, ```php public function actionError() @@ -150,8 +144,15 @@ a las siguientes variables si se define el error como un [[yii\web\ErrorAction]] * `exception`: el objeto de excepción a través del cual se puede obtener más información útil, tal como el código de estado HTTP, el código de error, la pila de llamadas del error, etc. -> Info: Tanto la [plantilla de aplicación básica](start-installation.md) como la - [plantilla de aplicación avanzada](tutorial-advanced-app.md), ya incorporan la acción de error y la vista de error. +> Info: Tanto la [plantilla de aplicación básica](start-installation.md) como la [plantilla de aplicación avanzada](tutorial-advanced-app.md), +ya incorporan la acción de error y la vista de error. + +> Note: Si necesitas redireccionar en un gestor de error, hazlo de la siguiente manera: +> ```php +> Yii::$app->getResponse()->redirect($url)->send(); +> return; +> ``` + ### Personalizar el Formato de Respuesta de Error diff --git a/docs/guide-es/structure-views.md b/docs/guide-es/structure-views.md index e868c6f..b32a3f3 100644 --- a/docs/guide-es/structure-views.md +++ b/docs/guide-es/structure-views.md @@ -2,13 +2,18 @@ Vistas ====== Las Vistas (views) son una parte de la arquitectura [MVC](http://es.wikipedia.org/wiki/Modelo%E2%80%93vista%E2%80%93controlador). -Estas son el código responsable de presentar los datos al usuario final. En una aplicación Web, las vistas son usualmente creadas en términos de *templates* que son archivos PHP que contienen principalmente HTML y PHP. -Son manejadas por el componente de la aplicación [[yii\web\View|view]], el cual provee métodos comúnmente utilizados para facilitar la composición y el renderizado de las mismas. Por simplicidad, a menudo las llamamos *templates* o *archivos de templates*. +Estas son el código responsable de presentar los datos al usuario final. En una aplicación Web, las vistas son usualmente creadas +en términos de *templates* que son archivos PHP que contienen principalmente HTML y PHP. +Estas son manejadas por el [componente de la aplicación](structure-application-components.md) [[yii\web\View|view]], el cual provee los métodos comúnmente utilizados +para facilitar la composición y renderizado. Por simplicidad, a menudo nos referimos a los templates de vistas o archivos de templates +como vistas. -## Creando Vistas +## Crear Vistas -Como fue mencionado, una vista es simplemente un archivo PHP que mezcla código PHP y HTML. La siguiente es una vista que muestra un formulario de login. Como puedes ver, el código PHP utilizado es para generar contenido dinámico, como el título de la página y el formulario mismo, mientras que el código HTML organiza estos elementos en una página HTML mostrable. +Como fue mencionado, una vista es simplemente un archivo PHP que mezcla código PHP y HTML. La siguiente es una vista +que muestra un formulario de login. Como puedes ver, el código PHP utilizado es para generar contenido dinámico, como el +título de la página y el formulario mismo, mientras que el código HTML organiza estos elementos en una página HTML mostrable. ```php title = 'Login'; ``` -Dentro de una vista, puedes acceder a la variable `$this` referida al [[yii\web\View|componente view]] que maneja y renderiza la vista actual. +Dentro de una vista, puedes acceder a la variable `$this` referida al [[yii\web\View|componente view]] +que maneja y renderiza la vista actual. -Además de `$this`, puede haber otras variables predefinidas en una vista, como `$form` y `$model` en el ejemplo anterior. Estas variables representan los datos que son *inyectados* a la vista desde el [controlador](structure-controllers.md) o algún otro objeto que dispara la [renderización de la vista](#rendering-views). +Además de `$this`, puede haber otras variables predefinidas en una vista, como `$form` y `$model` en el +ejemplo anterior. Estas variables representan los datos que son *inyectados* a la vista desde el [controlador](structure-controllers.md) +o algún otro objeto que dispara la [renderización de la vista](#rendering-views). -> Tip: La lista de variables predefinidas están listadas en un bloque de comentario al principio de la vista así pueden ser reconocidas por las IDEs. Esto es también una buena manera de documentar tus propias vistas. +> Tip: La lista de variables predefinidas están listadas en un bloque de comentario al principio de la vista así + pueden ser reconocidas por las IDEs. Esto es también una buena manera de documentar tus propias vistas. ### Seguridad -Al crear vistas que generan páginas HTML, es importante que codifiques (encode) y/o filtres los datos provenientes de los usuarios antes de mostrarlos. De otro modo, tu aplicación puede estar expuesta a ataques tipo [cross-site scripting](http://es.wikipedia.org/wiki/Cross-site_scripting). +Al crear vistas que generan páginas HTML, es importante que codifiques (encode) y/o filtres los datos +provenientes de los usuarios antes de mostrarlos. De otro modo, tu aplicación puede estar expuesta +a ataques tipo [cross-site scripting](http://es.wikipedia.org/wiki/Cross-site_scripting). -Para mostrar un texto plano, codifícalos previamente utilizando [[yii\helpers\Html::encode()]]. Por ejemplo, el siguiente código aplica una codificación del nombre de usuario antes de mostrarlo: +Para mostrar un texto plano, codifícalos previamente utilizando [[yii\helpers\Html::encode()]]. Por ejemplo, el siguiente código aplica +una codificación del nombre de usuario antes de mostrarlo: ```php ``` -Para mostrar contenido HTML, utiliza [[yii\helpers\HtmlPurifier]] para filtrarlo antes. Por ejemplo, el siguiente código filtra el contenido del post antes de mostrarlo en pantalla: +Para mostrar contenido HTML, utiliza [[yii\helpers\HtmlPurifier]] para filtrarlo antes. Por ejemplo, el siguiente código +filtra el contenido del post antes de mostrarlo en pantalla: ```php ``` -> Tip: Aunque HTMLPurifier hace un excelente trabajo al hacer la salida más segura, no es rápido. Deberías considerar utilizar [caching](caching-overview.md) al resultado de aplicar el filtro si tu aplicación requiere un gran desempeño (performance). +> Tip: Aunque HTMLPurifier hace un excelente trabajo al hacer la salida más segura, no es rápido. Deberías considerar +el aplicar un [caching](caching-overview.md) al resultado de aplicar el filtro si tu aplicación requiere un gran desempeño (performance). -### Organizando Vistas +### Organizar las Vistas Así como en [controladores](structure-controllers.md) y [modelos](structure-models.md), existen convenciones para organizar las vistas. -* Para vistas renderizadas por controladores, deberían colocarse en un directorio tipo `@app/views/ControllerID` por defecto, donde `ControllerID` se refiere al [ID del controlador](structure-controllers.md#routes). Por ejemplo, si la clase del controlador es `PostController`, el directorio sería `@app/views/post`; Si fuera `PostCommentController`, el directorio sería `@app/views/post-comment`. En caso de que el controlador pertenezca a un módulo, el directorio sería `views/ControllerID` bajo el [[yii\base\Module::basePath|directorio del módulo]]. -* Para vistas renderizadas por un [widget](structure-widgets.md), deberían ser puestas en un directorio tipo `WidgetPath/views` por defecto, donde `WidgetPath` se refiere al directorio que contiene a la clase del widget. +* Para vistas renderizadas por controladores, deberían colocarse en un directorio tipo `@app/views/ControllerID` por defecto, + donde `ControllerID` se refiere al [ID del controlador](structure-controllers.md#routes). Por ejemplo, + si la clase del controlador es `PostController`, el directorio sería `@app/views/post`; Si fuera `PostCommentController`, + el directorio sería `@app/views/post-comment`. En caso de que el controlador pertenezca a un módulo, + el directorio sería `views/ControllerID` bajo el [[yii\base\Module::basePath|directorio del módulo]]. +* Para vistas renderizadas por un [widget](structure-widgets.md), deberían ser puestas en un directorio + tipo `WidgetPath/views` por defecto, donde `WidgetPath` se refiere al directorio que contiene a la clase del widget. * Para vistas renderizadas por otros objetos, se recomienda seguir una convención similar a la utilizada con los widgets. -Puedes personalizar estos directorios por defecto sobrescribiendo el método [[yii\base\ViewContextInterface::getViewPath()]] en el controlador o widget necesario. +Puedes personalizar estos directorios por defecto sobrescribiendo el método [[yii\base\ViewContextInterface::getViewPath()]] +en el controlador o widget necesario. ## Renderizando Vistas -Puedes renderizar vistas desde [controllers](structure-controllers.md), [widgets](structure-widgets.md), o cualquier otro lugar llamando a los métodos de renderización de vistas. Estos métodos comparten una firma similar, como se muestra a continuación: +Puedes renderizar vistas desde [controllers](structure-controllers.md), [widgets](structure-widgets.md), o cualquier otro lugar +llamando a los métodos de renderización de vistas. Estos métodos comparten una firma similar, como se muestra a continuación: ``` /** @@ -99,10 +120,15 @@ methodName($view, $params = []) Dentro de los [controladores](structure-controllers.md), puedes llamar al siguiente método del controlador para renderizar una vista: -* [[yii\base\Controller::render()|render()]]: renderiza la [vista nombrada](#named-views) y aplica un [layout](#layouts) al resultado de la renderización. +* [[yii\base\Controller::render()|render()]]: renderiza la [vista nombrada](#named-views) y aplica un [layout](#layouts) + al resultado de la renderización. * [[yii\base\Controller::renderPartial()|renderPartial()]]: renderiza la [vista nombrada](#named-views) sin ningún layout aplicado. -* [[yii\web\Controller::renderAjax()|renderAjax()]]: renderiza la [vista nombrada](#named-views) sin layout, e inyecta todos los scripts y archivos JS/CSS registrados. Esto sucede usualmente en respuestas a llamadas a AJAX `requests`. -* [[yii\base\Controller::renderFile()|renderFile()]]: renderiza la vista especificada en términos de la ruta al archivo o [alias](concept-aliases.md). +* [[yii\web\Controller::renderAjax()|renderAjax()]]: renderiza la [vista nombrada](#named-views) sin layout, + e inyecta todos los scripts y archivos JS/CSS registrados. Esto sucede usualmente en respuestas a peticiones AJAX. +* [[yii\base\Controller::renderFile()|renderFile()]]: renderiza la vista especificada en términos de la ruta al archivo o + [alias](concept-aliases.md). +* [[yii\base\Controller::renderContent()|renderContent()]]: renderiza un string fijo, inscrustándolo en + el [layout](#layouts) actualmente aplicable. Este método está disponible desde la versión 2.0.1. Por ejemplo: @@ -137,7 +163,8 @@ class PostController extends Controller Dentro de [widgets](structure-widgets.md), puedes llamar a cualquier de los siguientes métodos de widget para renderizar una vista. * [[yii\base\Widget::render()|render()]]: renderiza la [vista nombrada](#named-views). -* [[yii\base\Widget::renderFile()|renderFile()]]: renderiza la vista especificada en términos de ruta al archivo o [alias](concept-aliases.md). +* [[yii\base\Widget::renderFile()|renderFile()]]: renderiza la vista especificada en términos de ruta al archivo + o [alias](concept-aliases.md). Por ejemplo: @@ -162,22 +189,25 @@ class ListWidget extends Widget ``` -### Renderizando en Vistas +### Renderizar en Vistas Puedes renderizar una vista dentro de otra vista llamando a algunos de los siguientes métodos provistos por el [[yii\base\View|componente view]]: * [[yii\base\View::render()|render()]]: renderiza la [vista nombrada](#named-views). -* [[yii\web\View::renderAjax()|renderAjax()]]: renderiza la [vista nombrada](#named-views) e inyecta todos los archivos y scripts JS/CSS. Esto sucede usualmente en respuestas a llamadas a AJAX `requests`. -* [[yii\base\View::renderFile()|renderFile()]]: renderiza la vista especificada en términos de ruta al archivo o [alias](concept-aliases.md). +* [[yii\web\View::renderAjax()|renderAjax()]]: renderiza la [vista nombrada](#named-views) e inyecta + todos los archivos y scripts JS/CSS. Esto sucede usualmente en respuestas a las peticiones AJAX. +* [[yii\base\View::renderFile()|renderFile()]]: renderiza la vista especificada en términos de ruta al archivo + o [alias](concept-aliases.md). -Por ejemplo, el siguiente código en una vista renderiza el template `_overview.php` encontrado en el mismo directorio de la vista renderizada actualmente. Recuerda que la variable `$this` en una vista se refiere al componente [[yii\base\View|view]]: +Por ejemplo, el siguiente código en una vista renderiza el template `_overview.php` encontrado en el mismo directorio +de la vista renderizada actualmente. Recuerda que la variable `$this` en una vista se refiere al componente [[yii\base\View|view]]: ```php render('_overview') ?> ``` -### Renderizando en Otros Lugares +### Renderizar en Otros Lugares En cualquier lugar, puedes tener acceso al componente [[yii\base\View|view]] utilizando la expresión `Yii::$app->view` y entonces llamar a los métodos previamente mencionados para renderizar una vista. Por ejemplo: @@ -190,25 +220,43 @@ echo \Yii::$app->view->renderFile('@app/views/site/license.php'); ### Vistas Nombradas -Cuando renderizas una vista, puedes especificar el template utilizando tanto el nombre de la vista o la ruta/alias al archivo. En la mayoría de los casos, utilizarías la primera porque es más concisa y flexible. *Vistas nombradas* son vistas especificadas mediante un nombre en vez de una ruta al archivo o alias. +Cuando renderizas una vista, puedes especificar el template utilizando tanto el nombre de la vista o la ruta/alias al archivo. En la mayoría de los casos, +utilizarías la primera porque es más concisa y flexible. Las *vistas nombradas* son vistas especificadas mediante un nombre en vez de una ruta al archivo o alias. Un nombre de vista es resuelto a su correspondiente ruta de archivo siguiendo las siguientes reglas: -* Un nombre de vista puede omitir la extensión del archivo. En estos casos se utilizará `.php` como extensión del archivo. Por ejemplo, el nombre de vista `about` corresponde al archivo `about.php`. +* Un nombre de vista puede omitir la extensión del archivo. En estos casos se utilizará `.php` como extensión del archivo. Por ejemplo, + el nombre de vista `about` corresponde al archivo `about.php`. * Si el nombre de la vista comienza con doble barra (`//`), la ruta al archivo correspondiente será `@app/views/ViewName`. -Esto quiere decir que la vista es buscada bajo el [[yii\base\Application::viewPath|view path de la aplicación]]. -Por ejemplo, `//site/about` será resuelto como `@app/views/site/about.php`. -* Si el nombre de la vista comienza con una barra simple `/`, la ruta al archivo de la vista utilizará como prefijo el nombre de la vista con el [[yii\base\Module::viewPath|view path]] del [módulo](structure-modules.md) utilizado actualmente. Si no hubiera módulo activo se utilizará `@app/views/ViewName`. Por ejemplo, `/user/create` será resuelto a `@app/modules/user/views/user/create.php` si el módulo activo es `user`. Si no hubiera módulo activo, la ruta al archivo será `@app/views/user/create.php`. -* Si la vista es renderizada con un [[yii\base\View::context|context]] y dicho contexto implementa [[yii\base\ViewContextInterface]], la ruta al archivo se forma utilizando como prefijo el [[yii\base\ViewContextInterface::getViewPath()|view path]] del contexto de la vista. Esto principalmente aplica a vistas renderizadas en controladores y widgets. Por ejemplo, `site/about` será resuelto a `@app/views/site/about.php` si el contexto es el controlador `SiteController`. -* Si la vista es renderizada dentro de otra vista, el directorio que contiene la otra vista será prefijado al nuevo nombre de la vista para formar la ruta a la vista. Por ejemplo, `item` sera resuelto a `@app/views/post/item` si está siendo renderizado desde la vista `@app/views/post/index.php`. - -De acuerdo a las reglas mencionadas, al llamar a `$this->render('view')` en el controlador `app\controllers\PostController` se renderizará el template `@app/views/post/view.php`, mientras que llamando a `$this->render('_overview')` en la vista renderizará el template `@app/views/post/_overview.php`. - -### Accediendo a Datos en la Vista + Esto quiere decir que la vista es buscada bajo el [[yii\base\Application::viewPath|ruta de vistas de la aplicación]]. + Por ejemplo, `//site/about` será resuelto como `@app/views/site/about.php`. +* Si el nombre de la vista comienza con una barra simple `/`, la ruta al archivo de la vista utilizará como prefijo el nombre de la vista + con el [[yii\base\Module::viewPath|view path]] del [módulo](structure-modules.md) utilizado actualmente. + Si no hubiera módulo activo se utilizará `@app/views/ViewName`. Por ejemplo, `/user/create` será resuelto como + `@app/modules/user/views/user/create.php` si el módulo activo es `user`. Si no hubiera módulo activo, + la ruta al archivo será `@app/views/user/create.php`. +* Si la vista es renderizada con un [[yii\base\View::context|context]] y dicho contexto implementa [[yii\base\ViewContextInterface]], + la ruta al archivo se forma utilizando como prefijo la [[yii\base\ViewContextInterface::getViewPath()|ruta de vistas]] del contexto + de la vista. Esto principalmente aplica a vistas renderizadas en controladores y widgets. Por ejemplo, + `about` será resuelto como `@app/views/site/about.php` si el contexto es el controlador `SiteController`. +* Si la vista es renderizada dentro de otra vista, el directorio que contiene la otra vista será prefijado + al nuevo nombre de la vista para formar la ruta a la vista. Por ejemplo, `item` sera resuelto como `@app/views/post/item` + si está siendo renderizado desde la vista `@app/views/post/index.php`. + +De acuerdo a las reglas mencionadas, al llamar a `$this->render('view')` en el controlador `app\controllers\PostController` +se renderizará el template `@app/views/post/view.php`, mientras que llamando a `$this->render('_overview')` en la vista +renderizará el template `@app/views/post/_overview.php`. + + +### Acceder a Datos en la Vista Hay dos modos posibles de acceder a los datos en la vista: push (inyectar) y pull (traer). -Al pasar los datos como segundo parámetro en algún método de renderización, estás utilizando el modo push. Los datos deberían ser representados como un array de pares clave-valor. Cuando la vista está siendo renderizada, la función PHP `extract()` será llamada sobre este array así se extraen las variables que contiene a la vista actual. Por ejemplo, el siguiente código de renderización en un controlador inyectará dos variables a la vista `report`: `$foo = 1` and `$bar = 2`. +Al pasar los datos como segundo parámetro en algún método de renderización, estás utilizando el modo push. +Los datos deberían ser representados como un array de pares clave-valor. Cuando la vista está siendo renderizada, la función PHP `extract()` +será llamada sobre este array así se extraen las variables que contiene a la vista actual. +Por ejemplo, el siguiente código de renderización en un controlador inyectará dos variables a la vista `report`: +`$foo = 1` y `$bar = 2`. ```php echo $this->render('report', [ @@ -217,26 +265,34 @@ echo $this->render('report', [ ]); ``` -El modo pull obtiene los datos del [[yii\base\View|componente view]] u otros objetos accesibles en las vistas (ej. `Yii::$app`). Utilizando el código anterior como ejemplo, dentro de una vista puedes acceder al objeto del controlador a través de la expresión `$this->context`. Como resultado, te es posible acceder a cualquier propiedad o método del controlador en la vista `report`, tal como el ID del controlador como se muestra a continuación: +El modo pull obtiene los datos del [[yii\base\View|componente view]] u otros objetos accesibles +en las vistas (ej. `Yii::$app`). Utilizando el código anterior como ejemplo, dentro de una vista puedes acceder al objeto del controlador +a través de la expresión `$this->context`. Como resultado, te es posible acceder a cualquier propiedad o método +del controlador en la vista `report`, tal como el ID del controlador como se muestra a continuación: ```php El ID del controlador es: context->id ?> -?> ``` -Para acceder a datos en la vista, normalmente se prefiere el modo push, ya que hace a la vista menos dependiente de los objetos del contexto. La contra es que tienes que construir el array manualmente cada vez, lo que podría volverse tedioso y propenso al error si la misma vista es compartida y renderizada desde diferentes lugares. +Para acceder a datos en la vista, normalmente se prefiere el modo push, ya que hace a la vista menos dependiente +de los objetos del contexto. La contra es que tienes que construir el array manualmente cada vez, lo que podría +volverse tedioso y propenso al error si la misma vista es compartida y renderizada desde diferentes lugares. + -### Compartiendo Datos Entre las Vistas +### Compartir Datos Entre las Vistas -El [[yii\base\View|componente view]] provee la propiedad [[yii\base\View::params|params]] para que puedas compartir datos entre diferentes vistas. +El [[yii\base\View|componente view]] provee la propiedad [[yii\base\View::params|params]] para que puedas compartir datos +entre diferentes vistas. -Por ejemplo, en una vista `about`, podrías tener el siguiente código que especifica el segmento actual del breadcrumbs (migas de pan). +Por ejemplo, en una vista `about`, podrías tener el siguiente código que especifica el segmento actual +del breadcrumbs (migas de pan). ```php $this->params['breadcrumbs'][] = 'Acerca de Nosotros'; ``` -Entonces, en el archivo del [layout](#layouts), que es también una vista, puedes mostrar el breadcrumbs utilizando los datos pasados a través de [[yii\base\View::params|params]]: +Entonces, en el archivo del [layout](#layouts), que es también una vista, puedes mostrar el breadcrumbs utilizando los datos +pasados a través de [[yii\base\View::params|params]]: ```php -Los layouts son un tipo especial de vista que representan partes comunes de otras múltiples vistas. Por ejemplo, las páginas de la mayoría de las aplicaciones Web comparten el mismo encabezado y pie de página. Aunque puedes repetirlos en todas y cada una de las vistas, una mejor forma es hacerlo sólo en el layout e incrustar el resultado de la renderización de la vista en un lugar apropiado del mismo. +Los layouts son un tipo especial de vista que representan partes comunes de otras múltiples vistas. Por ejemplo, las páginas +de la mayoría de las aplicaciones Web comparten el mismo encabezado y pie de página. Aunque puedes repetirlos en todas y cada una de las vistas, +una mejor forma es hacerlo sólo en el layout e incrustar el resultado de la renderización de la vista +en un lugar apropiado del mismo. -### Creando Layouts +### Crear Layouts -Dado que los layouts son también vistas, pueden ser creados de manera similar a las vistas comunes. Por defecto, los layouts son guardados en el directorio `@app/views/layouts`. Para layouts utilizados dentro de un [módulo](structure-modules.md), deberían ser guardados en el directorio `views/layouts` bajo el [[yii\base\Module::basePath|directorio del módulo]]. Puedes personalizar el directorio de layouts por defecto configurando la propiedad [[yii\base\Module::layoutPath]] de la aplicación o módulos. +Dado que los layouts son también vistas, pueden ser creados de manera similar a las vistas comunes. Por defecto, los layouts +son guardados en el directorio `@app/views/layouts`. Para layouts utilizados dentro de un [módulo](structure-modules.md), deberían ser guardados +en el directorio `views/layouts` bajo el [[yii\base\Module::basePath|directorio del módulo]]. +Puedes personalizar el directorio de layouts por defecto configurando la propiedad [[yii\base\Module::layoutPath]] +de la aplicación o módulos. -El siguiente ejemplo muestra cómo debe verse un layout. Ten en cuenta que por motivos ilustrativos, hemos simplificado bastante el código del layout. En la práctica, probablemente le agregues más contenido, como tags en el `head`, un menú principal, etc. +El siguiente ejemplo muestra cómo debe verse un layout. Ten en cuenta que por motivos ilustrativos, hemos simplificado +bastante el código del layout. En la práctica, probablemente le agregues más contenido, como tags en el `head`, un menú principal, etc. ```php endPage() ?> ``` -Como puedes ver, el layout genera los tags HTML comunes a todas las páginas. Dentro de la sección ``,el layout imprime la variable `$content`, que representa el resultado de la renderización del contenido de cada vista y es incrustado dentro del layout cuando se llama al método [[yii\base\Controller::render()]]. +Como puedes ver, el layout genera los tags HTML comunes a todas las páginas. Dentro de la sección ``, +el layout imprime la variable `$content`, que representa el resultado de la renderización del contenido de cada vista +y es incrustado dentro del layout cuando se llama al método [[yii\base\Controller::render()]]. -La mayoría de layouts deberían llamar a los siguientes métodos (como fue mostrado recién). Estos métodos principalmente disparan eventos acerca del proceso de renderizado así los scripts y tags registrados en otros lugares pueden ser propiamente inyectados en los lugares donde los métodos son llamados. +La mayoría de layouts deberían llamar a los siguientes métodos (como fue mostrado recién). Estos métodos principalmente disparan eventos +acerca del proceso de renderizado así los scripts y tags registrados en otros lugares pueden ser propiamente inyectados +en los lugares donde los métodos son llamados. -- [[yii\base\View::beginPage()|beginPage()]]: Este método debería ser llamado bien al principio del layout. Esto dispara el evento [[yii\base\View::EVENT_BEGIN_PAGE|EVENT_BEGIN_PAGE]], el cual indica el comienzo de la página. -- [[yii\base\View::endPage()|endPage()]]: Este método debería ser llamado al final del layout. Esto dispara el evento [[yii\base\View::EVENT_END_PAGE|EVENT_END_PAGE]], indicando el final de la página. -- [[yii\web\View::head()|head()]]: Este método debería llamarse dentro de la sección `` de una página HTML. Esto genera un espacio vacío que será reemplazado con el código del head HTML registrado (ej. link tags, meta tags) cuando una página finaliza el renderizado. -- [[yii\base\View::beginBody()|beginBody()]]: Este método debería llamarse al principio de la sección ``. Esto dispara el evento [[yii\web\View::EVENT_BEGIN_BODY|EVENT_BEGIN_BODY]] y genera un espacio vacío que será reemplazado con el código HTML registrado (ej. JavaScript) que apunta al principio del body. -- [[yii\base\View::endBody()|endBody()]]: Este método debería llamarse al final de la sección ``. Esto dispara el evento [[yii\web\View::EVENT_END_BODY|EVENT_END_BODY]], que genera un espacio vacío a ser reemplazado por el código HTML registrado (ej. JavaScript) que apunta al final del body. +- [[yii\base\View::beginPage()|beginPage()]]: Este método debería ser llamado bien al principio del layout. + Esto dispara el evento [[yii\base\View::EVENT_BEGIN_PAGE|EVENT_BEGIN_PAGE]], el cual indica el comienzo de la página. +- [[yii\base\View::endPage()|endPage()]]: Este método debería ser llamado al final del layout. + Esto dispara el evento [[yii\base\View::EVENT_END_PAGE|EVENT_END_PAGE]], indicando el final de la página. +- [[yii\web\View::head()|head()]]: Este método debería llamarse dentro de la sección `` de una página HTML. + Esto genera un espacio vacío que será reemplazado con el código del head HTML registrado (ej. link tags, meta tags) + cuando una página finaliza el renderizado. +- [[yii\base\View::beginBody()|beginBody()]]: Este método debería llamarse al principio de la sección ``. + Esto dispara el evento [[yii\web\View::EVENT_BEGIN_BODY|EVENT_BEGIN_BODY]] y genera un espacio vacío que será reemplazado + con el código HTML registrado (ej. JavaScript) que apunta al principio del body. +- [[yii\base\View::endBody()|endBody()]]: Este método debería llamarse al final de la sección ``. + Esto dispara el evento [[yii\web\View::EVENT_END_BODY|EVENT_END_BODY]], que genera un espacio vacío a ser reemplazado + por el código HTML registrado (ej. JavaScript) que apunta al final del body. -### Accediendo a Datos en Layouts +### Acceder a Datos en Layouts -Dentro de un layout, tienes acceso a dos variables predefinidas: `$this` y `$content`. La primera se refiere al componente [[yii\base\View|view]], como en cualquier vista, mientras que la última contiene el resultado de la renderización del contenido de la vista que está siendo renderizada all llamar al método [[yii\base\Controller::render()|render()]] en los controladores. +Dentro de un layout, tienes acceso a dos variables predefinidas: `$this` y `$content`. La primera se refiere al componente [[yii\base\View|view]], +como en cualquier vista, mientras que la última contiene el resultado de la renderización del contenido de la vista que está siendo renderizada +al llamar al método [[yii\base\Controller::render()|render()]] en los controladores. -Si quieres acceder a otros datos en los layouts, debes utilizar el modo pull que fue descrito en la sub-sección [Accediendo a Datos en la Vista](#accessing-data-in-views). Si quieres pasar datos desde al contenido de la vista a un layout, puedes utilizar el método descrito en la sub-sección [Compartiendo Datos Entre las Vistas](#sharing-data-among-views). +Si quieres acceder a otros datos en los layouts, debes utilizar el modo pull que fue descrito en la sub-sección [Accediendo a Datos en la Vista](#accessing-data-in-views). +Si quieres pasar datos desde al contenido de la vista a un layout, puedes utilizar el método descrito en la +sub-sección [Compartiendo Datos Entre las Vistas](#sharing-data-among-views). -### Utilizando Layouts +### Utilizar Layouts -Como se describe en la sub-sección [Renderizando en Controllers](#rendering-in-controllers), cuando renderizas una vista llamando al método [[yii\base\Controller::render()|render()]] en un controlador, al resultado de dicha renderización le será aplicado un layout. Por defecto, el layout `@app/views/layouts/main.php` será el utilizado. +Como se describe en la sub-sección [Renderizando en Controllers](#rendering-in-controllers), cuando renderizas una vista +llamando al método [[yii\base\Controller::render()|render()]] en un controlador, al resultado de dicha renderización le será aplicado un layout. +Por defecto, el layout `@app/views/layouts/main.php` será el utilizado. -Puedes utilizar un layout diferente configurando la propiedad [[yii\base\Application::layout]] o [[yii\base\Controller::layout]]. El primero se refiere al layout utilizado por todos los controladores, mientras que el último sobrescribe el layout en controladores individuales. Por ejemplo, el siguiente código hace que el controlador `post` utilice `@app/views/layouts/post.php` como layout al renderizar sus vistas. Otros controladores, asumiendo que su propiedad `layout` no fue modificada, utilizarán `@app/views/layouts/main.php` como layout. +Puedes utilizar un layout diferente configurando la propiedad [[yii\base\Application::layout]] o [[yii\base\Controller::layout]]. El primero +se refiere al layout utilizado por todos los controladores, mientras que el último sobrescribe el layout en controladores individuales. +Por ejemplo, el siguiente código hace que el controlador `post` utilice `@app/views/layouts/post.php` como layout al renderizar sus vistas. +Otros controladores, asumiendo que su propiedad `layout` no fue modificada, +utilizarán `@app/views/layouts/main.php` como layout. ```php namespace app\controllers; @@ -320,20 +406,31 @@ class PostController extends Controller } ``` -Para controladores que pertencen a un módulo, puedes también configurar la propiedad [[yii\base\Module::layout|layout]] y así utilizar un layout en particular para esos controladores. +Para controladores que pertencen a un módulo, puedes también configurar la propiedad [[yii\base\Module::layout|layout]] y así utilizar un layout +en particular para esos controladores. -Dado que la propiedad `layout` puede ser configurada en diferentes niveles (controladores, módulos, aplicación), detrás de escena Yii realiza dos pasos para determinar cuál es el archivo de layout siendo utilizado para un controlador en particular. +Dado que la propiedad `layout` puede ser configurada en diferentes niveles (controladores, módulos, aplicación), detrás de escena +Yii realiza dos pasos para determinar cuál es el archivo de layout siendo utilizado para un controlador en particular. En el primer paso, determina el valor del layout y el módulo de contexto: -- Si la propiedad [[yii\base\Controller::layout]] no es `null`, la utiliza como valor del layout y el [[yii\base\Controller::module|módulo]] del controlador como el módulo de contexto. -- Si [[yii\base\Controller::layout|layout]] es `null`, busca a través de todos los módulos ancestros del controlador y encuentra el primer módulo cuya propiedad [[yii\base\Module::layout|layout]] no es `null`. Utiliza ese módulo y su valor de [[yii\base\Module::layout|layout]] como módulo de contexto y como layout seleccionado. Si tal módulo no puede ser encontrado, significa que no se aplicará ningún layout. +- Si la propiedad [[yii\base\Controller::layout]] no es `null`, la utiliza como valor del layout y el [[yii\base\Controller::module|módulo]] + del controlador como el módulo de contexto. +- Si [[yii\base\Controller::layout|layout]] es `null`, busca a través de todos los módulos ancestros del controlador + y encuentra el primer módulo cuya propiedad [[yii\base\Module::layout|layout]] no es `null`. + Utiliza ese módulo y su valor de [[yii\base\Module::layout|layout]] como módulo de contexto y como layout seleccionado. + Si tal módulo no puede ser encontrado, significa que no se aplicará ningún layout. -En el segundo paso, se determina el archivo de layout actual de acuerdo al valor de layout y el módulo de contexto determinado en el primer paso. El valor de layout puede ser: +En el segundo paso, se determina el archivo de layout actual de acuerdo al valor de layout y el módulo de contexto determinado en el primer paso. +El valor de layout puede ser: - un alias de ruta (ej. `@app/views/layouts/main`). -- una ruta absoluta (ej. `/main`): el valor del layout comienza con una barra. El archivo de layout actual será buscado bajo el [[yii\base\Application::layoutPath|layout path]] de la aplicación, que es por defecto `@app/views/layouts`. -- una ruta relativa (ej. `main`): El archivo de layout actual será buscado bajo el [[yii\base\Module::layoutPath|layout path]] del módulo de contexto, que es por defecto el directorio `views/layouts` bajo el [[yii\base\Module::basePath|directorio del módulo]]. +- una ruta absoluta (ej. `/main`): el valor del layout comienza con una barra. El archivo de layout actual será buscado + bajo el [[yii\base\Application::layoutPath|layout path]] de la aplicación, + que es por defecto `@app/views/layouts`. +- una ruta relativa (ej. `main`): El archivo de layout actual será buscado bajo el [[yii\base\Module::layoutPath|layout path]] + del módulo de contexto, que es por defecto el directorio `views/layouts` + bajo el [[yii\base\Module::basePath|directorio del módulo]]. - el valor booleano `false`: no se aplicará ningún layout. Si el valor de layout no contiene una extensión de tipo de archivo, utilizará por defecto `.php`. @@ -341,7 +438,10 @@ Si el valor de layout no contiene una extensión de tipo de archivo, utilizará ### Layouts Anidados -A veces podrías querer anidar un layout dentro de otro. Por ejemplo, en diferentes secciones de un sitio Web, podrías querer utilizar layouts diferentes, mientras que todos esos layouts comparten el mismo layout básico que genera la estructura general de la página en HTML5. Esto es posible llamando a los métodos [[yii\base\View::beginContent()|beginContent()]] y [[yii\base\View::endContent()|endContent()]] en los layouts hijos como se muestra a continuación: +A veces podrías querer anidar un layout dentro de otro. Por ejemplo, en diferentes secciones de un sitio Web, +podrías querer utilizar layouts diferentes, mientras que todos esos layouts comparten el mismo layout básico que genera +la estructura general de la página en HTML5. Esto es posible llamando a los métodos +[[yii\base\View::beginContent()|beginContent()]] y [[yii\base\View::endContent()|endContent()]] en los layouts hijos como se muestra a continuación: ```php beginContent('@app/views/layouts/base.php'); ?> @@ -351,14 +451,80 @@ A veces podrías querer anidar un layout dentro de otro. Por ejemplo, en diferen endContent(); ?> ``` -Como se acaba de mostrar, el contenido del layout hijo debe ser encerrado dentro de [[yii\base\View::beginContent()|beginContent()]] y [[yii\base\View::endContent()|endContent()]]. El parámetro pasado a [[yii\base\View::beginContent()|beginContent()]] especifica cuál es el módulo padre. Este puede ser tanto un archivo layout como un alias. +Como se acaba de mostrar, el contenido del layout hijo debe ser encerrado dentro de [[yii\base\View::beginContent()|beginContent()]] +y [[yii\base\View::endContent()|endContent()]]. El parámetro pasado a [[yii\base\View::beginContent()|beginContent()]] +especifica cuál es el módulo padre. Este puede ser tanto un archivo layout como un alias. Utilizando la forma recién mencionada, puedes anidar layouts en más de un nivel. -## Utilizando Componentes de Vista +### Utilizar Blocks + +Los bloques te permiten especificar el contenido de la vista en un lugar y mostrarlo en otro. Estos son a menudo utilizados junto a +los layouts. Por ejemplo, puedes definir un bloque un una vista de contenido y mostrarla en el layout. + +Para definir un bloque, llamas a [[yii\base\View::beginBlock()|beginBlock()]] y [[yii\base\View::endBlock()|endBlock()]]. +El bloque puede ser accedido vía `$view->blocks[$blockID]`, donde `$blockID` se refiere al ID único que le asignas +al bloque cuando lo defines. + +El siguiente ejemplo muestra cómo utilizar bloques para personalizar partes especificas del layout in una vista. -Los [[yii\base\View|componentes de vista]] proveen características relacionadas a las vistas. Aunque puedes obtener componentes de vista creando instancias individuales de [[yii\base\View]] o sus clases hijas, en la mayoría de los casos utilizarías el componente `view` del a aplicación. Puedes configurar este componente en la [configuración de la aplicación](structure-applications.md#application-configurations) como a continuación: +Primero, en una vista, define uno o varios bloques: + +```php +... + +beginBlock('block1'); ?> + +...contenido de block1... + +endBlock(); ?> + +... + +beginBlock('block3'); ?> + +...contenido de block3... + +endBlock(); ?> +``` + +Entonces, en la vista del layout, renderiza los bloques si están disponibles, o muestra un contenido por defecto si el bloque +no está definido. + +```php +... +blocks['block1'])): ?> + blocks['block1'] ?> + + ... contenido por defecto de block1 ... + + +... + +blocks['block2'])): ?> + blocks['block2'] ?> + + ... contenido por defecto de block2 ... + + +... + +blocks['block3'])): ?> + blocks['block3'] ?> + + ... contenido por defecto de block3 ... + +... +``` + + +## Utilizar Componentes de Vista + +Los [[yii\base\View|componentes de vista]] proveen características relacionadas a las vistas. Aunque puedes obtener componentes de vista +creando instancias individuales de [[yii\base\View]] o sus clases hijas, en la mayoría de los casos utilizarías el componente `view` del a aplicación. +Puedes configurar este componente en la [configuración de la aplicación](structure-applications.md#application-configurations) +como a continuación: ```php [ @@ -386,13 +552,15 @@ Puedes también utilizar frecuentemente el siguiente menor pero útil grupo de c ### Definiendo Títulos de Página -Toda página Web debería tener un título. Normalmente el tag de título es generado en [layout](#layouts). De todos modos, en la práctica el título es determinado en el contenido de las vistas más que en layouts. Para resolver este problema, [[yii\web\View]] provee la propiedad [[yii\web\View::title|title]] para que puedas pasar información del título desde el contenido de la vista a los layouts. +Toda página Web debería tener un título. Normalmente el tag de título es generado en [layout](#layouts). De todos modos, en la práctica +el título es determinado en el contenido de las vistas más que en layouts. Para resolver este problema, [[yii\web\View]] provee +la propiedad [[yii\web\View::title|title]] para que puedas pasar información del título desde el contenido de la vista a los layouts. Para utilizar esta característica, en cada contenido de la vista, puedes definir el título de la siguiente manera: ```php title = 'Mi título de página'; +$this->title = 'Título de mi página'; ?> ``` @@ -403,11 +571,13 @@ Entonces en el layout, asegúrate de tener el siguiente código en la sección ` ``` -### Registrando Meta Tags +### Registrar Meta Tags -Las páginas Web usualmente necesitan generar varios meta tags necesarios por diferentes grupos (ej. Facebook, motores de búsqueda, etc). Cómo los títulos de página, los meta tags aparecen en la sección `` y son usualmente generado en los layouts. +Las páginas Web usualmente necesitan generar varios meta tags necesarios para diferentes grupos. Cómo los títulos de página, los meta tags +aparecen en la sección `` y son usualmente generado en los layouts. -Si quieres especificar cuáles meta tags generar en las vistas, puedes llamar a [[yii\web\View::registerMetaTag()]] dentro de una de ellas, como se muestra a continuación: +Si quieres especificar cuáles meta tags generar en las vistas, puedes llamar a [[yii\web\View::registerMetaTag()]] +dentro de una de ellas, como se muestra a continuación: ```php registerMetaTag(['name' => 'keywords', 'content' => 'yii, framework, php' ?> ``` -El código anterior registrará el meta tag "keywords" a través del componente view. El meta tag registrado no se renderiza hasta que finaliza el renderizado del layout. Para entonces, el siguiente código HTML será insertado en el lugar donde llamas a [[yii\web\View::head()]] en el layout, generando el siguiente HTML: +El código anterior registrará el meta tag "keywords" a través del componente view. El meta tag registrado +no se renderiza hasta que finaliza el renderizado del layout. Para entonces, el siguiente código HTML será insertado +en el lugar donde llamas a [[yii\web\View::head()]] en el layout, generando el siguiente HTML: ```php ``` -Ten en cuenta que si llamas a [[yii\web\View::registerMetaTag()]] varias veces, esto registrará varios meta tags, sin tener en cuenta si los meta tags son los mismo o no. +Ten en cuenta que si llamas a [[yii\web\View::registerMetaTag()]] varias veces, esto registrará varios meta tags, +sin tener en cuenta si los meta tags son los mismo o no. -Para asegurarte de que sólo haya una instancia de cierto tipo de meta tag, puedes especificar una clave al llamar al método. Por ejemplo, el siguiente código registra dos meta tags "description", aunque sólo el segundo será renderizado. +Para asegurarte de que sólo haya una instancia de cierto tipo de meta tag, puedes especificar una clave al llamar al método. +Por ejemplo, el siguiente código registra dos meta tags "description", aunque sólo el segundo será renderizado. -```html +```php $this->registerMetaTag(['name' => 'description', 'content' => 'Este es mi sitio Web cool hecho con Yii!'], 'description'); $this->registerMetaTag(['name' => 'description', 'content' => 'Este sitio Web es sobre mapaches graciosos.'], 'description'); ``` -### Registrando Link Tags +### Registrar Link Tags -Tal como los [meta tags](#adding-meta-tags), los link tags son útiles en muchos casos, como personalizar el ícono (favicon) del sitio, apuntar a una fuente de RSS o delegar OpenID a otro servidor. Puedes trabajar con link tags, al igual que con meta tags, utilizando [[yii\web\View::registerLinkTag()]]. Por ejemplo, en el contenido de una vista, puedes registrar un link tag como se muestra a continuación: +Tal como los [meta tags](#adding-meta-tags), los link tags son útiles en muchos casos, como personalizar el ícono (favicon) del sitio, +apuntar a una fuente de RSS o delegar OpenID a otro servidor. Puedes trabajar con link tags, al igual que con meta tags, +utilizando [[yii\web\View::registerLinkTag()]]. Por ejemplo, en el contenido de una vista, puedes registrar un link tag como se muestra a continuación: ```php $this->registerLinkTag([ @@ -450,16 +626,20 @@ El resultado del código es el siguiente: ``` -Al igual que con [[yii\web\View::registerMetaTag()|registerMetaTags()]], puedes especificar una clave al llamar a [[yii\web\View::registerLinkTag()|registerLinkTag()]] para evitar registrar link tags repetidos. +Al igual que con [[yii\web\View::registerMetaTag()|registerMetaTags()]], puedes especificar una clave al llamar +a [[yii\web\View::registerLinkTag()|registerLinkTag()]] para evitar registrar link tags repetidos. ## Eventos de Vistas -Los [[yii\base\View|componentes de vistas]] disparan varios eventos durante el proceso de renderizado de la vista. Puedes responder a estos eventos para inyectar contenido a la vista o procesar el resultado de la renderización antes de que sea enviada al usuario final. +Los [[yii\base\View|componentes de vistas]] disparan varios eventos durante el proceso de renderizado de la vista. Puedes responder +a estos eventos para inyectar contenido a la vista o procesar el resultado de la renderización antes de que sea enviada al usuario final. -- [[yii\base\View::EVENT_BEFORE_RENDER|EVENT_BEFORE_RENDER]]: disparado al principio del renderizado de un archivo en un controlador. Los manejadores de este evento pueden definir [[yii\base\ViewEvent::isValid]] como `false` para cancelar el proceso de renderizado. -- [[yii\base\View::EVENT_AFTER_RENDER|EVENT_AFTER_RENDER]]: disparado por la llamada a [[yii\base\View::beginPage()]] en layouts. -Los manejadores de este evento pueden obtener el resultado de la renderización a través de [[yii\base\ViewEvent::output]] y entonces modificar esta propiedad para así cambiar el mismo. +- [[yii\base\View::EVENT_BEFORE_RENDER|EVENT_BEFORE_RENDER]]: disparado al principio del renderizado de un archivo + en un controlador. Los manejadores de este evento pueden definir [[yii\base\ViewEvent::isValid]] como `false` para cancelar el proceso de renderizado. +- [[yii\base\View::EVENT_AFTER_RENDER|EVENT_AFTER_RENDER]]: disparado luego de renderizar un archivo con la llamada de [[yii\base\View::afterRender()]]. + Los manejadores de este evento pueden obtener el resultado del renderizado a través de [[yii\base\ViewEvent::output]] y modificar + esta propiedad para cambiar dicho resultado. - [[yii\base\View::EVENT_BEGIN_PAGE|EVENT_BEGIN_PAGE]]: disparado por la llamada a [[yii\base\View::beginPage()]] en layouts. - [[yii\base\View::EVENT_END_PAGE|EVENT_END_PAGE]]: disparado por la llamada a [[yii\base\View::endPage()]] en layouts. - [[yii\web\View::EVENT_BEGIN_BODY|EVENT_BEGIN_BODY]]: disparado por la llamada a [[yii\web\View::beginBody()]] en layouts. @@ -474,9 +654,10 @@ Por ejemplo, el siguiente código inyecta la fecha actual al final del body de l ``` -## Renderizando Páginas Estáticas +## Renderizar Páginas Estáticas -Con páginas estáticas nos referimos a esas páginas cuyo contenido es mayormente estático y sin necesidad de acceso a datos dinámicos enviados desde los controladores. +Con páginas estáticas nos referimos a esas páginas cuyo contenido es mayormente estático y sin necesidad de acceso +a datos dinámicos enviados desde los controladores. Puedes generar páginas estáticas utilizando un código como el que sigue dentro de un controlador: @@ -487,7 +668,9 @@ public function actionAbout() } ``` -Si un sitio Web contiene muchas páginas estáticas, resultaría tedioso repetir el mismo código en muchos lados. Para resolver este problema, puedes introducir una [acción independiente](structure-controllers.md#standalone-actions) llamada [[yii\web\ViewAction]] en el controlador. Por ejemplo, +Si un sitio Web contiene muchas páginas estáticas, resultaría tedioso repetir el mismo código en muchos lados. +Para resolver este problema, puedes introducir una [acción independiente](structure-controllers.md#standalone-actions) +llamada [[yii\web\ViewAction]] en el controlador. Por ejemplo, ```php namespace app\controllers; @@ -507,13 +690,16 @@ class SiteController extends Controller } ``` -Ahora, si creamos una vista llamada `about` bajo el directorio `@app/views/site/pages`, serás capáz de mostrarla en la siguiente URL: +Ahora, si creamos una vista llamada `about` bajo el directorio `@app/views/site/pages`, serás capáz de mostrarla +en la siguiente URL: ``` -http://localhost/index.php?r=site/page&view=about +http://localhost/index.php?r=site%2Fpage&view=about ``` -El parámetro `GET` `view` le comunica a [[yii\web\ViewAction]] cuál es la vista solicitada. La acción entonces buscará esta vista dentro de `@app/views/site/pages`. Puedes configurar la propiedad [[yii\web\ViewAction::viewPrefix]] para cambiar el directorio en el que se buscarán dichas páginas. +El parámetro `GET` `view` le comunica a [[yii\web\ViewAction]] cuál es la vista solicitada. La acción entonces buscará +esta vista dentro de `@app/views/site/pages`. Puedes configurar la propiedad [[yii\web\ViewAction::viewPrefix]] +para cambiar el directorio en el que se buscarán dichas páginas. ## Buenas Prácticas @@ -523,12 +709,15 @@ Las vistas son responsables de la presentación de modelos en el formato que el * deberían contener principalmente sólo código de presentación, como HTML, y PHP simple para recorrer, dar formato y renderizar datos. * no deberían contener código que realiza consultas a la base de datos. Ese tipo de código debe ir en los modelos. * deberían evitar el acceso directo a datos del `request`, como `$_GET` y/o `$_POST`. Esto es una responsabilidad de los controladores. -Si se necesitan datos del `request`, deben ser inyectados a la vista desde el controlador. + Si se necesitan datos del `request`, deben ser inyectados a la vista desde el controlador. * pueden leer propiedades del modelo, pero no debería modificarlas. -Para hacer las vistas más manejables, evita crear vistas que son demasiado complejas o que contengan código redundante. Puedes utilizar estas técnicas para alcanzar dicha meta: +Para hacer las vistas más manejables, evita crear vistas que son demasiado complejas o que contengan código redundante. +Puedes utilizar estas técnicas para alcanzar dicha meta: * utiliza [layouts](#layouts) para representar secciones comunes (ej. encabezado y footer de la página). -* divide una vista compleja en varias más simples. Las vistas pequeñas pueden ser renderizadas y unidas una mayor utilizando los métodos de renderización antes descritos. +* divide una vista compleja en varias más simples. Las vistas pequeñas pueden ser renderizadas y unidas una mayor + utilizando los métodos de renderización antes descritos. * crea y utiliza [widgets](structure-widgets.md) como bloques de construcción de la vista. * crea y utilizar helpers para transformar y dar formato a los datos en la vista. + diff --git a/docs/guide-es/test-functional.md b/docs/guide-es/test-functional.md new file mode 100644 index 0000000..ee53ebc --- /dev/null +++ b/docs/guide-es/test-functional.md @@ -0,0 +1,11 @@ +Tests Funcionales +================= + +> Note: Esta sección se encuentra en desarrollo. + +- [Tests Funcionales de Codeception](http://codeception.com/docs/04-FunctionalTests) + +Ejecutar test funcionales de templates básicos y avanzados +---------------------------------------------------------- + +Por favor consulta las instrucciones provistas en `apps/advanced/tests/README.md` y `apps/basic/tests/README.md`. diff --git a/docs/guide-es/tutorial-yii-integration.md b/docs/guide-es/tutorial-yii-integration.md index 50cf91d..20739f1 100644 --- a/docs/guide-es/tutorial-yii-integration.md +++ b/docs/guide-es/tutorial-yii-integration.md @@ -1,23 +1,27 @@ -Trabajando con código de terceros -================================= +Trabajar con código de terceros +=============================== -De tiempo en tiempo, puede necesitar usar algún código de terceros en sus aplicaciones Yii. O puedes querer usar Yii como una librería en otros sistemas de terceros. En esta sección, te enseñaremos cómo conseguir estos objetivos. +De tiempo en tiempo, puede necesitar usar algún código de terceros en sus aplicaciones Yii. O puedes querer +utilizar Yii como una librería en otros sistemas de terceros. En esta sección, te enseñaremos cómo conseguir estos objetivos. -## Usando librerías de terceros en Yii - -Para usar una librería en una aplicación Yii, primeramente debes de asegurarte que las clases een la librería son incluidas adecuadamente o pueden ser cargadas de forma automática. +Utilizar librerías de terceros en Yii +------------------------------------- +Para usar una librería en una aplicación Yii, primeramente debes de asegurarte que las clases en la librería +son incluidas adecuadamente o pueden ser cargadas de forma automática. ### Usando Paquetes de Composer Muchas librerías de terceros son liberadas en términos de paquetes [Composer](https://getcomposer.org/). -Puedes instalar este tipo de librerias siguiendo dos sencillos pasos: +Puedes instalar este tipo de librerías siguiendo dos sencillos pasos: 1. modificar el fichero `composer.json` de tu aplicación y especificar que paquetes Composer quieres instalar. -2. ejecuta `composer install` para instalar los paquetes específicados. +2. ejecuta `composer install` para instalar los paquetes especificados. -Las clases en los paquetes Composer instalados pueden ser autocargados usando el cargador automatizado de Composer autoloader. Asegúrate que el fichero [script de entrada](structure-entry-scripts.md) de tu aplicación contiene las siguientes líneas para instalar el cargador automático de Composer: +Las clases en los paquetes Composer instalados pueden ser autocargados usando el cargador automatizado de Composer autoloader. +Asegúrate que el fichero [script de entrada](structure-entry-scripts.md) de tu aplicación contiene las siguientes líneas +para instalar el cargador automático de Composer: ```php // instalar el cargador automático de Composer @@ -27,15 +31,21 @@ require(__DIR__ . '/../vendor/autoload.php'); require(__DIR__ . '/../vendor/yiisoft/yii2/Yii.php'); ``` - ### Usando librerías Descargadas Si la librería no es liberada como un paquete de Composer, debes de seguir sus instrucciones de instalación para instalarla. -En muchos casos, puedes necesitar descargar manualmente el fichero de la versión y desempaquetarlo en el directorio `BasePath/vendor` , donde `BasePath` representa el [camino base (base path)](structure-applications.md#basePath) de tu aplicación. +En muchos casos, puedes necesitar descargar manualmente el fichero de la versión y desempaquetarlo en el directorio `BasePath/vendor`, +donde `BasePath` representa el [camino base (base path)](structure-applications.md#basePath) de tu aplicación. -Si la librería lleva su propio cargador automático (autoloader), puedes instalarlo en [script de entrada](structure-entry-scripts.md) de tu aplicación. Es recomendable que la instalación se termine antes de incluir el fichero `Yii.php` de forma que el cargador automático tenga precedencia al cargar de forma automática las clases. +Si la librería lleva su propio cargador automático (autoloader), puedes instalarlo en [script de entrada](structure-entry-scripts.md) de tu aplicación. +Es recomendable que la instalación se termine antes de incluir el fichero `Yii.php` de forma que el cargador automático tenga precedencia al cargar +de forma automática las clases. -Si la librería no provee un cargador automático de clases, pero la denominación de sus clases sigue el [PSR-4](http://www.php-fig.org/psr/psr-4/), puedes usar el cargador automático de Yii para cargar de forma automática las clases. Todo lo que necesitas es declarar un [alias raiz](concept-aliases.md#defining-aliases) para cada espacio de nombres (namespace) raiz usado en sus clases. Por ejemplo, asume que has instalado una librería en el directorio `vendor/foo/bar`, y que las clases de la librería están bajo el espacio de nombres raiz `xyz`. Puedes incluir el siguiente código en la configuración de tu aplicación: +Si la librería no provee un cargador automático de clases, pero la denominación de sus clases sigue el [PSR-4](http://www.php-fig.org/psr/psr-4/), +puedes usar el cargador automático de Yii para cargar de forma automática las clases. Todo lo que necesitas +es declarar un [alias raíz](concept-aliases.md#defining-aliases) para cada espacio de nombres (namespace) raiz usado en sus clases. Por ejemplo, +asume que has instalado una librería en el directorio `vendor/foo/bar`, y que las clases de la librería están bajo el espacio de nombres raiz `xyz`. +Puedes incluir el siguiente código en la configuración de tu aplicación: ```php [ @@ -45,30 +55,61 @@ Si la librería no provee un cargador automático de clases, pero la denominaci ] ``` -Si ninguno de lo anterior es el caso, estaría bien que la librería dependa del camino de inclusión (include path) de configuración de PHP para localizar correctamente e incluir los ficheros de las clases. Simplemente siguiendo estas instrucciones de cómo configurar el camino de inclusión de PHP. +Si ninguno de lo anterior es el caso, estaría bien que la librería dependa del camino de inclusión (include path) de configuración de PHP +para localizar correctamente e incluir los ficheros de las clases. Simplemente siguiendo estas instrucciones de cómo configurar el camino de inclusión de PHP. -En el caso más grave en el que la librería necesite incluir cada uno de sus ficheros de clases, puedes usar el siguiente método para incluir las clases según se pidan: +En el caso más grave en el que la librería necesite incluir cada uno de sus ficheros de clases, puedes usar el siguiente método +para incluir las clases según se pidan: * Identificar que clases contiene la librería. -* Listar las clases y el camino a los ficheros correspondientes en `Yii::$classMap` en el script de entrada [script de entrada](structure-entry-scripts.md) de la aplicación. Por ejemplo, +* Listar las clases y el camino a los archivos correspondientes en `Yii::$classMap` en el script de entrada [script de entrada](structure-entry-scripts.md) + de la aplicación. Por ejemplo, ```php Yii::$classMap['Class1'] = 'path/to/Class1.php'; Yii::$classMap['Class2'] = 'path/to/Class2.php'; ``` -## Usando Yii en Sistemas de Terceros +Utilizar Yii en Sistemas de Terceros +------------------------------------ -Debido a que Yii provee muchas posibilidades excelentes, a veces puedes querer usar alguna de sus características para permitir el desarrollo o mejora de sistemas de terceros, como es WordPress, Joomla, o aplicaciones desarrolladas usando otros frameworks de PHP. Por ejemplo, puedes queres usar la clase [[yii\helpers\ArrayHelper]] o usar la característica [Active Record](db-active-record.md) en un sistema de terceros. Para lograr este objetivo, principalmente necesitas realizar dos pasos: instalar Yii , e iniciar Yii. +Debido a que Yii provee muchas posibilidades excelentes, a veces puedes querer usar alguna de sus características para permitir +el desarrollo o mejora de sistemas de terceros, como es WordPress, Joomla, o aplicaciones desarrolladas usando otros frameworks de PHP. +Por ejemplo, puedes querer utilizar la clase [[yii\helpers\ArrayHelper]] o usar la característica [Active Record](db-active-record.md) +en un sistema de terceros. Para lograr este objetivo, principalmente necesitas realizar dos pasos: +instalar Yii , e iniciar Yii. -Si el sistema de terceros usa Composer para manejar sus dependencias, simplemente ejecuta estos comandos para instalar Yii: +Si el sistema de terceros usa Composer para manejar sus dependencias, simplemente ejecuta estos comandos +para instalar Yii: + composer global require "fxp/composer-asset-plugin:~1.1.1" + composer require yiisoft/yii2 + composer install + +El primer comando instala el [composer asset plugin](https://github.com/francoispluchino/composer-asset-plugin/), +que permite administrar paquetes bower y npm a través de Composer. Incluso si sólo quieres utilizar la capa de base de datos +u otra característica de Yii no relacionada a assets, requiere que instales el paquete composer de Yii. + +Si quieres utilizar la [publicación de Assets de Yii](structure-assets.md) deberías agregar también la siguiente configuración +a la sección `extra` de tu `composer.json`: + +```json +{ + ... + "extra": { + "asset-installer-paths": { + "npm-asset-library": "vendor/npm", + "bower-asset-library": "vendor/bower" + } + } +} ``` -composer require "yiisoft/yii2:*" -composer install -``` -En otro caso, puedes [descargar](http://www.yiiframework.com/download/) el fichero de la edición de Yii y desempaquetarla en el directorio `BasePath/vendor`. +Visita también la [sección de cómo instalar Yii](start-installation.md#installing-via-composer) para más información +sobre Composer y sobre cómo solucionar posibles problemas que surjan durante la instalación. + +En otro caso, puedes [descargar](http://www.yiiframework.com/download/) el archivo de la edición de Yii +y desempaquetarla en el directorio `BasePath/vendor`. Después, debes de modificar el script de entrada de sistema de terceros para incluir el siguiente código al principio: @@ -79,24 +120,32 @@ $yiiConfig = require(__DIR__ . '/../config/yii/web.php'); new yii\web\Application($yiiConfig); // No ejecutes run() aquí ``` -Como puedes ver, el código anterior es muy similar al que puedes ver en [script de entrada](structure-entry-scripts.md) de una aplicación típica. La única diferencia es que después de que se crea la instancia de la aplicación, el método `run()` no es llamado. Esto es así porque llamando a `run()`, Yii se haría cargo del control del flujo de trabajo del manejo de las peticiones, lo cual no es necesario en este caso por estar ya es manejado por la aplicación existente. +Como puedes ver, el código anterior es muy similar al que puedes ver en [script de entrada](structure-entry-scripts.md) +de una aplicación típica. La única diferencia es que después de que se crea la instancia de la aplicación, el método `run()` no es llamado. +Esto es así porque llamando a `run()`, Yii se haría cargo del control del flujo de trabajo del manejo de las peticiones, +lo cual no es necesario en este caso por estar ya es manejado por la aplicación existente. -Como en una aplicación Yii, debes configurar la instancia de la aplicación basándose en el entorno que se está ejecutando del sistema de terceros. Por ejemplo, para usar la característica [Active Record](db-active-record.md) , necesitas configurar `db` [componente de la aplicación](structure-application-components.md) con los parámetros de la conexión de base de datos usados por el sistema de terceros. +Como en una aplicación Yii, debes configurar la instancia de la aplicación basándose en el entorno que se está +ejecutando del sistema de terceros. Por ejemplo, para usar la característica [Active Record](db-active-record.md), necesitas configurar +el [componente de la aplicación](structure-application-components.md) `db` con los parámetros de la conexión a la BD del sistema de terceros. -Ahora puedes usar muchas características provistas por Yii. Por ejemplo, puedes crear clases Active Record y usarlas para trabajar con bases de datos. +Ahora puedes usar muchas características provistas por Yii. Por ejemplo, puedes crear clases Active Record y usarlas +para trabajar con bases de datos. -## Usando Yii 2 con Yii 1 - -Si estaba usando Yii 1 previamente, es como si tuvieras una aplicación Yii 1 funcionando. En vez de reescribir toda la aplicación en Yii 2, puedes solamente mejorarla usando alguna de las características sólo disponibles en Yii 2. +Utilizar Yii 2 con Yii 1 +------------------------ +Si estaba usando Yii 1 previamente, es como si tuvieras una aplicación Yii 1 funcionando. En vez de reescribir +toda la aplicación en Yii 2, puedes solamente mejorarla usando alguna de las características sólo disponibles en Yii 2. Esto se puede lograr tal y como se describe abajo. -> Note: Yii 2 requiere PHP 5.4 o superior. Debes de estar seguro que tanto tu servidor como la aplicación existente lo soportan. +> Note: Yii 2 requiere PHP 5.4 o superior. Debes de estar seguro que tanto tu servidor como la aplicación +> existente lo soportan. Primero, instala Yii 2 en tu aplicación siguiendo las instrucciones descritas en la [última subsección](#using-yii-in-others). -Segundo,modifica el script de entrada de la aplicación como sigue, +Segundo, modifica el script de entrada de la aplicación como sigue, ```php // incluir la clase Yii personalizada descrita debajo @@ -112,7 +161,6 @@ Yii::createWebApplication($yii1Config)->run(); ``` Debido a que ambos Yii 1 y Yii 2 tiene la clase `Yii` , debes crear una versión personalizada para combinarlas. - El código anterior incluye el fichero con la clase `Yii` personalizada, que tiene que ser creada como sigue. ```php @@ -128,15 +176,17 @@ class Yii extends \yii\BaseYii } Yii::$classMap = include($yii2path . '/classes.php'); -// registrar el autoloader de Yii2 autoloader via Yii1 +// registrar el autoloader de Yii 2 vía Yii 1 Yii::registerAutoloader(['Yii', 'autoload']); // crear el contenedor de inyección de dependencia Yii::$container = new yii\di\Container; ``` -¡Esto es todo!. Ahora, en cualquier parte de tu código, puedes usar `Yii::$app` para acceder a la instancia de la aplicación de Yii 2, mientras `Yii::app()` proporciona la instancia de la aplicación de Yii 1 : +¡Esto es todo!. Ahora, en cualquier parte de tu código, puedes usar `Yii::$app` para acceder a la instancia de la aplicación de Yii 2, +mientras `Yii::app()` proporciona la instancia de la aplicación de Yii 1 : ```php echo get_class(Yii::app()); // genera 'CWebApplication' echo get_class(Yii::$app); // genera 'yii\web\Application' ``` + From f89307655bddee14b60819538a77644cca8a5dde Mon Sep 17 00:00:00 2001 From: hableel Date: Sat, 4 Jun 2016 22:57:15 +0300 Subject: [PATCH 81/90] Update yii.php (#11694) Add a missing translation [skip ci] --- framework/messages/ar/yii.php | 1 + 1 file changed, 1 insertion(+) diff --git a/framework/messages/ar/yii.php b/framework/messages/ar/yii.php index 01a8266..847f709 100644 --- a/framework/messages/ar/yii.php +++ b/framework/messages/ar/yii.php @@ -66,6 +66,7 @@ return [ '{attribute} must be a string.' => '{attribute} يجب أن يكون كلمات', '{attribute} must be an integer.' => '{attribute} يجب أن يكون رقمًا صحيحًا', '{attribute} must be either "{true}" or "{false}".' => '{attribute} يجب أن يكن إما "{true}" أو "{false}".', + '{attribute} must be equal to "{compareValueOrAttribute}".' => '{attribute} يجب أن يساوي "{compareValueOrAttribute}".', '{attribute} must be greater than "{compareValue}".' => '{attribute} يجب أن يكون أكبر من "{compareValue}".', '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute} يجب أن يكون أكبر من أو يساوي "{compareValue}".', '{attribute} must be less than "{compareValue}".' => '{attribute} يجب أن يكون أصغر من "{compareValue}".', From ff5f7b5cf1ea1fd3fa5cdad8c594cc5156c9b50f Mon Sep 17 00:00:00 2001 From: Macbenach Date: Sat, 4 Jun 2016 14:59:16 -0500 Subject: [PATCH 82/90] [docs] [skip-ci] RBAC Spanish Translation (#11675) * [docs] [skip-ci] RBAC Spanish Translation * Update security-authorization.md --- docs/guide-es/security-authorization.md | 524 ++++++++++++++++++++++++++++++++ 1 file changed, 524 insertions(+) create mode 100644 docs/guide-es/security-authorization.md diff --git a/docs/guide-es/security-authorization.md b/docs/guide-es/security-authorization.md new file mode 100644 index 0000000..4750003 --- /dev/null +++ b/docs/guide-es/security-authorization.md @@ -0,0 +1,524 @@ +Autorización +============= + +La autorización es el proceso para verificar que un usuario tiene permitido realizar algo. Yii provee dos metodos de autorización: + Filtro de Control de Acceso (Access Control Filter-ACF) y Control de Acceso Basado en Roles (Role-Based Access Control-RBAC). + + +## Filtro de Control de Acceso (ACF) + +El Filtro de Control de Acceso (ACF) es un metodo simple de autorizacion implementado como [[yii\filters\AccessControl]] el cual +es la mejor opcion para ser usado en aplicaciones que requieren un control de acceso sencillo. Como su nombre lo indica, ACF es una accion +de [filter](structure-filters.md) que puede ser usada por un controlador o un módulo. Mientras un usuario este solicitando +la ejecucion de una accion, ACF se encargara de verificar en la lista de [[yii\filters\AccessControl::rules|access rules]] +para determinar si el usuario esta autorizado para acceder a la accion solicitada. + +El codigo siguiente muestra como utilizar ACF en el controlador `site` : + +```php +use yii\web\Controller; +use yii\filters\AccessControl; + +class SiteController extends Controller +{ + public function behaviors() + { + return [ + 'access' => [ + 'class' => AccessControl::className(), + 'only' => ['login', 'logout', 'signup'], + 'rules' => [ + [ + 'allow' => true, + 'actions' => ['login', 'signup'], + 'roles' => ['?'], + ], + [ + 'allow' => true, + 'actions' => ['logout'], + 'roles' => ['@'], + ], + ], + ], + ]; + } + // ... +} +``` + +En el codigo anterior ACF esta unido al controlador `site` como un behavior. Esta es la forma tipica de la utilizacion del filtro en una accion. +La opción `only` especifica como ACF debe aplicarse a las acciones `login`, `logout` y `signup`. +Todas las otras acciones en el controlador `site` no son asunto del control de acceso. La lista de opciones de `rules` +del [[yii\filters\AccessRule|access rules]], dice lo siguiente: + +- Permitir a todos los Invitados (los usuarios que no estan autenticados) acceder a las acciones `login` y `signup`. La opcion `roles` + contiene un signo de interrogacion `?` el cual es una especia de token que representa a los usuarios que sean "usuarios invitados". +- Todos los usuarios autenticados pueden acceder a la accion `logout`. El caracter `@` es otro token especial que representa a todos los "usuarios autenticados". + +ACF lleva a cabo la verificación de autorización mediante el examen de las reglas de acceso de uno en uno de arriba a abajo hasta que encuentra +una regla que se relaciones con el contexto de ejecucion. El valor `allow` de la regla de coincidencia luego serán utilizados para +juzgar si el usuario esta autorizado o no. Si ninguna de las reglas se relaciona, significa que el usuario no esta autorizado, +y ACF detendra la ejecucion de la accion.. + +Cuando ACF determina que un usuario no esta autorizado para acceder a una accion, Toma alguna de las siguientes medidas por default: + +* Si el usuario es un invitado, llamara a [[yii\web\User::loginRequired()]] y redirigir al usuario mediante el navegador a la pagina de login. +* Si el usuario esta autenticado, arrojara una [[yii\web\ForbiddenHttpException]]. + +Puedes personalizar este comportamiento (behavior) mediante la configuracion de la propiedad [[yii\filters\AccessControl::denyCallback]], por ejemplo: + +```php +[ + 'class' => AccessControl::className(), + ... + 'denyCallback' => function ($rule, $action) { + throw new \Exception('You are not allowed to access this page'); + } +] +``` + +[[yii\filters\AccessRule|Access rules]] somporta algunas opciones. A continuacion se muestra un resumen de las opciones soportadas. +Tambien puedes extender [[yii\filters\AccessRule]] para crear tus propias clases de reglas personalizadas. + + * [[yii\filters\AccessRule::allow|allow]]: Especifica si se trata de una regla para "permitir" ("allow") o de una regla "denegar" ("deny") . + + * [[yii\filters\AccessRule::actions|actions]]: especifica cual de las acciones de esta regla se relaciona. Este deberia ser una matriz de IDs de accion. + La comparacion entre mayúsculas y minúsculas. Si esta opcion esta vacia (empty) o no esta asignada, + significa que la regla se aplica a todas las acciones. + + * [[yii\filters\AccessRule::controllers|controllers]]: especifica a cual de los controladores se relaciona esta regla. + Esta deberia ser un arreglo de IDs de controladores. Cada ID de controlador es prefijado con el modulo ID (si cualquier). +La comparacion se hace entre mayúsculas y minúsculas.Si esta opcion esta vacia o no esta asignada, significa que la regla aplica a todo el controlador. + + * [[yii\filters\AccessRule::roles|roles]]: especifica a que rol de los usuario esta regla se relaciona. + Dos roles especiales son reconocidos, y ellos son verificados por medio de [[yii\web\User::isGuest]] + + - `?`: Esta relacionado con usuarios invitados (no autenticados). + - `@`: Esta relacionado a usuarios que estan autenticados. + + Usando otros nombres de rol este disparara la invocacion de [[yii\web\User::can()]], el cual requiere que este habilitado RBAC + (Sera descrito en la siguiente subsección). Si esta opcion esta vacia o no esta asignada, esta regla aplica para todos los roles. + + * [[yii\filters\AccessRule::ips|ips]]: esta especifica a cual [[yii\web\Request::userIP|client IP addresses]] esta regla se relaciona. +Una direccion IP puede contener el comodin `*` al final para que coincida con las direcciones IP con el mismo prefijo. +Por ejemplo, '192.168.*' se relaciona con todas las direcciones IP con el segmento '192.168.'. Si esta opcion esta vacia o no se asigna, +aplicara para todas las direcciones IP. + + * [[yii\filters\AccessRule::verbs|verbs]]: especifica el metodo de peticion por el cual la regla se relaciona (por ejemplo, `GET`, `POST`). +La comparacion se hace entre mayúsculas y minúsculas. + + * [[yii\filters\AccessRule::matchCallback|matchCallback]]: especifica una llamada a PHP para determinar si esta regla debe aplicar. + + * [[yii\filters\AccessRule::denyCallback|denyCallback]]: especifica una llamada a PHP para determinar cuando esta regla debe negar el acceso. + +A continuación se muestra un ejemplo de como hacer uso de la opcion `matchCallback`, el cual permite escribir el acceso arbitrariamente. +comprobar la lógica: + +```php +use yii\filters\AccessControl; + +class SiteController extends Controller +{ + public function behaviors() + { + return [ + 'access' => [ + 'class' => AccessControl::className(), + 'only' => ['special-callback'], + 'rules' => [ + [ + 'actions' => ['special-callback'], + 'allow' => true, + 'matchCallback' => function ($rule, $action) { + return date('d-m') === '31-10'; + } + ], + ], + ], + ]; + } + + // ¡match-callback es llamado! A esta pagina solo se puede acceder cada 31 de octubre + public function actionSpecialCallback() + { + return $this->render('happy-halloween'); + } +} +``` + + +## Control de Acceso Basado en Roles (Role Based Access Control-RBAC) + +El Control de Acceso Basado en Roles (RBAC) provee un simple pero poderoso control de acceso centralizado. Por favor dirigete a + [Wikipedia](http://en.wikipedia.org/wiki/Role-based_access_control) para mas detalles acerca de la comparacion de RBAC +con otros mas esquemas de control de acceso. + +Yii implementa un RBAC General y Jerarquico, siguiendo el [NIST RBAC model](http://csrc.nist.gov/rbac/sandhu-ferraiolo-kuhn-00.pdf). +Se provee la funcionalidad del RBAC a través de [[yii\rbac\ManagerInterface|authManager]] [application component](structure-application-components.md). + +El uso de RBAC implica dos partes del trabajo. La primera parte es la construcción de los datos de la autorización de RBAC, y la segunda + parte es la de usar esos datos de autorizacion realizar la comprobación de acceso en lugares donde se necesita. + +Para facilitar nuestra descripcion proxima, nosotros primero deberemos introducirnos en los conceptos basicos de RBAC + + +### Conceptos Basicos + +Un rol representa una coleccion de *permisos* (e.j. creando posts, actualizando posts). Un rol puede ser asignado +a uno o multiples usuarios. Para comprobar si un usuario tiene un permiso específico, podemos comprobar si se ha asignado al usuario +con un rol que contiene este permiso. + +Asociado con cada función o permoso, puede haber una *regla*. Una regla representa una pieza de codigo que debera ser +ejecutada durante el chequeo de acceso para determinar si el rol o permiso aplica para el usuario que intenta acceder. +Por ejemplo, el permiso "actualizar post" deberia tener una regla que cheque si el usuario que esta accediendo es el creador del post. +Durante el chequeo de acceso, si el usuario NO es el creador del post, el/ella se considero que no tienen el permiso "actualizar post". + +Ambos, roles y permisos pueden ser organizados en una jerarquia. En particular, un un rol puede contener a otros roles y permisos; + y un permiso puede contener a otros permisos. Yii implementa un *orden parcial* jerarquico el cual incluye el +jerarquia mas especial en *arbol*. Un rol puede contener a uno o varios permisos,mas no viceversa. + + +### Configurando RBAC + +Antes de partir para definir los datos de autorización y realizar la comprobación de acceso, tenemos que configurar el +componente [[yii\base\Application::authManager|authManager]] de la aplicacion. Yii provee dos tipos de manejadores de autorizacion: +[[yii\rbac\PhpManager]] y [[yii\rbac\DbManager]]. El primero utiliza un archivo de script de PHP para almacenar los datos de autorizacion, +mientras que el segundo almacena los datos de autorizacion en la base de datos. Puedes considerar el uso de la primera si su aplicación +no requiere papel muy dinámico y gestión de permisos. + + +#### Usando `PhpManager` + +El codigo siguiente muestra cómo configurar el `authManager` en la configuracion de la aplicacion usando la clase [[yii\rbac\PhpManager]]: + +```php +return [ + // ... + 'components' => [ + 'authManager' => [ + 'class' => 'yii\rbac\PhpManager', + ], + // ... + ], +]; +``` + +El `authManager` (manejador de autenticación) puede accesarse ahora via `\Yii::$app->authManager`. + +Por defecto, [[yii\rbac\PhpManager]] almacena los datos de RBAC bajo el directorio `@app/rbac`. Asegurese que el directorio +y todos los archivos en el tienen los permisos suficientes para ser escritor por el proceso del servidor Web y otorgueselos de ser necesarios online. + + +#### Usando `DbManager` + +El codigo siguiente muestra como configurar `authManager` in la configuracion de la aplicacion para la clase [yii\rbac\DbManager]]: + +```php +return [ + // ... + 'components' => [ + 'authManager' => [ + 'class' => 'yii\rbac\DbManager', + ], + // ... + ], +]; +``` +> Nota: Si estas usando yii2-basic-app (aplicacion basica de yii2), deberas configurar el archivo que esta en `config/console.php` para declarar + `authManager` y necesitas adicionalmente declararlo en `config/web.php`. +> En el caso de yii2-advanced-app el `authManager` debera ser declarado bajo el directorio `common/config/main.php`. + +`DbManager` usa cuatro tablas en la base de datos donde almacena los datos de autorización: + +- [[yii\rbac\DbManager::$itemTable|itemTable]]: La tabla para almacenar los items de autorizacion. Por default "auth_item". +- [[yii\rbac\DbManager::$itemChildTable|itemChildTable]]: La tabla donde se almacenan los items de autorizacion por jerarquia. Por defecto es "auth_item_child". +- [[yii\rbac\DbManager::$assignmentTable|assignmentTable]]: La tabla donde se almacenan las asignaciones a los items de autorizacion. Por default "auth_assignment". +- [[yii\rbac\DbManager::$ruleTable|ruleTable]]: La tabla donde se almacenan las reglas. Por defecto "auth_rule". + +Antes de que puedas implementar necesitas crear las tablas respectivas en la base de datos. Para hacerlo, tu puedes usar las migraciones contenidas en `@yii/rbac/migrations`: + +`yii migrate --migrationPath=@yii/rbac/migrations` + +El `authManager` puede ahora ser accedido via `\Yii::$app->authManager`. + + +### Construyendo los Datos de Autorización + +Construyendo los datos de autorización que tienen que ver con las siguientes tareas: + +- definiendo roles y permisos; +- estableciendo relaciones entre roles y permisos; +- definiendo reglas; +- asociando reglas con roles y permisos; +- asignando roles a usuarios. + +Dependiendo de la flexibilidad en la autorizacion que se requiera las tareas se pueden cumplir por caminos diferentes + +Si la jerarquía de permisos no cambia en absoluto y que tiene un número fijo de usuarios puede crear un +[console command](tutorial-console.md#create-command) que va a inicializar los datos de autorización una vez a través de las API que ofrece via `authManager`: + +```php +authManager; + + // add "createPost" permission + $createPost = $auth->createPermission('createPost'); + $createPost->description = 'Create a post'; + $auth->add($createPost); + + // add "updatePost" permission + $updatePost = $auth->createPermission('updatePost'); + $updatePost->description = 'Update post'; + $auth->add($updatePost); + + // add "author" role and give this role the "createPost" permission + $author = $auth->createRole('author'); + $auth->add($author); + $auth->addChild($author, $createPost); + + // add "admin" role and give this role the "updatePost" permission + // as well as the permissions of the "author" role + $admin = $auth->createRole('admin'); + $auth->add($admin); + $auth->addChild($admin, $updatePost); + $auth->addChild($admin, $author); + + // Assign roles to users. 1 and 2 are IDs returned by IdentityInterface::getId() + // usually implemented in your User model. + $auth->assign($author, 2); + $auth->assign($admin, 1); + } +} +``` + +> Note: Si estas usando la plantilla avanzada, necesitas poner tu `RbacController` dentro del directorio `console/controllers` + y cambiar el namespace a `console/controllers`. + +Despues, ejecutando el comando `yii rbac/init` obtendremos la siguiente jerarquia: + +![Simple RBAC hierarchy](../guide/images/rbac-hierarchy-1.png "Simple RBAC hierarchy") + +Autor puede crear un post, admin puede actualizar su mensaje y hacer todo lo posible autor. + +Si tu aplicacion permite al usuario el registro necesitas asignar los roles necesarios para cada usuario nuevo registrado. Por ejemplo, en orden para todos los usuarios inscritos para convertirse en autores de si plantilla avanzada tienes que modificar `frontend\models\SignupForm::signup()` +as follows: + +```php +public function signup() +{ + if ($this->validate()) { + $user = new User(); + $user->username = $this->username; + $user->email = $this->email; + $user->setPassword($this->password); + $user->generateAuthKey(); + $user->save(false); + + // the following three lines were added: + $auth = Yii::$app->authManager; + $authorRole = $auth->getRole('author'); + $auth->assign($authorRole, $user->getId()); + + return $user; + } + + return null; +} +``` + +Para aplicaciones que requieren un control de acceso complejo con una actualizacion constante en los datos de autorizacion , interfaces especiales de usuario +(e.j. admin panel) pueden necesitar ser desarrollado utilizando las API que ofrece `authManager`. + + +### Usando Reglas + +Como se habia mencionado, las reglas agregan restricciones adicionales a los roles y permisos. Una regla es una clase extendida desde +[[yii\rbac\Rule]]. Debe implementar al metodo [[yii\rbac\Rule::execute()|execute()]]. En la jerarquia tenemos creado previamente +que autor no puede editar su propio post. Vamos a arreglarlo. Primero necesitamos una regla para comprobar que el usuario es el autor del post: + +```php +namespace app\rbac; + +use yii\rbac\Rule; + +/** + * Checks if authorID matches user passed via params + */ +class AuthorRule extends Rule +{ + public $name = 'isAuthor'; + + /** + * @param string|integer $user the user ID. + * @param Item $item the role or permission that this rule is associated with + * @param array $params parameters passed to ManagerInterface::checkAccess(). + * @return boolean a value indicating whether the rule permits the role or permission it is associated with. + */ + public function execute($user, $item, $params) + { + return isset($params['post']) ? $params['post']->createdBy == $user : false; + } +} +``` + +La regla anterior comprueba si el `post` es creado por `$user`. Debemos crear un permiso especial `updateOwnPost` codigo que hemos utilizado +anteriormente: + +```php +$auth = Yii::$app->authManager; + +// add the rule +$rule = new \app\rbac\AuthorRule; +$auth->add($rule); + +// add the "updateOwnPost" permission and associate the rule with it. +$updateOwnPost = $auth->createPermission('updateOwnPost'); +$updateOwnPost->description = 'Update own post'; +$updateOwnPost->ruleName = $rule->name; +$auth->add($updateOwnPost); + +// "updateOwnPost" will be used from "updatePost" +$auth->addChild($updateOwnPost, $updatePost); + +// allow "author" to update their own posts +$auth->addChild($author, $updateOwnPost); +``` + +Now we have got the following hierarchy: + +![RBAC hierarchy with a rule](../guide/images/rbac-hierarchy-2.png "RBAC hierarchy with a rule") + + +### Comprobación de acceso + +Con los datos de autorizacion listos, la comprobacion de acceso es una simple llamada a el metodo [[yii\rbac\ManagerInterface::checkAccess()]]. +Dado que la mayoría de comprobación de acceso está sobre el usuario actual, para mayor comodidad Yii proporciona un método de acceso directo +[[yii\web\User::can()]], que puede ser utilizado como el siguiente: + +```php +if (\Yii::$app->user->can('createPost')) { + // create post +} +``` + +Si el usuario actual es Jane con `ID=1` estamos empezando a `createPost` y tratando de obtener a `Jane`: + +![Access check](../guide/images/rbac-access-check-1.png "Access check") + +Con el fin de comprobar si un usuario puede actualizar un anuncio, necesitamos pasar un parámetro adicional que es requerido por `AuthorRule` descrito antes: + +```php +if (\Yii::$app->user->can('updatePost', ['post' => $post])) { + // update post +} +``` + +Aquí es lo que sucede si el usuario actual es John: + + +![Access check](../guide/images/rbac-access-check-2.png "Access check") + +Estamos comenzando con el `updatePost` y pasando por `updateOwnPost`. Con el fin de pasar a la comprobación de acceso, `AuthorRule` +debe devolver `true` desde el metodo `execute()`. el metodo recive estos `$params` desde la llamada al metodo `can()` por lo que el valor es +`['post' => $post]`. Si todo está bien, vamos a obtener a `author` el cual es asignado a John. + +En caso de Jane que es un poco más simple ya que es un admin: + +![Access check](../guide/images/rbac-access-check-3.png "Access check") + + +### Usando los roles por defecto + +Un rol por defecto es un rol que esta asignado *implicitamente* a *todos* los usuarios. La llamada a [[yii\rbac\ManagerInterface::assign()]] +no es necesaria, y los datos de autorización no contiene la información de asignación. + +Un rol por defecto es usualmente asociado con una regla la cual determina si el rol aplica al usuario verificado. + + +Los roles por defecto se utilizan a menudo en aplicaciones que ya tienen algún tipo de asignación de funciones. Por ejemplo, una aplicación +puede tener una columna "grupo" en su tabla de usuario para representar a qué grupo de privilegio cada usuario pertenece. +Si cada grupo privilegio puede ser asignada a un rol de RBAC, se puede utilizar la función de rol por default de forma automática +asignar a cada usuario a una función RBAC. Usemos un ejemplo para mostrar cómo se puede hacer esto. + +Suponga que en la tabla de usuario, usted tiene una columna `grupo` que utiliza para representar el grupo administrador y 2 del grupo autor. +Planea tener dos roles RBAC `admin` y `author` para representar los permisos de estos dos grupos, respectivamente. +Puede configurar los datos de la siguiente manera en el RBAC, + + +```php +namespace app\rbac; + +use Yii; +use yii\rbac\Rule; + +/** + * Checks if user group matches + */ +class UserGroupRule extends Rule +{ + public $name = 'userGroup'; + + public function execute($user, $item, $params) + { + if (!Yii::$app->user->isGuest) { + $group = Yii::$app->user->identity->group; + if ($item->name === 'admin') { + return $group == 1; + } elseif ($item->name === 'author') { + return $group == 1 || $group == 2; + } + } + return false; + } +} + +$auth = Yii::$app->authManager; + +$rule = new \app\rbac\UserGroupRule; +$auth->add($rule); + +$author = $auth->createRole('author'); +$author->ruleName = $rule->name; +$auth->add($author); +// ... add permissions as children of $author ... + +$admin = $auth->createRole('admin'); +$admin->ruleName = $rule->name; +$auth->add($admin); +$auth->addChild($admin, $author); +// ... add permissions as children of $admin ... +``` + +Tenga en cuenta que en la anterior, porque "author" es agregado como un hijo de "admin", cuando implementes el metodo `execute()` +de la clase de la regla, por lo que necesitas jerarquizar respectivamente bien. Por eso cuando el nombre del rol es "author", +el metodo `execute()` debera regresar true si el grupo de usuario es cualquiera 1 o 2 (significa que el usuario se encuentra en +cualquiera de los dos grupos "admin" o grupo "author"). + +Lo mas próximo, configura `authManager` enumerando los dos roles en [[yii\rbac\BaseManager::$defaultRoles]]: + +```php +return [ + // ... + 'components' => [ + 'authManager' => [ + 'class' => 'yii\rbac\PhpManager', + 'defaultRoles' => ['admin', 'author'], + ], + // ... + ], +]; +``` + +Ahora bien, si se realiza una comprobación de acceso, tanto del rol `admin` y del rol `author` el cual debera ser evaluado bajo +las reglas asociadas con ellas. si la regla devuelve true, esto significa que la regla se aplica al usuario actual. +Basado en la aplicacion de la regla anterior, esto significa que si el valor un grupo `group` de un usuario es 1, el rol `admin` +se aplicaría al usuario; y si el valor de `group` es 2, el rol `author` se aplicaria. From 21c2c7a768662f035c08fd74c32460615f8b5790 Mon Sep 17 00:00:00 2001 From: Luciano Baraglia Date: Sun, 5 Jun 2016 16:58:03 -0300 Subject: [PATCH 83/90] Some spanish docs updates [skip ci] (#11695) --- docs/guide-es/images/application-lifecycle.graphml | 527 +++++++++++++++++++++ docs/guide-es/images/application-lifecycle.png | Bin 0 -> 32044 bytes docs/guide-es/images/rbac-access-check-1.graphml | 368 ++++++++++++++ docs/guide-es/images/rbac-access-check-1.png | Bin 0 -> 19024 bytes docs/guide-es/images/rbac-access-check-2.graphml | 368 ++++++++++++++ docs/guide-es/images/rbac-access-check-2.png | Bin 0 -> 19364 bytes docs/guide-es/images/rbac-access-check-3.graphml | 368 ++++++++++++++ docs/guide-es/images/rbac-access-check-3.png | Bin 0 -> 19336 bytes docs/guide-es/images/rbac-hierarchy-1.graphml | 312 ++++++++++++ docs/guide-es/images/rbac-hierarchy-1.png | Bin 0 -> 16085 bytes docs/guide-es/images/rbac-hierarchy-2.graphml | 368 ++++++++++++++ docs/guide-es/images/rbac-hierarchy-2.png | Bin 0 -> 18786 bytes docs/guide-es/images/tutorial-console-help.png | Bin 0 -> 141692 bytes docs/guide-es/rest-authentication.md | 44 +- docs/guide-es/security-authorization.md | 347 +++++++------- docs/guide-es/start-databases.md | 89 ++-- docs/guide-es/test-unit.md | 25 + docs/guide-es/tutorial-mailing.md | 231 +++++++++ 18 files changed, 2811 insertions(+), 236 deletions(-) create mode 100644 docs/guide-es/images/application-lifecycle.graphml create mode 100644 docs/guide-es/images/application-lifecycle.png create mode 100644 docs/guide-es/images/rbac-access-check-1.graphml create mode 100644 docs/guide-es/images/rbac-access-check-1.png create mode 100644 docs/guide-es/images/rbac-access-check-2.graphml create mode 100644 docs/guide-es/images/rbac-access-check-2.png create mode 100644 docs/guide-es/images/rbac-access-check-3.graphml create mode 100644 docs/guide-es/images/rbac-access-check-3.png create mode 100644 docs/guide-es/images/rbac-hierarchy-1.graphml create mode 100644 docs/guide-es/images/rbac-hierarchy-1.png create mode 100644 docs/guide-es/images/rbac-hierarchy-2.graphml create mode 100644 docs/guide-es/images/rbac-hierarchy-2.png create mode 100644 docs/guide-es/images/tutorial-console-help.png create mode 100644 docs/guide-es/test-unit.md create mode 100644 docs/guide-es/tutorial-mailing.md diff --git a/docs/guide-es/images/application-lifecycle.graphml b/docs/guide-es/images/application-lifecycle.graphml new file mode 100644 index 0000000..850863a --- /dev/null +++ b/docs/guide-es/images/application-lifecycle.graphml @@ -0,0 +1,527 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + Entry script (index.php or yii) + + + + + + + + + + Folder 4 + + + + + + + + + + + + + + + + Load application config + + + + + + + + + + + + + + + + + + + + + Create application instance + + + + + + + + + + Folder 5 + + + + + + + + + + + + + + + + preInit() + + + + + + + + + + + + + + + + + Register error handler + + + + + + + + + + + + + + + + + Configure application properties + + + + + + + + + + + + + + + + + init() + + + + + + + + + + + + + + + + + bootstrap() + + + + + + + + + + + + + + + + + + + + + + + Run application + + + + + + + + + + Folder 3 + + + + + + + + + + + + + + + + EVENT_BEFORE_REQUEST + + + + + + + + + + + + + + + + + + + + + Handle request + + + + + + + + + + Folder 4 + + + + + + + + + + + + + + + + Resolve request into route and parameters + + + + + + + + + + + + + + + + + Create module, controller and action + + + + + + + + + + + + + + + + + Run action + + + + + + + + + + + + + + + + + + + EVENT_AFTER_REQUEST + + + + + + + + + + + + + + + + + Send response to end user + + + + + + + + + + + + + + + + + + + Complete request processing + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Configuration array + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Exit status + + + + + + + + + + + + + + + + diff --git a/docs/guide-es/images/application-lifecycle.png b/docs/guide-es/images/application-lifecycle.png new file mode 100644 index 0000000000000000000000000000000000000000..6a505ccefb89072e4bee7691114dd6cbe2e34bc2 GIT binary patch literal 32044 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 M#?fO9(&T8+BG-Yon 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 literal 0 HcmV?d00001 diff --git a/docs/guide-es/images/rbac-access-check-1.graphml b/docs/guide-es/images/rbac-access-check-1.graphml new file mode 100644 index 0000000..4407851 --- /dev/null +++ b/docs/guide-es/images/rbac-access-check-1.graphml @@ -0,0 +1,368 @@ + + + + + + + + + + + + + + + + + + + + + + + admin + + + + + + + + + + + + + + + + + author + + + + + + + + + + + + + + + + + John, ID=2 + + + + + + + + + + + + + + + + + + + + Jane, ID=1 + + + + + + + + + + + + + + + + + + + + updatePost + + + + + + + + + + + + + + + + + updateOwnPost + + + + + + + + + + + + + + + + + createPost + + + + + + + + + + + + + + + + + AuthorRule + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="65px" viewBox="0 0 57 65" enable-background="new 0 0 57 65" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + <path id="body_18_" fill="#ECECEC" stroke="#9B9B9B" stroke-miterlimit="10" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-11.244-6.146-11.244-6.146 + c-1.771,1.655-5.61,3.802-10.063,3.802c-4.453,0-8.292-2.146-10.063-3.802c0,0-5.755,0.586-11.189,6.021 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> + + <radialGradient id="SVGID_2_" cx="22.6621" cy="21.707" r="17.7954" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_2_)" stroke="#E55E03" d="M28.106,33.486c-8.112,0-12.688,4.313-12.688,10.438 + c0,7.422,12.688,10.438,12.688,10.438s14.688-3.016,14.688-10.438C42.793,38.75,36.215,33.486,28.106,33.486z M26.288,53.051 + c0,0-7.135-2.093-8.805-7.201c-0.222-0.682,0.147-1.156,0.795-1.521V37.8h20.188v6.663c0.235,0.352,1.109,0.737,1.229,1.387 + C40.445,49.917,26.288,53.051,26.288,53.051z"/> + + <radialGradient id="SVGID_3_" cx="15.2056" cy="831.1875" r="32.3071" gradientTransform="matrix(1 0 0 1 0.0801 -773.6914)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_3_)" stroke="#E55E03" d="M49.529,51.225c-2.239-2.24-5.041-3.724-7.396-4.67 + c-2.854,5.51-14.021,7.807-14.021,7.807s-10.472-2.483-12.387-8.514c-2.439,0.771-5.787,2.287-8.749,5.25 + c-5.592,5.592-6.47,11.67-6.47,11.67c0,1.938,1.575,3.492,3.523,3.492h48.51c1.946,0,3.521-1.558,3.521-3.492 + C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="SVGID_4_" cx="17.0723" cy="18.4907" r="11.8931" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_4_)" stroke="#E55E03" d="M13.404,44.173c1.15-1.81,2.039-3.832,3.332-5.397 + c-0.514,1.027-1.669,4.084-1.669,5.148c0,5.186,10.366,9.079,14.688,10.438c-3.472,1.627-9.134-1.498-11.334-2.359 + c-3.601-1.419-4.071-3.063-5.89-4.854C12.523,47.135,12.878,45,13.404,44.173z"/> + + <radialGradient id="SVGID_5_" cx="31.8184" cy="19.3525" r="14.63" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_5_)" stroke="#E55E03" d="M45.777,43.924c-1.317-1.568-5.11-9.424-6.604-6.617 + c0.516,1.025,3.617,3.693,3.617,6.617c0,5.186-10.271,8.576-16.699,9.145c1.429,4.938,11.373,1.293,13.805-0.313 + c3.563-2.354,4.563-5.133,7.854-3.705C47.754,49.045,48.006,46.574,45.777,43.924z"/> + + <radialGradient id="SVGID_6_" cx="30.4893" cy="4.8721" r="5.2028" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_6_)" stroke="#E55E03" d="M30.777,54.167c0.357,0.836-0.153,1.983-0.352,2.813 + c-0.256,1.084-0.072,2.104,0.102,3.186c0.164,1.02,0.156,2.107,0.25,3.167c0.082,0.916,0.482,1.849,0.357,2.75"/> + + <radialGradient id="SVGID_7_" cx="23.2871" cy="5.3008" r="5.5143" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_7_)" stroke="#E55E03" d="M23.695,53.417c-0.508,0.584-0.476,2.209-0.398,3 + c0.116,1.183,0.456,2.099,0.333,3.333c-0.192,1.943,0.154,4.479-0.436,6.333"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.2-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.501" y1="-12291.5195" x2="6492.1304" y2="-12384.9688" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3351.7349)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Young_Black_1_" fill="#5C5C5C" stroke="#353535" stroke-linecap="round" stroke-linejoin="round" d="M20.278,13.25 + c3.417,4.333,9.333,6.917,9.333,6.917l-1.417-3.5c0,0,7.094,4.691,8.083,4.333c0.968-0.2-1.082-3.807-1.082-3.807 + s3.138,1.795,4.854,3.969c1.803,2.28,4.285,3.504,4.285,3.504S47.027,2.719,27.289,2.744C8.278,2.709,12.058,27.678,12.058,27.678 + L14.695,17c0,0,0.914,5.757,1.399,4.875C17.861,15.211,18.861,11.5,20.278,13.25z"/> +</g> +</svg> + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="67px" viewBox="0 0 57 67" enable-background="new 0 0 57 67" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.199-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.5" y1="-12286.8594" x2="6492.1294" y2="-12380.3086" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3350.4617)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Female_1_Red_1_" fill="#FAE1AA" stroke="#E2B354" stroke-linecap="round" stroke-linejoin="round" d="M28.372,0.5 + C17.537,0.5,8.269,7.748,9.153,26.125c0.563,6.563,5.862,12.042,9.366,13.531c-2.929-10.968-0.304-25.021-0.585-25.526 + c-0.281-0.505,3.536,6.728,3.536,6.728l3.183-8.312c5.541,4.28,0.393,11.309,1.049,11.058c4.26-1.631,5.34-9.228,5.34-9.228 + s2.729,3.657,2.701,5.504c-0.054,3.562,2.194-6.067,2.194-6.067l1.027,2.031c6.727,9.822,3.684,16.208,1.648,22.781 + c15.666-0.703,12.291-10.48,9.66-18.407C43.59,6.092,39.206,0.5,28.372,0.5z"/> + + <linearGradient id="body_1_" gradientUnits="userSpaceOnUse" x1="95.9063" y1="-3134.2153" x2="31.5133" y2="-3134.2153" gradientTransform="matrix(0.9852 0 0 -0.9852 -34.4844 -3031.9851)"> + <stop offset="0" style="stop-color:#49AD33"/> + <stop offset="1" style="stop-color:#C2DA92"/> + </linearGradient> + <path id="body_8_" fill="url(#body_1_)" stroke="#008D33" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-8.244-5.146-8.244-5.146 + c-1.444,6.983-8.555,8.786-13.007,8.786s-11.322-2.643-11.941-9.439c0,0-4.559,1.199-9.367,5.674 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> +</g> +</svg> + + + + diff --git a/docs/guide-es/images/rbac-access-check-1.png b/docs/guide-es/images/rbac-access-check-1.png new file mode 100644 index 0000000000000000000000000000000000000000..77ad551c26d4f4b4a3cf4d9b7cfd5f4b7472ec82 GIT binary patch literal 19024 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 literal 0 HcmV?d00001 diff --git a/docs/guide-es/images/rbac-access-check-2.graphml b/docs/guide-es/images/rbac-access-check-2.graphml new file mode 100644 index 0000000..c521d42 --- /dev/null +++ b/docs/guide-es/images/rbac-access-check-2.graphml @@ -0,0 +1,368 @@ + + + + + + + + + + + + + + + + + + + + + + + admin + + + + + + + + + + + + + + + + + author + + + + + + + + + + + + + + + + + John, ID=2 + + + + + + + + + + + + + + + + + + + + Jane, ID=1 + + + + + + + + + + + + + + + + + + + + updatePost + + + + + + + + + + + + + + + + + updateOwnPost + + + + + + + + + + + + + + + + + createPost + + + + + + + + + + + + + + + + + AuthorRule + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="65px" viewBox="0 0 57 65" enable-background="new 0 0 57 65" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + <path id="body_18_" fill="#ECECEC" stroke="#9B9B9B" stroke-miterlimit="10" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-11.244-6.146-11.244-6.146 + c-1.771,1.655-5.61,3.802-10.063,3.802c-4.453,0-8.292-2.146-10.063-3.802c0,0-5.755,0.586-11.189,6.021 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> + + <radialGradient id="SVGID_2_" cx="22.6621" cy="21.707" r="17.7954" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_2_)" stroke="#E55E03" d="M28.106,33.486c-8.112,0-12.688,4.313-12.688,10.438 + c0,7.422,12.688,10.438,12.688,10.438s14.688-3.016,14.688-10.438C42.793,38.75,36.215,33.486,28.106,33.486z M26.288,53.051 + c0,0-7.135-2.093-8.805-7.201c-0.222-0.682,0.147-1.156,0.795-1.521V37.8h20.188v6.663c0.235,0.352,1.109,0.737,1.229,1.387 + C40.445,49.917,26.288,53.051,26.288,53.051z"/> + + <radialGradient id="SVGID_3_" cx="15.2056" cy="831.1875" r="32.3071" gradientTransform="matrix(1 0 0 1 0.0801 -773.6914)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_3_)" stroke="#E55E03" d="M49.529,51.225c-2.239-2.24-5.041-3.724-7.396-4.67 + c-2.854,5.51-14.021,7.807-14.021,7.807s-10.472-2.483-12.387-8.514c-2.439,0.771-5.787,2.287-8.749,5.25 + c-5.592,5.592-6.47,11.67-6.47,11.67c0,1.938,1.575,3.492,3.523,3.492h48.51c1.946,0,3.521-1.558,3.521-3.492 + C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="SVGID_4_" cx="17.0723" cy="18.4907" r="11.8931" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_4_)" stroke="#E55E03" d="M13.404,44.173c1.15-1.81,2.039-3.832,3.332-5.397 + c-0.514,1.027-1.669,4.084-1.669,5.148c0,5.186,10.366,9.079,14.688,10.438c-3.472,1.627-9.134-1.498-11.334-2.359 + c-3.601-1.419-4.071-3.063-5.89-4.854C12.523,47.135,12.878,45,13.404,44.173z"/> + + <radialGradient id="SVGID_5_" cx="31.8184" cy="19.3525" r="14.63" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_5_)" stroke="#E55E03" d="M45.777,43.924c-1.317-1.568-5.11-9.424-6.604-6.617 + c0.516,1.025,3.617,3.693,3.617,6.617c0,5.186-10.271,8.576-16.699,9.145c1.429,4.938,11.373,1.293,13.805-0.313 + c3.563-2.354,4.563-5.133,7.854-3.705C47.754,49.045,48.006,46.574,45.777,43.924z"/> + + <radialGradient id="SVGID_6_" cx="30.4893" cy="4.8721" r="5.2028" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_6_)" stroke="#E55E03" d="M30.777,54.167c0.357,0.836-0.153,1.983-0.352,2.813 + c-0.256,1.084-0.072,2.104,0.102,3.186c0.164,1.02,0.156,2.107,0.25,3.167c0.082,0.916,0.482,1.849,0.357,2.75"/> + + <radialGradient id="SVGID_7_" cx="23.2871" cy="5.3008" r="5.5143" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_7_)" stroke="#E55E03" d="M23.695,53.417c-0.508,0.584-0.476,2.209-0.398,3 + c0.116,1.183,0.456,2.099,0.333,3.333c-0.192,1.943,0.154,4.479-0.436,6.333"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.2-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.501" y1="-12291.5195" x2="6492.1304" y2="-12384.9688" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3351.7349)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Young_Black_1_" fill="#5C5C5C" stroke="#353535" stroke-linecap="round" stroke-linejoin="round" d="M20.278,13.25 + c3.417,4.333,9.333,6.917,9.333,6.917l-1.417-3.5c0,0,7.094,4.691,8.083,4.333c0.968-0.2-1.082-3.807-1.082-3.807 + s3.138,1.795,4.854,3.969c1.803,2.28,4.285,3.504,4.285,3.504S47.027,2.719,27.289,2.744C8.278,2.709,12.058,27.678,12.058,27.678 + L14.695,17c0,0,0.914,5.757,1.399,4.875C17.861,15.211,18.861,11.5,20.278,13.25z"/> +</g> +</svg> + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="67px" viewBox="0 0 57 67" enable-background="new 0 0 57 67" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.199-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.5" y1="-12286.8594" x2="6492.1294" y2="-12380.3086" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3350.4617)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Female_1_Red_1_" fill="#FAE1AA" stroke="#E2B354" stroke-linecap="round" stroke-linejoin="round" d="M28.372,0.5 + C17.537,0.5,8.269,7.748,9.153,26.125c0.563,6.563,5.862,12.042,9.366,13.531c-2.929-10.968-0.304-25.021-0.585-25.526 + c-0.281-0.505,3.536,6.728,3.536,6.728l3.183-8.312c5.541,4.28,0.393,11.309,1.049,11.058c4.26-1.631,5.34-9.228,5.34-9.228 + s2.729,3.657,2.701,5.504c-0.054,3.562,2.194-6.067,2.194-6.067l1.027,2.031c6.727,9.822,3.684,16.208,1.648,22.781 + c15.666-0.703,12.291-10.48,9.66-18.407C43.59,6.092,39.206,0.5,28.372,0.5z"/> + + <linearGradient id="body_1_" gradientUnits="userSpaceOnUse" x1="95.9063" y1="-3134.2153" x2="31.5133" y2="-3134.2153" gradientTransform="matrix(0.9852 0 0 -0.9852 -34.4844 -3031.9851)"> + <stop offset="0" style="stop-color:#49AD33"/> + <stop offset="1" style="stop-color:#C2DA92"/> + </linearGradient> + <path id="body_8_" fill="url(#body_1_)" stroke="#008D33" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-8.244-5.146-8.244-5.146 + c-1.444,6.983-8.555,8.786-13.007,8.786s-11.322-2.643-11.941-9.439c0,0-4.559,1.199-9.367,5.674 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> +</g> +</svg> + + + + diff --git a/docs/guide-es/images/rbac-access-check-2.png b/docs/guide-es/images/rbac-access-check-2.png new file mode 100644 index 0000000000000000000000000000000000000000..254f307a89e5e59fceda04f27667982b60c3fcab GIT binary patch literal 19364 zcmeFYbx@mK_%@h2P+Ce_N>i+O@!}3G?(SM3K#|~52vULI5*&h*;_hxO8eD@1iaWtw zHhtfp?EbMkJG&Z4FI^)hJAW) z9%6HRgWkLZ0OW0CB|d0;p594$5)#~${?oMM?%l6|uYh!S$|ImBhHQf@ zO9o?j=YwkI=^Wwx4j5U~d!US%{G0CN+f-go6Al_KT31ZffE-7P2Zdz|bl#hjhXmWm z|NFo9n^e(x8v|+G{h!qB8=4+!D{`Y+$HZ&)5+su!000hcvu1M1qR|Kyb#t_lxVZXp zvZ!{##mtyG@BZgH#S{)yLVr@RTl?I*V^zjELg>I4K)oX6v?*HgapZ}4?k)UUL6awT zaK|ko!E<8FESoXuKxw%QPXKee$kH>Uj&x3WvMKe+d{bGy0d7)1LpUn*FnffIJ=UT$ zPbs?xp|{z`0?1r|xuoTN&lch2?j9Q)5H`{lzxX}omh(eHh@xINoabx*fbg!dBI=9) ztTUqFcj!*!;6kN2?9rqYb~AWB9ceNhYXX;W%QfI|*$E_S>`tkisiV#=aZT^q3NjzP zo4Esv85MV!W*)EQ&g{+R5WHo@yvW&dgQ9nHMx`umC(>B!jl@0sJkI;S;E!6Ia4~Hu zLR@wO$=t(745k{^tEo>K^p#3#qT@&fO?N-f!&I38nZM@hl)`+LXJ@X_Q$Gw8>G|MI zt_WwYvl?fYbw;$H&y6uprq@8WnX`yilfgU*^fb+t+98=C$_f5u+z})l{{59r=;h!8`!i;8e45zaW|7p zN&pd|Hda&?T1_i-b_>_fsEfn*PNr^?PWD}Mn`4WvNECL9e$q|5s`N20Owm2)OU=Q*Xa61F-a@*s>pXd7su5uISU5W;#Ox>mDY+&yROMa(yqsRu!5A$xS zH6a)x#Lm+>RRBOnom5|QEq!Ury5;pJ+|mX2-y}(?1T*MEwf1zvlS5IZnDu#!-j$Y@ z-777``b5`VmOxkAYJ+zZlBBQg2&U%v89#|fzoqU*eYM%tzOYe5TY@X>>E?86KkV)u zANR+w88-(UEO)EZ`QBV*DD3d~K1!@#URj~lTRlBoJ@CbQ@wT-8N~CYLTCLciE)|&~ zc#7t6`vv$DC;dI-8Y57auQdL=z}oex9`W8~9)yfFMVMon{JJY=f3aaW_y&?H;ltbN z<0QW&|8?uGMVf&z{q26o&FLX)n``YTaz~cBhFr?Umxxhg-78Nm%alpGm>E@|9WUUz zDTA(eEy~v{h&#Kyl#gZ9%v4C^7+$R=?!RvBzotWYa!QAjp1zR;)H2-6k~+*A7v!(v z>CJy$$zv%ROuaNoz8*K@3qraHeJ0KseHXEYqg&eL5Fj5bA$qVT&CW`IESorhJaeuM^CvL zE=!1RR=$ltB*mTQeZzB6Q+KQPE!Fh|`4+mDt!09pXww!`c`F;QiuyS>rDv6yE*fB4576nct2z9JHQGVx%lN)s=Wc zb=fAg*$Th?!)HS)>p_G!npM^)zSF!RUv~x`^y}RRK38m#>qc2}v1*jNdj|)e zgF^EujVpl2FH1I86i*D)eWx~>uGyrnc`(%>L-{Qt@uh~srgtZJug=oUxzo&@Lz)+7lEE*A{kc~vEC-cV z0l$8CR~;J(l+~ZyF`PUwE_~hOzoB;7-On=qps3_DJN3`t%kIzXZ~MpW47oi8v!#U# zyv`3CMMOmO!=BCmK|cb7{Ni?f=x4k240ya%2qsS7iV0EIpKedlQEzA_JiKQM4-J5; zO#o@-?xS~A(=R=O{2voH$B6pg;0-uGyYq(nd40z4cY3XfDUXvq8BRt6howC zG#$f=GYgSE!M2Z5{?R@0Y!n+JMEPm2)tg-$`7g}v%X}|`3X^9) z^bK9qvt6tjl+(qiL!YIY$^*bEH+rf|{l8rA1cVj>Q>ajcY2oWiiK>4(j4k-2yjuoh zB3U5IBhBel^NNvykQB0vwt4JxC{6!rpF8#k_Z_Db!Ps?6r}0o6&7|KpuM_4)>sQy` zxmd>6KDW6GOE%EQ$X2U#zK6 zY8_036OF9L3lrBp{3n3sSTr8pG#s@xP5}X+m>*BV5CL{u4h9e6*I(p;@;2VGIcSpc zLqBKImIc}eAQQ>ZG^<0e;MCE}i>Y^jWexG%Dfy#6b54(HKD~w! zQEzP2pma@Kg`hNuQ0WD~Q1>#V-0O4&V5L!!tnpTV8ac>bw5i>g@Of>JzN26HdKJ(8 zJnwDQ(LrW7$x9|Zn%PjL!%_wS$J?)4h)}_%8f6I$AVsJ5&1D&|x-JED?ONuSJ(@x} zGu`Xobg$i~ohuiUgY58r%D-s^Vr*AO=*y0!el->4(xP&6;dR<(`LOl6yMwH$quJii z&wgh;Su7>~$m%6Rzk}RZw_r1^;@++qQ2OWq9`Zx-rD=LmoJZ^ zCciHgrLYpEdx>f;2Us~-Uw+z{NUzplT%*@y)Q&n>>6I-Ruga9?>>9H+cTTif*1@_B zb zFJyL+Uk6|I)EXUUy2VDRjU&gM1*&8gTHWmJF;qMv1ux#1IhktixWWTOf)zJk8J(pz zJGn_K2MHeqCC{Mrv1|_KoN16P$71x=P!--nPcydtcZ_+)Muhg2TmXffvFk`*CCX)(qz(208OL|RUsM3}pj zJd`SwWI2yzR5geTgRqGD42!>>V5VLP`-~>uIwN9xt&N;%kW1t+Kl{Y%} z6M>RTtEVM&kjjlzpgD!yV8+Vp_Ff)4&1U1si}^ORCkied)LjLHQwu zCr88$;rAny+_}gLK>cNmn}52Ttj(NpXHbS>HP!LG^I?wL9loMdWq&?M2L^(l)~cfw;cCA=(d0ni~>Hs{?59pmy}?Pn?)WhBWI;ddF%LUiZj}G&xXL3q2jP&)de;wpOyjM;|(IpdZ~b2jcv4g;C0TtvWy<`xVQaN(RHo9_&{6^=R$nCRsMaKt zesbg0XZBXhRY_;Ws-u>P{B@=l7F)*hs%Yd%F_j=RDLG-Aj2xr$E@R}fBeqGIOq4rB z6x!nu>@3~K&1B+tv6Jh^F>B_w=SC36qOLFd-P&g{WnTVB-v9iU-52~opG1u4yv9sg z4F0i8$TLok#>ysy)mv(^FezfT+U)#F(DkCNj41YqR|h^TL;6UI2hYvPi}4y>KDScB zY(NO6hrHIit62E%h^ClI`TH0d8MU}ssc`V@^{$@W=a@y$fGu*nM`A z!|>7I&mT3uZR>K(a?60FMfv%P>|DZ}{7w0*?!KXZ?C4RIBHOJ(OH`i~W-w<0uB~aY zVwJLx&f!04cdhS1g#x)~Y9#R|tYp{)FLjv|i=OXAN17}zz%NirGgwi*m*Tt;)A%$z z{FC8@?PPfzyEb^T4Y?;C9qVHu-Gj3oz%Y;)#7|x~`haURWy*Qik1W-}^`y`YMqRjs z+UJ>!BT?(_g zW*|WgYwRQuu;nBwR~wnKh70-i3%XU0Y=fl=xtu(F@f%&;2<1nzIcH~QLMGU;`;<8O zrWvC+hJcci;waijP=4T(ypT1Qd;K~(GXeJ{VdRhBRlbgh! z`DC!~rF_~eLUU1JR?NlZ${l!lo?$Y#=H_d%O|jSRl$%czi#gDier|haT{5bN?x;YA zBh=K)5-E`#XGC3d?4&kNsk8!v6Qzs6990PM%@%n#{AVn`$&k*uR{(PT>Rhwm_Jhr)t?=#j4`JrcF4Wt7Cs&f6>5IMZJwt0KMmmQq%I#cH@PXNK0^0=KB3;&N^Bg1 z7EV$%rLu+WBpU0Cs1DKXUV6?b+u0o-i;^JN!(u6=0_p@M6UaL>+nXgwBae4iJc{S`;XJ7S6t>RO zU~XTae=^It){`Rt&GYg;m94kA=U6CS2C=ULrIT8Eiiz1lOLxOl04|Qv%I!E-mGMZD zzb4<|LoGVvqUU5;3vKA!AaAF27D?2*nEDx-1?ptq+Dj>U*u31DPojE7?$K)T!ZJy9a^faloTs|+^GO4|%re3YSg`nH z22D+c4NCed73TTA{p20ZPHFSn((?FIXQr?3Ov=J_g*h(C{%&?)*}0i%9LgOma|kvs zfKW}@Y*inw(EUDU0S4~PR{jKzV>J2E`~}I@N#_UMu+t-aYYS+yI~f2-{L`%GXzRJn zW{OBzqi0{DZpTi1b0NbCcYktNUTnoir5n=mJGAaK+?i|Lpp(4j=htL}q1xS++|P?? zD<~h~viuYHa~qQFr`RxQ!H*}3u=^04JzPDb1>eR?5aQQ``x;(;+D1ThKSl5pva@<& zFSQRrx>|#9T6CS$;2bQo++Ffpd39bpYe^2Z7%tgalEjIZtXPs|S=!Du%&!{h#8A3` zej=_n#aDF~0Hk-(&3F`1Qi#4yBf5Dcmh+PMRa$drF z#|LztOBatD(?gYTdp0|P6_qfF*MzC$YqY;vD5B)W=b{gVjpItIl_#m|TWs6U_V(4E za1!Zk57G|-e!21q^I!%-(zGHgDz28Vl1H@6g7pbMN=!`ZW$%6x8F&bO9f#&huZ_I} zY$z<2V9BL{a|Q>^8J`~Xn-TpX^KZw&tvyBkx=4JQW_uDT>?uc!Yjs-b_taZ{pr3)3 zP#v$=zs_7Cdo$6CyUc>5czK{`oR22=u{moW8$F>^=5U%e5jxQs`7Q^!eit~mV1t40nWe^ai(J7B1xomBLeo}MqYxJ>Y`FACTFx$n3t)wSxlD(c}2oN@Tv zP{#u7k$;d;ZaId^$qkk^)t%2A-!hU?elfoN9lh;k^)w@1Blgf)o&@)0wH-xGc^S7; zXtLHAWm~!w%nVOvcFzDf%@K0#)j|rKz8H4?{RCt8Y;n+q0brmEo97A?#zGFQR*_Kmv1 z@{d^{XUQ%R$kPI|CEzr-4VD~u%ewIVF*0%goK$^}&bR*g%OXx<8~ygR0l2U`=;aGg zqVj+nl#=!5;o-@l_k81v>8pjbZ=2{8>5Cbi+6Msb_4#?;(GACQay+uMt*O#74}$i> zPYKA)VYdNwhUBWL3LvYmG%WI!XW?8c|d#|>Lb%FI_+iFrN*6_F2P{=ll~ zzrCJs*;(}CD5Hd=MXd*|EH9poocz&>kVlrf(^!~C)0Dd}fkG1&=)V?G#8zN^yf&hd z8Pp@gh6~w`lpr{bnuN~*r#Ip2KQ7#PhYty+7_o6L3w7uD9nq=Fy3;OCO$LGVKjF-2 z%V9U3Fym$atSYun$S0flq6@gTVX@bh=C@7H9Vegc4=M3*y*0)^Dtnj-oiV3#7S%a;_|2fe$M-O#6?H&3! zuJO7&Z(#DRiEOJaLNBjPg@Mm&cMr_E+;Xt|TxSNXU3tP7w!3mc>8wZgm zP}604tokaU2ABI?7~Vh3JRHM|hzG41W{!+eA?6Y{Nenka@(;%Q7Rx{BelZy8%rO2Q zt<&MF;}k)Xn`+7{YBo_5n_IFa`zKk=t)%RH**|D5-Z*W$X(12SXr%%{Xk|xjUMLu@{-oZ}Z%w6ww*cU0*-28%qJtR!PR^_Bl_V{$R(n-iP;FIJAK#GXCR+imcidRa z`xLwS&8;LL!xnUH-y{q_6pSj9d{2>|+<$jKx!>7_K) zN@Nks7v-yD1w9SxH(&j_%W?XVAn_W)yU~)B^#Y--3$PNyDUMQ)zNU_LG7PFnO2*r_}+KM45pEdJ_v!noXH>7nky z`CZ+^6&E+bq>RMXr|}6X-YX*mK28?B@2P*m%bm};IDyGFvU__Am}Rg-sb30S z+~{STtjypk%~;@xMo7R)>z`9vJ2Bic<+pTY)Q7SfExapU5w=d{-PcY(edz3~U%VHv z+#O+za7|RNHr(ClP>#{jknG&vq)8*cP+6te2C=mzTDa(pguZ~X<a{C_L2>fRXQmpoIPYNEopMQ z?*Yr58vV#_4O_*;X(t1BkZ|g9$0aG22u{X zD12oCLAZLrs~tgH1gceN?r{9myF#=&P`La@yCN&~Nf7O_7iNMsbLu;AWRa48imT#2 zcdh=nySs;k7W9A-HgV!h(P8@!E6Q`JdPfQ?Y&OW4Cbao)_okH!2{7MheWq^ioR~i2 zDFv#&+`(r1NW!GNYgyEv%&lJ^%P3w^`C_hAdInkOn;*-`jG~CL!Vvff5QxXnP>PY6 zg;h7~Srh%|l9ROU9sg&e5jei2D&V92xQLV>(qw7V)rsiZ4i*|aS^0v2i2dlH@WEbx zk&}qy1W-{t*t<|rrj}FTtU#=bm?lj^~z4CeDoo3 zWRo4&>N+8rY_GUoi36Cd^I+A?1gP~eiE74T}}*$sQLbWr1sIr(W*Q@s#4#WUlqh-WGL6;tJ??sIoN)+wWZX5 zdt<89CNsJ|wmzJ`0!#jM4xX$d^p_y+ZD(-|-V4#n){~!&htOCD!;LO2R}@MnXg$>) z1f}Tfvxc)WreimtKiL+`JXK|WHswy^&)*A}9+}==UNWCh?9KH7DIw!raF0iq$i=qnVy3pdOF>x# z_Y<16?Whq=2Q}~xOMP*0cb51Em-Xq?R40SfLo|qRhCD& z=-mG1HlK9Rlw~;eH|rU*l>2IRoK5T#)O5tfC{}^?0jcG82Wcsjit;voLEx0gyxZ-q z2G&%jXtF;>$h1d#IrW6DD!F^8{_J{DCE;eGLvG_jJa*;_6v{14))SOeg4`M?4PdB# zdmqX<95PEKSiG28_61JwGVlwod)Em~g#s+Ki7#z^18{P*V-of|Rupp96oi@BsT(Ca zU%ougj66=vGL;JOyfHcY!n=~`aYNCta`nmhwp_V_=+4L^_sV3$^aK6TklSLat0NWT z^F`zrlDLIsZ?ep;&dw-%BM<|3%QppGlv;R_s?lEaZ=jWwcRYbN^9e>OIALUdkc1zW z1phM+ebkf9Vwi;@=3Y78Xjb!5Ww5HK&UcwsIfj8JKW3%H=&4DREB4zD@{1T>I)xLTIC*Ja` z5&Q7<^tu&=N1ST5pCj(`Ai*p!el#~CnJhv5r_{OJ_EC?NkJK0EWMHx;N~W}&TiDa* z4-_jU9fP(L4Nd#abaOOn!F;R1_*FkIni;?Dq(-L+HflngedjMHqX1UPU#7Aunt0Ke zkG~EsI8J9o%8xF;^0KrAL1%gsgp0C%ALtznYy+cuHFi2s;J*CTaz`6?{`!WNt8&XP zwsYg+7R5F!WX;7abIx)g__<88vb@YvEQL&b7)tScJ_diQ=q>95kwbV5-AcqhIcA6A z9U?m>j&FMYHdVA?b&_bBDZn7ra5S*A4uLbHLsi*+>ME_CGMk@~(dWvjkRl>Hy7h#* zIe_`xAEBC(N}fJ1b{Ji|5vu^Yqr||bN*V{AX7psUW6>xU5T*6NLwTWEQew)Ux!^swdwIn9lg&o3^2 z*D$y3LuYzwg(2Xv;_(q5h;ZJSxp_Btjh~QcRjCkr-VY&>vM)lsS&@yx4GCw=Z)s62 z#WZBRW09!{8`%g&?yQ+?{b-Yv70H!j-8nS}(;KsuE)VGO+USRo`}HY4ua|4ylrB9c zK4wZ>e97$?ig*0Vq!DLEFter)$3d4k=(SnvWW)R+bBdn@QBfuh^#Xu4o(v&@`?U08KWr7zpF5NPzz_TNU8UC)L;3j+PJc zR%5Us%0uI*b`o=QFq?wcmxSyRRA7!2EAV&Dw;93*Ud|s?616Au0ah^%_Yf6e8TRLhto>&vX*S(QDVc?+?0I0O+>(Pv!@`Y_4 z6NO#gHO9R5XKu`Gd8v>ff7&t`qDHw0UPocso9*+I;G~ygauH537{SO`=9#>AXc2Da zZ#SATo~t7HNw&10ofWhC_S(=tH$#GqY_qKWK^RS;Gfz;eYX80tt;;G7-aUogulA7gv5Bp! zbl7EjKb^6UkCAp(U~H^8KY*3*4FS$q11PPuU{!IKayNr`G6O+IYfW9NECaabd5Dgi z8~5w+r6x!^MYCCpPyx)9)ufeSq&j@bZ#=iOLfsEd>t?Pjy@82)mXgtywNrHI=hDYG ztX@D9X)^l_fMtAXF+m|F;;xtRwfx+qUHF!o&oBE3+C?(T)nO}P_`ONP3;o?HTM9Xa z2l6$iH~h0qx0kktU$~@-a7DzMEaPzzl{xW+d;-kG9*) zOo@3MaokMI=gh2K-K!m|7 z_1O!WL<#dxWSlPX={7EtXw`AYHrDW^paC;j{W@Kq)d=6A&^Xt|*$6N`6F;R3-(D3~ zChjcffXG>iuo#R1Gu_K1bPDUIm~!%XzrBcgn8gIT@Li%7dE7`l=+~Hz9ap!zFrF9j zrv)-RV@qM*2tPVtR6bz=eeEKwY`;5`?0NbOCdMZ3-_ zZgWS`U*G8cl95SDT6~&ka|^5pKT-;LC2It2QQR>GQiDKpWy0 zTOX}eWV5oU*7fe?52rUg6Xd`DNt2#{u)1n@3Hwb)7dukYv#~VYBmAKh?0ESq@S9X1 zKaGW)KNJ0oewu(YHHS;8<+`J_zop^!W&IUuAN8u5f+4(4*C z{#?ScE4(OKfPeT8dEJk+`s6u^uV0q?i!^kDA>~_IMW+QwgWy3=izXH+JG9HV)3WP z)gIbHSg)(OqMP-TTm)rWovevR;QVw@UAZGVA>dTs4=2Wx=ib~9AwDqTp3TPn{ zhWo@~9 zHYTe~?6r>;R#wesM!MMem$AX}Av*MEVqFy#Bg7`r^<2AG7oGxhWIRi>AqCA8pzfmbMccLrAkG_$70JVe8j%W@CBAx67}IlnO0UbHd>!Ed1zm z$)ML8)Q+4YGN!b7>agcm60_#(Cq+cEJ$p-yz`1?Uc~SgCnyopOnh=Q{uqM&6kL&l+ zmdobZ8EYo(w4V%kT$0a>rDQnwK2KW?V&bj)RTD-}JkTLs{*{VHpHBT!aX+yN5Xq>w zV<&@nkx(NOAX06;=8H7HdG0ZfJuXc&zNp*t^l{rcB7h8oXG4&RjSx2XI}V1EXqU3> zt-9`Kcfgyv;Rl>u!r)hSjGXpQW-UMuKVz@#8UFJoZ<|s~yK156>kM<-wJdTkp)?-^ zh)O)hCL_aO_iL2>&e6-nOFWXB^@Us*Ab^IrUWJYh7i3Me$ntH5rfo`TW5bHbp3isC zVv}hN)9VqBZSK`l%-m_VS4ztoZ`J(yC2)?a_6b#==_b9SV2&oKhx4`3w1>Cb91EZp zy)JFM(F!swjNFi;2+r}(`>3w#QSe7JEKR|3lIHoR>wzG6t zxTo~(%ZW|PEqSNJ10xsqiWjRSC1u2A3}b}{nOgXy12^K9=%ByBY$%jfw6N^j)VK{^Z%_u-WX$>Lbi3%<~RI2aGXg} zKlyuX5WV(0HSI?oG@@DApT~SO1ivC_6T&#D5t_#vX>u?TI#1VOS)dkh0Fs1Ri9i7x zQn2f|(5BNE5S^`nzWmj+@8NZnC_U1EKdN{F zP;0_(;k!&k>D04r)3ostG;UtZ8N0?4v~0({hdC=N#1BJZy?vr|`lsp}8!Mi=5rcxg z$-dbg^LBgn&HFVIy~h{nB#$!PKo;z%&M{>>MWm*$K#4T0hSEJLHlmt9_6P%Sfa%V> z$4t2ED$B!)6`o8D-R7^os?Cl#K2!H0jsLUH6=HT0R!UA!)bq~Jg5Zs2uY>BOfyYLZ z?V@_qj~MGk`oZRl(6iI4U-fp5?OqW>9=wR9H}(>vka!!+Pd`jb7coAKgzmP`i>N0lYiDyoJ=5HSmrKcS%ht$a$D#5z{FSEYt$-c6n)Y@!EU1zw+) z+67&4eO}c}9NFFsPu>!VZoZY+Z9cWLc{F(Flb2PUgW1mN`gX*ln6Iu;|14dF8*@KH zlx(Ap0yCON>khK;V3fad^%Ojv+1xqe+2wF)@wOJ8TY7nrlk)iGje==DBPVs+OmqVR zo(|1+5iqJ)?J2g&++a*;8nRNKV3uSQu}EO8h@bQ1v;QCvolZ`m!BCYpH7&rJ0bMU1 z{!x4vmA#0)Q&jXF$i?iQhz`U@+owGQuZzW(Yjal3WiXU|S>;(7;11gmu~v|S?Sh=* zP3p9=O7uFsd{-|E*Xm>aKQdH)p))im0Ey+trSss=PmApPZl%`S*y|%g$a@_XAzC|n z4AebCDcjkd2t0Qsb8~6ft@o~rNl5W4)Kc(;d#n;saTIn}=VSmq)jDOWB8q@#&9KvaL-v z&A^|bPTD`@M3~%5mOZ=c2MmppB%6v@17+o4dIF016I1nHOd- zz4}KfWBCH;$G2B~LLy)3ysrq9IO|6HXk!KYm%~z<%ie%_D7qeENk^v`+wWOlI4FE#Zv8m^y15ZR&oEo`ugU-tFnmUb;3gU z^?SwwTEetW@O_Yv;)$A}w|RL0KHLPhl&GB=eu3S2lCTEsmr_>{p24!C^S{bYo%mwXj1uo3 z`){mzIH!>&F?r05+ztrAA@A%~tem3-WAA5X0`63|E32=!>ffuWyZ3;`G3oB#BHaTw zG~iqc`q-eFM|Nh9rqlfJBXIwDA>+e)UiWfHqRvn&sRqYR{j?iH*Yc`{L2bhPZ+$c* z6)7KvMj7WEZc_WlvHH*jEw&8a=Xdjh|9cmfwwAW=hG4fj|^;89BTdc zo%JYBxR9MKU%QEw7GQKSQpQF`S|QTXUlL262P+_-*@>@pub!W$q|V>2T?O)#KSDmk zc>vD+0~h{*xswh6BO3rm!f#$+wcI-N{adV_8#?`>g4K8bzQuVze^2ZV^4XW>M}Bv) zy3d;c=K)smU64P+!-~HDRq%gJ&i_Qu33h4x&4Jjo--Ht3#r|~d8^elln&`it8Kv$= zF!@wYNk6c!+P|B1G?GAtK@AF7uprrv?y@E6bL01Wwt;o;H+4K(JTosCK81Xc%F8zm zea5?q1NR%jdi;AQ+rl;5H8A;IIoIR7oi9p>Q7V7ETqZS)Rv|2VghBm=ZTPPpN6)kK z%z?#Q;J;7+BX4Q{MFTSOR{mc=z$tOF{=rrt{?~tT!*Tzgx(+!=CtK;9FXm^xzm4rfX@@Q=CzI6a>J3&{sJ;`fo0sd7jm#F&*O2yj%eR$ z*uNBZXjxjjkmd0>3G%%aVBM+aU`Tsf-u*K@JZTcWKWWxraM^tE`l>OK@-LR(wCNW% zD?&z^zADOj%z%GKGR3D&furvoKEJ&jx*JfdA+|-w=SNgMOxG(l1Mcc8NHgo@LU?hH zP#T?f2{sPV&HL_tCXL5G&bjnPuT+l)ErG_kTvr`j#l-t+-rWA2{=XW_uh3$*eZLgIB6#d#!o8FESM^ zj++iXW56}C#S>(MBvqV_pv~*e_=XdkgT_WVRm9n9wQSckkH_(YuD8!YcmGa1hk}!* zs{gWp#p$10(ysYpdT|!SPS1+FWx4FKR@b~|sPcoHmI6P#M(B!23tL5Z(8u*V zBdRIk*>+kmMwW?{ne3V6edh3ag!i8Wn5(Z6QM3|b^V+G=>zKVwQJr)mVq>}XCM|Y? zqNv`uxWrhxK|sI-W#~@lMGBUgzxU<^^4-m6MEJ-NU%JijV7&E(zNhSU5w8uYi!;IN z&0jhVVLLX4@PY!Dqr^$a=Js$;#HgOzZDibNSypDh?2l^%Xz zj4#+*OooB-}BUuTJXejk3Fblc8y_#THtI3qI@GOA z^@5gqh7(kK`Oj&AaygO(t+ z(I!~2C3e+6=o`XA%_Fy&ug>2%a_vY`E`6dFe5!adM~e*vlqYUasWC#~Y>lt8pewe4 zW18|W$z)HNE|BC?@(igQQ(KZ)8F|b-RgtA}a-?%-R6VaGdnZixbj=fvZ9i(?%$n;M zMHAihLjPH2x+QhyKo1TFSTMRNCYWM|-}U8R@hXXx?f>Z6OeFb#$mkbS;(ucTYRE_` zDE9wIYOUB8uH`jvPU`)$E)aBC4qG2s3q9Q7z zEF1Zdh~_J0BYSHkBZop$tkESIhr&+*1wA%<&vWz;1k-f;b!*OIaaVpEG5Z6h+AN~ty`J!pdL(tpA7Ur-YqJb(Pht$OE{44-oBpD9kJ%Ms{#)k)S^C4 zhZ`~|2Czo0mA6oz_o8kmN_e@!M4;?Bxsq#E!ybpeY|aszo39eL$Ub!v- z|DLgioKd;ytbS~Rf6Dru=Tw6~lg#hnu;u+VqwtnNKSR}ntF5}iyj&`jp57_Ld}WEN zVHm4f5wCL2og;Ra9y#7xWxSKJVqnjpu9dscjRIExy<%) z9AtLJ)7D!A!r_^?x;`L?Dd))WU1QN5O;SVowpFmLMQ-yy)*U>bte7fB1)%ya2n;DXc>yT|+;@ z(XXxiWxg-gVvv_X)(s}CqMiFw<_B&yCZ5UZv8D(Z=Bnr1YQch5WNNDH zdS?=8A#~H*KxmU9jJdQ}o!w^K;9yiLt8VymYv~jD@w|g}c0&d zoJUzR??NwjNsv{<*(4*H_0q**BScTp4S6WRrShFUlb1zU7@^v zT>m8Wmv3q}N*zztEQ^OMu{o*+=L+nc-%#)!gc8Way3>QhgOl`5b7i%@vtM}_i z#)H$5k2KtnKa45BAqNj0?b9Vqf&3w1A;`zq%;3-H|81-$QMY|Vz2B5{OHWNqiuBTQ z-^ITA_Q$d`PuJzu7dzLksow1{^T*yQ*{3fH#qVCc{QJfJ#V&Wx1?@lI!}7`7t4(ue zXiCNFrMKgk3&qyI7FnFfe_nRS5~F3xry<$qE5pWhR+o0>|J@zZ{EjnaMKJ3+)qT4b zmhwl5OT4(VBm2Co`IgsqyDr`dfB8~w@^87V|BOp2*7q;0_NrPCrGK4w_4doJ*NOAR zet7Wj&#&$G!k5dK+>w&Io?Dvl*&n>q-rT6ieyh@*$j82WKjv?q9x`pxsfqvUc5i-? zrB=1Z?fatG?^k_ZY!tt4GUHw4{vT=Yek9NQ^=nH^@b2weho1nG{+_SECR<+m^=}v7 zu3v2CFR3=w5V$O83gF9$YcyH36r_x~F6e)%o3d3VFa{WFp;?p>2D8}Mz; zy$ioze17%1VC}oh&z7#g{&m-v<*OHh{IF%2WAu!wKl{(bB}MKE>&q*jcIWdV@2g)c zLvH-Cx)c61v-nc|GHq$AsH?}nPq}{{QAvP_hsq*=!>^^guT0)6Z?9fm6hk4t%gQ> z{@(n2=F-=qDRUw(z0Q9g^mz&>Q1;$_?v^+G;;+~?>zDV6^Jh4lL9Z|56eB@Jwdb+x&C1B!y`c4wt(U?mN#$QLOB8wBmZ zQ8bT>6M&5eXHdf}!y~01SV;pbauXG3Rj4T?bW^z+G?a!gj!P2QmH}&3bcQKQIZ>tt zQ3h&GfNkZH+%)$j*w$8_o97_5LfQjBt00X4U_$}ikN~<7(s}?o440CnTX@pnbR{V- z*)(gLxtbbqmgCJtV6){u%ckkCvL>C3>pE%hsWp7@v~#!5w|S&)O}RL6zpql?(&u?V zzsGrfoN_WwAEjw|!iKI=L&Ud`gFJW%K@ z6}jp2ZJx=^V8vG)g+@2MH(vT)*KqRQYqjd~8NjBSQMJ^c4cCj$byfY?IJs&~18{_F z=l{k7JFJa+z22Yg0QxAz;n{QFZ*m4Vi{+Lt3R>~b^yco%Dd(pZZel0)j2 + + + + + + + + + + + + + + + + + + + + + + admin + + + + + + + + + + + + + + + + + author + + + + + + + + + + + + + + + + + John, ID=2 + + + + + + + + + + + + + + + + + + + + Jane, ID=1 + + + + + + + + + + + + + + + + + + + + updatePost + + + + + + + + + + + + + + + + + updateOwnPost + + + + + + + + + + + + + + + + + createPost + + + + + + + + + + + + + + + + + AuthorRule + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="65px" viewBox="0 0 57 65" enable-background="new 0 0 57 65" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + <path id="body_18_" fill="#ECECEC" stroke="#9B9B9B" stroke-miterlimit="10" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-11.244-6.146-11.244-6.146 + c-1.771,1.655-5.61,3.802-10.063,3.802c-4.453,0-8.292-2.146-10.063-3.802c0,0-5.755,0.586-11.189,6.021 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> + + <radialGradient id="SVGID_2_" cx="22.6621" cy="21.707" r="17.7954" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_2_)" stroke="#E55E03" d="M28.106,33.486c-8.112,0-12.688,4.313-12.688,10.438 + c0,7.422,12.688,10.438,12.688,10.438s14.688-3.016,14.688-10.438C42.793,38.75,36.215,33.486,28.106,33.486z M26.288,53.051 + c0,0-7.135-2.093-8.805-7.201c-0.222-0.682,0.147-1.156,0.795-1.521V37.8h20.188v6.663c0.235,0.352,1.109,0.737,1.229,1.387 + C40.445,49.917,26.288,53.051,26.288,53.051z"/> + + <radialGradient id="SVGID_3_" cx="15.2056" cy="831.1875" r="32.3071" gradientTransform="matrix(1 0 0 1 0.0801 -773.6914)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_3_)" stroke="#E55E03" d="M49.529,51.225c-2.239-2.24-5.041-3.724-7.396-4.67 + c-2.854,5.51-14.021,7.807-14.021,7.807s-10.472-2.483-12.387-8.514c-2.439,0.771-5.787,2.287-8.749,5.25 + c-5.592,5.592-6.47,11.67-6.47,11.67c0,1.938,1.575,3.492,3.523,3.492h48.51c1.946,0,3.521-1.558,3.521-3.492 + C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="SVGID_4_" cx="17.0723" cy="18.4907" r="11.8931" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_4_)" stroke="#E55E03" d="M13.404,44.173c1.15-1.81,2.039-3.832,3.332-5.397 + c-0.514,1.027-1.669,4.084-1.669,5.148c0,5.186,10.366,9.079,14.688,10.438c-3.472,1.627-9.134-1.498-11.334-2.359 + c-3.601-1.419-4.071-3.063-5.89-4.854C12.523,47.135,12.878,45,13.404,44.173z"/> + + <radialGradient id="SVGID_5_" cx="31.8184" cy="19.3525" r="14.63" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_5_)" stroke="#E55E03" d="M45.777,43.924c-1.317-1.568-5.11-9.424-6.604-6.617 + c0.516,1.025,3.617,3.693,3.617,6.617c0,5.186-10.271,8.576-16.699,9.145c1.429,4.938,11.373,1.293,13.805-0.313 + c3.563-2.354,4.563-5.133,7.854-3.705C47.754,49.045,48.006,46.574,45.777,43.924z"/> + + <radialGradient id="SVGID_6_" cx="30.4893" cy="4.8721" r="5.2028" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_6_)" stroke="#E55E03" d="M30.777,54.167c0.357,0.836-0.153,1.983-0.352,2.813 + c-0.256,1.084-0.072,2.104,0.102,3.186c0.164,1.02,0.156,2.107,0.25,3.167c0.082,0.916,0.482,1.849,0.357,2.75"/> + + <radialGradient id="SVGID_7_" cx="23.2871" cy="5.3008" r="5.5143" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_7_)" stroke="#E55E03" d="M23.695,53.417c-0.508,0.584-0.476,2.209-0.398,3 + c0.116,1.183,0.456,2.099,0.333,3.333c-0.192,1.943,0.154,4.479-0.436,6.333"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.2-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.501" y1="-12291.5195" x2="6492.1304" y2="-12384.9688" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3351.7349)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Young_Black_1_" fill="#5C5C5C" stroke="#353535" stroke-linecap="round" stroke-linejoin="round" d="M20.278,13.25 + c3.417,4.333,9.333,6.917,9.333,6.917l-1.417-3.5c0,0,7.094,4.691,8.083,4.333c0.968-0.2-1.082-3.807-1.082-3.807 + s3.138,1.795,4.854,3.969c1.803,2.28,4.285,3.504,4.285,3.504S47.027,2.719,27.289,2.744C8.278,2.709,12.058,27.678,12.058,27.678 + L14.695,17c0,0,0.914,5.757,1.399,4.875C17.861,15.211,18.861,11.5,20.278,13.25z"/> +</g> +</svg> + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="67px" viewBox="0 0 57 67" enable-background="new 0 0 57 67" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.199-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.5" y1="-12286.8594" x2="6492.1294" y2="-12380.3086" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3350.4617)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Female_1_Red_1_" fill="#FAE1AA" stroke="#E2B354" stroke-linecap="round" stroke-linejoin="round" d="M28.372,0.5 + C17.537,0.5,8.269,7.748,9.153,26.125c0.563,6.563,5.862,12.042,9.366,13.531c-2.929-10.968-0.304-25.021-0.585-25.526 + c-0.281-0.505,3.536,6.728,3.536,6.728l3.183-8.312c5.541,4.28,0.393,11.309,1.049,11.058c4.26-1.631,5.34-9.228,5.34-9.228 + s2.729,3.657,2.701,5.504c-0.054,3.562,2.194-6.067,2.194-6.067l1.027,2.031c6.727,9.822,3.684,16.208,1.648,22.781 + c15.666-0.703,12.291-10.48,9.66-18.407C43.59,6.092,39.206,0.5,28.372,0.5z"/> + + <linearGradient id="body_1_" gradientUnits="userSpaceOnUse" x1="95.9063" y1="-3134.2153" x2="31.5133" y2="-3134.2153" gradientTransform="matrix(0.9852 0 0 -0.9852 -34.4844 -3031.9851)"> + <stop offset="0" style="stop-color:#49AD33"/> + <stop offset="1" style="stop-color:#C2DA92"/> + </linearGradient> + <path id="body_8_" fill="url(#body_1_)" stroke="#008D33" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-8.244-5.146-8.244-5.146 + c-1.444,6.983-8.555,8.786-13.007,8.786s-11.322-2.643-11.941-9.439c0,0-4.559,1.199-9.367,5.674 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> +</g> +</svg> + + + + diff --git a/docs/guide-es/images/rbac-access-check-3.png b/docs/guide-es/images/rbac-access-check-3.png new file mode 100644 index 0000000000000000000000000000000000000000..1fdc0d935a370f3165c9d96ad335dad6e3e1b364 GIT binary patch literal 19336 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 + + + + + + + + + + + + + + + + + + + + + + admin + + + + + + + + + + + + + + + + + author + + + + + + + + + + + + + + + + + John, ID=2 + + + + + + + + + + + + + + + + + + + + Jane, ID=1 + + + + + + + + + + + + + + + + + + + + updatePost + + + + + + + + + + + + + + + + + createPost + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="65px" viewBox="0 0 57 65" enable-background="new 0 0 57 65" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + <path id="body_18_" fill="#ECECEC" stroke="#9B9B9B" stroke-miterlimit="10" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-11.244-6.146-11.244-6.146 + c-1.771,1.655-5.61,3.802-10.063,3.802c-4.453,0-8.292-2.146-10.063-3.802c0,0-5.755,0.586-11.189,6.021 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> + + <radialGradient id="SVGID_2_" cx="22.6621" cy="21.707" r="17.7954" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_2_)" stroke="#E55E03" d="M28.106,33.486c-8.112,0-12.688,4.313-12.688,10.438 + c0,7.422,12.688,10.438,12.688,10.438s14.688-3.016,14.688-10.438C42.793,38.75,36.215,33.486,28.106,33.486z M26.288,53.051 + c0,0-7.135-2.093-8.805-7.201c-0.222-0.682,0.147-1.156,0.795-1.521V37.8h20.188v6.663c0.235,0.352,1.109,0.737,1.229,1.387 + C40.445,49.917,26.288,53.051,26.288,53.051z"/> + + <radialGradient id="SVGID_3_" cx="15.2056" cy="831.1875" r="32.3071" gradientTransform="matrix(1 0 0 1 0.0801 -773.6914)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_3_)" stroke="#E55E03" d="M49.529,51.225c-2.239-2.24-5.041-3.724-7.396-4.67 + c-2.854,5.51-14.021,7.807-14.021,7.807s-10.472-2.483-12.387-8.514c-2.439,0.771-5.787,2.287-8.749,5.25 + c-5.592,5.592-6.47,11.67-6.47,11.67c0,1.938,1.575,3.492,3.523,3.492h48.51c1.946,0,3.521-1.558,3.521-3.492 + C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="SVGID_4_" cx="17.0723" cy="18.4907" r="11.8931" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_4_)" stroke="#E55E03" d="M13.404,44.173c1.15-1.81,2.039-3.832,3.332-5.397 + c-0.514,1.027-1.669,4.084-1.669,5.148c0,5.186,10.366,9.079,14.688,10.438c-3.472,1.627-9.134-1.498-11.334-2.359 + c-3.601-1.419-4.071-3.063-5.89-4.854C12.523,47.135,12.878,45,13.404,44.173z"/> + + <radialGradient id="SVGID_5_" cx="31.8184" cy="19.3525" r="14.63" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_5_)" stroke="#E55E03" d="M45.777,43.924c-1.317-1.568-5.11-9.424-6.604-6.617 + c0.516,1.025,3.617,3.693,3.617,6.617c0,5.186-10.271,8.576-16.699,9.145c1.429,4.938,11.373,1.293,13.805-0.313 + c3.563-2.354,4.563-5.133,7.854-3.705C47.754,49.045,48.006,46.574,45.777,43.924z"/> + + <radialGradient id="SVGID_6_" cx="30.4893" cy="4.8721" r="5.2028" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_6_)" stroke="#E55E03" d="M30.777,54.167c0.357,0.836-0.153,1.983-0.352,2.813 + c-0.256,1.084-0.072,2.104,0.102,3.186c0.164,1.02,0.156,2.107,0.25,3.167c0.082,0.916,0.482,1.849,0.357,2.75"/> + + <radialGradient id="SVGID_7_" cx="23.2871" cy="5.3008" r="5.5143" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_7_)" stroke="#E55E03" d="M23.695,53.417c-0.508,0.584-0.476,2.209-0.398,3 + c0.116,1.183,0.456,2.099,0.333,3.333c-0.192,1.943,0.154,4.479-0.436,6.333"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.2-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.501" y1="-12291.5195" x2="6492.1304" y2="-12384.9688" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3351.7349)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Young_Black_1_" fill="#5C5C5C" stroke="#353535" stroke-linecap="round" stroke-linejoin="round" d="M20.278,13.25 + c3.417,4.333,9.333,6.917,9.333,6.917l-1.417-3.5c0,0,7.094,4.691,8.083,4.333c0.968-0.2-1.082-3.807-1.082-3.807 + s3.138,1.795,4.854,3.969c1.803,2.28,4.285,3.504,4.285,3.504S47.027,2.719,27.289,2.744C8.278,2.709,12.058,27.678,12.058,27.678 + L14.695,17c0,0,0.914,5.757,1.399,4.875C17.861,15.211,18.861,11.5,20.278,13.25z"/> +</g> +</svg> + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="67px" viewBox="0 0 57 67" enable-background="new 0 0 57 67" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.199-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.5" y1="-12286.8594" x2="6492.1294" y2="-12380.3086" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3350.4617)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Female_1_Red_1_" fill="#FAE1AA" stroke="#E2B354" stroke-linecap="round" stroke-linejoin="round" d="M28.372,0.5 + C17.537,0.5,8.269,7.748,9.153,26.125c0.563,6.563,5.862,12.042,9.366,13.531c-2.929-10.968-0.304-25.021-0.585-25.526 + c-0.281-0.505,3.536,6.728,3.536,6.728l3.183-8.312c5.541,4.28,0.393,11.309,1.049,11.058c4.26-1.631,5.34-9.228,5.34-9.228 + s2.729,3.657,2.701,5.504c-0.054,3.562,2.194-6.067,2.194-6.067l1.027,2.031c6.727,9.822,3.684,16.208,1.648,22.781 + c15.666-0.703,12.291-10.48,9.66-18.407C43.59,6.092,39.206,0.5,28.372,0.5z"/> + + <linearGradient id="body_1_" gradientUnits="userSpaceOnUse" x1="95.9063" y1="-3134.2153" x2="31.5133" y2="-3134.2153" gradientTransform="matrix(0.9852 0 0 -0.9852 -34.4844 -3031.9851)"> + <stop offset="0" style="stop-color:#49AD33"/> + <stop offset="1" style="stop-color:#C2DA92"/> + </linearGradient> + <path id="body_8_" fill="url(#body_1_)" stroke="#008D33" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-8.244-5.146-8.244-5.146 + c-1.444,6.983-8.555,8.786-13.007,8.786s-11.322-2.643-11.941-9.439c0,0-4.559,1.199-9.367,5.674 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> +</g> +</svg> + + + + diff --git a/docs/guide-es/images/rbac-hierarchy-1.png b/docs/guide-es/images/rbac-hierarchy-1.png new file mode 100644 index 0000000000000000000000000000000000000000..7443fc7e71f555901f7f2d77aa7c27924c2be2e1 GIT binary patch literal 16085 zcmcJ$byOT*u%t4($!5$vKm?mpo$29f#(y$Ok5X}=72wmhK}I- z;-I8XNEHEdOJ;6mHPm4{zvP|u%75z1)~5e65vlw1@m`30opn!m)o+)7*Y{Lf0r@T3 z|JHV9R6Jsxe|H+$|7`|6epTFHSQpj&dQ`owpjZd$YjW(IPTZSOroSY2%iYmpM&-#} zv=prC`}DqYE>#n&RGeVe`U9cgfVClRCW@%(heXY=yCrS*x-r(+yRJGj^8tD7i>6zh z`cIl!n)q4|+igkNcWrE$oUz=JUosd5H`n?P&`Q-2@~n9Yoya#!_RM|-GWjRsGZAaG zN;`CM#crT#;p^I&;PxIAe1EbARMZNvhP;2xdc_|+JoD$H8WjRpA6%nA7Ymo7oZFBE z+;3~(4}y-t8>b;+UY;<6b?I_Sq`-s0@iRpFpv`N9!OI@+!x0 zoZzy9h}Ffg^?bi2!e~66V~&q=vT|xhZO?LI6z%CJU$m)orNa}}eXI4qJ(j<4l!JqUR<1&xoEGPDI=eju)V2Fe^i3iJh{X?R9 zGzM^{TX;on0C@5!Px81BKX{hmMAs9l1Yz^yeA0OW7vP8746easms7%KWebE+ZWiS) zP@FJ~>TH*9=C6+7=q)`ET`zzkCQVzrjnjT?(Y19LXpZSz7z3&7SdHemWF{k))H}8@J_syEAyfC9grCq8>H6 z7ZT?Al2@}Pohudjq`mk8?^J)BTN3DfIXX$xbIXNK9$9D|1J3ID)VmR+aRS?(u}2di zJ5?3TQ}8wioL~Cg(keF`y#mSIK%9g@u*u@`G2mqp1IV^(_L)^cSNpl^x9<()JD}&fTKMag&-!Kke$?IwrPw7UA!H1o=HYV? zK3EB5WtyBKwu$L~LHLQS7_zNo{n{t27y8V=tHB+iQ*% z@>vIur!N0RY%2KWPp=levV2S%=mH6kQhzIMnfG{=HFJ2{bE(Y`FNWT0Sg8po!aMj zgBy_(U&rW%T5%=#k2<0hoFRh)vb%lcPJ^c6xOn(0t2g!@t8gy8Wjry=9*Wi}DJe6v zGc&%0N@+Z^US3|vx)~;`1GS9f6BCq%M~AjS{kj?q6-0(Xs~0pB4`RmlyixCUxH8OX7vG zy+GOa$Hn8-UNyLHYq)sqUQZM16|8b&R@jhK%qSdQuSA3W}iq;-aT@{((I?%j`W&0f~)lSLDV#W~KrUi11| zvo@MUkCwyPb#)6TFUX~vNwDoDC{kwBxJ7;AS{($l-PKApyX|6T>@?HeP1UH^XpaOB zAG!hMm3}ah3tw+D$Es&!BpT?h#Jtq;Df^l+=0u_Q{<6aLSUz59 zZs0Fbhh>n57|sJ zK$0ZajKADwGkSxsNz-tS$)=#Ir-f*vx|#M2RApjkfnn|Wh|V`Y36dVZ1TR%NruZIAI6oVox%gt zHo)PQ)Q~7wBnYilee_hk`?%X#2ZNTxDgVk@J-D6HPwym)>CP!ihrYkVP}i7ZyXp}O z9ep?k_0aCidxxLp991U_VJumk`NN!kuJuar$Su`2Q~);S0+?YLkjkQ)nT=k9`o?fu z;W9$w$+)T6@ZQbnenN(W#GzWkPRD_L8V}aZ6UGmbz0N zJZ^f1w2LRAISFz3LHvuBtK2+qQq>uu;`zEV#5Dzeo^Hq_0~5U>=0RE(4c(7*w7$XyHJ@HD+@BO+!a{M`{JfR7xALz=>!AN)>M7uB*12RokL$Y)_ihOTGriJZ6Cy+ zn7sGTFupCTh+n*5ay5=`zlm^ibMp-g;O580VY(iCE=NNYX!V31WP?M(CP$}kX-MCD zK#~_YkyV!wh_$0vIIy-<;Ni*m9NSOLesrObVL zf8st}erK1TA@0z43){X>tS-yR!4&G8MX*bXWe&}E;^9@ck2A=?eFOd7mtkD zoTfeun^!nP&DBzSF;0K5lmTV;$q0upTfe#>PQRifMt@^LhmVftUhT!bJqTp_=xfAl z;6CXahhTHP`qNm&g$6tC+CWXg{BH`f2P=b7UJI^yR=Fe$BGwfpT%gdnwI!k)(Owgy zU45jSXk_8yNLRg<4q4n!fPvkF)u4CGOTUAmYOF2Ay`Pu&Z!h~m`B~DE_B1s z!-#pYg#S(b+nJe}Pjni&v~ACh#mQoh2Y-nr&yJDGq|oCdE<#K2j`jc%axikzC`-Ve zU-0jJpE!Kv=)asERLE@!=Lzbj5~}^-R2hm)Q36+~5{>tiOuH&2*^T^h!t~d;_phsz zMGi!IRUDg^LI9+8weCc8ZuyEZ9T_hoaDFV*}8aC0d3uwebsD_kvDSnIrZ z^f0mYE6L%hg|6e>N<}##A<==WB!wo`(`Bd*MaJ)YQ{CXgwR=}dPfN=Z3xLR)&zVC7 zQxa7otd>s@7jk}fnRBX&ys?oSBp{Rk{{SR5FJ=PDll_(x70SGLNA@<)kDcf1qoSsD zCGHe&jJf=Qi|0q_AzXaC=lR+iLwwd&a-ERF#lG<1SXCgFB^zHxEp=D#D&E=)yL8u$4SpDLBhA9_yVxE z%Yp1+LcDyqarLk9lN@M4?~{`@K)QgMZ_&9BQ$vjjT6iBLCjNz71~>4kvdaoCIsZo` z4o=R4lFG_g_bgTm7w_RuIk|>X0|QunckC+Py|tk=)BXr~ylAvGs$H|WpBGqAz(yuy zw+LEhyBQnriwTU-&8u@Ucr_F2b?C--WTKs;hxDY`GC4Qn*}uNDuX?4IGQ{5HLk*Tm zGV+~_z`6QcSKWu?wAk%PJOSj-2XgaN;jwJe#)|WPiHi7}g_8SiyB+L@+s^ZtX_@q@ z>eJ{d8864>zu}sZDO_Fhz%fw{GcK@@j?>bDBP)P=X-dCFj;^Ql08I`HxQn0Uq=Jx_ zFcq$>m>tRVuycE0Ml)db%2*Ssp+<53BUaj5bsZ58Xz4Q#RV&t9E%AZ+d{&o53IL!X zj{Fvz!(b8N2&dcxtO*bsYODDQW*)4*L!kGuQ2HRP*B9M%sXd}{Zn)N z8;2*Q8l#zv*=^6}L)&KB0+aM@Z}{0ze9Z}>H_$owd3Zxi-~}0ghN3u%!gJ5%8Rw=D zC1Vah`C>SrLy`E{fJuha=kMahN&MT&HWt4a&JcrI%IQJV8MTr!*sGZ?PQq_8*({Fr zX!pJ7R=XXO2LGzxPz-KiMdHB`WFbtX7rT zL`Dt?PHB<=eHsKl$j#86tG4vQ-LyQErTt;VSHRSNV3s8W$n)SLo6^;wS&d7=glD~| z2yAE(;4e&JWW{OtPRvb+;BnMH^LzG=qeNKvyF|fGhJRt^rVBe!QVX8spPko7R1H=E zoYJJtr!G&xR&(LgNhEsvyoM>)%taC@H>U}#DjHc=@qK{Gsx-Lw$Kj`7cz&u=yl%Z1 zMx|j5^(w%U2uI!WWDV(L{A$mP&!&&8Ren7KDjIXtzfzaWp5VkMxuu+^)E;VY-pJ0% z?G=^dJP~7C)XV0B0U5}lPFIg1@GP*R6Q_&I%G8JvjkM4>$R*RD(ux3BRh!*>J^2kh z#RfafVNWIiv!tZvmE;_d8p(xa1%n(x&MB2w-zX_sZ1RaY*8ROOJx_4CS`s-hQ^Nx? zh{+{h-^CDwg)fOxfb6W<<>DI77#75i1OZZ}WRDgr-74s#j=@#7>h5wr&U!@hKA-ePA~k|VMSib4-bB+% zr{c>dy~j$}?Df!O_`>0VT#iO(Sm-HMEiN(!m(e*8Z6rwbThT8*+V=XWmuz5l%FPGb zTpA_kiojv0v;AhBbaG-{{zlM^+66e13h7&*Y+h_JQk7}3ROdd7zmxjah@6+5mx+__ zYF)#a-UME1j*OHS$OReq3gi;p{Mnn3AW#cp!adrH%OtaHqo@9HJxAbQQe%tko|@!l z0jk*%e7P5VG=GY&o1oS>cCIGC*02<9A`wat0@IfSg{#5irS;uiD+u{0S67<;v7`TU z8N<PNVO9z8VSiY$r?SUrOfqe+!9hbuAIgxHuxH-F+JRX#SS% zk5y66V#1cwlI{#6Z{8SmccE0@kvCUb%8wU%#Bn-97*T#NClh#(GYk7hvsGk zedZ3fpR|!C5+qL4p1m0IXs#N*lUtH4)E(;?F1;+rhTn5}WJ*%m7Pa;}vvw*;wW=05 z#0j_6fU+{5gW?-|g|o1BBif1Cym+--#uQw>wT~K)ncVKJsb&i@E-kk*Q2*KbmG7cz6(O%Qh#5b-}mj#C)NpLNNL^g^|<7?u2hql0JxW=r6INNgh5^|1Xt$BLMNsf;? ziQZ4u`Q1fkR2uHG^0TDEI?JpdgjL75eAR}%_%1Xn6lzM{!QfNU{oTS{;HqA~=pzGL zJSv5Qd$4PJa*5uC(^;3ONP|=mNIVY^Wch{QoRINY%;*J>F!Y2ZLJ$**`6>w=9;%PM zN%hdsqbF+(XXX5{GmYp6X=Z2XoE;uTvxKFy)W(WhxHo8bdZjopOteFTpgEa&M#mk} z*{-K-5v!&+MCD^^mq<%F-7)_R(~f3on3_=8g7S%YFveSLR9qW$A-)}`$2?GkB_1)X zfF*^C$N|Bzb(0qv*Gusg7Ay9A#(vFwJ^EehHx%@%OYTc(dg!aS&mV7s45DdTSnFGP z^yz5GDRFF9n;KF!O%pE&g_vzZSWY?_TdHrk4(rx~U=VyLo5G|x#Ra|S(s>%%$qmYH zZd~n9u>IaM7!_H2aZl%d03bKFxT6S8M=!vT&!p|5L7!65f7iIrPz3>pSQD;o)HB>1{0Pv==csl+KJ`K6_|PwQ-Cr zXiRZbNti1<9_?xv@aL39`$B9ThNKVfp-L(iL_4G^#FdCA(G>)oH#PcB@`0K6@B-R% zcBapX%nwl5Z`n&u;=#t#_L_eOOqL#84@_Mv`#91&r+t{{vf6(Q+G*fRU$4~GsB7#T zqC81hOS0rY@sy_%lf#fkW5Ja{*ZQtL1aV++b&-@O(r;v+xc`vlpO&JCzr^koq z+O|bKGk?O~6>d<&zYu|cnOi(j(r@1RX1s>x8r6m$8Wn2#ye`aPhqh5CE7_DNqI2fu zv_EUGo+BzBtYk+lF(Fb#5GY>wK6_Y7)mW{93dWKlLV1~-HXH6V#2+guMw?@tYtrzP zAGKZ0yx}{1U%v*j>GfwM!xHaUyeC$FxgnECrbelw_{APg^bt$qAO+w;fqVSrhb^9r zmA@AR^M~D_C<&S@!RPK|mFj0BVcfY_&7st6CAm!UI9oU8A$@!K;O?NkoD0Sa%#(0& zDdWJR&+cqPX2uYrACibYE7<`lqG}Ce+K`R=x3*l@@J!?yU@l&gC#k8Ll@Omz0 zaQRP4&8!aSW&tgLB;r|_*%l75P*oE;42#_gqWHe=9ew=iv4Urs9l%*rZ)0q?meiXC zhD1%3Y*4upj6K(RlPERk4!Q4YfCxQd$B$UcpwE6(lc_@7E~~XrzBcu1Mlf&>caKNX zhQ(ZM8=W5y=hXSW?Qp2QHT)OpR%?Xh1(i#X`>RlsdP4OPfU8PIlcuC2Cf$I;XMoYIQd^83Lyp3%N z&PLX*L5%hO-kB?CUU~R4;Fpv~+s03d?7$&?;|ioaUFKLk!dMe!rg+rS!rz&b>1HRV zqCm$AE{b_Fg~nw1SU+ObHZAR%cdSc`Sx${E=C#`J*|a-c1Z-ql^M`f}4zeoop{T6$ zVP=WVH7wAGkYQ5#a<4C=m3_T}Gg@ZK4ocF8k4f*}-rswZ0Me9PQc9T_e2d@Z!yc40 zzunbHQzJG+Ck9B_yJJl{kz29)C)s*w` z9kDKi+v}i4w!OBB=)Aelc^=(P*Cb}m+`&H#vq%*A5cxJi$E36^ZTv#6OWN*O@N=45 zaCP+e7KIzMdVMEWcefbu&E9OK&^mh81 zJ5_W=jN(i(V=#bqVxeY|tJzQd0yIWHR5%-NFE$_NzO2vD8*4BU&TW?;{$ZPXJ+cI6 zRO~3As7&jXWpr95np=$8hwArHs?d^dC+~g(G;4HfMEiP#pNe}`bzo-|*Klfm)CU?F z3(PZ#X`j~83as*=azE9{iDCP+0a0liR!fzwHanUPnr77~iJ$p}zn9A*QWH&f=?3m? zGyo!wBvTz*glawieUKbv8S~RJ0ligo_3W%*vyEX5xlP9NbFPCbL6V-D zPjCb0-_%6%%wC76g#ivKq7iyF`P1if`D4ik_&f#$;Jr;+0UjkC%h^;|YMHzpN55v9 zjHWJEUdu6#Ux0-zDP3n2fVrZnyrXZZeP~6FZ|BL6q6`Iw?Qyesa^hD6bcNO;vr$~K zG_m9xmOKMJiM^%f+N-&IbsWJH`l0)68+qd8*vSDuacMspsdpAqk*EOQ-hn|x<;r`y zo2hQ27S9Q0OU^%)jZZ$~jKjF)THh^o?CbNbWS6aG?yIN44&@8ZwM*MOG6WArWl}w1 z%E2<3^OG@}{_*9&F5c^vde~hPTIlrsaJ=})wES?$7Eep7wy?hp*?Jc4L<^4(X;bj5 zt&_w2lLQ-I?0CZh9&%5St8g%An^Qq1SGb_kZEqP~l$>?r-ft^d)@-jQg>2@8j2~QX zeqQc7$8qXP^T`cW(`)Dz=o28im%9$F7B1^mITbWeQvy`@+}vU6dBt99F*zX~D%xXa z41NL2T362or7};AJz9saR5jS%f8MdtBBs`SUh@vcx18TwC3^0O*13g)h}_E>!|46_ zy&~}aV(uz?3o}ct?J^BQ(gFc(oJ=may6*$0DHp2kxPniSvYE2|aqvGxI>kJK)P9>W zhNVbYO-$(^u>AP$%cLV)r;L!pq|K^sA_1LGL#ui)-wrvt#2(c7LyPL0>}2tz7(sgs zY|djD#NPw0Ku!VPKBn?6o(U%NHK^3r0XmJXHqRV3f@luylT;1MvlVrFdH8m1SIBlv zd+H>>Qr+a~Ojkv0_p9tbrkg0nkxx6kHq}`A@n@OJVg=3qIou-e|2E=M7 zJCB_PsySSQ2wjoNljtXJz)xp;6!J!$C)*oB)ix}G`OIsVH^`IH3g?dxq`NHpCw7Lh ztomrjnm3tRr#|+`I=P~u~bUy4pHMo zrsaRd20o1ummUUKjIcIF{4qW4SIl`yYmMuILv})}&tH#o+lHI`!Om1OKcj^}yP5A5 z=bL=^+(w#=&~`OkM_el~5=DBWHWb(_)5}|hE7;4vu-`wFfzv9)!1f(>EKCJpC!s!s zexP{aiUv8fROTG}1~6tn{FH=FHBW9C{8LWOf;5&n<#`x}AroYBs(O(x327UR#2WBX zkn35=m72ZJi+YHvu{@0T@w5b0oD6jrAp-&gR72cN<*%RKs@`i^3(ufR*5_O#V@gsexs2>-SDcz zu{Ye?b1yE>y=+%&ONt=T@URX&&SV0F@}`9S<9u)gGz-JXA(vJbXhNTq3XGl8C)4e{6y_@T z^pN*dysA$1c;D&WnC?E?xJggYXQ@zbOqp?rRE~ti%H@925A0k@%AwSxTG~v<)hxbQA zm3W3g1BbbijhrX)2(V)^1{MQ3yWRw&e1A0Hm$FWYxyoa>*$KI7feLnf?HtzIdl(oi zH93m&p|mH|KGfGJ`xx8V&9KUOVuC@-Aa!79zi4?~+g@*y)gh~5&hlJ=@D~aeVXgqY z47b8}j}PPDD36P9nL>|+KdbFwLj#PQxEh{9R!|mJAH%MudFvQHc@OL zUYz&=v;0|6Lq+RG1qb+iqZivnXKIa?r=3S$qM931()$x?GhLrsA6{@>?)3I)MW}g*IXod_|-lu6{@PJ=7SkIWSK;K+p zo=Z<%{3|U$YoW%363153^AZ){+|3I)y2(8A@T1H#2@iI3J2v+3WpcKad>D8fXiF2Z zJKJ5#f?X+XZCm!NpUIBVYsG`jy3Q7!vk-EnfXzJ5`#MEdca|p@9kwTH;5M&T>Uvx> z;e`Fb{wr2Q$NFcIj?9%LP>*xPR$BP>&t#Eg`>$OfIT5&t*OT0@Ma*f*QdwzkFJT?j zuE+E4L7<~4;wJy=w<+=|7T$sA2ccwedYDORWQpL0w@jZgw*;<`w=TjlspQ3K?}pJS zDa+D8D}A56%8=1i+GM}4(!Af!)h9IMl<*PY&OIh=?bo5RmxvlVUllpYm28(R2Zlh+ zRBdWJ!)qN`jjZK?7ACMWi1koxh=OVTC^bG93pTY@jMLoTjQNB*5sjv=jR(@52{!df zTOCzt>^JJXuWYKQA(-U;!|e7j5m)zkaAk=Fj8th(SS>cLTia4i#ov^7Hiery!Vdhr zbMDXm+=Q(p%=5K?lJchO5dLlWq0D>*X$4)VYJ0i8A4%?1=2h3!&~lx3 zzV-)0Sr?>zp4>kc?NJ3WDx$r;;L0b#ou09SDU^@tR>S$lSBuvvY<3ja^OiDRX(&b8 zpbT4IupP2Kw{HEsy~FFl@wq5z!t+ILjiVCV`p4YK;SVY^9dYBN(Re+dNe0pHiXy-) zLGgcRkI5*7=sh(ghm2P0H7Ec)f^j|>)Ppj9^35-Jc{+;<^`UhI@BnRhQw4rOj#yhA z3Rg3O$pl!eO76;#tHN8A+X}_;N5b4k$d#^%kb-Eh0PWt@`vtJ+jRHCFrZYu#6Su{z zu4$xX1-YKsIH>eY=EiJl^wwatJ!5lK`6z}PI-I3TKFP*Z!(ig&oH>f_EwAS^_kD9> z`jvKgC2)ug3Ox;f}AB!bFX@YJVWN#s79hmcLi4@y((73Tz%Ku7<=V$ ze&)1kkm0WY354i!bYz-p$%V?3STZc&%A^?Mer$vee$vJa@NO@D04b0DDAvO~%I0@V zUXr_mBKzlXH9rY2)=9&8Yw1O)71H39@9K8Y#gB%j4^GW!DR7@3O(CV9-ML5Fu7#7` zD|eV{Fe;i@d1HfwX{;!UTl2n@VBN&^7u_WH3e$988cxJ3b@|0qnIRvEMIgb*ElGL} ziN%ju%OR3wjf+;GE|@~HVzpkpQhILq2OEtt4a=biUwBP9SISiEl0FZ!4{0V0Z=$je zc>1QWr0Z@PcS#0@%AOZA=*_FY9H?$&#H-R;&dbAMA!l&<*U8}m_Y+dpZlF!$FDc^T z;;V|6?~8fs^AkB+FrU{io%v+$5sO=O^!T^W5Ek5hmFtmd(w3jFVC=dXw3S}SUh=7Y zLn^AGu(~CPfaWbURt%oS5ZI!I{iytq$&S!)q^(ZQYzw$pO+A+ zR*~9^<`M05hdXshs_}MNlI^xkGqlAw@$yC_x=!L|`)@j%*(2J<;vgrh`3rIQ14k^+ zRc^p`L-*q>5_EZQV?>qM$NaY?e$)PD_d578)=4HOW8H<&pWfzkorxuRzs#Ckl*NnY z+mDD0c3xv!w5uX&Qm1Qx+c7b+v=DdtZ!|`-J17V^yu?ttpst=tqCw{k^*GC<+}m<83vWISFj$ z4c)7{>F0^Ti7ofZNWA{2oO=%H&nMyQ+`B>WBLXMNTrsT^SEfCm4<;zNX%`adWYG)c zWcBxwsRMuQR&gN{(qF_7iJ9g;Ozlbf4l+%~!A8TNiv9@|qV|gOmQo?YvsaPq_KvmD zamV8^uu?4|Y`W^lhpkO*^ZWKl)Q#j}t2xxg(NIG&$ zQ%_zl=njr6NJHIU+!nYOWtnL=ubLJkrq=&(@9Wj-e4g9k5j!SGNQRT&LUTP?ekAxD zdbYBx+B9^B5Jh= zGCrIiSacF6D*zIt9~O}e(lltW9{$|Mnlvo~rKo6#Ygq-!^l{K&5x6#n9fh_{+yF^v zI%^Jf~NTB~U@_#qC(wgb#jZm4=m5NulbdOJJi5it}OU-m{PHjc# z9MLs5mu#$NYYcl%MHK8!mm_va)5P`EXD2F-MvCN%jtPlBPs}$@FE#1vmup)a6rW_j zzg;sVylOp=a;9Ww&@4@@yMIvm(ooXyQ@BYx1kMN?KVV6ZA~nOvqxU=?wXIDMTg?<)=h4GuJ7uvmb%BRWV7={**b ze~<)4mv9q2fCBcfL4q=Njch3pCl>mzAsYkeh<+lD1oZ!FP(}C~gaS5C?5~00J=Rj7 zcP#Wj=cvX)pZv=)r2oVnMDcf=w|BQp=|8}|mZ+4oAc|{{7yrmp}-HPKG z*EXP?QkQSTv!>>AOO#{s+Z9Ddn? z#AfhD<>p?p!Ad@~mBEqxg`jY#)@!Yz2N^oq+aG{-PYn}wH!Bev10#HV&3ETdZho(2 zMa7y15ng3NI?t4dl&;H|r?M2n@TW~omoa0^w~w&&mdK_*vv@r#zhMBa3oXblST4B= zf>p2#MelCEf+`0L^j+L4&IAzh7-{-uA+A^3D$#>Yg_OF@I+KN~+!Wqf_T9dVTs*4k z%MQg#16jp(V6KO|;cpQz;pk?$r^F!VO^a?{*TZ({W4H#|W!43WFKW}>?dq?`8w23V zRS0v&@BLgh5t`6J;Zkb~7E@X#`mky9P^IJD-3$wQ)@Q%9r3da6l=_l^NJ>c-wu>FdjAwfc`=GT5Iw=TOujbq>#Q}}z zdYo+r_B=+m<+W!~--=@9vB~h<_vy>zz{SA%01!s*;jX0RB^h`g-sJCF7(lCM-~t?b z>kANRiq(B=zP1*)X)k{%j&wiwK`cC(tq#?I&0l+z6e5r*bHktA_bC9mOFtbEkn3EV z?viB~$`e`r5K^X)BY(wuAAf}dM%RiX$_{#)xvdN@Iojh)MkJbI$w{$;t=a6~^UJ)4 z9h6UHh5Y=1VqW5X@FTywn(n+sOp7f^MSl9&RT^5jlH2HE!^p{FYx$Q=_R$((zn`qV zjd9?)FkN_ZcD9Z=QR1_FKi$;4#j@DRg9b zt7nOF*jm!@*N(&_SWvfO<{U<)S8xUhXoz3Ee{il5hwl@CwyH})C1KXcJSO<862U~y zC}^l(z1O6jE1&^)`KR??Jl;lOLlO>uFCCwx>#GM(LIrjEGm=V*#K&*pTJZd^zswJ} zb0t*r{o4?jXVE>PI@hsFbkDzn&^Dfx9E;x09wQbZw(8iGx#ZHfmPGoShB)8*e=U|! zvC#j1!Tqn6)BjPu{}1c?zZ33_5&kU!{5u~1MV$EG0*?PM+#9s~rI-I2aQu%M`gi!| zze7O(wQ&DksCh%OfB*jfD{!rmp}qUxX0O`|)yDNk7Ok1`Tk(G-ruOsBe@jeDlVyg1 zm8tZN>@QyY zai*zxG;70F)6W0zBTo)~Q6kIAV8_rbrE_S1U?a}do8kWpI!x|dXyr~br9Zf&r9XVi z#d&*bYe1hcN-SoA#Jc%<{yzxi|B#>l|B*vOrFfHrQPE$1)7=AJM{Q?!vLY7qDO8*_ z&H&8@d#=w9m5nb`r}uoK#Qqx3(8VnRp<*RJWZM)q|a9`PB);D>Fr0I6Lb9eK}_C4qtIq%>=zaAw|I&|uDcgW?o-&kj6(>>P6 zf_riZ%bn(4P~I3R8HP;e3;fV~(3x(qBQbdNuD&qRVYjmHmH6&$E$|c1=?s0)zI*IYlh#p2iw%IzRV9%6w5Kb!Idy7Taf#CguD`5 zpeT_mR)0e4*$aKvR2VI0066Qzz1rD+T9#7K7q+pK)C2u!l)Qz?c4a>`K=*B%tM4Y* zAz}D8OZU&Btf@u8syluktu-5mYhR!K&lL`u2W!O+4omulcWxp~FQ*x|LH^pqUGSNW zBW-s!j%v@}ERe9?h;J;T`PO0mb}Mkyl8ZItK}002cT$M$VS22_~&%vdBevCz7@{KQ_-Zl#aq@H zVL5o9F!qJN#vD^+8)H!BM>LV1E5b&Ny&-)I`+~2g=s~+g?#?Wk?`Hb?Hjb~J0>h`* zhEI#_+tGsv6UORN|NhqMp~Jt?l22VMJzbUV^sS2iPD$O!Ben&TY-G8rTcIv zP}vDUiP50Qy0(nW>bXrk@VzS@-??aKc=4LFmw#UUB!X|dlF8hRfe0SoSv_3QdRcud(-M zu6(a6)<2s-{=Iq@*TpI#<37v67TX-g85NnrcVeYkJ@C=!Ef3fCWqt5KuQQLdVWYq? zEuYQ!)lv8)(U{d+2L84H^yG*8C+`>i6))%JW}$`qiCD6bAj9>-z{ILZkgU)9) zFF$Tvx;Tr=?l!Ai?Agqk$(HP5Zqp#~ff)uY^qT4L-t97-S}4gO=8`aG%msb%)3oj7 zz4P7JcIQCT2m0W}`=|l1(e-KHtLCZyza(U5?TmlNci~Iip}6@dxNS!W*;Q0>t@vmZ znrE-~A=%jn>Td2JMf}pFCIT*2v?E77W0EjXG^BA)C{~^RKr!}QM3XX zSGb~pUwe}^3Ig}Har_eEoyZ6)>iv`LruzqV?B8DT|58u+KQW*GKYb_z@ZIgdG>)+U zOAGoxx&N;{rA9r%8zQ#7AtI81x`qb!-`=wwxn&^)$_)IscqK>{}vHN^%i8` zicZ&GL|oF}RpAk;Ep`6eCToZEvy2@0PVc2@u)<}PXWp)&$ZG08{ZU!IhyP$_N2Xo+ z_Wi1@$AQ-F-xj-)>VU_3!D&SAbXEmtE~ZGn + + + + + + + + + + + + + + + + + + + + + + admin + + + + + + + + + + + + + + + + + author + + + + + + + + + + + + + + + + + John, ID=2 + + + + + + + + + + + + + + + + + + + + Jane, ID=1 + + + + + + + + + + + + + + + + + + + + updatePost + + + + + + + + + + + + + + + + + updateOwnPost + + + + + + + + + + + + + + + + + createPost + + + + + + + + + + + + + + + + + AuthorRule + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="65px" viewBox="0 0 57 65" enable-background="new 0 0 57 65" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + <path id="body_18_" fill="#ECECEC" stroke="#9B9B9B" stroke-miterlimit="10" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-11.244-6.146-11.244-6.146 + c-1.771,1.655-5.61,3.802-10.063,3.802c-4.453,0-8.292-2.146-10.063-3.802c0,0-5.755,0.586-11.189,6.021 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> + + <radialGradient id="SVGID_2_" cx="22.6621" cy="21.707" r="17.7954" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_2_)" stroke="#E55E03" d="M28.106,33.486c-8.112,0-12.688,4.313-12.688,10.438 + c0,7.422,12.688,10.438,12.688,10.438s14.688-3.016,14.688-10.438C42.793,38.75,36.215,33.486,28.106,33.486z M26.288,53.051 + c0,0-7.135-2.093-8.805-7.201c-0.222-0.682,0.147-1.156,0.795-1.521V37.8h20.188v6.663c0.235,0.352,1.109,0.737,1.229,1.387 + C40.445,49.917,26.288,53.051,26.288,53.051z"/> + + <radialGradient id="SVGID_3_" cx="15.2056" cy="831.1875" r="32.3071" gradientTransform="matrix(1 0 0 1 0.0801 -773.6914)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_3_)" stroke="#E55E03" d="M49.529,51.225c-2.239-2.24-5.041-3.724-7.396-4.67 + c-2.854,5.51-14.021,7.807-14.021,7.807s-10.472-2.483-12.387-8.514c-2.439,0.771-5.787,2.287-8.749,5.25 + c-5.592,5.592-6.47,11.67-6.47,11.67c0,1.938,1.575,3.492,3.523,3.492h48.51c1.946,0,3.521-1.558,3.521-3.492 + C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="SVGID_4_" cx="17.0723" cy="18.4907" r="11.8931" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_4_)" stroke="#E55E03" d="M13.404,44.173c1.15-1.81,2.039-3.832,3.332-5.397 + c-0.514,1.027-1.669,4.084-1.669,5.148c0,5.186,10.366,9.079,14.688,10.438c-3.472,1.627-9.134-1.498-11.334-2.359 + c-3.601-1.419-4.071-3.063-5.89-4.854C12.523,47.135,12.878,45,13.404,44.173z"/> + + <radialGradient id="SVGID_5_" cx="31.8184" cy="19.3525" r="14.63" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_5_)" stroke="#E55E03" d="M45.777,43.924c-1.317-1.568-5.11-9.424-6.604-6.617 + c0.516,1.025,3.617,3.693,3.617,6.617c0,5.186-10.271,8.576-16.699,9.145c1.429,4.938,11.373,1.293,13.805-0.313 + c3.563-2.354,4.563-5.133,7.854-3.705C47.754,49.045,48.006,46.574,45.777,43.924z"/> + + <radialGradient id="SVGID_6_" cx="30.4893" cy="4.8721" r="5.2028" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_6_)" stroke="#E55E03" d="M30.777,54.167c0.357,0.836-0.153,1.983-0.352,2.813 + c-0.256,1.084-0.072,2.104,0.102,3.186c0.164,1.02,0.156,2.107,0.25,3.167c0.082,0.916,0.482,1.849,0.357,2.75"/> + + <radialGradient id="SVGID_7_" cx="23.2871" cy="5.3008" r="5.5143" gradientTransform="matrix(1 0 0 -1 0.04 64.1543)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FCB57A"/> + <stop offset="1" style="stop-color:#FF8C36"/> + </radialGradient> + <path fill="url(#SVGID_7_)" stroke="#E55E03" d="M23.695,53.417c-0.508,0.584-0.476,2.209-0.398,3 + c0.116,1.183,0.456,2.099,0.333,3.333c-0.192,1.943,0.154,4.479-0.436,6.333"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.2-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.501" y1="-12291.5195" x2="6492.1304" y2="-12384.9688" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3351.7349)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Young_Black_1_" fill="#5C5C5C" stroke="#353535" stroke-linecap="round" stroke-linejoin="round" d="M20.278,13.25 + c3.417,4.333,9.333,6.917,9.333,6.917l-1.417-3.5c0,0,7.094,4.691,8.083,4.333c0.968-0.2-1.082-3.807-1.082-3.807 + s3.138,1.795,4.854,3.969c1.803,2.28,4.285,3.504,4.285,3.504S47.027,2.719,27.289,2.744C8.278,2.709,12.058,27.678,12.058,27.678 + L14.695,17c0,0,0.914,5.757,1.399,4.875C17.861,15.211,18.861,11.5,20.278,13.25z"/> +</g> +</svg> + + <?xml version="1.0" encoding="utf-8"?> +<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + width="57px" height="67px" viewBox="0 0 57 67" enable-background="new 0 0 57 67" xml:space="preserve"> +<g> + + <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="26.3398" y1="3115.7266" x2="27.5807" y2="3145.5239" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)"> + <stop offset="0.2711" style="stop-color:#FFAB4F"/> + <stop offset="1" style="stop-color:#FFD28F"/> + </linearGradient> + <path fill="url(#SVGID_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M49.529,51.225c-4.396-4.396-10.951-5.884-12.063-6.109 + V37.8H19.278c0,0,0.038,6.903,0,6.868c0,0-6.874,0.997-12.308,6.432C1.378,56.691,0.5,62.77,0.5,62.77 + c0,1.938,1.575,3.492,3.523,3.492h48.51c1.947,0,3.521-1.558,3.521-3.492C56.055,62.768,54.211,55.906,49.529,51.225z"/> + + <radialGradient id="face_x5F_white_1_" cx="27.5835" cy="3117.4922" r="23.425" fx="23.0139" fy="3115.0024" gradientTransform="matrix(1 0 0 1 0.3203 -3091.7656)" gradientUnits="userSpaceOnUse"> + <stop offset="0" style="stop-color:#FFD28F"/> + <stop offset="1" style="stop-color:#FFAB4F"/> + </radialGradient> + <path id="face_x5F_white_3_" fill="url(#face_x5F_white_1_)" stroke="#ED9135" stroke-miterlimit="10" d="M43.676,23.357 + c0.086,10.199-6.738,18.52-15.25,18.586c-8.5,0.068-15.464-8.146-15.55-18.344C12.794,13.4,19.618,5.079,28.123,5.012 + C36.627,4.945,43.59,13.158,43.676,23.357z"/> + + <linearGradient id="face_highlight_1_" gradientUnits="userSpaceOnUse" x1="6468.5" y1="-12286.8594" x2="6492.1294" y2="-12380.3086" gradientTransform="matrix(0.275 0 0 -0.2733 -1752.8849 -3350.4617)"> + <stop offset="0" style="stop-color:#FFFFFF;stop-opacity:0.24"/> + <stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0.16"/> + </linearGradient> + <path id="face_highlight_3_" fill="url(#face_highlight_1_)" d="M28.415,5.625c-6.035,0.047-10.747,4.493-12.787,10.386 + c-0.664,1.919-0.294,4.043,0.98,5.629c2.73,3.398,5.729,6.283,9.461,8.088c3.137,1.518,7.535,2.385,11.893,1.247 + c2.274-0.592,3.988-2.459,4.375-4.766c0.187-1.094,0.293-2.289,0.283-3.553C42.54,13.244,36.729,5.56,28.415,5.625z"/> + <path id="Hair_Female_1_Red_1_" fill="#FAE1AA" stroke="#E2B354" stroke-linecap="round" stroke-linejoin="round" d="M28.372,0.5 + C17.537,0.5,8.269,7.748,9.153,26.125c0.563,6.563,5.862,12.042,9.366,13.531c-2.929-10.968-0.304-25.021-0.585-25.526 + c-0.281-0.505,3.536,6.728,3.536,6.728l3.183-8.312c5.541,4.28,0.393,11.309,1.049,11.058c4.26-1.631,5.34-9.228,5.34-9.228 + s2.729,3.657,2.701,5.504c-0.054,3.562,2.194-6.067,2.194-6.067l1.027,2.031c6.727,9.822,3.684,16.208,1.648,22.781 + c15.666-0.703,12.291-10.48,9.66-18.407C43.59,6.092,39.206,0.5,28.372,0.5z"/> + + <linearGradient id="body_1_" gradientUnits="userSpaceOnUse" x1="95.9063" y1="-3134.2153" x2="31.5133" y2="-3134.2153" gradientTransform="matrix(0.9852 0 0 -0.9852 -34.4844 -3031.9851)"> + <stop offset="0" style="stop-color:#49AD33"/> + <stop offset="1" style="stop-color:#C2DA92"/> + </linearGradient> + <path id="body_8_" fill="url(#body_1_)" stroke="#008D33" d="M0.5,62.768c0,1.938,1.575,3.494,3.523,3.494h48.51 + c1.947,0,3.521-1.559,3.521-3.494c0,0-1.844-6.861-6.525-11.543c-4.815-4.813-8.244-5.146-8.244-5.146 + c-1.444,6.983-8.555,8.786-13.007,8.786s-11.322-2.643-11.941-9.439c0,0-4.559,1.199-9.367,5.674 + C1.378,56.689,0.5,62.768,0.5,62.768z"/> +</g> +</svg> + + + + diff --git a/docs/guide-es/images/rbac-hierarchy-2.png b/docs/guide-es/images/rbac-hierarchy-2.png new file mode 100644 index 0000000000000000000000000000000000000000..e77c5647c1d40c4e739962cd81a7b1f78a89a484 GIT binary patch literal 18786 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 E0KMRz#Q*>R literal 0 HcmV?d00001 diff --git a/docs/guide-es/images/tutorial-console-help.png b/docs/guide-es/images/tutorial-console-help.png new file mode 100644 index 0000000000000000000000000000000000000000..15b8b66a03917fa52f4011ea97f52435ffadf9c8 GIT binary patch literal 141692 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 literal 0 HcmV?d00001 diff --git a/docs/guide-es/rest-authentication.md b/docs/guide-es/rest-authentication.md index 3a39c68..acb8a60 100644 --- a/docs/guide-es/rest-authentication.md +++ b/docs/guide-es/rest-authentication.md @@ -1,22 +1,25 @@ Autenticación ============= -A diferencia de las aplicaciones Web, las API RESTful son usualmente sin estado (stateless), lo que permite que las sesiones o las cookies no sean usadas. -Por lo tanto, cada petición debe llevar alguna suerte de credenciales de autenticación, porque la autenticación del usuario no puede ser mantenida por las sesiones o las cookies. -Una práctica común es enviar una pieza (token) secreta de acceso con cada petición para autenticar al usuario. Dado que una pieza de autenticación -puede ser usada para identificar y autenticar solamente a un usuario, **el API de peticiones tiene que ser siempre enviado vía HTTPS para prevenir ataques que intervengan en la transmisión "man-in-the-middle" (MitM) **. +A diferencia de las aplicaciones Web, las API RESTful son usualmente sin estado (stateless), lo que permite que las sesiones o las cookies +no sean usadas. Por lo tanto, cada petición debe llevar alguna suerte de credenciales de autenticación, +porque la autenticación del usuario no puede ser mantenida por las sesiones o las cookies. Una práctica común +es enviar una pieza (token) secreta de acceso con cada petición para autenticar al usuario. Dado que una pieza de autenticación +puede ser usada para identificar y autenticar solamente a un usuario, **la API de peticiones tiene que ser siempre enviado +vía HTTPS para prevenir ataques tipo "man-in-the-middle" (MitM) **. Hay muchas maneras de enviar una token (pieza) de acceso: -* [Autorización Básica HTTP](http://en.wikipedia.org/wiki/Basic_access_authentication): la pieza de acceso +* [Autenticación Básica HTTP](https://es.wikipedia.org/wiki/Autenticaci%C3%B3n_de_acceso_b%C3%A1sica): la pieza de acceso es enviada como nombre de usuario. Esto sólo debe de ser usado cuando la pieza de acceso puede ser guardada de forma segura en la parte del API del consumidor. Por ejemplo, el API del consumidor es un programa ejecutándose en un servidor. * Parámetro de la consulta: la pieza de acceso es enviada como un parámetro de la consulta en la URL de la API, p.e., `https://example.com/users?access-token=xxxxxxxx`. Debido que muchos servidores dejan los parámetros de consulta en los logs del servidor, - esta aproximación suele ser usada principalmente para servir peticiones `JSONP` + esta aproximación suele ser usada principalmente para servir peticiones `JSONP` que no usen las cabeceras HTTP para enviar piezas de acceso. * [OAuth 2](http://oauth.net/2/): la pieza de acceso es obtenida por el consumidor por medio de una autorización del servidor - y enviada al API del servidor según el protocolo OAuth 2 [tokens HTTP del portador] (http://tools.ietf.org/html/rfc6750). + y enviada al API del servidor según el protocolo + OAuth 2 [tokens HTTP del portador](http://tools.ietf.org/html/rfc6750). Yii soporta todos los métodos anteriores de autenticación. Puedes crear nuevos métodos de autenticación de una forma fácil. @@ -34,17 +37,18 @@ Cuando [[yii\web\User::enableSession|enableSession]] es false, el estado de aute Si embargo, la autenticación será realizada para cada petición, lo que se consigue en los pasos 2 y 3. > Tip:Puedes configurar [[yii\web\User::enableSession|enableSession]] del componente de la aplicación `user` en la configuración - de las aplicaciones si estás desarrollando APIs RESTful en términos de un aplicación. Si desarrollas un módulo de las APIs RESTful, - puedes poner la siguiente línea en el método del módulo `init()`, tal y como sigue: +> de las aplicaciones si estás desarrollando APIs RESTful en términos de un aplicación. Si desarrollas un módulo de las APIs RESTful, +> puedes poner la siguiente línea en el método del módulo `init()`, tal y como sigue: +> > ```php -public function init() -{ - parent::init(); - \Yii::$app->user->enableSession = false; -} -``` +> public function init() +> { +> parent::init(); +> \Yii::$app->user->enableSession = false; +> } +> ``` -Por ejemplo, para usar HTTP Basic Auth, puedes configurar el comportamiento `authenticator` como sigue, +Por ejemplo, para usar HTTP Basic Auth, puedes configurar el comportamiento (behavior) `authenticator` como sigue, ```php use yii\filters\auth\HttpBasicAuth; @@ -108,14 +112,16 @@ puede intentar autenticar al usuario en su evento `beforeAction()`. Si la autenticación tiene éxito, el controlador realizará otras comprobaciones (como son límite del ratio, autorización) y entonces ejecutar la acción. La identidad del usuario autenticado puede ser recuperada via `Yii::$app->user->identity`. -Si la autenticación falla, una respuesta con estado HTTP 401 será devuelta junto con otras cabeceras apropiadas (tal como la cabecera para autenticación básica HTTP `WWW-Authenticate`). +Si la autenticación falla, una respuesta con estado HTTP 401 será devuelta junto con otras cabeceras apropiadas +(tal como la cabecera para autenticación básica HTTP `WWW-Authenticate`). ## Autorización Después de que un usuario se ha autenticado, probablementer querrás comprobar si él o ella tiene los permisos para realizar -la acción solicitada. Este proceso es llamado *autorización (authorization)* y está cubierto en detalle en la [Sección de Autorización](security-authorization.md). +la acción solicitada. Este proceso es llamado *autorización (authorization)* y está cubierto en detalle +en la [Sección de Autorización](security-authorization.md). Si tus controladores extienden de [[yii\rest\ActiveController]], puedes sobreescribir -el método [[yii\rest\Controller::checkAccess()|checkAccess()]] para realizar la comprobación de la autorización. +el método [[yii\rest\ActiveController::checkAccess()|checkAccess()]] para realizar la comprobación de la autorización. El método será llamado por las acciones contenidas en [[yii\rest\ActiveController]]. diff --git a/docs/guide-es/security-authorization.md b/docs/guide-es/security-authorization.md index 4750003..00ed610 100644 --- a/docs/guide-es/security-authorization.md +++ b/docs/guide-es/security-authorization.md @@ -1,19 +1,19 @@ Autorización -============= +============ -La autorización es el proceso para verificar que un usuario tiene permitido realizar algo. Yii provee dos metodos de autorización: - Filtro de Control de Acceso (Access Control Filter-ACF) y Control de Acceso Basado en Roles (Role-Based Access Control-RBAC). +Autorización esl el proceso de verificación de que un usuario tenga sugifientes permisos para realizar algo. Yii provee +dos métodos de autorización: Filtro de Control de Acceso y Control Basado en Roles (ACF y RBAC por sus siglas en inglés). -## Filtro de Control de Acceso (ACF) +## Filtro de Control de Acceso -El Filtro de Control de Acceso (ACF) es un metodo simple de autorizacion implementado como [[yii\filters\AccessControl]] el cual -es la mejor opcion para ser usado en aplicaciones que requieren un control de acceso sencillo. Como su nombre lo indica, ACF es una accion -de [filter](structure-filters.md) que puede ser usada por un controlador o un módulo. Mientras un usuario este solicitando -la ejecucion de una accion, ACF se encargara de verificar en la lista de [[yii\filters\AccessControl::rules|access rules]] -para determinar si el usuario esta autorizado para acceder a la accion solicitada. +Filtro de Control de Acceso (ACF) es un único método de autorización implementado como [[yii\filters\AccessControl]], el cual +es mejor utilizado por aplicaciones que sólo requieran un control de acceso simple. Como su nombre lo indica, ACF es +un [filtro](structure-filters.md) de acción que puede ser utilizado en un controlador o en un módulo. Cuando un usuario solicita +la ejecución de una acción, ACF comprobará una lista de [[yii\filters\AccessControl::rules|reglas de acceso]] +para determinar si el usuario tiene permitido acceder a dicha acción. -El codigo siguiente muestra como utilizar ACF en el controlador `site` : +El siguiente código muestra cómo utilizar ACF en el controlador `site`: ```php use yii\web\Controller; @@ -46,73 +46,76 @@ class SiteController extends Controller } ``` -En el codigo anterior ACF esta unido al controlador `site` como un behavior. Esta es la forma tipica de la utilizacion del filtro en una accion. -La opción `only` especifica como ACF debe aplicarse a las acciones `login`, `logout` y `signup`. -Todas las otras acciones en el controlador `site` no son asunto del control de acceso. La lista de opciones de `rules` -del [[yii\filters\AccessRule|access rules]], dice lo siguiente: +En el código anterior, ACF es adjuntado al controlador `site` en forma de behavior (comportamiento). Esta es la forma típica de utilizar +un filtro de acción. La opción `only` especifica que el ACF debe ser aplicado solamente a las acciones `login`, `logout` y `signup`. +Las acciones restantes en el controlador `site` no están sujetas al control de acceso. La opción `rules` lista +las [[yii\filters\AccessRule|reglas de acceso]], y se lee como a continuación: -- Permitir a todos los Invitados (los usuarios que no estan autenticados) acceder a las acciones `login` y `signup`. La opcion `roles` - contiene un signo de interrogacion `?` el cual es una especia de token que representa a los usuarios que sean "usuarios invitados". -- Todos los usuarios autenticados pueden acceder a la accion `logout`. El caracter `@` es otro token especial que representa a todos los "usuarios autenticados". +- Permite a todos los usuarios invitados (sin autenticar) acceder a las acciones `login` y `signup`. La opción `roles` + contiene el signo de interrogación `?`, que es un código especial para representar a los "invitados". +- Permite a los usuarios autenticados acceder a la acción `logout`. El signo `@` es otro código especial que representa + a los "usuarios autenticados". -ACF lleva a cabo la verificación de autorización mediante el examen de las reglas de acceso de uno en uno de arriba a abajo hasta que encuentra -una regla que se relaciones con el contexto de ejecucion. El valor `allow` de la regla de coincidencia luego serán utilizados para -juzgar si el usuario esta autorizado o no. Si ninguna de las reglas se relaciona, significa que el usuario no esta autorizado, -y ACF detendra la ejecucion de la accion.. +ACF ejecuta la comprobación de autorización examinando las reglas de acceso una a una desde arriba hacia abajo hasta que encuentra +una regla que aplique al contexto de ejecución actual. El valor `allow` de la regla que coincida será entonces utilizado +para juzgar si el usuario está autorizado o no. Si ninguna de las reglas coincide, significa que el usuario NO está autorizado, +y el ACF detendrá la ejecución de la acción. -Cuando ACF determina que un usuario no esta autorizado para acceder a una accion, Toma alguna de las siguientes medidas por default: +Cuando el ACF determina que un usuario no está autorizado a acceder a la acción actual, toma las siguientes medidas por defecto: -* Si el usuario es un invitado, llamara a [[yii\web\User::loginRequired()]] y redirigir al usuario mediante el navegador a la pagina de login. -* Si el usuario esta autenticado, arrojara una [[yii\web\ForbiddenHttpException]]. +* Si el usuario es un invitado, llamará a [[yii\web\User::loginRequired()]] para redireccionar el navegador a la pantalla de login. +* Si el usuario está autenticado, lanzará una excepeción [[yii\web\ForbiddenHttpException]]. -Puedes personalizar este comportamiento (behavior) mediante la configuracion de la propiedad [[yii\filters\AccessControl::denyCallback]], por ejemplo: +Puedes personalizar este comportamiento configurando la propiedad [[yii\filters\AccessControl::denyCallback]] como a continuación: ```php [ 'class' => AccessControl::className(), ... 'denyCallback' => function ($rule, $action) { - throw new \Exception('You are not allowed to access this page'); + throw new \Exception('No tienes los suficientes permisos para acceder a esta página'); } ] ``` -[[yii\filters\AccessRule|Access rules]] somporta algunas opciones. A continuacion se muestra un resumen de las opciones soportadas. -Tambien puedes extender [[yii\filters\AccessRule]] para crear tus propias clases de reglas personalizadas. +Las [[yii\filters\AccessRule|Reglas de Acceso]] soportan varias opciones. Abajo hay un resumen de las mismas. +También puedes extender de [[yii\filters\AccessRule]] para crear tus propias clases de reglas de acceso personalizadas. - * [[yii\filters\AccessRule::allow|allow]]: Especifica si se trata de una regla para "permitir" ("allow") o de una regla "denegar" ("deny") . + * [[yii\filters\AccessRule::allow|allow]]: especifica si la regla es de tipo "allow" (permitir) o "deny" (denegar). - * [[yii\filters\AccessRule::actions|actions]]: especifica cual de las acciones de esta regla se relaciona. Este deberia ser una matriz de IDs de accion. - La comparacion entre mayúsculas y minúsculas. Si esta opcion esta vacia (empty) o no esta asignada, - significa que la regla se aplica a todas las acciones. + * [[yii\filters\AccessRule::actions|actions]]: especifica con qué acciones coinciden con esta regla. Esta debería ser +un array de IDs de acciones. La comparación es sensible a mayúsculas. Si la opción está vacía o no definida, +significa que la regla se aplica a todas las acciones. - * [[yii\filters\AccessRule::controllers|controllers]]: especifica a cual de los controladores se relaciona esta regla. - Esta deberia ser un arreglo de IDs de controladores. Cada ID de controlador es prefijado con el modulo ID (si cualquier). -La comparacion se hace entre mayúsculas y minúsculas.Si esta opcion esta vacia o no esta asignada, significa que la regla aplica a todo el controlador. + * [[yii\filters\AccessRule::controllers|controllers]]: especifica con qué controladores coincide +esta regla. Esta debería ser un array de IDs de controladores. Cada ID de controlador es prefijado con el ID del módulo (si existe). +La comparación es sensible a mayúsculas. Si la opción está vacía o no definida, significa que la regla se aplica a todos los controladores. - * [[yii\filters\AccessRule::roles|roles]]: especifica a que rol de los usuario esta regla se relaciona. - Dos roles especiales son reconocidos, y ellos son verificados por medio de [[yii\web\User::isGuest]] + * [[yii\filters\AccessRule::roles|roles]]: especifica con qué roles de usuarios coincide esta regla. + Son reconocidos dos roles especiales, y son comprobados vía [[yii\web\User::isGuest]]: - - `?`: Esta relacionado con usuarios invitados (no autenticados). - - `@`: Esta relacionado a usuarios que estan autenticados. + - `?`: coincide con el usuario invitado (sin autenticar) + - `@`: coincide con el usuario autenticado - Usando otros nombres de rol este disparara la invocacion de [[yii\web\User::can()]], el cual requiere que este habilitado RBAC - (Sera descrito en la siguiente subsección). Si esta opcion esta vacia o no esta asignada, esta regla aplica para todos los roles. + El utilizar otro nombre de rol invocará una llamada a [[yii\web\User::can()]], que requiere habilitar RBAC + (a ser descrito en la próxima subsección). Si la opción está vacía o no definida, significa que la regla se aplica a todos los roles. - * [[yii\filters\AccessRule::ips|ips]]: esta especifica a cual [[yii\web\Request::userIP|client IP addresses]] esta regla se relaciona. -Una direccion IP puede contener el comodin `*` al final para que coincida con las direcciones IP con el mismo prefijo. -Por ejemplo, '192.168.*' se relaciona con todas las direcciones IP con el segmento '192.168.'. Si esta opcion esta vacia o no se asigna, -aplicara para todas las direcciones IP. + * [[yii\filters\AccessRule::ips|ips]]: especifica con qué [[yii\web\Request::userIP|dirección IP del cliente]] coincide esta regla. +Una dirección IP puede contener el caracter especial `*` al final de manera que coincidan todas las IPs que comiencen igual. +Por ejemplo, '192.168.*' coincide con las direcciones IP en el segmento '192.168.'. Si la opción está vacía o no definida, +significa que la regla se aplica a todas las direcciones IP. - * [[yii\filters\AccessRule::verbs|verbs]]: especifica el metodo de peticion por el cual la regla se relaciona (por ejemplo, `GET`, `POST`). -La comparacion se hace entre mayúsculas y minúsculas. + * [[yii\filters\AccessRule::verbs|verbs]]: especifica con qué método de la solicitud (por ej. `GET`, `POST`) coincide esta regla. +La comparación no distingue minúsculas de mayúsculas. - * [[yii\filters\AccessRule::matchCallback|matchCallback]]: especifica una llamada a PHP para determinar si esta regla debe aplicar. + * [[yii\filters\AccessRule::matchCallback|matchCallback]]: especifica una función PHP invocable que debe ser llamada para determinar +si la regla debe ser aplicada. - * [[yii\filters\AccessRule::denyCallback|denyCallback]]: especifica una llamada a PHP para determinar cuando esta regla debe negar el acceso. + * [[yii\filters\AccessRule::denyCallback|denyCallback]]: especifica una función PHP invocable que debe ser llamada cuando esta regla +deniegue el acceso. -A continuación se muestra un ejemplo de como hacer uso de la opcion `matchCallback`, el cual permite escribir el acceso arbitrariamente. -comprobar la lógica: +Debajo hay un ejemplo que muestra cómo utilizar la opción `matchCallback`, que te permite escribir lógica de comprabación de acceso +arbitraria: ```php use yii\filters\AccessControl; @@ -138,7 +141,7 @@ class SiteController extends Controller ]; } - // ¡match-callback es llamado! A esta pagina solo se puede acceder cada 31 de octubre + // Callback coincidente llamado! Esta página sólo puede ser accedida cada 31 de Octubre public function actionSpecialCallback() { return $this->render('happy-halloween'); @@ -147,49 +150,49 @@ class SiteController extends Controller ``` -## Control de Acceso Basado en Roles (Role Based Access Control-RBAC) +## Control de Acceso Basado en Roles (RBAC) -El Control de Acceso Basado en Roles (RBAC) provee un simple pero poderoso control de acceso centralizado. Por favor dirigete a - [Wikipedia](http://en.wikipedia.org/wiki/Role-based_access_control) para mas detalles acerca de la comparacion de RBAC -con otros mas esquemas de control de acceso. +El Control de Acceso Basado en Roles (RBAC) provee una simple pero poderosa manera centralizada de control de acceso. Por favos consulta +la [Wikipedia](http://en.wikipedia.org/wiki/Role-based_access_control) para más detalles sobre comparar RBAC +con otros mecanismos de control de acceso más tradicionales. -Yii implementa un RBAC General y Jerarquico, siguiendo el [NIST RBAC model](http://csrc.nist.gov/rbac/sandhu-ferraiolo-kuhn-00.pdf). -Se provee la funcionalidad del RBAC a través de [[yii\rbac\ManagerInterface|authManager]] [application component](structure-application-components.md). +Yii implementa una Jerarquía General RBAC, siguiendo el [modelo NIST RBAC](http://csrc.nist.gov/rbac/sandhu-ferraiolo-kuhn-00.pdf). +Esto provee la funcionalidad RBAC a través de [componente de la aplicación](structure-application-components.md) [[yii\rbac\ManagerInterface|authManager]]. -El uso de RBAC implica dos partes del trabajo. La primera parte es la construcción de los datos de la autorización de RBAC, y la segunda - parte es la de usar esos datos de autorizacion realizar la comprobación de acceso en lugares donde se necesita. +Utilizar RBAC envuelve dos cosas. La primera es construir los datos de autorización RBAC, y la segunda +es utilizar esos datos de autorización para comprobar el acceso en los lugares donde se necesite. -Para facilitar nuestra descripcion proxima, nosotros primero deberemos introducirnos en los conceptos basicos de RBAC +Para facilitar la próxima descripción, necesitamos primero instroducir algunos conceptos RBAC básicos. -### Conceptos Basicos +### Conceptos Básicos -Un rol representa una coleccion de *permisos* (e.j. creando posts, actualizando posts). Un rol puede ser asignado -a uno o multiples usuarios. Para comprobar si un usuario tiene un permiso específico, podemos comprobar si se ha asignado al usuario -con un rol que contiene este permiso. +Un rol representa una colección de *permisos* (por ej. crear posts, actualizar posts). Un rol puede ser asignado +a uno o varios usuarios. Para comprobar que un usuario cuenta con determinado permiso, podemos comprobar si el usuario tiene asignado +un rol que cuente con dicho permiso. -Asociado con cada función o permoso, puede haber una *regla*. Una regla representa una pieza de codigo que debera ser -ejecutada durante el chequeo de acceso para determinar si el rol o permiso aplica para el usuario que intenta acceder. -Por ejemplo, el permiso "actualizar post" deberia tener una regla que cheque si el usuario que esta accediendo es el creador del post. -Durante el chequeo de acceso, si el usuario NO es el creador del post, el/ella se considero que no tienen el permiso "actualizar post". +Asociado a cada rol o permiso, puede puede haber una *regla*. Una regla representa una porción de código que será +ejecutada durante la comprobación de acceso para determinar si el rol o permiso correspondiente aplica al usuario actual. +Por ejemplo, el permiso "actualizar post" puede tener una regla que compruebe que el usuario actual es el autor del post. +Durante la comprobación de acceso, si el usuario NO es el autor del post, se considerará que el/ella no cuenta con el permiso "actualizar post". -Ambos, roles y permisos pueden ser organizados en una jerarquia. En particular, un un rol puede contener a otros roles y permisos; - y un permiso puede contener a otros permisos. Yii implementa un *orden parcial* jerarquico el cual incluye el -jerarquia mas especial en *arbol*. Un rol puede contener a uno o varios permisos,mas no viceversa. +Tanto los roles como los permisos pueden ser organizados en una jerarquía. En particular, un rol puede consistir en otros roles o permisos; +y un permiso puede consistir en otros permisos. Yii implementa una jerarquía de *orden parcial*, que incluye +una jerarquía de *árbol* especial. Mientras que un rol puede contener un permiso, esto no sucede al revés. -### Configurando RBAC +### Configurar RBAC -Antes de partir para definir los datos de autorización y realizar la comprobación de acceso, tenemos que configurar el -componente [[yii\base\Application::authManager|authManager]] de la aplicacion. Yii provee dos tipos de manejadores de autorizacion: -[[yii\rbac\PhpManager]] y [[yii\rbac\DbManager]]. El primero utiliza un archivo de script de PHP para almacenar los datos de autorizacion, -mientras que el segundo almacena los datos de autorizacion en la base de datos. Puedes considerar el uso de la primera si su aplicación -no requiere papel muy dinámico y gestión de permisos. +Antes de definir todos los datos de autorización y ejecutar la comprobación de acceso, necesitamos configurar el +componente de la aplicación [[yii\base\Application::authManager|authManager]]. Yii provee dos tipos de administradores de autorización: +[[yii\rbac\PhpManager]] y [[yii\rbac\DbManager]]. El primero utiliza un archivo PHP para almacenar los datos +de autorización, mientras que el segundo almacena dichos datos en una base de datos. Puedes considerar utilizar el primero si tu aplicación +no requiere una administración de permisos y roles muy dinámica. -#### Usando `PhpManager` +#### Utilizar `PhpManager` -El codigo siguiente muestra cómo configurar el `authManager` en la configuracion de la aplicacion usando la clase [[yii\rbac\PhpManager]]: +El siguiente código muestra cómo configurar `authManager` en la configuración de nuestra aplicación utilizando la clase [[yii\rbac\PhpManager]]: ```php return [ @@ -203,15 +206,15 @@ return [ ]; ``` -El `authManager` (manejador de autenticación) puede accesarse ahora via `\Yii::$app->authManager`. +El `authManager` ahora puede ser accedido vía `\Yii::$app->authManager`. -Por defecto, [[yii\rbac\PhpManager]] almacena los datos de RBAC bajo el directorio `@app/rbac`. Asegurese que el directorio -y todos los archivos en el tienen los permisos suficientes para ser escritor por el proceso del servidor Web y otorgueselos de ser necesarios online. +Por defecto, [[yii\rbac\PhpManager]] almacena datos RBAC en archivos bajo el directorio `@app/rbac`. Asegúrate de que el directorio +y todos sus archivos son tienen permiso de escritura para el proceso del servidor Web si la jerarquía de permisos necesita ser modoficada en línea. -#### Usando `DbManager` +#### Utilizar `DbManager` -El codigo siguiente muestra como configurar `authManager` in la configuracion de la aplicacion para la clase [yii\rbac\DbManager]]: +El sigiente código muestra cómo configurar `authManager` en la configuración de la aplicación utilizando la clase [[yii\rbac\DbManager]]: ```php return [ @@ -224,38 +227,38 @@ return [ ], ]; ``` -> Nota: Si estas usando yii2-basic-app (aplicacion basica de yii2), deberas configurar el archivo que esta en `config/console.php` para declarar - `authManager` y necesitas adicionalmente declararlo en `config/web.php`. -> En el caso de yii2-advanced-app el `authManager` debera ser declarado bajo el directorio `common/config/main.php`. +> Note: si estás utilizando el template yii2-basic-app, existe el archivo de configuración `config/console.php` donde + necesita declararse `authManager` adicionalmente a `config/web.php`. +> En el caso de yii2-advanced-app, `authManager` sólo debe declararse en `common/config/main.php`. -`DbManager` usa cuatro tablas en la base de datos donde almacena los datos de autorización: +`DbManager` utiliza cuatro tablas de la BD para almacenar los datos: -- [[yii\rbac\DbManager::$itemTable|itemTable]]: La tabla para almacenar los items de autorizacion. Por default "auth_item". -- [[yii\rbac\DbManager::$itemChildTable|itemChildTable]]: La tabla donde se almacenan los items de autorizacion por jerarquia. Por defecto es "auth_item_child". -- [[yii\rbac\DbManager::$assignmentTable|assignmentTable]]: La tabla donde se almacenan las asignaciones a los items de autorizacion. Por default "auth_assignment". -- [[yii\rbac\DbManager::$ruleTable|ruleTable]]: La tabla donde se almacenan las reglas. Por defecto "auth_rule". +- [[yii\rbac\DbManager::$itemTable|itemTable]]: la tabla para almacenar los ítems de autorización. Por defecto "auth_item". +- [[yii\rbac\DbManager::$itemChildTable|itemChildTable]]: la tabla para almacentar la jerarquía de los ítems de autorización. Por defecto "auth_item_child". +- [[yii\rbac\DbManager::$assignmentTable|assignmentTable]]: la tabla para almacenar las asignaciones de los ítems de autorización. Por defecto "auth_assignment". +- [[yii\rbac\DbManager::$ruleTable|ruleTable]]: la tabla para almacenar las reglas. Por defecto "auth_rule". -Antes de que puedas implementar necesitas crear las tablas respectivas en la base de datos. Para hacerlo, tu puedes usar las migraciones contenidas en `@yii/rbac/migrations`: +Antes de continuar, necesitas crear las tablas respectivas en la base de datos. Para hacerlo, puedes utilizar las migraciones contenidas en `@yii/rbac/migrations`: `yii migrate --migrationPath=@yii/rbac/migrations` -El `authManager` puede ahora ser accedido via `\Yii::$app->authManager`. +El `authManager` puede ahora ser accedido vía `\Yii::$app->authManager`. -### Construyendo los Datos de Autorización +### Construir los Datos de Autorización -Construyendo los datos de autorización que tienen que ver con las siguientes tareas: +Construir los datos de autorización implica las siguientes tareas: -- definiendo roles y permisos; -- estableciendo relaciones entre roles y permisos; -- definiendo reglas; -- asociando reglas con roles y permisos; -- asignando roles a usuarios. +- definir roles y permisos; +- establecer relaciones entre roles y permisos; +- definir reglas; +- asociar reglas con roles y permisos; +- asignar roles a usuarios. -Dependiendo de la flexibilidad en la autorizacion que se requiera las tareas se pueden cumplir por caminos diferentes +Dependiendo de los requerimientos de flexibilidad en la autorización, las tareas se pueden lograr de diferentes maneras. -Si la jerarquía de permisos no cambia en absoluto y que tiene un número fijo de usuarios puede crear un -[console command](tutorial-console.md#create-command) que va a inicializar los datos de autorización una vez a través de las API que ofrece via `authManager`: +Si la jerarquía de permisos no cambia en absoluto y tienes un número fijo de usuarios puede crear un +[comando de consola](tutorial-console.md#create-command) que va a inicializar los datos de autorización una vez a través de las API que ofrece por `authManager`: ```php authManager; - // add "createPost" permission + // agrega el permiso "createPost" $createPost = $auth->createPermission('createPost'); $createPost->description = 'Create a post'; $auth->add($createPost); - // add "updatePost" permission + // agrega el permiso "updatePost" $updatePost = $auth->createPermission('updatePost'); $updatePost->description = 'Update post'; $auth->add($updatePost); - // add "author" role and give this role the "createPost" permission + // agrega el rol "author" y le asigna el permiso "createPost" $author = $auth->createRole('author'); $auth->add($author); $auth->addChild($author, $createPost); - // add "admin" role and give this role the "updatePost" permission - // as well as the permissions of the "author" role + // agrega el rol "admin" y le asigna el permiso "updatePost" + // más los permisos del rol "author" $admin = $auth->createRole('admin'); $auth->add($admin); $auth->addChild($admin, $updatePost); $auth->addChild($admin, $author); - // Assign roles to users. 1 and 2 are IDs returned by IdentityInterface::getId() - // usually implemented in your User model. + // asigna roles a usuarios. 1 y 2 son IDs devueltos por IdentityInterface::getId() + // usualmente implementado en tu modelo User. $auth->assign($author, 2); $auth->assign($admin, 1); } } ``` -> Note: Si estas usando la plantilla avanzada, necesitas poner tu `RbacController` dentro del directorio `console/controllers` - y cambiar el namespace a `console/controllers`. +> Note: Si estas utilizando el template avanzado, necesitas poner tu `RbacController` dentro del directorio `console/controllers` + y cambiar el espacio de nombres a `console/controllers`. -Despues, ejecutando el comando `yii rbac/init` obtendremos la siguiente jerarquia: +Después de ejecutar el comando `yii rbac/init`, obtendremos la siguiente jerarquía: ![Simple RBAC hierarchy](../guide/images/rbac-hierarchy-1.png "Simple RBAC hierarchy") -Autor puede crear un post, admin puede actualizar su mensaje y hacer todo lo posible autor. +"Author" puede crear un post, "admin" puede actualizar posts y hacer todo lo que puede hacer "author". -Si tu aplicacion permite al usuario el registro necesitas asignar los roles necesarios para cada usuario nuevo registrado. Por ejemplo, en orden para todos los usuarios inscritos para convertirse en autores de si plantilla avanzada tienes que modificar `frontend\models\SignupForm::signup()` -as follows: +Si tu aplicación permite el registro de usuarios, necesitas asignar los roles necesarios para cada usuario nuevo. Por ejemplo, para que todos +los usuarios registrados tengan el rol "author", en el template de aplicación avanzada debes modificar `frontend\models\SignupForm::signup()` +como a continuación: ```php public function signup() @@ -323,7 +327,7 @@ public function signup() $user->generateAuthKey(); $user->save(false); - // the following three lines were added: + // las siguientes tres líneas fueron agregadas $auth = Yii::$app->authManager; $authorRole = $auth->getRole('author'); $auth->assign($authorRole, $user->getId()); @@ -335,15 +339,15 @@ public function signup() } ``` -Para aplicaciones que requieren un control de acceso complejo con una actualizacion constante en los datos de autorizacion , interfaces especiales de usuario -(e.j. admin panel) pueden necesitar ser desarrollado utilizando las API que ofrece `authManager`. +Para aplicaciones que requieren un control de acceso complejo con una actualización constante en los datos de autorización, puede ser necesario +desarrollar una interfaz especial (por ej. un panel de administración) utilizando las APIs ofrecidas por `authManager`. -### Usando Reglas +### Utilizar Reglas -Como se habia mencionado, las reglas agregan restricciones adicionales a los roles y permisos. Una regla es una clase extendida desde -[[yii\rbac\Rule]]. Debe implementar al metodo [[yii\rbac\Rule::execute()|execute()]]. En la jerarquia tenemos creado previamente -que autor no puede editar su propio post. Vamos a arreglarlo. Primero necesitamos una regla para comprobar que el usuario es el autor del post: +Como se había mencionado, las reglas agregan restricciones adicionales a los roles y permisos. Una regla es una clase extendida +de [[yii\rbac\Rule]]. Debe implementar al método [[yii\rbac\Rule::execute()|execute()]]. En la jerarquía que creamos +previamente, "author" no puede editar su propio post. Vamos a arreglarlo. Primero necesitamos una regla para comprobar que el usuario actual es el autor del post: ```php namespace app\rbac; @@ -351,17 +355,17 @@ namespace app\rbac; use yii\rbac\Rule; /** - * Checks if authorID matches user passed via params + * Comprueba si authorID coincide con el usuario pasado como parámetro */ class AuthorRule extends Rule { public $name = 'isAuthor'; /** - * @param string|integer $user the user ID. - * @param Item $item the role or permission that this rule is associated with - * @param array $params parameters passed to ManagerInterface::checkAccess(). - * @return boolean a value indicating whether the rule permits the role or permission it is associated with. + * @param string|integer $user el ID de usuario. + * @param Item $item el rol o permiso asociado a la regla + * @param array $params parámetros pasados a ManagerInterface::checkAccess(). + * @return boolean un valor indicando si la regla permite al rol o permiso con el que está asociado. */ public function execute($user, $item, $params) { @@ -370,55 +374,55 @@ class AuthorRule extends Rule } ``` -La regla anterior comprueba si el `post` es creado por `$user`. Debemos crear un permiso especial `updateOwnPost` codigo que hemos utilizado +La regla anterior comprueba si el `post` fue creado por `$user`. Crearemos un permiso especial, `updateOwnPost`, en el comando que hemos utilizado anteriormente: ```php $auth = Yii::$app->authManager; -// add the rule +// agrega la regla $rule = new \app\rbac\AuthorRule; $auth->add($rule); -// add the "updateOwnPost" permission and associate the rule with it. +// agrega el permiso "updateOwnPost" y le asocia la regla. $updateOwnPost = $auth->createPermission('updateOwnPost'); $updateOwnPost->description = 'Update own post'; $updateOwnPost->ruleName = $rule->name; $auth->add($updateOwnPost); -// "updateOwnPost" will be used from "updatePost" +// "updateOwnPost" será utilizado desde "updatePost" $auth->addChild($updateOwnPost, $updatePost); -// allow "author" to update their own posts +// permite a "author" editar sus propios posts $auth->addChild($author, $updateOwnPost); ``` -Now we have got the following hierarchy: +Ahora tenemos la siguiente jerarquía: ![RBAC hierarchy with a rule](../guide/images/rbac-hierarchy-2.png "RBAC hierarchy with a rule") -### Comprobación de acceso +### Comprobación de Acceso -Con los datos de autorizacion listos, la comprobacion de acceso es una simple llamada a el metodo [[yii\rbac\ManagerInterface::checkAccess()]]. -Dado que la mayoría de comprobación de acceso está sobre el usuario actual, para mayor comodidad Yii proporciona un método de acceso directo -[[yii\web\User::can()]], que puede ser utilizado como el siguiente: +Con los datos de autorización listos, la comprobación de acceso se hace con una simple llamada al método [[yii\rbac\ManagerInterface::checkAccess()]]. +Dado que la mayoría de la comprobación de acceso se hace sobre el usuario actual, para mayor comodidad Yii proporciona el atajo +[[yii\web\User::can()]], que puede ser utilizado como a continuación: ```php if (\Yii::$app->user->can('createPost')) { - // create post + // crear el post } ``` -Si el usuario actual es Jane con `ID=1` estamos empezando a `createPost` y tratando de obtener a `Jane`: +Si el usuario actual es Jane con `ID=1`, comenzamos desde `createPost` y tratamos de alcanzar a `Jane`: ![Access check](../guide/images/rbac-access-check-1.png "Access check") -Con el fin de comprobar si un usuario puede actualizar un anuncio, necesitamos pasar un parámetro adicional que es requerido por `AuthorRule` descrito antes: +Con el fin de comprobar si un usuario puede actualizar un post, necesitamos pasarle un parámetro adicional requerido por `AuthorRule`, descrito antes: ```php if (\Yii::$app->user->can('updatePost', ['post' => $post])) { - // update post + // actualizar post } ``` @@ -427,31 +431,30 @@ Aquí es lo que sucede si el usuario actual es John: ![Access check](../guide/images/rbac-access-check-2.png "Access check") -Estamos comenzando con el `updatePost` y pasando por `updateOwnPost`. Con el fin de pasar a la comprobación de acceso, `AuthorRule` -debe devolver `true` desde el metodo `execute()`. el metodo recive estos `$params` desde la llamada al metodo `can()` por lo que el valor es -`['post' => $post]`. Si todo está bien, vamos a obtener a `author` el cual es asignado a John. +Comenzamos desde `updatePost` y pasamos por `updateOwnPost`. Con el fin de pasar la comprobación de acceso, `AuthorRule` +debe devolver `true` desde su método `execute()`. El método recive `$params` desde la llamada al método `can()`, cuyo valor es +`['post' => $post]`. Si todo está bien, vamos a obtener `author`, el cual es asignado a John. -En caso de Jane que es un poco más simple ya que es un admin: +En caso de Jane es un poco más simple, ya que ella es un "admin": ![Access check](../guide/images/rbac-access-check-3.png "Access check") -### Usando los roles por defecto +### Utilizar Roles por Defecto -Un rol por defecto es un rol que esta asignado *implicitamente* a *todos* los usuarios. La llamada a [[yii\rbac\ManagerInterface::assign()]] -no es necesaria, y los datos de autorización no contiene la información de asignación. +Un rol por defecto es un rol que esta asignado *implícitamente* a *todos* los usuarios. La llamada a [[yii\rbac\ManagerInterface::assign()]] +no es necesaria, y los datos de autorización no contienen su información de asignación. -Un rol por defecto es usualmente asociado con una regla la cual determina si el rol aplica al usuario verificado. +Un rol por defecto es usualmente asociado con una regla que determina si el rol aplica al usuario siendo verificado. +Los roles por defecto se utilizan a menudo en aplicaciones que ya tienen algún tipo de asignación de roles. Por ejemplo, una aplicación +puede tener una columna "grupo" en su tabla de usuario para representar a qué grupo de privilegio pertenece cada usuario. +Si cada grupo privilegio puede ser conectado a un rol de RBAC, se puede utilizar la función de rol por defecto para asignar +cada usuario a un rol RBAC automáticamente. Usemos un ejemplo para mostrar cómo se puede hacer esto. -Los roles por defecto se utilizan a menudo en aplicaciones que ya tienen algún tipo de asignación de funciones. Por ejemplo, una aplicación -puede tener una columna "grupo" en su tabla de usuario para representar a qué grupo de privilegio cada usuario pertenece. -Si cada grupo privilegio puede ser asignada a un rol de RBAC, se puede utilizar la función de rol por default de forma automática -asignar a cada usuario a una función RBAC. Usemos un ejemplo para mostrar cómo se puede hacer esto. - -Suponga que en la tabla de usuario, usted tiene una columna `grupo` que utiliza para representar el grupo administrador y 2 del grupo autor. -Planea tener dos roles RBAC `admin` y `author` para representar los permisos de estos dos grupos, respectivamente. -Puede configurar los datos de la siguiente manera en el RBAC, +Suponga que en la tabla de usuario, usted tiene una columna `group` que utiliza 1 para representar el grupo administrador y 2 al grupo autor. +Planeas tener dos roles RBAC, `admin` y `author`, para representar los permisos de estos dos grupos, respectivamente. +Puede configurar los datos RBAC de la siguiente manera, ```php @@ -461,7 +464,7 @@ use Yii; use yii\rbac\Rule; /** - * Checks if user group matches + * Comprueba si el grupo coincide */ class UserGroupRule extends Rule { @@ -489,21 +492,21 @@ $auth->add($rule); $author = $auth->createRole('author'); $author->ruleName = $rule->name; $auth->add($author); -// ... add permissions as children of $author ... +// ... agrega permisos hijos a $author ... $admin = $auth->createRole('admin'); $admin->ruleName = $rule->name; $auth->add($admin); $auth->addChild($admin, $author); -// ... add permissions as children of $admin ... +// ... agrega permisos hijos a $admin ... ``` -Tenga en cuenta que en la anterior, porque "author" es agregado como un hijo de "admin", cuando implementes el metodo `execute()` -de la clase de la regla, por lo que necesitas jerarquizar respectivamente bien. Por eso cuando el nombre del rol es "author", -el metodo `execute()` debera regresar true si el grupo de usuario es cualquiera 1 o 2 (significa que el usuario se encuentra en -cualquiera de los dos grupos "admin" o grupo "author"). +Tenga en cuenta que en el ejemplo anterior, dado que "author" es agregado como hijo de "admin", cuando implementes el método `execute()` +de la clase de la regla, necesitas respetar esta jerarquía. Esto se debe a que cuando el nombre del rol es "author", +el método `execute()` devolverá true si el grupo de usuario es tanto 1 como 2 (lo que significa que el usuario se encuentra en +cualquiera de los dos grupos, "admin" o "author"). -Lo mas próximo, configura `authManager` enumerando los dos roles en [[yii\rbac\BaseManager::$defaultRoles]]: +Luego, configura `authManager` enumerando los dos roles en [[yii\rbac\BaseManager::$defaultRoles]]: ```php return [ @@ -518,7 +521,7 @@ return [ ]; ``` -Ahora bien, si se realiza una comprobación de acceso, tanto del rol `admin` y del rol `author` el cual debera ser evaluado bajo -las reglas asociadas con ellas. si la regla devuelve true, esto significa que la regla se aplica al usuario actual. -Basado en la aplicacion de la regla anterior, esto significa que si el valor un grupo `group` de un usuario es 1, el rol `admin` -se aplicaría al usuario; y si el valor de `group` es 2, el rol `author` se aplicaria. +Ahora si realizas una comprobación de acceso, tanto el rol `admin` y como el rol `author` serán comprobados evaluando +las reglas asociadas con ellos. Si la regla devuelve true, significa que la regla aplica al usuario actual. +Basado en la implementación de la regla anterior, esto significa que si el valor `group` en un usuario es 1, el rol `admin` +se aplicaría al usuario; y si el valor de `group` es 2, se le aplicaría el rol `author`. diff --git a/docs/guide-es/start-databases.md b/docs/guide-es/start-databases.md index 7bff7a6..85e1ac6 100644 --- a/docs/guide-es/start-databases.md +++ b/docs/guide-es/start-databases.md @@ -1,37 +1,35 @@ -Trabajando con Bases de Datos -============================= +Trabajar con Bases de Datos +=========================== En esta sección, explicaremos cómo crear una nueva página para mostrar datos de países traídos de una tabla de la base de datos llamada `country`. Para lograr este objetivo, configurarás una conexión a la base de datos, crearás una clase [Active Record](db-active-record.md), una [acción](structure-controllers.md) y una [vista](structure-views.md). -A lo largo de este tutorial, aprenderás +A lo largo de este tutorial, aprenderás a -* Cómo configurar una conexión a la base de datos; -* Cómo definir una clase Active Record; -* Cómo realizar consultas a la base de datos utilizando la clase Active Record; -* Cómo mostrar datos en una vista con paginación incluida. +* configurar una conexión a la base de datos; +* definir una clase Active Record; +* realizar consultas a la base de datos utilizando la clase Active Record; +* mostrar datos en una vista con paginación incluida. Ten en cuenta que para finalizar esta sección, deberás tener al menos conocimientos básicos y experiencia con bases de datos. -En particular, deberás ser capaz de crear una base de datos y saber ejecutar consultas SQL usando alguna herramienta de cliente de -base de datos. +En particular, deberás ser capaz de crear una base de datos y saber ejecutar consultas SQL usando alguna herramienta de cliente de base de datos. -Preparando una Base de Datos ----------------------------- +Preparar una Base de Datos +-------------------------- Para empezar, crea una base de datos llamada `yii2basic` de la cual tomarás los datos en la aplicación. -Puedes elegir entre una base de datos SQLite, MySQL, PostgreSQL, MSSQL u Oracle. Por simplicidad, usaremos MySQL -en la siguiente descripción. +Puedes elegir entre una base de datos SQLite, MySQL, PostgreSQL, MSSQL u Oracle, dado que Yii incluye soporte para varios motores. Por simplicidad, usaremos MySQL en la siguiente descripción. -Crea una tabla llamada `country` e inserta algunos datos de ejemplo. Puedes utilizar las siguientes declaraciones SQL. +A continuación, crea una tabla llamada `country` e inserta algunos datos de ejemplo. Puedes utilizar las siguientes declaraciones SQL. ```sql CREATE TABLE `country` ( - `code` char(2) NOT NULL PRIMARY KEY, - `name` char(52) NOT NULL, - `population` int(11) NOT NULL DEFAULT '0' + `code` CHAR(2) NOT NULL PRIMARY KEY, + `name` CHAR(52) NOT NULL, + `population` INT(11) NOT NULL DEFAULT '0' ) ENGINE=InnoDB DEFAULT CHARSET=utf8; INSERT INTO `country` VALUES ('AU','Australia',24016400); @@ -46,12 +44,10 @@ INSERT INTO `country` VALUES ('RU','Russia',146519759); INSERT INTO `country` VALUES ('US','United States',322976000); ``` -Al final, tendrás una base de datos llamada `yii2basic`, y dentro de esta, una tabla llamada `country` con diez -registros en ella. +Al final, tendrás una base de datos llamada `yii2basic`, y dentro de esta, una tabla llamada `country` con diez registros en ella. - -Configurando una conexión a la Base de Datos --------------------------------------------- +Configurar una conexión a la Base de Datos +------------------------------------------ Asegúrate de tener instalado la extensión de PHP [PDO](http://www.php.net/manual/es/book.pdo.php) y el driver de PDO para el motor que estés utilizando (ej. `pdo_mysql` para MySQL). Este es un requisito básico si tu aplicación @@ -72,8 +68,8 @@ return [ ]; ``` -Esta es una típica [configuración](concept-configurations.md) basada en archivos. Especifica los parámetros -necesarios para crear e inicializar una instancia de [[yii\db\Connection]] a través de la cual puedes realizar +El archivo `config/db.php` representa la típica [configuración](concept-configurations.md) basada en archivos. Este archivo de configuración en particular +especifica los parámetros necesarios para crear e inicializar una instancia de [[yii\db\Connection]] a través de la cual puedes realizar consultas SQL contra la base de datos subyacente. La conexión a la base de datos realizada anteriormente puede ser accedida mediante `Yii::$app->db`. @@ -82,9 +78,15 @@ La conexión a la base de datos realizada anteriormente puede ser accedida media el cual especifica cómo la instancia de la [aplicación](structure-applications.md) debe ser inicializada. Para más información, consulta la sección [Configuraciones](concept-configurations.md). +Si necesitas trabajar con bases de datos cuyo soporte no está incluído en Yii, revisa las siguientes extensiones: + +- [Informix](https://github.com/edgardmessias/yii2-informix) +- [IBM DB2](https://github.com/edgardmessias/yii2-ibm-db2) +- [Firebird](https://github.com/edgardmessias/yii2-firebird) -Creando un Active Record ------------------------- + +Crear un Active Record +---------------------- Para representar y extraer datos de la tabla `country`, crea una clase [Active Record](db-active-record.md) llamada `Country` y guárdala en el archivo `models/Country.php`. @@ -101,12 +103,13 @@ class Country extends ActiveRecord } ``` -La clase `Country` extiende de [[yii\db\ActiveRecord]]. No necesitas escribir ningún código dentro de ella. -Yii adivinará la tabla correspondiente a la clase desde su nombre. En caso de que esto no funcione, puedes -sobrescribir el método [[yii\db\ActiveRecord::tableName()]] para especificar la tabla asociada a la clase. +La clase `Country` extiende de [[yii\db\ActiveRecord]]. No necesitas escribir ningún código dentro de ella! Con tan sólo el código de arriba, +Yii adivinará la tabla correspondiente a la clase desde su nombre. + +> Info: Si no se puede realizar un emparejamiento entre el nombre de la clase y la tabla, puedes +sobrescribir el método [[yii\db\ActiveRecord::tableName()]] para especificar explícitamente el nombre de la tabla asiciada. -Utilizando la clase `Country`, puedes manipular los datos de la tabla `country` fácilmente. Debajo hay sencillos -ejemplos de código que muestran cómo utilizar la clase `Country`. +Utilizando la clase `Country`, puedes manipular los datos de la tabla `country` fácilmente, como se muestra en los siguiente ejemplos: ```php use app\models\Country; @@ -125,14 +128,12 @@ $country->name = 'U.S.A.'; $country->save(); ``` -> Info: Active Record es una potente forma de acceder y manipular datos de una base de datos de una manera -orientada a objetos. -Puedes encontrar información más detallada acerca de [Active Record](db-active-record.md). Además de Active Record, -puedes utilizar un método de acceso de bajo nivel llamado [Data Access Objects](db-dao.md). +> Info: Active Record es una potente forma de acceder y manipular datos de una base de datos de una manera orientada a objetos. +Puedes encontrar información más detallada acerca de [Active Record](db-active-record.md). Además de Active Record, puedes utilizar un método de acceso de bajo nivel llamado [Data Access Objects](db-dao.md). -Creando una Acción ------------------- +Crear una Acción +---------------- Para mostrar el país a los usuarios, necesitas crear una acción. En vez de hacerlo en el controlador `site` como lo hiciste en las secciones previas, tiene más sentido crear un nuevo controlador que englobe todas las @@ -174,8 +175,7 @@ class CountryController extends Controller Guarda el código anterior en el archivo `controllers/CountryController.php`. -La acción `index` llama a `Country::find()` para generar una consulta a la base de datos y traer todos los datos -de la tabla `country`. +La acción `index` llama a `Country::find()` para generar una consulta a la base de datos y traer todos los datos de la tabla `country`. Para limitar la cantidad de registros traídos en cada petición, la consulta es paginada con la ayuda de un objeto [[yii\data\Pagination]]. El objeto `Pagination` sirve para dos propósitos: @@ -188,8 +188,8 @@ Al final, la acción `index` renderiza una vista llamada `index` y le pasa los d de paginación relacionada. -Creando una Vista ------------------ +Crear una Vista +--------------- Bajo el directorio `views`, crea primero un sub-directorio llamado `country`. Este será usado para contener todas las vistas renderizadas por el controlador `country`. @@ -214,8 +214,7 @@ use yii\widgets\LinkPager; ``` La vista consiste en dos partes. En la primera, los datos de países son recorridos y renderizados como una lista HTML. -En la segunda parte, un widget [[yii\widgets\LinkPager]] es renderizado usando la información de paginación -pasada desde la acción. +En la segunda parte, un widget [[yii\widgets\LinkPager]] es renderizado usando la información de paginación pasada desde la acción. El widget `LinkPager` muestra una lista de botones que representan las páginas disponibles. Haciendo click en cualquiera de ellas mostrará los datos de países de la página correspondiente. @@ -226,7 +225,7 @@ Probándolo Para ver cómo funciona, utiliza a la siguiente URL en tu navegador: ``` -http://hostname/index.php?r=country/index +http://hostname/index.php?r=country%2Findex ``` ![Lista de Países](images/start-country-list.png) @@ -236,7 +235,7 @@ Si haces click en el botón "2", verás que la página muestra otros cinco país Observa más cuidadosamente y verás que la URL en el navegador cambia a ``` -http://hostname/index.php?r=country/index&page=2 +http://hostname/index.php?r=country%2Findex&page=2 ``` Entre bastidores, [[yii\data\Pagination|Pagination]] está realizando su magia. diff --git a/docs/guide-es/test-unit.md b/docs/guide-es/test-unit.md new file mode 100644 index 0000000..5cb43fe --- /dev/null +++ b/docs/guide-es/test-unit.md @@ -0,0 +1,25 @@ +Tests de Unidad +=============== + +> Note: Esta sección se encuentra en desarrollo. + +Un test de unidad se encarga de verificar que una unidad simple de código funcione como se espera. En la programación orientada a objetos, +la unidad de código más básica es una clase. Por lo tanto, un test de unidad necesita verificar que cada método de la interfaz de la clase funciona apropiadamente. +Esto quiere decir que, dando diferentes parámetros de entrada, el test verifica que el método devuelve el resultado esperado. +Los tests de unidad son normalmente desarrollados por la persona que escribe las clases siendo testeadas. + +Los tests de unidad en Yii están construidos en base a PHPUnit y opcionalmente, Codeception, por lo que se recomineda consultar su respectiva documentación: + +- [Documentación de PHPUnit comienza en el capítulo 2](http://phpunit.de/manual/current/en/writing-tests-for-phpunit.html). +- [Tests de Unidad con Codeception](http://codeception.com/docs/05-UnitTests). + +Ejecutar test de unidad de templates básicos y avanzados +-------------------------------------------------------- + +Por favor consulta las instrucciones provistas en `apps/advanced/tests/README.md` y `apps/basic/tests/README.md`. + +Test de unidad del Framework +---------------------------- + +Si quieres ejecutar tests de unidad para Yii en sí, consulta +"[Comenzando a desarrollar con Yii 2](https://github.com/yiisoft/yii2/blob/master/docs/internals/getting-started.md)". diff --git a/docs/guide-es/tutorial-mailing.md b/docs/guide-es/tutorial-mailing.md new file mode 100644 index 0000000..a0cae15 --- /dev/null +++ b/docs/guide-es/tutorial-mailing.md @@ -0,0 +1,231 @@ +Envío de Emails +=============== + +> Note: Esta sección se encuentra en desarrollo. + +Yii soporta composición y envío de emails. De cualquier modo, el núcleo del framework provee +sólo la funcionalidad de composición y una interfaz básica. En mecanismo de envío en sí debería +ser provisto por la extensión, dado que diferentes proyectos pueden requerir diferente implementación y esto +usualmente depende de servicios y librerías externas. + +Para la mayoría de los casos, puedes utilizar la extensión oficial [yii2-swiftmailer](https://github.com/yiisoft/yii2-swiftmailer). + + +Configuración +------------- + +La configuración del componente Mail depende de la extensión que hayas elegido. +En general, la configuración de tu aplicación debería verse así: + +```php +return [ + //.... + 'components' => [ + 'mailer' => [ + 'class' => 'yii\swiftmailer\Mailer', + ], + ], +]; +``` + + +Uso Básico +---------- + +Una vez configurado el componente 'mailer', puedes utilizar el siguiente código para enviar un correo electrónico: + +```php +Yii::$app->mailer->compose() + ->setFrom('from@domain.com') + ->setTo('to@domain.com') + ->setSubject('Asunto del mensaje') + ->setTextBody('Contenido en texto plano') + ->setHtmlBody('Contenido HTML') + ->send(); +``` + +En el ejemplo anterior, el método `compose()` crea una instancia del mensaje de correo, el cual puede ser llenado y enviado. +En caso de ser necesario, puedes agregar una lógica más compleja en el proceso: + +```php +$message = Yii::$app->mailer->compose(); +if (Yii::$app->user->isGuest) { + $message->setFrom('from@domain.com') +} else { + $message->setFrom(Yii::$app->user->identity->email) +} +$message->setTo(Yii::$app->params['adminEmail']) + ->setSubject('Asunto del mensaje') + ->setTextBody('Contenido en texto plano') + ->send(); +``` + +> Note: cada extensión 'mailer' viene en dos grandes clases: 'Mailer' y 'Message'. 'Mailer' siempre conoce + el nombre de clase especifico de 'Message'. No intentes instanciar el objeto 'Message' directamente - + siempre utiliza el método `compose()` para ello. + +Puedes también enviar varios mensajes al mismo tiempo: + +```php +$messages = []; +foreach ($users as $user) { + $messages[] = Yii::$app->mailer->compose() + // ... + ->setTo($user->email); +} +Yii::$app->mailer->sendMultiple($messages); +``` + +Algunas extensiones en particular pueden beneficiarse de este enfoque, utilizando mensaje simple de red, etc. + + +Componer el contenido del mensaje +--------------------------------- + +Yii permite componer el contenido de los mensajes de correo a través de archivos de vista especiales. +Por defecto, estos archivos deben estar ubicados en la ruta '@app/mail'. + +Ejemplo de archivo de contenido de correo: + +```php + +

Este mensaje te permite visitar nuestro sitio con un sólo click

+ +``` + +Para componer el contenido del mensaje utilizando un archivo, simplemente pasa el nombre de la vista al método `compose()`: + +```php +Yii::$app->mailer->compose('home-link') // el resultado del renderizado de la vista se transforma en el cuerpo del mensaje aquí + ->setFrom('from@domain.com') + ->setTo('to@domain.com') + ->setSubject('Asunto del mensaje') + ->send(); +``` + +Puedes pasarle parámetros adicionales a la vista en el método `compose()`, los cuales estarán disponibles dentro de las vistas: + +```php +Yii::$app->mailer->compose('greetings', [ + 'user' => Yii::$app->user->identity, + 'advertisement' => $adContent, +]); +``` + +Puedes especificar diferentes archivos de vista para el contenido del mensaje en HTML y texto plano: + +```php +Yii::$app->mailer->compose([ + 'html' => 'contact-html', + 'text' => 'contact-text', +]); +``` + +Si especificas el nombre de la vista como un string, el resultado de su renderización será utilizado como cuerpo HTML, mientras +que el cuerpo en texto plano será compuesto removiendo todas las entidades HTML del anterior. + +El resultado de la renderización de la vista puede ser envuelta en el layout, que puede ser definido utiliazando [[yii\mail\BaseMailer::htmlLayout]] +y [[yii\mail\BaseMailer::textLayout]]. Esto funciona igual a como funcionan los layouts en una aplicación web normal. +El layout puede utilizar estilos CSS u otros contenidos compartidos: + +```php + +beginPage() ?> + + + + + + head() ?> + + + beginBody() ?> + + + endBody() ?> + + +endPage() ?> +``` + + +Adjuntar archivos +----------------- + +Puedes adjuntar archivos al mensaje utilizando los métodos `attach()` y `attachContent()`: + +```php +$message = Yii::$app->mailer->compose(); + +// Adjunta un archivo del sistema local de archivos: +$message->attach('/path/to/file.pdf'); + +// Crear adjuntos sobre la marcha +$message->attachContent('Contenido adjunto', ['fileName' => 'attach.txt', 'contentType' => 'text/plain']); +``` + + +Incrustar imágenes +------------------ + +Puedes incrustar imágenes en el mensaje utilizando el método `embed()`. Este método devuelve el id del adjunto, +que debería ser utilizado como tag 'img'. +Este método es fácil de utilizar al componer mensajes a través de un archivo de vista: + +```php +Yii::$app->mailer->compose('embed-email', ['imageFileName' => '/path/to/image.jpg']) + // ... + ->send(); +``` + +Entonces, dentro de tu archivo de vista, puedes utilizar el siguiente código: + +```php + +``` + + +Testear y depurar +----------------- + +Un desarrollador a menudo necesita comprobar qué emails están siendo enviados por la aplicación, cuál es su contenido y otras cosas. +Yii concede dicha habilidad vía `yii\mail\BaseMailer::useFileTransport`. Si se habilita, esta opción hace que +los datos del mensaje sean guardados en archivos locales en vez de enviados. Esos archivos serán guardados bajo +`yii\mail\BaseMailer::fileTransportPath`, que por defecto es '@runtime/mail'. + +> Note: puedes o bien guardar los mensajes en archivos, o enviarlos a sus receptores correspondientes, pero no puedes hacer las dos cosas al mismo tiempo. + +Un archivo de mensaje puede ser abierto por un editor de texto común, de modo que puedas ver sus cabeceras, su contenido y demás. +Este mecanismo en sí puede comprobarse al depurar la aplicación o al ejecutar un test de unidad. + +> Note: el archivo de contenido de mensaje es compuesto vía `\yii\mail\MessageInterface::toString()`, por lo que depende de la extensión + actual de correo utilizada en tu aplicación. + + +Crear tu solución personalizada de correo +----------------------------------------- + +Para crear tu propia solución de correo, necesitas crear 2 clases: una para 'Mailer' y +otra para 'Message'. +Puedes utilizar `yii\mail\BaseMailer` y `yii\mail\BaseMessage` como clases base de tu solución. Estas clases +ya contienen un lógica básica, la cual se describe en esta guía. De cualquier modo, su utilización no es obligatoria, es suficiente +con implementar las interfaces `yii\mail\MailerInterface` y `yii\mail\MessageInterface`. +Luego necesitas implementar todos los métodos abstractos para construir tu solución. From c55b20a3d2244fc8bff7f913768cd29cb17eb94b Mon Sep 17 00:00:00 2001 From: DumpOfTheVar Date: Sun, 5 Jun 2016 23:01:57 +0300 Subject: [PATCH 84/90] fix misspell (theses -> these) in docs/guide/db-active-record.md (#11688) [skip ci] --- docs/guide/db-active-record.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/db-active-record.md b/docs/guide/db-active-record.md index 538b40d..41e236b 100644 --- a/docs/guide/db-active-record.md +++ b/docs/guide/db-active-record.md @@ -182,7 +182,7 @@ Both methods can take one of the following parameter formats: - an associative array: the keys are column names and the values are the corresponding desired column values to be looked for. Please refer to [Hash Format](db-query-builder.md#hash-format) for more details. -The following code shows how theses methods can be used: +The following code shows how these methods can be used: ```php // returns a single customer whose ID is 123 From d7933547f12336b2616380042e331a6f419f50eb Mon Sep 17 00:00:00 2001 From: Konstantin Baranov Date: Mon, 6 Jun 2016 21:06:26 +0500 Subject: [PATCH 85/90] Fixed syntax of command (#11703) [skip ci] --- docs/guide-ru/db-migrations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide-ru/db-migrations.md b/docs/guide-ru/db-migrations.md index 408bd39..356c2fd 100644 --- a/docs/guide-ru/db-migrations.md +++ b/docs/guide-ru/db-migrations.md @@ -330,7 +330,7 @@ class m150811_220037_drop_position_from_post extends Migration Если имя миграции задано как `create_junction_xxx_and_yyy`, файл будет содержать код для создания промежуточной таблцы. ```php -yii create/migration create_junction_post_and_tag +yii migrate/create create_junction_post_and_tag ``` сгенерирует From dca4f00030a5c14ed053a0e7183d5f0e75901eea Mon Sep 17 00:00:00 2001 From: jonny7 Date: Mon, 6 Jun 2016 13:18:45 -0300 Subject: [PATCH 86/90] Update schema-oci.sql (#11662) - `if exists` is not a recognized oracle expression. To do something like this it needs to be in a package - `text` is not a valid data type in oracle, adjusted to VARCHAR() - `on update` does not exist in Oracle --- framework/rbac/migrations/schema-oci.sql | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/framework/rbac/migrations/schema-oci.sql b/framework/rbac/migrations/schema-oci.sql index 44fca9e..efe3526 100644 --- a/framework/rbac/migrations/schema-oci.sql +++ b/framework/rbac/migrations/schema-oci.sql @@ -9,41 +9,44 @@ * @since 2.0 */ -drop table if exists "auth_assignment"; -drop table if exists "auth_item_child"; -drop table if exists "auth_item"; -drop table if exists "auth_rule"; +drop table "auth_assignment"; +drop table "auth_item_child"; +drop table "auth_item"; +drop table "auth_rule"; +-- create new auth_rule table create table "auth_rule" ( "name" varchar(64) not null, - "data" text, + "data" varchar(1000), "created_at" integer, "updated_at" integer, primary key ("name") ); +-- create auth_item table create table "auth_item" ( "name" varchar(64) not null, "type" integer not null, - "description" text, + "description" varchar(1000), "rule_name" varchar(64), - "data" text, + "data" varchar(1000), "created_at" integer, "updated_at" integer, - primary key ("name"), - foreign key ("rule_name") references "auth_rule" ("name") on delete set null on update cascade, - key "type" ("type") + foreign key ("rule_name") references "auth_rule"("name") on delete set null, + primary key ("name") ); +-- adds oracle specific index to auth_item +CREATE INDEX auth_type_index ON "auth_item"("type"); create table "auth_item_child" ( "parent" varchar(64) not null, "child" varchar(64) not null, primary key ("parent","child"), - foreign key ("parent") references "auth_item" ("name") on delete cascade on update cascade, - foreign key ("child") references "auth_item" ("name") on delete cascade on update cascade + foreign key ("parent") references "auth_item"("name") on delete cascade, + foreign key ("child") references "auth_item"("name") on delete cascade ); create table "auth_assignment" @@ -52,5 +55,5 @@ create table "auth_assignment" "user_id" varchar(64) not null, "created_at" integer, primary key ("item_name","user_id"), - foreign key ("item_name") references "auth_item" ("name") on delete cascade on update cascade + foreign key ("item_name") references "auth_item" ("name") on delete cascade ); From 1918f271daa5f8b8ed10f610d7270454315333ce Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Mon, 6 Jun 2016 19:20:29 +0300 Subject: [PATCH 87/90] Changelog for #11662 --- framework/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index fd3181e..902c491 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -23,6 +23,7 @@ Yii Framework 2 Change Log - Bug #11523: Fixed `yii\web\User::checkRedirectAcceptable()` to treat acceptable content type `*/*` as `*` (silverfire) - Bug #11532: Fixed casting of empty char value to `null` resulting in integrity constraint violation for not null columns (samdark) - Bug #11571: Fixed `yii\db\ColumnSchemaBuilder` to work with custom column types (andrey-mokhov, silverfire) +- Bug #11662: Fixed `schema-oci.sql` for RBAC (jonny7) 2.0.8 April 28, 2016 From 7032b9633168af063540486d9982050e105ac75b Mon Sep 17 00:00:00 2001 From: Roman Grinyov Date: Mon, 6 Jun 2016 19:21:03 +0300 Subject: [PATCH 88/90] Add missing paragraph (#11678) [skip ci] --- docs/guide-ru/structure-application-components.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/guide-ru/structure-application-components.md b/docs/guide-ru/structure-application-components.md index 0e20131..07147b8 100644 --- a/docs/guide-ru/structure-application-components.md +++ b/docs/guide-ru/structure-application-components.md @@ -16,6 +16,8 @@ Например, вы можете использовать `\Yii::$app->db` для получения [[yii\db\Connection|соединения с БД]], и `\Yii::$app->cache` для получения доступа к основному компоненту [[yii\caching\Cache|кэша]], зарегистрированному в приложении. +Компонент приложения будет создан при первом обращении к нему через вышеуказанное выражение. Любые дальнейшие обращения будут возвращать тот же экземпляр компонента. + Компонентами приложения могут быть любые объекты. Вы можете зарегистрировать их с помощью свойства [[yii\base\Application::components]] в [конфигурации](structure-applications.md#application-configurations) приложения. Например, From 0a60466729661ab1f8429875f096955f88311c42 Mon Sep 17 00:00:00 2001 From: Boudewijn Vahrmeijer Date: Sun, 29 May 2016 20:19:13 +0200 Subject: [PATCH 89/90] Fixes #11527: Fixed `bigPrimaryKey()` for SQLite --- framework/CHANGELOG.md | 1 + framework/db/sqlite/QueryBuilder.php | 4 ++-- framework/log/migrations/m141106_185632_log_init.php | 2 +- tests/framework/db/QueryBuilderTest.php | 4 ++-- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 902c491..450f63c 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -24,6 +24,7 @@ Yii Framework 2 Change Log - Bug #11532: Fixed casting of empty char value to `null` resulting in integrity constraint violation for not null columns (samdark) - Bug #11571: Fixed `yii\db\ColumnSchemaBuilder` to work with custom column types (andrey-mokhov, silverfire) - Bug #11662: Fixed `schema-oci.sql` for RBAC (jonny7) +- Bug #11527: Fixed `bigPrimaryKey()` for SQLite (dynasource) 2.0.8 April 28, 2016 diff --git a/framework/db/sqlite/QueryBuilder.php b/framework/db/sqlite/QueryBuilder.php index 1847b30..4c40857 100644 --- a/framework/db/sqlite/QueryBuilder.php +++ b/framework/db/sqlite/QueryBuilder.php @@ -28,8 +28,8 @@ class QueryBuilder extends \yii\db\QueryBuilder public $typeMap = [ Schema::TYPE_PK => 'integer PRIMARY KEY AUTOINCREMENT NOT NULL', Schema::TYPE_UPK => 'integer UNSIGNED PRIMARY KEY AUTOINCREMENT NOT NULL', - Schema::TYPE_BIGPK => 'bigint PRIMARY KEY AUTOINCREMENT NOT NULL', - Schema::TYPE_UBIGPK => 'bigint UNSIGNED PRIMARY KEY AUTOINCREMENT NOT NULL', + Schema::TYPE_BIGPK => 'bigint PRIMARY KEY NOT NULL', + Schema::TYPE_UBIGPK => 'bigint UNSIGNED PRIMARY KEY NOT NULL', Schema::TYPE_CHAR => 'char(1)', Schema::TYPE_STRING => 'varchar(255)', Schema::TYPE_TEXT => 'text', diff --git a/framework/log/migrations/m141106_185632_log_init.php b/framework/log/migrations/m141106_185632_log_init.php index 3cef167..fa5485b 100644 --- a/framework/log/migrations/m141106_185632_log_init.php +++ b/framework/log/migrations/m141106_185632_log_init.php @@ -61,7 +61,7 @@ class m141106_185632_log_init extends Migration } $this->createTable($target->logTable, [ - 'id' => $this->bigPrimaryKey(), + 'id' => $this->db->driverName === 'sqlite' ? $this->primaryKey() : $this->bigPrimaryKey(), 'level' => $this->integer(), 'category' => $this->string(), 'log_time' => $this->double(), diff --git a/tests/framework/db/QueryBuilderTest.php b/tests/framework/db/QueryBuilderTest.php index 93ce8f2..4387910 100644 --- a/tests/framework/db/QueryBuilderTest.php +++ b/tests/framework/db/QueryBuilderTest.php @@ -124,7 +124,7 @@ abstract class QueryBuilderTest extends DatabaseTestCase [ 'mysql' => 'bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY', 'postgres' => 'bigserial NOT NULL PRIMARY KEY', - 'sqlite' => 'bigint PRIMARY KEY AUTOINCREMENT NOT NULL', + 'sqlite' => 'bigint PRIMARY KEY NOT NULL', ], ], [ @@ -891,7 +891,7 @@ abstract class QueryBuilderTest extends DatabaseTestCase [ 'mysql' => 'bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY', 'postgres' => 'bigserial NOT NULL PRIMARY KEY', - 'sqlite' => 'bigint UNSIGNED PRIMARY KEY AUTOINCREMENT NOT NULL', + 'sqlite' => 'bigint UNSIGNED PRIMARY KEY NOT NULL', ], ], [ From 7cea46bcb4816b10a15ee945643999b7ddfd9fbe Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Mon, 6 Jun 2016 19:57:42 +0300 Subject: [PATCH 90/90] Changed SQLite fix #11652 according to https://github.com/yiisoft/yii2/pull/11653#issuecomment-224016289 --- framework/db/sqlite/QueryBuilder.php | 4 ++-- framework/log/migrations/m141106_185632_log_init.php | 2 +- tests/framework/db/QueryBuilderTest.php | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/framework/db/sqlite/QueryBuilder.php b/framework/db/sqlite/QueryBuilder.php index 4c40857..405601f 100644 --- a/framework/db/sqlite/QueryBuilder.php +++ b/framework/db/sqlite/QueryBuilder.php @@ -28,8 +28,8 @@ class QueryBuilder extends \yii\db\QueryBuilder public $typeMap = [ Schema::TYPE_PK => 'integer PRIMARY KEY AUTOINCREMENT NOT NULL', Schema::TYPE_UPK => 'integer UNSIGNED PRIMARY KEY AUTOINCREMENT NOT NULL', - Schema::TYPE_BIGPK => 'bigint PRIMARY KEY NOT NULL', - Schema::TYPE_UBIGPK => 'bigint UNSIGNED PRIMARY KEY NOT NULL', + Schema::TYPE_BIGPK => 'integer PRIMARY KEY AUTOINCREMENT NOT NULL', + Schema::TYPE_UBIGPK => 'integer UNSIGNED PRIMARY KEY AUTOINCREMENT NOT NULL', Schema::TYPE_CHAR => 'char(1)', Schema::TYPE_STRING => 'varchar(255)', Schema::TYPE_TEXT => 'text', diff --git a/framework/log/migrations/m141106_185632_log_init.php b/framework/log/migrations/m141106_185632_log_init.php index fa5485b..3cef167 100644 --- a/framework/log/migrations/m141106_185632_log_init.php +++ b/framework/log/migrations/m141106_185632_log_init.php @@ -61,7 +61,7 @@ class m141106_185632_log_init extends Migration } $this->createTable($target->logTable, [ - 'id' => $this->db->driverName === 'sqlite' ? $this->primaryKey() : $this->bigPrimaryKey(), + 'id' => $this->bigPrimaryKey(), 'level' => $this->integer(), 'category' => $this->string(), 'log_time' => $this->double(), diff --git a/tests/framework/db/QueryBuilderTest.php b/tests/framework/db/QueryBuilderTest.php index 4387910..0755f7d 100644 --- a/tests/framework/db/QueryBuilderTest.php +++ b/tests/framework/db/QueryBuilderTest.php @@ -124,7 +124,7 @@ abstract class QueryBuilderTest extends DatabaseTestCase [ 'mysql' => 'bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY', 'postgres' => 'bigserial NOT NULL PRIMARY KEY', - 'sqlite' => 'bigint PRIMARY KEY NOT NULL', + 'sqlite' => 'integer PRIMARY KEY AUTOINCREMENT NOT NULL', ], ], [ @@ -891,7 +891,7 @@ abstract class QueryBuilderTest extends DatabaseTestCase [ 'mysql' => 'bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY', 'postgres' => 'bigserial NOT NULL PRIMARY KEY', - 'sqlite' => 'bigint UNSIGNED PRIMARY KEY NOT NULL', + 'sqlite' => 'integer UNSIGNED PRIMARY KEY AUTOINCREMENT NOT NULL', ], ], [