From a0014e7a303067bb9c36d438de5a70fe819d22a7 Mon Sep 17 00:00:00 2001 From: Chen Cohen Date: Fri, 23 Aug 2013 13:27:06 +0300 Subject: [PATCH] Ticket 4011 - Adding matchers support for Hash::remove() and Hash::insert() --- .../Case/Model/Datasource/CakeSessionTest.php | 4 +- lib/Cake/Test/Case/Utility/HashTest.php | 50 +++++++++++++++ lib/Cake/Utility/Hash.php | 63 +++++++++++++++---- 3 files changed, 104 insertions(+), 13 deletions(-) diff --git a/lib/Cake/Test/Case/Model/Datasource/CakeSessionTest.php b/lib/Cake/Test/Case/Model/Datasource/CakeSessionTest.php index b034c2d5466..b6d0a8c7dff 100644 --- a/lib/Cake/Test/Case/Model/Datasource/CakeSessionTest.php +++ b/lib/Cake/Test/Case/Model/Datasource/CakeSessionTest.php @@ -443,10 +443,10 @@ public function testCheckEmpty() { public function testKeyExploit() { $key = "a'] = 1; phpinfo(); \$_SESSION['a"; $result = TestCakeSession::write($key, 'haxored'); - $this->assertTrue($result); + $this->assertFalse($result); $result = TestCakeSession::read($key); - $this->assertEquals('haxored', $result); + $this->assertNull($result); } /** diff --git a/lib/Cake/Test/Case/Utility/HashTest.php b/lib/Cake/Test/Case/Utility/HashTest.php index 67adfc9db17..7f28223e663 100644 --- a/lib/Cake/Test/Case/Utility/HashTest.php +++ b/lib/Cake/Test/Case/Utility/HashTest.php @@ -1297,6 +1297,23 @@ public function testInsertMulti() { $result = Hash::insert($data, '{n}.Comment.{n}.insert', 'value'); $this->assertEquals('value', $result[0]['Comment'][0]['insert']); $this->assertEquals('value', $result[0]['Comment'][1]['insert']); + + $data = 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')), + 4 => array('Item' => array('id' => 5, 'title' => 'fifth')), + ); + $result = Hash::insert($data, '{n}.Item[id=/\b2|\b4/]', array('test' => 2)); + $expected = array( + 0 => array('Item' => array('id' => 1, 'title' => 'first')), + 1 => array('Item' => array('id' => 2, 'title' => 'second', 'test' => 2)), + 2 => array('Item' => array('id' => 3, 'title' => 'third')), + 3 => array('Item' => array('id' => 4, 'title' => 'fourth', 'test' => 2)), + 4 => array('Item' => array('id' => 5, 'title' => 'fifth')), + ); + $this->assertEquals($expected, $result); } /** @@ -1360,6 +1377,23 @@ public function testRemove() { $result = Hash::remove($a, 'pages.2.vars'); $expected = $a; $this->assertEquals($expected, $result); + + $a = array( + 0 => array( + 'name' => 'pages' + ), + 1 => array( + 'name' => 'files' + ) + ); + + $result = Hash::remove($a, '{n}[name=files]'); + $expected = array( + 0 => array( + 'name' => 'pages' + ) + ); + $this->assertEquals($expected, $result); } /** @@ -1379,6 +1413,22 @@ public function testRemoveMulti() { $this->assertFalse(isset($result[0]['Article']['user_id'])); $this->assertFalse(isset($result[0]['Article']['title'])); $this->assertFalse(isset($result[0]['Article']['body'])); + + $data = 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')), + 4 => array('Item' => array('id' => 5, 'title' => 'fifth')), + ); + + $result = Hash::remove($data, '{n}.Item[id=/\b2|\b4/]'); + $expected = array( + 0 => array('Item' => array('id' => 1, 'title' => 'first')), + 2 => array('Item' => array('id' => 3, 'title' => 'third')), + 4 => array('Item' => array('id' => 5, 'title' => 'fifth')), + ); + $this->assertEquals($result, $expected); } /** diff --git a/lib/Cake/Utility/Hash.php b/lib/Cake/Utility/Hash.php index 80d8f291a2a..ea514975ec7 100644 --- a/lib/Cake/Utility/Hash.php +++ b/lib/Cake/Utility/Hash.php @@ -109,12 +109,7 @@ public static function extract(array $data, $path) { foreach ($tokens as $token) { $next = array(); - $conditions = false; - $position = strpos($token, '['); - if ($position !== false) { - $conditions = substr($token, $position); - $token = substr($token, 0, $position); - } + list($token, $conditions) = self::_splitConditions($token); foreach ($context[$_key] as $item) { foreach ((array)$item as $k => $v) { @@ -139,6 +134,22 @@ public static function extract(array $data, $path) { } return $context[$_key]; } +/** + * Split token conditions + * + * @param string $token the token being splitted. + * @return array array(token, conditions) with token splitted + */ + protected static function _splitConditions($token) { + $conditions = false; + $position = strpos($token, '['); + if ($position !== false) { + $conditions = substr($token, $position); + $token = substr($token, 0, $position); + } + + return array($token, $conditions); + } /** * Check a key against a token. @@ -222,16 +233,31 @@ protected static function _matches(array $data, $selector) { * @return array The data with $values inserted. */ public static function insert(array $data, $path, $values = null) { - $tokens = explode('.', $path); - if (strpos($path, '{') === false) { + if (strpos($path, '[') === false) { + $tokens = explode('.', $path); + } else { + $tokens = String::tokenize($path, '.', '[', ']'); + } + + if (strpos($path, '{') === false && strpos($path, '[') === false) { return self::_simpleOp('insert', $data, $tokens, $values); } $token = array_shift($tokens); $nextPath = implode('.', $tokens); + + list($token, $conditions) = self::_splitConditions($token); + foreach ($data as $k => $v) { if (self::_matchToken($k, $token)) { - $data[$k] = self::insert($v, $nextPath, $values); + if ($conditions) { + if (self::_matches($v, $conditions)) { + $data[$k] = array_merge($v, $values); + continue; + } + } else { + $data[$k] = self::insert($v, $nextPath, $values); + } } } return $data; @@ -290,17 +316,32 @@ protected static function _simpleOp($op, $data, $path, $values = null) { * @return array The modified array. */ public static function remove(array $data, $path) { - $tokens = explode('.', $path); - if (strpos($path, '{') === false) { + if (strpos($path, '[') === false) { + $tokens = explode('.', $path); + } else { + $tokens = String::tokenize($path, '.', '[', ']'); + } + + if (strpos($path, '{') === false && strpos($path, '[') === false) { return self::_simpleOp('remove', $data, $tokens); } $token = array_shift($tokens); $nextPath = implode('.', $tokens); + + list($token, $conditions) = self::_splitConditions($token); + foreach ($data as $k => $v) { $match = self::_matchToken($k, $token); if ($match && is_array($v)) { + if ($conditions && self::_matches($v, $conditions)) { + unset($data[$k]); + continue; + } $data[$k] = self::remove($v, $nextPath); + if (empty($data[$k])) { + unset($data[$k]); + } } elseif ($match) { unset($data[$k]); }