From ed1a64ca04dce5f21833f0e9fbad0c632a670e2f Mon Sep 17 00:00:00 2001 From: mark_story Date: Fri, 4 May 2012 08:16:43 -0400 Subject: [PATCH 01/29] Use correct argument order. --- lib/Cake/Utility/Xml.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Cake/Utility/Xml.php b/lib/Cake/Utility/Xml.php index 6b96f7f61..1f9f69caf 100644 --- a/lib/Cake/Utility/Xml.php +++ b/lib/Cake/Utility/Xml.php @@ -230,7 +230,7 @@ class Xml { if ($key[0] === '@') { throw new XmlException(__d('cake_dev', 'Invalid array')); } - if (is_numeric(implode(array_keys($value), ''))) { // List + if (is_numeric(implode('', array_keys($value)))) { // List foreach ($value as $item) { $itemData = compact('dom', 'node', 'key', 'format'); $itemData['value'] = $item; From 7bb56e7fc38a68993dc4158a54a255111d017a92 Mon Sep 17 00:00:00 2001 From: Frank de Graaf Date: Fri, 4 May 2012 16:58:51 +0200 Subject: [PATCH 02/29] Fixed HTTP status codes for non-redirects which were breaking AuthComponent. --- lib/Cake/Controller/Controller.php | 17 ++++++++--------- .../Test/Case/Controller/ControllerTest.php | 5 +++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/Cake/Controller/Controller.php b/lib/Cake/Controller/Controller.php index 4dacc8e07..f1d40faff 100644 --- a/lib/Cake/Controller/Controller.php +++ b/lib/Cake/Controller/Controller.php @@ -531,7 +531,7 @@ class Controller extends Object implements CakeEventListener { } /** - * Merge components, helpers, and uses vars from + * Merge components, helpers, and uses vars from * Controller::$_mergeParent and PluginAppController. * * @return void @@ -770,18 +770,17 @@ class Controller extends Object implements CakeEventListener { session_write_close(); } - if (!empty($status) && is_string($status)) { - $codes = array_flip($this->response->httpCodes()); - if (isset($codes[$status])) { - $status = $codes[$status]; - } - } - if ($url !== null) { $this->response->header('Location', Router::url($url, true)); } - if (!empty($status) && ($status >= 300 && $status < 400)) { + if (!empty($status)) { + if (is_string($status)) { + $codes = array_flip($this->response->httpCodes()); + if (isset($codes[$status])) { + $status = $codes[$status]; + } + } $this->response->statusCode($status); } diff --git a/lib/Cake/Test/Case/Controller/ControllerTest.php b/lib/Cake/Test/Case/Controller/ControllerTest.php index 472c7a48c..b0f084749 100644 --- a/lib/Cake/Test/Case/Controller/ControllerTest.php +++ b/lib/Cake/Test/Case/Controller/ControllerTest.php @@ -387,7 +387,7 @@ class AnotherTestController extends ControllerTestAppController { /** * merge parent - * + * * @var string */ protected $_mergeParent = 'ControllerTestAppController'; @@ -729,7 +729,8 @@ class ControllerTest extends CakeTestCase { array(303, "See Other"), array(304, "Not Modified"), array(305, "Use Proxy"), - array(307, "Temporary Redirect") + array(307, "Temporary Redirect"), + array(403, "Forbidden"), ); } From 6f9b26fe5814d23efad31c465304af94fd7769d3 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Fri, 4 May 2012 19:27:08 -0430 Subject: [PATCH 03/29] Again using same serialization strategy for cached view files, as in some environments file is corrupted --- lib/Cake/View/Helper/CacheHelper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Cake/View/Helper/CacheHelper.php b/lib/Cake/View/Helper/CacheHelper.php index a8636fe5a..d66f24d34 100644 --- a/lib/Cake/View/Helper/CacheHelper.php +++ b/lib/Cake/View/Helper/CacheHelper.php @@ -291,7 +291,7 @@ class CacheHelper extends AppHelper { } $file .= ' - $request = unserialize(\'' . str_replace("'", "\\'", serialize($this->request)) . '\'); + $request = unserialize(base64_decode(\'' . base64_encode(serialize($this->request)) . '\')); $response = new CakeResponse(array("charset" => Configure::read("App.encoding"))); $controller = new ' . $this->_View->name . 'Controller($request, $response); $controller->plugin = $this->plugin = \'' . $this->_View->plugin . '\'; From 985d6809f13c06bfd1da27ca93c358082f92c6f6 Mon Sep 17 00:00:00 2001 From: Phally Date: Sat, 5 May 2012 17:09:43 +0200 Subject: [PATCH 04/29] Removed triple nested condition. --- lib/Cake/Controller/Controller.php | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/Cake/Controller/Controller.php b/lib/Cake/Controller/Controller.php index f1d40faff..6b8f1955c 100644 --- a/lib/Cake/Controller/Controller.php +++ b/lib/Cake/Controller/Controller.php @@ -774,13 +774,14 @@ class Controller extends Object implements CakeEventListener { $this->response->header('Location', Router::url($url, true)); } - if (!empty($status)) { - if (is_string($status)) { - $codes = array_flip($this->response->httpCodes()); - if (isset($codes[$status])) { - $status = $codes[$status]; - } + if (is_string($status)) { + $codes = array_flip($this->response->httpCodes()); + if (isset($codes[$status])) { + $status = $codes[$status]; } + } + + if ($status) { $this->response->statusCode($status); } From 6c5255ac73df0043262782d28ef1409c0aed67cc Mon Sep 17 00:00:00 2001 From: mark_story Date: Sun, 6 May 2012 21:29:39 -0400 Subject: [PATCH 05/29] Fix saving translate records with saveAll() Also fixes issues saving translated data with validation disabled. By enabling data backup in beforeSave() and beforeValidate() the existing behavior is preserved, and the current issue is fixed. Fixes #2857 --- lib/Cake/Model/Behavior/TranslateBehavior.php | 30 ++- .../Model/Behavior/TranslateBehaviorTest.php | 179 ++++++++++++++---- lib/Cake/Test/Case/Model/models.php | 11 +- .../Test/Fixture/TranslatedItemFixture.php | 7 +- 4 files changed, 187 insertions(+), 40 deletions(-) diff --git a/lib/Cake/Model/Behavior/TranslateBehavior.php b/lib/Cake/Model/Behavior/TranslateBehavior.php index bce937cca..79036e01a 100644 --- a/lib/Cake/Model/Behavior/TranslateBehavior.php +++ b/lib/Cake/Model/Behavior/TranslateBehavior.php @@ -310,8 +310,35 @@ class TranslateBehavior extends ModelBehavior { * @return boolean */ public function beforeValidate(Model $model) { + unset($this->runtime[$model->alias]['beforeSave']); + $this->_setRuntimeData($model); + return true; + } + +/** + * beforeSave callback. + * + * @param Model $model Model save was called on. + * @return boolean true. + */ + public function beforeSave(Model $model) { + $this->_setRuntimeData($model); + return true; + } + +/** + * Sets the runtime data. + * + * Used from beforeValidate() and beforeSave() for compatibility issues, + * and to allow translations to be persisted even when validation + * is disabled. + * + * @param Model $model + * @return void + */ + protected function _setRuntimeData(Model $model) { $locale = $this->_getLocale($model); - if (empty($locale)) { + if (empty($locale) || isset($this->runtime[$model->alias]['beforeSave'])) { return true; } $fields = array_merge($this->settings[$model->alias], $this->runtime[$model->alias]['fields']); @@ -333,7 +360,6 @@ class TranslateBehavior extends ModelBehavior { } } $this->runtime[$model->alias]['beforeSave'] = $tempData; - return true; } /** diff --git a/lib/Cake/Test/Case/Model/Behavior/TranslateBehaviorTest.php b/lib/Cake/Test/Case/Model/Behavior/TranslateBehaviorTest.php index cc2cf2033..ad2e3cef3 100644 --- a/lib/Cake/Test/Case/Model/Behavior/TranslateBehaviorTest.php +++ b/lib/Cake/Test/Case/Model/Behavior/TranslateBehaviorTest.php @@ -16,9 +16,6 @@ * @since CakePHP(tm) v 1.2.0.5669 * @license MIT License (http://www.opensource.org/licenses/mit-license.php) */ -if (!defined('CAKEPHP_UNIT_TEST_EXECUTION')) { - define('CAKEPHP_UNIT_TEST_EXECUTION', 1); -} App::uses('Model', 'Model'); App::uses('AppModel', 'Model'); @@ -119,7 +116,11 @@ class TranslateBehaviorTest extends CakeTestCase { $TestModel->locale = false; $result = $TestModel->read(null, 1); - $expected = array('TranslatedItem' => array('id' => 1, 'slug' => 'first_translated')); + $expected = array('TranslatedItem' => array( + 'id' => 1, + 'slug' => 'first_translated', + 'translated_article_id' => 1, + )); $this->assertEquals($expected, $result); $result = $TestModel->find('all', array('fields' => array('slug'))); @@ -147,7 +148,7 @@ class TranslateBehaviorTest extends CakeTestCase { $result = $TestModel->read(null, 1); $expected = array( - 'TranslatedItem' => array('id' => 1, 'slug' => 'first_translated'), + 'TranslatedItem' => array('id' => 1, 'slug' => 'first_translated', 'translated_article_id' => 1), 'Title' => array( array('id' => 1, 'locale' => 'eng', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'title', 'content' => 'Title #1'), array('id' => 3, 'locale' => 'deu', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'title', 'content' => 'Titel #1'), @@ -203,7 +204,8 @@ class TranslateBehaviorTest extends CakeTestCase { 'slug' => 'first_translated', 'locale' => 'eng', 'title' => 'Title #1', - 'content' => 'Content #1' + 'content' => 'Content #1', + 'translated_article_id' => 1, ) ); $this->assertEquals($expected, $result); @@ -216,7 +218,8 @@ class TranslateBehaviorTest extends CakeTestCase { 'slug' => 'first_translated', 'locale' => 'eng', 'title' => 'Title #1', - 'content' => 'Content #1' + 'content' => 'Content #1', + 'translated_article_id' => 1, ) ), array( @@ -225,7 +228,8 @@ class TranslateBehaviorTest extends CakeTestCase { 'slug' => 'second_translated', 'locale' => 'eng', 'title' => 'Title #2', - 'content' => 'Content #2' + 'content' => 'Content #2', + 'translated_article_id' => 1, ) ), array( @@ -234,7 +238,8 @@ class TranslateBehaviorTest extends CakeTestCase { 'slug' => 'third_translated', 'locale' => 'eng', 'title' => 'Title #3', - 'content' => 'Content #3' + 'content' => 'Content #3', + 'translated_article_id' => 1, ) ) ); @@ -259,7 +264,8 @@ class TranslateBehaviorTest extends CakeTestCase { 'slug' => 'first_translated', 'locale' => 'eng', 'title' => 'Title #1', - 'content' => 'Content #1' + 'content' => 'Content #1', + 'translated_article_id' => 1, ) ) ); @@ -273,7 +279,8 @@ class TranslateBehaviorTest extends CakeTestCase { 'slug' => 'first_translated', 'locale' => 'eng', 'title' => 'Title #1', - 'content' => 'Content #1' + 'content' => 'Content #1', + 'translated_article_id' => 1, ) ) ); @@ -301,7 +308,8 @@ class TranslateBehaviorTest extends CakeTestCase { 'slug' => 'first_translated', 'locale' => 'eng', 'title' => 'Title #1', - 'content' => 'Content #1' + 'content' => 'Content #1', + 'translated_article_id' => 1, ), 'Title' => array( array('id' => 1, 'locale' => 'eng', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'title', 'content' => 'Title #1'), @@ -322,17 +330,35 @@ class TranslateBehaviorTest extends CakeTestCase { $result = $TestModel->find('all', array('fields' => array('TranslatedItem.title'))); $expected = array( array( - 'TranslatedItem' => array('id' => 1, 'locale' => 'eng', 'title' => 'Title #1', 'slug' => 'first_translated'), + 'TranslatedItem' => array( + 'id' => 1, + 'locale' => 'eng', + 'title' => 'Title #1', + 'slug' => 'first_translated', + 'translated_article_id' => 1, + ), 'Title' => array(array('foreign_key' => 1, 'content' => 'Title #1')), 'Content' => array(array('foreign_key' => 1, 'content' => 'Content #1')) ), array( - 'TranslatedItem' => array('id' => 2, 'locale' => 'eng', 'title' => 'Title #2', 'slug' => 'second_translated'), + 'TranslatedItem' => array( + 'id' => 2, + 'locale' => 'eng', + 'title' => 'Title #2', + 'slug' => 'second_translated', + 'translated_article_id' => 1, + ), 'Title' => array(array('foreign_key' => 2, 'content' => 'Title #2')), 'Content' => array(array('foreign_key' => 2, 'content' => 'Content #2')) ), array( - 'TranslatedItem' => array('id' => 3, 'locale' => 'eng', 'title' => 'Title #3','slug' => 'third_translated'), + 'TranslatedItem' => array( + 'id' => 3, + 'locale' => 'eng', + 'title' => 'Title #3', + 'slug' => 'third_translated', + 'translated_article_id' => 1, + ), 'Title' => array(array('foreign_key' => 3, 'content' => 'Title #3')), 'Content' => array(array('foreign_key' => 3, 'content' => 'Content #3')) ) @@ -358,7 +384,8 @@ class TranslateBehaviorTest extends CakeTestCase { 'slug' => 'first_translated', 'locale' => 'deu', 'title' => 'Titel #1', - 'content' => 'Inhalt #1' + 'content' => 'Inhalt #1', + 'translated_article_id' => 1, ) ); $this->assertEquals($expected, $result); @@ -370,7 +397,7 @@ class TranslateBehaviorTest extends CakeTestCase { 'slug' => 'first_translated', 'locale' => 'deu', 'content' => 'Inhalt #1', - 'title' => 'Titel #1' + 'title' => 'Titel #1', ) ), array( @@ -378,7 +405,7 @@ class TranslateBehaviorTest extends CakeTestCase { 'slug' => 'second_translated', 'locale' => 'deu', 'title' => 'Titel #2', - 'content' => 'Inhalt #2' + 'content' => 'Inhalt #2', ) ), array( @@ -386,7 +413,7 @@ class TranslateBehaviorTest extends CakeTestCase { 'slug' => 'third_translated', 'locale' => 'deu', 'title' => 'Titel #3', - 'content' => 'Inhalt #3' + 'content' => 'Inhalt #3', ) ) ); @@ -415,7 +442,8 @@ class TranslateBehaviorTest extends CakeTestCase { 'slug' => 'first_translated', 'locale' => 'rus', 'title' => '', - 'content' => '' + 'content' => '', + 'translated_article_id' => 1, ) ); $this->assertEquals($expected, $result); @@ -499,7 +527,12 @@ class TranslateBehaviorTest extends CakeTestCase { $TestModel = new TranslatedItem(); $TestModel->locale = 'spa'; - $data = array('slug' => 'fourth_translated', 'title' => 'Leyenda #4', 'content' => 'Contenido #4'); + $data = array( + 'slug' => 'fourth_translated', + 'title' => 'Leyenda #4', + 'content' => 'Contenido #4', + 'translated_article_id' => null + ); $TestModel->create($data); $TestModel->save(); $result = $TestModel->read(); @@ -517,7 +550,7 @@ class TranslateBehaviorTest extends CakeTestCase { $TestModel = new TranslatedItem(); $TestModel->locale = 'spa'; - $oldData = array('slug' => 'fourth_translated', 'title' => 'Leyenda #4'); + $oldData = array('slug' => 'fourth_translated', 'title' => 'Leyenda #4', 'translated_article_id' => 1); $TestModel->create($oldData); $TestModel->save(); $id = $TestModel->id; @@ -554,7 +587,14 @@ class TranslateBehaviorTest extends CakeTestCase { $result = $TestModel->read(); $expected = array( - 'TranslatedItem' => array('id' => 4, 'slug' => 'new_translated', 'locale' => 'eng', 'title' => 'New title', 'content' => 'New content'), + 'TranslatedItem' => array( + 'id' => 4, + 'slug' => 'new_translated', + 'locale' => 'eng', + 'title' => 'New title', + 'content' => 'New content', + 'translated_article_id' => null, + ), 'Title' => array( array('id' => 21, 'locale' => 'eng', 'model' => 'TranslatedItem', 'foreign_key' => 4, 'field' => 'title', 'content' => 'New title'), array('id' => 22, 'locale' => 'spa', 'model' => 'TranslatedItem', 'foreign_key' => 4, 'field' => 'title', 'content' => 'Nuevo leyenda') @@ -591,7 +631,14 @@ class TranslateBehaviorTest extends CakeTestCase { $TestModel->bindTranslation($translations, false); $result = $TestModel->read(null, 1); $expected = array( - 'TranslatedItem' => array('id' => '1', 'slug' => 'first_translated', 'locale' => 'eng', 'title' => 'New Title #1', 'content' => 'New Content #1'), + 'TranslatedItem' => array( + 'id' => '1', + 'slug' => 'first_translated', + 'locale' => 'eng', + 'title' => 'New Title #1', + 'content' => 'New Content #1', + 'translated_article_id' => 1, + ), 'Title' => array( array('id' => 1, 'locale' => 'eng', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'title', 'content' => 'New Title #1'), array('id' => 3, 'locale' => 'deu', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'title', 'content' => 'Neue Titel #1'), @@ -634,7 +681,14 @@ class TranslateBehaviorTest extends CakeTestCase { $result['Title'] = Set::sort($result['Title'], '{n}.id', 'asc'); $result['Content'] = Set::sort($result['Content'], '{n}.id', 'asc'); $expected = array( - 'TranslatedItem' => array('id' => 1, 'slug' => 'first_translated', 'locale' => 'cze', 'title' => 'Titulek #1', 'content' => 'Upraveny obsah #1'), + 'TranslatedItem' => array( + 'id' => 1, + 'slug' => 'first_translated', + 'locale' => 'cze', + 'title' => 'Titulek #1', + 'content' => 'Upraveny obsah #1', + 'translated_article_id' => 1, + ), 'Title' => array( array('id' => 1, 'locale' => 'eng', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'title', 'content' => 'Updated Title #1'), array('id' => 3, 'locale' => 'deu', 'model' => 'TranslatedItem', 'foreign_key' => 1, 'field' => 'title', 'content' => 'Titel #1'), @@ -650,6 +704,44 @@ class TranslateBehaviorTest extends CakeTestCase { $this->assertEquals($expected, $result); } +/** + * Test that saveAll() works with hasMany associations that contain + * translations. + * + * @return void + */ + public function testSaveAllTranslatedAssociations() { + $this->loadFixtures('Translate', 'TranslateArticle', 'TranslatedItem', 'TranslatedArticle', 'User'); + $Model = new TranslatedArticle(); + $Model->locale = 'eng'; + + $data = array( + 'TranslatedArticle' => array( + 'user_id' => 1, + 'published' => 'Y', + 'title' => 'Title (eng) #1', + 'body' => 'Body (eng) #1' + ), + 'TranslatedItem' => array( + array( + 'title' => 'Nuevo leyenda #1', + 'content' => 'Upraveny obsah #1' + ), + array( + 'title' => 'New Title #2', + 'content' => 'New Content #2' + ), + ) + ); + $result = $Model->saveAll($data); + $this->assertTrue($result); + + $result = $Model->TranslatedItem->find('all', array( + 'conditions' => array('translated_article_id' => $Model->id) + )); + $this->assertCount(2, $result); + } + /** * testValidation method * @@ -661,11 +753,13 @@ class TranslateBehaviorTest extends CakeTestCase { $TestModel = new TranslatedItem(); $TestModel->locale = 'eng'; $TestModel->validate['title'] = '/Only this title/'; - $data = array('TranslatedItem' => array( - 'id' => 1, - 'title' => array('eng' => 'New Title #1', 'deu' => 'Neue Titel #1', 'cze' => 'Novy Titulek #1'), - 'content' => array('eng' => 'New Content #1', 'deu' => 'Neue Inhalt #1', 'cze' => 'Novy Obsah #1') - )); + $data = array( + 'TranslatedItem' => array( + 'id' => 1, + 'title' => array('eng' => 'New Title #1', 'deu' => 'Neue Titel #1', 'cze' => 'Novy Titulek #1'), + 'content' => array('eng' => 'New Content #1', 'deu' => 'Neue Inhalt #1', 'cze' => 'Novy Obsah #1') + ) + ); $TestModel->create(); $this->assertFalse($TestModel->save($data)); $this->assertEquals(array('This field cannot be left blank'), $TestModel->validationErrors['title']); @@ -748,7 +842,8 @@ class TranslateBehaviorTest extends CakeTestCase { 'slug' => 'first_translated', 'locale' => 'eng', 'title' => 'Another Title #1', - 'content' => 'Another Content #1' + 'content' => 'Another Content #1', + 'translated_article_id' => 1, ) ); $this->assertEquals($expected, $result); @@ -760,7 +855,7 @@ class TranslateBehaviorTest extends CakeTestCase { * @return void */ public function testTranslateWithAssociations() { - $this->loadFixtures('TranslateArticle', 'TranslatedArticle', 'User', 'Comment', 'ArticlesTag', 'Tag'); + $this->loadFixtures('TranslateArticle', 'TranslatedArticle', 'TranslatedItem', 'User', 'Comment', 'ArticlesTag', 'Tag'); $TestModel = new TranslatedArticle(); $TestModel->locale = 'eng'; @@ -784,6 +879,23 @@ class TranslateBehaviorTest extends CakeTestCase { 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', 'created' => '2007-03-17 01:16:23', 'updated' => '2007-03-17 01:18:31' + ), + 'TranslatedItem' => array( + array( + 'id' => 1, + 'translated_article_id' => 1, + 'slug' => 'first_translated' + ), + array( + 'id' => 2, + 'translated_article_id' => 1, + 'slug' => 'second_translated' + ), + array( + 'id' => 3, + 'translated_article_id' => 1, + 'slug' => 'third_translated' + ), ) ); $this->assertEquals($expected, $result); @@ -863,7 +975,8 @@ class TranslateBehaviorTest extends CakeTestCase { 'slug' => 'first_translated', 'locale' => 'eng', 'content' => 'Content #1', - 'title' => 'Title #1' + 'title' => 'Title #1', + 'translated_article_id' => 1, )); $this->assertEquals($expected, $result); } diff --git a/lib/Cake/Test/Case/Model/models.php b/lib/Cake/Test/Case/Model/models.php index e5c2856e8..43bad3123 100644 --- a/lib/Cake/Test/Case/Model/models.php +++ b/lib/Cake/Test/Case/Model/models.php @@ -3119,7 +3119,7 @@ class TranslatedItem2 extends CakeTestModel { /** * translateModel property * - * @var string 'TranslateTestModel' + * @var string */ public $translateModel = 'TranslateWithPrefix'; @@ -3163,7 +3163,7 @@ class TranslatedItemWithTable extends CakeTestModel { /** * translateModel property * - * @var string 'TranslateTestModel' + * @var string */ public $translateModel = 'TranslateTestModel'; @@ -3248,6 +3248,13 @@ class TranslatedArticle extends CakeTestModel { */ public $belongsTo = array('User'); +/** + * belongsTo property + * + * @var array + */ + public $hasMany = array('TranslatedItem'); + } class CounterCacheUser extends CakeTestModel { diff --git a/lib/Cake/Test/Fixture/TranslatedItemFixture.php b/lib/Cake/Test/Fixture/TranslatedItemFixture.php index 28485f26d..3d5490371 100644 --- a/lib/Cake/Test/Fixture/TranslatedItemFixture.php +++ b/lib/Cake/Test/Fixture/TranslatedItemFixture.php @@ -38,6 +38,7 @@ class TranslatedItemFixture extends CakeTestFixture { */ public $fields = array( 'id' => array('type' => 'integer', 'key' => 'primary'), + 'translated_article_id' => array('type' => 'integer'), 'slug' => array('type' => 'string', 'null' => false) ); @@ -47,8 +48,8 @@ class TranslatedItemFixture extends CakeTestFixture { * @var array */ public $records = array( - array('slug' => 'first_translated'), - array('slug' => 'second_translated'), - array('slug' => 'third_translated') + array('translated_article_id' => 1, 'slug' => 'first_translated'), + array('translated_article_id' => 1, 'slug' => 'second_translated'), + array('translated_article_id' => 1, 'slug' => 'third_translated') ); } From a1e06b9c8b6b87e18066803c617d5e2dc8878018 Mon Sep 17 00:00:00 2001 From: mark_story Date: Sun, 6 May 2012 23:39:02 -0400 Subject: [PATCH 06/29] Fix error in Sqlite tests. --- lib/Cake/Test/Case/Model/Behavior/TranslateBehaviorTest.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/Cake/Test/Case/Model/Behavior/TranslateBehaviorTest.php b/lib/Cake/Test/Case/Model/Behavior/TranslateBehaviorTest.php index ad2e3cef3..170e7f362 100644 --- a/lib/Cake/Test/Case/Model/Behavior/TranslateBehaviorTest.php +++ b/lib/Cake/Test/Case/Model/Behavior/TranslateBehaviorTest.php @@ -531,7 +531,7 @@ class TranslateBehaviorTest extends CakeTestCase { 'slug' => 'fourth_translated', 'title' => 'Leyenda #4', 'content' => 'Contenido #4', - 'translated_article_id' => null + 'translated_article_id' => 1, ); $TestModel->create($data); $TestModel->save(); @@ -724,10 +724,12 @@ class TranslateBehaviorTest extends CakeTestCase { ), 'TranslatedItem' => array( array( + 'slug' => '', 'title' => 'Nuevo leyenda #1', 'content' => 'Upraveny obsah #1' ), array( + 'slug' => '', 'title' => 'New Title #2', 'content' => 'New Content #2' ), From 45a96ae588f88ea1046b327a12a564fb094903fc Mon Sep 17 00:00:00 2001 From: Jelle Henkens Date: Mon, 7 May 2012 21:48:58 +0100 Subject: [PATCH 07/29] Fixing order inconsistency in queryString and bug with string based existing querystring --- lib/Cake/Routing/Router.php | 11 +++++-- lib/Cake/Test/Case/Routing/RouterTest.php | 37 +++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/lib/Cake/Routing/Router.php b/lib/Cake/Routing/Router.php index aab278608..081a44812 100644 --- a/lib/Cake/Routing/Router.php +++ b/lib/Cake/Routing/Router.php @@ -957,12 +957,19 @@ class Router { $out = ''; if (is_array($q)) { - $q = array_merge($extra, $q); + $q = array_merge($q, $extra); } else { $out = $q; $q = $extra; } - $out .= http_build_query($q, null, $join); + $addition = http_build_query($q, null, $join); + + if ($out && $addition) { + $out .= $join; + } + + $out .= $addition; + if (isset($out[0]) && $out[0] != '?') { $out = '?' . $out; } diff --git a/lib/Cake/Test/Case/Routing/RouterTest.php b/lib/Cake/Test/Case/Routing/RouterTest.php index db3a3a7a3..6d75fc831 100644 --- a/lib/Cake/Test/Case/Routing/RouterTest.php +++ b/lib/Cake/Test/Case/Routing/RouterTest.php @@ -2590,4 +2590,41 @@ class RouterTest extends CakeTestCase { Router::defaultRouteClass('NonExistentClass'); } +/** + * Tests generating well-formed querystrings + * + * @return void + */ + public function testQueryString() { + $result = Router::queryString(array('var' => 'foo bar')); + $expected = '?var=foo+bar'; + $this->assertEquals($expected, $result); + + $result = Router::queryString(false, array('some' => 'param', 'foo' => 'bar')); + $expected = '?some=param&foo=bar'; + $this->assertEquals($expected, $result); + + $existing = array('apple' => 'red', 'pear' => 'green'); + $result = Router::queryString($existing, array('some' => 'param', 'foo' => 'bar')); + $expected = '?apple=red&pear=green&some=param&foo=bar'; + $this->assertEquals($expected, $result); + + $existing = 'apple=red&pear=green'; + $result = Router::queryString($existing, array('some' => 'param', 'foo' => 'bar')); + $expected = '?apple=red&pear=green&some=param&foo=bar'; + $this->assertEquals($expected, $result); + + $existing = '?apple=red&pear=green'; + $result = Router::queryString($existing, array('some' => 'param', 'foo' => 'bar')); + $expected = '?apple=red&pear=green&some=param&foo=bar'; + $this->assertEquals($expected, $result); + + $result = Router::queryString('apple=red&pear=green'); + $expected = '?apple=red&pear=green'; + $this->assertEquals($expected, $result); + + $result = Router::queryString('foo=bar', array('php' => 'nut', 'jose' => 'zap'), true); + $expected = '?foo=bar&php=nut&jose=zap'; + $this->assertEquals($expected, $result); + } } From fbba3621b5e522d1c7f7fe4c175a3298871469de Mon Sep 17 00:00:00 2001 From: mark_story Date: Mon, 7 May 2012 21:36:28 -0400 Subject: [PATCH 08/29] Fix additional issues with saveAll(). Fixes #2857 --- lib/Cake/Model/Behavior/TranslateBehavior.php | 24 +++++++++++++++---- .../Model/Behavior/TranslateBehaviorTest.php | 2 ++ 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/lib/Cake/Model/Behavior/TranslateBehavior.php b/lib/Cake/Model/Behavior/TranslateBehavior.php index 79036e01a..83378a6c2 100644 --- a/lib/Cake/Model/Behavior/TranslateBehavior.php +++ b/lib/Cake/Model/Behavior/TranslateBehavior.php @@ -318,10 +318,19 @@ class TranslateBehavior extends ModelBehavior { /** * beforeSave callback. * + * Copies data into the runtime property when `$options['validate']` is + * disabled. Or the runtime data hasn't been set yet. + * * @param Model $model Model save was called on. * @return boolean true. */ - public function beforeSave(Model $model) { + public function beforeSave(Model $model, $options = array()) { + if (isset($options['validate']) && $options['validate'] == false) { + unset($this->runtime[$model->alias]['beforeSave']); + } + if (isset($this->runtime[$model->alias]['beforeSave'])) { + return true; + } $this->_setRuntimeData($model); return true; } @@ -338,7 +347,7 @@ class TranslateBehavior extends ModelBehavior { */ protected function _setRuntimeData(Model $model) { $locale = $this->_getLocale($model); - if (empty($locale) || isset($this->runtime[$model->alias]['beforeSave'])) { + if (empty($locale)) { return true; } $fields = array_merge($this->settings[$model->alias], $this->runtime[$model->alias]['fields']); @@ -370,12 +379,17 @@ class TranslateBehavior extends ModelBehavior { * @return void */ public function afterSave(Model $model, $created) { - if (!isset($this->runtime[$model->alias]['beforeSave'])) { + if (!isset($this->runtime[$model->alias]['beforeValidate']) && !isset($this->runtime[$model->alias]['beforeSave'])) { return true; } $locale = $this->_getLocale($model); - $tempData = $this->runtime[$model->alias]['beforeSave']; - unset($this->runtime[$model->alias]['beforeSave']); + if (isset($this->runtime[$model->alias]['beforeValidate'])) { + $tempData = $this->runtime[$model->alias]['beforeValidate']; + } else { + $tempData = $this->runtime[$model->alias]['beforeSave']; + } + + unset($this->runtime[$model->alias]['beforeValidate'], $this->runtime[$model->alias]['beforeSave']); $conditions = array('model' => $model->alias, 'foreign_key' => $model->id); $RuntimeModel = $this->translateModel($model); diff --git a/lib/Cake/Test/Case/Model/Behavior/TranslateBehaviorTest.php b/lib/Cake/Test/Case/Model/Behavior/TranslateBehaviorTest.php index 170e7f362..eba3d42cb 100644 --- a/lib/Cake/Test/Case/Model/Behavior/TranslateBehaviorTest.php +++ b/lib/Cake/Test/Case/Model/Behavior/TranslateBehaviorTest.php @@ -742,6 +742,8 @@ class TranslateBehaviorTest extends CakeTestCase { 'conditions' => array('translated_article_id' => $Model->id) )); $this->assertCount(2, $result); + $this->assertEquals($data['TranslatedItem'][0]['title'], $result[0]['TranslatedItem']['title']); + $this->assertEquals($data['TranslatedItem'][1]['title'], $result[1]['TranslatedItem']['title']); } /** From 597913aa5b260e3dc7163240c55f2af0e3c60fd3 Mon Sep 17 00:00:00 2001 From: mark_story Date: Mon, 7 May 2012 22:10:17 -0400 Subject: [PATCH 09/29] Fix test cases with postgres. Setting id values confuses postgres' sequences. --- .../Model/Behavior/TranslateBehaviorTest.php | 1 + .../Test/Fixture/TranslateArticleFixture.php | 36 +++++++++---------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/lib/Cake/Test/Case/Model/Behavior/TranslateBehaviorTest.php b/lib/Cake/Test/Case/Model/Behavior/TranslateBehaviorTest.php index eba3d42cb..98fb2e291 100644 --- a/lib/Cake/Test/Case/Model/Behavior/TranslateBehaviorTest.php +++ b/lib/Cake/Test/Case/Model/Behavior/TranslateBehaviorTest.php @@ -717,6 +717,7 @@ class TranslateBehaviorTest extends CakeTestCase { $data = array( 'TranslatedArticle' => array( + 'id' => 4, 'user_id' => 1, 'published' => 'Y', 'title' => 'Title (eng) #1', diff --git a/lib/Cake/Test/Fixture/TranslateArticleFixture.php b/lib/Cake/Test/Fixture/TranslateArticleFixture.php index 8ab79aec1..2fcfd9672 100644 --- a/lib/Cake/Test/Fixture/TranslateArticleFixture.php +++ b/lib/Cake/Test/Fixture/TranslateArticleFixture.php @@ -58,23 +58,23 @@ class TranslateArticleFixture extends CakeTestFixture { * @var array */ public $records = array( - array('id' => 1, 'locale' => 'eng', 'model' => 'TranslatedArticle', 'foreign_key' => 1, 'field' => 'title', 'content' => 'Title (eng) #1'), - array('id' => 2, 'locale' => 'eng', 'model' => 'TranslatedArticle', 'foreign_key' => 1, 'field' => 'body', 'content' => 'Body (eng) #1'), - array('id' => 3, 'locale' => 'deu', 'model' => 'TranslatedArticle', 'foreign_key' => 1, 'field' => 'title', 'content' => 'Title (deu) #1'), - array('id' => 4, 'locale' => 'deu', 'model' => 'TranslatedArticle', 'foreign_key' => 1, 'field' => 'body', 'content' => 'Body (deu) #1'), - array('id' => 5, 'locale' => 'cze', 'model' => 'TranslatedArticle', 'foreign_key' => 1, 'field' => 'title', 'content' => 'Title (cze) #1'), - array('id' => 6, 'locale' => 'cze', 'model' => 'TranslatedArticle', 'foreign_key' => 1, 'field' => 'body', 'content' => 'Body (cze) #1'), - array('id' => 7, 'locale' => 'eng', 'model' => 'TranslatedArticle', 'foreign_key' => 2, 'field' => 'title', 'content' => 'Title (eng) #2'), - array('id' => 8, 'locale' => 'eng', 'model' => 'TranslatedArticle', 'foreign_key' => 2, 'field' => 'body', 'content' => 'Body (eng) #2'), - array('id' => 9, 'locale' => 'deu', 'model' => 'TranslatedArticle', 'foreign_key' => 2, 'field' => 'title', 'content' => 'Title (deu) #2'), - array('id' => 10, 'locale' => 'deu', 'model' => 'TranslatedArticle', 'foreign_key' => 2, 'field' => 'body', 'content' => 'Body (deu) #2'), - array('id' => 11, 'locale' => 'cze', 'model' => 'TranslatedArticle', 'foreign_key' => 2, 'field' => 'title', 'content' => 'Title (cze) #2'), - array('id' => 12, 'locale' => 'cze', 'model' => 'TranslatedArticle', 'foreign_key' => 2, 'field' => 'body', 'content' => 'Body (cze) #2'), - array('id' => 13, 'locale' => 'eng', 'model' => 'TranslatedArticle', 'foreign_key' => 3, 'field' => 'title', 'content' => 'Title (eng) #3'), - array('id' => 14, 'locale' => 'eng', 'model' => 'TranslatedArticle', 'foreign_key' => 3, 'field' => 'body', 'content' => 'Body (eng) #3'), - array('id' => 15, 'locale' => 'deu', 'model' => 'TranslatedArticle', 'foreign_key' => 3, 'field' => 'title', 'content' => 'Title (deu) #3'), - array('id' => 16, 'locale' => 'deu', 'model' => 'TranslatedArticle', 'foreign_key' => 3, 'field' => 'body', 'content' => 'Body (deu) #3'), - array('id' => 17, 'locale' => 'cze', 'model' => 'TranslatedArticle', 'foreign_key' => 3, 'field' => 'title', 'content' => 'Title (cze) #3'), - array('id' => 18, 'locale' => 'cze', 'model' => 'TranslatedArticle', 'foreign_key' => 3, 'field' => 'body', 'content' => 'Body (cze) #3') + array('locale' => 'eng', 'model' => 'TranslatedArticle', 'foreign_key' => 1, 'field' => 'title', 'content' => 'Title (eng) #1'), + array('locale' => 'eng', 'model' => 'TranslatedArticle', 'foreign_key' => 1, 'field' => 'body', 'content' => 'Body (eng) #1'), + array('locale' => 'deu', 'model' => 'TranslatedArticle', 'foreign_key' => 1, 'field' => 'title', 'content' => 'Title (deu) #1'), + array('locale' => 'deu', 'model' => 'TranslatedArticle', 'foreign_key' => 1, 'field' => 'body', 'content' => 'Body (deu) #1'), + array('locale' => 'cze', 'model' => 'TranslatedArticle', 'foreign_key' => 1, 'field' => 'title', 'content' => 'Title (cze) #1'), + array('locale' => 'cze', 'model' => 'TranslatedArticle', 'foreign_key' => 1, 'field' => 'body', 'content' => 'Body (cze) #1'), + array('locale' => 'eng', 'model' => 'TranslatedArticle', 'foreign_key' => 2, 'field' => 'title', 'content' => 'Title (eng) #2'), + array('locale' => 'eng', 'model' => 'TranslatedArticle', 'foreign_key' => 2, 'field' => 'body', 'content' => 'Body (eng) #2'), + array('locale' => 'deu', 'model' => 'TranslatedArticle', 'foreign_key' => 2, 'field' => 'title', 'content' => 'Title (deu) #2'), + array('locale' => 'deu', 'model' => 'TranslatedArticle', 'foreign_key' => 2, 'field' => 'body', 'content' => 'Body (deu) #2'), + array('locale' => 'cze', 'model' => 'TranslatedArticle', 'foreign_key' => 2, 'field' => 'title', 'content' => 'Title (cze) #2'), + array('locale' => 'cze', 'model' => 'TranslatedArticle', 'foreign_key' => 2, 'field' => 'body', 'content' => 'Body (cze) #2'), + array('locale' => 'eng', 'model' => 'TranslatedArticle', 'foreign_key' => 3, 'field' => 'title', 'content' => 'Title (eng) #3'), + array('locale' => 'eng', 'model' => 'TranslatedArticle', 'foreign_key' => 3, 'field' => 'body', 'content' => 'Body (eng) #3'), + array('locale' => 'deu', 'model' => 'TranslatedArticle', 'foreign_key' => 3, 'field' => 'title', 'content' => 'Title (deu) #3'), + array('locale' => 'deu', 'model' => 'TranslatedArticle', 'foreign_key' => 3, 'field' => 'body', 'content' => 'Body (deu) #3'), + array('locale' => 'cze', 'model' => 'TranslatedArticle', 'foreign_key' => 3, 'field' => 'title', 'content' => 'Title (cze) #3'), + array('locale' => 'cze', 'model' => 'TranslatedArticle', 'foreign_key' => 3, 'field' => 'body', 'content' => 'Body (cze) #3') ); } From 45a86a8b20853801234f0ffb749259a24683a74c Mon Sep 17 00:00:00 2001 From: Christian Winther Date: Wed, 9 May 2012 22:57:21 +0200 Subject: [PATCH 10/29] Copy changes made to $this->data in beforeValidate callbacks back to saveAssociated data variable This allow changes in beforeValidate to be saved --- lib/Cake/Model/Model.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Cake/Model/Model.php b/lib/Cake/Model/Model.php index 026e72bb9..235ae7ff1 100644 --- a/lib/Cake/Model/Model.php +++ b/lib/Cake/Model/Model.php @@ -2178,6 +2178,7 @@ class Model extends Object implements CakeEventListener { if ($options['validate'] === 'first') { $validates = $this->validateAssociated($data, $options); + $data = $this->data; if ((!$validates && $options['atomic']) || (!$options['atomic'] && in_array(false, $validates, true))) { return $validates; } From 07735069fad116d205fd97cb539f7b6241a1e6ce Mon Sep 17 00:00:00 2001 From: Jelle Henkens Date: Wed, 9 May 2012 22:32:57 +0100 Subject: [PATCH 11/29] Adding support for a trailing & or & in the existing queryString --- lib/Cake/Routing/Router.php | 2 +- lib/Cake/Test/Case/Routing/RouterTest.php | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/Cake/Routing/Router.php b/lib/Cake/Routing/Router.php index 081a44812..7015ece0a 100644 --- a/lib/Cake/Routing/Router.php +++ b/lib/Cake/Routing/Router.php @@ -964,7 +964,7 @@ class Router { } $addition = http_build_query($q, null, $join); - if ($out && $addition) { + if ($out && $addition && substr($out, strlen($join) * -1, strlen($join)) != $join) { $out .= $join; } diff --git a/lib/Cake/Test/Case/Routing/RouterTest.php b/lib/Cake/Test/Case/Routing/RouterTest.php index 6d75fc831..67bcea886 100644 --- a/lib/Cake/Test/Case/Routing/RouterTest.php +++ b/lib/Cake/Test/Case/Routing/RouterTest.php @@ -2626,5 +2626,13 @@ class RouterTest extends CakeTestCase { $result = Router::queryString('foo=bar', array('php' => 'nut', 'jose' => 'zap'), true); $expected = '?foo=bar&php=nut&jose=zap'; $this->assertEquals($expected, $result); + + $result = Router::queryString('foo=bar&', array('php' => 'nut', 'jose' => 'zap'), true); + $expected = '?foo=bar&php=nut&jose=zap'; + $this->assertEquals($expected, $result); + + $result = Router::queryString('foo=bar&', array('php' => 'nut', 'jose' => 'zap')); + $expected = '?foo=bar&php=nut&jose=zap'; + $this->assertEquals($expected, $result); } } From 3b154fa32512317e19b79ab4bc2815c2f74f5796 Mon Sep 17 00:00:00 2001 From: Christian Winther Date: Wed, 9 May 2012 23:34:37 +0200 Subject: [PATCH 12/29] Another fix for Pull request 642 --- lib/Cake/Model/Model.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Cake/Model/Model.php b/lib/Cake/Model/Model.php index 235ae7ff1..43c6d04e0 100644 --- a/lib/Cake/Model/Model.php +++ b/lib/Cake/Model/Model.php @@ -2044,6 +2044,7 @@ class Model extends Object implements CakeEventListener { if ($options['validate'] === 'first') { $validates = $this->validateMany($data, $options); + $data = $this->data; if ((!$validates && $options['atomic']) || (!$options['atomic'] && in_array(false, $validates, true))) { return $validates; } From e8fce37a6562904083f40b4cb3f1649bf23ceeff Mon Sep 17 00:00:00 2001 From: Christian Winther Date: Wed, 9 May 2012 23:59:02 +0200 Subject: [PATCH 13/29] Fixing saveMany --- lib/Cake/Model/Model.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/Cake/Model/Model.php b/lib/Cake/Model/Model.php index 43c6d04e0..ab12d98db 100644 --- a/lib/Cake/Model/Model.php +++ b/lib/Cake/Model/Model.php @@ -2117,6 +2117,7 @@ class Model extends Object implements CakeEventListener { } else { $validates = $this->create($record) && $this->validates($options); } + $data[$key] = $this->data; if ($validates === false || (is_array($validates) && in_array(false, $validates, true))) { $validationErrors[$key] = $this->validationErrors; $validates = false; @@ -2125,6 +2126,7 @@ class Model extends Object implements CakeEventListener { } $return[$key] = $validates; } + $this->data = $data; $this->validationErrors = $validationErrors; if (!$options['atomic']) { return $return; From 07bf73e88f8105f7424a3ddcc37a2a40c15e3608 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Wed, 9 May 2012 17:57:48 -0430 Subject: [PATCH 14/29] Revert "Merge pull request #643 from nodesagency/hotfix/beforeValidate-copy-data" This reverts commit b4a42e4a03a1c5a7321769636283ca163656b47f, reversing changes made to 6bd7da36e7e74fb0f83f619a552a852b8abe63a8. --- lib/Cake/Model/Model.php | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/Cake/Model/Model.php b/lib/Cake/Model/Model.php index ab12d98db..6f4f8cb9d 100644 --- a/lib/Cake/Model/Model.php +++ b/lib/Cake/Model/Model.php @@ -2044,7 +2044,6 @@ class Model extends Object implements CakeEventListener { if ($options['validate'] === 'first') { $validates = $this->validateMany($data, $options); - $data = $this->data; if ((!$validates && $options['atomic']) || (!$options['atomic'] && in_array(false, $validates, true))) { return $validates; } From 12ae03b87655059eb94ede1a58579b64fdf33ed0 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Wed, 9 May 2012 17:58:08 -0430 Subject: [PATCH 15/29] Revert "Merge pull request #644 from nodesagency/hotfix/beforeValidate-copy-data" This reverts commit 0f54758e5a5e2ac369234e506827925bd781d9ea, reversing changes made to b4a42e4a03a1c5a7321769636283ca163656b47f. --- lib/Cake/Model/Model.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/Cake/Model/Model.php b/lib/Cake/Model/Model.php index 6f4f8cb9d..235ae7ff1 100644 --- a/lib/Cake/Model/Model.php +++ b/lib/Cake/Model/Model.php @@ -2116,7 +2116,6 @@ class Model extends Object implements CakeEventListener { } else { $validates = $this->create($record) && $this->validates($options); } - $data[$key] = $this->data; if ($validates === false || (is_array($validates) && in_array(false, $validates, true))) { $validationErrors[$key] = $this->validationErrors; $validates = false; @@ -2125,7 +2124,6 @@ class Model extends Object implements CakeEventListener { } $return[$key] = $validates; } - $this->data = $data; $this->validationErrors = $validationErrors; if (!$options['atomic']) { return $return; From 0fb025f6dc7192f00e7af7db595b5b2cdb0fd93a Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Wed, 9 May 2012 23:51:27 -0430 Subject: [PATCH 16/29] Fixing error with validateMany and validateAssociated not saving values altered in beforeValidate callbacks --- lib/Cake/Model/Model.php | 12 ++- .../Test/Case/Model/ModelValidationTest.php | 81 +++++++++++++++- lib/Cake/Test/Case/Model/ModelWriteTest.php | 97 +++++++++---------- lib/Cake/Test/Case/Model/models.php | 16 +++ 4 files changed, 151 insertions(+), 55 deletions(-) diff --git a/lib/Cake/Model/Model.php b/lib/Cake/Model/Model.php index 235ae7ff1..c527bbd29 100644 --- a/lib/Cake/Model/Model.php +++ b/lib/Cake/Model/Model.php @@ -2107,14 +2107,15 @@ class Model extends Object implements CakeEventListener { * Otherwise: array similar to the $data array passed, but values are set to true/false * depending on whether each record validated successfully. */ - public function validateMany($data, $options = array()) { + public function validateMany(&$data, $options = array()) { $options = array_merge(array('atomic' => true, 'deep' => false), $options); $this->validationErrors = $validationErrors = $return = array(); - foreach ($data as $key => $record) { + foreach ($data as $key => &$record) { if ($options['deep']) { $validates = $this->validateAssociated($record, $options); } else { $validates = $this->create($record) && $this->validates($options); + $data[$key] = $this->data; } if ($validates === false || (is_array($validates) && in_array(false, $validates, true))) { $validationErrors[$key] = $this->validationErrors; @@ -2178,7 +2179,6 @@ class Model extends Object implements CakeEventListener { if ($options['validate'] === 'first') { $validates = $this->validateAssociated($data, $options); - $data = $this->data; if ((!$validates && $options['atomic']) || (!$options['atomic'] && in_array(false, $validates, true))) { return $validates; } @@ -2188,6 +2188,7 @@ class Model extends Object implements CakeEventListener { $db = $this->getDataSource(); $transactionBegun = $db->begin(); } + $associations = $this->getAssociated(); $return = array(); $validates = true; @@ -2306,7 +2307,7 @@ class Model extends Object implements CakeEventListener { * Otherwise: array similar to the $data array passed, but values are set to true/false * depending on whether each record validated successfully. */ - public function validateAssociated($data, $options = array()) { + public function validateAssociated(&$data, $options = array()) { $options = array_merge(array('atomic' => true, 'deep' => false), $options); $this->validationErrors = $validationErrors = $return = array(); if (!($this->create($data) && $this->validates($options))) { @@ -2315,8 +2316,9 @@ class Model extends Object implements CakeEventListener { } else { $return[$this->alias] = true; } + $data[$this->alias] = $this->data[$this->alias]; $associations = $this->getAssociated(); - foreach ($data as $association => $values) { + foreach ($data as $association => &$values) { $validates = true; if (isset($associations[$association])) { if (in_array($associations[$association], array('belongsTo', 'hasOne'))) { diff --git a/lib/Cake/Test/Case/Model/ModelValidationTest.php b/lib/Cake/Test/Case/Model/ModelValidationTest.php index 451224e61..6b6accf96 100644 --- a/lib/Cake/Test/Case/Model/ModelValidationTest.php +++ b/lib/Cake/Test/Case/Model/ModelValidationTest.php @@ -991,4 +991,83 @@ class ModelValidationTest extends BaseModelTest { $this->assertFalse($Article->validates()); } -} +/** + * Tests that altering data in a beforeValidate callback will lead to saving those + * values in database + * + * @return void + */ + public function testValidateFirstWithBeforeValidate() { + $this->loadFixtures('Article', 'User'); + $model = new CustomArticle(); + $model->validate = array( + 'title' => array( + 'notempty' => array( + 'rule' => 'notEmpty', + 'required' => true, + 'allowEmpty' => false + ) + ) + ); + $data = array( + 'CustomArticle' => array( + 'body' => 'foo0' + ) + ); + $result = $model->saveAll($data, array('validate' => 'first')); + $this->assertTrue($result); + + $title = $model->field('title', array('body' => 'foo0')); + $this->assertEquals('foo', $title); + + $data = array( + array('body' => 'foo1'), + array('body' => 'foo2'), + array('body' => 'foo3') + ); + + $result = $model->saveAll($data, array('validate' => 'first')); + $this->assertTrue($result); + + $this->assertEquals('foo', $model->field('title', array('body' => 'foo1'))); + $this->assertEquals('foo', $model->field('title', array('body' => 'foo2'))); + $this->assertEquals('foo', $model->field('title', array('body' => 'foo3'))); + } + +/** + * Tests that altering data in a beforeValidate callback will lead to saving those + * values in database + * + * @return void + */ + public function testValidateAssociatedWithBeforeValidate() { + $this->loadFixtures('Article', 'User'); + $model = new CustomArticle(); + $model->validate = array( + 'title' => array( + 'notempty' => array( + 'rule' => 'notEmpty', + 'required' => true + ) + ) + ); + $articles = array( + array('body' => 'foo1'), + array('body' => 'foo2'), + array('body' => 'foo3') + ); + $user = new User(); + $user->hasMany['CustomArticle'] = array('foreignKey' => 'user_id'); + $data = array( + 'User' => array('user' => 'foo', 'password' => 'bar'), + 'CustomArticle' => $articles + ); + $result = $user->saveAll($data, array('validate' => 'first')); + $this->assertTrue($result); + + $this->assertEquals('foo', $model->field('title', array('body' => 'foo1'))); + $this->assertEquals('foo', $model->field('title', array('body' => 'foo2'))); + $this->assertEquals('foo', $model->field('title', array('body' => 'foo3'))); + } + +} \ No newline at end of file diff --git a/lib/Cake/Test/Case/Model/ModelWriteTest.php b/lib/Cake/Test/Case/Model/ModelWriteTest.php index f931b7ca3..f72b2a8bc 100644 --- a/lib/Cake/Test/Case/Model/ModelWriteTest.php +++ b/lib/Cake/Test/Case/Model/ModelWriteTest.php @@ -4277,7 +4277,7 @@ class ModelWriteTest extends BaseModelTest { 'author_id' => '3', 'title' => 'Just update the title', 'body' => 'Second Post Body', - 'published' => 'Y', + 'published' => 'N', 'created' => '2007-03-18 10:41:23' )), array( @@ -4366,7 +4366,7 @@ class ModelWriteTest extends BaseModelTest { 'author_id' => '3', 'title' => 'Just update the title', 'body' => 'Second Post Body', - 'published' => 'Y', + 'published' => 'N', 'created' => '2007-03-18 10:41:23' ) ), @@ -5631,7 +5631,7 @@ class ModelWriteTest extends BaseModelTest { 'author_id' => '3', 'title' => 'Just update the title', 'body' => 'Second Post Body', - 'published' => 'Y', + 'published' => 'N', 'created' => '2007-03-18 10:41:23' ) ), @@ -5727,7 +5727,7 @@ class ModelWriteTest extends BaseModelTest { 'author_id' => '3', 'title' => 'Just update the title', 'body' => 'Second Post Body', - 'published' => 'Y', + 'published' => 'N', )), array( 'Post' => array( @@ -5779,24 +5779,24 @@ class ModelWriteTest extends BaseModelTest { public function testValidateMany() { $TestModel = new Article(); $TestModel->validate = array('title' => 'notEmpty'); - $result = $TestModel->validateMany( - array( + $data = array( 0 => array('title' => ''), 1 => array('title' => 'title 1'), 2 => array('title' => 'title 2'), - )); + ); + $result = $TestModel->validateMany($data); $this->assertFalse($result); $expected = array( 0 => array('title' => array('This field cannot be left blank')), ); $this->assertEquals($expected, $TestModel->validationErrors); - $result = $TestModel->validateMany( - array( + $data = array( 0 => array('title' => 'title 0'), 1 => array('title' => ''), 2 => array('title' => 'title 2'), - )); + ); + $result = $TestModel->validateMany($data); $this->assertFalse($result); $expected = array( 1 => array('title' => array('This field cannot be left blank')), @@ -5966,47 +5966,46 @@ class ModelWriteTest extends BaseModelTest { $TestModel->belongsTo = $TestModel->hasAndBelongsToMany = array(); $TestModel->Comment->validate = array('comment' => 'notEmpty'); - $result = $TestModel->validateAssociated( - array( - 'Article' => array('id' => 2), - 'Comment' => array( - array( - 'id' => 1, - 'comment' => '', - 'published' => 'Y', - 'user_id' => 1), - array( - 'id' => 2, - 'comment' => - 'comment', - 'published' => 'Y', - 'user_id' => 1 - )))); + $data = array( + 'Article' => array('id' => 2), + 'Comment' => array( + array( + 'id' => 1, + 'comment' => '', + 'published' => 'Y', + 'user_id' => 1), + array( + 'id' => 2, + 'comment' => + 'comment', + 'published' => 'Y', + 'user_id' => 1 + ))); + $result = $TestModel->validateAssociated($data); $this->assertFalse($result); - $result = $TestModel->validateAssociated( - array( - 'Article' => array('id' => 2), - 'Comment' => array( - array( - 'id' => 1, - 'comment' => '', - 'published' => 'Y', - 'user_id' => 1 - ), - array( - 'id' => 2, - 'comment' => 'comment', - 'published' => 'Y', - 'user_id' => 1 - ), - array( - 'id' => 3, - 'comment' => '', - 'published' => 'Y', - 'user_id' => 1 - ))), - array( + $data = array( + 'Article' => array('id' => 2), + 'Comment' => array( + array( + 'id' => 1, + 'comment' => '', + 'published' => 'Y', + 'user_id' => 1 + ), + array( + 'id' => 2, + 'comment' => 'comment', + 'published' => 'Y', + 'user_id' => 1 + ), + array( + 'id' => 3, + 'comment' => '', + 'published' => 'Y', + 'user_id' => 1 + ))); + $result = $TestModel->validateAssociated($data, array( 'atomic' => false )); $expected = array( diff --git a/lib/Cake/Test/Case/Model/models.php b/lib/Cake/Test/Case/Model/models.php index 43bad3123..41138e155 100644 --- a/lib/Cake/Test/Case/Model/models.php +++ b/lib/Cake/Test/Case/Model/models.php @@ -4948,6 +4948,13 @@ class CustomArticle extends AppModel { */ public $findMethods = array('unPublished' => true); +/** + * belongsTo property + * + * @var array + */ + public $belongsTo = array('User'); + /** * _findUnPublished custom find * @@ -4961,4 +4968,13 @@ class CustomArticle extends AppModel { return $results; } +/** + * Alters title data + * + * @return void + **/ + public function beforeValidate() { + $this->data[$this->alias]['title'] = 'foo'; + } + } From 00a34f14fe49940ccb13ce9f1bde5c27b61d9362 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Thu, 10 May 2012 00:05:30 -0430 Subject: [PATCH 17/29] Added warning messages in doc block for validateMany and validateAssociated about first param being passed by reference --- lib/Cake/Model/Model.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/Cake/Model/Model.php b/lib/Cake/Model/Model.php index c527bbd29..9e90a4e03 100644 --- a/lib/Cake/Model/Model.php +++ b/lib/Cake/Model/Model.php @@ -2100,6 +2100,10 @@ class Model extends Object implements CakeEventListener { * - fieldList: Equivalent to the $fieldList parameter in Model::save() * - deep: If set to true, all associated data will be validated as well. * + * Warning: This method could potentially change the passed argument `$data`, + * If you do not want this to happen, make a copy of `$data` before passing it + * to this method + * * @param array $data Record data to validate. This should be a numerically-indexed array * @param array $options Options to use when validating record data (see above), See also $options of validates(). * @return boolean True on success, or false on failure. @@ -2301,6 +2305,10 @@ class Model extends Object implements CakeEventListener { * - fieldList: Equivalent to the $fieldList parameter in Model::save() * - deep: If set to true, not only directly associated data , but deeper nested associated data is validated as well. * + * Warning: This method could potentially change the passed argument `$data`, + * If you do not want this to happen, make a copy of `$data` before passing it + * to this method + * * @param array $data Record data to validate. This should be an array indexed by association name. * @param array $options Options to use when validating record data (see above), See also $options of validates(). * @return array|boolean If atomic: True on success, or false on failure. From 6da2fe8323403545220a751bac9c280fb2b1a190 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Thu, 10 May 2012 00:47:35 -0430 Subject: [PATCH 18/29] Ugly hack to fix saveAll deep and beforeValidate callbacks changing model data --- lib/Cake/Model/Model.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/Cake/Model/Model.php b/lib/Cake/Model/Model.php index 9e90a4e03..2fa1a7e13 100644 --- a/lib/Cake/Model/Model.php +++ b/lib/Cake/Model/Model.php @@ -2324,7 +2324,16 @@ class Model extends Object implements CakeEventListener { } else { $return[$this->alias] = true; } - $data[$this->alias] = $this->data[$this->alias]; + + if (empty($options['deep'])) { + $data[$this->alias] = $this->data[$this->alias]; + } else { + $modelData = $this->data; + $recordData = $modelData[$this->alias]; + unset($modelData[$this->alias]); + $data = $modelData + array_merge($data, $recordData); + } + $associations = $this->getAssociated(); foreach ($data as $association => &$values) { $validates = true; From b54dc69f6405ffb1a6a26524978594305d93ccd0 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Thu, 10 May 2012 00:52:54 -0430 Subject: [PATCH 19/29] Making validateAssociated overwrite data consistently with validateMany --- lib/Cake/Model/Model.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Cake/Model/Model.php b/lib/Cake/Model/Model.php index 2fa1a7e13..8a2a9b223 100644 --- a/lib/Cake/Model/Model.php +++ b/lib/Cake/Model/Model.php @@ -2326,7 +2326,7 @@ class Model extends Object implements CakeEventListener { } if (empty($options['deep'])) { - $data[$this->alias] = $this->data[$this->alias]; + $data = $this->data; } else { $modelData = $this->data; $recordData = $modelData[$this->alias]; From 5b67534acc4f6d9c708676bcc16258789c4465b1 Mon Sep 17 00:00:00 2001 From: Rachman Chavik Date: Wed, 9 May 2012 12:04:24 +0700 Subject: [PATCH 20/29] fix incorrect __construct params for mock object --- lib/Cake/Test/Case/Console/ShellDispatcherTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Cake/Test/Case/Console/ShellDispatcherTest.php b/lib/Cake/Test/Case/Console/ShellDispatcherTest.php index 3268d934d..f7705c47a 100644 --- a/lib/Cake/Test/Case/Console/ShellDispatcherTest.php +++ b/lib/Cake/Test/Case/Console/ShellDispatcherTest.php @@ -437,7 +437,7 @@ class ShellDispatcherTest extends CakeTestCase { */ public function testDispatchShellWithMain() { $Dispatcher = new TestShellDispatcher(); - $Mock = $this->getMock('Shell', array(), array(&$Dispatcher), 'MockWithMainShell'); + $Mock = $this->getMock('Shell', array(), array(), 'MockWithMainShell'); $Mock->expects($this->once())->method('initialize'); $Mock->expects($this->once())->method('loadTasks'); @@ -460,9 +460,9 @@ class ShellDispatcherTest extends CakeTestCase { */ public function testDispatchShellWithoutMain() { $Dispatcher = new TestShellDispatcher(); - $Shell = $this->getMock('Shell', array(), array(&$Dispatcher), 'MockWithoutMainShell'); + $Shell = $this->getMock('Shell', array(), array(), 'MockWithoutMainShell'); - $Shell = new MockWithoutMainShell($Dispatcher); + $Shell = new MockWithoutMainShell(); $this->mockObjects[] = $Shell; $Shell->expects($this->once())->method('initialize'); From 2598d176702ca8ab934ca377cec369577d4a5213 Mon Sep 17 00:00:00 2001 From: Rachman Chavik Date: Thu, 10 May 2012 19:03:12 +0700 Subject: [PATCH 21/29] check extension before passing to addTestFile() --- lib/Cake/Test/Case/Console/AllConsoleLibsTest.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/Cake/Test/Case/Console/AllConsoleLibsTest.php b/lib/Cake/Test/Case/Console/AllConsoleLibsTest.php index 4d13c5df1..5a6846b9e 100644 --- a/lib/Cake/Test/Case/Console/AllConsoleLibsTest.php +++ b/lib/Cake/Test/Case/Console/AllConsoleLibsTest.php @@ -38,7 +38,10 @@ class AllConsoleLibsTest extends PHPUnit_Framework_TestSuite { if (!$file->isFile() || strpos($file, 'All') === 0) { continue; } - $suite->addTestFile($file->getRealPath()); + $fileName = $file->getRealPath(); + if (substr($fileName, -4) === '.php') { + $suite->addTestFile($file->getRealPath()); + } } return $suite; } From 73c66e2df4904131d1d0d325b0f49eca08c0e5bd Mon Sep 17 00:00:00 2001 From: Rachman Chavik Date: Thu, 10 May 2012 19:47:01 +0700 Subject: [PATCH 22/29] fixing method signature in test models --- lib/Cake/Test/Case/Model/models.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Cake/Test/Case/Model/models.php b/lib/Cake/Test/Case/Model/models.php index 41138e155..fe0db80a2 100644 --- a/lib/Cake/Test/Case/Model/models.php +++ b/lib/Cake/Test/Case/Model/models.php @@ -4973,7 +4973,7 @@ class CustomArticle extends AppModel { * * @return void **/ - public function beforeValidate() { + public function beforeValidate($options = array()) { $this->data[$this->alias]['title'] = 'foo'; } From 15a74722b34be6b64f4c20b413c5fa84f3666132 Mon Sep 17 00:00:00 2001 From: Rachman Chavik Date: Fri, 11 May 2012 08:32:00 +0700 Subject: [PATCH 23/29] use LOGS constant --- lib/Cake/Test/Case/Utility/DebuggerTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Cake/Test/Case/Utility/DebuggerTest.php b/lib/Cake/Test/Case/Utility/DebuggerTest.php index 7285b6924..864b8f955 100644 --- a/lib/Cake/Test/Case/Utility/DebuggerTest.php +++ b/lib/Cake/Test/Case/Utility/DebuggerTest.php @@ -361,10 +361,10 @@ TEXT; $this->assertRegExp('/DebuggerTest\:\:testLog/i', $result); $this->assertRegExp("/'cool'/", $result); - unlink(TMP . 'logs' . DS . 'debug.log'); + unlink(LOGS . 'debug.log'); Debugger::log(array('whatever', 'here')); - $result = file_get_contents(TMP . 'logs' . DS . 'debug.log'); + $result = file_get_contents(LOGS . 'debug.log'); $this->assertRegExp('/DebuggerTest\:\:testLog/i', $result); $this->assertRegExp('/\[main\]/', $result); $this->assertRegExp('/array/', $result); From 8103eae9f114d428f7c4d1cb038c53baad5b0abc Mon Sep 17 00:00:00 2001 From: mark_story Date: Sat, 12 May 2012 20:18:10 -0400 Subject: [PATCH 24/29] Make input prompt clearer in extract task. Make the prompt display the paths that will be scanned. Hopefully this makes the shell a bit easier to use and understand. Also make the shell not accept 0 paths as a correct answer. Fixes #2877 --- lib/Cake/Console/Command/Task/ExtractTask.php | 51 ++++++++++++------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/lib/Cake/Console/Command/Task/ExtractTask.php b/lib/Cake/Console/Command/Task/ExtractTask.php index e791a44b5..abebb840a 100644 --- a/lib/Cake/Console/Command/Task/ExtractTask.php +++ b/lib/Cake/Console/Command/Task/ExtractTask.php @@ -104,6 +104,38 @@ class ExtractTask extends AppShell { */ protected $_validationDomain = 'default'; +/** + * Method to interact with the User and get path selections. + * + * @return void + */ + protected function _getPaths() { + $defaultPath = APP; + while (true) { + $currentPaths = count($this->_paths) > 0 ? $this->_paths : array('None'); + $message = __d( + 'cake_console', + "Current paths: %s\nWhat is the path you would like to extract?\n[Q]uit [D]one", + implode(', ', $currentPaths) + ); + $response = $this->in($message, null, $defaultPath); + if (strtoupper($response) === 'Q') { + $this->out(__d('cake_console', 'Extract Aborted')); + return $this->_stop(); + } elseif (strtoupper($response) === 'D' && count($this->_paths)) { + $this->out(); + return; + } elseif (strtoupper($response) === 'D') { + $this->err(__d('cake_console', 'No directories selected. Please choose a directory.')); + } elseif (is_dir($response)) { + $this->_paths[] = $response; + $defaultPath = 'D'; + } else { + $this->err(__d('cake_console', 'The directory path you supplied was not found. Please try again.')); + } + $this->out(); + } + } /** * Execution method always used for tasks * @@ -126,24 +158,7 @@ class ExtractTask extends AppShell { $this->_paths = array(CakePlugin::path($plugin)); $this->params['plugin'] = $plugin; } else { - $defaultPath = APP; - $message = __d('cake_console', "What is the path you would like to extract?\n[Q]uit [D]one"); - while (true) { - $response = $this->in($message, null, $defaultPath); - if (strtoupper($response) === 'Q') { - $this->out(__d('cake_console', 'Extract Aborted')); - $this->_stop(); - } elseif (strtoupper($response) === 'D') { - $this->out(); - break; - } elseif (is_dir($response)) { - $this->_paths[] = $response; - $defaultPath = 'D'; - } else { - $this->err(__d('cake_console', 'The directory path you supplied was not found. Please try again.')); - } - $this->out(); - } + $this->_getPaths(); } if (!empty($this->params['exclude-plugins']) && $this->_isExtractingApp()) { From cbd6cafed7936c369e670ed3f7b0e02bbc4d596b Mon Sep 17 00:00:00 2001 From: mark_story Date: Mon, 14 May 2012 21:18:09 -0400 Subject: [PATCH 25/29] The event manager should be re-used. View::__construct() gets the controller's event manager, only to override it the first time getEventManager() is called. Don't overwrite the Controller's event manager as it allows plugins to more easily hook into the view process. --- lib/Cake/View/View.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/Cake/View/View.php b/lib/Cake/View/View.php index 1078211ae..f2e40eac5 100644 --- a/lib/Cake/View/View.php +++ b/lib/Cake/View/View.php @@ -332,8 +332,10 @@ class View extends Object { * @return CakeEventManager */ public function getEventManager() { - if (empty($this->_eventManager) || !$this->_eventManagerConfigured) { + if (empty($this->_eventManager)) { $this->_eventManager = new CakeEventManager(); + } + if (!$this->_eventManagerConfigured) { $this->_eventManager->attach($this->Helpers); $this->_eventManagerConfigured = true; } From 0faaedfff5c542909b58fbe55c78b83c29f4596b Mon Sep 17 00:00:00 2001 From: mark_story Date: Mon, 14 May 2012 21:32:31 -0400 Subject: [PATCH 26/29] Split up tests. Fix API usage in tests. --- .../Test/Case/View/Helper/CacheHelperTest.php | 60 ++++++++----------- 1 file changed, 24 insertions(+), 36 deletions(-) diff --git a/lib/Cake/Test/Case/View/Helper/CacheHelperTest.php b/lib/Cake/Test/Case/View/Helper/CacheHelperTest.php index cce3ca21b..f6e04b6a4 100644 --- a/lib/Cake/Test/Case/View/Helper/CacheHelperTest.php +++ b/lib/Cake/Test/Case/View/Helper/CacheHelperTest.php @@ -291,15 +291,14 @@ class CacheHelperTest extends CakeTestCase { */ public function testCacheViewVars() { $this->Controller->cache_parsing(); - $this->Controller->params = array( + $this->Controller->request->addParams(array( 'controller' => 'cache_test', 'action' => 'cache_parsing', 'pass' => array(), 'named' => array() - ); + )); + $this->Controller->request->here = '/cacheTest/cache_parsing'; $this->Controller->cacheAction = 21600; - $this->Controller->here = '/cacheTest/cache_parsing'; - $this->Controller->action = 'cache_parsing'; $View = new View($this->Controller); $result = $View->render('index'); @@ -323,21 +322,20 @@ class CacheHelperTest extends CakeTestCase { * @return void */ public function testCacheCallbacks() { - $this->Controller->cache_parsing(); - $this->Controller->params = array( + $this->Controller->request->addParams(array( 'controller' => 'cache_test', 'action' => 'cache_parsing', 'pass' => array(), 'named' => array() - ); + )); $this->Controller->cacheAction = array( 'cache_parsing' => array( 'duration' => 21600, 'callbacks' => true ) ); - $this->Controller->here = '/cacheTest/cache_parsing'; - $this->Controller->action = 'cache_parsing'; + $this->Controller->request->here = '/cacheTest/cache_parsing'; + $this->Controller->cache_parsing(); $View = new View($this->Controller); $result = $View->render('index'); @@ -358,18 +356,18 @@ class CacheHelperTest extends CakeTestCase { * @return void */ public function testCacheActionArray() { - $this->Controller->cache_parsing(); $this->Controller->request->addParams(array( 'controller' => 'cache_test', 'action' => 'cache_parsing', 'pass' => array(), 'named' => array() )); + $this->Controller->request->here = '/cache_test/cache_parsing'; $this->Controller->cacheAction = array( 'cache_parsing' => 21600 ); - $this->Controller->request->here = '/cache_test/cache_parsing'; - $this->Controller->action = 'cache_parsing'; + + $this->Controller->cache_parsing(); $View = new View($this->Controller); $result = $View->render('index'); @@ -380,13 +378,25 @@ class CacheHelperTest extends CakeTestCase { $filename = CACHE . 'views' . DS . 'cache_test_cache_parsing.php'; $this->assertTrue(file_exists($filename)); @unlink($filename); + } - $this->Controller->cache_parsing(); +/** + * Test that cacheAction works with camelcased controller names. + * + * @return void + */ + public function testCacheActionArrayCamelCase() { + $this->Controller->request->addParams(array( + 'controller' => 'cache_test', + 'action' => 'cache_parsing', + 'pass' => array(), + 'named' => array() + )); $this->Controller->cacheAction = array( 'cache_parsing' => 21600 ); $this->Controller->request->here = '/cacheTest/cache_parsing'; - $this->Controller->action = 'cache_parsing'; + $this->Controller->cache_parsing(); $View = new View($this->Controller); $result = $View->render('index'); @@ -397,28 +407,6 @@ class CacheHelperTest extends CakeTestCase { $filename = CACHE . 'views' . DS . 'cachetest_cache_parsing.php'; $this->assertTrue(file_exists($filename)); @unlink($filename); - - $this->Controller->cache_parsing(); - $this->Controller->request->addParams(array( - 'controller' => 'cache_test', - 'action' => 'cache_parsing', - 'pass' => array(), - 'named' => array() - )); - $this->Controller->cacheAction = array( - 'some_other_action' => 21600 - ); - $this->Controller->request->here = '/cacheTest/cache_parsing'; - $this->Controller->action = 'cache_parsing'; - - $View = new View($this->Controller); - $result = $View->render('index'); - - $this->assertNotRegExp('/cake:nocache/', $result); - $this->assertNotRegExp('/php echo/', $result); - - $filename = CACHE . 'views' . DS . 'cachetest_cache_parsing.php'; - $this->assertFalse(file_exists($filename)); } /** From c6258fa68c09e785bedbc6e517b61c869f25f727 Mon Sep 17 00:00:00 2001 From: mark_story Date: Wed, 16 May 2012 21:07:45 -0400 Subject: [PATCH 27/29] HTML escape context variables. When creating HTML or js errors the context should be escaped, as it is very possible that the context vars will contain HTML. Clean up some internals in Debugger::outputError(). There were a few duplicate data structures, and $$ variables. Fixes #2884 --- lib/Cake/Test/Case/Utility/DebuggerTest.php | 9 +++--- lib/Cake/Utility/Debugger.php | 33 +++++++++++---------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/lib/Cake/Test/Case/Utility/DebuggerTest.php b/lib/Cake/Test/Case/Utility/DebuggerTest.php index 864b8f955..f45f19cdc 100644 --- a/lib/Cake/Test/Case/Utility/DebuggerTest.php +++ b/lib/Cake/Test/Case/Utility/DebuggerTest.php @@ -140,8 +140,8 @@ class DebuggerTest extends CakeTestCase { 'a' => array( 'href' => "javascript:void(0);", 'onclick' => "preg:/document\.getElementById\('cakeErr[a-z0-9]+\-trace'\)\.style\.display = " . - "\(document\.getElementById\('cakeErr[a-z0-9]+\-trace'\)\.style\.display == 'none'" . - " \? '' \: 'none'\);/" + "\(document\.getElementById\('cakeErr[a-z0-9]+\-trace'\)\.style\.display == 'none'" . + " \? '' \: 'none'\);/" ), 'b' => array(), 'Notice', '/b', ' (8)', )); @@ -149,6 +149,7 @@ class DebuggerTest extends CakeTestCase { $this->assertRegExp('/Undefined variable:\s+buzz/', $result[1]); $this->assertRegExp('/]+>Code/', $result[1]); $this->assertRegExp('/]+>Context/', $result[2]); + $this->assertContains('$wrong = ''', $result[3], 'Context should be HTML escaped.'); } /** @@ -162,14 +163,14 @@ class DebuggerTest extends CakeTestCase { Debugger::output('js', array( 'traceLine' => '{:reference} - {:path}, line {:line}' + '&line={:line}">{:path}, line {:line}' )); $result = Debugger::trace(); $this->assertRegExp('/' . preg_quote('txmt://open?url=file://', '/') . '(\/|[A-Z]:\\\\)' . '/', $result); Debugger::output('xml', array( 'error' => '{:code}{:file}{:line}' . - '{:description}', + '{:description}', 'context' => "{:context}", 'trace' => "{:trace}", )); diff --git a/lib/Cake/Utility/Debugger.php b/lib/Cake/Utility/Debugger.php index 9ed57d1d9..2a9fc9c23 100644 --- a/lib/Cake/Utility/Debugger.php +++ b/lib/Cake/Utility/Debugger.php @@ -63,11 +63,13 @@ class Debugger { 'trace' => '
{:trace}
', 'code' => '', 'context' => '', - 'links' => array() + 'links' => array(), + 'escapeContext' => true, ), 'html' => array( 'trace' => '
Trace 

{:trace}

', - 'context' => '
Context 

{:context}

' + 'context' => '
Context 

{:context}

', + 'escapeContext' => true, ), 'txt' => array( 'error' => "{:error}: {:code} :: {:description} on line {:line} of {:path}\n{:info}", @@ -716,7 +718,7 @@ class Debugger { $info = ''; foreach ((array)$data['context'] as $var => $value) { - $context[] = "\${$var}\t=\t" . $this->exportVar($value, 1); + $context[] = "\${$var} = " . $this->exportVar($value, 1); } switch ($this->_outputFormat) { @@ -731,30 +733,29 @@ class Debugger { $data['trace'] = $trace; $data['id'] = 'cakeErr' . uniqid(); $tpl = array_merge($this->_templates['base'], $this->_templates[$this->_outputFormat]); - $insert = array('context' => join("\n", $context)) + $data; - - $detect = array('context'); if (isset($tpl['links'])) { foreach ($tpl['links'] as $key => $val) { - if (in_array($key, $detect) && empty($insert[$key])) { - continue; - } - $links[$key] = String::insert($val, $insert, $insertOpts); + $links[$key] = String::insert($val, $data, $insertOpts); } } - foreach (array('code', 'context', 'trace') as $key) { - if (empty($$key) || !isset($tpl[$key])) { + if (!empty($tpl['escapeContext'])) { + $context = h($context); + } + + $infoData = compact('code', 'context', 'trace'); + foreach ($infoData as $key => $value) { + if (empty($value) || !isset($tpl[$key])) { continue; } - if (is_array($$key)) { - $$key = join("\n", $$key); + if (is_array($value)) { + $value = join("\n", $value); } - $info .= String::insert($tpl[$key], compact($key) + $insert, $insertOpts); + $info .= String::insert($tpl[$key], array($key => $value) + $data, $insertOpts); } $links = join(' ', $links); - unset($data['context']); + if (isset($tpl['callback']) && is_callable($tpl['callback'])) { return call_user_func($tpl['callback'], $data, compact('links', 'info')); } From 7107cd663142fe79b98fde39529516344e729685 Mon Sep 17 00:00:00 2001 From: mark_story Date: Sat, 19 May 2012 11:35:55 -0400 Subject: [PATCH 28/29] Fix fatal error when checking for PHPUnit. Doubly including PHPUnit/Autoload.php causes fatal errors. Having access to one of the PHPUnit classes means phpunit exists as well. Fixes #2890 --- lib/Cake/Console/Shell.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/Cake/Console/Shell.php b/lib/Cake/Console/Shell.php index 788bb8b39..40c8c04ef 100644 --- a/lib/Cake/Console/Shell.php +++ b/lib/Cake/Console/Shell.php @@ -680,12 +680,14 @@ class Shell extends Object { * @return boolean Success */ protected function _checkUnitTest() { - if (App::import('Vendor', 'phpunit', array('file' => 'PHPUnit' . DS . 'Autoload.php'))) { - return true; - } - if (@include 'PHPUnit' . DS . 'Autoload.php') { + if (class_exists('PHPUnit_Framework_TestCase')) { + return true; + } elseif (@include 'PHPUnit' . DS . 'Autoload.php') { + return true; + } elseif (App::import('Vendor', 'phpunit', array('file' => 'PHPUnit' . DS . 'Autoload.php'))) { return true; } + $prompt = __d('cake_console', 'PHPUnit is not installed. Do you want to bake unit test files anyway?'); $unitTest = $this->in($prompt, array('y', 'n'), 'y'); $result = strtolower($unitTest) == 'y' || strtolower($unitTest) == 'yes'; From 7dbd6bc3a2bb17501fb411c6a0605e3908a9d0c5 Mon Sep 17 00:00:00 2001 From: Ceeram Date: Tue, 15 May 2012 16:57:42 +0200 Subject: [PATCH 29/29] make sure model data is moved to alias, even when first key in data is assoc model add extra tests for saveAll and validate first ensure db values dont get reset to default values --- lib/Cake/Model/Model.php | 38 +++++++---- lib/Cake/Test/Case/Model/ModelReadTest.php | 2 +- .../Test/Case/Model/ModelValidationTest.php | 67 +++++++++++++++++-- lib/Cake/Test/Case/Model/ModelWriteTest.php | 10 +-- lib/Cake/Test/Case/Model/models.php | 7 +- 5 files changed, 98 insertions(+), 26 deletions(-) diff --git a/lib/Cake/Model/Model.php b/lib/Cake/Model/Model.php index 8a2a9b223..77b740350 100644 --- a/lib/Cake/Model/Model.php +++ b/lib/Cake/Model/Model.php @@ -1129,9 +1129,7 @@ class Model extends Object implements CakeEventListener { if (is_array($one)) { $data = $one; if (empty($one[$this->alias])) { - if ($this->getAssociated(key($one)) === null) { - $data = array($this->alias => $one); - } + $data = $this->_setAliasData($one); } } else { $data = array($this->alias => array($one => $two)); @@ -1160,6 +1158,24 @@ class Model extends Object implements CakeEventListener { return $data; } +/** + * Move values to alias + * + * @param array $data + * @return array + */ + protected function _setAliasData($data) { + $models = array_keys($this->getAssociated()); + $schema = array_keys($this->schema()); + foreach ($data as $field => $value) { + if (in_array($field, $schema) || !in_array($field, $models)) { + $data[$this->alias][$field] = $value; + unset($data[$field]); + } + } + return $data; + } + /** * Normalize Xml::toArray() to use in Model::save() * @@ -2118,7 +2134,8 @@ class Model extends Object implements CakeEventListener { if ($options['deep']) { $validates = $this->validateAssociated($record, $options); } else { - $validates = $this->create($record) && $this->validates($options); + $this->create(null); + $validates = $this->set($record) && $this->validates($options); $data[$key] = $this->data; } if ($validates === false || (is_array($validates) && in_array(false, $validates, true))) { @@ -2318,21 +2335,14 @@ class Model extends Object implements CakeEventListener { public function validateAssociated(&$data, $options = array()) { $options = array_merge(array('atomic' => true, 'deep' => false), $options); $this->validationErrors = $validationErrors = $return = array(); - if (!($this->create($data) && $this->validates($options))) { + $this->create(null); + if (!($this->set($data) && $this->validates($options))) { $validationErrors[$this->alias] = $this->validationErrors; $return[$this->alias] = false; } else { $return[$this->alias] = true; } - - if (empty($options['deep'])) { - $data = $this->data; - } else { - $modelData = $this->data; - $recordData = $modelData[$this->alias]; - unset($modelData[$this->alias]); - $data = $modelData + array_merge($data, $recordData); - } + $data = $this->data; $associations = $this->getAssociated(); foreach ($data as $association => &$values) { diff --git a/lib/Cake/Test/Case/Model/ModelReadTest.php b/lib/Cake/Test/Case/Model/ModelReadTest.php index 88b1b1711..f0e4bc4fb 100644 --- a/lib/Cake/Test/Case/Model/ModelReadTest.php +++ b/lib/Cake/Test/Case/Model/ModelReadTest.php @@ -7868,7 +7868,7 @@ class ModelReadTest extends BaseModelTest { $Article = new CustomArticle(); $data = array('user_id' => 3, 'title' => 'Fourth Article', 'body' => 'Article Body, unpublished', 'published' => 'N'); $Article->create($data); - $Article->save(); + $Article->save(null, false); $this->assertEquals(4, $Article->id); $result = $Article->find('published'); diff --git a/lib/Cake/Test/Case/Model/ModelValidationTest.php b/lib/Cake/Test/Case/Model/ModelValidationTest.php index 6b6accf96..eb90ebbfa 100644 --- a/lib/Cake/Test/Case/Model/ModelValidationTest.php +++ b/lib/Cake/Test/Case/Model/ModelValidationTest.php @@ -1016,22 +1016,34 @@ class ModelValidationTest extends BaseModelTest { ); $result = $model->saveAll($data, array('validate' => 'first')); $this->assertTrue($result); + $this->assertFalse($model->findMethods['unPublished'], 'beforeValidate was run twice'); - $title = $model->field('title', array('body' => 'foo0')); + $model->findMethods['unPublished'] = true; + $data = array( + 'CustomArticle' => array( + 'body' => 'foo1' + ) + ); + $result = $model->saveAll($data, array('validate' => 'first', 'deep' => true)); + $this->assertTrue($result); + $title = $model->field('title', array('body' => 'foo1')); $this->assertEquals('foo', $title); + $this->assertFalse($model->findMethods['unPublished'], 'beforeValidate was run twice'); $data = array( - array('body' => 'foo1'), array('body' => 'foo2'), - array('body' => 'foo3') + array('body' => 'foo3'), + array('body' => 'foo4') ); $result = $model->saveAll($data, array('validate' => 'first')); $this->assertTrue($result); + $result = $model->saveAll($data, array('validate' => 'first', 'deep' => true)); + $this->assertTrue($result); - $this->assertEquals('foo', $model->field('title', array('body' => 'foo1'))); $this->assertEquals('foo', $model->field('title', array('body' => 'foo2'))); $this->assertEquals('foo', $model->field('title', array('body' => 'foo3'))); + $this->assertEquals('foo', $model->field('title', array('body' => 'foo4'))); } /** @@ -1040,7 +1052,7 @@ class ModelValidationTest extends BaseModelTest { * * @return void */ - public function testValidateAssociatedWithBeforeValidate() { + public function testValidateFirstAssociatedWithBeforeValidate() { $this->loadFixtures('Article', 'User'); $model = new CustomArticle(); $model->validate = array( @@ -1070,4 +1082,49 @@ class ModelValidationTest extends BaseModelTest { $this->assertEquals('foo', $model->field('title', array('body' => 'foo3'))); } +/** + * testValidateFirstWithDefaults method + * + * return @void + */ + public function testFirstWithDefaults() { + $this->loadFixtures('Article', 'Tag', 'Comment', 'User', 'ArticlesTag'); + $TestModel = new Article(); + + $result = $TestModel->find('first', array( + 'conditions' => array('Article.id' => 1) + )); + $expected = array( + 'Article' => array( + 'id' => 1, + 'user_id' => 1, + 'title' => 'First Article', + 'body' => 'First Article Body', + 'published' => 'Y', + 'created' => '2007-03-18 10:39:23' + ), + ); + unset($result['Article']['updated']); + $this->assertEquals($expected['Article'], $result['Article']); + + $data = array( + 'Article' => array( + 'id' => 1, + 'title' => 'First Article (modified)' + ), + 'Comment' => array( + array('comment' => 'Article comment', 'user_id' => 1) + ) + ); + $result = $TestModel->saveAll($data, array('validate' => 'first')); + $this->assertTrue($result); + + $result = $TestModel->find('first', array( + 'conditions' => array('Article.id' => 1) + )); + $expected['Article']['title'] = 'First Article (modified)'; + unset($result['Article']['updated']); + $this->assertEquals($expected['Article'], $result['Article']); + } + } \ No newline at end of file diff --git a/lib/Cake/Test/Case/Model/ModelWriteTest.php b/lib/Cake/Test/Case/Model/ModelWriteTest.php index f72b2a8bc..0c8b4e054 100644 --- a/lib/Cake/Test/Case/Model/ModelWriteTest.php +++ b/lib/Cake/Test/Case/Model/ModelWriteTest.php @@ -4277,7 +4277,7 @@ class ModelWriteTest extends BaseModelTest { 'author_id' => '3', 'title' => 'Just update the title', 'body' => 'Second Post Body', - 'published' => 'N', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23' )), array( @@ -4366,7 +4366,7 @@ class ModelWriteTest extends BaseModelTest { 'author_id' => '3', 'title' => 'Just update the title', 'body' => 'Second Post Body', - 'published' => 'N', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23' ) ), @@ -4734,7 +4734,7 @@ class ModelWriteTest extends BaseModelTest { 'password' => '5f4dcc3b5aa765d61d8327deb882cf90' ))); - $result = $TestModel->find('all'); + $result = $TestModel->find('all', array('order' => array('Post.id ' => 'ASC'))); $expected = array( 'Post' => array( 'id' => '4', @@ -5631,7 +5631,7 @@ class ModelWriteTest extends BaseModelTest { 'author_id' => '3', 'title' => 'Just update the title', 'body' => 'Second Post Body', - 'published' => 'N', + 'published' => 'Y', 'created' => '2007-03-18 10:41:23' ) ), @@ -5727,7 +5727,7 @@ class ModelWriteTest extends BaseModelTest { 'author_id' => '3', 'title' => 'Just update the title', 'body' => 'Second Post Body', - 'published' => 'N', + 'published' => 'Y', )), array( 'Post' => array( diff --git a/lib/Cake/Test/Case/Model/models.php b/lib/Cake/Test/Case/Model/models.php index fe0db80a2..e9e79a1b6 100644 --- a/lib/Cake/Test/Case/Model/models.php +++ b/lib/Cake/Test/Case/Model/models.php @@ -3119,7 +3119,7 @@ class TranslatedItem2 extends CakeTestModel { /** * translateModel property * - * @var string + * @var string */ public $translateModel = 'TranslateWithPrefix'; @@ -4975,6 +4975,11 @@ class CustomArticle extends AppModel { **/ public function beforeValidate($options = array()) { $this->data[$this->alias]['title'] = 'foo'; + if ($this->findMethods['unPublished'] === true) { + $this->findMethods['unPublished'] = false; + } else { + $this->findMethods['unPublished'] = 'true again'; + } } }