From 050e368bd091cf66cca7e2d191fc2dfb184c8d6f Mon Sep 17 00:00:00 2001 From: Hunter Perrin Date: Wed, 1 Oct 2014 11:22:44 -0700 Subject: [PATCH 01/13] Improvements to Hash::expand and Hash::merge. Because of the recursion in these functions, processing very large arrays would take a very long time. I rewrote the functions to eliminate any unnecessary recursion and function calls. Large arrays are now processed much faster. --- lib/Cake/Utility/Hash.php | 51 ++++++++++++++++++++++++++++++--------- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/lib/Cake/Utility/Hash.php b/lib/Cake/Utility/Hash.php index 6cb5c05d4..3d659a54f 100644 --- a/lib/Cake/Utility/Hash.php +++ b/lib/Cake/Utility/Hash.php @@ -622,6 +622,9 @@ class Hash { */ public static function expand($data, $separator = '.') { $result = array(); + + $stack = array(); + foreach ($data as $flat => $value) { $keys = explode($separator, $flat); $keys = array_reverse($keys); @@ -634,7 +637,24 @@ class Hash { $k => $child ); } - $result = self::merge($result, $child); + + $stack[] = array($child, &$result); + + while (!empty($stack)) { + foreach ($stack as $curKey => &$curMerge) { + foreach ($curMerge[0] as $key => &$val) { + if (!empty($curMerge[1][$key]) && (array)$curMerge[1][$key]===$curMerge[1][$key] && (array)$val===$val) { + $stack[] = array(&$val, &$curMerge[1][$key]); + } elseif ((int)$key===$key && isset($curMerge[1][$key])) { + $curMerge[1][] = $val; + } else { + $curMerge[1][$key] = $val; + } + } + unset($stack[$curKey]); + } + unset($curMerge); + } } return $result; } @@ -654,19 +674,28 @@ class Hash { * @link http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::merge */ public static function merge(array $data, $merge) { - $args = func_get_args(); - $return = current($args); + $args = array_slice(func_get_args(), 1); + $return = $data; - while (($arg = next($args)) !== false) { - foreach ((array)$arg as $key => $val) { - if (!empty($return[$key]) && is_array($return[$key]) && is_array($val)) { - $return[$key] = self::merge($return[$key], $val); - } elseif (is_int($key) && isset($return[$key])) { - $return[] = $val; - } else { - $return[$key] = $val; + foreach ($args as &$curArg) { + $stack[] = array((array)$curArg, &$return); + } + unset($curArg); + + while (!empty($stack)) { + foreach ($stack as $curKey => &$curMerge) { + foreach ($curMerge[0] as $key => &$val) { + if (!empty($curMerge[1][$key]) && (array)$curMerge[1][$key]===$curMerge[1][$key] && (array)$val===$val) { + $stack[] = array(&$val, &$curMerge[1][$key]); + } elseif ((int)$key===$key && isset($curMerge[1][$key])) { + $curMerge[1][] = $val; + } else { + $curMerge[1][$key] = $val; + } } + unset($stack[$curKey]); } + unset($curMerge); } return $return; } From be8c591dd868a6f923507e261605bfc2249be281 Mon Sep 17 00:00:00 2001 From: Hunter Perrin Date: Wed, 1 Oct 2014 12:40:22 -0700 Subject: [PATCH 02/13] Fixed coding standard violations. --- lib/Cake/Utility/Hash.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/Cake/Utility/Hash.php b/lib/Cake/Utility/Hash.php index 3d659a54f..73f4a4c69 100644 --- a/lib/Cake/Utility/Hash.php +++ b/lib/Cake/Utility/Hash.php @@ -643,11 +643,11 @@ class Hash { while (!empty($stack)) { foreach ($stack as $curKey => &$curMerge) { foreach ($curMerge[0] as $key => &$val) { - if (!empty($curMerge[1][$key]) && (array)$curMerge[1][$key]===$curMerge[1][$key] && (array)$val===$val) { + if (!empty($curMerge[1][$key]) && (array)$curMerge[1][$key] === $curMerge[1][$key] && (array)$val === $val) { $stack[] = array(&$val, &$curMerge[1][$key]); - } elseif ((int)$key===$key && isset($curMerge[1][$key])) { + } elseif ((int)$key === $key && isset($curMerge[1][$key])) { $curMerge[1][] = $val; - } else { + } else { $curMerge[1][$key] = $val; } } @@ -685,11 +685,11 @@ class Hash { while (!empty($stack)) { foreach ($stack as $curKey => &$curMerge) { foreach ($curMerge[0] as $key => &$val) { - if (!empty($curMerge[1][$key]) && (array)$curMerge[1][$key]===$curMerge[1][$key] && (array)$val===$val) { + if (!empty($curMerge[1][$key]) && (array)$curMerge[1][$key] === $curMerge[1][$key] && (array)$val === $val) { $stack[] = array(&$val, &$curMerge[1][$key]); - } elseif ((int)$key===$key && isset($curMerge[1][$key])) { + } elseif ((int)$key === $key && isset($curMerge[1][$key])) { $curMerge[1][] = $val; - } else { + } else { $curMerge[1][$key] = $val; } } From 8507ef83f175bbf3f6532dc68b6051f0d41acd82 Mon Sep 17 00:00:00 2001 From: Ceeram Date: Thu, 2 Oct 2014 16:07:10 +0200 Subject: [PATCH 03/13] add test to prove requesthandler works correct with Angular wonky accept headers --- .../Component/RequestHandlerComponentTest.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/Cake/Test/Case/Controller/Component/RequestHandlerComponentTest.php b/lib/Cake/Test/Case/Controller/Component/RequestHandlerComponentTest.php index 3f330b60a..340ac3894 100644 --- a/lib/Cake/Test/Case/Controller/Component/RequestHandlerComponentTest.php +++ b/lib/Cake/Test/Case/Controller/Component/RequestHandlerComponentTest.php @@ -197,6 +197,20 @@ class RequestHandlerComponentTest extends CakeTestCase { $this->assertEquals('json', $this->RequestHandler->ext); } +/** + * Test that RequestHandler sets $this->ext when Angular sends its wonky-ish headers. + * + * @return void + */ + public function testInitializeContentTypeWithAngularAccept() { + $_SERVER['HTTP_ACCEPT'] = 'application/json, text/plain, * / *'; + $this->assertNull($this->RequestHandler->ext); + Router::parseExtensions('json'); + + $this->RequestHandler->initialize($this->Controller); + $this->assertEquals('json', $this->RequestHandler->ext); + } + /** * Test that RequestHandler sets $this->ext when jQuery sends its wonky-ish headers * and the application is configured to handle multiple extensions From fcffe3961f4594202dbd0816768fe900584ed8e2 Mon Sep 17 00:00:00 2001 From: Ceeram Date: Thu, 2 Oct 2014 22:12:35 +0200 Subject: [PATCH 04/13] Revert "add test to prove requesthandler works correct with Angular wonky accept headers" This reverts commit 8507ef83f175bbf3f6532dc68b6051f0d41acd82. Incorrect header was used for this test, Cake cannot safely determine correct header. To get CakePHP to respond with json, you can modify the angular common headers. --- .../Component/RequestHandlerComponentTest.php | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/lib/Cake/Test/Case/Controller/Component/RequestHandlerComponentTest.php b/lib/Cake/Test/Case/Controller/Component/RequestHandlerComponentTest.php index 340ac3894..3f330b60a 100644 --- a/lib/Cake/Test/Case/Controller/Component/RequestHandlerComponentTest.php +++ b/lib/Cake/Test/Case/Controller/Component/RequestHandlerComponentTest.php @@ -197,20 +197,6 @@ class RequestHandlerComponentTest extends CakeTestCase { $this->assertEquals('json', $this->RequestHandler->ext); } -/** - * Test that RequestHandler sets $this->ext when Angular sends its wonky-ish headers. - * - * @return void - */ - public function testInitializeContentTypeWithAngularAccept() { - $_SERVER['HTTP_ACCEPT'] = 'application/json, text/plain, * / *'; - $this->assertNull($this->RequestHandler->ext); - Router::parseExtensions('json'); - - $this->RequestHandler->initialize($this->Controller); - $this->assertEquals('json', $this->RequestHandler->ext); - } - /** * Test that RequestHandler sets $this->ext when jQuery sends its wonky-ish headers * and the application is configured to handle multiple extensions From ce98a1ff5738af6db0f42a3647aaaaa5c3773d24 Mon Sep 17 00:00:00 2001 From: mark_story Date: Sat, 4 Oct 2014 22:59:03 -0400 Subject: [PATCH 05/13] Update version number to 2.5.5 --- lib/Cake/VERSION.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Cake/VERSION.txt b/lib/Cake/VERSION.txt index 78ab46f12..59df1c4a8 100644 --- a/lib/Cake/VERSION.txt +++ b/lib/Cake/VERSION.txt @@ -17,4 +17,4 @@ // @license http://www.opensource.org/licenses/mit-license.php MIT License // +--------------------------------------------------------------------------------------------+ // //////////////////////////////////////////////////////////////////////////////////////////////////// -2.5.4 +2.5.5 From e4c0dee6ee62beab596644d801443f786935d8dc Mon Sep 17 00:00:00 2001 From: suzuki86 Date: Wed, 8 Oct 2014 23:37:38 +0900 Subject: [PATCH 06/13] Fix small typo --- 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 bb4a44ce1..3a698101b 100644 --- a/lib/Cake/Model/Model.php +++ b/lib/Cake/Model/Model.php @@ -757,7 +757,7 @@ class Model extends Object implements CakeEventListener { /** * Returns a list of all events that will fire in the model during it's lifecycle. - * You can override this function to add you own listener callbacks + * You can override this function to add your own listener callbacks * * @return array */ From e16bef186836c70206d5ebca29a209a42d6524be Mon Sep 17 00:00:00 2001 From: Bryan Crowe Date: Wed, 8 Oct 2014 15:06:30 -0400 Subject: [PATCH 07/13] Fix missing 'r' from your --- lib/Cake/Controller/Controller.php | 2 +- lib/Cake/Core/App.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Cake/Controller/Controller.php b/lib/Cake/Controller/Controller.php index 6727434ae..c92467b05 100644 --- a/lib/Cake/Controller/Controller.php +++ b/lib/Cake/Controller/Controller.php @@ -611,7 +611,7 @@ class Controller extends Object implements CakeEventListener { /** * Returns a list of all events that will fire in the controller during its lifecycle. - * You can override this function to add you own listener callbacks + * You can override this function to add your own listener callbacks * * @return array */ diff --git a/lib/Cake/Core/App.php b/lib/Cake/Core/App.php index d019f7f6d..a8bc3f117 100644 --- a/lib/Cake/Core/App.php +++ b/lib/Cake/Core/App.php @@ -33,7 +33,7 @@ App::uses('CakePlugin', 'Core'); * CakePHP is organized around the idea of packages, each class belongs to a package or folder where other * classes reside. You can configure each package location in your application using `App::build('APackage/SubPackage', $paths)` * to inform the framework where should each class be loaded. Almost every class in the CakePHP framework can be swapped - * by your own compatible implementation. If you wish to use you own class instead of the classes the framework provides, + * by your own compatible implementation. If you wish to use your own class instead of the classes the framework provides, * just add the class to your libs folder mocking the directory location of where CakePHP expects to find it. * * For instance if you'd like to use your own HttpSocket class, put it under From e515a02e1677500bf62dbb2ff606d6b13429a887 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20W=C3=BCrth?= Date: Thu, 9 Oct 2014 14:13:34 +0200 Subject: [PATCH 08/13] Added code consistency badge from phpcs --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index bb2961b1c..c80b7e76f 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ # CakePHP -[![Bake Status](https://secure.travis-ci.org/cakephp/cakephp.png?branch=master)](http://travis-ci.org/cakephp/cakephp) [![Latest Stable Version](https://poser.pugx.org/cakephp/cakephp/v/stable.svg)](https://packagist.org/packages/cakephp/cakephp) [![License](https://poser.pugx.org/cakephp/cakephp/license.svg)](https://packagist.org/packages/cakephp/cakephp) +[![Bake Status](https://secure.travis-ci.org/cakephp/cakephp.png?branch=master)](http://travis-ci.org/cakephp/cakephp) +[![Code consistency](http://squizlabs.github.io/PHP_CodeSniffer/analysis/cakephp/cakephp/grade.svg)](squizlabs.github.io/PHP_CodeSniffer/analysis/cakephp/cakephp/) [![CakePHP](http://cakephp.org/img/cake-logo.png)](http://www.cakephp.org) From 051c005b43159bb5b51eed453bae4cba2b086137 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc=20W=C3=BCrth?= Date: Thu, 9 Oct 2014 14:25:40 +0200 Subject: [PATCH 09/13] Added HTTP to URL --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c80b7e76f..d427d607d 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Latest Stable Version](https://poser.pugx.org/cakephp/cakephp/v/stable.svg)](https://packagist.org/packages/cakephp/cakephp) [![License](https://poser.pugx.org/cakephp/cakephp/license.svg)](https://packagist.org/packages/cakephp/cakephp) [![Bake Status](https://secure.travis-ci.org/cakephp/cakephp.png?branch=master)](http://travis-ci.org/cakephp/cakephp) -[![Code consistency](http://squizlabs.github.io/PHP_CodeSniffer/analysis/cakephp/cakephp/grade.svg)](squizlabs.github.io/PHP_CodeSniffer/analysis/cakephp/cakephp/) +[![Code consistency](http://squizlabs.github.io/PHP_CodeSniffer/analysis/cakephp/cakephp/grade.svg)](http://squizlabs.github.io/PHP_CodeSniffer/analysis/cakephp/cakephp/) [![CakePHP](http://cakephp.org/img/cake-logo.png)](http://www.cakephp.org) From 94e718372a4a01089a487318e85b07174287fd81 Mon Sep 17 00:00:00 2001 From: ADmad Date: Thu, 9 Oct 2014 19:19:53 +0530 Subject: [PATCH 10/13] Make CakeSession::read() return null for all failure cases. --- lib/Cake/Model/Datasource/CakeSession.php | 2 +- lib/Cake/Test/Case/Model/Datasource/CakeSessionTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Cake/Model/Datasource/CakeSession.php b/lib/Cake/Model/Datasource/CakeSession.php index 96a66d899..6d7d53a20 100644 --- a/lib/Cake/Model/Datasource/CakeSession.php +++ b/lib/Cake/Model/Datasource/CakeSession.php @@ -378,7 +378,7 @@ class CakeSession { */ public static function read($name = null) { if (empty($name) && $name !== null) { - return false; + return null; } if (!self::_hasSession() || !self::start()) { return null; diff --git a/lib/Cake/Test/Case/Model/Datasource/CakeSessionTest.php b/lib/Cake/Test/Case/Model/Datasource/CakeSessionTest.php index 7f70ea82c..534aaa1ed 100644 --- a/lib/Cake/Test/Case/Model/Datasource/CakeSessionTest.php +++ b/lib/Cake/Test/Case/Model/Datasource/CakeSessionTest.php @@ -280,7 +280,7 @@ class CakeSessionTest extends CakeTestCase { * @return void */ public function testReadyEmpty() { - $this->assertFalse(TestCakeSession::read('')); + $this->assertNull(TestCakeSession::read('')); } /** From 0ff9545e5adab509153551596a0be81a6a1493bd Mon Sep 17 00:00:00 2001 From: mark_story Date: Thu, 9 Oct 2014 19:56:00 -0400 Subject: [PATCH 11/13] Add test for find() and array conditions. Add a test for conditions using an array with only one element. Closes #4848 --- lib/Cake/Test/Case/Model/ModelReadTest.php | 59 +++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/lib/Cake/Test/Case/Model/ModelReadTest.php b/lib/Cake/Test/Case/Model/ModelReadTest.php index 294ce636f..b684f06d0 100644 --- a/lib/Cake/Test/Case/Model/ModelReadTest.php +++ b/lib/Cake/Test/Case/Model/ModelReadTest.php @@ -6550,12 +6550,43 @@ class ModelReadTest extends BaseModelTest { } } +/** + * Test that find() with array conditions works when there is only one element. + * + * @return void + */ + public function testFindAllArrayConditions() { + $this->loadFixtures('User'); + $TestModel = new User(); + $TestModel->cacheQueries = false; + + $result = $TestModel->find('all', array( + 'conditions' => array('User.id' => array(3)), + )); + $expected = array( + array( + 'User' => array( + 'id' => '3', + 'user' => 'larry', + 'password' => '5f4dcc3b5aa765d61d8327deb882cf99', + 'created' => '2007-03-17 01:20:23', + 'updated' => '2007-03-17 01:22:31' + )) + ); + $this->assertEquals($expected, $result); + + $result = $TestModel->find('all', array( + 'conditions' => array('User.user' => array('larry')), + )); + $this->assertEquals($expected, $result); + } + /** * test find('list') method * * @return void */ - public function testGenerateFindList() { + public function testFindList() { $this->loadFixtures('Article', 'Apple', 'Post', 'Author', 'User', 'Comment'); $TestModel = new Article(); @@ -6825,6 +6856,32 @@ class ModelReadTest extends BaseModelTest { $this->assertEquals($expected, $result); } +/** + * Test that find(list) works with array conditions that have only one element. + * + * @return void + */ + public function testFindListArrayCondition() { + $this->loadFixtures('User'); + $TestModel = new User(); + $TestModel->cacheQueries = false; + + $result = $TestModel->find('list', array( + 'fields' => array('id', 'user'), + 'conditions' => array('User.id' => array(3)), + )); + $expected = array( + 3 => 'larry' + ); + $this->assertEquals($expected, $result); + + $result = $TestModel->find('list', array( + 'fields' => array('id', 'user'), + 'conditions' => array('User.user' => array('larry')), + )); + $this->assertEquals($expected, $result); + } + /** * testFindField method * From 39011cd9d8def8308629991af5f5bda0d01040d1 Mon Sep 17 00:00:00 2001 From: mark_story Date: Thu, 9 Oct 2014 22:49:17 -0400 Subject: [PATCH 12/13] Fix Model::isUnique() not working as a validator. While it *did* work for single fields, isUnique could not be used to validate the uniqueness across multiple fields as documented. Because validation methods pass arguments in an order the validator did not expect the validation method would not work correctly. Fixes #4571 --- lib/Cake/Model/Model.php | 10 ++++- .../Test/Case/Model/ModelValidationTest.php | 44 +++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/lib/Cake/Model/Model.php b/lib/Cake/Model/Model.php index 3a698101b..c9e7ce7e9 100644 --- a/lib/Cake/Model/Model.php +++ b/lib/Cake/Model/Model.php @@ -3293,11 +3293,19 @@ class Model extends Object implements CakeEventListener { /** * Returns false if any fields passed match any (by default, all if $or = false) of their matching values. * + * Can be used as a validation method. When used as a validation method, the `$or` parameter + * contains an array of fields to be validated. + * * @param array $fields Field/value pairs to search (if no values specified, they are pulled from $this->data) - * @param bool $or If false, all fields specified must match in order for a false return value + * @param bool|array $or If false, all fields specified must match in order for a false return value * @return bool False if any records matching any fields are found */ public function isUnique($fields, $or = true) { + if (is_array($or)) { + $args = func_get_args(); + $fields = $args[1]; + $or = isset($args[2]) ? $args[2] : true; + } if (!is_array($fields)) { $fields = func_get_args(); if (is_bool($fields[count($fields) - 1])) { diff --git a/lib/Cake/Test/Case/Model/ModelValidationTest.php b/lib/Cake/Test/Case/Model/ModelValidationTest.php index 8a3d266fd..c5f3b22a3 100644 --- a/lib/Cake/Test/Case/Model/ModelValidationTest.php +++ b/lib/Cake/Test/Case/Model/ModelValidationTest.php @@ -2405,6 +2405,50 @@ class ModelValidationTest extends BaseModelTest { $this->assertEquals($expected, $result); } +/** + * Test the isUnique method when used as a validator for multiple fields. + * + * @return void + */ + public function testIsUniqueValidator() { + $this->loadFixtures('Article'); + $Article = ClassRegistry::init('Article'); + $Article->validate = array( + 'user_id' => array( + 'duplicate' => array( + 'rule' => array('isUnique', array('user_id', 'title'), false) + ) + ) + ); + $data = array( + 'user_id' => 1, + 'title' => 'First Article', + ); + $Article->create($data); + $this->assertFalse($Article->validates(), 'Contains a dupe'); + + $data = array( + 'user_id' => 1, + 'title' => 'Unique Article', + ); + $Article->create($data); + $this->assertTrue($Article->validates(), 'Should pass'); + + $Article->validate = array( + 'user_id' => array( + 'duplicate' => array( + 'rule' => array('isUnique', array('user_id', 'title')) + ) + ) + ); + $data = array( + 'user_id' => 1, + 'title' => 'Unique Article', + ); + $Article->create($data); + $this->assertFalse($Article->validates(), 'Should fail, conditions are combined with or'); + } + } /** From ecf030796b3e6f72a86f02d5ad32c8fe785999c0 Mon Sep 17 00:00:00 2001 From: ADmad Date: Sat, 11 Oct 2014 19:37:22 +0530 Subject: [PATCH 13/13] Fix docblock --- lib/Cake/Model/Datasource/DboSource.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Cake/Model/Datasource/DboSource.php b/lib/Cake/Model/Datasource/DboSource.php index 861c8104d..86516546e 100644 --- a/lib/Cake/Model/Datasource/DboSource.php +++ b/lib/Cake/Model/Datasource/DboSource.php @@ -396,7 +396,7 @@ class DboSource extends DataSource { * * @param string $sql SQL statement * @param array $params Additional options for the query. - * @return bool + * @return mixed Resource or object representing the result set, or false on failure */ public function rawQuery($sql, $params = array()) { $this->took = $this->numRows = false;