diff --git a/src/Query/GroupOption.php b/src/Query/GroupOption.php index d51a20c..ef4ce30 100644 --- a/src/Query/GroupOption.php +++ b/src/Query/GroupOption.php @@ -92,6 +92,10 @@ class GroupOption implements QueryOptionInterface, QueryOptionTreeItemInterface $this->{$prop}[$option->id()] = $option; return TRUE; } + // Direct child. + elseif (isset($this->childGroups[$target_id])) { + return $this->childGroups[$target_id]->insert($target_id, $option); + } elseif ($proper_child = array_reduce($this->childGroups, $find_proper_id, NULL)) { return $this->childGroups[$proper_child]->insert($target_id, $option); } @@ -115,15 +119,15 @@ class GroupOption implements QueryOptionInterface, QueryOptionTreeItemInterface } if (!empty($this->childOptions)) { - $group = array_reduce($this->childOptions, function ($group, $child) { - return $child->apply($group); - }, $group); + foreach ($this->childOptions as $child) { + $child->apply($group); + } } if (!empty($this->childGroups)) { - $group = array_reduce($this->childGroups, function ($group, $child) { - return $child->apply($group); - }, $group); + foreach ($this->childGroups as $child) { + $child->apply($group); + } } return $query->condition($group); @@ -143,11 +147,16 @@ class GroupOption implements QueryOptionInterface, QueryOptionTreeItemInterface return TRUE; } - // If any child GroupOptions or their children have the id return TRUE. - return array_reduce($this->groupOptions, function ($has_child, $group) use ($id) { - // If we already know that we have the child, skip evaluation and return. - return $has_child || $group->id() == $id || $group->hasChild($id); - }, FALSE); + if (is_array($this->childGroups)) { + // If any child Groups or their children have the id return TRUE. + return array_reduce($this->childGroups, function ($has_child, $group) use ($id) { + // If we already know that we have the child, skip evaluation and return. + return $has_child || $group->id() == $id || $group->hasChild($id); + }, FALSE); + } + else { + return FALSE; + } } } diff --git a/src/Query/QueryOptionTreeItemInterface.php b/src/Query/QueryOptionTreeItemInterface.php index 102921c..632f8af 100644 --- a/src/Query/QueryOptionTreeItemInterface.php +++ b/src/Query/QueryOptionTreeItemInterface.php @@ -21,7 +21,7 @@ interface QueryOptionTreeItemInterface { public function insert($target_id, QueryOptionInterface $option); /** - * Returns whether or the given id is a (grand)child of the object. + * Returns whether the given id is a (grand)child of the object. */ public function hasChild($id); diff --git a/tests/src/Functional/JsonApiFunctionalTest.php b/tests/src/Functional/JsonApiFunctionalTest.php index 366ab7e..9a6a0e5 100644 --- a/tests/src/Functional/JsonApiFunctionalTest.php +++ b/tests/src/Functional/JsonApiFunctionalTest.php @@ -259,6 +259,122 @@ class JsonApiFunctionalTest extends JsonApiFunctionalTestBase { // With the 'Accept' header we can know we want the 404 error formatted as // JSON API. $this->assertSession()->responseHeaderContains('Content-Type', 'application/vnd.api+json'); + + // Test documentation filtering examples. + // 1. Only get published nodes. + $filter = [ + 'status-filter' => [ + 'condition' => [ + 'path' => 'status', + 'value' => 1, + ], + ], + ]; + $collection_output = Json::decode($this->drupalGet('/jsonapi/node/article', [ + 'query' => ['filter' => $filter], + ])); + $this->assertSession()->statusCodeEquals(200); + $this->assertGreaterThanOrEqual(OffsetPage::$maxSize, count($collection_output['data'])); + // 2. Nested Filters: Get nodes created by user admin. + $filter = [ + 'name-filter' => [ + 'condition' => [ + 'path' => 'uid.name', + 'value' => $this->user->getAccountName(), + ], + ], + ]; + $collection_output = Json::decode($this->drupalGet('/jsonapi/node/article', [ + 'query' => ['filter' => $filter], + ])); + $this->assertSession()->statusCodeEquals(200); + $this->assertGreaterThanOrEqual(OffsetPage::$maxSize, count($collection_output['data'])); + // 3. Filtering with arrays: Get nodes created by users [admin, john]. + $filter = [ + 'name-filter' => [ + 'condition' => [ + 'path' => 'uid.name', + 'operator' => 'IN', + 'value' => [ + $this->user->getAccountName(), + $this->getRandomGenerator()->name(), + ], + ], + ], + ]; + $collection_output = Json::decode($this->drupalGet('/jsonapi/node/article', [ + 'query' => ['filter' => $filter], + ])); + $this->assertSession()->statusCodeEquals(200); + $this->assertGreaterThanOrEqual(OffsetPage::$maxSize, count($collection_output['data'])); + // 4. Grouping filters: Get nodes that are published and create by admin. + $filter = [ + 'and-group' => [ + 'group' => [ + 'conjunction' => 'AND', + ], + ], + 'name-filter' => [ + 'condition' => [ + 'path' => 'uid.name', + 'value' => $this->user->getAccountName(), + 'memberOf' => 'and-group', + ], + ], + 'status-filter' => [ + 'condition' => [ + 'path' => 'status', + 'value' => 1, + 'memberOf' => 'and-group', + ], + ], + ]; + $collection_output = Json::decode($this->drupalGet('/jsonapi/node/article', [ + 'query' => ['filter' => $filter], + ])); + $this->assertSession()->statusCodeEquals(200); + $this->assertGreaterThanOrEqual(OffsetPage::$maxSize, count($collection_output['data'])); + // 5. Grouping grouped filters: Get nodes that are promoted or sticky and + // created by admin. + $filter = [ + 'and-group' => [ + 'group' => [ + 'conjunction' => 'AND', + ], + ], + 'or-group' => [ + 'group' => [ + 'conjunction' => 'OR', + 'memberOf' => 'and-group', + ], + ], + 'admin-filter' => [ + 'condition' => [ + 'path' => 'uid.name', + 'value' => $this->user->getAccountName(), + 'memberOf' => 'and-group', + ], + ], + 'sticky-filter' => [ + 'condition' => [ + 'path' => 'sticky', + 'value' => 1, + 'memberOf' => 'or-group', + ], + ], + 'promote-filter' => [ + 'condition' => [ + 'path' => 'promote', + 'value' => 0, + 'memberOf' => 'or-group', + ], + ], + ]; + $collection_output = Json::decode($this->drupalGet('/jsonapi/node/article', [ + 'query' => ['filter' => $filter], + ])); + $this->assertSession()->statusCodeEquals(200); + $this->assertEquals(0, count($collection_output['data'])); } /**