From 29394758bad59befe305f4f88009d25389935707 Mon Sep 17 00:00:00 2001 From: gsd Date: Wed, 18 Sep 2013 22:12:47 +0600 Subject: [PATCH 01/30] Update passwordResetToken.php --- apps/advanced/frontend/views/emails/passwordResetToken.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/advanced/frontend/views/emails/passwordResetToken.php b/apps/advanced/frontend/views/emails/passwordResetToken.php index 11aa8e9..1e7a855 100644 --- a/apps/advanced/frontend/views/emails/passwordResetToken.php +++ b/apps/advanced/frontend/views/emails/passwordResetToken.php @@ -13,4 +13,4 @@ Hello username)?>, Follow the link below to reset your password: - \ No newline at end of file + From 41e2c41002fc11defdf327b933a36f705de5cfe2 Mon Sep 17 00:00:00 2001 From: gsd Date: Wed, 18 Sep 2013 22:18:12 +0600 Subject: [PATCH 02/30] Update SiteController.php --- apps/advanced/frontend/controllers/SiteController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/advanced/frontend/controllers/SiteController.php b/apps/advanced/frontend/controllers/SiteController.php index be9a634..a9413de 100644 --- a/apps/advanced/frontend/controllers/SiteController.php +++ b/apps/advanced/frontend/controllers/SiteController.php @@ -164,7 +164,7 @@ class SiteController extends Controller $headers = "From: $name <{$fromEmail}>\r\n" . "MIME-Version: 1.0\r\n" . "Content-type: text/plain; charset=UTF-8"; - return mail($fromEmail, $subject, $body, $headers); + return mail($email, $subject, $body, $headers); } return false; From c2a3aa3d99097752f7f79525aec1e722feb4aa06 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Wed, 18 Sep 2013 16:18:47 -0400 Subject: [PATCH 03/30] Fixes #885: removed NULLs from filtering by models. --- framework/yii/db/ActiveRelation.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/framework/yii/db/ActiveRelation.php b/framework/yii/db/ActiveRelation.php index 0be4feb..f05c56a 100644 --- a/framework/yii/db/ActiveRelation.php +++ b/framework/yii/db/ActiveRelation.php @@ -279,7 +279,9 @@ class ActiveRelation extends ActiveQuery // single key $attribute = reset($this->link); foreach ($models as $model) { - $values[] = $model[$attribute]; + if (($value = $model[$attribute]) !== null) { + $values[] = $value; + } } } else { // composite keys From b8ffee655930a23cd616a2a4e781850efca534fa Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 18 Sep 2013 22:38:53 +0200 Subject: [PATCH 04/30] moved CacheSession::init() parent call after init of cache component session autostart would fail otherwise. fixes #887 --- framework/yii/web/CacheSession.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/web/CacheSession.php b/framework/yii/web/CacheSession.php index bb387e1..b4ce2ae 100644 --- a/framework/yii/web/CacheSession.php +++ b/framework/yii/web/CacheSession.php @@ -42,13 +42,13 @@ class CacheSession extends Session */ public function init() { - parent::init(); if (is_string($this->cache)) { $this->cache = Yii::$app->getComponent($this->cache); } if (!$this->cache instanceof Cache) { throw new InvalidConfigException('CacheSession::cache must refer to the application component ID of a cache object.'); } + parent::init(); } /** From bd2404f1c6f53356e959a14455e7f4f9f3e96dc2 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 18 Sep 2013 22:46:43 +0200 Subject: [PATCH 05/30] added simple unit test for CacheSession --- tests/unit/framework/web/CacheSessionTest.php | 28 +++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 tests/unit/framework/web/CacheSessionTest.php diff --git a/tests/unit/framework/web/CacheSessionTest.php b/tests/unit/framework/web/CacheSessionTest.php new file mode 100644 index 0000000..c80fa1c --- /dev/null +++ b/tests/unit/framework/web/CacheSessionTest.php @@ -0,0 +1,28 @@ +mockApplication(); + Yii::$app->setComponent('cache', new FileCache()); + } + + public function testCreate() + { + $session = new CacheSession(); + + $session->writeSession('test', 'sessionData'); + $this->assertEquals('sessionData', $session->readSession('test')); + } +} From 128ee07b78f83848f5ebeded650c6e89cb14675b Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 18 Sep 2013 22:54:11 +0200 Subject: [PATCH 06/30] 100% test coverage for CacheSession --- tests/unit/framework/web/CacheSessionTest.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/unit/framework/web/CacheSessionTest.php b/tests/unit/framework/web/CacheSessionTest.php index c80fa1c..e740596 100644 --- a/tests/unit/framework/web/CacheSessionTest.php +++ b/tests/unit/framework/web/CacheSessionTest.php @@ -18,11 +18,22 @@ class CacheSessionTest extends \yiiunit\TestCase Yii::$app->setComponent('cache', new FileCache()); } - public function testCreate() + public function testCacheSession() { $session = new CacheSession(); $session->writeSession('test', 'sessionData'); $this->assertEquals('sessionData', $session->readSession('test')); + $session->destroySession('test'); + $this->assertEquals('', $session->readSession('test')); + } + + public function testInvalidCache() + { + $this->setExpectedException('yii\base\InvalidConfigException'); + + $session = new CacheSession(array( + 'cache' => 'invalid', + )); } } From df6176edb6c0817ee6b1e5ffc2f63050d6c32feb Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 18 Sep 2013 23:43:46 +0200 Subject: [PATCH 07/30] added unit test for Model::load() --- tests/unit/data/base/Speaker.php | 7 +++++++ tests/unit/framework/base/ModelTest.php | 26 ++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/tests/unit/data/base/Speaker.php b/tests/unit/data/base/Speaker.php index 5668dad..b0acc6b 100644 --- a/tests/unit/data/base/Speaker.php +++ b/tests/unit/data/base/Speaker.php @@ -17,6 +17,13 @@ class Speaker extends Model protected $protectedProperty; private $_privateProperty; + public static $formName = 'Speaker'; + + public function formName() + { + return static::$formName; + } + public function attributeLabels() { return array( diff --git a/tests/unit/framework/base/ModelTest.php b/tests/unit/framework/base/ModelTest.php index b338c17..d500933 100644 --- a/tests/unit/framework/base/ModelTest.php +++ b/tests/unit/framework/base/ModelTest.php @@ -75,6 +75,32 @@ class ModelTest extends TestCase $this->assertEquals('Qiang', $speaker->firstName); } + public function testLoad() + { + $singer = new Singer(); + $this->assertEquals('Singer', $singer->formName()); + + $post = array('firstName' => 'Qiang'); + + Speaker::$formName = ''; + $model = new Speaker(); + $model->setScenario('test'); + $this->assertTrue($model->load($post)); + $this->assertEquals('Qiang', $model->firstName); + + Speaker::$formName = 'Speaker'; + $model = new Speaker(); + $model->setScenario('test'); + $this->assertTrue($model->load(array('Speaker' => $post))); + $this->assertEquals('Qiang', $model->firstName); + + Speaker::$formName = 'Speaker'; + $model = new Speaker(); + $model->setScenario('test'); + $this->assertFalse($model->load(array('Example' => array()))); + $this->assertEquals('', $model->firstName); + } + public function testActiveAttributes() { // by default mass assignment doesn't work at all From 1f8ab8106db89b177fe5b15975a7afcc61d84b3a Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Thu, 19 Sep 2013 00:21:12 +0200 Subject: [PATCH 08/30] updated classmap --- framework/yii/classes.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/framework/yii/classes.php b/framework/yii/classes.php index aee93c0..3880620 100644 --- a/framework/yii/classes.php +++ b/framework/yii/classes.php @@ -49,6 +49,7 @@ return array( 'yii\bootstrap\Alert' => YII_PATH . '/bootstrap/Alert.php', 'yii\bootstrap\BootstrapAsset' => YII_PATH . '/bootstrap/BootstrapAsset.php', 'yii\bootstrap\BootstrapPluginAsset' => YII_PATH . '/bootstrap/BootstrapPluginAsset.php', + 'yii\bootstrap\BootstrapThemeAsset' => YII_PATH . '/bootstrap/BootstrapThemeAsset.php', 'yii\bootstrap\Button' => YII_PATH . '/bootstrap/Button.php', 'yii\bootstrap\ButtonDropdown' => YII_PATH . '/bootstrap/ButtonDropdown.php', 'yii\bootstrap\ButtonGroup' => YII_PATH . '/bootstrap/ButtonGroup.php', @@ -60,7 +61,6 @@ return array( 'yii\bootstrap\NavBar' => YII_PATH . '/bootstrap/NavBar.php', 'yii\bootstrap\Progress' => YII_PATH . '/bootstrap/Progress.php', 'yii\bootstrap\Tabs' => YII_PATH . '/bootstrap/Tabs.php', - 'yii\bootstrap\Typeahead' => YII_PATH . '/bootstrap/Typeahead.php', 'yii\bootstrap\Widget' => YII_PATH . '/bootstrap/Widget.php', 'yii\caching\ApcCache' => YII_PATH . '/caching/ApcCache.php', 'yii\caching\Cache' => YII_PATH . '/caching/Cache.php', @@ -75,6 +75,7 @@ return array( 'yii\caching\GroupDependency' => YII_PATH . '/caching/GroupDependency.php', 'yii\caching\MemCache' => YII_PATH . '/caching/MemCache.php', 'yii\caching\MemCacheServer' => YII_PATH . '/caching/MemCacheServer.php', + 'yii\caching\RedisCache' => YII_PATH . '/caching/RedisCache.php', 'yii\caching\WinCache' => YII_PATH . '/caching/WinCache.php', 'yii\caching\XCache' => YII_PATH . '/caching/XCache.php', 'yii\caching\ZendDataCache' => YII_PATH . '/caching/ZendDataCache.php', @@ -104,10 +105,13 @@ return array( 'yii\db\StaleObjectException' => YII_PATH . '/db/StaleObjectException.php', 'yii\db\TableSchema' => YII_PATH . '/db/TableSchema.php', 'yii\db\Transaction' => YII_PATH . '/db/Transaction.php', + 'yii\db\cubrid\QueryBuilder' => YII_PATH . '/db/cubrid/QueryBuilder.php', + 'yii\db\cubrid\Schema' => YII_PATH . '/db/cubrid/Schema.php', 'yii\db\mssql\PDO' => YII_PATH . '/db/mssql/PDO.php', 'yii\db\mssql\QueryBuilder' => YII_PATH . '/db/mssql/QueryBuilder.php', 'yii\db\mssql\Schema' => YII_PATH . '/db/mssql/Schema.php', 'yii\db\mssql\SqlsrvPDO' => YII_PATH . '/db/mssql/SqlsrvPDO.php', + 'yii\db\mssql\TableSchema' => YII_PATH . '/db/mssql/TableSchema.php', 'yii\db\mysql\QueryBuilder' => YII_PATH . '/db/mysql/QueryBuilder.php', 'yii\db\mysql\Schema' => YII_PATH . '/db/mysql/Schema.php', 'yii\db\pgsql\QueryBuilder' => YII_PATH . '/db/pgsql/QueryBuilder.php', @@ -162,6 +166,8 @@ return array( 'yii\rbac\Item' => YII_PATH . '/rbac/Item.php', 'yii\rbac\Manager' => YII_PATH . '/rbac/Manager.php', 'yii\rbac\PhpManager' => YII_PATH . '/rbac/PhpManager.php', + 'yii\redis\Connection' => YII_PATH . '/redis/Connection.php', + 'yii\redis\Transaction' => YII_PATH . '/redis/Transaction.php', 'yii\requirements\YiiRequirementChecker' => YII_PATH . '/requirements/YiiRequirementChecker.php', 'yii\validators\BooleanValidator' => YII_PATH . '/validators/BooleanValidator.php', 'yii\validators\CompareValidator' => YII_PATH . '/validators/CompareValidator.php', @@ -177,6 +183,7 @@ return array( 'yii\validators\RangeValidator' => YII_PATH . '/validators/RangeValidator.php', 'yii\validators\RegularExpressionValidator' => YII_PATH . '/validators/RegularExpressionValidator.php', 'yii\validators\RequiredValidator' => YII_PATH . '/validators/RequiredValidator.php', + 'yii\validators\SafeValidator' => YII_PATH . '/validators/SafeValidator.php', 'yii\validators\StringValidator' => YII_PATH . '/validators/StringValidator.php', 'yii\validators\UniqueValidator' => YII_PATH . '/validators/UniqueValidator.php', 'yii\validators\UrlValidator' => YII_PATH . '/validators/UrlValidator.php', From 0fc423c74aa5ce4a4115bee63f14c058c90eeab5 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Wed, 18 Sep 2013 23:11:55 -0400 Subject: [PATCH 09/30] Added support for data-method and data-confirm. --- apps/basic/controllers/SiteController.php | 10 ++- apps/basic/views/layouts/main.php | 4 +- framework/yii/assets/yii.js | 100 +++++++++++++++++++++++++++++- 3 files changed, 109 insertions(+), 5 deletions(-) diff --git a/apps/basic/controllers/SiteController.php b/apps/basic/controllers/SiteController.php index 785eddf..1196280 100644 --- a/apps/basic/controllers/SiteController.php +++ b/apps/basic/controllers/SiteController.php @@ -3,7 +3,9 @@ namespace app\controllers; use Yii; +use yii\web\AccessControl; use yii\web\Controller; +use yii\web\VerbFilter; use app\models\LoginForm; use app\models\ContactForm; @@ -13,7 +15,7 @@ class SiteController extends Controller { return array( 'access' => array( - 'class' => \yii\web\AccessControl::className(), + 'class' => AccessControl::className(), 'only' => array('login', 'logout'), 'rules' => array( array( @@ -28,6 +30,12 @@ class SiteController extends Controller ), ), ), + 'verbs' => array( + 'class' => VerbFilter::className(), + 'actions' => array( + 'logout' => array('post'), + ), + ), ); } diff --git a/apps/basic/views/layouts/main.php b/apps/basic/views/layouts/main.php index 240c2a3..1b7083d 100644 --- a/apps/basic/views/layouts/main.php +++ b/apps/basic/views/layouts/main.php @@ -36,7 +36,9 @@ app\config\AppAsset::register($this); array('label' => 'Contact', 'url' => array('/site/contact')), Yii::$app->user->isGuest ? array('label' => 'Login', 'url' => array('/site/login')) : - array('label' => 'Logout (' . Html::encode(Yii::$app->user->identity->username) .')' , 'url' => array('/site/logout')), + array('label' => 'Logout (' . Yii::$app->user->identity->username .')' , + 'url' => array('/site/logout'), + 'linkOptions' => array('data-method' => 'post')), ), )); NavBar::end(); diff --git a/framework/yii/assets/yii.js b/framework/yii/assets/yii.js index eb5ecf6..b1fb0cb 100644 --- a/framework/yii/assets/yii.js +++ b/framework/yii/assets/yii.js @@ -37,26 +37,103 @@ * * Using this structure, you can define public and private functions/properties for a module. * Private functions/properties are only visible within the module, while public functions/properties - * may be accessed outside of the module. For example, you can access "yii.sample.init()". + * may be accessed outside of the module. For example, you can access "yii.sample.isActive". * * You must call "yii.initModule()" once for the root module of all your modules. */ yii = (function ($) { var pub = { /** + * The selector for links that support confirmation and form submission. + */ + linkClickSelector: 'a[data-confirm], a[data-method]', + + /** * @return string|undefined the CSRF variable name. Undefined is returned is CSRF validation is not enabled. */ - getCsrfVar: function() { + getCsrfVar: function () { return $('meta[name=csrf-var]').prop('content'); }, /** * @return string|undefined the CSRF token. Undefined is returned is CSRF validation is not enabled. */ - getCsrfToken: function() { + getCsrfToken: function () { return $('meta[name=csrf-token]').prop('content'); }, + /** + * Displays a confirmation dialog. + * The default implementation simply displays a js confirmation dialog. + * You may override this by setting `yii.confirm`. + * @param message the confirmation message. + * @return boolean whether the user confirms with the message in the dialog + */ + confirm: function (message) { + return confirm(message); + }, + + /** + * Returns a value indicating whether to allow executing the action defined for the specified element. + * @param $e the jQuery representation of the element + * @return boolean whether to allow executing the action defined for the specified element. + */ + allowAction: function ($e) { + var message = $e.data('confirm'); + if (!message) { + return true; + } + return pub.confirm(message); + }, + + /** + * Handles form submission triggered by elements with "method" data attribute. + * If the element is enclosed within an existing form, the form will be submitted. + * Otherwise, a new form will be created and submitted. The new form's method and action + * are determined using the element's "method" and "action" data attributes, respectively. + * If the "action" data attribute is not specified, it will try the "href" property and + * the current URL. + * @param $e the jQuery representation of the element + */ + handleSubmit: function ($e) { + var method = $e.data('method'); + if (method === undefined) { + return; + } + + var $form = $e.closest('form'); + if (!$form.length) { + var action = $e.data('action'); + if (action === undefined) { + action = $e.prop('href'); + if (action === undefined) { + action = window.location.href; + } + } + $form = $('
'); + var target = $e.prop('target'); + if (target) { + $form.attr('target', target); + } + if (!method.match(/(get|post)/i)) { + $form.append(''); + } + var csrfVar = pub.getCsrfVar(); + if (csrfVar) { + $form.append(''); + } + $form.hide().appendTo('body'); + } + + var activeFormData = $form.data('yiiActiveForm'); + if (activeFormData) { + // remember who triggers the form submission. This is used by yii.activeForm.js + activeFormData.submitObject = $e; + } + + $form.trigger('submit'); + }, + initModule: function (module) { if (module.isActive === undefined || module.isActive) { if ($.isFunction(module.init)) { @@ -68,6 +145,23 @@ yii = (function ($) { } }); } + }, + + init: function () { + var $document = $(document); + + $document.on('click.yii', pub.linkClickSelector, function () { + var $this = $(this); + if (!pub.allowAction($this)) { + $this.stopImmediatePropagation(); + return false; + } else { + if ($this.data('method')) { + pub.handleSubmit($this); + return false; + } + } + }); } }; return pub; From f9b957554f2b7f2b08747d4ecd3da0effd7bc51b Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Wed, 18 Sep 2013 23:24:11 -0400 Subject: [PATCH 10/30] Added Controller::enableCsrfValidation to support turning on/off CSRF validation for particular actions. --- framework/yii/base/Controller.php | 2 ++ framework/yii/web/Controller.php | 18 ++++++++++++++++++ framework/yii/web/Request.php | 4 ++-- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/framework/yii/base/Controller.php b/framework/yii/base/Controller.php index 20f6e2b..3eebaa0 100644 --- a/framework/yii/base/Controller.php +++ b/framework/yii/base/Controller.php @@ -210,6 +210,7 @@ class Controller extends Component /** * This method is invoked right before an action is to be executed (after all possible filters.) * You may override this method to do last-minute preparation for the action. + * If you override this method, please make sure you call the parent implementation first. * @param Action $action the action to be executed. * @return boolean whether the action should continue to be executed. */ @@ -223,6 +224,7 @@ class Controller extends Component /** * This method is invoked right after an action is executed. * You may override this method to do some postprocessing for the action. + * If you override this method, please make sure you call the parent implementation first. * @param Action $action the action just executed. * @param mixed $result the action return result. */ diff --git a/framework/yii/web/Controller.php b/framework/yii/web/Controller.php index adb1b4d..9238063 100644 --- a/framework/yii/web/Controller.php +++ b/framework/yii/web/Controller.php @@ -20,6 +20,12 @@ use yii\helpers\Html; class Controller extends \yii\base\Controller { /** + * @var boolean whether to enable CSRF validation for the actions in this controller. + * CSRF validation is enabled only when both this property and [[Request::enableCsrfValidation]] are true. + */ + public $enableCsrfValidation = true; + + /** * Binds the parameters to the action. * This method is invoked by [[Action]] when it begins to run with the given parameters. * This method will check the parameter names that the action requires and return @@ -62,6 +68,18 @@ class Controller extends \yii\base\Controller } /** + * @inheritdoc + */ + public function beforeAction($action) + { + if (parent::beforeAction($action)) { + return !$this->enableCsrfValidation || Yii::$app->getRequest()->validateCsrfToken(); + } else { + return false; + } + } + + /** * Creates a URL using the given route and parameters. * * This method enhances [[UrlManager::createUrl()]] by supporting relative routes. diff --git a/framework/yii/web/Request.php b/framework/yii/web/Request.php index 9e625f7..1186e05 100644 --- a/framework/yii/web/Request.php +++ b/framework/yii/web/Request.php @@ -87,6 +87,7 @@ class Request extends \yii\base\Request * In JavaScript, you may get the values of [[csrfVar]] and [[csrfToken]] via `yii.getCsrfVar()` and * `yii.getCsrfToken()`, respectively. The [[\yii\web\YiiAsset]] asset must be registered. * + * @see Controller::enableCsrfValidation * @see http://en.wikipedia.org/wiki/Cross-site_request_forgery */ public $enableCsrfValidation = false; @@ -122,8 +123,6 @@ class Request extends \yii\base\Request */ public function resolve() { - $this->validateCsrfToken(); - $result = Yii::$app->getUrlManager()->parseRequest($this); if ($result !== false) { list ($route, $params) = $result; @@ -1023,6 +1022,7 @@ class Request extends \yii\base\Request * Performs the CSRF validation. * The method will compare the CSRF token obtained from a cookie and from a POST field. * If they are different, a CSRF attack is detected and a 400 HTTP exception will be raised. + * This method is called in [[Controller::beforeAction()]]. * @throws HttpException if the validation fails */ public function validateCsrfToken() From 4f555a57517bfe7c6e59d4a24e574965ebffe942 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Thu, 19 Sep 2013 08:17:07 -0400 Subject: [PATCH 11/30] Fixed CSRF validation bug. --- framework/yii/web/Controller.php | 5 ++++- framework/yii/web/Request.php | 11 +++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/framework/yii/web/Controller.php b/framework/yii/web/Controller.php index 9238063..773e2de 100644 --- a/framework/yii/web/Controller.php +++ b/framework/yii/web/Controller.php @@ -73,7 +73,10 @@ class Controller extends \yii\base\Controller public function beforeAction($action) { if (parent::beforeAction($action)) { - return !$this->enableCsrfValidation || Yii::$app->getRequest()->validateCsrfToken(); + if ($this->enableCsrfValidation && !Yii::$app->getRequest()->validateCsrfToken()) { + throw new HttpException(400, Yii::t('yii', 'Unable to verify your data submission.')); + } + return true; } else { return false; } diff --git a/framework/yii/web/Request.php b/framework/yii/web/Request.php index 1186e05..6b805ea 100644 --- a/framework/yii/web/Request.php +++ b/framework/yii/web/Request.php @@ -1023,12 +1023,12 @@ class Request extends \yii\base\Request * The method will compare the CSRF token obtained from a cookie and from a POST field. * If they are different, a CSRF attack is detected and a 400 HTTP exception will be raised. * This method is called in [[Controller::beforeAction()]]. - * @throws HttpException if the validation fails + * @return boolean whether CSRF token is valid. If [[enableCsrfValidation]] is false, this method will return true. */ public function validateCsrfToken() { if (!$this->enableCsrfValidation) { - return; + return true; } $method = $this->getMethod(); if ($method === 'POST' || $method === 'PUT' || $method === 'PATCH' || $method === 'DELETE') { @@ -1047,10 +1047,9 @@ class Request extends \yii\base\Request $token = $this->getDelete($this->csrfVar); } - $valid = !empty($token) && $token === $trueToken || $this->getCsrfTokenFromHeader() === $trueToken; - if (!$valid) { - throw new HttpException(400, Yii::t('yii', 'Unable to verify your data submission.')); - } + return !empty($token) && $token === $trueToken || $this->getCsrfTokenFromHeader() === $trueToken; + } else { + return true; } } } From 1aeb86df78148bd3793ebac2047150ec4e2c57ce Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Thu, 19 Sep 2013 08:24:06 -0400 Subject: [PATCH 12/30] refactored Request::validateCsrfToken(). --- framework/yii/web/Request.php | 38 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/framework/yii/web/Request.php b/framework/yii/web/Request.php index 6b805ea..a07deaa 100644 --- a/framework/yii/web/Request.php +++ b/framework/yii/web/Request.php @@ -1027,29 +1027,25 @@ class Request extends \yii\base\Request */ public function validateCsrfToken() { - if (!$this->enableCsrfValidation) { - return true; - } $method = $this->getMethod(); - if ($method === 'POST' || $method === 'PUT' || $method === 'PATCH' || $method === 'DELETE') { - $trueToken = $this->getCookies()->getValue($this->csrfVar); - switch ($method) { - case 'POST': - $token = $this->getPost($this->csrfVar); - break; - case 'PUT': - $token = $this->getPut($this->csrfVar); - break; - case 'PATCH': - $token = $this->getPatch($this->csrfVar); - break; - case 'DELETE': - $token = $this->getDelete($this->csrfVar); - } - - return !empty($token) && $token === $trueToken || $this->getCsrfTokenFromHeader() === $trueToken; - } else { + if (!$this->enableCsrfValidation || !in_array($method, array('POST', 'PUT', 'PATCH', 'DELETE'), true)) { return true; } + $trueToken = $this->getCookies()->getValue($this->csrfVar); + switch ($method) { + case 'PUT': + $token = $this->getPut($this->csrfVar); + break; + case 'PATCH': + $token = $this->getPatch($this->csrfVar); + break; + case 'DELETE': + $token = $this->getDelete($this->csrfVar); + break; + default: + $token = $this->getPost($this->csrfVar); + break; + } + return $token === $trueToken || $this->getCsrfTokenFromHeader() === $trueToken; } } From 41f7a7d243a95fe879bfe2ff437e9e77e7d2f5aa Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Fri, 20 Sep 2013 14:57:41 -0400 Subject: [PATCH 13/30] Supports more elements to use data-confirm and data-method attributes. --- framework/yii/assets/yii.js | 75 ++++++++++++++++++++++++++++----------------- 1 file changed, 47 insertions(+), 28 deletions(-) diff --git a/framework/yii/assets/yii.js b/framework/yii/assets/yii.js index b1fb0cb..99a314e 100644 --- a/framework/yii/assets/yii.js +++ b/framework/yii/assets/yii.js @@ -44,9 +44,13 @@ yii = (function ($) { var pub = { /** - * The selector for links that support confirmation and form submission. + * The selector for clickable elements that need to support confirmation and form submission. */ - linkClickSelector: 'a[data-confirm], a[data-method]', + clickableSelector: 'a, button, input[type="submit"], input[type="button"], input[type="reset"], input[type="image"]', + /** + * The selector for changeable elements that need to support confirmation and form submission. + */ + changeableSelector: 'select, input, textarea', /** * @return string|undefined the CSRF variable name. Undefined is returned is CSRF validation is not enabled. @@ -75,40 +79,43 @@ yii = (function ($) { /** * Returns a value indicating whether to allow executing the action defined for the specified element. + * This method recognizes the `data-confirm` attribute of the element and uses it + * as the message in a confirmation dialog. The method will return true if this special attribute + * is not defined or if the user confirms the message. * @param $e the jQuery representation of the element * @return boolean whether to allow executing the action defined for the specified element. */ allowAction: function ($e) { var message = $e.data('confirm'); - if (!message) { - return true; - } - return pub.confirm(message); + return message === undefined || pub.confirm(message); }, /** - * Handles form submission triggered by elements with "method" data attribute. - * If the element is enclosed within an existing form, the form will be submitted. - * Otherwise, a new form will be created and submitted. The new form's method and action - * are determined using the element's "method" and "action" data attributes, respectively. - * If the "action" data attribute is not specified, it will try the "href" property and - * the current URL. + * Handles the action triggered by user. + * This method recognizes the `data-method` attribute of the element. If the attribute exists, + * the method will submit the form containing this element. If there is no containing form, a form + * will be created and submitted using the method given by this attribute value (e.g. "post", "put"). + * For hyperlinks, the form action will take the value of the "href" attribute of the link. + * For other elements, either the containing form action or the current page URL will be used + * as the form action URL. + * + * If the `data-method` attribute is not defined, the default element action will be performed. + * * @param $e the jQuery representation of the element + * @return boolean whether to execute the default action for the element. */ - handleSubmit: function ($e) { + handleAction: function ($e) { var method = $e.data('method'); if (method === undefined) { - return; + return true; } var $form = $e.closest('form'); - if (!$form.length) { - var action = $e.data('action'); - if (action === undefined) { - action = $e.prop('href'); - if (action === undefined) { - action = window.location.href; - } + var newForm = !$form.length; + if (newForm) { + var action = $e.prop('href'); + if (!action || !action.match(/(^\/|:\/\/)/)) { + action = window.location.href; } $form = $('
'); var target = $e.prop('target'); @@ -132,6 +139,12 @@ yii = (function ($) { } $form.trigger('submit'); + + if (newForm) { + $form.remove(); + } + + return false; }, initModule: function (module) { @@ -150,16 +163,22 @@ yii = (function ($) { init: function () { var $document = $(document); - $document.on('click.yii', pub.linkClickSelector, function () { + $document.on('click.yii', pub.clickableSelector, function (event) { var $this = $(this); - if (!pub.allowAction($this)) { - $this.stopImmediatePropagation(); + if (pub.allowAction($this)) { + return pub.handleAction($this); + } else { + event.stopImmediatePropagation(); return false; + } + }); + $document.on('change.yii', pub.changeableSelector, function (event) { + var $this = $(this); + if (pub.allowAction($this)) { + return pub.handleAction($this); } else { - if ($this.data('method')) { - pub.handleSubmit($this); - return false; - } + event.stopImmediatePropagation(); + return false; } }); } From 3acca93ad3b4d1d5340730c19f6e932fbb4549af Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Fri, 20 Sep 2013 15:04:28 -0400 Subject: [PATCH 14/30] Enable CSRF validation by default. --- framework/yii/assets/yii.js | 6 ++++++ framework/yii/web/Request.php | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/framework/yii/assets/yii.js b/framework/yii/assets/yii.js index 99a314e..22f92a5 100644 --- a/framework/yii/assets/yii.js +++ b/framework/yii/assets/yii.js @@ -163,6 +163,12 @@ yii = (function ($) { init: function () { var $document = $(document); + $.ajaxPrefilter(function (options, originalOptions, xhr) { + if (!options.crossDomain && pub.getCsrfVar()) { + xhr.setRequestHeader('X-CSRF-TOKEN', pub.getCsrfToken()); + } + }); + $document.on('click.yii', pub.clickableSelector, function (event) { var $this = $(this); if (pub.allowAction($this)) { diff --git a/framework/yii/web/Request.php b/framework/yii/web/Request.php index a07deaa..76c8883 100644 --- a/framework/yii/web/Request.php +++ b/framework/yii/web/Request.php @@ -76,7 +76,7 @@ class Request extends \yii\base\Request const CSRF_HEADER = 'X-CSRF-TOKEN'; /** - * @var boolean whether to enable CSRF (Cross-Site Request Forgery) validation. Defaults to false. + * @var boolean whether to enable CSRF (Cross-Site Request Forgery) validation. Defaults to true. * When CSRF validation is enabled, forms submitted to an Yii Web application must be originated * from the same application. If not, a 400 HTTP exception will be raised. * @@ -90,7 +90,7 @@ class Request extends \yii\base\Request * @see Controller::enableCsrfValidation * @see http://en.wikipedia.org/wiki/Cross-site_request_forgery */ - public $enableCsrfValidation = false; + public $enableCsrfValidation = true; /** * @var string the name of the token used to prevent CSRF. Defaults to '_csrf'. * This property is used only when [[enableCsrfValidation]] is true. From ae39324e5491f8e41bbc8e1b1d2a0d1e34fded54 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Fri, 20 Sep 2013 16:21:18 -0400 Subject: [PATCH 15/30] Support ajax redirection. --- framework/yii/assets/yii.js | 14 +++++++++++++- framework/yii/web/Controller.php | 3 +-- framework/yii/web/Request.php | 2 +- framework/yii/web/Response.php | 36 ++++++++++++++++++------------------ 4 files changed, 33 insertions(+), 22 deletions(-) diff --git a/framework/yii/assets/yii.js b/framework/yii/assets/yii.js index 22f92a5..add3a02 100644 --- a/framework/yii/assets/yii.js +++ b/framework/yii/assets/yii.js @@ -163,12 +163,22 @@ yii = (function ($) { init: function () { var $document = $(document); + // automatically send CSRF token for all AJAX requests $.ajaxPrefilter(function (options, originalOptions, xhr) { if (!options.crossDomain && pub.getCsrfVar()) { - xhr.setRequestHeader('X-CSRF-TOKEN', pub.getCsrfToken()); + xhr.setRequestHeader('X-CSRF-Token', pub.getCsrfToken()); } }); + // handle AJAX redirection + $document.ajaxComplete(function (event, xhr, settings) { + var url = xhr.getResponseHeader('X-Redirect'); + if (url) { + window.location = url; + } + }); + + // handle data-confirm and data-method for clickable elements $document.on('click.yii', pub.clickableSelector, function (event) { var $this = $(this); if (pub.allowAction($this)) { @@ -178,6 +188,8 @@ yii = (function ($) { return false; } }); + + // handle data-confirm and data-method for changeable elements $document.on('change.yii', pub.changeableSelector, function (event) { var $this = $(this); if (pub.allowAction($this)) { diff --git a/framework/yii/web/Controller.php b/framework/yii/web/Controller.php index 773e2de..6b8afa4 100644 --- a/framework/yii/web/Controller.php +++ b/framework/yii/web/Controller.php @@ -126,8 +126,7 @@ class Controller extends \yii\base\Controller * Any relative URL will be converted into an absolute one by prepending it with the host info * of the current request. * - * @param integer $statusCode the HTTP status code. If null, it will use 302 - * for normal requests, and [[ajaxRedirectCode]] for AJAX requests. + * @param integer $statusCode the HTTP status code. If null, it will use 302. * See [[http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html]] * for details about HTTP status code * @return Response the current response object diff --git a/framework/yii/web/Request.php b/framework/yii/web/Request.php index 76c8883..c76fd4e 100644 --- a/framework/yii/web/Request.php +++ b/framework/yii/web/Request.php @@ -73,7 +73,7 @@ class Request extends \yii\base\Request /** * The name of the HTTP header for sending CSRF token. */ - const CSRF_HEADER = 'X-CSRF-TOKEN'; + const CSRF_HEADER = 'X-CSRF-Token'; /** * @var boolean whether to enable CSRF (Cross-Site Request Forgery) validation. Defaults to true. diff --git a/framework/yii/web/Response.php b/framework/yii/web/Response.php index cfbc537..979cce0 100644 --- a/framework/yii/web/Response.php +++ b/framework/yii/web/Response.php @@ -112,13 +112,6 @@ class Response extends \yii\base\Response */ public $charset; /** - * @var integer the HTTP status code that should be used when redirecting in AJAX mode. - * This is used by [[redirect()]]. A 2xx code should normally be used for this purpose - * so that the AJAX handler will treat the response as a success. - * @see redirect - */ - public $ajaxRedirectCode = 278; - /** * @var string */ public $statusText; @@ -565,17 +558,22 @@ class Response extends \yii\base\Response /** * Redirects the browser to the specified URL. + * * This method will send out a "Location" header to achieve the redirection. + * * In AJAX mode, this normally will not work as expected unless there are some * client-side JavaScript code handling the redirection. To help achieve this goal, - * this method will use [[ajaxRedirectCode]] as the HTTP status code when performing - * redirection in AJAX mode. The following JavaScript code may be used on the client - * side to handle the redirection response: + * this method will send out a "X-Redirect" header instead of "Location". + * + * If you use the "yii" JavaScript module, it will handle the AJAX redirection as + * described above. Otherwise, you should write the following JavaScript code to + * handle the redirection: * * ~~~ - * $(document).ajaxSuccess(function(event, xhr, settings) { - * if (xhr.status == 278) { - * window.location = xhr.getResponseHeader('Location'); + * $document.ajaxComplete(function (event, xhr, settings) { + * var url = xhr.getResponseHeader('X-Redirect'); + * if (url) { + * window.location = url; * } * }); * ~~~ @@ -597,8 +595,7 @@ class Response extends \yii\base\Response * Any relative URL will be converted into an absolute one by prepending it with the host info * of the current request. * - * @param integer $statusCode the HTTP status code. If null, it will use 302 - * for normal requests, and [[ajaxRedirectCode]] for AJAX requests. + * @param integer $statusCode the HTTP status code. If null, it will use 302. * See [[http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html]] * for details about HTTP status code * @return Response the response object itself @@ -613,11 +610,14 @@ class Response extends \yii\base\Response if (strpos($url, '/') === 0 && strpos($url, '//') !== 0) { $url = Yii::$app->getRequest()->getHostInfo() . $url; } - if ($statusCode === null) { - $statusCode = Yii::$app->getRequest()->getIsAjax() ? $this->ajaxRedirectCode : 302; + + if (Yii::$app->getRequest()->getIsAjax()) { + $this->getHeaders()->set('X-Redirect', $url); + } else { + $this->getHeaders()->set('Location', $url); } - $this->getHeaders()->set('Location', $url); $this->setStatusCode($statusCode); + return $this; } From 463ff43b4feae6fe7c20cb2cd89f44d391a11225 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Fri, 20 Sep 2013 16:39:22 -0400 Subject: [PATCH 16/30] test break fix. --- tests/unit/framework/helpers/HtmlTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/framework/helpers/HtmlTest.php b/tests/unit/framework/helpers/HtmlTest.php index 857f9c2..88aa33a 100644 --- a/tests/unit/framework/helpers/HtmlTest.php +++ b/tests/unit/framework/helpers/HtmlTest.php @@ -19,6 +19,7 @@ class HtmlTest extends TestCase 'request' => array( 'class' => 'yii\web\Request', 'url' => '/test', + 'enableCsrfValidation' => false, ), 'response' => array( 'class' => 'yii\web\Response', From dde7d731a53310ff233efe29e2dcf81f3c868fea Mon Sep 17 00:00:00 2001 From: Larry Ullman Date: Fri, 20 Sep 2013 20:22:51 -0400 Subject: [PATCH 17/30] Doing more editing... --- docs/guide/bootstrap-widgets.md | 6 +++--- docs/guide/overview.md | 2 +- docs/guide/security.md | 7 ++----- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/docs/guide/bootstrap-widgets.md b/docs/guide/bootstrap-widgets.md index 3f56839..432dcd8 100644 --- a/docs/guide/bootstrap-widgets.md +++ b/docs/guide/bootstrap-widgets.md @@ -1,17 +1,17 @@ Bootstrap widgets ================= -Yii includes support for the [Bootstrap 3](http://getbootstrap.com/) markup and components framework out of the box. Bootstrap is an excellent, responsive framework that can greatly speed up your development process. +Out of the box, Yii includes support for the [Bootstrap 3](http://getbootstrap.com/) markup and components framework (also known as "Twitter Bootstrap"). Bootstrap is an excellent, responsive framework that can greatly speed up the client-side of your development process. The core of Bootstrap is represented by two parts: -- CSS basics, such as grid layout system, typography, helper classes, and responsive utilities. +- CSS basics, such as a grid layout system, typography, helper classes, and responsive utilities. - Ready to use components, such as menus, pagination, modal boxes, tabs etc. Basics ------ -Yii doesn't wrap bootstrap basics into PHP code since HTML is very simple by itself in this case. You can find details +Yii doesn't wrap the bootstrap basics into PHP code since HTML is very simple by itself in this case. You can find details about using the basics at [bootstrap documentation website](http://getbootstrap.com/css/). Still Yii provides a convenient way to include bootstrap assets in your pages with a single line added to `AppAsset.php` located in your `config` directory: diff --git a/docs/guide/overview.md b/docs/guide/overview.md index ef71aa0..835c511 100644 --- a/docs/guide/overview.md +++ b/docs/guide/overview.md @@ -24,7 +24,7 @@ Yii is a generic Web programming framework that can be used for developing virtually any type of Web application. Because it is light-weight and equipped with sophisticated caching mechanisms, it is especially suited to high-traffic applications, such as portals, forums, content -management systems (CMS), e-commerce systems, etc. +management systems (CMS), e-commerce projects, etc. How does Yii Compare with Other Frameworks? diff --git a/docs/guide/security.md b/docs/guide/security.md index f9adf7c..af30e5b 100644 --- a/docs/guide/security.md +++ b/docs/guide/security.md @@ -4,12 +4,9 @@ Security Hashing and verifying passwords ------------------------------ -It is important not to store passwords in plain text but, contrary to popular belief, just using `md5` or `sha1` to -compute and verify hashes isn't a good way either. Modern hardware allows to brute force these very fast. +Most developers know that you cannot store passwords in plain text, but many believe it's safe to hash passwords using `md5` or `sha1`. There was a time when those hashing algorithms were sufficient, but modern hardware makes it possible to break those hashes very quickly using a brute force attack. -In order to truly secure user passwords even in case your database is leaked you need to use a function that is resistant -to brute-force such as bcrypt. In PHP it can be achieved by using [crypt function](http://php.net/manual/en/function.crypt.php) -but since usage isn't trivial and one can easily misuse it, Yii provides two helper functions for generating hash from +In order to truly secure user passwords, even in the worst case scenario (your database is broken into), you need to use a hashing algorithm that is resistant to brute force attacks. The best current choice is bcrypt. In PHP, you can create a bcrypt hash by using [crypt function](http://php.net/manual/en/function.crypt.php). However, this function is not easy to use properly, so Yii provides two helper functions for generating hash from password and verifying existing hash. When user sets his password we're taking password string from POST and then getting a hash: From 73cdbb37c0831a39340800ff7d5296c8c3e7a687 Mon Sep 17 00:00:00 2001 From: iJackUA Date: Sat, 21 Sep 2013 17:20:40 +0300 Subject: [PATCH 18/30] Fix parenthesis typo in CRUD index template it was causing blocking PHP error --- framework/yii/gii/generators/crud/templates/views/index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/gii/generators/crud/templates/views/index.php b/framework/yii/gii/generators/crud/templates/views/index.php index 8efa53a..98d35b3 100644 --- a/framework/yii/gii/generators/crud/templates/views/index.php +++ b/framework/yii/gii/generators/crud/templates/views/index.php @@ -63,7 +63,7 @@ foreach ($generator->getTableSchema()->columns as $column) { 'class' => 'item', ), 'itemView' => function ($model, $key, $index, $widget) { - return Html::a(Html::encode($model->), array('view', ); + return Html::a(Html::encode($model->), array('view', )); }, )); ?> From 2765bcccf8e5f9918a605423982f7889327e18ff Mon Sep 17 00:00:00 2001 From: Niko Wicaksono Date: Sun, 22 Sep 2013 00:49:12 +0700 Subject: [PATCH 19/30] Use str_replace() rather than implode-explode --- framework/yii/base/Module.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/base/Module.php b/framework/yii/base/Module.php index a85385b..9b98a3c 100644 --- a/framework/yii/base/Module.php +++ b/framework/yii/base/Module.php @@ -615,7 +615,7 @@ abstract class Module extends Component if (isset($this->controllerMap[$id])) { $controller = Yii::createObject($this->controllerMap[$id], $id, $this); } elseif (preg_match('/^[a-z0-9\\-_]+$/', $id)) { - $className = str_replace(' ', '', ucwords(implode(' ', explode('-', $id)))) . 'Controller'; + $className = str_replace(' ', '', ucwords(str_replace('-', ' ', $id))) . 'Controller'; $classFile = $this->controllerPath . DIRECTORY_SEPARATOR . $className . '.php'; if (!is_file($classFile)) { return false; From 93d5f5a3ec1b71b1ff6130f071d7d1f4927155e0 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sun, 22 Sep 2013 13:01:27 -0400 Subject: [PATCH 20/30] Fixes #897. --- framework/yii/base/ErrorHandler.php | 2 ++ framework/yii/web/Request.php | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/framework/yii/base/ErrorHandler.php b/framework/yii/base/ErrorHandler.php index 41fa7f9..40f5c37 100644 --- a/framework/yii/base/ErrorHandler.php +++ b/framework/yii/base/ErrorHandler.php @@ -93,6 +93,8 @@ class ErrorHandler extends Component $response->getHeaders()->removeAll(); if ($useErrorView && $this->errorAction !== null) { + // disable CSRF validation so that errorAction can run in case the error is caused by CSRF validation failure + Yii::$app->getRequest()->enableCsrfValidation = false; $result = Yii::$app->runAction($this->errorAction); if ($result instanceof Response) { $response = $result; diff --git a/framework/yii/web/Request.php b/framework/yii/web/Request.php index c76fd4e..4fb6257 100644 --- a/framework/yii/web/Request.php +++ b/framework/yii/web/Request.php @@ -1001,7 +1001,8 @@ class Request extends \yii\base\Request */ public function getCsrfTokenFromHeader() { - return isset($_SERVER[self::CSRF_HEADER]) ? $_SERVER[self::CSRF_HEADER] : null; + $key = 'HTTP_' . str_replace('-', '_', strtoupper(self::CSRF_HEADER)); + return isset($_SERVER[$key]) ? $_SERVER[$key] : null; } /** From f415193cdc62e1e2c9094a72bee7621e50b52232 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sun, 22 Sep 2013 17:02:46 -0400 Subject: [PATCH 21/30] Fixes #898: supported different signature of MemCache::addServer(). --- framework/yii/caching/MemCache.php | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/framework/yii/caching/MemCache.php b/framework/yii/caching/MemCache.php index 53202f0..69a90b4 100644 --- a/framework/yii/caching/MemCache.php +++ b/framework/yii/caching/MemCache.php @@ -87,7 +87,14 @@ class MemCache extends Cache parent::init(); $servers = $this->getServers(); $cache = $this->getMemCache(); - if (count($servers)) { + if (empty($servers)) { + $cache->addServer('127.0.0.1', 11211); + } else { + if (!$this->useMemcached) { + // different version of memcache may have different number of parameters for the addServer method. + $class = new \ReflectionClass($cache); + $paramCount = $class->getMethod('addServer')->getNumberOfParameters(); + } foreach ($servers as $server) { if ($server->host === null) { throw new InvalidConfigException("The 'host' property must be specified for every memcache server."); @@ -97,15 +104,21 @@ class MemCache extends Cache } else { // $timeout is used for memcache versions that do not have timeoutms parameter $timeout = (int) ($server->timeout / 1000) + (($server->timeout % 1000 > 0) ? 1 : 0); - $cache->addServer( - $server->host, $server->port, $server->persistent, - $server->weight, $timeout, $server->retryInterval, - $server->status, $server->failureCallback, $server->timeout - ); + if ($paramCount === 9) { + $cache->addServer( + $server->host, $server->port, $server->persistent, + $server->weight, $timeout, $server->retryInterval, + $server->status, $server->failureCallback, $server->timeout + ); + } else { + $cache->addServer( + $server->host, $server->port, $server->persistent, + $server->weight, $timeout, $server->retryInterval, + $server->status, $server->failureCallback + ); + } } } - } else { - $cache->addServer('127.0.0.1', 11211); } } From 9861ed36cd5c7ef7e2a39f3f49011fd737742295 Mon Sep 17 00:00:00 2001 From: Larry Ullman Date: Sun, 22 Sep 2013 20:35:56 -0400 Subject: [PATCH 22/30] Edited introduction --- docs/guide/model.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/guide/model.md b/docs/guide/model.md index fec7ac3..b93fb7a 100644 --- a/docs/guide/model.md +++ b/docs/guide/model.md @@ -1,15 +1,15 @@ Model ===== -A model in Yii is intended for application data storage and has the following basic features: +In keeping with the MVC approach, a model in Yii is intended for storing or temporarily representing application data. Yii models have the following basic features: -- attribute declaration: a model defines what is considered an attribute. -- attribute labels: each attribute may be associated with a label for display purpose. -- massive attribute assignment. -- scenario-based data validation. +- Attribute declaration: a model defines what is considered an attribute. +- Attribute labels: each attribute may be associated with a label for display purpose. +- Massive attribute assignment: the ability to populate multiple model attributes in one step. +- Scenario-based data validation. -Models extending from [[\yii\base\Model]] class are typically used to hold data and corresponding validation rules of complex web forms. -The class is also a base for more advanced models with additional functionality such as [Active Record](active-record.md). +Models in Yii extend from the [[\yii\base\Model]] class. Models are typically used to both hold data and define the validation rules for that data. The validation rules greatly simply the generation of models from complex web forms. +The Model class is also the base for more advanced models with additional functionality such as [Active Record](active-record.md). Attributes ---------- From 43d392d8d2fe04271842e6ff64575ba0a78edebf Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Mon, 23 Sep 2013 07:18:42 -0400 Subject: [PATCH 23/30] porting the fix from https://github.com/yiisoft/yii/pull/2894 --- framework/yii/db/QueryBuilder.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/yii/db/QueryBuilder.php b/framework/yii/db/QueryBuilder.php index 3cc7971..f210f65 100644 --- a/framework/yii/db/QueryBuilder.php +++ b/framework/yii/db/QueryBuilder.php @@ -585,7 +585,7 @@ class QueryBuilder extends \yii\base\Object foreach ($tables as $i => $table) { if (strpos($table, '(') === false) { - if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)(.*)$/i', $table, $matches)) { // with alias + if (preg_match('/^(.*?)(?i:\s+as|)\s+([^ ]+)$/', $table, $matches)) { // with alias $tables[$i] = $this->db->quoteTableName($matches[1]) . ' ' . $this->db->quoteTableName($matches[2]); } else { $tables[$i] = $this->db->quoteTableName($table); @@ -619,7 +619,7 @@ class QueryBuilder extends \yii\base\Object // 0:join type, 1:table name, 2:on-condition $table = $join[1]; if (strpos($table, '(') === false) { - if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)(.*)$/', $table, $matches)) { // with alias + if (preg_match('/^(.*?)(?i:\s+as|)\s+([^ ]+)$/', $table, $matches)) { // with alias $table = $this->db->quoteTableName($matches[1]) . ' ' . $this->db->quoteTableName($matches[2]); } else { $table = $this->db->quoteTableName($table); From 4d901acd8e0e307b73b040e2169d20fedaf8abb8 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Mon, 23 Sep 2013 18:01:13 +0400 Subject: [PATCH 24/30] Advanced application template: removed unused scenario from User model --- apps/advanced/common/models/User.php | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/advanced/common/models/User.php b/apps/advanced/common/models/User.php index e4101e8..da8f067 100644 --- a/apps/advanced/common/models/User.php +++ b/apps/advanced/common/models/User.php @@ -123,7 +123,6 @@ class User extends ActiveRecord implements Identity { return array( 'signup' => array('username', 'email', 'password'), - 'login' => array('username', 'password'), 'resetPassword' => array('password'), 'requestPasswordResetToken' => array('email'), ); From c05477b14144f1bf3970f809356407afc4631063 Mon Sep 17 00:00:00 2001 From: Larry Ullman Date: Mon, 23 Sep 2013 17:03:15 -0400 Subject: [PATCH 25/30] Polished up the basic discussion of template alternatives --- docs/guide/template.md | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/docs/guide/template.md b/docs/guide/template.md index 6d2db88..f9405ff 100644 --- a/docs/guide/template.md +++ b/docs/guide/template.md @@ -3,8 +3,8 @@ Using template engines By default Yii uses PHP as template language, but you can configure it to support other rendering engines, such as [Twig](http://twig.sensiolabs.org/) or [Smarty](http://www.smarty.net/). -The component responsible for rendering a view is called `view`. You can add -a custom template engines as follows: +The `view` component is responsible for rendering views. You can add +a custom template engines by reconfiguring this component's behavior: ```php array( @@ -26,15 +26,13 @@ array( ) ``` -Note that Smarty and Twig are not bundled with Yii and you have to download and -unpack these yourself and then specify `twigPath` and `smartyPath` respectively. +Note that the Smarty and Twig packages themselves are not bundled with Yii. You must download them yourself. Then unpack the packages and place the resulting files in a logical location, such as the application's `protected/vendor` folder. Finally, specify the correct `smartyPath` or `twigPath`, as in the code above (for Twig). Twig ---- -In order to use Twig you need to put you templates in files with extension `.twig` -(or another one if configured differently). -Also you need to specify this extension explicitly when calling `$this->render()` +To use Twig, you need to create templates in files with the `.twig` extension (or use another file extension but configure the component accordingly). +Unlike standard view files, when using Twig, you must include the extension when calling `$this->render()` or `$this->renderPartial()` from your controller: ```php @@ -43,25 +41,25 @@ echo $this->render('renderer.twig', array('username' => 'Alex')); ### Additional functions -Additionally to regular Twig syntax the following is available in Yii: +Yii adds the following construct to the standard Twig syntax: ```php {{ post.title }} ``` -path function calls `Html::url()` internally. +Internally, the `path()` function calls Yii's `Html::url()` method. ### Additional variables -- `app` = `\Yii::$app` -- `this` = current `View` object +Within Twig templates, you can also make use of these variables: + +- `app`, which equates to `\Yii::$app` +- `this`, which equates to the current `View` object Smarty ------ -In order to use Smarty you need to put you templates in files with extension `.tpl` -(or another one if configured differently). -Also you need to specify this extension explicitly when calling `$this->render()` +To use Smarty, you need to create templates in files with the `.tpl` extension (or use another file extension but configure the component accordingly). Unlike standard view files, when using Smarty, you must include the extension when calling `$this->render()` or `$this->renderPartial()` from your controller: ```php @@ -70,16 +68,18 @@ echo $this->render('renderer.tpl', array('username' => 'Alex')); ### Additional functions -Additionally to regular Smarty syntax the following is available in Yii: +Yii adds the following construct to the standard Smarty syntax: ```php {$post.title} ``` -path function calls `Html::url()` internally. +Internally, the `path()` function calls Yii's `Html::url()` method. ### Additional variables -- `$app` = `\Yii::$app` -- `$this` = current `View` object +Within Smarty templates, you can also make use of these variables: + +- `$app`, which equates to `\Yii::$app` +- `$this`, which equates to the current `View` object From 76154b97766f7a4ef77d6d13480ceeafea7205e9 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Mon, 23 Sep 2013 20:38:54 -0400 Subject: [PATCH 26/30] Fixes #901: Added $delete parameter to Session::getFlash(). --- framework/yii/web/Session.php | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/framework/yii/web/Session.php b/framework/yii/web/Session.php index fc9fe16..92ec3ad 100644 --- a/framework/yii/web/Session.php +++ b/framework/yii/web/Session.php @@ -582,12 +582,22 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co * A flash message is available only in the current request and the next request. * @param string $key the key identifying the flash message * @param mixed $defaultValue value to be returned if the flash message does not exist. + * @param boolean $delete whether to delete this flash message right after this method is called. + * If false, the flash message will be automatically deleted after the next request. * @return mixed the flash message */ - public function getFlash($key, $defaultValue = null) + public function getFlash($key, $defaultValue = null, $delete = false) { $counters = $this->get($this->flashVar, array()); - return isset($counters[$key]) ? $this->get($key, $defaultValue) : $defaultValue; + if (isset($counters[$key])) { + $value = $this->get($key, $defaultValue); + if ($delete) { + $this->removeFlash($key); + } + return $value; + } else { + return $defaultValue; + } } /** From e1061f19acc7ac083fee9c80984461313d67abdb Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Tue, 24 Sep 2013 12:07:41 +0400 Subject: [PATCH 27/30] Advanced application template: Delete flash message after it was displayed --- apps/advanced/frontend/widgets/Alert.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/advanced/frontend/widgets/Alert.php b/apps/advanced/frontend/widgets/Alert.php index 20f3372..b68bfb0 100644 --- a/apps/advanced/frontend/widgets/Alert.php +++ b/apps/advanced/frontend/widgets/Alert.php @@ -23,13 +23,13 @@ class Alert extends \yii\bootstrap\Alert private $_doNotRender = false; public function init() { - if ($this->body = \Yii::$app->getSession()->getFlash('error')) { + if ($this->body = \Yii::$app->getSession()->getFlash('error', null, true)) { Html::addCssClass($this->options, 'alert-danger'); - } elseif ($this->body = \Yii::$app->getSession()->getFlash('success')) { + } elseif ($this->body = \Yii::$app->getSession()->getFlash('success', null, true)) { Html::addCssClass($this->options, 'alert-success'); - } elseif ($this->body = \Yii::$app->getSession()->getFlash('info')) { + } elseif ($this->body = \Yii::$app->getSession()->getFlash('info', null, true)) { Html::addCssClass($this->options, 'alert-info'); - } elseif ($this->body = \Yii::$app->getSession()->getFlash('warning')) { + } elseif ($this->body = \Yii::$app->getSession()->getFlash('warning', null, true)) { Html::addCssClass($this->options, 'alert-warning'); } else { $this->_doNotRender = true; From e19d0dacf9992a08045bd8cdfcaaefc4bb1ca12c Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Tue, 24 Sep 2013 15:45:09 +0400 Subject: [PATCH 28/30] Fixes #823: consistent interface naming --- apps/advanced/common/models/User.php | 6 +- apps/basic/models/User.php | 2 +- docs/guide/upgrade-from-v1.md | 6 +- framework/yii/classes.php | 8 +-- framework/yii/data/DataProvider.php | 4 +- framework/yii/data/DataProviderInterface.php | 58 +++++++++++++++++ framework/yii/data/IDataProvider.php | 58 ----------------- framework/yii/web/AssetConverter.php | 2 +- framework/yii/web/AssetConverterInterface.php | 25 ++++++++ framework/yii/web/AssetManager.php | 8 +-- framework/yii/web/IAssetConverter.php | 25 -------- framework/yii/web/Identity.php | 81 ------------------------ framework/yii/web/IdentityInterface.php | 81 ++++++++++++++++++++++++ framework/yii/web/Response.php | 4 +- framework/yii/web/ResponseFormatter.php | 23 ------- framework/yii/web/ResponseFormatterInterface.php | 23 +++++++ framework/yii/web/User.php | 30 ++++----- framework/yii/web/UserEvent.php | 2 +- framework/yii/web/XmlResponseFormatter.php | 2 +- framework/yii/widgets/ListViewBase.php | 2 +- phpunit.xml.dist | 2 +- 21 files changed, 226 insertions(+), 226 deletions(-) create mode 100644 framework/yii/data/DataProviderInterface.php delete mode 100644 framework/yii/data/IDataProvider.php create mode 100644 framework/yii/web/AssetConverterInterface.php delete mode 100644 framework/yii/web/IAssetConverter.php delete mode 100644 framework/yii/web/Identity.php create mode 100644 framework/yii/web/IdentityInterface.php delete mode 100644 framework/yii/web/ResponseFormatter.php create mode 100644 framework/yii/web/ResponseFormatterInterface.php diff --git a/apps/advanced/common/models/User.php b/apps/advanced/common/models/User.php index da8f067..62baf48 100644 --- a/apps/advanced/common/models/User.php +++ b/apps/advanced/common/models/User.php @@ -3,7 +3,7 @@ namespace common\models; use yii\db\ActiveRecord; use yii\helpers\Security; -use yii\web\Identity; +use yii\web\IdentityInterface; /** * Class User @@ -20,7 +20,7 @@ use yii\web\Identity; * @property integer $create_time * @property integer $update_time */ -class User extends ActiveRecord implements Identity +class User extends ActiveRecord implements IdentityInterface { /** * @var string the raw password. Used to collect password input and isn't saved in database @@ -49,7 +49,7 @@ class User extends ActiveRecord implements Identity * Finds an identity by the given ID. * * @param string|integer $id the ID to be looked for - * @return Identity|null the identity object that matches the given ID. + * @return IdentityInterface|null the identity object that matches the given ID. */ public static function findIdentity($id) { diff --git a/apps/basic/models/User.php b/apps/basic/models/User.php index afbf9f8..e1088a0 100644 --- a/apps/basic/models/User.php +++ b/apps/basic/models/User.php @@ -2,7 +2,7 @@ namespace app\models; -class User extends \yii\base\Object implements \yii\web\Identity +class User extends \yii\base\Object implements \yii\web\IdentityInterface { public $id; public $username; diff --git a/docs/guide/upgrade-from-v1.md b/docs/guide/upgrade-from-v1.md index ee2a3d5..f174864 100644 --- a/docs/guide/upgrade-from-v1.md +++ b/docs/guide/upgrade-from-v1.md @@ -450,11 +450,11 @@ This feature is especially useful if you are developing an application that supp different DBMS. -User and Identity ------------------ +User and IdentityInterface +-------------------------- The `CWebUser` class in 1.1 is now replaced by `\yii\Web\User`, and there is no more -`CUserIdentity` class. Instead, you should implement the `Identity` interface which +`CUserIdentity` class. Instead, you should implement the `IdentityInterface` which is much more straightforward to implement. The bootstrap application provides such an example. diff --git a/framework/yii/classes.php b/framework/yii/classes.php index 3880620..40ca225 100644 --- a/framework/yii/classes.php +++ b/framework/yii/classes.php @@ -86,7 +86,7 @@ return array( 'yii\data\ActiveDataProvider' => YII_PATH . '/data/ActiveDataProvider.php', 'yii\data\ArrayDataProvider' => YII_PATH . '/data/ArrayDataProvider.php', 'yii\data\DataProvider' => YII_PATH . '/data/DataProvider.php', - 'yii\data\IDataProvider' => YII_PATH . '/data/IDataProvider.php', + 'yii\data\DataProviderInterface' => YII_PATH . '/data/DataProviderInterface.php', 'yii\data\Pagination' => YII_PATH . '/data/Pagination.php', 'yii\data\Sort' => YII_PATH . '/data/Sort.php', 'yii\db\ActiveQuery' => YII_PATH . '/db/ActiveQuery.php', @@ -204,15 +204,15 @@ return array( 'yii\web\HeaderCollection' => YII_PATH . '/web/HeaderCollection.php', 'yii\web\HttpCache' => YII_PATH . '/web/HttpCache.php', 'yii\web\HttpException' => YII_PATH . '/web/HttpException.php', - 'yii\web\IAssetConverter' => YII_PATH . '/web/IAssetConverter.php', - 'yii\web\Identity' => YII_PATH . '/web/Identity.php', + 'yii\web\AssetConverterInterface' => YII_PATH . '/web/AssetConverterInterface.php', + 'yii\web\IdentityInterface' => YII_PATH . '/web/IdentityInterface.php', 'yii\web\JqueryAsset' => YII_PATH . '/web/JqueryAsset.php', 'yii\web\JsExpression' => YII_PATH . '/web/JsExpression.php', 'yii\web\PageCache' => YII_PATH . '/web/PageCache.php', 'yii\web\Request' => YII_PATH . '/web/Request.php', 'yii\web\Response' => YII_PATH . '/web/Response.php', 'yii\web\ResponseEvent' => YII_PATH . '/web/ResponseEvent.php', - 'yii\web\ResponseFormatter' => YII_PATH . '/web/ResponseFormatter.php', + 'yii\web\ResponseFormatterInterface' => YII_PATH . '/web/ResponseFormatterInterface.php', 'yii\web\Session' => YII_PATH . '/web/Session.php', 'yii\web\SessionIterator' => YII_PATH . '/web/SessionIterator.php', 'yii\web\UploadedFile' => YII_PATH . '/web/UploadedFile.php', diff --git a/framework/yii/data/DataProvider.php b/framework/yii/data/DataProvider.php index 84491d6..b29f616 100644 --- a/framework/yii/data/DataProvider.php +++ b/framework/yii/data/DataProvider.php @@ -14,7 +14,7 @@ use yii\base\InvalidParamException; /** * DataProvider is the base class of data provider classes. * - * It implements the [[getPagination()]] and [[getSort()]] methods as specified by the [[IDataProvider]] interface. + * It implements the [[getPagination()]] and [[getSort()]] methods as specified by the [[DataProviderInterface]]. * * @property integer $count The number of data models in the current page. This property is read-only. * @property Pagination|boolean $pagination The pagination object. If this is false, it means the pagination @@ -26,7 +26,7 @@ use yii\base\InvalidParamException; * @author Qiang Xue * @since 2.0 */ -abstract class DataProvider extends Component implements IDataProvider +abstract class DataProvider extends Component implements DataProviderInterface { /** * @var string an ID that uniquely identifies the data provider among all data providers. diff --git a/framework/yii/data/DataProviderInterface.php b/framework/yii/data/DataProviderInterface.php new file mode 100644 index 0000000..f0bc39d --- /dev/null +++ b/framework/yii/data/DataProviderInterface.php @@ -0,0 +1,58 @@ + + * @since 2.0 + */ +interface DataProviderInterface +{ + /** + * Returns the number of data models in the current page. + * This is equivalent to `count($provider->getModels())`. + * When [[pagination]] is false, this is the same as [[totalCount]]. + * @return integer the number of data models in the current page. + */ + public function getCount(); + + /** + * Returns the total number of data models. + * When [[pagination]] is false, this is the same as [[count]]. + * @return integer total number of possible data models. + */ + public function getTotalCount(); + + /** + * Returns the data models in the current page. + * @return array the list of data models in the current page. + */ + public function getModels(); + + /** + * Returns the key values associated with the data models. + * @return array the list of key values corresponding to [[models]]. Each data model in [[models]] + * is uniquely identified by the corresponding key value in this array. + */ + public function getKeys(); + + /** + * @return Sort the sorting object. If this is false, it means the sorting is disabled. + */ + public function getSort(); + + /** + * @return Pagination the pagination object. If this is false, it means the pagination is disabled. + */ + public function getPagination(); +} diff --git a/framework/yii/data/IDataProvider.php b/framework/yii/data/IDataProvider.php deleted file mode 100644 index 9ae5546..0000000 --- a/framework/yii/data/IDataProvider.php +++ /dev/null @@ -1,58 +0,0 @@ - - * @since 2.0 - */ -interface IDataProvider -{ - /** - * Returns the number of data models in the current page. - * This is equivalent to `count($provider->getModels())`. - * When [[pagination]] is false, this is the same as [[totalCount]]. - * @return integer the number of data models in the current page. - */ - public function getCount(); - - /** - * Returns the total number of data models. - * When [[pagination]] is false, this is the same as [[count]]. - * @return integer total number of possible data models. - */ - public function getTotalCount(); - - /** - * Returns the data models in the current page. - * @return array the list of data models in the current page. - */ - public function getModels(); - - /** - * Returns the key values associated with the data models. - * @return array the list of key values corresponding to [[models]]. Each data model in [[models]] - * is uniquely identified by the corresponding key value in this array. - */ - public function getKeys(); - - /** - * @return Sort the sorting object. If this is false, it means the sorting is disabled. - */ - public function getSort(); - - /** - * @return Pagination the pagination object. If this is false, it means the pagination is disabled. - */ - public function getPagination(); -} diff --git a/framework/yii/web/AssetConverter.php b/framework/yii/web/AssetConverter.php index cd931c9..420a5bc 100644 --- a/framework/yii/web/AssetConverter.php +++ b/framework/yii/web/AssetConverter.php @@ -16,7 +16,7 @@ use yii\base\Component; * @author Qiang Xue * @since 2.0 */ -class AssetConverter extends Component implements IAssetConverter +class AssetConverter extends Component implements AssetConverterInterface { /** * @var array the commands that are used to perform the asset conversion. diff --git a/framework/yii/web/AssetConverterInterface.php b/framework/yii/web/AssetConverterInterface.php new file mode 100644 index 0000000..51309c6 --- /dev/null +++ b/framework/yii/web/AssetConverterInterface.php @@ -0,0 +1,25 @@ + + * @since 2.0 + */ +interface AssetConverterInterface +{ + /** + * Converts a given asset file into a CSS or JS file. + * @param string $asset the asset file path, relative to $basePath + * @param string $basePath the directory the $asset is relative to. + * @return string the converted asset file path, relative to $basePath. + */ + public function convert($asset, $basePath); +} diff --git a/framework/yii/web/AssetManager.php b/framework/yii/web/AssetManager.php index c6f7fea..500848b 100644 --- a/framework/yii/web/AssetManager.php +++ b/framework/yii/web/AssetManager.php @@ -16,7 +16,7 @@ use yii\helpers\FileHelper; /** * AssetManager manages asset bundles and asset publishing. * - * @property IAssetConverter $converter The asset converter. Note that the type of this property differs in + * @property AssetConverterInterface $converter The asset converter. Note that the type of this property differs in * getter and setter. See [[getConverter()]] and [[setConverter()]] for details. * * @author Qiang Xue @@ -116,7 +116,7 @@ class AssetManager extends Component /** * Returns the asset converter. - * @return IAssetConverter the asset converter. + * @return AssetConverterInterface the asset converter. */ public function getConverter() { @@ -130,8 +130,8 @@ class AssetManager extends Component /** * Sets the asset converter. - * @param array|IAssetConverter $value the asset converter. This can be either - * an object implementing the [[IAssetConverter]] interface, or a configuration + * @param array|AssetConverterInterface $value the asset converter. This can be either + * an object implementing the [[AssetConverterInterface]], or a configuration * array that can be used to create the asset converter object. */ public function setConverter($value) diff --git a/framework/yii/web/IAssetConverter.php b/framework/yii/web/IAssetConverter.php deleted file mode 100644 index 6021963..0000000 --- a/framework/yii/web/IAssetConverter.php +++ /dev/null @@ -1,25 +0,0 @@ - - * @since 2.0 - */ -interface IAssetConverter -{ - /** - * Converts a given asset file into a CSS or JS file. - * @param string $asset the asset file path, relative to $basePath - * @param string $basePath the directory the $asset is relative to. - * @return string the converted asset file path, relative to $basePath. - */ - public function convert($asset, $basePath); -} diff --git a/framework/yii/web/Identity.php b/framework/yii/web/Identity.php deleted file mode 100644 index 101ecdb..0000000 --- a/framework/yii/web/Identity.php +++ /dev/null @@ -1,81 +0,0 @@ -id; - * } - * - * public function getAuthKey() - * { - * return $this->authKey; - * } - * - * public function validateAuthKey($authKey) - * { - * return $this->authKey === $authKey; - * } - * } - * ~~~ - * - * @author Qiang Xue - * @since 2.0 - */ -interface Identity -{ - /** - * Finds an identity by the given ID. - * @param string|integer $id the ID to be looked for - * @return Identity the identity object that matches the given ID. - * Null should be returned if such an identity cannot be found - * or the identity is not in an active state (disabled, deleted, etc.) - */ - public static function findIdentity($id); - /** - * Returns an ID that can uniquely identify a user identity. - * @return string|integer an ID that uniquely identifies a user identity. - */ - public function getId(); - /** - * Returns a key that can be used to check the validity of a given identity ID. - * - * The key should be unique for each individual user, and should be persistent - * so that it can be used to check the validity of the user identity. - * - * The space of such keys should be big enough to defeat potential identity attacks. - * - * This is required if [[User::enableAutoLogin]] is enabled. - * @return string a key that is used to check the validity of a given identity ID. - * @see validateAuthKey() - */ - public function getAuthKey(); - /** - * Validates the given auth key. - * - * This is required if [[User::enableAutoLogin]] is enabled. - * @param string $authKey the given auth key - * @return boolean whether the given auth key is valid. - * @see getAuthKey() - */ - public function validateAuthKey($authKey); -} diff --git a/framework/yii/web/IdentityInterface.php b/framework/yii/web/IdentityInterface.php new file mode 100644 index 0000000..c796b50 --- /dev/null +++ b/framework/yii/web/IdentityInterface.php @@ -0,0 +1,81 @@ +id; + * } + * + * public function getAuthKey() + * { + * return $this->authKey; + * } + * + * public function validateAuthKey($authKey) + * { + * return $this->authKey === $authKey; + * } + * } + * ~~~ + * + * @author Qiang Xue + * @since 2.0 + */ +interface IdentityInterface +{ + /** + * Finds an identity by the given ID. + * @param string|integer $id the ID to be looked for + * @return IdentityInterface the identity object that matches the given ID. + * Null should be returned if such an identity cannot be found + * or the identity is not in an active state (disabled, deleted, etc.) + */ + public static function findIdentity($id); + /** + * Returns an ID that can uniquely identify a user identity. + * @return string|integer an ID that uniquely identifies a user identity. + */ + public function getId(); + /** + * Returns a key that can be used to check the validity of a given identity ID. + * + * The key should be unique for each individual user, and should be persistent + * so that it can be used to check the validity of the user identity. + * + * The space of such keys should be big enough to defeat potential identity attacks. + * + * This is required if [[User::enableAutoLogin]] is enabled. + * @return string a key that is used to check the validity of a given identity ID. + * @see validateAuthKey() + */ + public function getAuthKey(); + /** + * Validates the given auth key. + * + * This is required if [[User::enableAutoLogin]] is enabled. + * @param string $authKey the given auth key + * @return boolean whether the given auth key is valid. + * @see getAuthKey() + */ + public function validateAuthKey($authKey); +} diff --git a/framework/yii/web/Response.php b/framework/yii/web/Response.php index 979cce0..e6505fd 100644 --- a/framework/yii/web/Response.php +++ b/framework/yii/web/Response.php @@ -766,10 +766,10 @@ class Response extends \yii\base\Response if (!is_object($formatter)) { $formatter = Yii::createObject($formatter); } - if ($formatter instanceof ResponseFormatter) { + if ($formatter instanceof ResponseFormatterInterface) { $formatter->format($this); } else { - throw new InvalidConfigException("The '{$this->format}' response formatter is invalid. It must implement the ResponseFormatter interface."); + throw new InvalidConfigException("The '{$this->format}' response formatter is invalid. It must implement the ResponseFormatterInterface."); } } else { switch ($this->format) { diff --git a/framework/yii/web/ResponseFormatter.php b/framework/yii/web/ResponseFormatter.php deleted file mode 100644 index dc7c979..0000000 --- a/framework/yii/web/ResponseFormatter.php +++ /dev/null @@ -1,23 +0,0 @@ - - * @since 2.0 - */ -interface ResponseFormatter -{ - /** - * Formats the specified response. - * @param Response $response the response to be formatted. - */ - public function format($response); -} diff --git a/framework/yii/web/ResponseFormatterInterface.php b/framework/yii/web/ResponseFormatterInterface.php new file mode 100644 index 0000000..689ee1e --- /dev/null +++ b/framework/yii/web/ResponseFormatterInterface.php @@ -0,0 +1,23 @@ + + * @since 2.0 + */ +interface ResponseFormatterInterface +{ + /** + * Formats the specified response. + * @param Response $response the response to be formatted. + */ + public function format($response); +} diff --git a/framework/yii/web/User.php b/framework/yii/web/User.php index 22b85e5..f6a9bc8 100644 --- a/framework/yii/web/User.php +++ b/framework/yii/web/User.php @@ -18,12 +18,12 @@ use yii\base\InvalidParamException; * In particular, [[User::isGuest]] returns a value indicating whether the current user is a guest or not. * Through methods [[login()]] and [[logout()]], you can change the user authentication status. * - * User works with a class implementing the [[Identity]] interface. This class implements + * User works with a class implementing the [[IdentityInterface]]. This class implements * the actual user authentication logic and is often backed by a user database table. * * @property string|integer $id The unique identifier for the user. If null, it means the user is a guest. * This property is read-only. - * @property Identity $identity The identity object associated with the currently logged user. Null is + * @property IdentityInterface $identity The identity object associated with the currently logged user. Null is * returned if the user is not logged in (not authenticated). * @property boolean $isGuest Whether the current user is a guest. This property is read-only. * @property string $returnUrl The URL that the user should be redirected to after login. Note that the type @@ -128,7 +128,7 @@ class User extends Component /** * Returns the identity object associated with the currently logged user. - * @return Identity the identity object associated with the currently logged user. + * @return IdentityInterface the identity object associated with the currently logged user. * Null is returned if the user is not logged in (not authenticated). * @see login * @see logout @@ -140,7 +140,7 @@ class User extends Component if ($id === null) { $this->_identity = null; } else { - /** @var $class Identity */ + /** @var $class IdentityInterface */ $class = $this->identityClass; $this->_identity = $class::findIdentity($id); } @@ -156,7 +156,7 @@ class User extends Component * You should normally update the user identity via methods [[login()]], [[logout()]] * or [[switchIdentity()]]. * - * @param Identity $identity the identity object associated with the currently logged user. + * @param IdentityInterface $identity the identity object associated with the currently logged user. */ public function setIdentity($identity) { @@ -171,7 +171,7 @@ class User extends Component * and [[enableAutoLogin]] is true, it will also send out an identity * cookie to support cookie-based login. * - * @param Identity $identity the user identity (which should already be authenticated) + * @param IdentityInterface $identity the user identity (which should already be authenticated) * @param integer $duration number of seconds that the user can remain in logged-in status. * Defaults to 0, meaning login till the user closes the browser or the session is manually destroyed. * If greater than 0 and [[enableAutoLogin]] is true, cookie-based login will be supported. @@ -200,7 +200,7 @@ class User extends Component $data = json_decode($value, true); if (count($data) === 3 && isset($data[0], $data[1], $data[2])) { list ($id, $authKey, $duration) = $data; - /** @var $class Identity */ + /** @var $class IdentityInterface */ $class = $this->identityClass; $identity = $class::findIdentity($id); if ($identity !== null && $identity->validateAuthKey($authKey)) { @@ -318,7 +318,7 @@ class User extends Component * The default implementation will trigger the [[EVENT_BEFORE_LOGIN]] event. * If you override this method, make sure you call the parent implementation * so that the event is triggered. - * @param Identity $identity the user identity information + * @param IdentityInterface $identity the user identity information * @param boolean $cookieBased whether the login is cookie-based * @return boolean whether the user should continue to be logged in */ @@ -337,7 +337,7 @@ class User extends Component * The default implementation will trigger the [[EVENT_AFTER_LOGIN]] event. * If you override this method, make sure you call the parent implementation * so that the event is triggered. - * @param Identity $identity the user identity information + * @param IdentityInterface $identity the user identity information * @param boolean $cookieBased whether the login is cookie-based */ protected function afterLogin($identity, $cookieBased) @@ -353,7 +353,7 @@ class User extends Component * The default implementation will trigger the [[EVENT_BEFORE_LOGOUT]] event. * If you override this method, make sure you call the parent implementation * so that the event is triggered. - * @param Identity $identity the user identity information + * @param IdentityInterface $identity the user identity information * @return boolean whether the user should continue to be logged out */ protected function beforeLogout($identity) @@ -370,7 +370,7 @@ class User extends Component * The default implementation will trigger the [[EVENT_AFTER_LOGOUT]] event. * If you override this method, make sure you call the parent implementation * so that the event is triggered. - * @param Identity $identity the user identity information + * @param IdentityInterface $identity the user identity information */ protected function afterLogout($identity) { @@ -402,9 +402,9 @@ class User extends Component /** * Sends an identity cookie. * This method is used when [[enableAutoLogin]] is true. - * It saves [[id]], [[Identity::getAuthKey()|auth key]], and the duration of cookie-based login + * It saves [[id]], [[IdentityInterface::getAuthKey()|auth key]], and the duration of cookie-based login * information in the cookie. - * @param Identity $identity + * @param IdentityInterface $identity * @param integer $duration number of seconds that the user can remain in logged-in status. * @see loginByCookie */ @@ -430,7 +430,7 @@ class User extends Component * This method is mainly called by [[login()]], [[logout()]] and [[loginByCookie()]] * when the current user needs to be associated with the corresponding identity information. * - * @param Identity $identity the identity information to be associated with the current user. + * @param IdentityInterface $identity the identity information to be associated with the current user. * If null, it means switching to be a guest. * @param integer $duration number of seconds that the user can remain in logged-in status. * This parameter is used only when `$identity` is not null. @@ -444,7 +444,7 @@ class User extends Component $this->setIdentity($identity); $session->remove($this->idVar); $session->remove($this->authTimeoutVar); - if ($identity instanceof Identity) { + if ($identity instanceof IdentityInterface) { $session->set($this->idVar, $identity->getId()); if ($this->authTimeout !== null) { $session->set($this->authTimeoutVar, time() + $this->authTimeout); diff --git a/framework/yii/web/UserEvent.php b/framework/yii/web/UserEvent.php index 3e403da..8577ef5 100644 --- a/framework/yii/web/UserEvent.php +++ b/framework/yii/web/UserEvent.php @@ -18,7 +18,7 @@ use yii\base\Event; class UserEvent extends Event { /** - * @var Identity the identity object associated with this event + * @var IdentityInterface the identity object associated with this event */ public $identity; /** diff --git a/framework/yii/web/XmlResponseFormatter.php b/framework/yii/web/XmlResponseFormatter.php index adf8807..737011d 100644 --- a/framework/yii/web/XmlResponseFormatter.php +++ b/framework/yii/web/XmlResponseFormatter.php @@ -20,7 +20,7 @@ use yii\helpers\StringHelper; * @author Qiang Xue * @since 2.0 */ -class XmlResponseFormatter extends Component implements ResponseFormatter +class XmlResponseFormatter extends Component implements ResponseFormatterInterface { /** * @var string the Content-Type header for the response diff --git a/framework/yii/widgets/ListViewBase.php b/framework/yii/widgets/ListViewBase.php index 8c2f8f4..33186ae 100644 --- a/framework/yii/widgets/ListViewBase.php +++ b/framework/yii/widgets/ListViewBase.php @@ -25,7 +25,7 @@ abstract class ListViewBase extends Widget */ public $options = array(); /** - * @var \yii\data\IDataProvider the data provider for the view. This property is required. + * @var \yii\data\DataProviderInterface the data provider for the view. This property is required. */ public $dataProvider; /** diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 3100413..1f3056e 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -21,7 +21,7 @@ framework/yii/helpers/ArrayHelper.php framework/yii/helpers/Console.php framework/yii/i18n/GettextFile.php - framework/yii/web/ResponseFormatter.php + framework/yii/web/ResponseFormatterInterface.php framework/yii/base framework/yii/db/mssql framework/yii/bootstrap From cd969509e6be6f89f2eda7b85727c073b06c1cd8 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 24 Sep 2013 21:46:31 -0400 Subject: [PATCH 29/30] Added ActionColumn. crud generator WIP. --- .../gii/generators/crud/templates/controller.php | 13 +++ .../gii/generators/crud/templates/views/index.php | 4 + .../gii/generators/crud/templates/views/view.php | 6 +- framework/yii/grid/ActionColumn.php | 102 +++++++++++++++++++++ 4 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 framework/yii/grid/ActionColumn.php diff --git a/framework/yii/gii/generators/crud/templates/controller.php b/framework/yii/gii/generators/crud/templates/controller.php index fd33eda..f53f819 100644 --- a/framework/yii/gii/generators/crud/templates/controller.php +++ b/framework/yii/gii/generators/crud/templates/controller.php @@ -28,12 +28,25 @@ use searchModelClass, '\\'); ?>; use yii\data\ActiveDataProvider; use baseControllerClass, '\\'); ?>; use yii\web\HttpException; +use yii\web\VerbFilter; /** * implements the CRUD actions for model. */ class extends baseControllerClass) . "\n"; ?> { + public function behaviors() + { + return array( + 'verbs' => array( + 'class' => VerbFilter::className(), + 'actions' => array( + 'delete' => array('post'), + ), + ), + ); + } + /** * Lists all models. * @return mixed diff --git a/framework/yii/gii/generators/crud/templates/views/index.php b/framework/yii/gii/generators/crud/templates/views/index.php index 98d35b3..5f55e84 100644 --- a/framework/yii/gii/generators/crud/templates/views/index.php +++ b/framework/yii/gii/generators/crud/templates/views/index.php @@ -54,6 +54,10 @@ foreach ($generator->getTableSchema()->columns as $column) { } } ?> + + array( + 'class' => 'yii\grid\ActionColumn', + ), ), )); ?> diff --git a/framework/yii/gii/generators/crud/templates/views/view.php b/framework/yii/gii/generators/crud/templates/views/view.php index d08ec23..a3b6cad 100644 --- a/framework/yii/gii/generators/crud/templates/views/view.php +++ b/framework/yii/gii/generators/crud/templates/views/view.php @@ -31,7 +31,11 @@ $this->params['breadcrumbs'][] = $this->title;
echo Html::a('Update', array('update', ), array('class' => 'btn btn-danger')); ?> - echo Html::a('Delete', array('delete', ), array('class' => 'btn btn-danger')); ?> + echo Html::a('Delete', array('delete', ), array( + 'class' => 'btn btn-danger', + 'data-confirm' => Yii::t('app', 'Are you sure to delete this item?'), + 'data-method' => 'post', + )); ?>
echo DetailView::widget(array( diff --git a/framework/yii/grid/ActionColumn.php b/framework/yii/grid/ActionColumn.php new file mode 100644 index 0000000..72fafb6 --- /dev/null +++ b/framework/yii/grid/ActionColumn.php @@ -0,0 +1,102 @@ + + * @since 2.0 + */ +class ActionColumn extends Column +{ + public $template = '{view} {update} {delete}'; + public $buttons = array(); + public $urlCreator; + + public function init() + { + parent::init(); + $this->initDefaultButtons(); + } + + protected function initDefaultButtons() + { + if (!isset($this->buttons['view'])) { + $this->buttons['view'] = function ($model, $column) { + /** @var ActionColumn $column */ + $url = $column->createUrl($model, 'view'); + return Html::a('', $url, array( + 'title' => Yii::t('yii', 'View'), + )); + }; + } + if (!isset($this->buttons['update'])) { + $this->buttons['update'] = function ($model, $column) { + /** @var ActionColumn $column */ + $url = $column->createUrl($model, 'update'); + return Html::a('', $url, array( + 'title' => Yii::t('yii', 'Update'), + )); + }; + } + if (!isset($this->buttons['delete'])) { + $this->buttons['delete'] = function ($model, $column) { + /** @var ActionColumn $column */ + $url = $column->createUrl($model, 'delete'); + return Html::a('', $url, array( + 'title' => Yii::t('yii', 'Delete'), + 'data-confirm' => Yii::t('yii', 'Are you sure to delete this item?'), + 'data-method' => 'post', + )); + }; + } + } + + /** + * @param \yii\db\ActiveRecord $model + * @param string $action + * @return string + */ + public function createUrl($model, $action) + { + if ($this->urlCreator instanceof Closure) { + return call_user_func($this->urlCreator, $model, $action); + } else { + $route = Inflector::camel2id(StringHelper::basename(get_class($model))) . '/' . $action; + $params = $model->getPrimaryKey(true); + if (count($params) === 1) { + $params = array('id' => reset($params)); + } + return Yii::$app->getUrlManager()->createUrl($route, $params); + } + } + + /** + * Renders the data cell content. + * @param mixed $model the data model + * @param integer $index the zero-based index of the data model among the models array returned by [[dataProvider]]. + * @return string the rendering result + */ + protected function renderDataCellContent($model, $index) + { + $column = $this; + return preg_replace_callback('/\\{(\w+)\\}/', function ($matches) use ($model, $column) { + $name = $matches[1]; + if (isset($column->buttons[$name])) { + return call_user_func($column->buttons[$name], $model, $column); + } else { + return ''; + } + }, $this->template); + } +} From 7d2d925dbe1809e42dcb0bd4e006c7e6919e0b94 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 24 Sep 2013 21:50:08 -0400 Subject: [PATCH 30/30] Added SerialColumn to crud generated code. --- framework/yii/gii/generators/crud/templates/views/index.php | 6 +++--- framework/yii/grid/SerialColumn.php | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/framework/yii/gii/generators/crud/templates/views/index.php b/framework/yii/gii/generators/crud/templates/views/index.php index 5f55e84..df76581 100644 --- a/framework/yii/gii/generators/crud/templates/views/index.php +++ b/framework/yii/gii/generators/crud/templates/views/index.php @@ -43,6 +43,8 @@ $this->params['breadcrumbs'][] = $this->title; 'dataProvider' => $dataProvider, 'filterModel' => $searchModel, 'columns' => array( + array('class' => 'yii\grid\SerialColumn'), + getTableSchema()->columns as $column) { @@ -55,9 +57,7 @@ foreach ($generator->getTableSchema()->columns as $column) { } ?> - array( - 'class' => 'yii\grid\ActionColumn', - ), + array('class' => 'yii\grid\ActionColumn'), ), )); ?> diff --git a/framework/yii/grid/SerialColumn.php b/framework/yii/grid/SerialColumn.php index 2fdb770..6a875ae 100644 --- a/framework/yii/grid/SerialColumn.php +++ b/framework/yii/grid/SerialColumn.php @@ -15,6 +15,8 @@ namespace yii\grid; */ class SerialColumn extends Column { + public $header = '#'; + /** * Renders the data cell content. * @param mixed $model the data model