From 02c77da81209693b340c23df3fabfc59c6bc6bcd Mon Sep 17 00:00:00 2001 From: Bizley Date: Tue, 22 Dec 2020 20:35:51 +0100 Subject: [PATCH] Fix #18395: Fix regression in `yii\helpers\BaseArrayHelper::filter()` (allowing filtering arrays with numeric keys) --- framework/CHANGELOG.md | 1 + framework/helpers/BaseArrayHelper.php | 10 ++-- tests/framework/helpers/ArrayHelperTest.php | 71 +++++++++++++++++++---------- 3 files changed, 54 insertions(+), 28 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 31cf9dd..a2915f1 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -17,6 +17,7 @@ Yii Framework 2 Change Log - Bug #16492: Fix eager loading Active Record relations when relation key is a subject to a type-casting behavior (bizley) - Bug #18435: Fix ensuring Active Record relation links' keys to be strings (bizley) - Bug #18435: Change the check order whether an object is an implementation of `Arrayable` or `JsonSerializable` in `\yii\base\ArrayableTrait::toArray()` and `\yii\rest\Serializer::serialize()` (spell6inder) +- Bug #18395: Fix regression in `yii\helpers\BaseArrayHelper::filter()` (allowing filtering arrays with numeric keys) (bizley) 2.0.39.3 November 23, 2020 diff --git a/framework/helpers/BaseArrayHelper.php b/framework/helpers/BaseArrayHelper.php index e30d0c0..0f3d368 100644 --- a/framework/helpers/BaseArrayHelper.php +++ b/framework/helpers/BaseArrayHelper.php @@ -944,13 +944,17 @@ class BaseArrayHelper $excludeFilters = []; foreach ($filters as $filter) { - if ($filter[0] === '!') { + if (!is_string($filter) && !is_int($filter)) { + continue; + } + + if (is_string($filter) && strpos($filter, '!') === 0) { $excludeFilters[] = substr($filter, 1); continue; } $nodeValue = $array; //set $array as root node - $keys = explode('.', $filter); + $keys = explode('.', (string) $filter); foreach ($keys as $key) { if (!array_key_exists($key, $nodeValue)) { continue 2; //Jump to next filter @@ -971,7 +975,7 @@ class BaseArrayHelper foreach ($excludeFilters as $filter) { $excludeNode = &$result; - $keys = explode('.', $filter); + $keys = explode('.', (string) $filter); $numNestedKeys = count($keys) - 1; foreach ($keys as $i => $key) { if (!array_key_exists($key, $excludeNode)) { diff --git a/tests/framework/helpers/ArrayHelperTest.php b/tests/framework/helpers/ArrayHelperTest.php index 8ef84ff..a5f8aae 100644 --- a/tests/framework/helpers/ArrayHelperTest.php +++ b/tests/framework/helpers/ArrayHelperTest.php @@ -1402,7 +1402,7 @@ class ArrayHelperTest extends TestCase ]; //Include tests - $this->assertEquals(ArrayHelper::filter($array, ['A']), [ + $this->assertEquals([ 'A' => [ 'B' => 1, 'C' => 2, @@ -1411,28 +1411,28 @@ class ArrayHelperTest extends TestCase 'F' => 2, ], ], - ]); - $this->assertEquals(ArrayHelper::filter($array, ['A.B']), [ + ], ArrayHelper::filter($array, ['A'])); + $this->assertEquals([ 'A' => [ 'B' => 1, ], - ]); - $this->assertEquals(ArrayHelper::filter($array, ['A.D']), [ + ], ArrayHelper::filter($array, ['A.B'])); + $this->assertEquals([ 'A' => [ 'D' => [ 'E' => 1, 'F' => 2, ], ], - ]); - $this->assertEquals(ArrayHelper::filter($array, ['A.D.E']), [ + ], ArrayHelper::filter($array, ['A.D'])); + $this->assertEquals([ 'A' => [ 'D' => [ 'E' => 1, ], ], - ]); - $this->assertEquals(ArrayHelper::filter($array, ['A', 'G']), [ + ], ArrayHelper::filter($array, ['A.D.E'])); + $this->assertEquals([ 'A' => [ 'B' => 1, 'C' => 2, @@ -1442,18 +1442,18 @@ class ArrayHelperTest extends TestCase ], ], 'G' => 1, - ]); - $this->assertEquals(ArrayHelper::filter($array, ['A.D.E', 'G']), [ + ], ArrayHelper::filter($array, ['A', 'G'])); + $this->assertEquals([ 'A' => [ 'D' => [ 'E' => 1, ], ], 'G' => 1, - ]); + ], ArrayHelper::filter($array, ['A.D.E', 'G'])); //Exclude (combined with include) tests - $this->assertEquals(ArrayHelper::filter($array, ['A', '!A.B']), [ + $this->assertEquals([ 'A' => [ 'C' => 2, 'D' => [ @@ -1461,8 +1461,8 @@ class ArrayHelperTest extends TestCase 'F' => 2, ], ], - ]); - $this->assertEquals(ArrayHelper::filter($array, ['!A.B', 'A']), [ + ], ArrayHelper::filter($array, ['A', '!A.B'])); + $this->assertEquals([ 'A' => [ 'C' => 2, 'D' => [ @@ -1470,8 +1470,8 @@ class ArrayHelperTest extends TestCase 'F' => 2, ], ], - ]); - $this->assertEquals(ArrayHelper::filter($array, ['A', '!A.D.E']), [ + ], ArrayHelper::filter($array, ['!A.B', 'A'])); + $this->assertEquals([ 'A' => [ 'B' => 1, 'C' => 2, @@ -1479,19 +1479,19 @@ class ArrayHelperTest extends TestCase 'F' => 2, ], ], - ]); - $this->assertEquals(ArrayHelper::filter($array, ['A', '!A.D']), [ + ], ArrayHelper::filter($array, ['A', '!A.D.E'])); + $this->assertEquals([ 'A' => [ 'B' => 1, 'C' => 2, ], - ]); + ], ArrayHelper::filter($array, ['A', '!A.D'])); //Non existing keys tests - $this->assertEquals(ArrayHelper::filter($array, ['X']), []); - $this->assertEquals(ArrayHelper::filter($array, ['X.Y']), []); - $this->assertEquals(ArrayHelper::filter($array, ['X.Y.Z']), []); - $this->assertEquals(ArrayHelper::filter($array, ['A.X']), []); + $this->assertEquals([], ArrayHelper::filter($array, ['X'])); + $this->assertEquals([], ArrayHelper::filter($array, ['X.Y'])); + $this->assertEquals([], ArrayHelper::filter($array, ['X.Y.Z'])); + $this->assertEquals([], ArrayHelper::filter($array, ['A.X'])); //Values that evaluate to `true` with `empty()` tests $tmp = [ @@ -1502,7 +1502,28 @@ class ArrayHelperTest extends TestCase 'e' => true, ]; - $this->assertEquals(ArrayHelper::filter($tmp, array_keys($tmp)), $tmp); + $this->assertEquals($tmp, ArrayHelper::filter($tmp, array_keys($tmp))); + } + + /** + * @see https://github.com/yiisoft/yii2/issues/18395 + */ + public function testFilterForIntegerKeys() + { + $array = ['a', 'b', ['c', 'd']]; + + // to make sure order is changed test it encoded + $this->assertEquals('{"1":"b","0":"a"}', json_encode(ArrayHelper::filter($array, [1, 0]))); + $this->assertEquals([2 => ['c']], ArrayHelper::filter($array, ['2.0'])); + $this->assertEquals([2 => [1 => 'd']], ArrayHelper::filter($array, [2, '!2.0'])); + } + + public function testFilterWithInvalidValues() + { + $array = ['a' => 'b']; + + $this->assertEquals([], ArrayHelper::filter($array, [new \stdClass()])); + $this->assertEquals([], ArrayHelper::filter($array, [['a']])); } /**