From 3b01f48c6afcdd82a663250005e370c3a21de0fd Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sat, 9 Aug 2014 21:39:50 -0400 Subject: [PATCH 01/41] Fixes #4597: `yii\composer\Installer::setPermission()` supports setting permission for both directories and files now (qiangxue) --- extensions/composer/CHANGELOG.md | 1 + extensions/composer/Installer.php | 8 ++++---- framework/CHANGELOG.md | 1 + 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/extensions/composer/CHANGELOG.md b/extensions/composer/CHANGELOG.md index 5a972cd769..c48e017e5a 100644 --- a/extensions/composer/CHANGELOG.md +++ b/extensions/composer/CHANGELOG.md @@ -5,6 +5,7 @@ Yii Framework 2 composer extension Change Log -------------------------- - Bug #3438: Fixed support for non-lowercase package names (cebe) +- Enh #4597: `yii\composer\Installer::setPermission()` supports setting permission for both directories and files now (qiangxue) 2.0.0-beta April 13, 2014 ------------------------- diff --git a/extensions/composer/Installer.php b/extensions/composer/Installer.php index b4b6ad80f4..368206b725 100644 --- a/extensions/composer/Installer.php +++ b/extensions/composer/Installer.php @@ -237,11 +237,11 @@ EOF foreach ((array) $options[self::EXTRA_WRITABLE] as $path) { echo "Setting writable: $path ..."; - if (is_dir($path)) { + if (is_dir($path) || is_file($path)) { chmod($path, 0777); echo "done\n"; } else { - echo "The directory was not found: " . getcwd() . DIRECTORY_SEPARATOR . $path; + echo "The directory or file was not found: " . getcwd() . DIRECTORY_SEPARATOR . $path; return; } @@ -249,11 +249,11 @@ EOF foreach ((array) $options[self::EXTRA_EXECUTABLE] as $path) { echo "Setting executable: $path ..."; - if (is_file($path)) { + if (is_dir($path) || is_file($path)) { chmod($path, 0755); echo "done\n"; } else { - echo "\n\tThe file was not found: " . getcwd() . DIRECTORY_SEPARATOR . $path . "\n"; + echo "\n\tThe directory or file was not found: " . getcwd() . DIRECTORY_SEPARATOR . $path . "\n"; return; } diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 69a72a42f0..b2ea7298d6 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -173,6 +173,7 @@ Yii Framework 2 Change Log - Enh #4566: Added client validation support for image validator (Skysplit, qiangxue) - Enh #4581: Added ability to disable url encoding in `UrlRule` (tadaszelvys) - Enh #4602: Added $key param in ActionColumn buttons Closure call (disem) +- Enh #4597: `yii\composer\Installer::setPermission()` supports setting permission for both directories and files now (qiangxue) - Enh: Added support for using sub-queries when building a DB query with `IN` condition (qiangxue) - Enh: Supported adding a new response formatter without the need to reconfigure existing formatters (qiangxue) - Enh: Added `yii\web\UrlManager::addRules()` to simplify adding new URL rules (qiangxue) From b70ba7661b276dac8ef51dbef98ea48eab8e04ee Mon Sep 17 00:00:00 2001 From: Jawad Date: Mon, 11 Aug 2014 00:26:35 +0100 Subject: [PATCH 02/41] Update db-active-record.md to correct a minor typo changed problem to problems to correct a minor typo. --- 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 afbe77d773..aed6c13c6f 100644 --- a/docs/guide/db-active-record.md +++ b/docs/guide/db-active-record.md @@ -20,7 +20,7 @@ $customer->save(); ``` The above code is equivalent to using the following raw SQL statement, which is less -intuitive, more error prone, and may have compatibility problem for different DBMS: +intuitive, more error prone, and may have compatibility problems for different DBMS: ```php $db->createCommand('INSERT INTO customer (name) VALUES (:name)', [ From 545a908b539e7b183d5eb301dd030511ad03da22 Mon Sep 17 00:00:00 2001 From: Anton Andersen Date: Sun, 3 Aug 2014 22:04:03 +0400 Subject: [PATCH 03/41] Add runner for all tests --- apps/advanced/codeception.yml | 11 +++++++++++ apps/advanced/tests/_log/.gitignore | 2 ++ 2 files changed, 13 insertions(+) create mode 100644 apps/advanced/codeception.yml create mode 100644 apps/advanced/tests/_log/.gitignore diff --git a/apps/advanced/codeception.yml b/apps/advanced/codeception.yml new file mode 100644 index 0000000000..c94b9fde94 --- /dev/null +++ b/apps/advanced/codeception.yml @@ -0,0 +1,11 @@ +include: + - common + - console + - backend + - frontend + +paths: + log: tests/_log + +settings: + colors: true diff --git a/apps/advanced/tests/_log/.gitignore b/apps/advanced/tests/_log/.gitignore new file mode 100644 index 0000000000..d6b7ef32c8 --- /dev/null +++ b/apps/advanced/tests/_log/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore From 71adca60eeae3315030c3aaa7cb988f89fc4e6e2 Mon Sep 17 00:00:00 2001 From: Anton Andersen Date: Sun, 3 Aug 2014 22:04:55 +0400 Subject: [PATCH 04/41] Add namespaces to all tests --- apps/advanced/backend/codeception.yml | 1 + apps/advanced/backend/tests/acceptance/LoginCept.php | 1 + apps/advanced/backend/tests/functional/LoginCept.php | 1 + apps/advanced/common/codeception.yml | 1 + apps/advanced/console/codeception.yml | 1 + apps/advanced/frontend/codeception.yml | 1 + apps/advanced/frontend/tests/acceptance/AboutCept.php | 1 + apps/advanced/frontend/tests/acceptance/ContactCept.php | 1 + apps/advanced/frontend/tests/acceptance/HomeCept.php | 2 ++ apps/advanced/frontend/tests/acceptance/LoginCept.php | 1 + apps/advanced/frontend/tests/functional/AboutCept.php | 1 + apps/advanced/frontend/tests/functional/ContactCept.php | 1 + apps/advanced/frontend/tests/functional/HomeCept.php | 2 ++ apps/advanced/frontend/tests/functional/LoginCept.php | 1 + 14 files changed, 16 insertions(+) diff --git a/apps/advanced/backend/codeception.yml b/apps/advanced/backend/codeception.yml index 6d2019c859..b873ade13f 100644 --- a/apps/advanced/backend/codeception.yml +++ b/apps/advanced/backend/codeception.yml @@ -1,3 +1,4 @@ +namespace: backend actor: Tester paths: tests: tests diff --git a/apps/advanced/backend/tests/acceptance/LoginCept.php b/apps/advanced/backend/tests/acceptance/LoginCept.php index fddcf87f09..327b466d52 100644 --- a/apps/advanced/backend/tests/acceptance/LoginCept.php +++ b/apps/advanced/backend/tests/acceptance/LoginCept.php @@ -1,6 +1,7 @@ wantTo('ensure login page works'); diff --git a/apps/advanced/backend/tests/functional/LoginCept.php b/apps/advanced/backend/tests/functional/LoginCept.php index 3c347b7c43..5bba9594a7 100644 --- a/apps/advanced/backend/tests/functional/LoginCept.php +++ b/apps/advanced/backend/tests/functional/LoginCept.php @@ -1,6 +1,7 @@ wantTo('ensure login page works'); diff --git a/apps/advanced/common/codeception.yml b/apps/advanced/common/codeception.yml index 6d2019c859..4ee15239c3 100644 --- a/apps/advanced/common/codeception.yml +++ b/apps/advanced/common/codeception.yml @@ -1,3 +1,4 @@ +namespace: common actor: Tester paths: tests: tests diff --git a/apps/advanced/console/codeception.yml b/apps/advanced/console/codeception.yml index 6d2019c859..94a12e3570 100644 --- a/apps/advanced/console/codeception.yml +++ b/apps/advanced/console/codeception.yml @@ -1,3 +1,4 @@ +namespace: console actor: Tester paths: tests: tests diff --git a/apps/advanced/frontend/codeception.yml b/apps/advanced/frontend/codeception.yml index 6d2019c859..145c3db57f 100644 --- a/apps/advanced/frontend/codeception.yml +++ b/apps/advanced/frontend/codeception.yml @@ -1,3 +1,4 @@ +namespace: frontend actor: Tester paths: tests: tests diff --git a/apps/advanced/frontend/tests/acceptance/AboutCept.php b/apps/advanced/frontend/tests/acceptance/AboutCept.php index 71e387c82e..e8d17b2174 100644 --- a/apps/advanced/frontend/tests/acceptance/AboutCept.php +++ b/apps/advanced/frontend/tests/acceptance/AboutCept.php @@ -1,6 +1,7 @@ wantTo('ensure that about works'); diff --git a/apps/advanced/frontend/tests/acceptance/ContactCept.php b/apps/advanced/frontend/tests/acceptance/ContactCept.php index b8492cdc65..5a36e1d5f3 100644 --- a/apps/advanced/frontend/tests/acceptance/ContactCept.php +++ b/apps/advanced/frontend/tests/acceptance/ContactCept.php @@ -1,6 +1,7 @@ wantTo('ensure that contact works'); diff --git a/apps/advanced/frontend/tests/acceptance/HomeCept.php b/apps/advanced/frontend/tests/acceptance/HomeCept.php index 62456f930e..4f5cc19966 100644 --- a/apps/advanced/frontend/tests/acceptance/HomeCept.php +++ b/apps/advanced/frontend/tests/acceptance/HomeCept.php @@ -1,5 +1,7 @@ wantTo('ensure that home page works'); $I->amOnPage(Yii::$app->homeUrl); diff --git a/apps/advanced/frontend/tests/acceptance/LoginCept.php b/apps/advanced/frontend/tests/acceptance/LoginCept.php index fddcf87f09..3728baff1d 100644 --- a/apps/advanced/frontend/tests/acceptance/LoginCept.php +++ b/apps/advanced/frontend/tests/acceptance/LoginCept.php @@ -1,6 +1,7 @@ wantTo('ensure login page works'); diff --git a/apps/advanced/frontend/tests/functional/AboutCept.php b/apps/advanced/frontend/tests/functional/AboutCept.php index b2153b3c1f..f9dd1a5a03 100644 --- a/apps/advanced/frontend/tests/functional/AboutCept.php +++ b/apps/advanced/frontend/tests/functional/AboutCept.php @@ -1,6 +1,7 @@ wantTo('ensure that about works'); diff --git a/apps/advanced/frontend/tests/functional/ContactCept.php b/apps/advanced/frontend/tests/functional/ContactCept.php index c08e8e0c4b..774428c8bd 100644 --- a/apps/advanced/frontend/tests/functional/ContactCept.php +++ b/apps/advanced/frontend/tests/functional/ContactCept.php @@ -1,6 +1,7 @@ wantTo('ensure that contact works'); diff --git a/apps/advanced/frontend/tests/functional/HomeCept.php b/apps/advanced/frontend/tests/functional/HomeCept.php index 3258ba3331..1bc1d5b6ea 100644 --- a/apps/advanced/frontend/tests/functional/HomeCept.php +++ b/apps/advanced/frontend/tests/functional/HomeCept.php @@ -1,5 +1,7 @@ wantTo('ensure that home page works'); $I->amOnPage(Yii::$app->homeUrl); diff --git a/apps/advanced/frontend/tests/functional/LoginCept.php b/apps/advanced/frontend/tests/functional/LoginCept.php index 3c347b7c43..7e1e1be508 100644 --- a/apps/advanced/frontend/tests/functional/LoginCept.php +++ b/apps/advanced/frontend/tests/functional/LoginCept.php @@ -1,6 +1,7 @@ wantTo('ensure login page works'); From dfffea9af36e56def401a44c05171a6324a365c8 Mon Sep 17 00:00:00 2001 From: Anton Andersen Date: Mon, 11 Aug 2014 12:39:47 +0400 Subject: [PATCH 05/41] Use config variables instead constants. Cleanup redundant constants --- apps/advanced/README.md | 4 +++- apps/advanced/backend/codeception.yml | 4 ++++ apps/advanced/backend/tests/_bootstrap.php | 13 ++++--------- apps/advanced/backend/tests/functional/_config.php | 4 ++-- apps/advanced/common/tests/_bootstrap.php | 9 --------- apps/advanced/console/tests/_bootstrap.php | 9 --------- apps/advanced/frontend/codeception.yml | 4 ++++ apps/advanced/frontend/tests/_bootstrap.php | 13 ++++--------- apps/advanced/frontend/tests/functional/_config.php | 4 ++-- 9 files changed, 23 insertions(+), 41 deletions(-) diff --git a/apps/advanced/README.md b/apps/advanced/README.md index b1d7351059..508a4140bd 100644 --- a/apps/advanced/README.md +++ b/apps/advanced/README.md @@ -113,7 +113,7 @@ it will upgrade your database to the last state according migrations. To be able to run acceptance tests you need a running webserver. For this you can use the php builtin server and run it in the directory where your main project folder is located. For example if your application is located in `/www/advanced` all you need to is: `cd /www` and then `php -S 127.0.0.1:8080` because the default configuration of acceptance tests expects the url of the application to be `/advanced/`. -If you already have a server configured or your application is not located in a folder called `advanced`, you may need to adjust the `TEST_ENTRY_URL` in `frontend/tests/_bootstrap.php` and `backend/tests/_bootstrap.php`. +If you already have a server configured or your application is not located in a folder called `advanced`, you may need to adjust the `test_entry_url` in `backend/codeception.yml` and `frontend/codeception.yml`. After that is done you should be able to run your tests, for example to run `frontend` tests do: @@ -123,5 +123,7 @@ After that is done you should be able to run your tests, for example to run `fro In similar way you can run tests for other application tiers - `backend`, `console`, `common`. +If you already have run `../vendor/bin/codecept build` for each application, you can run all tests by one command: `vendor/bin/codecept run` + You also can adjust you application suite configs and `_bootstrap.php` settings to use other urls and files, as it is can be done in `yii2-basic`. Current template also includes [yii2-faker](https://github.com/yiisoft/yii2/tree/master/extensions/faker) extension, that is correctly setup for each application tier. diff --git a/apps/advanced/backend/codeception.yml b/apps/advanced/backend/codeception.yml index b873ade13f..49a978b6dc 100644 --- a/apps/advanced/backend/codeception.yml +++ b/apps/advanced/backend/codeception.yml @@ -18,3 +18,7 @@ modules: user: '' password: '' dump: tests/_data/dump.sql +config: + # the entry script URL (without host info) for functional and acceptance tests + # PLEASE ADJUST IT TO THE ACTUAL ENTRY SCRIPT URL + test_entry_url: /advanced/backend/web/index-test.php diff --git a/apps/advanced/backend/tests/_bootstrap.php b/apps/advanced/backend/tests/_bootstrap.php index 76f6348cea..bc9d17c28a 100644 --- a/apps/advanced/backend/tests/_bootstrap.php +++ b/apps/advanced/backend/tests/_bootstrap.php @@ -1,12 +1,5 @@ Date: Mon, 11 Aug 2014 15:27:02 +0400 Subject: [PATCH 06/41] Added runtime cleanup for Smarty and Twig tests --- tests/unit/extensions/smarty/ViewRendererTest.php | 9 +++++++++ tests/unit/extensions/twig/ViewRendererTest.php | 7 +++++++ 2 files changed, 16 insertions(+) diff --git a/tests/unit/extensions/smarty/ViewRendererTest.php b/tests/unit/extensions/smarty/ViewRendererTest.php index 5fe1137103..02028f6785 100644 --- a/tests/unit/extensions/smarty/ViewRendererTest.php +++ b/tests/unit/extensions/smarty/ViewRendererTest.php @@ -7,6 +7,7 @@ namespace yiiunit\extensions\smarty; +use yii\helpers\FileHelper; use yii\web\AssetManager; use yii\web\View; use Yii; @@ -20,9 +21,17 @@ class ViewRendererTest extends TestCase { protected function setUp() { + parent::setUp(); $this->mockApplication(); } + protected function tearDown() + { + parent::tearDown(); + FileHelper::removeDirectory(Yii::getAlias('@runtime/assets')); + FileHelper::removeDirectory(Yii::getAlias('@runtime/Smarty')); + } + /** * https://github.com/yiisoft/yii2/issues/2265 */ diff --git a/tests/unit/extensions/twig/ViewRendererTest.php b/tests/unit/extensions/twig/ViewRendererTest.php index 7f4e03fa6f..13fff38ff1 100644 --- a/tests/unit/extensions/twig/ViewRendererTest.php +++ b/tests/unit/extensions/twig/ViewRendererTest.php @@ -1,6 +1,7 @@ mockApplication(); } + protected function tearDown() + { + parent::tearDown(); + FileHelper::removeDirectory(Yii::getAlias('@runtime/assets')); + } + /** * https://github.com/yiisoft/yii2/issues/1755 */ From c4b8e045f0168fad24d2196b4da1fc2bab1b7b5c Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Mon, 11 Aug 2014 16:46:51 +0400 Subject: [PATCH 07/41] Fixed Smarty extension "use" function to work properly in linux environment --- extensions/smarty/Extension.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extensions/smarty/Extension.php b/extensions/smarty/Extension.php index 4359dc799b..b81099f57a 100644 --- a/extensions/smarty/Extension.php +++ b/extensions/smarty/Extension.php @@ -10,6 +10,7 @@ namespace yii\smarty; use Smarty; use Yii; use yii\helpers\ArrayHelper; +use yii\helpers\StringHelper; use yii\helpers\Url; use yii\web\View; @@ -133,7 +134,7 @@ class Extension } $class = $params['class']; - $alias = ArrayHelper::getValue($params, 'as', basename($params['class'])); + $alias = ArrayHelper::getValue($params, 'as', StringHelper::basename($params['class'])); $type = ArrayHelper::getValue($params, 'type', 'static'); // Register the class during compile time From 327914e45281ba2b0f6145cfe56143e62488754b Mon Sep 17 00:00:00 2001 From: Klimov Paul Date: Mon, 11 Aug 2014 17:43:00 +0300 Subject: [PATCH 08/41] Added automatic generating of unique slug value to `yii\behaviors\Sluggable` --- framework/CHANGELOG.md | 1 + framework/behaviors/SluggableBehavior.php | 147 ++++++++++++- .../behaviors/SluggableBehaviorTest.php | 197 ++++++++++++++++++ 3 files changed, 341 insertions(+), 4 deletions(-) create mode 100644 tests/unit/framework/behaviors/SluggableBehaviorTest.php diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 69a72a42f0..32dc7f6b30 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -173,6 +173,7 @@ Yii Framework 2 Change Log - Enh #4566: Added client validation support for image validator (Skysplit, qiangxue) - Enh #4581: Added ability to disable url encoding in `UrlRule` (tadaszelvys) - Enh #4602: Added $key param in ActionColumn buttons Closure call (disem) +- Enh #4630: Added automatic generating of unique slug value to `yii\behaviors\Sluggable` (klimov-paul) - Enh: Added support for using sub-queries when building a DB query with `IN` condition (qiangxue) - Enh: Supported adding a new response formatter without the need to reconfigure existing formatters (qiangxue) - Enh: Added `yii\web\UrlManager::addRules()` to simplify adding new URL rules (qiangxue) diff --git a/framework/behaviors/SluggableBehavior.php b/framework/behaviors/SluggableBehavior.php index 8dd0a1261e..67038912b0 100644 --- a/framework/behaviors/SluggableBehavior.php +++ b/framework/behaviors/SluggableBehavior.php @@ -7,6 +7,8 @@ namespace yii\behaviors; +use yii\base\DynamicModel; +use yii\base\Exception; use yii\base\InvalidConfigException; use yii\db\BaseActiveRecord; use yii\helpers\Inflector; @@ -47,6 +49,7 @@ use yii\helpers\Inflector; * } * ``` * @author Alexander Kochetov + * @author Paul Klimov * @since 2.0 */ class SluggableBehavior extends AttributeBehavior @@ -56,11 +59,11 @@ class SluggableBehavior extends AttributeBehavior */ public $slugAttribute = 'slug'; /** - * @var string the attribute whose value will be converted into a slug + * @var string|array the attribute or list of attributes whose value will be converted into a slug */ public $attribute; /** - * @var string|callable the value that will be used as a slug. This can be an anonymous function + * @var mixed the value that will be used as a slug. This can be an anonymous function * or an arbitrary value. If the former, the return value of the function will be used as a slug. * The signature of the function should be as follows, * @@ -72,6 +75,39 @@ class SluggableBehavior extends AttributeBehavior * ``` */ public $value; + /** + * @var boolean whether to ensure generated slug value to be unique among owner class records. + * If enabled behavior will validate slug uniqueness automatically. If validation fails it will attempt + * generating unique slug value from based one until success. + */ + public $unique = false; + /** + * @var array configuration for slug uniqueness validator. This configuration should not contain validator name + * and validated attributes - only options in format 'name => value' are allowed. + * For example: + * [ + * 'filter' => ['type' => 1, 'status' => 2] + * ] + * @see yii\validators\UniqueValidator + */ + public $uniqueValidatorConfig = []; + /** + * @var string|callable slug unique value generator. It is used in case [[unique]] enabled and generated + * slug is not unique. This can be a PHP callable with following signature: + * + * ```php + * function ($baseSlug, $iteration) + * { + * // return uniqueSlug + * } + * ``` + * + * Also one of the following predefined values can be used: + * - 'increment' - adds incrementing suffix to the base slug + * - 'uniqueid' - adds part of uniqueId hash string to the base slug + * - 'timestamp' - adds current UNIX timestamp to the base slug + */ + public $uniqueSlugGenerator = 'increment'; /** @@ -96,9 +132,112 @@ class SluggableBehavior extends AttributeBehavior protected function getValue($event) { if ($this->attribute !== null) { - $this->value = Inflector::slug($this->owner->{$this->attribute}); + if (is_array($this->attribute)) { + $slugParts = []; + foreach ($this->attribute as $attribute) { + $slugParts[] = Inflector::slug($this->owner->{$attribute}); + } + $this->value = implode('-', $slugParts); + } else { + $this->value = Inflector::slug($this->owner->{$this->attribute}); + } } + $slug = parent::getValue($event); - return parent::getValue($event); + if ($this->unique) { + $baseSlug = $slug; + $iteration = 0; + while (!$this->validateSlugUnique($slug)) { + $iteration++; + $slug = $this->generateUniqueSlug($baseSlug, $iteration); + } + } + return $slug; + } + + /** + * Checks if given slug value is unique. + * @param string $slug slug value + * @return boolean whether slug is unique. + */ + private function validateSlugUnique($slug) + { + $validator = array_merge( + [ + ['slug'], + 'unique', + 'targetClass' => get_class($this->owner) + ], + $this->uniqueValidatorConfig + ); + $model = DynamicModel::validateData(compact('slug'), [$validator]); + return !$model->hasErrors(); + } + + /** + * @param string $baseSlug base slug value + * @param integer $iteration iteration number + * @return string slug suffix + * @throws \yii\base\InvalidConfigException + */ + private function generateUniqueSlug($baseSlug, $iteration) + { + $generator = $this->uniqueSlugGenerator; + switch ($generator) { + case 'increment': + return $this->generateUniqueSlugIncrement($baseSlug, $iteration); + case 'uniqueid': + return $this->generateUniqueSlugUniqueId($baseSlug, $iteration); + case 'timestamp': + return $this->generateSuffixSlugTimestamp($baseSlug, $iteration); + default: + if (is_callable($generator)) { + return call_user_func($generator, $baseSlug, $iteration); + } + throw new InvalidConfigException("Unrecognized slug unique suffix generator '{$generator}'."); + } + } + + /** + * Generates slug using increment of iteration. + * @param string $baseSlug base slug value + * @param integer $iteration iteration number + * @return string generated suffix. + */ + protected function generateUniqueSlugIncrement($baseSlug, $iteration) + { + return $baseSlug . '-' . ($iteration + 1); + } + + /** + * Generates slug using unique id. + * @param string $baseSlug base slug value + * @param integer $iteration iteration number + * @throws \yii\base\Exception + * @return string generated suffix. + */ + protected function generateUniqueSlugUniqueId($baseSlug, $iteration) + { + static $uniqueId; + if ($iteration < 2) { + $uniqueId = sha1(uniqid(get_class($this), true)); + } + $subStringLength = 6 + $iteration; + if ($subStringLength > strlen($uniqueId)) { + throw new Exception('Unique id is exhausted.'); + } + return $baseSlug . '-' . substr($uniqueId, 0, $subStringLength); + } + + /** + * Generates slug using current timestamp. + * @param string $baseSlug base slug value + * @param integer $iteration iteration number + * @throws \yii\base\Exception + * @return string generated suffix. + */ + protected function generateSuffixSlugTimestamp($baseSlug, $iteration) + { + return $baseSlug . '-' . (time() + $iteration - 1); } } diff --git a/tests/unit/framework/behaviors/SluggableBehaviorTest.php b/tests/unit/framework/behaviors/SluggableBehaviorTest.php new file mode 100644 index 0000000000..861b400b77 --- /dev/null +++ b/tests/unit/framework/behaviors/SluggableBehaviorTest.php @@ -0,0 +1,197 @@ +mockApplication([ + 'components' => [ + 'db' => [ + 'class' => '\yii\db\Connection', + 'dsn' => 'sqlite::memory:', + ] + ] + ]); + + $columns = [ + 'id' => 'pk', + 'name' => 'string', + 'slug' => 'string', + 'category_id' => 'integer', + ]; + Yii::$app->getDb()->createCommand()->createTable('test_slug', $columns)->execute(); + } + + public function tearDown() + { + Yii::$app->getDb()->close(); + parent::tearDown(); + } + + // Tests : + + public function testSlug() + { + $model = new ActiveRecordSluggable(); + $model->name = 'test name'; + $model->validate(); + + $this->assertEquals('test-name', $model->slug); + } + + /** + * @depends testSlug + */ + public function testSlugSeveralAttributes() + { + $model = new ActiveRecordSluggable(); + $model->getBehavior('sluggable')->attribute = array('name', 'category_id'); + + $model->name = 'test'; + $model->category_id = 10; + + $model->validate(); + $this->assertEquals('test-10', $model->slug); + } + + /** + * @depends testSlug + */ + public function testUniqueByIncrement() + { + $name = 'test name'; + + $model = new ActiveRecordSluggable(); + $model->name = $name; + $model->save(); + + $model = new ActiveRecordSluggable(); + $model->sluggable->unique = true; + $model->name = $name; + $model->save(); + + $this->assertEquals('test-name-2', $model->slug); + } + + /** + * @depends testUniqueByIncrement + */ + public function testUniqueByCallback() + { + $name = 'test name'; + + $model = new ActiveRecordSluggable(); + $model->name = $name; + $model->save(); + + $model = new ActiveRecordSluggable(); + $model->sluggable->unique = true; + $model->sluggable->uniqueSlugGenerator = function($baseSlug, $iteration) {return $baseSlug . '-callback';}; + $model->name = $name; + $model->save(); + + $this->assertEquals('test-name-callback', $model->slug); + } + + /** + * @depends testUniqueByIncrement + */ + public function testUniqueByUniqueId() + { + $name = 'test name'; + + $model1 = new ActiveRecordSluggable(); + $model1->name = $name; + $model1->save(); + + $model2 = new ActiveRecordSluggable(); + $model2->sluggable->unique = true; + $model2->sluggable->uniqueSlugGenerator = 'uniqueid'; + $model2->name = $name; + $model2->save(); + + $this->assertNotEquals($model2->slug, $model1->slug); + } + + /** + * @depends testUniqueByIncrement + */ + public function testUniqueByTimestamp() + { + $name = 'test name'; + + $model1 = new ActiveRecordSluggable(); + $model1->name = $name; + $model1->save(); + + $model2 = new ActiveRecordSluggable(); + $model2->sluggable->unique = true; + $model2->sluggable->uniqueSlugGenerator = 'timestamp'; + $model2->name = $name; + $model2->save(); + + $this->assertNotEquals($model2->slug, $model1->slug); + } +} + +/** + * Test Active Record class with [[SluggableBehavior]] behavior attached. + * + * @property integer $id + * @property string $name + * @property string $slug + * @property integer $category_id + * + * @property SluggableBehavior $sluggable + */ +class ActiveRecordSluggable extends ActiveRecord +{ + public function behaviors() + { + return [ + 'sluggable' => [ + 'class' => SluggableBehavior::className(), + 'attribute' => 'name', + ], + ]; + } + + public static function tableName() + { + return 'test_slug'; + } + + /** + * @return SluggableBehavior + */ + public function getSluggable() + { + return $this->getBehavior('sluggable'); + } +} \ No newline at end of file From b062a6601d66e0dd854b087810a1d9c0201f3b6a Mon Sep 17 00:00:00 2001 From: Klimov Paul Date: Mon, 11 Aug 2014 18:04:18 +0300 Subject: [PATCH 09/41] Default event for `yii\behaviors\Sluggable` with `unique` enabled changed to 'insert'. --- framework/behaviors/SluggableBehavior.php | 10 ++-- .../behaviors/SluggableBehaviorTest.php | 52 ++++++++++++++----- 2 files changed, 47 insertions(+), 15 deletions(-) diff --git a/framework/behaviors/SluggableBehavior.php b/framework/behaviors/SluggableBehavior.php index 67038912b0..351de28f9b 100644 --- a/framework/behaviors/SluggableBehavior.php +++ b/framework/behaviors/SluggableBehavior.php @@ -118,7 +118,11 @@ class SluggableBehavior extends AttributeBehavior parent::init(); if (empty($this->attributes)) { - $this->attributes = [BaseActiveRecord::EVENT_BEFORE_VALIDATE => $this->slugAttribute]; + if ($this->unique) { + $this->attributes = [BaseActiveRecord::EVENT_BEFORE_INSERT => $this->slugAttribute]; + } else { + $this->attributes = [BaseActiveRecord::EVENT_BEFORE_VALIDATE => $this->slugAttribute]; + } } if ($this->attribute === null && $this->value === null) { @@ -135,9 +139,9 @@ class SluggableBehavior extends AttributeBehavior if (is_array($this->attribute)) { $slugParts = []; foreach ($this->attribute as $attribute) { - $slugParts[] = Inflector::slug($this->owner->{$attribute}); + $slugParts[] = $this->owner->{$attribute}; } - $this->value = implode('-', $slugParts); + $this->value = Inflector::slug(implode('-', $slugParts)); } else { $this->value = Inflector::slug($this->owner->{$this->attribute}); } diff --git a/tests/unit/framework/behaviors/SluggableBehaviorTest.php b/tests/unit/framework/behaviors/SluggableBehaviorTest.php index 861b400b77..81225680ef 100644 --- a/tests/unit/framework/behaviors/SluggableBehaviorTest.php +++ b/tests/unit/framework/behaviors/SluggableBehaviorTest.php @@ -87,12 +87,12 @@ class SluggableBehaviorTest extends TestCase { $name = 'test name'; - $model = new ActiveRecordSluggable(); + $model = new ActiveRecordSluggableUnique(); $model->name = $name; $model->save(); - $model = new ActiveRecordSluggable(); - $model->sluggable->unique = true; + $model = new ActiveRecordSluggableUnique(); + $model->sluggable->uniqueSlugGenerator = 'increment'; $model->name = $name; $model->save(); @@ -106,12 +106,11 @@ class SluggableBehaviorTest extends TestCase { $name = 'test name'; - $model = new ActiveRecordSluggable(); + $model = new ActiveRecordSluggableUnique(); $model->name = $name; $model->save(); - $model = new ActiveRecordSluggable(); - $model->sluggable->unique = true; + $model = new ActiveRecordSluggableUnique(); $model->sluggable->uniqueSlugGenerator = function($baseSlug, $iteration) {return $baseSlug . '-callback';}; $model->name = $name; $model->save(); @@ -126,12 +125,11 @@ class SluggableBehaviorTest extends TestCase { $name = 'test name'; - $model1 = new ActiveRecordSluggable(); + $model1 = new ActiveRecordSluggableUnique(); $model1->name = $name; $model1->save(); - $model2 = new ActiveRecordSluggable(); - $model2->sluggable->unique = true; + $model2 = new ActiveRecordSluggableUnique(); $model2->sluggable->uniqueSlugGenerator = 'uniqueid'; $model2->name = $name; $model2->save(); @@ -146,18 +144,34 @@ class SluggableBehaviorTest extends TestCase { $name = 'test name'; - $model1 = new ActiveRecordSluggable(); + $model1 = new ActiveRecordSluggableUnique(); $model1->name = $name; $model1->save(); - $model2 = new ActiveRecordSluggable(); - $model2->sluggable->unique = true; + $model2 = new ActiveRecordSluggableUnique(); $model2->sluggable->uniqueSlugGenerator = 'timestamp'; $model2->name = $name; $model2->save(); $this->assertNotEquals($model2->slug, $model1->slug); } + + /** + * @depends testSlug + */ + public function testUpdateUnique() + { + $name = 'test name'; + + $model = new ActiveRecordSluggableUnique(); + $model->name = $name; + $model->save(); + + $model = ActiveRecordSluggableUnique::find()->one(); + $model->save(); + + $this->assertEquals('test-name', $model->slug); + } } /** @@ -194,4 +208,18 @@ class ActiveRecordSluggable extends ActiveRecord { return $this->getBehavior('sluggable'); } +} + +class ActiveRecordSluggableUnique extends ActiveRecordSluggable +{ + public function behaviors() + { + return [ + 'sluggable' => [ + 'class' => SluggableBehavior::className(), + 'attribute' => 'name', + 'unique' => true, + ], + ]; + } } \ No newline at end of file From 316317c87f1f87f1dbfc516e698f2bb285d6a865 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Mon, 11 Aug 2014 17:32:42 +0200 Subject: [PATCH 10/41] Add note about smarty SVN --- extensions/smarty/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extensions/smarty/README.md b/extensions/smarty/README.md index d13f609245..d5c2b8ea44 100644 --- a/extensions/smarty/README.md +++ b/extensions/smarty/README.md @@ -39,3 +39,5 @@ or add ``` to the require section of your composer.json. + +Note that the smarty composer package is distributed using subversion so you may need to install subversion. From 299f0fc4411a424c22435095e999271def2d16ef Mon Sep 17 00:00:00 2001 From: Vadim Belorussov Date: Mon, 11 Aug 2014 23:20:59 +0500 Subject: [PATCH 11/41] Add rest-controllers.md to translate into Russian --- docs/guide-ru/rest-controllers.md | 152 ++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 docs/guide-ru/rest-controllers.md diff --git a/docs/guide-ru/rest-controllers.md b/docs/guide-ru/rest-controllers.md new file mode 100644 index 0000000000..ce0289a6fe --- /dev/null +++ b/docs/guide-ru/rest-controllers.md @@ -0,0 +1,152 @@ +Controllers +=========== + +After creating the resource classes and specifying how resource data should be formatted, the next thing +to do is to create controller actions to expose the resources to end users through RESTful APIs. + +Yii provides two base controller classes to simplify your work of creating RESTful actions: +[[yii\rest\Controller]] and [[yii\rest\ActiveController]]. The difference between these two controllers +is that the latter provides a default set of actions that are specifically designed to deal with +resources represented as [Active Record](db-active-record.md). So if you are using [Active Record](db-active-record.md) +and are comfortable with the provided built-in actions, you may consider extending your controller classes +from [[yii\rest\ActiveController]], which will allow you to create powerful RESTful APIs with minimal code. + +Both [[yii\rest\Controller]] and [[yii\rest\ActiveController]] provide the following features, some of which +will be described in detail in the next few sections: + +* HTTP method validation; +* [Content negotiation and Data formatting](rest-response-formatting.md); +* [Authentication](rest-authentication.md); +* [Rate limiting](rest-rate-limiting.md). + +[[yii\rest\ActiveController]] in addition provides the following features: + +* A set of commonly needed actions: `index`, `view`, `create`, `update`, `delete`, `options`; +* User authorization in regarding to the requested action and resource. + + +## Creating Controller Classes + +When creating a new controller class, a convention in naming the controller class is to use +the type name of the resource and use singular form. For example, to serve user information, +the controller may be named as `UserController`. + +Creating a new action is similar to creating an action for a Web application. The only difference +is that instead of rendering the result using a view by calling the `render()` method, for RESTful actions +you directly return the data. The [[yii\rest\Controller::serializer|serializer]] and the +[[yii\web\Response|response object]] will handle the conversion from the original data to the requested +format. For example, + +```php +public function actionView($id) +{ + return User::findOne($id); +} +``` + + +## Filters + +Most RESTful API features provided by [[yii\rest\Controller]] are implemented in terms of [filters](structure-filters.md). +In particular, the following filters will be executed in the order they are listed: + +* [[yii\filters\ContentNegotiator|contentNegotiator]]: supports content negotiation, to be explained in + the [Response Formatting](rest-response-formatting.md) section; +* [[yii\filters\VerbFilter|verbFilter]]: supports HTTP method validation; +* [[yii\filters\AuthMethod|authenticator]]: supports user authentication, to be explained in + the [Authentication](rest-authentication.md) section; +* [[yii\filters\RateLimiter|rateLimiter]]: supports rate limiting, to be explained in + the [Rate Limiting](rest-rate-limiting.md) section. + +These named filters are declared in the [[yii\rest\Controller::behaviors()|behaviors()]] method. +You may override this method to configure individual filters, disable some of them, or add your own filters. +For example, if you only want to use HTTP basic authentication, you may write the following code: + +```php +use yii\filters\auth\HttpBasicAuth; + +public function behaviors() +{ + $behaviors = parent::behaviors(); + $behaviors['authenticator'] = [ + 'class' => HttpBasicAuth::className(), + ]; + return $behaviors; +} +``` + + +## Extending `ActiveController` + +If your controller class extends from [[yii\rest\ActiveController]], you should set +its [[yii\rest\ActiveController::modelClass||modelClass]] property to be the name of the resource class +that you plan to serve through this controller. The class must extend from [[yii\db\ActiveRecord]]. + + +### Customizing Actions + +By default, [[yii\rest\ActiveController]] provides the following actions: + +* [[yii\rest\IndexAction|index]]: list resources page by page; +* [[yii\rest\ViewAction|view]]: return the details of a specified resource; +* [[yii\rest\CreateAction|create]]: create a new resource; +* [[yii\rest\UpdateAction|update]]: update an existing resource; +* [[yii\rest\DeleteAction|delete]]: delete the specified resource; +* [[yii\rest\OptionsAction|options]]: return the supported HTTP methods. + +All these actions are declared through the [[yii\rest\ActiveController::actions()|actions()]] method. +You may configure these actions or disable some of them by overriding the `actions()` method, like shown the following, + +```php +public function actions() +{ + $actions = parent::actions(); + + // disable the "delete" and "create" actions + unset($actions['delete'], $actions['create']); + + // customize the data provider preparation with the "prepareDataProvider()" method + $actions['index']['prepareDataProvider'] = [$this, 'prepareDataProvider']; + + return $actions; +} + +public function prepareDataProvider() +{ + // prepare and return a data provider for the "index" action +} +``` + +Please refer to the class references for individual action classes to learn what configuration options are available. + + +### Performing Access Check + +When exposing resources through RESTful APIs, you often need to check if the current user has the permission +to access and manipulate the requested resource(s). With [[yii\rest\ActiveController]], this can be done +by overriding the [[yii\rest\ActiveController::checkAccess()|checkAccess()]] method like the following, + +```php +/** + * Checks the privilege of the current user. + * + * This method should be overridden to check whether the current user has the privilege + * to run the specified action against the specified data model. + * If the user does not have access, a [[ForbiddenHttpException]] should be thrown. + * + * @param string $action the ID of the action to be executed + * @param \yii\base\Model $model the model to be accessed. If null, it means no specific model is being accessed. + * @param array $params additional parameters + * @throws ForbiddenHttpException if the user does not have access + */ +public function checkAccess($action, $model = null, $params = []) +{ + // check if the user can access $action and $model + // throw ForbiddenHttpException if access should be denied +} +``` + +The `checkAccess()` method will be called by the default actions of [[yii\rest\ActiveController]]. If you create +new actions and also want to perform access check, you should call this method explicitly in the new actions. + +> Tip: You may implement `checkAccess()` by using the [Role-Based Access Control (RBAC) component](security-authorization.md). From fcf25cbf7f4c989e1b471671d82439796bc0cf47 Mon Sep 17 00:00:00 2001 From: Vadim Belorussov Date: Tue, 12 Aug 2014 00:31:35 +0500 Subject: [PATCH 12/41] Translated rest-controllers.md into Russian --- docs/guide-ru/rest-controllers.md | 150 +++++++++++++++--------------- 1 file changed, 75 insertions(+), 75 deletions(-) diff --git a/docs/guide-ru/rest-controllers.md b/docs/guide-ru/rest-controllers.md index ce0289a6fe..5b0beaaf87 100644 --- a/docs/guide-ru/rest-controllers.md +++ b/docs/guide-ru/rest-controllers.md @@ -1,41 +1,41 @@ -Controllers +Контроллеры =========== -After creating the resource classes and specifying how resource data should be formatted, the next thing -to do is to create controller actions to expose the resources to end users through RESTful APIs. +После создания классов ресурсов и настройки способа форматирования ресурсных данных следующим шагом +является создание действий контроллеров для предоставления ресурсов конечным пользователям через RESTful API. -Yii provides two base controller classes to simplify your work of creating RESTful actions: -[[yii\rest\Controller]] and [[yii\rest\ActiveController]]. The difference between these two controllers -is that the latter provides a default set of actions that are specifically designed to deal with -resources represented as [Active Record](db-active-record.md). So if you are using [Active Record](db-active-record.md) -and are comfortable with the provided built-in actions, you may consider extending your controller classes -from [[yii\rest\ActiveController]], which will allow you to create powerful RESTful APIs with minimal code. +В Yii есть два базовых класса контроллеров для упрощения вашей работы по созданию RESTful-действий: +[[yii\rest\Controller]] и [[yii\rest\ActiveController]]. Разница между этими двумя контроллерами в том, +что у последнего есть набор действий по умолчанию, который специально создан для работы с ресурсами, +представленными [Active Record](db-active-record.md). Так что если вы используете [Active Record](db-active-record.md) +и вас устраивает предоставленный набор встроенных действий, вы можете унаследовать классы ваших контроллеров +от [[yii\rest\ActiveController]], что позволит вам создать полноценные RESTful API, написав минимум кода. -Both [[yii\rest\Controller]] and [[yii\rest\ActiveController]] provide the following features, some of which -will be described in detail in the next few sections: +[[yii\rest\Controller]] и [[yii\rest\ActiveController]] имеют следующие возможности, некоторые из которых +будут подробно описаны в следующих нескольких разделах: -* HTTP method validation; -* [Content negotiation and Data formatting](rest-response-formatting.md); -* [Authentication](rest-authentication.md); -* [Rate limiting](rest-rate-limiting.md). +* Проверка HTTP-метода; +* [Согласование содержимого и форматирование данных](rest-response-formatting.md); +* [Аутентификация](rest-authentication.md); +* [Ограничение частоты запросов](rest-rate-limiting.md). -[[yii\rest\ActiveController]] in addition provides the following features: +[[yii\rest\ActiveController]], кроме того, предоставляет следующие возможности: -* A set of commonly needed actions: `index`, `view`, `create`, `update`, `delete`, `options`; -* User authorization in regarding to the requested action and resource. +* Набор наиболее употребительных действий: `index`, `view`, `create`, `update`, `delete`, `options`; +* Авторизация пользователя для запрашиваемых действия и ресурса. -## Creating Controller Classes +## Создание классов контроллеров -When creating a new controller class, a convention in naming the controller class is to use -the type name of the resource and use singular form. For example, to serve user information, -the controller may be named as `UserController`. +При создании нового класса контроллера в имени класса обычно используется +название типа ресурса в единственном числе. Например, контроллер, отвечающий за предоставление информации о пользователях, +можно назвать `UserController`. -Creating a new action is similar to creating an action for a Web application. The only difference -is that instead of rendering the result using a view by calling the `render()` method, for RESTful actions -you directly return the data. The [[yii\rest\Controller::serializer|serializer]] and the -[[yii\web\Response|response object]] will handle the conversion from the original data to the requested -format. For example, +Создание нового действия похоже на создание действия для Web-приложения. Единственное отличие в том, +что в RESTful-действиях вместо рендера результата в представлении с помощью вызова метода `render()` +вы просто возвращает данные. Выполнение преобразования исходных данных в запрошенный формат ложится на +[[yii\rest\Controller::serializer|сериализатор]] и [[yii\web\Response|объект ответа]]. +Например: ```php public function actionView($id) @@ -45,22 +45,22 @@ public function actionView($id) ``` -## Filters +## Фильтры -Most RESTful API features provided by [[yii\rest\Controller]] are implemented in terms of [filters](structure-filters.md). -In particular, the following filters will be executed in the order they are listed: +Большинство возможностей RESTful API, предоставляемых [[yii\rest\Controller]], реализовано на основе [фильтров](structure-filters.md). +В частности, следующие фильтры будут выполняться в том порядке, в котором они перечислены: -* [[yii\filters\ContentNegotiator|contentNegotiator]]: supports content negotiation, to be explained in - the [Response Formatting](rest-response-formatting.md) section; -* [[yii\filters\VerbFilter|verbFilter]]: supports HTTP method validation; -* [[yii\filters\AuthMethod|authenticator]]: supports user authentication, to be explained in - the [Authentication](rest-authentication.md) section; -* [[yii\filters\RateLimiter|rateLimiter]]: supports rate limiting, to be explained in - the [Rate Limiting](rest-rate-limiting.md) section. +* [[yii\filters\ContentNegotiator|contentNegotiator]]: обеспечивает согласование содержимого, более подробно описан + в разделе [Форматирование ответа](rest-response-formatting.md); +* [[yii\filters\VerbFilter|verbFilter]]: обеспечивает проверку HTTP-метода; +* [[yii\filters\AuthMethod|authenticator]]: обеспечивает аутентификацию пользователя, более подробно описан + в разделе [Аутентификация](rest-authentication.md); +* [[yii\filters\RateLimiter|rateLimiter]]: обеспечивает ограничение частоты запросов, более подробно описан + в разделе [Ограничение частоты запросов](rest-rate-limiting.md). -These named filters are declared in the [[yii\rest\Controller::behaviors()|behaviors()]] method. -You may override this method to configure individual filters, disable some of them, or add your own filters. -For example, if you only want to use HTTP basic authentication, you may write the following code: +Эти именованные фильтры объявлены в методе [[yii\rest\Controller::behaviors()|behaviors()]]. +Вы можете переопределить этот метод для настройки отдельных фильтров, отключения каких-то из них или для добавления ваших собственных фильтров. +Например, если вы хотите использовать только базовую HTTP-аутентификацию, вы можете написать такой код: ```php use yii\filters\auth\HttpBasicAuth; @@ -76,36 +76,36 @@ public function behaviors() ``` -## Extending `ActiveController` +## Наследование от `ActiveController` -If your controller class extends from [[yii\rest\ActiveController]], you should set -its [[yii\rest\ActiveController::modelClass||modelClass]] property to be the name of the resource class -that you plan to serve through this controller. The class must extend from [[yii\db\ActiveRecord]]. +Если ваш класс контроллера наследуется от [[yii\rest\ActiveController]], вам следует установить +значение его свойства [[yii\rest\ActiveController::modelClass||modelClass]] равным имени класса ресурса, +который вы планируете обслуживать с помощью этого контроллера. Класс ресурса должен быть унаследован от [[yii\db\ActiveRecord]]. -### Customizing Actions +### Настройка действий -By default, [[yii\rest\ActiveController]] provides the following actions: +По умолчанию [[yii\rest\ActiveController]] предоставляет набор из следующих действий: -* [[yii\rest\IndexAction|index]]: list resources page by page; -* [[yii\rest\ViewAction|view]]: return the details of a specified resource; -* [[yii\rest\CreateAction|create]]: create a new resource; -* [[yii\rest\UpdateAction|update]]: update an existing resource; -* [[yii\rest\DeleteAction|delete]]: delete the specified resource; -* [[yii\rest\OptionsAction|options]]: return the supported HTTP methods. +* [[yii\rest\IndexAction|index]]: постраничный список ресурсов; +* [[yii\rest\ViewAction|view]]: возвращает подробную информацию об указанном ресурсе; +* [[yii\rest\CreateAction|create]]: создание нового ресурса; +* [[yii\rest\UpdateAction|update]]: обновление существующего ресурса; +* [[yii\rest\DeleteAction|delete]]: удаление указанного ресурса; +* [[yii\rest\OptionsAction|options]]: возвращает поддерживаемые HTTP-методы. -All these actions are declared through the [[yii\rest\ActiveController::actions()|actions()]] method. -You may configure these actions or disable some of them by overriding the `actions()` method, like shown the following, +Все эти действия объявляются в методе [[yii\rest\ActiveController::actions()|actions()]]. +Вы можете настроить эти действия или отключить какие-то из них, переопределив метод `actions()`, как показано ниже: ```php public function actions() { $actions = parent::actions(); - // disable the "delete" and "create" actions + // отключить действия "delete" и "create" unset($actions['delete'], $actions['create']); - // customize the data provider preparation with the "prepareDataProvider()" method + // настроить подготовку провайдера данных с помощью метода "prepareDataProvider()" $actions['index']['prepareDataProvider'] = [$this, 'prepareDataProvider']; return $actions; @@ -113,40 +113,40 @@ public function actions() public function prepareDataProvider() { - // prepare and return a data provider for the "index" action + // подготовить и вернуть провайдер данных для действия "index" } ``` -Please refer to the class references for individual action classes to learn what configuration options are available. +Чтобы узнать, какие опции доступны для настройки классов отдельных действий, обратитесь к соответствующим разделам справочника классов. -### Performing Access Check +### Выполнение контроля доступа -When exposing resources through RESTful APIs, you often need to check if the current user has the permission -to access and manipulate the requested resource(s). With [[yii\rest\ActiveController]], this can be done -by overriding the [[yii\rest\ActiveController::checkAccess()|checkAccess()]] method like the following, +При предоставлении ресурсов через RESTful API часто бывает нужно проверять, имеет ли текущий пользователь разрешение +на доступ к запрошенному ресурсу (или ресурсам) и манипуляцию им (ими). Для [[yii\rest\ActiveController]] эта задача может быть решена +переопределением метода [[yii\rest\ActiveController::checkAccess()|checkAccess()]] следующим образом: ```php /** - * Checks the privilege of the current user. + * Проверяет права текущего пользователя. * - * This method should be overridden to check whether the current user has the privilege - * to run the specified action against the specified data model. - * If the user does not have access, a [[ForbiddenHttpException]] should be thrown. + * Этот метод должен быть переопределен, чтобы проверить, имеет ли текущий пользователь + * право выполнения указанного действия над указанной моделью данных. + * Если у пользователя нет доступа, следует выбросить исключение [[ForbiddenHttpException]]. * - * @param string $action the ID of the action to be executed - * @param \yii\base\Model $model the model to be accessed. If null, it means no specific model is being accessed. - * @param array $params additional parameters - * @throws ForbiddenHttpException if the user does not have access +* @param string $action ID действия, которое надо выполнить + * @param \yii\base\Model $model модель, к которой нужно получить доступ. Если null, это означает, что модель, к которой нужно получить доступ, отсутствует. + * @param array $params дополнительные параметры + * @throws ForbiddenHttpException если у пользователя нет доступа */ public function checkAccess($action, $model = null, $params = []) { - // check if the user can access $action and $model - // throw ForbiddenHttpException if access should be denied + // проверить, имеет ли пользователь доступ к $action и $model + // выбросить ForbiddenHttpException, если доступ следует запретить } ``` -The `checkAccess()` method will be called by the default actions of [[yii\rest\ActiveController]]. If you create -new actions and also want to perform access check, you should call this method explicitly in the new actions. +Метод `checkAccess()` будет вызван действиями по умолчанию контроллера [[yii\rest\ActiveController]]. Если вы создаете +новые действия и хотите в них выполнять контроль доступа, вы должны вызвать этот метод явно в своих новых действиях. -> Tip: You may implement `checkAccess()` by using the [Role-Based Access Control (RBAC) component](security-authorization.md). +> Подсказка: вы можете реализовать метод `checkAccess()` с помощью ["Контроля доступа на основе ролей" (RBAC)](security-authorization.md). From b4a9ea8f8a4d226f91c9fec006994edb8feb3e7d Mon Sep 17 00:00:00 2001 From: Vadim Belorussov Date: Tue, 12 Aug 2014 00:56:39 +0500 Subject: [PATCH 13/41] Add rest-routing.md to translate into Russian --- docs/guide-ru/rest-routing.md | 78 +++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 docs/guide-ru/rest-routing.md diff --git a/docs/guide-ru/rest-routing.md b/docs/guide-ru/rest-routing.md new file mode 100644 index 0000000000..ff0684ea3c --- /dev/null +++ b/docs/guide-ru/rest-routing.md @@ -0,0 +1,78 @@ +Routing +======= + +With resource and controller classes ready, you can access the resources using the URL like +`http://localhost/index.php?r=user/create`, similar to what you can do with normal Web applications. + +In practice, you usually want to enable pretty URLs and take advantage of HTTP verbs. +For example, a request `POST /users` would mean accessing the `user/create` action. +This can be done easily by configuring the `urlManager` application component in the application +configuration like the following: + +```php +'urlManager' => [ + 'enablePrettyUrl' => true, + 'enableStrictParsing' => true, + 'showScriptName' => false, + 'rules' => [ + ['class' => 'yii\rest\UrlRule', 'controller' => 'user'], + ], +] +``` + +Compared to the URL management for Web applications, the main new thing above is the use of +[[yii\rest\UrlRule]] for routing RESTful API requests. This special URL rule class will +create a whole set of child URL rules to support routing and URL creation for the specified controller(s). +For example, the above code is roughly equivalent to the following rules: + +```php +[ + 'PUT,PATCH users/' => 'user/update', + 'DELETE users/' => 'user/delete', + 'GET,HEAD users/' => 'user/view', + 'POST users' => 'user/create', + 'GET,HEAD users' => 'user/index', + 'users/' => 'user/options', + 'users' => 'user/options', +] +``` + +And the following API endpoints are supported by this rule: + +* `GET /users`: list all users page by page; +* `HEAD /users`: show the overview information of user listing; +* `POST /users`: create a new user; +* `GET /users/123`: return the details of the user 123; +* `HEAD /users/123`: show the overview information of user 123; +* `PATCH /users/123` and `PUT /users/123`: update the user 123; +* `DELETE /users/123`: delete the user 123; +* `OPTIONS /users`: show the supported verbs regarding endpoint `/users`; +* `OPTIONS /users/123`: show the supported verbs regarding endpoint `/users/123`. + +You may configure the `only` and `except` options to explicitly list which actions to support or which +actions should be disabled, respectively. For example, + +```php +[ + 'class' => 'yii\rest\UrlRule', + 'controller' => 'user', + 'except' => ['delete', 'create', 'update'], +], +``` + +You may also configure `patterns` or `extraPatterns` to redefine existing patterns or add new patterns supported by this rule. +For example, to support a new action `search` by the endpoint `GET /users/search`, configure the `extraPatterns` option as follows, + +```php +[ + 'class' => 'yii\rest\UrlRule', + 'controller' => 'user', + 'extraPatterns' => [ + 'GET search' => 'search', + ], +``` + +You may have noticed that the controller ID `user` appears in plural form as `users` in the endpoints. +This is because [[yii\rest\UrlRule]] automatically pluralizes controller IDs for them to use in endpoints. +You may disable this behavior by setting [[yii\rest\UrlRule::pluralize]] to be false, or if you want +to use some special names you may configure the [[yii\rest\UrlRule::controller]] property. From 20774165bc10ef68d5ab2f363aa835da13de7a77 Mon Sep 17 00:00:00 2001 From: "mcd.php" Date: Mon, 11 Aug 2014 17:48:20 +0400 Subject: [PATCH 14/41] Fixes #4644: Added `\yii\db\Schema::createColumnSchema()` to be able to customize column schema used --- framework/CHANGELOG.md | 1 + framework/db/Schema.php | 7 +++++++ framework/db/cubrid/Schema.php | 2 +- framework/db/mssql/Schema.php | 2 +- framework/db/mysql/Schema.php | 2 +- framework/db/oci/Schema.php | 2 +- framework/db/pgsql/Schema.php | 2 +- framework/db/sqlite/Schema.php | 2 +- 8 files changed, 14 insertions(+), 6 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 69a72a42f0..163fd91b01 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -173,6 +173,7 @@ Yii Framework 2 Change Log - Enh #4566: Added client validation support for image validator (Skysplit, qiangxue) - Enh #4581: Added ability to disable url encoding in `UrlRule` (tadaszelvys) - Enh #4602: Added $key param in ActionColumn buttons Closure call (disem) +- Enh #4644: Added `\yii\db\Schema::createColumnSchema()` to be able to customize column schema used (mcd-php) - Enh: Added support for using sub-queries when building a DB query with `IN` condition (qiangxue) - Enh: Supported adding a new response formatter without the need to reconfigure existing formatters (qiangxue) - Enh: Added `yii\web\UrlManager::addRules()` to simplify adding new URL rules (qiangxue) diff --git a/framework/db/Schema.php b/framework/db/Schema.php index 983d5608e6..5d0083e497 100644 --- a/framework/db/Schema.php +++ b/framework/db/Schema.php @@ -84,6 +84,13 @@ abstract class Schema extends Object */ private $_builder; + /** + * @return \yii\db\ColumnSchema + * @throws \yii\base\InvalidConfigException + */ + protected function createColumnSchema() { + return Yii::createObject('yii\db\ColumnSchema'); + } /** * Loads the metadata for the specified table. diff --git a/framework/db/cubrid/Schema.php b/framework/db/cubrid/Schema.php index 1a4655c8d1..558d8dfb78 100644 --- a/framework/db/cubrid/Schema.php +++ b/framework/db/cubrid/Schema.php @@ -195,7 +195,7 @@ class Schema extends \yii\db\Schema */ protected function loadColumnSchema($info) { - $column = new ColumnSchema(); + $column = $this->createColumnSchema(); $column->name = $info['Field']; $column->allowNull = $info['Null'] === 'YES'; diff --git a/framework/db/mssql/Schema.php b/framework/db/mssql/Schema.php index 994402a371..ce52d30ce9 100644 --- a/framework/db/mssql/Schema.php +++ b/framework/db/mssql/Schema.php @@ -176,7 +176,7 @@ class Schema extends \yii\db\Schema */ protected function loadColumnSchema($info) { - $column = new ColumnSchema(); + $column = $this->createColumnSchema(); $column->name = $info['column_name']; $column->allowNull = $info['is_nullable'] == 'YES'; diff --git a/framework/db/mysql/Schema.php b/framework/db/mysql/Schema.php index 66392cd374..80e11a1a8c 100644 --- a/framework/db/mysql/Schema.php +++ b/framework/db/mysql/Schema.php @@ -127,7 +127,7 @@ class Schema extends \yii\db\Schema */ protected function loadColumnSchema($info) { - $column = new ColumnSchema; + $column = $this->createColumnSchema(); $column->name = $info['Field']; $column->allowNull = $info['Null'] === 'YES'; diff --git a/framework/db/oci/Schema.php b/framework/db/oci/Schema.php index 8de7b7111e..5a1617df73 100644 --- a/framework/db/oci/Schema.php +++ b/framework/db/oci/Schema.php @@ -200,7 +200,7 @@ EOD; */ protected function createColumn($column) { - $c = new ColumnSchema(); + $c = $this->createColumnSchema(); $c->name = $column['COLUMN_NAME']; $c->allowNull = $column['NULLABLE'] === 'Y'; $c->isPrimaryKey = strpos($column['KEY'], 'P') !== false; diff --git a/framework/db/pgsql/Schema.php b/framework/db/pgsql/Schema.php index 9d9f433f9b..2d546a2d62 100644 --- a/framework/db/pgsql/Schema.php +++ b/framework/db/pgsql/Schema.php @@ -434,7 +434,7 @@ SQL; */ protected function loadColumnSchema($info) { - $column = new ColumnSchema(); + $column = $this->createColumnSchema(); $column->allowNull = $info['is_nullable']; $column->autoIncrement = $info['is_autoinc']; $column->comment = $info['column_comment']; diff --git a/framework/db/sqlite/Schema.php b/framework/db/sqlite/Schema.php index 39acfcfece..d4ca499512 100644 --- a/framework/db/sqlite/Schema.php +++ b/framework/db/sqlite/Schema.php @@ -212,7 +212,7 @@ class Schema extends \yii\db\Schema */ protected function loadColumnSchema($info) { - $column = new ColumnSchema; + $column = $this->createColumnSchema(); $column->name = $info['name']; $column->allowNull = !$info['notnull']; $column->isPrimaryKey = $info['pk'] != 0; From a2800fe52e89f21b5e8d8a1ade299611b03e2732 Mon Sep 17 00:00:00 2001 From: Vadim Belorussov Date: Tue, 12 Aug 2014 01:23:05 +0500 Subject: [PATCH 15/41] Translated rest-routing.md into Russian --- docs/guide-ru/rest-routing.md | 60 +++++++++++++++++------------------ 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/docs/guide-ru/rest-routing.md b/docs/guide-ru/rest-routing.md index ff0684ea3c..59a94605ce 100644 --- a/docs/guide-ru/rest-routing.md +++ b/docs/guide-ru/rest-routing.md @@ -1,13 +1,13 @@ -Routing -======= +Маршрутизация +============= -With resource and controller classes ready, you can access the resources using the URL like -`http://localhost/index.php?r=user/create`, similar to what you can do with normal Web applications. +Имея готовые классы ресурсов и контроллеров, можно получить доступ к ресурсам, используя URL вроде +`http://localhost/index.php?r=user/create`, подобно тому, как вы это делаете с обычными Web-приложениями. -In practice, you usually want to enable pretty URLs and take advantage of HTTP verbs. -For example, a request `POST /users` would mean accessing the `user/create` action. -This can be done easily by configuring the `urlManager` application component in the application -configuration like the following: +На деле вам обычно хочется включить «красивые» URL-адреса и использовать все преимущества HTTP-методов (HTTP-verbs). +Например, чтобы запрос `POST /users` означал обращение к действию `user/create`. +Это может быть легко сделано с помощью настройки компонента приложения `urlManager` в +конфигурации приложения следующим образом: ```php 'urlManager' => [ @@ -20,10 +20,10 @@ configuration like the following: ] ``` -Compared to the URL management for Web applications, the main new thing above is the use of -[[yii\rest\UrlRule]] for routing RESTful API requests. This special URL rule class will -create a whole set of child URL rules to support routing and URL creation for the specified controller(s). -For example, the above code is roughly equivalent to the following rules: +Главная новинка в коде выше по сравнению с управлением URL-адресами в Web-приложениях состоит в использовании +[[yii\rest\UrlRule]] для маршрутизации запросов к RESTful API. Этот особый класс URL-правил будет +создавать целый набор дочерних URL-правил для поддержки маршрутизации и создания URL-адресов для указанного контроллера (или контроллеров). +Например, приведенный выше код является приближенным аналогом следующего набора правил: ```php [ @@ -37,20 +37,20 @@ For example, the above code is roughly equivalent to the following rules: ] ``` -And the following API endpoints are supported by this rule: +Этим правилом поддерживаются следующие точки входа в API: -* `GET /users`: list all users page by page; -* `HEAD /users`: show the overview information of user listing; -* `POST /users`: create a new user; -* `GET /users/123`: return the details of the user 123; -* `HEAD /users/123`: show the overview information of user 123; -* `PATCH /users/123` and `PUT /users/123`: update the user 123; -* `DELETE /users/123`: delete the user 123; -* `OPTIONS /users`: show the supported verbs regarding endpoint `/users`; -* `OPTIONS /users/123`: show the supported verbs regarding endpoint `/users/123`. +* `GET /users`: разбитый на страницы список всех пользователей; +* `HEAD /users`: общая информация по списку пользователей; +* `POST /users`: создание нового пользователя; +* `GET /users/123`: подробная информация о пользователе 123; +* `HEAD /users/123`: общая информация о пользователе 123; +* `PATCH /users/123` и `PUT /users/123`: обновление пользователя 123; +* `DELETE /users/123`: удаление пользователя 123; +* `OPTIONS /users`: список HTTP-методов, поддерживаемые точкой входа `/users`; +* `OPTIONS /users/123`: список HTTP-методов, поддерживаемые точкой входа `/users/123`. -You may configure the `only` and `except` options to explicitly list which actions to support or which -actions should be disabled, respectively. For example, +Вы можете настроить опции `only` и `except`, явно указав для них список действий, которые поддерживаются или +которые должны быть отключены, соответственно. Например: ```php [ @@ -60,8 +60,8 @@ actions should be disabled, respectively. For example, ], ``` -You may also configure `patterns` or `extraPatterns` to redefine existing patterns or add new patterns supported by this rule. -For example, to support a new action `search` by the endpoint `GET /users/search`, configure the `extraPatterns` option as follows, +Вы также можете настроить опции `patterns` или `extraPatterns` для переопределения существующих шаблонов или добавления новых шаблонов, поддерживаемых этим правилом. +Например, для включения нового действия `search` в точке входа `GET /users/search` настройте опцию `extraPatterns` следующим образом: ```php [ @@ -72,7 +72,7 @@ For example, to support a new action `search` by the endpoint `GET /users/search ], ``` -You may have noticed that the controller ID `user` appears in plural form as `users` in the endpoints. -This is because [[yii\rest\UrlRule]] automatically pluralizes controller IDs for them to use in endpoints. -You may disable this behavior by setting [[yii\rest\UrlRule::pluralize]] to be false, or if you want -to use some special names you may configure the [[yii\rest\UrlRule::controller]] property. +Как вы могли заметить, ID контроллера `user` в этих точках входа используется в форме множественного числа (как `users`). +Это происходит потому, что [[yii\rest\UrlRule]] автоматически приводит идентификаторы контроллеров к множественной форме для использования в точках входа. +Вы можете отключить такое поведение, назначив свойству [[yii\rest\UrlRule::pluralize]] значение false, или, если вы хотите использовать +какие-то особые имена, вы можете настроить свойство [[yii\rest\UrlRule::controller]]. From ba606d06eb9007cc919ac99a41936b8196793f60 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Tue, 12 Aug 2014 00:56:50 +0400 Subject: [PATCH 16/41] Fixes #4673 --- docs/guide/test-acceptance.md | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/docs/guide/test-acceptance.md b/docs/guide/test-acceptance.md index 45ca7b1cb0..e3871549b7 100644 --- a/docs/guide/test-acceptance.md +++ b/docs/guide/test-acceptance.md @@ -7,5 +7,20 @@ Acceptance Tests - https://github.com/yiisoft/yii2/blob/master/apps/advanced/README.md#testing - https://github.com/yiisoft/yii2/blob/master/apps/basic/tests/README.md -How to run php-server ---------------------- +How to run webserver +-------------------- + +In order to perform acceptance tests you need a web server. Since PHP 5.4 has built-in one, we can use it. For the basic +application template it would be: + +``` +cd web +php -S localhost:8080 +``` + +In order for the tests to work correctly you need to adjust `TEST_ENTRY_URL` in `_bootstrap.php` file. It should point +to `index-test.php` of your webserver. Since we're running directly from its directory the line would be: + +```php +defined('TEST_ENTRY_URL') or define('TEST_ENTRY_URL', '/index-test.php'); +``` From b7cad9df3d64781fb7b0534a9693d5a6d5d80572 Mon Sep 17 00:00:00 2001 From: abrahamy Date: Sat, 19 Jul 2014 22:01:22 +0100 Subject: [PATCH 17/41] Fixes #4371: Active form client validation wasn't working in case of two models having same named fields When rendering multiple models in the same ActiveForm if any of the field names in the two models is the same then the corresponding attribute in the yiiActiveForm object in yiiActiveForm.js (ie $('form').data('yiiActiveForm').attributes) of the first rendered model is overwritten by the attribute of the last model causing the client-side validation for the overwritten field not to run at all. Example: given two models Car and Owner with attributes {make, age} and {name, age} then if the Car is rendered first its age attribute will not have any client validation. --- framework/CHANGELOG.md | 1 + framework/widgets/ActiveField.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 163fd91b01..a07e2dbcde 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -69,6 +69,7 @@ Yii Framework 2 Change Log - Bug #4241: `yii\widgets\Pjax` was incorrectly setting container id (mitalcoi) - Bug #4276: Added check for UPLOAD_ERR_NO_FILE in `yii\web\UploadedFile` and return null if no file was uploaded (OmgDef) - Bug #4342: mssql (dblib) driver does not support getting attributes (tof06) +- Bug #4371: Active form client validation wasn't working in case of two models having same named fields (abrahamy) - Bug #4409: Upper case letters in subdirectory prefixes of controller IDs were not properly handled (qiangxue) - Bug #4412: Formatter used SI Prefixes for base 1024, now uses binary prefixes (kmindi) - Bug #4427: Formatter could do one division too much (kmindi) diff --git a/framework/widgets/ActiveField.php b/framework/widgets/ActiveField.php index 636db97fe5..565d2223e5 100644 --- a/framework/widgets/ActiveField.php +++ b/framework/widgets/ActiveField.php @@ -195,7 +195,7 @@ class ActiveField extends Component { $clientOptions = $this->getClientOptions(); if (!empty($clientOptions)) { - $this->form->attributes[$this->attribute] = $clientOptions; + $this->form->attributes[Html::getInputId($this->model, $this->attribute)] = $clientOptions; } $inputID = Html::getInputId($this->model, $this->attribute); From 564c53f790e036dd4ef5c46956e65f26f0235862 Mon Sep 17 00:00:00 2001 From: Evgeniy Tkachenko Date: Thu, 31 Jul 2014 09:14:04 +0300 Subject: [PATCH 18/41] Added information about format of Data column --- docs/guide/output-data-widgets.md | 45 +++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/docs/guide/output-data-widgets.md b/docs/guide/output-data-widgets.md index 54b750d152..1b01668f45 100644 --- a/docs/guide/output-data-widgets.md +++ b/docs/guide/output-data-widgets.md @@ -140,6 +140,51 @@ You may specify various container HTML options passing arrays to: Data column is for displaying and sorting data. It is default column type so specifying class could be omitted when using it. + +**Attribute `format`.** +Data column uses component [[\yii\base\Formatter|Formatter]] of application for own attribute `format` by default. `Formatter` is configured as an application component in [[\yii\base\Application]] by default. You can access that instance via `Yii::$app->formatter` also. + +It can be set as a string or an array for Data column: + +```php + [ + [ + 'format' => 'text', + ], + // or + [ + 'format' => ['text', []], + ], + ], + ] +); ?> +``` +By default format supports formats such as methods in a class [[\yii\base\Formatter]] that begins with "as ..". +For type "xyz", the method "asXyz" will be used. For example, if the format is "html", then [[\yii\base\Formatter::asHtml()]] will be used. Format names are case insensitive. + +You may specify various format options passing arrays to: + +- [[\yii\base\Formatter::asRaw()|raw]] - Raw Formats the value as is without any formatting. +- [[\yii\base\Formatter::asText()|text]] - Formats the value as an HTML-encoded plain text. Is used by default. +- [[\yii\base\Formatter::asNtext()|ntext]] - Formats the value as an HTML-encoded plain text with newlines converted into breaks. +- [[\yii\base\Formatter::asParagraphs()|paragraphs]] - Formats the value as HTML-encoded text paragraphs. +- [[\yii\base\Formatter::asHtml()|html]] - The value will be purified using [[HtmlPurifier]] to avoid XSS attacks. You can use `['html', ['Attr.AllowedFrameTargets' => ['_blank']]]` +- [[\yii\base\Formatter::asEmail()|email]] - Formats the value as a mailto link. +- [[\yii\base\Formatter::asImage()|image]] - Formats the value as an image tag. +- [[\yii\base\Formatter::asUrl()|url]] - Formats the value as a hyperlink. +- [[\yii\base\Formatter::asBoolean()|boolean]] - Formats the value as a boolean. Use `Yii::$app->formatter->booleanFormat = ['Nooooo','It is true'];` before GridView for custom values. +- [[\yii\base\Formatter::asDate()|date]] - Formats the value as a date. +- [[\yii\base\Formatter::asTime()|time]] - Formats the value as a time. +- [[\yii\base\Formatter::asDatetime()|datetime]] - Formats the value as a datetime. +- [[\yii\base\Formatter::asInteger()|integer]] - Formats the value as an integer. +- [[\yii\base\Formatter::asDouble()|double]] - Formats the value as a double number. +- [[\yii\base\Formatter::asNumber()|number]] - Formats the value as a number with decimal and thousand separators. +- [[\yii\base\Formatter::asSize()|size]] - Formats the value in bytes as a size in human readable form. +- [[\yii\base\Formatter::asRelativeTime()|relativeTime]] - Formats the value as the time interval between a date and now in human readable form. + TBD #### Action column From a2e8083bebbce47e07a9613ee71132c1b1eb0308 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Mon, 11 Aug 2014 23:48:04 +0200 Subject: [PATCH 19/41] Created ArrayCache class - can be used to enable caching in a request without the need for any storage - useful for example in complex console tasks that should still be independed --- framework/CHANGELOG.md | 1 + framework/caching/ArrayCache.php | 86 +++++++++++++++++++ .../unit/framework/caching/ArrayCacheTest.php | 49 +++++++++++ .../unit/framework/caching/CacheTestCase.php | 18 ++++ 4 files changed, 154 insertions(+) create mode 100644 framework/caching/ArrayCache.php create mode 100644 tests/unit/framework/caching/ArrayCacheTest.php diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 163fd91b01..53ad03547b 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -233,6 +233,7 @@ Yii Framework 2 Change Log - New #3911: Added `yii\behaviors\SluggableBehavior` that fills the specified model attribute with the transliterated and adjusted version to use in URLs (creocoder) - New #4193: Added `yii\filters\Cors` CORS filter to allow Cross Origin Resource Sharing (pgaultier) - New: Added `yii\base\InvalidValueException` (qiangxue) +- New: Added `yii\caching\ArrayCache` (cebe) 2.0.0-beta April 13, 2014 diff --git a/framework/caching/ArrayCache.php b/framework/caching/ArrayCache.php new file mode 100644 index 0000000000..7295fc06e2 --- /dev/null +++ b/framework/caching/ArrayCache.php @@ -0,0 +1,86 @@ + + * @since 2.0 + */ +class ArrayCache extends Cache +{ + private $_cache; + + + /** + * @inheritdoc + */ + public function exists($key) + { + $key = $this->buildKey($key); + return isset($this->_cache[$key]) && ($this->_cache[$key][1] === 0 || $this->_cache[$key][1] > microtime(true)); + } + + /** + * @inheritdoc + */ + protected function getValue($key) + { + if (isset($this->_cache[$key]) && ($this->_cache[$key][1] === 0 || $this->_cache[$key][1] > microtime(true))) { + return $this->_cache[$key][0]; + } else { + return false; + } + } + + /** + * @inheritdoc + */ + protected function setValue($key, $value, $duration) + { + $this->_cache[$key] = [$value, $duration === 0 ? 0 : microtime(true) + $duration]; + return true; + } + + /** + * @inheritdoc + */ + protected function addValue($key, $value, $duration) + { + if (isset($this->_cache[$key]) && ($this->_cache[$key][1] === 0 || $this->_cache[$key][1] > microtime(true))) { + return false; + } else { + $this->_cache[$key] = [$value, $duration === 0 ? 0 : microtime(true) + $duration]; + return true; + } + } + + /** + * @inheritdoc + */ + protected function deleteValue($key) + { + unset($this->_cache[$key]); + return true; + } + + /** + * @inheritdoc + */ + protected function flushValues() + { + $this->_cache = []; + return true; + } +} diff --git a/tests/unit/framework/caching/ArrayCacheTest.php b/tests/unit/framework/caching/ArrayCacheTest.php new file mode 100644 index 0000000000..5ec7d731b9 --- /dev/null +++ b/tests/unit/framework/caching/ArrayCacheTest.php @@ -0,0 +1,49 @@ +_cacheInstance === null) { + $this->_cacheInstance = new ArrayCache(); + } + return $this->_cacheInstance; + } + + public function testExpire() + { + $cache = $this->getCacheInstance(); + + static::$microtime = \microtime(true); + $this->assertTrue($cache->set('expire_test', 'expire_test', 2)); + static::$microtime++; + $this->assertEquals('expire_test', $cache->get('expire_test')); + static::$microtime++; + $this->assertFalse($cache->get('expire_test')); + } + + public function testExpireAdd() + { + $cache = $this->getCacheInstance(); + + static::$microtime = \microtime(true); + $this->assertTrue($cache->add('expire_testa', 'expire_testa', 2)); + static::$microtime++; + $this->assertEquals('expire_testa', $cache->get('expire_testa')); + static::$microtime++; + $this->assertFalse($cache->get('expire_testa')); + } +} diff --git a/tests/unit/framework/caching/CacheTestCase.php b/tests/unit/framework/caching/CacheTestCase.php index 40c7705308..f31d6004ff 100644 --- a/tests/unit/framework/caching/CacheTestCase.php +++ b/tests/unit/framework/caching/CacheTestCase.php @@ -11,8 +11,19 @@ function time() return \yiiunit\framework\caching\CacheTestCase::$time ?: \time(); } +/** + * Mock for the microtime() function for caching classes + * @param bool $float + * @return float + */ +function microtime($float = false) +{ + return \yiiunit\framework\caching\CacheTestCase::$microtime ?: \microtime($float); +} + namespace yiiunit\framework\caching; +use yii\caching\Cache; use yiiunit\TestCase; /** @@ -25,6 +36,12 @@ abstract class CacheTestCase extends TestCase * Null means normal time() behavior. */ public static $time; + /** + * @var float virtual time to be returned by mocked microtime() function. + * Null means normal microtime() behavior. + */ + public static $microtime; + /** * @return Cache @@ -40,6 +57,7 @@ abstract class CacheTestCase extends TestCase protected function tearDown() { static::$time = null; + static::$microtime = null; } /** From 1070344278e512cded015e30ad50c19c6781d76e Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Tue, 12 Aug 2014 01:58:37 +0400 Subject: [PATCH 20/41] Adjusted data column docs --- docs/guide/output-data-widgets.md | 78 +++++++++++++++---------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/docs/guide/output-data-widgets.md b/docs/guide/output-data-widgets.md index 1b01668f45..f3370dcb4b 100644 --- a/docs/guide/output-data-widgets.md +++ b/docs/guide/output-data-widgets.md @@ -140,52 +140,52 @@ You may specify various container HTML options passing arrays to: Data column is for displaying and sorting data. It is default column type so specifying class could be omitted when using it. - -**Attribute `format`.** -Data column uses component [[\yii\base\Formatter|Formatter]] of application for own attribute `format` by default. `Formatter` is configured as an application component in [[\yii\base\Application]] by default. You can access that instance via `Yii::$app->formatter` also. - -It can be set as a string or an array for Data column: +The main setting of the data column is its format. It could be specified via `format` attribute. Its values are +corresponding to methods in `format` application component that is [[\yii\base\Formatter|Formatter]] by default: ```php - [ - [ - 'format' => 'text', - ], - // or - [ - 'format' => ['text', []], - ], + [ + [ + 'attribute' => 'name', + 'format' => 'text' ], - ] -); ?> + [ + 'attribute' => 'birthday', + 'format' => ['date', 'Y-m-d'] + ], + ], +]); ?> ``` -By default format supports formats such as methods in a class [[\yii\base\Formatter]] that begins with "as ..". -For type "xyz", the method "asXyz" will be used. For example, if the format is "html", then [[\yii\base\Formatter::asHtml()]] will be used. Format names are case insensitive. -You may specify various format options passing arrays to: +In the above `text` corresponds to [[\yii\base\Formatter::asText()]]. The value of the column is passed as the first +argument. In the second column definition `date` corresponds to [[\yii\base\Formatter::asDate()]]. The value of the +column is, again, passed as the first argument while 'Y-m-d' is used as the second argument value. -- [[\yii\base\Formatter::asRaw()|raw]] - Raw Formats the value as is without any formatting. -- [[\yii\base\Formatter::asText()|text]] - Formats the value as an HTML-encoded plain text. Is used by default. -- [[\yii\base\Formatter::asNtext()|ntext]] - Formats the value as an HTML-encoded plain text with newlines converted into breaks. -- [[\yii\base\Formatter::asParagraphs()|paragraphs]] - Formats the value as HTML-encoded text paragraphs. -- [[\yii\base\Formatter::asHtml()|html]] - The value will be purified using [[HtmlPurifier]] to avoid XSS attacks. You can use `['html', ['Attr.AllowedFrameTargets' => ['_blank']]]` -- [[\yii\base\Formatter::asEmail()|email]] - Formats the value as a mailto link. -- [[\yii\base\Formatter::asImage()|image]] - Formats the value as an image tag. -- [[\yii\base\Formatter::asUrl()|url]] - Formats the value as a hyperlink. -- [[\yii\base\Formatter::asBoolean()|boolean]] - Formats the value as a boolean. Use `Yii::$app->formatter->booleanFormat = ['Nooooo','It is true'];` before GridView for custom values. -- [[\yii\base\Formatter::asDate()|date]] - Formats the value as a date. -- [[\yii\base\Formatter::asTime()|time]] - Formats the value as a time. -- [[\yii\base\Formatter::asDatetime()|datetime]] - Formats the value as a datetime. -- [[\yii\base\Formatter::asInteger()|integer]] - Formats the value as an integer. -- [[\yii\base\Formatter::asDouble()|double]] - Formats the value as a double number. -- [[\yii\base\Formatter::asNumber()|number]] - Formats the value as a number with decimal and thousand separators. -- [[\yii\base\Formatter::asSize()|size]] - Formats the value in bytes as a size in human readable form. -- [[\yii\base\Formatter::asRelativeTime()|relativeTime]] - Formats the value as the time interval between a date and now in human readable form. +Here's the bundled formatters list: -TBD +- [[\yii\base\Formatter::asRaw()|raw]] - the value is outputted as is. +- [[\yii\base\Formatter::asText()|text]] - the value is HTML-encoded. This format is used by default. +- [[\yii\base\Formatter::asNtext()|ntext]] - the value is formatted as an HTML-encoded plain text with newlines converted + into line breaks. +- [[\yii\base\Formatter::asParagraphs()|paragraphs]] - the value is formatted as HTML-encoded text paragraphs wrapped + into `

` tags. +- [[\yii\base\Formatter::asHtml()|html]] - the value is purified using [[HtmlPurifier]] to avoid XSS attacks. You can + pass additional options such as `['html', ['Attr.AllowedFrameTargets' => ['_blank']]]`. +- [[\yii\base\Formatter::asEmail()|email]] - the value is formatted as a mailto link. +- [[\yii\base\Formatter::asImage()|image]] - the value is formatted as an image tag. +- [[\yii\base\Formatter::asUrl()|url]] - the value is formatted as a hyperlink. +- [[\yii\base\Formatter::asBoolean()|boolean]] - the value is formatted as a boolean. You can set what's rendered for + true and false values by calling `Yii::$app->formatter->booleanFormat = ['No', 'Yes'];` before outputting GridView. +- [[\yii\base\Formatter::asDate()|date]] - the value is formatted as date. +- [[\yii\base\Formatter::asTime()|time]] - the value is formatted as time. +- [[\yii\base\Formatter::asDatetime()|datetime]] - the value is formatted as datetime. +- [[\yii\base\Formatter::asInteger()|integer]] - the value is formatted as an integer. +- [[\yii\base\Formatter::asDouble()|double]] - the value is formatted as a double number. +- [[\yii\base\Formatter::asNumber()|number]] - the value is formatted as a number with decimal and thousand separators. +- [[\yii\base\Formatter::asSize()|size]] - the value that is a number of bytes is formatted as a human readable size. +- [[\yii\base\Formatter::asRelativeTime()|relativeTime]] - the value is formatted as the time interval between a date + and now in human readable form. #### Action column From 7728274ce6973f5b2a334e685ab0559622a111f9 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Tue, 12 Aug 2014 02:13:34 +0400 Subject: [PATCH 21/41] Fixes #4122: `Html::error()` and `Html::errorSummary()` are now accepting `encode` option. If set to false it prevents encoding of error messages --- framework/CHANGELOG.md | 1 + framework/helpers/BaseHtml.php | 18 +++++++++++------- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index a07e2dbcde..3aeb638603 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -158,6 +158,7 @@ Yii Framework 2 Change Log - Enh #4080: Added proper handling and support of the symlinked directories in `FileHelper`, added $options parameter in `FileHelper::removeDirectory()` (resurtm) - Enh #4086: changedAttributes of afterSave Event now contain old values (dizews) - Enh #4114: Added `Security::generateRandomBytes()`, improved tests (samdark) +- Enh #4122: `Html::error()` and `Html::errorSummary()` are now accepting `encode` option. If set to false it prevents encoding of error messages (samdark) - Enh #4131: Security adjustments (tom--) - Added HKDF to `yii\base\Security`. - Reverted auto fallback to PHP PBKDF2. diff --git a/framework/helpers/BaseHtml.php b/framework/helpers/BaseHtml.php index 87ef5dd95c..8293b7f6e1 100644 --- a/framework/helpers/BaseHtml.php +++ b/framework/helpers/BaseHtml.php @@ -1067,6 +1067,7 @@ class BaseHtml * * - header: string, the header HTML for the error summary. If not set, a default prompt string will be used. * - footer: string, the footer HTML for the error summary. + * - encode: boolean, if set to false then value won't be encoded. * * The rest of the options will be rendered as the attributes of the container tag. The values will * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. @@ -1074,6 +1075,11 @@ class BaseHtml */ public static function errorSummary($models, $options = []) { + $header = isset($options['header']) ? $options['header'] : '

' . Yii::t('yii', 'Please fix the following errors:') . '

'; + $footer = isset($options['footer']) ? $options['footer'] : ''; + $encode = !isset($options['encode']) || $options['encode'] !== false; + unset($options['header'], $options['footer'], $options['encode']); + $lines = []; if (!is_array($models)) { $models = [$models]; @@ -1081,14 +1087,10 @@ class BaseHtml foreach ($models as $model) { /* @var $model Model */ foreach ($model->getFirstErrors() as $error) { - $lines[] = Html::encode($error); + $lines[] = $encode ? Html::encode($error) : $error; } } - $header = isset($options['header']) ? $options['header'] : '

' . Yii::t('yii', 'Please fix the following errors:') . '

'; - $footer = isset($options['footer']) ? $options['footer'] : ''; - unset($options['header'], $options['footer']); - if (empty($lines)) { // still render the placeholder for client-side validation use $content = "
    "; @@ -1111,6 +1113,7 @@ class BaseHtml * The following options are specially handled: * * - tag: this specifies the tag name. If not set, "div" will be used. + * - encode: boolean, if set to false then value won't be encoded. * * See [[renderTagAttributes()]] for details on how attributes are being rendered. * @@ -1121,8 +1124,9 @@ class BaseHtml $attribute = static::getAttributeName($attribute); $error = $model->getFirstError($attribute); $tag = isset($options['tag']) ? $options['tag'] : 'div'; - unset($options['tag']); - return Html::tag($tag, Html::encode($error), $options); + $encode = !isset($options['encode']) || $options['encode'] !== false; + unset($options['tag'], $options['encode']); + return Html::tag($tag, $encode ? Html::encode($error) : $error, $options); } /** From 9b6f84ca58e0375e15cc6a3711977c2b63cde915 Mon Sep 17 00:00:00 2001 From: Alex-Code Date: Fri, 8 Aug 2014 15:15:26 +0100 Subject: [PATCH 22/41] BaseHtmlPurifier config can now be a closure `$config` can now be a closure that will have the `$configInstance` passed as the first param. updated docs. close #4656 --- framework/helpers/BaseHtmlPurifier.php | 30 ++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/framework/helpers/BaseHtmlPurifier.php b/framework/helpers/BaseHtmlPurifier.php index 6f5b4509c8..30d8ead706 100644 --- a/framework/helpers/BaseHtmlPurifier.php +++ b/framework/helpers/BaseHtmlPurifier.php @@ -19,17 +19,39 @@ class BaseHtmlPurifier { /** * Passes markup through HTMLPurifier making it safe to output to end user + * + * @param string $content The HTML content to purify + * @param array|\Closure|null $config The config to use for HtmlPurifier. + * If not specified or `null` the default config will be used. + * You can use an array or an anonymous function to provide configuration options: * - * @param string $content - * @param array|null $config - * @return string + * - An array will be passed to the `HTMLPurifier_Config::create()` method. + * - An anonymous function will be called after the config was created. + * The signature should be: `function($config)` where `$config` will be an + * instance of `HTMLPurifier_Config`. + * + * Here is a usage example of such a function: + * + * ~~~ + * // Allow the HTML5 data attribute `data-type` on `img` elements. + * $content = HtmlPurifier::process($content, function($config) { + * $config->getHTMLDefinition(true) + * ->addAttribute('img', 'data-type', 'Text'); + * }); + * ~~~ + * + * @return string the purified HTML content. */ public static function process($content, $config = null) { - $configInstance = \HTMLPurifier_Config::create($config); + $configInstance = \HTMLPurifier_Config::create($config instanceof \Closure ? null : $config); $configInstance->autoFinalize = false; $purifier=\HTMLPurifier::instance($configInstance); $purifier->config->set('Cache.SerializerPath', \Yii::$app->getRuntimePath()); + + if ($config instanceof \Closure) { + call_user_func($config, $configInstance); + } return $purifier->purify($content); } From 70e0115d5a03a60a14db4ce7a6a36c3ae2aeeaad Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Tue, 12 Aug 2014 00:45:15 +0200 Subject: [PATCH 23/41] changelog for #4656 --- framework/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 3aeb638603..b4e503547b 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -176,6 +176,7 @@ Yii Framework 2 Change Log - Enh #4581: Added ability to disable url encoding in `UrlRule` (tadaszelvys) - Enh #4602: Added $key param in ActionColumn buttons Closure call (disem) - Enh #4644: Added `\yii\db\Schema::createColumnSchema()` to be able to customize column schema used (mcd-php) +- Enh #4656: HtmlPufifier helper config can now be a closure to change the purifier config object after it was created (Alex-Code) - Enh: Added support for using sub-queries when building a DB query with `IN` condition (qiangxue) - Enh: Supported adding a new response formatter without the need to reconfigure existing formatters (qiangxue) - Enh: Added `yii\web\UrlManager::addRules()` to simplify adding new URL rules (qiangxue) From f1dd83e2a9d042ea78311d981d69133e397e5be2 Mon Sep 17 00:00:00 2001 From: Nikitin Vitaly Date: Fri, 8 Aug 2014 16:50:28 +0400 Subject: [PATCH 24/41] Added test that are making sure batchInsert boolean values in PostgreSQL isn't failing with errors close #4655 --- tests/unit/data/postgres.sql | 6 ++++++ .../framework/db/pgsql/PostgreSQLCommandTest.php | 14 ++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/tests/unit/data/postgres.sql b/tests/unit/data/postgres.sql index c5656f2ad0..5988fe7e8a 100644 --- a/tests/unit/data/postgres.sql +++ b/tests/unit/data/postgres.sql @@ -16,6 +16,7 @@ DROP TABLE IF EXISTS "profile" CASCADE; DROP TABLE IF EXISTS "type" CASCADE; DROP TABLE IF EXISTS "null_values" CASCADE; DROP TABLE IF EXISTS "constraints" CASCADE; +DROP TABLE IF EXISTS "bool_values" CASCADE; CREATE TABLE "constraints" ( @@ -113,6 +114,11 @@ CREATE TABLE "type" ( bit_col BIT(8) NOT NULL DEFAULT B'10000010' ); +CREATE TABLE "bool_values" ( + id serial not null primary key, + bool_col bool +); + INSERT INTO "profile" (description) VALUES ('profile customer 1'); INSERT INTO "profile" (description) VALUES ('profile customer 3'); diff --git a/tests/unit/framework/db/pgsql/PostgreSQLCommandTest.php b/tests/unit/framework/db/pgsql/PostgreSQLCommandTest.php index b810a0a770..037f21b77a 100644 --- a/tests/unit/framework/db/pgsql/PostgreSQLCommandTest.php +++ b/tests/unit/framework/db/pgsql/PostgreSQLCommandTest.php @@ -19,4 +19,18 @@ class PostgreSQLCommandTest extends CommandTest $command = $db->createCommand($sql); $this->assertEquals('SELECT "id", "t"."name" FROM "customer" t', $command->sql); } + + public function testBatchInsert() + { + parent::testBatchInsert(); + + $command = $this->getConnection()->createCommand(); + $command->batchInsert('bool_values', + ['bool_col'], [ + [true], + [false], + ] + ); + $this->assertEquals(2, $command->execute()); + } } \ No newline at end of file From 92d65ab78b0a96a84df1c873a970a5f8c20c808c Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Tue, 12 Aug 2014 01:42:08 +0200 Subject: [PATCH 25/41] fix issue with postgreSQL and batch inserting boolean values fixes #4654 --- framework/CHANGELOG.md | 1 + framework/db/Schema.php | 4 +- framework/db/pgsql/QueryBuilder.php | 40 +++++++++++++++++++ .../db/pgsql/PostgreSQLCommandTest.php | 27 +++++++++++-- 4 files changed, 68 insertions(+), 4 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index b4e503547b..28cf55bd5c 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -80,6 +80,7 @@ Yii Framework 2 Change Log - Bug #4514: Fixed Request class crashing when empty CSRF token value is sent in cookie (cebe) - Bug #4519: `yii\base\Model::isAttributeRequired()` should check if the `when` option of the validator is set (thiagotalma) - Bug #4592: Fixed `yii help` command was listing incorrect action names for methods like `actionSayNO` (samdark) +- Bug #4654: Fixed issue with PostgreSQL and inserting boolean values with batch insert (cebe) - Bug: Fixed inconsistent return of `\yii\console\Application::runAction()` (samdark) - Bug: URL encoding for the route parameter added to `\yii\web\UrlManager` (klimov-paul) - Bug: Fixed the bug that requesting protected or private action methods would cause 500 error instead of 404 (qiangxue) diff --git a/framework/db/Schema.php b/framework/db/Schema.php index 5d0083e497..bed969a65a 100644 --- a/framework/db/Schema.php +++ b/framework/db/Schema.php @@ -84,11 +84,13 @@ abstract class Schema extends Object */ private $_builder; + /** * @return \yii\db\ColumnSchema * @throws \yii\base\InvalidConfigException */ - protected function createColumnSchema() { + protected function createColumnSchema() + { return Yii::createObject('yii\db\ColumnSchema'); } diff --git a/framework/db/pgsql/QueryBuilder.php b/framework/db/pgsql/QueryBuilder.php index 33ce912563..8673d737e2 100644 --- a/framework/db/pgsql/QueryBuilder.php +++ b/framework/db/pgsql/QueryBuilder.php @@ -159,4 +159,44 @@ class QueryBuilder extends \yii\db\QueryBuilder . $this->db->quoteColumnName($column) . ' TYPE ' . $this->getColumnType($type); } + + /** + * @inheritdoc + */ + public function batchInsert($table, $columns, $rows) + { + if (($tableSchema = $this->db->getTableSchema($table)) !== null) { + $columnSchemas = $tableSchema->columns; + } else { + $columnSchemas = []; + } + + $values = []; + foreach ($rows as $row) { + $vs = []; + foreach ($row as $i => $value) { + if (!is_array($value) && isset($columnSchemas[$columns[$i]])) { + $value = $columnSchemas[$columns[$i]]->dbTypecast($value); + } + if (is_string($value)) { + $value = $this->db->quoteValue($value); + } elseif ($value === true) { + $value = 'TRUE'; + } elseif ($value === false) { + $value = 'FALSE'; + } elseif ($value === null) { + $value = 'NULL'; + } + $vs[] = $value; + } + $values[] = '(' . implode(', ', $vs) . ')'; + } + + foreach ($columns as $i => $name) { + $columns[$i] = $this->db->quoteColumnName($name); + } + + return 'INSERT INTO ' . $this->db->quoteTableName($table) + . ' (' . implode(', ', $columns) . ') VALUES ' . implode(', ', $values); + } } diff --git a/tests/unit/framework/db/pgsql/PostgreSQLCommandTest.php b/tests/unit/framework/db/pgsql/PostgreSQLCommandTest.php index 037f21b77a..1382bd7d6e 100644 --- a/tests/unit/framework/db/pgsql/PostgreSQLCommandTest.php +++ b/tests/unit/framework/db/pgsql/PostgreSQLCommandTest.php @@ -20,11 +20,27 @@ class PostgreSQLCommandTest extends CommandTest $this->assertEquals('SELECT "id", "t"."name" FROM "customer" t', $command->sql); } - public function testBatchInsert() + public function testBooleanValuesInsert() { - parent::testBatchInsert(); + $db = $this->getConnection(); + $command = $db->createCommand(); + $command->insert('bool_values', ['bool_col' => true]); + $this->assertEquals(1, $command->execute()); - $command = $this->getConnection()->createCommand(); + $command = $db->createCommand(); + $command->insert('bool_values', ['bool_col' => false]); + $this->assertEquals(1, $command->execute()); + + $command = $db->createCommand('SELECT COUNT(*) FROM "bool_values" WHERE bool_col = TRUE;'); + $this->assertEquals(1, $command->queryScalar()); + $command = $db->createCommand('SELECT COUNT(*) FROM "bool_values" WHERE bool_col = FALSE;'); + $this->assertEquals(1, $command->queryScalar()); + } + + public function testBooleanValuesBatchInsert() + { + $db = $this->getConnection(); + $command = $db->createCommand(); $command->batchInsert('bool_values', ['bool_col'], [ [true], @@ -32,5 +48,10 @@ class PostgreSQLCommandTest extends CommandTest ] ); $this->assertEquals(2, $command->execute()); + + $command = $db->createCommand('SELECT COUNT(*) FROM "bool_values" WHERE bool_col = TRUE;'); + $this->assertEquals(1, $command->queryScalar()); + $command = $db->createCommand('SELECT COUNT(*) FROM "bool_values" WHERE bool_col = FALSE;'); + $this->assertEquals(1, $command->queryScalar()); } } \ No newline at end of file From c6274acf6cfd52c55948fa6296bd8e4d69840b8e Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Tue, 12 Aug 2014 02:15:57 +0200 Subject: [PATCH 26/41] ensure postgres boolean values are handled correctly also fixed an issue with default value loading of bool columns. fixes #3489, fixes #4085, fixes #3920 related to #4672 --- framework/CHANGELOG.md | 1 + framework/db/pgsql/Schema.php | 2 + tests/unit/data/postgres.sql | 4 +- .../db/pgsql/PostgreSQLActiveRecordTest.php | 50 +++++++++++++++++++ .../db/pgsql/PostgreSQLQueryTest.php | 24 +++++++++ .../db/pgsql/PostgreSQLSchemaTest.php | 10 ++++ 6 files changed, 90 insertions(+), 1 deletion(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 28cf55bd5c..cce3026d14 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -57,6 +57,7 @@ Yii Framework 2 Change Log - Bug #3863: Fixed incorrect js selector for `\yii\widgets\ActiveForm::errorSummaryCssClass` when it contains multiple classes (creocoder, umneeq) - Bug #3893: Headers did not overwrite default setting by webserver (cebe) - Bug #3909: `Html::to()` should not prefix base URL to URLs that already contain scheme (qiangxue) +- Bug #3920: Fixed issue with loading default values of PostgreSQL boolean columns (cebe) - Bug #3934: yii.handleAction() in yii.js does not correctly detect if a hyperlink contains useful URL or not (joni-jones, qiangxue) - Bug #3968: Messages logged in shutdown functions are not handled (qiangxue) - Bug #3989: Fixed yii\log\FileTarget::$rotateByCopy to avoid any rename (cebe) diff --git a/framework/db/pgsql/Schema.php b/framework/db/pgsql/Schema.php index 2d546a2d62..15f9b08fff 100644 --- a/framework/db/pgsql/Schema.php +++ b/framework/db/pgsql/Schema.php @@ -412,6 +412,8 @@ SQL; } elseif ($column->defaultValue) { if ($column->type === 'timestamp' && $column->defaultValue === 'now()') { $column->defaultValue = new Expression($column->defaultValue); + } elseif ($column->type === 'boolean') { + $column->defaultValue = ($column->defaultValue === 'true'); } elseif (stripos($column->dbType, 'bit') === 0 || stripos($column->dbType, 'varbit') === 0) { $column->defaultValue = bindec(trim($column->defaultValue, 'B\'')); } elseif (preg_match("/^'(.*?)'::/", $column->defaultValue, $matches)) { diff --git a/tests/unit/data/postgres.sql b/tests/unit/data/postgres.sql index 5988fe7e8a..73147df9c7 100644 --- a/tests/unit/data/postgres.sql +++ b/tests/unit/data/postgres.sql @@ -116,7 +116,9 @@ CREATE TABLE "type" ( CREATE TABLE "bool_values" ( id serial not null primary key, - bool_col bool + bool_col bool, + default_true bool not null default true, + default_false boolean not null default false ); INSERT INTO "profile" (description) VALUES ('profile customer 1'); diff --git a/tests/unit/framework/db/pgsql/PostgreSQLActiveRecordTest.php b/tests/unit/framework/db/pgsql/PostgreSQLActiveRecordTest.php index 1b2a687b2a..a4cb39ed18 100644 --- a/tests/unit/framework/db/pgsql/PostgreSQLActiveRecordTest.php +++ b/tests/unit/framework/db/pgsql/PostgreSQLActiveRecordTest.php @@ -2,6 +2,7 @@ namespace yiiunit\framework\db\pgsql; +use yiiunit\data\ar\ActiveRecord; use yiiunit\framework\db\ActiveRecordTest; /** @@ -11,4 +12,53 @@ use yiiunit\framework\db\ActiveRecordTest; class PostgreSQLActiveRecordTest extends ActiveRecordTest { protected $driverName = 'pgsql'; + + + public function testBooleanValues() + { + $db = $this->getConnection(); + $command = $db->createCommand(); + $command->batchInsert('bool_values', + ['bool_col'], [ + [true], + [false], + ] + )->execute(); + + $this->assertEquals(1, BoolAR::find()->where('bool_col = TRUE')->count('*', $db)); + $this->assertEquals(1, BoolAR::find()->where('bool_col = FALSE')->count('*', $db)); + $this->assertEquals(2, BoolAR::find()->where('bool_col IN (TRUE, FALSE)')->count('*', $db)); + + $this->assertEquals(1, BoolAR::find()->where(['bool_col' => true])->count('*', $db)); + $this->assertEquals(1, BoolAR::find()->where(['bool_col' => false])->count('*', $db)); + $this->assertEquals(2, BoolAR::find()->where(['bool_col' => [true, false]])->count('*', $db)); + + $this->assertEquals(1, BoolAR::find()->where('bool_col = :bool_col', ['bool_col' => true])->count('*', $db)); + $this->assertEquals(1, BoolAR::find()->where('bool_col = :bool_col', ['bool_col' => false])->count('*', $db)); + + $this->assertSame(true, BoolAR::find()->where(['bool_col' => true])->one($db)->bool_col); + $this->assertSame(false, BoolAR::find()->where(['bool_col' => false])->one($db)->bool_col); + } + + public function testBooleanDefaultValues() + { + $model = new BoolAR(); + $this->assertNull($model->bool_col); + $this->assertNull($model->default_true); + $this->assertNull($model->default_false); + $model->loadDefaultValues(); + $this->assertNull($model->bool_col); + $this->assertSame(true, $model->default_true); + $this->assertSame(false, $model->default_false); + + $this->assertTrue($model->save(false)); + } } + +class BoolAR extends ActiveRecord +{ + public static function tableName() + { + return 'bool_values'; + } +} \ No newline at end of file diff --git a/tests/unit/framework/db/pgsql/PostgreSQLQueryTest.php b/tests/unit/framework/db/pgsql/PostgreSQLQueryTest.php index fbb8449591..af2c383d96 100644 --- a/tests/unit/framework/db/pgsql/PostgreSQLQueryTest.php +++ b/tests/unit/framework/db/pgsql/PostgreSQLQueryTest.php @@ -3,6 +3,7 @@ namespace yiiunit\framework\db\pgsql; use yii\db\pgsql\Schema; +use yii\db\Query; use yiiunit\framework\db\QueryTest; use yiiunit\framework\db\SchemaTest; @@ -13,4 +14,27 @@ use yiiunit\framework\db\SchemaTest; class PostgreSQLQueryTest extends QueryTest { public $driverName = 'pgsql'; + + public function testBooleanValues() + { + $db = $this->getConnection(); + $command = $db->createCommand(); + $command->batchInsert('bool_values', + ['bool_col'], [ + [true], + [false], + ] + )->execute(); + + $this->assertEquals(1, (new Query())->from('bool_values')->where('bool_col = TRUE')->count('*', $db)); + $this->assertEquals(1, (new Query())->from('bool_values')->where('bool_col = FALSE')->count('*', $db)); + $this->assertEquals(2, (new Query())->from('bool_values')->where('bool_col IN (TRUE, FALSE)')->count('*', $db)); + + $this->assertEquals(1, (new Query())->from('bool_values')->where(['bool_col' => true])->count('*', $db)); + $this->assertEquals(1, (new Query())->from('bool_values')->where(['bool_col' => false])->count('*', $db)); + $this->assertEquals(2, (new Query())->from('bool_values')->where(['bool_col' => [true, false]])->count('*', $db)); + + $this->assertEquals(1, (new Query())->from('bool_values')->where('bool_col = :bool_col', ['bool_col' => true])->count('*', $db)); + $this->assertEquals(1, (new Query())->from('bool_values')->where('bool_col = :bool_col', ['bool_col' => false])->count('*', $db)); + } } diff --git a/tests/unit/framework/db/pgsql/PostgreSQLSchemaTest.php b/tests/unit/framework/db/pgsql/PostgreSQLSchemaTest.php index a93027aad2..1f154eec66 100644 --- a/tests/unit/framework/db/pgsql/PostgreSQLSchemaTest.php +++ b/tests/unit/framework/db/pgsql/PostgreSQLSchemaTest.php @@ -80,4 +80,14 @@ class PostgreSQLSchemaTest extends SchemaTest } fclose($fp); } + + public function testBooleanDefaultValues() + { + /* @var $schema Schema */ + $schema = $this->getConnection()->schema; + + $table = $schema->getTableSchema('bool_values'); + $this->assertSame(true, $table->getColumn('default_true')->defaultValue); + $this->assertSame(false, $table->getColumn('default_false')->defaultValue); + } } From 19b98948e43f8c95e672f75a43a3bc65fa5edcd4 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Mon, 11 Aug 2014 21:42:09 -0400 Subject: [PATCH 27/41] Use 0666 to set files as writable. --- extensions/composer/Installer.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/extensions/composer/Installer.php b/extensions/composer/Installer.php index 368206b725..3ac378d0ac 100644 --- a/extensions/composer/Installer.php +++ b/extensions/composer/Installer.php @@ -238,11 +238,10 @@ EOF foreach ((array) $options[self::EXTRA_WRITABLE] as $path) { echo "Setting writable: $path ..."; if (is_dir($path) || is_file($path)) { - chmod($path, 0777); + chmod($path, is_file($path) ? 0666 : 0777); echo "done\n"; } else { echo "The directory or file was not found: " . getcwd() . DIRECTORY_SEPARATOR . $path; - return; } } @@ -254,7 +253,6 @@ EOF echo "done\n"; } else { echo "\n\tThe directory or file was not found: " . getcwd() . DIRECTORY_SEPARATOR . $path . "\n"; - return; } } From 5fe6c55d6b890df3014987e1cc5e5d89f26833cf Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Mon, 11 Aug 2014 22:09:51 -0400 Subject: [PATCH 28/41] Fixes #4586: Signed bigint and unsigned int will be converted into integers when they are loaded from DB by AR --- framework/CHANGELOG.md | 1 + framework/db/Schema.php | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index cceea127bb..53ecda7351 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -225,6 +225,7 @@ Yii Framework 2 Change Log - Chg #4310: Removed `$data` from signature of `yii\rbac\ManagerInterface` (samdark) - Chg #4318: `yii\helpers\Html::ul()` and `ol()` will return an empty list tag if an empty item array is given (qiangxue) - Chg #4331: `yii\helpers\Url` now uses `UrlManager` to determine base URL when generating URLs (qiangxue) +- Chg #4586: Signed bigint and unsigned int will be converted into integers when they are loaded from DB by AR (qiangxue) - Chg #4591: `yii\helpers\Url::to()` will no longer prefix relative URLs with the base URL (qiangxue) - Chg #4595: `yii\widgets\LinkPager`'s `nextPageLabel`, `prevPageLabel`, `firstPageLabel`, `lastPageLabel` are now taking `false` instead of `null` for "no label" (samdark) - Chg: Replaced `clearAll()` and `clearAllAssignments()` in `yii\rbac\ManagerInterface` with `removeAll()`, `removeAllRoles()`, `removeAllPermissions()`, `removeAllRules()` and `removeAllAssignments()` (qiangxue) diff --git a/framework/db/Schema.php b/framework/db/Schema.php index bed969a65a..6760397d79 100644 --- a/framework/db/Schema.php +++ b/framework/db/Schema.php @@ -490,13 +490,16 @@ abstract class Schema extends Object // abstract type => php type 'smallint' => 'integer', 'integer' => 'integer', + 'bigint' => 'integer', 'boolean' => 'boolean', 'float' => 'double', 'binary' => 'resource', ]; if (isset($typeMap[$column->type])) { - if ($column->type === 'integer') { - return $column->unsigned ? 'string' : 'integer'; + if ($column->type === 'bigint') { + return PHP_INT_SIZE == 8 && !$column->unsigned ? 'integer' : 'string'; + } elseif ($column->type === 'integer') { + return PHP_INT_SIZE == 4 && $column->unsigned ? 'string' : 'integer'; } else { return $typeMap[$column->type]; } From 9f155de82d4d49e7de38950f43017cb79b6d1cd8 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Mon, 11 Aug 2014 23:30:43 -0400 Subject: [PATCH 29/41] removed the key from the array in the fix for #4371 --- framework/widgets/ActiveField.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/widgets/ActiveField.php b/framework/widgets/ActiveField.php index 565d2223e5..ffd0174e6e 100644 --- a/framework/widgets/ActiveField.php +++ b/framework/widgets/ActiveField.php @@ -195,7 +195,7 @@ class ActiveField extends Component { $clientOptions = $this->getClientOptions(); if (!empty($clientOptions)) { - $this->form->attributes[Html::getInputId($this->model, $this->attribute)] = $clientOptions; + $this->form->attributes[] = $clientOptions; } $inputID = Html::getInputId($this->model, $this->attribute); From 701991f06c6beb062d9ae8d05169ddbc4f6c9518 Mon Sep 17 00:00:00 2001 From: Vadim Belorussov Date: Tue, 12 Aug 2014 11:02:04 +0500 Subject: [PATCH 30/41] Add rest-authentication.md to translate into Russian --- docs/guide-ru/rest-authentication.md | 124 +++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 docs/guide-ru/rest-authentication.md diff --git a/docs/guide-ru/rest-authentication.md b/docs/guide-ru/rest-authentication.md new file mode 100644 index 0000000000..45d13ba924 --- /dev/null +++ b/docs/guide-ru/rest-authentication.md @@ -0,0 +1,124 @@ +Authentication +============== + +Unlike Web applications, RESTful APIs are usually stateless, which means sessions or cookies should not +be used. Therefore, each request should come with some sort of authentication credentials because +the user authentication status may not be maintained by sessions or cookies. A common practice is +to send a secret access token with each request to authenticate the user. Since an access token +can be used to uniquely identify and authenticate a user, **API requests should always be sent +via HTTPS to prevent from man-in-the-middle (MitM) attacks**. + +There are different ways to send an access token: + +* [HTTP Basic Auth](http://en.wikipedia.org/wiki/Basic_access_authentication): the access token + is sent as the username. This is should only be used when an access token can be safely stored + on the API consumer side. For example, the API consumer is a program running on a server. +* Query parameter: the access token is sent as a query parameter in the API URL, e.g., + `https://example.com/users?access-token=xxxxxxxx`. Because most Web servers will keep query + parameters in server logs, this approach should be mainly used to serve `JSONP` requests which + cannot use HTTP headers to send access tokens. +* [OAuth 2](http://oauth.net/2/): the access token is obtained by the consumer from an authorization + server and sent to the API server via [HTTP Bearer Tokens](http://tools.ietf.org/html/rfc6750), + according to the OAuth2 protocol. + +Yii supports all of the above authentication methods. You can also easily create new authentication methods. + +To enable authentication for your APIs, do the following steps: + +1. Configure the [[yii\web\User::enableSession|enableSession]] property of the `user` application component to be false. +2. Specify which authentication methods you plan to use by configuring the `authenticator` behavior + in your REST controller classes. +3. Implement [[yii\web\IdentityInterface::findIdentityByAccessToken()]] in your [[yii\web\User::identityClass|user identity class]]. + +Step 1 is not required but is recommended for RESTful APIs which should be stateless. When [[yii\web\User::enableSession|enableSession]] +is false, the user authentication status will NOT be persisted across requests using sessions. Instead, authentication +will be performed for every request, which is accomplished by Step 2 and 3. + +> Tip: You may configure [[yii\web\User::enableSession|enableSession]] of the `user` application component + in application configurations if you are developing RESTful APIs in terms of an application. If you develop + RESTful APIs as a module, you may put the following line in the module's `init()` method, like the following: +> ```php +public function init() +{ + parent::init(); + \Yii::$app->user->enableSession = false; +} +``` + +For example, to use HTTP Basic Auth, you may configure `authenticator` as follows, + +```php +use yii\filters\auth\HttpBasicAuth; + +public function behaviors() +{ + $behaviors = parent::behaviors(); + $behaviors['authenticator'] = [ + 'class' => HttpBasicAuth::className(), + ]; + return $behaviors; +} +``` + +If you want to support all three authentication methods explained above, you can use `CompositeAuth` like the following, + +```php +use yii\filters\auth\CompositeAuth; +use yii\filters\auth\HttpBasicAuth; +use yii\filters\auth\HttpBearerAuth; +use yii\filters\auth\QueryParamAuth; + +public function behaviors() +{ + $behaviors = parent::behaviors(); + $behaviors['authenticator'] = [ + 'class' => CompositeAuth::className(), + 'authMethods' => [ + HttpBasicAuth::className(), + HttpBearerAuth::className(), + QueryParamAuth::className(), + ], + ]; + return $behaviors; +} +``` + +Each element in `authMethods` should be an auth method class name or a configuration array. + + +Implementation of `findIdentityByAccessToken()` is application specific. For example, in simple scenarios +when each user can only have one access token, you may store the access token in an `access_token` column +in the user table. The method can then be readily implemented in the `User` class as follows, + +```php +use yii\db\ActiveRecord; +use yii\web\IdentityInterface; + +class User extends ActiveRecord implements IdentityInterface +{ + public static function findIdentityByAccessToken($token, $type = null) + { + return static::findOne(['access_token' => $token]); + } +} +``` + +After authentication is enabled as described above, for every API request, the requested controller +will try to authenticate the user in its `beforeAction()` step. + +If authentication succeeds, the controller will perform other checks (such as rate limiting, authorization) +and then run the action. The authenticated user identity information can be retrieved via `Yii::$app->user->identity`. + +If authentication fails, a response with HTTP status 401 will be sent back together with other appropriate headers +(such as a `WWW-Authenticate` header for HTTP Basic Auth). + + +## Authorization + +After a user is authenticated, you probably want to check if he or she has the permission to perform the requested +action for the requested resource. This process is called *authorization* which is covered in detail in +the [Authorization section](security-authorization.md). + +If your controllers extend from [[yii\rest\ActiveController]], you may override +the [[yii\rest\Controller::checkAccess()|checkAccess()]] method to perform authorization check. The method +will be called by the built-in actions provided by [[yii\rest\ActiveController]]. From f6968f89dd12e995fea0bf96ae603ebc0c9f558e Mon Sep 17 00:00:00 2001 From: Vadim Belorussov Date: Tue, 12 Aug 2014 12:25:28 +0500 Subject: [PATCH 31/41] Translated rest-authentication.md into Russian --- docs/guide-ru/rest-authentication.md | 98 ++++++++++++++-------------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/docs/guide-ru/rest-authentication.md b/docs/guide-ru/rest-authentication.md index 45d13ba924..098c82d5f2 100644 --- a/docs/guide-ru/rest-authentication.md +++ b/docs/guide-ru/rest-authentication.md @@ -1,42 +1,42 @@ -Authentication +Аутентификация ============== -Unlike Web applications, RESTful APIs are usually stateless, which means sessions or cookies should not -be used. Therefore, each request should come with some sort of authentication credentials because -the user authentication status may not be maintained by sessions or cookies. A common practice is -to send a secret access token with each request to authenticate the user. Since an access token -can be used to uniquely identify and authenticate a user, **API requests should always be sent -via HTTPS to prevent from man-in-the-middle (MitM) attacks**. +В отличие от Web-приложений, RESTful API обычно не сохраняют информацию о состоянии, а это означает, что сессии и куки +использовать не следует. Следовательно, раз состояние аутентификации пользователя не может быть сохранено в сессиях или куках, +каждый запрос должен приходить вместе с определенным видом параметров аутентификации. Общепринятая практика состоит в том, +что для аутентификации пользователя с каждый запросом отправляется секретный токен доступа. Так как токен доступа +может использоваться для уникальной идентификации и аутентификации пользователя, **запросы к API всегда должны отсылаться +через протокол HTTPS, чтобы предотвратить атаки «человек посередине» (англ. "man-in-the-middle", MitM)**. -There are different ways to send an access token: +Есть различные способы отправки токена доступа: -* [HTTP Basic Auth](http://en.wikipedia.org/wiki/Basic_access_authentication): the access token - is sent as the username. This is should only be used when an access token can be safely stored - on the API consumer side. For example, the API consumer is a program running on a server. -* Query parameter: the access token is sent as a query parameter in the API URL, e.g., - `https://example.com/users?access-token=xxxxxxxx`. Because most Web servers will keep query - parameters in server logs, this approach should be mainly used to serve `JSONP` requests which - cannot use HTTP headers to send access tokens. -* [OAuth 2](http://oauth.net/2/): the access token is obtained by the consumer from an authorization - server and sent to the API server via [HTTP Bearer Tokens](http://tools.ietf.org/html/rfc6750), - according to the OAuth2 protocol. +* [HTTP Basic Auth](http://en.wikipedia.org/wiki/Basic_access_authentication): токен доступа + отправляется как имя пользователя. Такой подход следует использовать только в том случае, когда токен доступа может быть безопасно сохранен + на стороне абонента API. Например, если API используется программой, запущенной на сервере. +* Параметр запроса: токен доступа отправляется как параметр запроса в URL-адресе API, т.е. примерно таким образом: + `https://example.com/users?access-token=xxxxxxxx`. Так как большинство Web-серверов сохраняют параметры запроса в своих логах, + такой подход следует применять только при работе с `JSONP`-запросами, которые не могут отправлять токены доступа + в HTTP-заголовках. +* [OAuth 2](http://oauth.net/2/): токен доступа выдается абоненту API сервером авторизации + и отправляется API-серверу через [HTTP Bearer Tokens](http://tools.ietf.org/html/rfc6750), + в соответствии с протоколом OAuth2. -Yii supports all of the above authentication methods. You can also easily create new authentication methods. +Yii поддерживает все выше перечисленные методы аутентификации. Вы также можете легко создавать новые методы аутентификации. -To enable authentication for your APIs, do the following steps: +Чтобы включить аутентификацию для ваших API, выполните следующие шаги: -1. Configure the [[yii\web\User::enableSession|enableSession]] property of the `user` application component to be false. -2. Specify which authentication methods you plan to use by configuring the `authenticator` behavior - in your REST controller classes. -3. Implement [[yii\web\IdentityInterface::findIdentityByAccessToken()]] in your [[yii\web\User::identityClass|user identity class]]. +1. У компонента приложения `user` установите свойство [[yii\web\User::enableSession|enableSession]] равным false. +2. Укажите, какие методы аутентификации вы планируете использовать, настроив поведение `authenticator` + в ваших классах REST-контроллеров. +3. Реализуйте метод [[yii\web\IdentityInterface::findIdentityByAccessToken()]] *в вашем [[yii\web\User::identityClass|классе UserIdentity]]*. -Step 1 is not required but is recommended for RESTful APIs which should be stateless. When [[yii\web\User::enableSession|enableSession]] -is false, the user authentication status will NOT be persisted across requests using sessions. Instead, authentication -will be performed for every request, which is accomplished by Step 2 and 3. +Шаг 1 не обязателен, но рекомендуется его все-таки выполнить, так как RESTful API не должны сохранять информацию о состоянии клиента. Когда свойство [[yii\web\User::enableSession|enableSession]] + установлено в false, состояние аутентификации пользователя НЕ БУДЕТ постоянно +сохраняться между запросами с использованием сессий. Вместо этого аутентификация будет выполняться для каждого запроса, что достигается шагами 2 и 3. -> Tip: You may configure [[yii\web\User::enableSession|enableSession]] of the `user` application component - in application configurations if you are developing RESTful APIs in terms of an application. If you develop - RESTful APIs as a module, you may put the following line in the module's `init()` method, like the following: +> Подсказка: если вы разрабатываете RESTful API в пределах приложения, вы можете настроить свойство + [[yii\web\User::enableSession|enableSession]] компонента приложения `user` в конфигурации приложения. Если вы разрабатываете + RESTful API как модуль, можете добавить следующую строчку в метод `init()` модуля: > ```php public function init() { @@ -45,7 +45,7 @@ public function init() } ``` -For example, to use HTTP Basic Auth, you may configure `authenticator` as follows, +Например, для использования HTTP Basic Auth, вы можете настроить свойство `authenticator` следующим образом: ```php use yii\filters\auth\HttpBasicAuth; @@ -60,7 +60,7 @@ public function behaviors() } ``` -If you want to support all three authentication methods explained above, you can use `CompositeAuth` like the following, +Если вы хотите включить поддержку всех трех описанных выше методов аутентификации, можете использовать `CompositeAuth`: ```php use yii\filters\auth\CompositeAuth; @@ -83,12 +83,12 @@ public function behaviors() } ``` -Each element in `authMethods` should be an auth method class name or a configuration array. +Каждый элемент в массиве `authMethods` должен быть названием класса метода аутентификации или массивом настроек. -Implementation of `findIdentityByAccessToken()` is application specific. For example, in simple scenarios -when each user can only have one access token, you may store the access token in an `access_token` column -in the user table. The method can then be readily implemented in the `User` class as follows, +Реализация метода `findIdentityByAccessToken()` определяется особенностями приложения. Например, в простом варианте, +когда у каждого пользователя есть только один токен доступа, вы можете хранить этот токен в поле `access_token` +таблицы пользователей. В этом случае метод `findIdentityByAccessToken()` может быть легко реализован в классе `User` следующим образом: ```php use yii\db\ActiveRecord; @@ -103,22 +103,22 @@ class User extends ActiveRecord implements IdentityInterface } ``` -After authentication is enabled as described above, for every API request, the requested controller -will try to authenticate the user in its `beforeAction()` step. +После включения аутентификации описанным выше способом при каждом запросе к API запрашиваемый контроллер +будет пытаться аутентифицировать пользователя в своем методе `beforeAction()`. -If authentication succeeds, the controller will perform other checks (such as rate limiting, authorization) -and then run the action. The authenticated user identity information can be retrieved via `Yii::$app->user->identity`. +Если аутентификация прошла успешно, контроллер выполнит другие проверки (ограничение на количество запросов, авторизация) +и затем выполнит действие. *Информация о подлинности аутентифицированного пользователя может быть получена из объекта `Yii::$app->user->identity`*. -If authentication fails, a response with HTTP status 401 will be sent back together with other appropriate headers -(such as a `WWW-Authenticate` header for HTTP Basic Auth). +Если аутентификация прошла неудачно, будет возвращен ответ с HTTP-кодом состояния 401 вместе с другими необходимыми заголовками +(такими, как заголовок `WWW-Authenticate` для HTTP Basic Auth). -## Authorization +## Авторизация -After a user is authenticated, you probably want to check if he or she has the permission to perform the requested -action for the requested resource. This process is called *authorization* which is covered in detail in -the [Authorization section](security-authorization.md). +После аутентификации пользователя вы, вероятно, захотите проверить, есть ли у него или у нее разрешение на выполнение запрошенного +действия с запрошенным ресурсом. Этот процесс называется *авторизацией* и подробно описан +в разделе [Авторизация](security-authorization.md). -If your controllers extend from [[yii\rest\ActiveController]], you may override -the [[yii\rest\Controller::checkAccess()|checkAccess()]] method to perform authorization check. The method -will be called by the built-in actions provided by [[yii\rest\ActiveController]]. +Если ваши контроллеры унаследованы от [[yii\rest\ActiveController]], вы можете переопределить +метод [[yii\rest\Controller::checkAccess()|checkAccess()]] для выполнения авторизации. Этот метод будет вызываться +встроенными действиями, предоставляемыми контроллером [[yii\rest\ActiveController]]. From d362af6ede066825bb256834bfdb855b84b6e337 Mon Sep 17 00:00:00 2001 From: Klimov Paul Date: Tue, 12 Aug 2014 11:36:50 +0300 Subject: [PATCH 32/41] `yii\behaviors\Sluggable` optimized --- framework/behaviors/SluggableBehavior.php | 53 ++++++++++++------- .../behaviors/SluggableBehaviorTest.php | 2 +- 2 files changed, 36 insertions(+), 19 deletions(-) diff --git a/framework/behaviors/SluggableBehavior.php b/framework/behaviors/SluggableBehavior.php index 351de28f9b..6c7b0efd58 100644 --- a/framework/behaviors/SluggableBehavior.php +++ b/framework/behaviors/SluggableBehavior.php @@ -48,6 +48,7 @@ use yii\helpers\Inflector; * ]; * } * ``` + * * @author Alexander Kochetov * @author Paul Klimov * @since 2.0 @@ -63,7 +64,7 @@ class SluggableBehavior extends AttributeBehavior */ public $attribute; /** - * @var mixed the value that will be used as a slug. This can be an anonymous function + * @var string|callable the value that will be used as a slug. This can be an anonymous function * or an arbitrary value. If the former, the return value of the function will be used as a slug. * The signature of the function should be as follows, * @@ -80,7 +81,7 @@ class SluggableBehavior extends AttributeBehavior * If enabled behavior will validate slug uniqueness automatically. If validation fails it will attempt * generating unique slug value from based one until success. */ - public $unique = false; + public $ensureUnique = false; /** * @var array configuration for slug uniqueness validator. This configuration should not contain validator name * and validated attributes - only options in format 'name => value' are allowed. @@ -118,11 +119,7 @@ class SluggableBehavior extends AttributeBehavior parent::init(); if (empty($this->attributes)) { - if ($this->unique) { - $this->attributes = [BaseActiveRecord::EVENT_BEFORE_INSERT => $this->slugAttribute]; - } else { - $this->attributes = [BaseActiveRecord::EVENT_BEFORE_VALIDATE => $this->slugAttribute]; - } + $this->attributes = [BaseActiveRecord::EVENT_BEFORE_VALIDATE => $this->slugAttribute]; } if ($this->attribute === null && $this->value === null) { @@ -135,23 +132,43 @@ class SluggableBehavior extends AttributeBehavior */ protected function getValue($event) { + $isNewSlug = true; + if ($this->attribute !== null) { if (is_array($this->attribute)) { - $slugParts = []; - foreach ($this->attribute as $attribute) { - $slugParts[] = $this->owner->{$attribute}; - } - $this->value = Inflector::slug(implode('-', $slugParts)); + $attributes = $this->attribute; } else { - $this->value = Inflector::slug($this->owner->{$this->attribute}); + $attributes = [$this->attribute]; + } + /* @var $owner BaseActiveRecord */ + $owner = $this->owner; + if (!$owner->getIsNewRecord() && !empty($owner->{$this->slugAttribute})) { + $isNewSlug = false; + foreach ($attributes as $attribute) { + if ($owner->isAttributeChanged($attribute)) { + $isNewSlug = true; + break; + } + } } - } - $slug = parent::getValue($event); - if ($this->unique) { + if ($isNewSlug) { + $slugParts = []; + foreach ($attributes as $attribute) { + $slugParts[] = $owner->{$attribute}; + } + $slug = Inflector::slug(implode('-', $slugParts)); + } else { + $slug = $owner->{$this->slugAttribute}; + } + } else { + $slug = parent::getValue($event); + } + + if ($this->ensureUnique && $isNewSlug) { $baseSlug = $slug; $iteration = 0; - while (!$this->validateSlugUnique($slug)) { + while (!$this->validateSlug($slug)) { $iteration++; $slug = $this->generateUniqueSlug($baseSlug, $iteration); } @@ -164,7 +181,7 @@ class SluggableBehavior extends AttributeBehavior * @param string $slug slug value * @return boolean whether slug is unique. */ - private function validateSlugUnique($slug) + private function validateSlug($slug) { $validator = array_merge( [ diff --git a/tests/unit/framework/behaviors/SluggableBehaviorTest.php b/tests/unit/framework/behaviors/SluggableBehaviorTest.php index 81225680ef..5beaea8f47 100644 --- a/tests/unit/framework/behaviors/SluggableBehaviorTest.php +++ b/tests/unit/framework/behaviors/SluggableBehaviorTest.php @@ -218,7 +218,7 @@ class ActiveRecordSluggableUnique extends ActiveRecordSluggable 'sluggable' => [ 'class' => SluggableBehavior::className(), 'attribute' => 'name', - 'unique' => true, + 'ensureUnique' => true, ], ]; } From f27254b9aa3517e74dca5b7318c6e9448d086bdb Mon Sep 17 00:00:00 2001 From: Klimov Paul Date: Tue, 12 Aug 2014 11:43:15 +0300 Subject: [PATCH 33/41] `yii\behaviors\Sluggable` simplified --- framework/behaviors/SluggableBehavior.php | 73 +++---------------- .../behaviors/SluggableBehaviorTest.php | 38 ---------- 2 files changed, 9 insertions(+), 102 deletions(-) diff --git a/framework/behaviors/SluggableBehavior.php b/framework/behaviors/SluggableBehavior.php index 6c7b0efd58..4391b05f54 100644 --- a/framework/behaviors/SluggableBehavior.php +++ b/framework/behaviors/SluggableBehavior.php @@ -8,7 +8,6 @@ namespace yii\behaviors; use yii\base\DynamicModel; -use yii\base\Exception; use yii\base\InvalidConfigException; use yii\db\BaseActiveRecord; use yii\helpers\Inflector; @@ -93,8 +92,8 @@ class SluggableBehavior extends AttributeBehavior */ public $uniqueValidatorConfig = []; /** - * @var string|callable slug unique value generator. It is used in case [[unique]] enabled and generated - * slug is not unique. This can be a PHP callable with following signature: + * @var callable slug unique value generator. It is used in case [[ensureUnique]] enabled and generated + * slug is not unique. This should be a PHP callable with following signature: * * ```php * function ($baseSlug, $iteration) @@ -103,12 +102,9 @@ class SluggableBehavior extends AttributeBehavior * } * ``` * - * Also one of the following predefined values can be used: - * - 'increment' - adds incrementing suffix to the base slug - * - 'uniqueid' - adds part of uniqueId hash string to the base slug - * - 'timestamp' - adds current UNIX timestamp to the base slug + * If not set unique slug will be generated adding incrementing suffix to the base slug. */ - public $uniqueSlugGenerator = 'increment'; + public $uniqueSlugGenerator; /** @@ -196,6 +192,7 @@ class SluggableBehavior extends AttributeBehavior } /** + * Generates slug using configured callback or increment of iteration. * @param string $baseSlug base slug value * @param integer $iteration iteration number * @return string slug suffix @@ -203,62 +200,10 @@ class SluggableBehavior extends AttributeBehavior */ private function generateUniqueSlug($baseSlug, $iteration) { - $generator = $this->uniqueSlugGenerator; - switch ($generator) { - case 'increment': - return $this->generateUniqueSlugIncrement($baseSlug, $iteration); - case 'uniqueid': - return $this->generateUniqueSlugUniqueId($baseSlug, $iteration); - case 'timestamp': - return $this->generateSuffixSlugTimestamp($baseSlug, $iteration); - default: - if (is_callable($generator)) { - return call_user_func($generator, $baseSlug, $iteration); - } - throw new InvalidConfigException("Unrecognized slug unique suffix generator '{$generator}'."); + if (is_callable($this->uniqueSlugGenerator)) { + return call_user_func($this->uniqueSlugGenerator, $baseSlug, $iteration); + } else { + return $baseSlug . '-' . ($iteration + 1); } } - - /** - * Generates slug using increment of iteration. - * @param string $baseSlug base slug value - * @param integer $iteration iteration number - * @return string generated suffix. - */ - protected function generateUniqueSlugIncrement($baseSlug, $iteration) - { - return $baseSlug . '-' . ($iteration + 1); - } - - /** - * Generates slug using unique id. - * @param string $baseSlug base slug value - * @param integer $iteration iteration number - * @throws \yii\base\Exception - * @return string generated suffix. - */ - protected function generateUniqueSlugUniqueId($baseSlug, $iteration) - { - static $uniqueId; - if ($iteration < 2) { - $uniqueId = sha1(uniqid(get_class($this), true)); - } - $subStringLength = 6 + $iteration; - if ($subStringLength > strlen($uniqueId)) { - throw new Exception('Unique id is exhausted.'); - } - return $baseSlug . '-' . substr($uniqueId, 0, $subStringLength); - } - - /** - * Generates slug using current timestamp. - * @param string $baseSlug base slug value - * @param integer $iteration iteration number - * @throws \yii\base\Exception - * @return string generated suffix. - */ - protected function generateSuffixSlugTimestamp($baseSlug, $iteration) - { - return $baseSlug . '-' . (time() + $iteration - 1); - } } diff --git a/tests/unit/framework/behaviors/SluggableBehaviorTest.php b/tests/unit/framework/behaviors/SluggableBehaviorTest.php index 5beaea8f47..68da93bdaf 100644 --- a/tests/unit/framework/behaviors/SluggableBehaviorTest.php +++ b/tests/unit/framework/behaviors/SluggableBehaviorTest.php @@ -118,44 +118,6 @@ class SluggableBehaviorTest extends TestCase $this->assertEquals('test-name-callback', $model->slug); } - /** - * @depends testUniqueByIncrement - */ - public function testUniqueByUniqueId() - { - $name = 'test name'; - - $model1 = new ActiveRecordSluggableUnique(); - $model1->name = $name; - $model1->save(); - - $model2 = new ActiveRecordSluggableUnique(); - $model2->sluggable->uniqueSlugGenerator = 'uniqueid'; - $model2->name = $name; - $model2->save(); - - $this->assertNotEquals($model2->slug, $model1->slug); - } - - /** - * @depends testUniqueByIncrement - */ - public function testUniqueByTimestamp() - { - $name = 'test name'; - - $model1 = new ActiveRecordSluggableUnique(); - $model1->name = $name; - $model1->save(); - - $model2 = new ActiveRecordSluggableUnique(); - $model2->sluggable->uniqueSlugGenerator = 'timestamp'; - $model2->name = $name; - $model2->save(); - - $this->assertNotEquals($model2->slug, $model1->slug); - } - /** * @depends testSlug */ From 101d771ecf0e506b160f72d0dd780cb28e98c638 Mon Sep 17 00:00:00 2001 From: Klimov Paul Date: Tue, 12 Aug 2014 12:01:23 +0300 Subject: [PATCH 34/41] `yii\behaviors\Sluggable::validateSlug()` fixed to respect updating record --- framework/behaviors/SluggableBehavior.php | 34 ++++++++++++------- .../behaviors/SluggableBehaviorTest.php | 6 ++++ 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/framework/behaviors/SluggableBehavior.php b/framework/behaviors/SluggableBehavior.php index 4391b05f54..91cfbcb509 100644 --- a/framework/behaviors/SluggableBehavior.php +++ b/framework/behaviors/SluggableBehavior.php @@ -7,10 +7,11 @@ namespace yii\behaviors; -use yii\base\DynamicModel; use yii\base\InvalidConfigException; use yii\db\BaseActiveRecord; use yii\helpers\Inflector; +use yii\validators\UniqueValidator; +use Yii; /** * SluggableBehavior automatically fills the specified attribute with a value that can be used a slug in a URL. @@ -82,15 +83,19 @@ class SluggableBehavior extends AttributeBehavior */ public $ensureUnique = false; /** - * @var array configuration for slug uniqueness validator. This configuration should not contain validator name - * and validated attributes - only options in format 'name => value' are allowed. + * @var array configuration for slug uniqueness validator. Parameter 'class' may be omitted - by default + * [[UniqueValidator]] will be used. * For example: + * + * ```php * [ * 'filter' => ['type' => 1, 'status' => 2] * ] - * @see yii\validators\UniqueValidator + * ``` + * + * @see UniqueValidator */ - public $uniqueValidatorConfig = []; + public $uniqueValidator = []; /** * @var callable slug unique value generator. It is used in case [[ensureUnique]] enabled and generated * slug is not unique. This should be a PHP callable with following signature: @@ -179,15 +184,20 @@ class SluggableBehavior extends AttributeBehavior */ private function validateSlug($slug) { - $validator = array_merge( + /* @var $validator UniqueValidator */ + /* @var $model BaseActiveRecord */ + $validator = Yii::createObject(array_merge( [ - ['slug'], - 'unique', - 'targetClass' => get_class($this->owner) + 'class' => UniqueValidator::className() ], - $this->uniqueValidatorConfig - ); - $model = DynamicModel::validateData(compact('slug'), [$validator]); + $this->uniqueValidator + )); + + $model = clone $this->owner; + $model->clearErrors(); + $model->{$this->slugAttribute} = $slug; + + $validator->validateAttribute($model, $this->slugAttribute); return !$model->hasErrors(); } diff --git a/tests/unit/framework/behaviors/SluggableBehaviorTest.php b/tests/unit/framework/behaviors/SluggableBehaviorTest.php index 68da93bdaf..3bd6b7269f 100644 --- a/tests/unit/framework/behaviors/SluggableBehaviorTest.php +++ b/tests/unit/framework/behaviors/SluggableBehaviorTest.php @@ -129,9 +129,15 @@ class SluggableBehaviorTest extends TestCase $model->name = $name; $model->save(); + $model->save(); + $this->assertEquals('test-name', $model->slug); + $model = ActiveRecordSluggableUnique::find()->one(); $model->save(); + $this->assertEquals('test-name', $model->slug); + $model->name = 'test-name'; + $model->save(); $this->assertEquals('test-name', $model->slug); } } From ce183732214a5c2c0c204ca2fae3f26b9d633865 Mon Sep 17 00:00:00 2001 From: Klimov Paul Date: Tue, 12 Aug 2014 12:15:24 +0300 Subject: [PATCH 35/41] CHANGELOG updated --- framework/CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 53ecda7351..0359af1ba7 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -176,8 +176,9 @@ Yii Framework 2 Change Log - Enh #4559: Added `beforeValidateAll` and `afterValidateAll` callbacks to `ActiveForm` (Alex-Code) - Enh #4566: Added client validation support for image validator (Skysplit, qiangxue) - Enh #4581: Added ability to disable url encoding in `UrlRule` (tadaszelvys) -- Enh #4602: Added $key param in ActionColumn buttons Closure call (disem) - Enh #4597: `yii\composer\Installer::setPermission()` supports setting permission for both directories and files now (qiangxue) +- Enh #4602: Added $key param in ActionColumn buttons Closure call (disem) +- Enh #4630: Added automatic generating of unique slug value to `yii\behaviors\Sluggable` (klimov-paul) - Enh #4644: Added `\yii\db\Schema::createColumnSchema()` to be able to customize column schema used (mcd-php) - Enh #4656: HtmlPurifier helper config can now be a closure to change the purifier config object after it was created (Alex-Code) - Enh: Added support for using sub-queries when building a DB query with `IN` condition (qiangxue) From c072cc2eafda614fcd5ea821a384956abc806bab Mon Sep 17 00:00:00 2001 From: Klimov Paul Date: Tue, 12 Aug 2014 12:24:08 +0300 Subject: [PATCH 36/41] `yii\behaviors\Sluggable` adjusted --- framework/behaviors/SluggableBehavior.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/framework/behaviors/SluggableBehavior.php b/framework/behaviors/SluggableBehavior.php index 91cfbcb509..3526d8480c 100644 --- a/framework/behaviors/SluggableBehavior.php +++ b/framework/behaviors/SluggableBehavior.php @@ -136,11 +136,7 @@ class SluggableBehavior extends AttributeBehavior $isNewSlug = true; if ($this->attribute !== null) { - if (is_array($this->attribute)) { - $attributes = $this->attribute; - } else { - $attributes = [$this->attribute]; - } + $attributes = (array)$this->attribute; /* @var $owner BaseActiveRecord */ $owner = $this->owner; if (!$owner->getIsNewRecord() && !empty($owner->{$this->slugAttribute})) { @@ -205,7 +201,7 @@ class SluggableBehavior extends AttributeBehavior * Generates slug using configured callback or increment of iteration. * @param string $baseSlug base slug value * @param integer $iteration iteration number - * @return string slug suffix + * @return string new slug value * @throws \yii\base\InvalidConfigException */ private function generateUniqueSlug($baseSlug, $iteration) From 869d7b7cfcd84cda7e1909e629c91909a0cb5551 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Tue, 12 Aug 2014 12:00:52 +0200 Subject: [PATCH 37/41] Improved test config You can now customize test config without having uncommitted changes in git. issue #4687 --- tests/README.md | 12 +++++++++++- tests/unit/.gitignore | 3 ++- tests/unit/data/config.php | 22 +++++++++++++++++++++- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/tests/README.md b/tests/README.md index fa6b3a411a..019a2c9ef5 100644 --- a/tests/README.md +++ b/tests/README.md @@ -38,4 +38,14 @@ PHPUnit configuration is in `phpunit.xml.dist` in repository root folder. You can create your own phpunit.xml to override dist config. Database and other backend system configuration can be found in `unit/data/config.php` -adjust them to your needs to allow testing databases and caching in your environment. \ No newline at end of file +adjust them to your needs to allow testing databases and caching in your environment. +You can override configuration values by creating a `config.local.php` file +and manipulate the `$config` variable. +For example to change MySQL username and password your `config.local.php` should +contain the following: + +```php + [ 'cubrid' => [ 'dsn' => 'cubrid:dbname=demodb;host=localhost;port=33000', @@ -58,3 +72,9 @@ return [ 'options' => [], ] ]; + +if (is_file(__DIR__ . '/config.local.php')) { + include(__DIR__ . '/config.local.php'); +} + +return $config; \ No newline at end of file From 3b665fe3c4ae244f2a5a5e0b29fada60ce04582e Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Tue, 12 Aug 2014 12:08:39 +0200 Subject: [PATCH 38/41] improved unit test SKIP detection issue #4687 --- tests/unit/extensions/sphinx/SphinxTestCase.php | 6 ++++++ tests/unit/framework/caching/MemCacheTest.php | 5 +++++ tests/unit/framework/caching/MemCachedTest.php | 5 +++++ tests/unit/framework/db/sqlite/SqliteConnectionTest.php | 2 +- 4 files changed, 17 insertions(+), 1 deletion(-) diff --git a/tests/unit/extensions/sphinx/SphinxTestCase.php b/tests/unit/extensions/sphinx/SphinxTestCase.php index 83b0984853..c01161a359 100644 --- a/tests/unit/extensions/sphinx/SphinxTestCase.php +++ b/tests/unit/extensions/sphinx/SphinxTestCase.php @@ -48,6 +48,12 @@ class SphinxTestCase extends TestCase if (!extension_loaded('pdo') || !extension_loaded('pdo_mysql')) { $this->markTestSkipped('pdo and pdo_mysql extension are required.'); } + // check whether sphinx is running and skip tests if not. + if (preg_match('/host=([\w\d.]+)/i', $this->sphinxConfig['dsn'], $hm) && preg_match('/port=(\d+)/i', $this->sphinxConfig['dsn'], $pm)) { + if (!@stream_socket_client($hm[1] . ':' . $pm[1], $errorNumber, $errorDescription, 0.5)) { + $this->markTestSkipped('No redis server running at ' . $hm[1] . ':' . $pm[1] . ' : ' . $errorNumber . ' - ' . $errorDescription); + } + } $config = self::getParam('sphinx'); if (!empty($config)) { $this->sphinxConfig = $config['sphinx']; diff --git a/tests/unit/framework/caching/MemCacheTest.php b/tests/unit/framework/caching/MemCacheTest.php index 36d7904379..d3c35d3b1f 100644 --- a/tests/unit/framework/caching/MemCacheTest.php +++ b/tests/unit/framework/caching/MemCacheTest.php @@ -21,6 +21,11 @@ class MemCacheTest extends CacheTestCase $this->markTestSkipped("memcache not installed. Skipping."); } + // check whether memcached is running and skip tests if not. + if (!@stream_socket_client('127.0.0.1:11211', $errorNumber, $errorDescription, 0.5)) { + $this->markTestSkipped('No redis server running at ' . '127.0.0.1:11211' . ' : ' . $errorNumber . ' - ' . $errorDescription); + } + if ($this->_cacheInstance === null) { $this->_cacheInstance = new MemCache(); } diff --git a/tests/unit/framework/caching/MemCachedTest.php b/tests/unit/framework/caching/MemCachedTest.php index 35d9800f87..56a5570355 100644 --- a/tests/unit/framework/caching/MemCachedTest.php +++ b/tests/unit/framework/caching/MemCachedTest.php @@ -21,6 +21,11 @@ class MemCachedTest extends CacheTestCase $this->markTestSkipped("memcached not installed. Skipping."); } + // check whether memcached is running and skip tests if not. + if (!@stream_socket_client('127.0.0.1:11211', $errorNumber, $errorDescription, 0.5)) { + $this->markTestSkipped('No redis server running at ' . '127.0.0.1:11211' . ' : ' . $errorNumber . ' - ' . $errorDescription); + } + if ($this->_cacheInstance === null) { $this->_cacheInstance = new MemCache(['useMemcached' => true]); } diff --git a/tests/unit/framework/db/sqlite/SqliteConnectionTest.php b/tests/unit/framework/db/sqlite/SqliteConnectionTest.php index c75f58edab..11404c9428 100644 --- a/tests/unit/framework/db/sqlite/SqliteConnectionTest.php +++ b/tests/unit/framework/db/sqlite/SqliteConnectionTest.php @@ -122,7 +122,7 @@ class SqliteConnectionTest extends ConnectionTest $config = [ 'class' => 'yii\db\Connection', - 'dsn' => 'sqlite:memory:', + 'dsn' => 'sqlite::memory:', ]; $this->prepareDatabase($config, $fixture)->close(); From 9d4e3612e0257cab00bffd0382de6042ff2f8a8b Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Tue, 12 Aug 2014 12:51:24 +0200 Subject: [PATCH 39/41] fixed test break --- tests/unit/framework/db/sqlite/SqliteConnectionTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/framework/db/sqlite/SqliteConnectionTest.php b/tests/unit/framework/db/sqlite/SqliteConnectionTest.php index 11404c9428..c36e5e66d5 100644 --- a/tests/unit/framework/db/sqlite/SqliteConnectionTest.php +++ b/tests/unit/framework/db/sqlite/SqliteConnectionTest.php @@ -122,7 +122,7 @@ class SqliteConnectionTest extends ConnectionTest $config = [ 'class' => 'yii\db\Connection', - 'dsn' => 'sqlite::memory:', + 'dsn' => "sqlite:$basePath/yii2test.sq3", ]; $this->prepareDatabase($config, $fixture)->close(); From dd225fb628888f43e1d2dd0d4bd0f142c22a985a Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Tue, 12 Aug 2014 13:00:32 +0200 Subject: [PATCH 40/41] try running against postgres 9.3 for #4672 --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index e3a4d31091..439e42ff5e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,6 +21,10 @@ services: - elasticsearch - mongodb +# try running against postgres 9.3 +addons: + postgresql: "9.3" + install: - composer self-update && composer --version # core framework: From accd2d3124a9f2d0adcabf9b9e48dd3061cc7310 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Tue, 12 Aug 2014 13:35:45 +0200 Subject: [PATCH 41/41] Update README.md --- tests/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/README.md b/tests/README.md index 019a2c9ef5..028f74afe7 100644 --- a/tests/README.md +++ b/tests/README.md @@ -15,7 +15,7 @@ DIRECTORY STRUCTURE HOW TO RUN THE TESTS -------------------- -Make sure you have PHPUnit installed. +Make sure you have PHPUnit installed and that you installed all composer dependencies (run `composer update` in the repo base directory). Run PHPUnit in the yii repo base directory.