diff --git a/lib/Cake/Console/Shell.php b/lib/Cake/Console/Shell.php index e4ce01f3cf6..90ee0eb3853 100644 --- a/lib/Cake/Console/Shell.php +++ b/lib/Cake/Console/Shell.php @@ -415,7 +415,7 @@ public function dispatchShell() { * @param string $command The command name to run on this shell. If this argument is empty, * and the shell has a `main()` method, that will be called instead. * @param array $argv Array of arguments to run the shell with. This array should be missing the shell name. - * @return void + * @return int|bool * @link https://book.cakephp.org/2.0/en/console-and-shells.html#Shell::runCommand */ public function runCommand($command, $argv) { @@ -469,7 +469,7 @@ public function runCommand($command, $argv) { * Display the help in the correct format * * @param string $command The command to get help for. - * @return void + * @return int|bool */ protected function _displayHelp($command) { $format = 'text'; @@ -571,7 +571,7 @@ public function in($prompt, $options = null, $default = null) { * @param string $prompt Prompt text. * @param string|array $options Array or string of options. * @param string $default Default input value. - * @return Either the default value, or the user-provided input. + * @return string|int the default value, or the user-provided input. */ protected function _getInput($prompt, $options, $default) { if (!is_array($options)) { @@ -726,7 +726,7 @@ public function hr($newlines = 0, $width = 63) { * * @param string $title Title of the error * @param string $message An optional error message - * @return void + * @return int * @link https://book.cakephp.org/2.0/en/console-and-shells.html#Shell::error */ public function error($title, $message = null) { diff --git a/lib/Cake/Controller/Component/Acl/DbAcl.php b/lib/Cake/Controller/Component/Acl/DbAcl.php index eb420709c73..dbbb30fef4a 100644 --- a/lib/Cake/Controller/Component/Acl/DbAcl.php +++ b/lib/Cake/Controller/Component/Acl/DbAcl.php @@ -52,7 +52,7 @@ public function __construct() { /** * Initializes the containing component and sets the Aro/Aco objects to it. * - * @param AclComponent $component The AclComponent instance. + * @param Component $component The AclComponent instance. * @return void */ public function initialize(Component $component) { diff --git a/lib/Cake/Controller/Component/Auth/DigestAuthenticate.php b/lib/Cake/Controller/Component/Auth/DigestAuthenticate.php index f1ba711cda8..86fdac616ae 100644 --- a/lib/Cake/Controller/Component/Auth/DigestAuthenticate.php +++ b/lib/Cake/Controller/Component/Auth/DigestAuthenticate.php @@ -136,7 +136,7 @@ public function getUser(CakeRequest $request) { /** * Gets the digest headers from the request/environment. * - * @return array Array of digest information. + * @return array|bool|null Array of digest information. */ protected function _getDigest() { $digest = env('PHP_AUTH_DIGEST'); diff --git a/lib/Cake/Controller/Component/AuthComponent.php b/lib/Cake/Controller/Component/AuthComponent.php index 9fe60e68392..e21f6e08507 100644 --- a/lib/Cake/Controller/Component/AuthComponent.php +++ b/lib/Cake/Controller/Component/AuthComponent.php @@ -761,7 +761,7 @@ public function redirectUrl($url = null) { * * @param CakeRequest $request The request that contains authentication data. * @param CakeResponse $response The response - * @return array User record data, or false, if the user could not be identified. + * @return array|bool User record data, or false, if the user could not be identified. */ public function identify(CakeRequest $request, CakeResponse $response) { if (empty($this->_authenticateObjects)) { diff --git a/lib/Cake/Controller/Component/CookieComponent.php b/lib/Cake/Controller/Component/CookieComponent.php index af46d5e9e41..1d01e227d68 100644 --- a/lib/Cake/Controller/Component/CookieComponent.php +++ b/lib/Cake/Controller/Component/CookieComponent.php @@ -229,27 +229,14 @@ public function write($key, $value = null, $encrypt = true, $expires = null) { } foreach ($key as $name => $value) { - $names = array($name); if (strpos($name, '.') !== false) { - $names = explode('.', $name, 2); - } - $firstName = $names[0]; - $isMultiValue = (is_array($value) || count($names) > 1); - - if (!isset($this->_values[$this->name][$firstName]) && $isMultiValue) { - $this->_values[$this->name][$firstName] = array(); - } - - if (count($names) > 1) { - $this->_values[$this->name][$firstName] = Hash::insert( - $this->_values[$this->name][$firstName], - $names[1], - $value - ); + $this->_values[$this->name] = Hash::insert($this->_values[$this->name], $name, $value); + list($name) = explode('.', $name, 2); + $value = $this->_values[$this->name][$name]; } else { - $this->_values[$this->name][$firstName] = $value; + $this->_values[$this->name][$name] = $value; } - $this->_write('[' . $firstName . ']', $this->_values[$this->name][$firstName]); + $this->_write('[' . $name . ']', $value); } $this->_encrypted = true; } @@ -274,22 +261,7 @@ public function read($key = null) { if ($key === null) { return $this->_values[$this->name]; } - - if (strpos($key, '.') !== false) { - $names = explode('.', $key, 2); - $key = $names[0]; - } - if (!isset($this->_values[$this->name][$key])) { - return null; - } - - if (!empty($names[1])) { - if (is_array($this->_values[$this->name][$key])) { - return Hash::get($this->_values[$this->name][$key], $names[1]); - } - return null; - } - return $this->_values[$this->name][$key]; + return Hash::get($this->_values[$this->name], $key); } /** @@ -329,20 +301,16 @@ public function delete($key) { $this->read(); } if (strpos($key, '.') === false) { - if (isset($this->_values[$this->name][$key]) && is_array($this->_values[$this->name][$key])) { - foreach ($this->_values[$this->name][$key] as $idx => $val) { - $this->_delete("[$key][$idx]"); - } - } - $this->_delete("[$key]"); unset($this->_values[$this->name][$key]); - return; - } - $names = explode('.', $key, 2); - if (isset($this->_values[$this->name][$names[0]]) && is_array($this->_values[$this->name][$names[0]])) { - $this->_values[$this->name][$names[0]] = Hash::remove($this->_values[$this->name][$names[0]], $names[1]); + $this->_delete('[' . $key . ']'); + } else { + $this->_values[$this->name] = Hash::remove((array)$this->_values[$this->name], $key); + list($key) = explode('.', $key, 2); + if (isset($this->_values[$this->name][$key])) { + $value = $this->_values[$this->name][$key]; + $this->_write('[' . $key . ']', $value); + } } - $this->_delete('[' . implode('][', $names) . ']'); } /** @@ -360,14 +328,7 @@ public function destroy() { } foreach ($this->_values[$this->name] as $name => $value) { - if (is_array($value)) { - foreach ($value as $key => $val) { - unset($this->_values[$this->name][$name][$key]); - $this->_delete("[$name][$key]"); - } - } - unset($this->_values[$this->name][$name]); - $this->_delete("[$name]"); + $this->delete($name); } } @@ -494,7 +455,7 @@ protected function _encrypt($value) { * Decrypts $value using public $type method in Security class * * @param array $values Values to decrypt - * @return string decrypted string + * @return array decrypted string */ protected function _decrypt($values) { $decrypted = array(); @@ -516,7 +477,7 @@ protected function _decrypt($values) { * Decodes and decrypts a single value. * * @param string $value The value to decode & decrypt. - * @return string Decoded value. + * @return string|array Decoded value. */ protected function _decode($value) { $prefix = 'Q2FrZQ==.'; @@ -552,7 +513,7 @@ protected function _implode(array $array) { * Maintains reading backwards compatibility with 1.x CookieComponent::_implode(). * * @param string $string A string containing JSON encoded data, or a bare string. - * @return array Map of key and values + * @return string|array Map of key and values */ protected function _explode($string) { $first = substr($string, 0, 1); diff --git a/lib/Cake/Controller/Component/EmailComponent.php b/lib/Cake/Controller/Component/EmailComponent.php index 47e91d2b084..4fd96dfb7f5 100644 --- a/lib/Cake/Controller/Component/EmailComponent.php +++ b/lib/Cake/Controller/Component/EmailComponent.php @@ -282,7 +282,7 @@ public function initialize(Controller $controller) { * If you are rendering a template this variable will be sent to the templates as `$content` * @param string $template Template to use when sending email * @param string $layout Layout to use to enclose email body - * @return bool Success + * @return array Success */ public function send($content = null, $template = null, $layout = null) { $lib = new CakeEmail(); diff --git a/lib/Cake/Controller/Component/SessionComponent.php b/lib/Cake/Controller/Component/SessionComponent.php index 4057885ca74..b298e420dbb 100644 --- a/lib/Cake/Controller/Component/SessionComponent.php +++ b/lib/Cake/Controller/Component/SessionComponent.php @@ -153,7 +153,7 @@ public function setFlash($message, $element = 'default', $params = array(), $key * @return void */ public function renew() { - return CakeSession::renew(); + CakeSession::renew(); } /** @@ -176,7 +176,7 @@ public function valid() { * @link https://book.cakephp.org/2.0/en/core-libraries/components/sessions.html#SessionComponent::destroy */ public function destroy() { - return CakeSession::destroy(); + CakeSession::destroy(); } /** diff --git a/lib/Cake/Event/CakeEvent.php b/lib/Cake/Event/CakeEvent.php index 322fac19dc4..4a23756efc1 100644 --- a/lib/Cake/Event/CakeEvent.php +++ b/lib/Cake/Event/CakeEvent.php @@ -111,7 +111,7 @@ public function subject() { /** * Stops the event from being used anymore * - * @return void + * @return bool */ public function stopPropagation() { return $this->_stopped = true; diff --git a/lib/Cake/Model/Behavior/TranslateBehavior.php b/lib/Cake/Model/Behavior/TranslateBehavior.php index 740fcccadf9..1b9b51116a8 100644 --- a/lib/Cake/Model/Behavior/TranslateBehavior.php +++ b/lib/Cake/Model/Behavior/TranslateBehavior.php @@ -432,7 +432,7 @@ public function beforeSave(Model $Model, $options = array()) { * is disabled. * * @param Model $Model Model using this behavior. - * @return void + * @return bool true. */ protected function _setRuntimeData(Model $Model) { $locale = $this->_getLocale($Model); @@ -465,7 +465,7 @@ protected function _setRuntimeData(Model $Model) { * This solves issues with saveAssociated and validate = first. * * @param Model $Model Model using this behavior. - * @return void + * @return bool true. */ public function afterValidate(Model $Model) { $Model->data[$Model->alias] = array_merge( @@ -481,7 +481,7 @@ public function afterValidate(Model $Model) { * @param Model $Model Model the callback is called on * @param bool $created Whether or not the save created a record. * @param array $options Options passed from Model::save(). - * @return void + * @return bool true. */ public function afterSave(Model $Model, $created, $options = array()) { if (!isset($this->runtime[$Model->alias]['beforeValidate']) && !isset($this->runtime[$Model->alias]['beforeSave'])) { diff --git a/lib/Cake/Model/BehaviorCollection.php b/lib/Cake/Model/BehaviorCollection.php index d137cb22230..faae57064f4 100644 --- a/lib/Cake/Model/BehaviorCollection.php +++ b/lib/Cake/Model/BehaviorCollection.php @@ -73,7 +73,7 @@ public function init($modelName, $behaviors = array()) { * * @param string $behavior Behavior name. * @param array $config Configuration options. - * @return void + * @return bool true. * @deprecated 3.0.0 Will be removed in 3.0. Replaced with load(). */ public function attach($behavior, $config = array()) { @@ -97,7 +97,7 @@ public function attach($behavior, $config = array()) { * * @param string $behavior CamelCased name of the behavior to load * @param array $config Behavior configuration parameters - * @return bool True on success, false on failure + * @return bool True on success. * @throws MissingBehaviorException when a behavior could not be found. */ public function load($behavior, $config = array()) { diff --git a/lib/Cake/Model/Datasource/CakeSession.php b/lib/Cake/Model/Datasource/CakeSession.php index 38ab50e920a..a692ed74c8c 100644 --- a/lib/Cake/Model/Datasource/CakeSession.php +++ b/lib/Cake/Model/Datasource/CakeSession.php @@ -134,6 +134,13 @@ class CakeSession { */ protected static $_cookieName = null; +/** + * Whether this session is running under a CLI environment + * + * @var bool + */ + protected static $_isCLI = false; + /** * Pseudo constructor. * @@ -155,6 +162,7 @@ public static function init($base = null) { } static::$_initialized = true; + static::$_isCLI = (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg'); } /** @@ -596,14 +604,18 @@ protected static function _cookieName() { * @return bool */ protected static function _hasSession() { - return static::started() || isset($_COOKIE[static::_cookieName()]) || (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg'); + return static::started() + || !ini_get('session.use_cookies') + || isset($_COOKIE[static::_cookieName()]) + || static::$_isCLI + || (ini_get('session.use_trans_sid') && isset($_GET[session_name()])); } /** * Find the handler class and make sure it implements the correct interface. * * @param string $handler Handler name. - * @return void + * @return CakeSessionHandlerInterface * @throws CakeSessionException */ protected static function _getHandler($handler) { diff --git a/lib/Cake/Model/Datasource/Database/Sqlite.php b/lib/Cake/Model/Datasource/Database/Sqlite.php index 9a9e4c1c33f..2cf39c35690 100644 --- a/lib/Cake/Model/Datasource/Database/Sqlite.php +++ b/lib/Cake/Model/Datasource/Database/Sqlite.php @@ -212,7 +212,7 @@ public function describe($model) { * @param array $fields The fields to update. * @param array $values The values to set columns to. * @param mixed $conditions array of conditions to use. - * @return array + * @return bool */ public function update(Model $model, $fields = array(), $values = null, $conditions = null) { if (empty($values) && !empty($fields)) { diff --git a/lib/Cake/Model/Datasource/Database/Sqlserver.php b/lib/Cake/Model/Datasource/Database/Sqlserver.php index 03e9f909182..2e9c78fd706 100644 --- a/lib/Cake/Model/Datasource/Database/Sqlserver.php +++ b/lib/Cake/Model/Datasource/Database/Sqlserver.php @@ -411,7 +411,7 @@ public function limit($limit, $offset = null) { $rt = ' TOP'; } $rt .= sprintf(' %u', $limit); - if (is_int($offset) && $offset > 0) { + if ((is_int($offset) || ctype_digit($offset)) && $offset > 0) { $rt = sprintf(' OFFSET %u ROWS FETCH FIRST %u ROWS ONLY', $offset, $limit); } return $rt; diff --git a/lib/Cake/Model/ModelValidator.php b/lib/Cake/Model/ModelValidator.php index bcf6b9e4414..f968e2cfff5 100644 --- a/lib/Cake/Model/ModelValidator.php +++ b/lib/Cake/Model/ModelValidator.php @@ -234,7 +234,7 @@ public function validateMany(&$data, $options = array()) { * actually run validation rules over data, not just return the messages. * * @param string $options An optional array of custom options to be made available in the beforeValidate callback - * @return array Array of invalid fields + * @return array|bool Array of invalid fields * @triggers Model.afterValidate $model * @see ModelValidator::validates() */ diff --git a/lib/Cake/Network/Email/CakeEmail.php b/lib/Cake/Network/Email/CakeEmail.php index 5946e31af3b..bb4b47f6586 100644 --- a/lib/Cake/Network/Email/CakeEmail.php +++ b/lib/Cake/Network/Email/CakeEmail.php @@ -70,7 +70,7 @@ class CakeEmail { * * @var string */ - const EMAIL_PATTERN = '/^((?:[\p{L}0-9.!#$%&\'*+\/=?^_`{|}~-]+)*@[\p{L}0-9-.]+)$/ui'; + const EMAIL_PATTERN = '/^((?:[\p{L}0-9.!#$%&\'*+\/=?^_`{|}~-]+)*@[\p{L}0-9-_.]+)$/ui'; /** * Recipient of the email diff --git a/lib/Cake/Network/Http/HttpSocket.php b/lib/Cake/Network/Http/HttpSocket.php index 8aacde4ef53..1643bcbfd2c 100644 --- a/lib/Cake/Network/Http/HttpSocket.php +++ b/lib/Cake/Network/Http/HttpSocket.php @@ -760,7 +760,7 @@ protected function _buildUri($uri = array(), $uriTemplate = '%scheme://%user:%pa * * @param string|array $uri URI to parse * @param bool|array $base If true use default URI config, otherwise indexed array to set 'scheme', 'host', 'port', etc. - * @return array Parsed URI + * @return array|bool Parsed URI */ protected function _parseUri($uri = null, $base = array()) { $uriBase = array( diff --git a/lib/Cake/Network/Http/HttpSocketResponse.php b/lib/Cake/Network/Http/HttpSocketResponse.php index 3be02126a3b..69c6da725da 100644 --- a/lib/Cake/Network/Http/HttpSocketResponse.php +++ b/lib/Cake/Network/Http/HttpSocketResponse.php @@ -259,7 +259,7 @@ protected function _decodeChunkedBody($body) { * Parses an array based header. * * @param array $header Header as an indexed array (field => value) - * @return array Parsed header + * @return array|bool Parsed header */ protected function _parseHeader($header) { if (is_array($header)) { diff --git a/lib/Cake/Test/Case/Controller/Component/CookieComponentTest.php b/lib/Cake/Test/Case/Controller/Component/CookieComponentTest.php index c3fddddb379..1620842accc 100644 --- a/lib/Cake/Test/Case/Controller/Component/CookieComponentTest.php +++ b/lib/Cake/Test/Case/Controller/Component/CookieComponentTest.php @@ -429,6 +429,24 @@ public function testWriteMixedArray() { $this->assertEquals($expected, $result); } +/** + * Test that replacing scalar with array works. + * + * @return void + */ + public function testReplaceScalarWithArray() { + $this->Cookie->write('foo', 1); + $this->Cookie->write('foo.bar', 2); + + $data = $this->Cookie->read(); + $expected = array( + 'foo' => array( + 'bar' => 2 + ) + ); + $this->assertEquals($expected, $data); + } + /** * testReadingCookieValue * @@ -696,6 +714,20 @@ public function testReadEmpty() { $this->assertEquals(array(), $this->Cookie->read('Array')); } +/** + * Test reading empty key + * + * @return void + */ + public function testReadEmptyKey() { + $_COOKIE['CakeTestCookie'] = array( + '0' => '{"name":"value"}', + 'foo' => array('bar'), + ); + $this->assertEquals('value', $this->Cookie->read('0.name')); + $this->assertEquals('bar', $this->Cookie->read('foo.0')); + } + /** * test that no error is issued for non array data. * @@ -789,6 +821,89 @@ public function testDeleteChildrenNotExist() { $this->assertNull($this->Cookie->delete('Not.Found')); } +/** + * Test deleting deep child elements sends correct cookies. + * + * @return void + */ + public function testDeleteDeepChildren() { + $_COOKIE = array( + 'CakeTestCookie' => array( + 'foo' => $this->_encrypt(array( + 'bar' => array( + 'baz' => 'value', + ), + )), + ), + ); + + $this->Cookie->delete('foo.bar.baz'); + + $cookies = $this->Controller->response->cookie(); + $expected = array( + 'CakeTestCookie[foo]' => array( + 'name' => 'CakeTestCookie[foo]', + 'value' => '{"bar":[]}', + 'path' => '/', + 'domain' => '', + 'secure' => false, + 'httpOnly' => false + ), + ); + + $expires = Hash::combine($cookies, '{*}.name', '{*}.expire'); + $cookies = Hash::remove($cookies, '{*}.expire'); + $this->assertEquals($expected, $cookies); + + $this->assertWithinMargin($expires['CakeTestCookie[foo]'], time() + 10, 2); + } + +/** + * Test destroy works. + * + * @return void + */ + public function testDestroy() { + $_COOKIE = array( + 'CakeTestCookie' => array( + 'foo' => $this->_encrypt(array( + 'bar' => array( + 'baz' => 'value', + ), + )), + 'other' => 'value', + ), + ); + + $this->Cookie->destroy(); + + $cookies = $this->Controller->response->cookie(); + $expected = array( + 'CakeTestCookie[foo]' => array( + 'name' => 'CakeTestCookie[foo]', + 'value' => '', + 'path' => '/', + 'domain' => '', + 'secure' => false, + 'httpOnly' => false + ), + 'CakeTestCookie[other]' => array( + 'name' => 'CakeTestCookie[other]', + 'value' => '', + 'path' => '/', + 'domain' => '', + 'secure' => false, + 'httpOnly' => false + ), + ); + + $expires = Hash::combine($cookies, '{*}.name', '{*}.expire'); + $cookies = Hash::remove($cookies, '{*}.expire'); + $this->assertEquals($expected, $cookies); + $this->assertWithinMargin($expires['CakeTestCookie[foo]'], time() - 42000, 2); + $this->assertWithinMargin($expires['CakeTestCookie[other]'], time() - 42000, 2); + } + /** * Helper method for generating old style encoded cookie values. * diff --git a/lib/Cake/Test/Case/Model/Datasource/Database/SqlserverTest.php b/lib/Cake/Test/Case/Model/Datasource/Database/SqlserverTest.php index 18a78b78156..4013d215d6b 100644 --- a/lib/Cake/Test/Case/Model/Datasource/Database/SqlserverTest.php +++ b/lib/Cake/Test/Case/Model/Datasource/Database/SqlserverTest.php @@ -397,6 +397,13 @@ public function testDistinctWithLimit() { )); $result = $this->db->getLastQuery(); $this->assertRegExp('/^SELECT DISTINCT TOP 5/', $result); + + $this->db->read($this->model, array( + 'fields' => array('DISTINCT SqlserverTestModel.city', 'SqlserverTestModel.country'), + 'limit' => '5' + )); + $result = $this->db->getLastQuery(); + $this->assertRegExp('/^SELECT DISTINCT TOP 5/', $result); } /** diff --git a/lib/Cake/Test/Case/Network/Email/CakeEmailTest.php b/lib/Cake/Test/Case/Network/Email/CakeEmailTest.php index b84b35950c8..a0d15bec87d 100644 --- a/lib/Cake/Test/Case/Network/Email/CakeEmailTest.php +++ b/lib/Cake/Test/Case/Network/Email/CakeEmailTest.php @@ -275,6 +275,10 @@ public function testTo() { $expected = array('cake@cakephp.org' => 'CakePHP'); $this->assertSame($expected, $this->CakeEmail->to()); + $this->CakeEmail->to('cake@cake_php.org', 'CakePHPUnderscore'); + $expected = array('cake@cake_php.org' => 'CakePHPUnderscore'); + $this->assertSame($expected, $this->CakeEmail->to()); + $list = array( 'root@localhost' => 'root', 'bjørn@hammeröath.com' => 'Bjorn', diff --git a/lib/Cake/Test/Case/Utility/HashTest.php b/lib/Cake/Test/Case/Utility/HashTest.php index c4a43df9d65..fb5db2693c0 100644 --- a/lib/Cake/Test/Case/Utility/HashTest.php +++ b/lib/Cake/Test/Case/Utility/HashTest.php @@ -1356,7 +1356,7 @@ public function testSortNaturalIgnoreCase() { ); $this->assertEquals($expected, $result); - $result = Hash::sort($items, '{n}.Item.image', 'asc', array('type' => 'natural', 'ignoreCase' => true)); + $result = Hash::sort($items, '{n}.Item.image', 'asc', array('type' => 'NATURAL', 'ignoreCase' => true)); $expected = array( array('Item' => array('image' => 'img1.jpg')), array('Item' => array('image' => 'img2.jpg')), @@ -1529,6 +1529,58 @@ public function testSortRegularIgnoreCase() { $this->assertEquals($expected, $sorted); } +/** + * Test sorting on a nested key that is sometimes undefined. + * + * @return void + */ + public function testSortSparse() { + $data = array( + array( + 'id' => 1, + 'title' => 'element 1', + 'extra' => 1, + ), + array( + 'id' => 2, + 'title' => 'element 2', + 'extra' => 2, + ), + array( + 'id' => 3, + 'title' => 'element 3', + ), + array( + 'id' => 4, + 'title' => 'element 4', + 'extra' => 4, + ) + ); + $result = Hash::sort($data, '{n}.extra', 'desc', 'natural'); + $expected = array( + array( + 'id' => 4, + 'title' => 'element 4', + 'extra' => 4, + ), + array( + 'id' => 2, + 'title' => 'element 2', + 'extra' => 2, + ), + array( + 'id' => 1, + 'title' => 'element 1', + 'extra' => 1, + ), + array( + 'id' => 3, + 'title' => 'element 3', + ), + ); + $this->assertSame($expected, $result); + } + /** * Test insert() * @@ -1594,6 +1646,17 @@ public function testInsertMulti() { 4 => array('Item' => array('id' => 5, 'title' => 'fifth')), ); $this->assertEquals($expected, $result); + + $data[3]['testable'] = true; + $result = Hash::insert($data, '{n}[testable].Item[id=/\b2|\b4/].test', 2); + $expected = array( + 0 => array('Item' => array('id' => 1, 'title' => 'first')), + 1 => array('Item' => array('id' => 2, 'title' => 'second')), + 2 => array('Item' => array('id' => 3, 'title' => 'third')), + 3 => array('Item' => array('id' => 4, 'title' => 'fourth', 'test' => 2), 'testable' => true), + 4 => array('Item' => array('id' => 5, 'title' => 'fifth')), + ); + $this->assertEquals($expected, $result); } /** @@ -1686,6 +1749,43 @@ public function testRemove() { $this->assertEquals($expected, $result); $result = Hash::remove($array, '{n}.{n}.part'); $this->assertEquals($expected, $result); + + $array = array( + 'foo' => 'string', + ); + $expected = $array; + $result = Hash::remove($array, 'foo.bar'); + $this->assertEquals($expected, $result); + + $array = array( + 'foo' => 'string', + 'bar' => array( + 0 => 'a', + 1 => 'b', + ), + ); + $expected = array( + 'foo' => 'string', + 'bar' => array( + 1 => 'b', + ), + ); + $result = Hash::remove($array, '{s}.0'); + $this->assertEquals($expected, $result); + + $array = array( + 'foo' => array( + 0 => 'a', + 1 => 'b', + ), + ); + $expected = array( + 'foo' => array( + 1 => 'b', + ), + ); + $result = Hash::remove($array, 'foo[1=b].0'); + $this->assertEquals($expected, $result); } /** @@ -1721,6 +1821,17 @@ public function testRemoveMulti() { 4 => array('Item' => array('id' => 5, 'title' => 'fifth')), ); $this->assertEquals($expected, $result); + + $data[3]['testable'] = true; + $result = Hash::remove($data, '{n}[testable].Item[id=/\b2|\b4/].title'); + $expected = array( + 0 => array('Item' => array('id' => 1, 'title' => 'first')), + 1 => array('Item' => array('id' => 2, 'title' => 'second')), + 2 => array('Item' => array('id' => 3, 'title' => 'third')), + 3 => array('Item' => array('id' => 4), 'testable' => true), + 4 => array('Item' => array('id' => 5, 'title' => 'fifth')), + ); + $this->assertEquals($expected, $result); } /** diff --git a/lib/Cake/Test/Case/Utility/ValidationTest.php b/lib/Cake/Test/Case/Utility/ValidationTest.php index a4377b68540..de3b01e52eb 100644 --- a/lib/Cake/Test/Case/Utility/ValidationTest.php +++ b/lib/Cake/Test/Case/Utility/ValidationTest.php @@ -329,14 +329,14 @@ public function testCc() { $this->assertTrue(Validation::cc('214981579370225', array('enroute'))); $this->assertTrue(Validation::cc('201447595859877', array('enroute'))); //JCB 15 digit - $this->assertTrue(Validation::cc('210034762247893', array('jcb'))); + $this->assertTrue(Validation::cc('213134762247898', array('jcb'))); $this->assertTrue(Validation::cc('180078671678892', array('jcb'))); $this->assertTrue(Validation::cc('180010559353736', array('jcb'))); - $this->assertTrue(Validation::cc('210095474464258', array('jcb'))); - $this->assertTrue(Validation::cc('210006675562188', array('jcb'))); - $this->assertTrue(Validation::cc('210063299662662', array('jcb'))); + $this->assertTrue(Validation::cc('213195474464253', array('jcb'))); + $this->assertTrue(Validation::cc('213106675562183', array('jcb'))); + $this->assertTrue(Validation::cc('213163299662667', array('jcb'))); $this->assertTrue(Validation::cc('180032506857825', array('jcb'))); - $this->assertTrue(Validation::cc('210057919192738', array('jcb'))); + $this->assertTrue(Validation::cc('213157919192733', array('jcb'))); $this->assertTrue(Validation::cc('180031358949367', array('jcb'))); $this->assertTrue(Validation::cc('180033802147846', array('jcb'))); //JCB 16 digit @@ -706,7 +706,7 @@ public function testLuhn() { //enRoute $this->assertTrue(Validation::luhn('201496944158937', true)); //JCB 15 digit - $this->assertTrue(Validation::luhn('210034762247893', true)); + $this->assertTrue(Validation::luhn('213134762247898', true)); //JCB 16 digit $this->assertTrue(Validation::luhn('3096806857839939', true)); //Maestro (debit card) @@ -811,7 +811,7 @@ public function testAllCc() { //enRoute $this->assertTrue(Validation::cc('201496944158937', 'all')); //JCB 15 digit - $this->assertTrue(Validation::cc('210034762247893', 'all')); + $this->assertTrue(Validation::cc('213134762247898', 'all')); //JCB 16 digit $this->assertTrue(Validation::cc('3096806857839939', 'all')); //Maestro (debit card) @@ -861,7 +861,7 @@ public function testAllCcDeep() { //enRoute $this->assertTrue(Validation::cc('201496944158937', 'all', true)); //JCB 15 digit - $this->assertTrue(Validation::cc('210034762247893', 'all', true)); + $this->assertTrue(Validation::cc('213134762247898', 'all', true)); //JCB 16 digit $this->assertTrue(Validation::cc('3096806857839939', 'all', true)); //Maestro (debit card) diff --git a/lib/Cake/Utility/Folder.php b/lib/Cake/Utility/Folder.php index 2ee5ad30b18..62fef677a16 100644 --- a/lib/Cake/Utility/Folder.php +++ b/lib/Cake/Utility/Folder.php @@ -323,7 +323,7 @@ public static function isAbsolute($path) { * Returns true if given $path is a registered stream wrapper. * * @param string $path Path to check - * @return boo true If path is registered stream wrapper. + * @return bool true If path is registered stream wrapper. */ public static function isRegisteredStreamWrapper($path) { if (preg_match('/^[A-Z]+(?=:\/\/)/i', $path, $matches) && diff --git a/lib/Cake/Utility/Hash.php b/lib/Cake/Utility/Hash.php index 344d7bca196..08dd2700a52 100644 --- a/lib/Cake/Utility/Hash.php +++ b/lib/Cake/Utility/Hash.php @@ -91,8 +91,8 @@ public static function get(array $data, $path, $default = null) { * * - `1.User.name` Get the name of the user at index 1. * - `{n}.User.name` Get the name of every user in the set of users. - * - `{n}.User[id]` Get the name of every user with an id key. - * - `{n}.User[id>=2]` Get the name of every user with an id key greater than or equal to 2. + * - `{n}.User[id].name` Get the name of every user with an id key. + * - `{n}.User[id>=2].name` Get the name of every user with an id key greater than or equal to 2. * - `{n}.User[username=/^paul/]` Get User elements with username matching `^paul`. * * @param array $data The data to extract from. @@ -149,6 +149,7 @@ public static function extract(array $data, $path) { } return $context[$_key]; } + /** * Split token conditions * @@ -274,12 +275,10 @@ public static function insert(array $data, $path, $values = null) { foreach ($data as $k => $v) { if (static::_matchToken($k, $token)) { - if ($conditions && static::_matches($v, $conditions)) { - $data[$k] = array_merge($v, $values); - continue; - } - if (!$conditions) { - $data[$k] = static::insert($v, $nextPath, $values); + if (!$conditions || static::_matches($v, $conditions)) { + $data[$k] = $nextPath + ? static::insert($v, $nextPath, $values) + : array_merge($v, (array)$values); } } } @@ -301,9 +300,6 @@ protected static function _simpleOp($op, $data, $path, $values = null) { $count = count($path); $last = $count - 1; foreach ($path as $i => $key) { - if ((is_numeric($key) && intval($key) > 0 || $key === '0') && strpos($key, '0') !== 0) { - $key = (int)$key; - } if ($op === 'insert') { if ($i === $last) { $_list[$key] = $values; @@ -318,7 +314,9 @@ protected static function _simpleOp($op, $data, $path, $values = null) { } } elseif ($op === 'remove') { if ($i === $last) { - unset($_list[$key]); + if (is_array($_list)) { + unset($_list[$key]); + } return $data; } if (!isset($_list[$key])) { @@ -358,15 +356,21 @@ public static function remove(array $data, $path) { foreach ($data as $k => $v) { $match = static::_matchToken($k, $token); if ($match && is_array($v)) { - if ($conditions && static::_matches($v, $conditions)) { - unset($data[$k]); - continue; + if ($conditions) { + if (static::_matches($v, $conditions)) { + if ($nextPath !== '') { + $data[$k] = static::remove($v, $nextPath); + } else { + unset($data[$k]); + } + } + } else { + $data[$k] = static::remove($v, $nextPath); } - $data[$k] = static::remove($v, $nextPath); if (empty($data[$k])) { unset($data[$k]); } - } elseif ($match && empty($nextPath)) { + } elseif ($match && $nextPath === '') { unset($data[$k]); } } @@ -454,7 +458,7 @@ public static function combine(array $data, $keyPath, $valuePath = null, $groupP * The `$format` string can use any format options that `vsprintf()` and `sprintf()` do. * * @param array $data Source array from which to extract the data - * @param string $paths An array containing one or more Hash::extract()-style key paths + * @param array $paths An array containing one or more Hash::extract()-style key paths * @param string $format Format string into which values will be inserted, see sprintf() * @return array An array of strings extracted from `$path` and formatted with `$format` * @link https://book.cakephp.org/2.0/en/core-utility-libraries/hash.html#Hash::format @@ -729,7 +733,7 @@ public static function numeric(array $data) { * Counts the dimensions of an array. * Only considers the dimension of the first element in the array. * - * If you have an un-even or heterogenous array, consider using Hash::maxDimensions() + * If you have an un-even or heterogeneous array, consider using Hash::maxDimensions() * to get the dimensions of the array. * * @param array $data Array to count dimensions on @@ -809,11 +813,15 @@ public static function reduce(array $data, $path, $function) { * You can easily count the results of an extract using apply(). * For example to count the comments on an Article: * - * `$count = Hash::apply($data, 'Article.Comment.{n}', 'count');` + * ``` + * $count = Hash::apply($data, 'Article.Comment.{n}', 'count'); + * ``` * * You could also use a function like `array_sum` to sum the results. * - * `$total = Hash::apply($data, '{n}.Item.price', 'array_sum');` + * ``` + * $total = Hash::apply($data, '{n}.Item.price', 'array_sum'); + * ``` * * @param array $data The data to reduce. * @param string $path The path to extract from $data. @@ -833,7 +841,7 @@ public static function apply(array $data, $path, $function) { * - `asc` Sort ascending. * - `desc` Sort descending. * - * ## Sort types + * ### Sort types * * - `regular` For regular sorting (don't change types) * - `numeric` Compare values numerically @@ -868,12 +876,18 @@ public static function sort(array $data, $path, $dir = 'asc', $type = 'regular') $data = array_values($data); } $sortValues = static::extract($data, $path); - $sortCount = count($sortValues); $dataCount = count($data); // Make sortValues match the data length, as some keys could be missing // the sorted value path. - if ($sortCount < $dataCount) { + $missingData = count($sortValues) < $dataCount; + if ($missingData && $numeric) { + // Get the path without the leading '{n}.' + $itemPath = substr($path, 4); + foreach ($data as $key => $value) { + $sortValues[$key] = static::get($value, $itemPath); + } + } elseif ($missingData) { $sortValues = array_pad($sortValues, $dataCount, null); } $result = static::_squash($sortValues); @@ -888,9 +902,8 @@ public static function sort(array $data, $path, $dir = 'asc', $type = 'regular') $type += array('ignoreCase' => false, 'type' => 'regular'); $ignoreCase = $type['ignoreCase']; $type = $type['type']; - } else { - $type = strtolower($type); } + $type = strtolower($type); if ($type === 'natural' && version_compare(PHP_VERSION, '5.4.0', '<')) { $type = 'regular'; diff --git a/lib/Cake/Utility/Sanitize.php b/lib/Cake/Utility/Sanitize.php index 450d061f6ac..0f8b5e073fe 100644 --- a/lib/Cake/Utility/Sanitize.php +++ b/lib/Cake/Utility/Sanitize.php @@ -34,9 +34,9 @@ class Sanitize { /** * Removes any non-alphanumeric characters. * - * @param string $string String to sanitize + * @param string|array $string String to sanitize * @param array $allowed An array of additional characters that are not to be removed. - * @return string Sanitized string + * @return string|array Sanitized string */ public static function paranoid($string, $allowed = array()) { $allow = null; diff --git a/lib/Cake/Utility/Validation.php b/lib/Cake/Utility/Validation.php index 0f340a012ba..f7d27cc9ffb 100644 --- a/lib/Cake/Utility/Validation.php +++ b/lib/Cake/Utility/Validation.php @@ -175,7 +175,7 @@ public static function cc($check, $type = 'fast', $deep = false, $regex = null) 'disc' => '/^(?:6011|650\\d)\\d{12}$/', 'electron' => '/^(?:417500|4917\\d{2}|4913\\d{2})\\d{10}$/', 'enroute' => '/^2(?:014|149)\\d{11}$/', - 'jcb' => '/^(3\\d{4}|2100|1800)\\d{11}$/', + 'jcb' => '/^(3\\d{4}|2131|1800)\\d{11}$/', 'maestro' => '/^(?:5020|6\\d{3})\\d{12}$/', 'mc' => '/^(5[1-5]\\d{14})|(2(?:22[1-9]|2[3-9][0-9]|[3-6][0-9]{2}|7[0-1][0-9]|720)\\d{12})$/', 'solo' => '/^(6334[5-9][0-9]|6767[0-9]{2})\\d{10}(\\d{2,3})?$/', diff --git a/lib/Cake/VERSION.txt b/lib/Cake/VERSION.txt index 2e9e56b553d..17a775eda56 100644 --- a/lib/Cake/VERSION.txt +++ b/lib/Cake/VERSION.txt @@ -17,4 +17,4 @@ // @license https://opensource.org/licenses/mit-license.php MIT License // +--------------------------------------------------------------------------------------------+ // //////////////////////////////////////////////////////////////////////////////////////////////////// -2.10.3 +2.10.4