From 791a9cabc9825e8ce99822ee8e4998009c245bbb Mon Sep 17 00:00:00 2001 From: colemanw Date: Fri, 7 Aug 2026 13:58:45 -0400 Subject: [PATCH] SearchKit - When filtering a hierarchical entity, don't exclude children. Fixes an issue with e.g. the "Manage Groups" display where a search filter will only return parent groups. --- .../HierarchicalEntitySubscriber.php | 6 ++- .../api/v4/SearchDisplay/SearchRunTest.php | 43 +++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/Civi/Api4/Event/Subscriber/HierarchicalEntitySubscriber.php b/Civi/Api4/Event/Subscriber/HierarchicalEntitySubscriber.php index 35d8684c6cf9..385e232f2b5a 100644 --- a/Civi/Api4/Event/Subscriber/HierarchicalEntitySubscriber.php +++ b/Civi/Api4/Event/Subscriber/HierarchicalEntitySubscriber.php @@ -107,8 +107,10 @@ public function onApiRespond(RespondEvent $event): void { // Filter out children, maintaining sorted order $children = []; if (!$needsExtraDfkQuery) { - $records = array_filter($records, function($record) use ($parentName, $dfkControlName, $dfkValue, &$children) { - $isChild = !empty($record[$parentName]); + $allRecordIds = array_column($records, $idName); + $records = array_filter($records, function($record) use ($parentName, $dfkControlName, $dfkValue, $allRecordIds, &$children) { + // To avoid orphans disappearing, only designate child if parent can be found + $isChild = !empty($record[$parentName]) && array_intersect((array) $record[$parentName], $allRecordIds); if ($dfkValue) { $isChild = $record[$dfkControlName] == $dfkValue; } diff --git a/ext/search_kit/tests/phpunit/api/v4/SearchDisplay/SearchRunTest.php b/ext/search_kit/tests/phpunit/api/v4/SearchDisplay/SearchRunTest.php index 60bdf4c031c0..4eb097555849 100644 --- a/ext/search_kit/tests/phpunit/api/v4/SearchDisplay/SearchRunTest.php +++ b/ext/search_kit/tests/phpunit/api/v4/SearchDisplay/SearchRunTest.php @@ -4104,4 +4104,47 @@ public function testGroupFirstDateFilter(): void { $this->assertNotContains($contacts[0]['id'], $returnedContactIds, 'Alpha (February 2023) should be excluded'); } + /** + * Test filtering a hierarchical group SearchDisplay by title. + * + * Replicates issue where filtering by group title only returns matching top-level groups, + * while matching child groups are dropped because their parent groups are excluded by the filter. + */ + public function testHierarchicalGroupFilterByTitle(): void { + $parentGroup = $this->createTestRecord('Group', [ + 'title' => 'Parent Group ' . uniqid(), + ]); + $childGroup = $this->createTestRecord('Group', [ + 'title' => 'Child Group ' . uniqid(), + 'parents' => [$parentGroup['id']], + ]); + + $params = [ + 'checkPermissions' => FALSE, + 'savedSearch' => [ + 'api_entity' => 'Group', + 'api_params' => [ + 'version' => 4, + 'select' => ['id', 'title', 'parents'], + ], + ], + 'display' => [ + 'type' => 'table', + 'settings' => [ + 'hierarchical' => TRUE, + 'columns' => [ + ['type' => 'field', 'key' => 'title', 'sortable' => TRUE], + ], + ], + ], + 'filters' => [ + 'title' => $childGroup['title'], + ], + ]; + + $result = civicrm_api4('SearchDisplay', 'run', $params); + $returnedIds = array_column(array_column($result->getArrayCopy(), 'data'), 'id'); + $this->assertContains($childGroup['id'], $returnedIds, 'Matching child group should be returned when filtering by title'); + } + }