diff --git a/.gitignore b/.gitignore index 349903082..956dacdc7 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,9 @@ ################################# /nbproject .idea +/.project +/.buildpath +/.settings/ # OS generated files # ###################### diff --git a/app/View/Elements/Flash/default.ctp b/app/View/Elements/Flash/default.ctp index 4a86b69ac..96d4cbc37 100755 --- a/app/View/Elements/Flash/default.ctp +++ b/app/View/Elements/Flash/default.ctp @@ -1,15 +1 @@ -/** -* CakePHP(tm) : Rapid Development Framework (http://cakephp.org) -* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) -* -* Licensed under The MIT License -* For full copyright and license information, please see the LICENSE.txt -* Redistributions of files must retain the above copyright notice. -* -* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) -* @link http://cakephp.org CakePHP(tm) Project -* @package app.View.Emails.html -* @since CakePHP(tm) v 0.10.0.1076 -* @license http://www.opensource.org/licenses/mit-license.php MIT License -*/
diff --git a/app/composer.json b/app/composer.json index e05128e72..bf5c67cc8 100644 --- a/app/composer.json +++ b/app/composer.json @@ -23,7 +23,7 @@ }, "require-dev": { "phpunit/phpunit": "3.7.*", - "cakephp/cakephp": "~2.8" + "cakephp/cakephp": "~2.9" }, "suggest": { "cakephp/cakephp-codesniffer": "Easily check code formatting against the CakePHP coding standards." diff --git a/lib/Cake/Console/ShellDispatcher.php b/lib/Cake/Console/ShellDispatcher.php index d53851127..37aa30037 100644 --- a/lib/Cake/Console/ShellDispatcher.php +++ b/lib/Cake/Console/ShellDispatcher.php @@ -134,7 +134,7 @@ class ShellDispatcher { if (!defined('TMP') && !is_dir(APP . 'tmp')) { define('TMP', CAKE_CORE_INCLUDE_PATH . DS . 'Cake' . DS . 'Console' . DS . 'Templates' . DS . 'skel' . DS . 'tmp' . DS); } - $boot = file_exists(ROOT . DS . APP_DIR . DS . 'Config' . DS . 'bootstrap.php'); + require CORE_PATH . 'Cake' . DS . 'bootstrap.php'; if (!file_exists(APP . 'Config' . DS . 'core.php')) { diff --git a/lib/Cake/Model/CakeSchema.php b/lib/Cake/Model/CakeSchema.php index d06cc6186..9f68f86da 100644 --- a/lib/Cake/Model/CakeSchema.php +++ b/lib/Cake/Model/CakeSchema.php @@ -403,8 +403,14 @@ class CakeSchema extends CakeObject { * @param string $table Table name you want returned. * @param array $fields Array of field information to generate the table with. * @return string Variable declaration for a schema class. + * @throws Exception */ public function generateTable($table, $fields) { + // Valid var name regex (http://www.php.net/manual/en/language.variables.basics.php) + if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $table)) { + throw new Exception("Invalid table name '{$table}'"); + } + $out = "\tpublic \${$table} = array(\n"; if (is_array($fields)) { $cols = array(); diff --git a/lib/Cake/Model/Datasource/CakeSession.php b/lib/Cake/Model/Datasource/CakeSession.php index 4a25f67c8..9b60fe761 100644 --- a/lib/Cake/Model/Datasource/CakeSession.php +++ b/lib/Cake/Model/Datasource/CakeSession.php @@ -230,9 +230,12 @@ class CakeSession { * @return bool True if variable is there */ public static function check($name) { - if (empty($name) || !static::_hasSession() || !static::start()) { + if (!static::_hasSession() || !static::start()) { return false; } + if (isset($_SESSION[$name])) { + return true; + } return Hash::get($_SESSION, $name) !== null; } @@ -380,9 +383,6 @@ class CakeSession { * session not started, or provided name not found in the session, false on failure. */ public static function read($name = null) { - if (empty($name) && $name !== null) { - return null; - } if (!static::_hasSession() || !static::start()) { return null; } @@ -418,7 +418,7 @@ class CakeSession { * @return bool True if the write was successful, false if the write failed */ public static function write($name, $value = null) { - if (empty($name) || !static::start()) { + if (!static::start()) { return false; } diff --git a/lib/Cake/Model/Datasource/Database/Mysql.php b/lib/Cake/Model/Datasource/Database/Mysql.php index 21f763dc4..bb6e1d18e 100644 --- a/lib/Cake/Model/Datasource/Database/Mysql.php +++ b/lib/Cake/Model/Datasource/Database/Mysql.php @@ -849,4 +849,57 @@ class Mysql extends DboSource { return strpos(strtolower($real), 'unsigned') !== false; } +/** + * Inserts multiple values into a table. Uses a single query in order to insert + * multiple rows. + * + * @param string $table The table being inserted into. + * @param array $fields The array of field/column names being inserted. + * @param array $values The array of values to insert. The values should + * be an array of rows. Each row should have values keyed by the column name. + * Each row must have the values in the same order as $fields. + * @return bool + */ + public function insertMulti($table, $fields, $values) { + $table = $this->fullTableName($table); + $holder = implode(', ', array_fill(0, count($fields), '?')); + $fields = implode(', ', array_map(array($this, 'name'), $fields)); + $pdoMap = array( + 'integer' => PDO::PARAM_INT, + 'float' => PDO::PARAM_STR, + 'boolean' => PDO::PARAM_BOOL, + 'string' => PDO::PARAM_STR, + 'text' => PDO::PARAM_STR + ); + $columnMap = array(); + $rowHolder = "({$holder})"; + $sql = "INSERT INTO {$table} ({$fields}) VALUES "; + $countRows = count($values); + for ($i = 0; $i < $countRows; $i++) { + if ($i !== 0) { + $sql .= ','; + } + $sql .= " $rowHolder"; + } + $statement = $this->_connection->prepare($sql); + foreach ($values[key($values)] as $key => $val) { + $type = $this->introspectType($val); + $columnMap[$key] = $pdoMap[$type]; + } + $valuesList = array(); + $i = 1; + foreach ($values as $value) { + foreach ($value as $col => $val) { + $valuesList[] = $val; + $statement->bindValue($i, $val, $columnMap[$col]); + $i++; + } + } + $result = $statement->execute(); + $statement->closeCursor(); + if ($this->fullDebug) { + $this->logQuery($sql, $valuesList); + } + return $result; + } } diff --git a/lib/Cake/Model/Datasource/Database/Postgres.php b/lib/Cake/Model/Datasource/Database/Postgres.php index dfe5272ec..8673fbc5c 100644 --- a/lib/Cake/Model/Datasource/Database/Postgres.php +++ b/lib/Cake/Model/Datasource/Database/Postgres.php @@ -727,19 +727,14 @@ class Postgres extends DboSource { * @return int An integer representing the length of the column */ public function length($real) { - $col = str_replace(array(')', 'unsigned'), '', $real); - $limit = null; - - if (strpos($col, '(') !== false) { - list($col, $limit) = explode('(', $col); + $col = $real; + if (strpos($real, '(') !== false) { + list($col, $limit) = explode('(', $real); } if ($col === 'uuid') { return 36; } - if ($limit) { - return (int)$limit; - } - return null; + return parent::length($real); } /** diff --git a/lib/Cake/Model/Datasource/DboSource.php b/lib/Cake/Model/Datasource/DboSource.php index f37d6310e..a73eea8a7 100644 --- a/lib/Cake/Model/Datasource/DboSource.php +++ b/lib/Cake/Model/Datasource/DboSource.php @@ -3097,54 +3097,45 @@ class DboSource extends DataSource { * @return mixed An integer or string representing the length of the column, or null for unknown length. */ public function length($real) { - if (!preg_match_all('/([\w\s]+)(?:\((\d+)(?:,(\d+))?\))?(\sunsigned)?(\szerofill)?/', $real, $result)) { - $col = str_replace(array(')', 'unsigned'), '', $real); - $limit = null; - - if (strpos($col, '(') !== false) { - list($col, $limit) = explode('(', $col); - } - if ($limit !== null) { - return (int)$limit; - } - return null; - } - + preg_match('/([\w\s]+)(?:\((.+?)\))?(\sunsigned)?/i', $real, $result); $types = array( 'int' => 1, 'tinyint' => 1, 'smallint' => 1, 'mediumint' => 1, 'integer' => 1, 'bigint' => 1 ); - list($real, $type, $length, $offset, $sign) = $result; - $typeArr = $type; - $type = $type[0]; - $length = $length[0]; - $offset = $offset[0]; + $type = $length = null; + if (isset($result[1])) { + $type = $result[1]; + } + if (isset($result[2])) { + $length = $result[2]; + } + $sign = isset($result[3]); $isFloat = in_array($type, array('dec', 'decimal', 'float', 'numeric', 'double')); - if ($isFloat && $offset) { - return $length . ',' . $offset; + if ($isFloat && strpos($length, ',') !== false) { + return $length; } - if (($real[0] == $type) && (count($real) === 1)) { + if ($length === null) { return null; } if (isset($types[$type])) { - $length += $types[$type]; - if (!empty($sign)) { - $length--; - } - } elseif (in_array($type, array('enum', 'set'))) { - $length = 0; - foreach ($typeArr as $key => $enumValue) { - if ($key === 0) { - continue; - } + return (int)$length; + } + if (in_array($type, array('enum', 'set'))) { + $values = array_map(function ($value) { + return trim(trim($value), '\'"'); + }, explode(',', $length)); + + $maxLength = 0; + foreach ($values as $key => $enumValue) { $tmpLength = strlen($enumValue); - if ($tmpLength > $length) { - $length = $tmpLength; + if ($tmpLength > $maxLength) { + $maxLength = $tmpLength; } } + return $maxLength; } return (int)$length; } diff --git a/lib/Cake/Model/Model.php b/lib/Cake/Model/Model.php index 87336976f..dff60b57e 100644 --- a/lib/Cake/Model/Model.php +++ b/lib/Cake/Model/Model.php @@ -473,9 +473,16 @@ class Model extends CakeObject implements CakeEventListener { /** * List of behaviors to load when the model object is initialized. Settings can be - * passed to behaviors by using the behavior name as index. Eg: + * passed to behaviors by using the behavior name as index. * - * public $actsAs = array('Translate', 'MyBehavior' => array('setting1' => 'value1')) + * For example: + * + * ``` + * public $actsAs = array( + * 'Translate', + * 'MyBehavior' => array('setting1' => 'value1') + * ); + * ``` * * @var array * @link http://book.cakephp.org/2.0/en/models/behaviors.html#using-behaviors @@ -3446,12 +3453,19 @@ class Model extends CakeObject implements CakeEventListener { * - 3rd param: If 2nd argument is provided, a boolean flag for enabling/disabled * query caching. * + * If the query cache param as 2nd or 3rd argument is not given then the model's + * default `$cacheQueries` value is used. + * * @param string $sql SQL statement * @return mixed Resultset array or boolean indicating success / failure depending on the query executed * @link http://book.cakephp.org/2.0/en/models/retrieving-your-data.html#model-query */ public function query($sql) { $params = func_get_args(); + // use $this->cacheQueries as default when argument not explicitly given already + if (count($params) === 1 || count($params) === 2 && !is_bool($params[1])) { + $params[] = $this->cacheQueries; + } $db = $this->getDataSource(); return call_user_func_array(array(&$db, 'query'), $params); } diff --git a/lib/Cake/Routing/Router.php b/lib/Cake/Routing/Router.php index 83de88feb..b464728c8 100644 --- a/lib/Cake/Routing/Router.php +++ b/lib/Cake/Routing/Router.php @@ -523,6 +523,7 @@ class Router { * - 'id' - The regular expression fragment to use when matching IDs. By default, matches * integer values and UUIDs. * - 'prefix' - URL prefix to use for the generated routes. Defaults to '/'. + * - 'connectOptions' – Custom options for connecting the routes. * * @param string|array $controller A controller name or array of controller names (i.e. "Posts" or "ListItems") * @param array $options Options to use when generating REST routes diff --git a/lib/Cake/Test/Case/Model/CakeSchemaTest.php b/lib/Cake/Test/Case/Model/CakeSchemaTest.php index eb2a64a82..aedb6ef3d 100644 --- a/lib/Cake/Test/Case/Model/CakeSchemaTest.php +++ b/lib/Cake/Test/Case/Model/CakeSchemaTest.php @@ -686,6 +686,22 @@ class CakeSchemaTest extends CakeTestCase { $this->assertRegExp('/\'type\' \=\> \'fulltext\'/', $result); } +/** + * test that tables with unsupported name are not getting through + * + * @return void + */ + public function testGenerateInvalidTable() { + $invalidTableName = 'invalid name !@#$%^&*()'; + $expectedException = "Invalid table name '{$invalidTableName}'"; + try{ + $this->Schema->generateTable($invalidTableName, array()); + $this->fail("Expected exception \"{$expectedException}\" not thrown"); + } catch (Exception $e) { + $this->assertEquals($expectedException, $e->getMessage()); + } + } + /** * testSchemaWrite method * diff --git a/lib/Cake/Test/Case/Model/Datasource/CakeSessionTest.php b/lib/Cake/Test/Case/Model/Datasource/CakeSessionTest.php index d6cd59fa7..13371a946 100644 --- a/lib/Cake/Test/Case/Model/Datasource/CakeSessionTest.php +++ b/lib/Cake/Test/Case/Model/Datasource/CakeSessionTest.php @@ -307,9 +307,9 @@ class CakeSessionTest extends CakeTestCase { * @return void */ public function testWriteEmptyKey() { - $this->assertFalse(TestCakeSession::write('', 'graham')); - $this->assertFalse(TestCakeSession::write('', '')); - $this->assertFalse(TestCakeSession::write('')); + $this->assertTrue(TestCakeSession::write('', 'graham')); + $this->assertTrue(TestCakeSession::write('', '')); + $this->assertTrue(TestCakeSession::write('')); } /** @@ -403,6 +403,17 @@ class CakeSessionTest extends CakeTestCase { $this->assertFalse(TestCakeSession::check('Clearing')); } +/** + * test delete + * + * @return void + */ + public function testDeleteEmptyString() { + TestCakeSession::write('', 'empty string'); + $this->assertTrue(TestCakeSession::delete('')); + $this->assertFalse(TestCakeSession::check('')); + } + /** * testClear method * @@ -500,6 +511,10 @@ class CakeSessionTest extends CakeTestCase { * @return void */ public function testReadingSavedEmpty() { + TestCakeSession::write('', 'empty string'); + $this->assertTrue(TestCakeSession::check('')); + $this->assertEquals('empty string', TestCakeSession::read('')); + TestCakeSession::write('SessionTestCase', 0); $this->assertEquals(0, TestCakeSession::read('SessionTestCase')); @@ -511,7 +526,7 @@ class CakeSessionTest extends CakeTestCase { $this->assertFalse(TestCakeSession::read('SessionTestCase')); TestCakeSession::write('SessionTestCase', null); - $this->assertEquals(null, TestCakeSession::read('SessionTestCase')); + $this->assertNull(TestCakeSession::read('SessionTestCase')); } /** diff --git a/lib/Cake/Test/Case/Model/Datasource/Database/MysqlTest.php b/lib/Cake/Test/Case/Model/Datasource/Database/MysqlTest.php index dcae76f65..ee9c0f57e 100644 --- a/lib/Cake/Test/Case/Model/Datasource/Database/MysqlTest.php +++ b/lib/Cake/Test/Case/Model/Datasource/Database/MysqlTest.php @@ -4174,4 +4174,60 @@ SQL; $this->assertTrue($this->Dbo->isConnected(), 'Should be connected.'); } +/** + * Test insertMulti with id position. + * + * @return void + */ + public function testInsertMultiId() { + $this->loadFixtures('Article'); + $Article = ClassRegistry::init('Article'); + $db = $Article->getDatasource(); + $datetime = date('Y-m-d H:i:s'); + $data = array( + array( + 'user_id' => 1, + 'title' => 'test', + 'body' => 'test', + 'published' => 'N', + 'created' => $datetime, + 'updated' => $datetime, + 'id' => 100, + ), + array( + 'user_id' => 1, + 'title' => 'test 101', + 'body' => 'test 101', + 'published' => 'N', + 'created' => $datetime, + 'updated' => $datetime, + 'id' => 101, + ) + ); + $result = $db->insertMulti('articles', array_keys($data[0]), $data); + $this->assertTrue($result, 'Data was saved'); + + $data = array( + array( + 'id' => 102, + 'user_id' => 1, + 'title' => 'test', + 'body' => 'test', + 'published' => 'N', + 'created' => $datetime, + 'updated' => $datetime, + ), + array( + 'id' => 103, + 'user_id' => 1, + 'title' => 'test 101', + 'body' => 'test 101', + 'published' => 'N', + 'created' => $datetime, + 'updated' => $datetime, + ) + ); + $result = $db->insertMulti('articles', array_keys($data[0]), $data); + $this->assertTrue($result, 'Data was saved'); + } } diff --git a/lib/Cake/Test/Case/Model/Datasource/DboSourceTest.php b/lib/Cake/Test/Case/Model/Datasource/DboSourceTest.php index 933cd9347..911faec5c 100644 --- a/lib/Cake/Test/Case/Model/Datasource/DboSourceTest.php +++ b/lib/Cake/Test/Case/Model/Datasource/DboSourceTest.php @@ -1826,4 +1826,35 @@ class DboSourceTest extends CakeTestCase { $this->db->flushQueryCache(); $this->assertAttributeCount(0, '_queryCache', $this->db); } + +/** + * Test length parsing. + * + * @return void + */ + public function testLength() { + $result = $this->db->length('varchar(255)'); + $this->assertEquals(255, $result); + + $result = $this->db->length('integer(11)'); + $this->assertEquals(11, $result); + + $result = $this->db->length('integer unsigned'); + $this->assertNull($result); + + $result = $this->db->length('integer(11) unsigned'); + $this->assertEquals(11, $result); + + $result = $this->db->length('integer(11) zerofill'); + $this->assertEquals(11, $result); + + $result = $this->db->length('decimal(20,3)'); + $this->assertEquals('20,3', $result); + + $result = $this->db->length('enum("one", "longer")'); + $this->assertEquals(6, $result); + + $result = $this->db->length("enum('One Value','ANOTHER ... VALUE ...')"); + $this->assertEquals(21, $result); + } } diff --git a/lib/Cake/Test/Case/Model/ModelReadTest.php b/lib/Cake/Test/Case/Model/ModelReadTest.php index dd8f92b58..7723a8cfc 100644 --- a/lib/Cake/Test/Case/Model/ModelReadTest.php +++ b/lib/Cake/Test/Case/Model/ModelReadTest.php @@ -403,7 +403,7 @@ class ModelReadTest extends BaseModelTest { $query .= '.id = ? AND ' . $this->db->fullTableName('articles') . '.published = ?'; $params = array(1, 'Y'); - $result = $Article->query($query, $params); + $result = $Article->query($query, $params, true); $expected = array( '0' => array( $this->db->fullTableName('articles', false, false) => array( @@ -438,7 +438,7 @@ class ModelReadTest extends BaseModelTest { $query .= ' WHERE ' . $this->db->fullTableName('articles') . '.title LIKE ?'; $params = array('%First%'); - $result = $Article->query($query, $params); + $result = $Article->query($query, $params, true); $this->assertTrue(is_array($result)); $this->assertTrue( isset($result[0][$this->db->fullTableName('articles', false, false)]['title']) || @@ -449,7 +449,7 @@ class ModelReadTest extends BaseModelTest { $query = 'SELECT title FROM '; $query .= $this->db->fullTableName('articles') . ' WHERE title = ? AND published = ?'; $params = array('First? Article', 'Y'); - $Article->query($query, $params); + $Article->query($query, $params, true); $result = $this->db->getQueryCache($query, $params); $this->assertFalse($result === false); @@ -8531,4 +8531,118 @@ class ModelReadTest extends BaseModelTest { ); $this->assertEquals($expected, $results, 'Model related with belongsTo afterFind callback fails'); } + +/** + * Pull out the username from a result set. + * + * @param array $result The results. + * @return string The username. + */ + public static function extractUserNameFromQueryResult(array $result) { + return isset($result[0][0]) ? $result[0][0]['user'] : $result[0]['u']['user']; + } + +/** + * Test that query() doesn't override the 2nd argument with a default. + * + * @return void + */ + public function testQueryRespectsCacheQueriesAsSecondArgument() { + $model = new User(); + $model->save(array('user' => 'Chuck')); + $userTableName = $this->db->fullTableName('users'); + + $getUserNameFromDb = function ($cacheArgument) use ($model, $userTableName) { + $query = sprintf('SELECT u.user FROM %s u WHERE id=%d', $userTableName, $model->id); + $users = $model->query($query, $cacheArgument); + return ModelReadTest::extractUserNameFromQueryResult($users); + }; + + $model->cacheQueries = true; + $this->assertSame('Chuck', $getUserNameFromDb(true)); + $this->assertSame('Chuck', $getUserNameFromDb(false)); + + $model->updateAll(array('User.user' => "'Sylvester'"), array('User.id' => $model->id)); + $model->cacheQueries = false; + $this->assertSame('Chuck', $getUserNameFromDb(true)); + $this->assertSame('Sylvester', $getUserNameFromDb(false)); + } + +/** + * Test that query() doesn't override the cache param in the 3nd argument + * with a default. + * + * @return void + */ + public function testQueryRespectsCacheQueriesAsThirdArgument() { + $model = new User(); + $model->save(array('user' => 'Chuck')); + $userTableName = $this->db->fullTableName('users'); + + $getUserNameFromDb = function ($cacheArgument) use ($model, $userTableName) { + $query = sprintf('SELECT u.user FROM %s u WHERE id=?', $userTableName); + $users = $model->query($query, array($model->id), $cacheArgument); + return ModelReadTest::extractUserNameFromQueryResult($users); + }; + + $model->cacheQueries = true; + $this->assertSame('Chuck', $getUserNameFromDb(true)); + $this->assertSame('Chuck', $getUserNameFromDb(false)); + + $model->updateAll(array('User.user' => "'Sylvester'"), array('User.id' => $model->id)); + $model->cacheQueries = false; + $this->assertSame('Chuck', $getUserNameFromDb(true)); + $this->assertSame('Sylvester', $getUserNameFromDb(false)); + } + +/** + * Test that query() uses the cacheQueries property when there is one argument. + * + * @return void + */ + public function testQueryTakesModelCacheQueriesValueAsDefaultForOneArgument() { + $model = new User(); + $model->save(array('user' => 'Chuck')); + $userTableName = $this->db->fullTableName('users'); + + $getUserNameFromDb = function () use ($model, $userTableName) { + $query = sprintf('SELECT u.user FROM %s u WHERE id=%d', $userTableName, $model->id); + $users = $model->query($query); + return ModelReadTest::extractUserNameFromQueryResult($users); + }; + + $model->cacheQueries = true; + $this->assertSame('Chuck', $getUserNameFromDb()); + $model->updateAll(array('User.user' => "'Sylvester'"), array('User.id' => $model->id)); + + $this->assertSame('Chuck', $getUserNameFromDb()); + $model->cacheQueries = false; + $this->assertSame('Sylvester', $getUserNameFromDb()); + } + +/** + * Test that query() uses the cacheQueries property when there are two arguments. + * + * @return void + */ + public function testQueryTakesModelCacheQueriesValueAsDefaultForTwoArguments() { + $model = new User(); + $model->save(array('user' => 'Chuck')); + $userTableName = $this->db->fullTableName('users'); + + $getUserNameFromDb = function () use ($model, $userTableName) { + $query = sprintf('SELECT u.user FROM %s u WHERE id=?', $userTableName); + $users = $model->query($query, array($model->id)); + return ModelReadTest::extractUserNameFromQueryResult($users); + }; + + $model->cacheQueries = true; + $this->assertSame('Chuck', $getUserNameFromDb()); + + $model->updateAll(array('User.user' => "'Sylvester'"), array('User.id' => $model->id)); + $this->assertSame('Chuck', $getUserNameFromDb()); + + $model->cacheQueries = false; + $this->assertSame('Sylvester', $getUserNameFromDb()); + } } diff --git a/lib/Cake/Test/Case/TestSuite/CakeTestCaseTest.php b/lib/Cake/Test/Case/TestSuite/CakeTestCaseTest.php index da97150c3..0ea4d6253 100644 --- a/lib/Cake/Test/Case/TestSuite/CakeTestCaseTest.php +++ b/lib/Cake/Test/Case/TestSuite/CakeTestCaseTest.php @@ -39,6 +39,23 @@ class SecondaryPost extends Model { } +/** + * ConstructorPost test stub. + */ +class ConstructorPost extends Model { + +/** + * @var string + */ + public $useTable = 'posts'; + + public function __construct($id = false, $table = null, $ds = null) { + parent::__construct($id, $table, $ds); + $this->getDataSource()->cacheMethods = false; + } + +} + /** * CakeTestCaseTest * @@ -435,6 +452,16 @@ class CakeTestCaseTest extends CakeTestCase { ConnectionManager::drop('test_secondary'); } +/** + * Test getMockForModel when the model accesses the datasource in the constructor. + * + * @return void + */ + public function testGetMockForModelConstructorDatasource() { + $post = $this->getMockForModel('ConstructorPost', array('save'), array('ds' => 'test')); + $this->assertEquals('test', $post->useDbConfig); + } + /** * test getMockForModel() with plugin models * diff --git a/lib/Cake/Test/Case/Utility/HashTest.php b/lib/Cake/Test/Case/Utility/HashTest.php index a5a577e96..53b1d40c8 100644 --- a/lib/Cake/Test/Case/Utility/HashTest.php +++ b/lib/Cake/Test/Case/Utility/HashTest.php @@ -229,6 +229,19 @@ class HashTest extends CakeTestCase { $this->assertEquals($data[1]['Article'], $result); } +/** + * Test that get() can extract '' key data. + * + * @return void + */ + public function testGetEmptyKey() { + $data = array( + '' => 'some value' + ); + $result = Hash::get($data, ''); + $this->assertSame($data[''], $result); + } + /** * Test get() with an invalid path * diff --git a/lib/Cake/Test/Case/View/Helper/FormHelperTest.php b/lib/Cake/Test/Case/View/Helper/FormHelperTest.php index c61b4894d..d38f7e79c 100644 --- a/lib/Cake/Test/Case/View/Helper/FormHelperTest.php +++ b/lib/Cake/Test/Case/View/Helper/FormHelperTest.php @@ -541,6 +541,7 @@ class FormHelperTest extends CakeTestCase { $this->Form->request['action'] = 'add'; $this->Form->request->webroot = ''; $this->Form->request->base = ''; + Router::setRequestInfo($this->Form->request); ClassRegistry::addObject('Contact', new Contact()); ClassRegistry::addObject('ContactNonStandardPk', new ContactNonStandardPk()); @@ -8569,12 +8570,14 @@ class FormHelperTest extends CakeTestCase { */ public function testPostLinkSecurityHashInline() { $hash = Security::hash( - '/posts/delete/1' . + '/basedir/posts/delete/1' . serialize(array()) . '' . Configure::read('Security.salt') ); $hash .= '%3A'; + $this->Form->request->base = '/basedir'; + $this->Form->request->webroot = '/basedir/'; $this->Form->request->params['_Token']['key'] = 'test'; $this->Form->create('Post', array('url' => array('action' => 'add'))); @@ -8584,7 +8587,11 @@ class FormHelperTest extends CakeTestCase { $this->assertEquals(array('Post.title'), $this->Form->fields); $this->assertContains($hash, $result, 'Should contain the correct hash.'); - $this->assertAttributeEquals('/posts/add', '_lastAction', $this->Form, 'lastAction was should be restored.'); + $this->assertAttributeEquals( + '/basedir/posts/add', + '_lastAction', + $this->Form, + 'lastAction was should be restored.'); } /** @@ -10990,4 +10997,36 @@ class FormHelperTest extends CakeTestCase { $this->assertEquals($expected, $result); } +/** + * Tests `_lastAction`. + * + * With named, numeric value + * + * @return void + */ + public function testLastActionWithNamedNumeric() { + $here = '/users/index/page:1'; + + $this->Form->request->here = $here; + $this->Form->create('User'); + + $this->assertAttributeEquals($here, '_lastAction', $this->Form, "_lastAction shouldn't be empty."); + } + +/** + * Tests `_lastAction`. + * + * With named, string value + * + * @return void + */ + public function testLastActionWithNamedString() { + $here = '/users/index/foo:bar'; + + $this->Form->request->here = $here; + $this->Form->create('User'); + + $this->assertAttributeEquals($here, '_lastAction', $this->Form, "_lastAction shouldn't be empty."); + } + } diff --git a/lib/Cake/TestSuite/CakeTestCase.php b/lib/Cake/TestSuite/CakeTestCase.php index c375717fe..ab04db6a7 100644 --- a/lib/Cake/TestSuite/CakeTestCase.php +++ b/lib/Cake/TestSuite/CakeTestCase.php @@ -718,13 +718,13 @@ abstract class CakeTestCase extends PHPUnit_Framework_TestCase { * @return Model */ public function getMockForModel($model, $methods = array(), $config = array()) { - $config += ClassRegistry::config('Model'); + $defaults = ClassRegistry::config('Model'); + unset($defaults['ds']); list($plugin, $name) = pluginSplit($model, true); App::uses($name, $plugin . 'Model'); - $config = array_merge((array)$config, array('name' => $name)); - unset($config['ds']); + $config = array_merge($defaults, (array)$config, array('name' => $name)); if (!class_exists($name)) { throw new MissingModelException(array($model)); diff --git a/lib/Cake/TestSuite/Fixture/CakeFixtureManager.php b/lib/Cake/TestSuite/Fixture/CakeFixtureManager.php index 8d678047f..a39b75c02 100644 --- a/lib/Cake/TestSuite/Fixture/CakeFixtureManager.php +++ b/lib/Cake/TestSuite/Fixture/CakeFixtureManager.php @@ -218,7 +218,7 @@ class CakeFixtureManager { if (empty($test->fixtures)) { return; } - $fixtures = array_unique($test->fixtures); + $fixtures = $test->fixtures; if (empty($fixtures) || !$test->autoFixtures) { return; } @@ -229,9 +229,7 @@ class CakeFixtureManager { $db = ConnectionManager::getDataSource($fixture->useDbConfig); $db->begin(); $this->_setupTable($fixture, $db, $test->dropTables); - if (!$test->dropTables) { - $fixture->truncate($db); - } + $fixture->truncate($db); $fixture->insert($db); $db->commit(); } @@ -276,9 +274,7 @@ class CakeFixtureManager { $db = ConnectionManager::getDataSource($fixture->useDbConfig); } $this->_setupTable($fixture, $db, $dropTables); - if (!$dropTables) { - $fixture->truncate($db); - } + $fixture->truncate($db); $fixture->insert($db); } else { throw new UnexpectedValueException(__d('cake_dev', 'Referenced fixture class %s not found', $name)); diff --git a/lib/Cake/Utility/Hash.php b/lib/Cake/Utility/Hash.php index 1c6e0bf27..2ca530ece 100644 --- a/lib/Cake/Utility/Hash.php +++ b/lib/Cake/Utility/Hash.php @@ -43,7 +43,7 @@ class Hash { * @link http://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::get */ public static function get(array $data, $path, $default = null) { - if (empty($data) || $path === '' || $path === null) { + if (empty($data) || $path === null) { return $default; } if (is_string($path) || is_numeric($path)) { diff --git a/lib/Cake/View/Helper/FormHelper.php b/lib/Cake/View/Helper/FormHelper.php index b841ea9d2..49083962f 100644 --- a/lib/Cake/View/Helper/FormHelper.php +++ b/lib/Cake/View/Helper/FormHelper.php @@ -3128,7 +3128,7 @@ class FormHelper extends AppHelper { * @return void */ protected function _lastAction($url) { - $action = Router::url($url, true); + $action = html_entity_decode($this->url($url, true), ENT_QUOTES); $query = parse_url($action, PHP_URL_QUERY); $query = $query ? '?' . $query : ''; $this->_lastAction = parse_url($action, PHP_URL_PATH) . $query; diff --git a/lib/Cake/View/Helper/PaginatorHelper.php b/lib/Cake/View/Helper/PaginatorHelper.php index 5744d83b1..917eb7f7f 100644 --- a/lib/Cake/View/Helper/PaginatorHelper.php +++ b/lib/Cake/View/Helper/PaginatorHelper.php @@ -946,7 +946,7 @@ class PaginatorHelper extends AppHelper { unset($options['tag'], $options['before'], $options['model'], $options['separator'], $options['ellipsis'], $options['class']); $out = ''; - $lower = $params['pageCount'] - $last + 1; + $lower = $params['pageCount'] - (int)$last + 1; if ((is_int($last) || ctype_digit($last)) && $params['page'] <= $lower) { if ($before === null) { diff --git a/lib/Cake/bootstrap.php b/lib/Cake/bootstrap.php index 666c1b7ff..bdd8db4b8 100644 --- a/lib/Cake/bootstrap.php +++ b/lib/Cake/bootstrap.php @@ -146,6 +146,7 @@ App::uses('Configure', 'Core'); App::uses('CakePlugin', 'Core'); App::uses('Cache', 'Cache'); App::uses('CakeObject', 'Core'); +App::uses('Object', 'Core'); App::uses('Multibyte', 'I18n'); App::$bootstrapping = true;