diff --git a/ext/afform/mock/ang/testMultipleSearchForm.aff.html b/ext/afform/mock/ang/testMultipleSearchForm.aff.html new file mode 100644 index 000000000000..3d60563fafa1 --- /dev/null +++ b/ext/afform/mock/ang/testMultipleSearchForm.aff.html @@ -0,0 +1,21 @@ +
+
+ + + +
+
+ + + +
+
+
+ + + +
+ + + + diff --git a/ext/afform/mock/ang/testMultipleSearchForm.aff.json b/ext/afform/mock/ang/testMultipleSearchForm.aff.json new file mode 100644 index 000000000000..609e0f5033cf --- /dev/null +++ b/ext/afform/mock/ang/testMultipleSearchForm.aff.json @@ -0,0 +1,6 @@ +{ + "type": "search", + "title": "TestMultipleSearchForm", + "server_route": "", + "permission": "access CiviCRM" +} diff --git a/ext/search_kit/Civi/Api4/Action/SearchDisplay/AbstractRunAction.php b/ext/search_kit/Civi/Api4/Action/SearchDisplay/AbstractRunAction.php index abf81cd7d53c..6d63a56d8ff1 100644 --- a/ext/search_kit/Civi/Api4/Action/SearchDisplay/AbstractRunAction.php +++ b/ext/search_kit/Civi/Api4/Action/SearchDisplay/AbstractRunAction.php @@ -999,7 +999,7 @@ private function getAfformFilters() { } // Get afform field filters $filterKeys = array_column(\CRM_Utils_Array::findAll( - $afform['layout'] ?? [], + $afform['searchDisplay']['fieldset'], ['#tag' => 'af-field'] ), 'name'); // Get filters passed into search display directive from Afform markup @@ -1028,24 +1028,35 @@ private function getAfformFilters() { * * Verifies the searchDisplay is embedded in the afform and the user has permission to view it. * - * @return array|false|null + * @return array|false */ private function loadAfform() { // Only attempt to load afform once. if ($this->afform && !isset($this->_afform)) { $this->_afform = FALSE; // Permission checks are enabled in this api call to ensure the user has permission to view the form - $afform = \Civi\Api4\Afform::get() + $afform = \Civi\Api4\Afform::get($this->getCheckPermissions()) ->addWhere('name', '=', $this->afform) - ->setLayoutFormat('shallow') + ->setLayoutFormat('deep') ->execute()->first(); + if (empty($afform['layout'])) { + return FALSE; + } + // Get all search display fieldsets (which will have an empty value for the af-fieldset attribute) + $fieldsets = \CRM_Utils_Array::findAll($afform['layout'], ['af-fieldset' => '']); + // As a fallback, search the entire afform in case the search display is not in a fieldset + $fieldsets['form'] = $afform['layout']; // Validate that the afform contains this search display - $afform['searchDisplay'] = \CRM_Utils_Array::findAll( - $afform['layout'] ?? [], - ['#tag' => "{$this->display['type:name']}", 'display-name' => $this->display['name']] - )[0] ?? NULL; - if ($afform['searchDisplay']) { - $this->_afform = $afform; + foreach ($fieldsets as $key => $fieldset) { + $afform['searchDisplay'] = \CRM_Utils_Array::findAll( + $fieldset, + ['#tag' => $this->display['type:name'], 'search-name' => $this->savedSearch['name'], 'display-name' => $this->display['name']] + )[0] ?? NULL; + if ($afform['searchDisplay']) { + // Set the fieldset for this display (if it is in one and we haven't fallen back to the whole form) + $afform['searchDisplay']['fieldset'] = $key === 'form' ? [] : $fieldset; + return $this->_afform = $afform; + } } } return $this->_afform; diff --git a/ext/search_kit/tests/phpunit/api/v4/SearchDisplay/SearchAfformTest.php b/ext/search_kit/tests/phpunit/api/v4/SearchDisplay/SearchAfformTest.php index 577589455467..15d5425f39c7 100644 --- a/ext/search_kit/tests/phpunit/api/v4/SearchDisplay/SearchAfformTest.php +++ b/ext/search_kit/tests/phpunit/api/v4/SearchDisplay/SearchAfformTest.php @@ -5,6 +5,7 @@ use Civi\Api4\Afform; use Civi\Api4\Contact; use Civi\Api4\Email; +use Civi\Api4\Phone; use Civi\Api4\SavedSearch; use Civi\Api4\SearchDisplay; use Civi\Api4\Utils\CoreUtil; @@ -163,6 +164,175 @@ public function testRunWithAfform() { $this->assertCount(1, $result); } + public function testRunMultipleSearchForm() { + $email = uniqid('tester@'); + + Contact::create(FALSE) + ->addValue('first_name', 'tester') + ->addValue('last_name', __FUNCTION__) + ->addValue('source', 'afform_multi_test') + ->addChain('emails', Email::save() + ->addDefault('contact_id', '$id') + ->addRecord(['email' => $email, 'location_type_id:name' => 'Home']) + ->addRecord(['email' => $email, 'location_type_id:name' => 'Work']) + ) + ->addChain('phones', Phone::save() + ->addDefault('contact_id', '$id') + ->addRecord(['phone' => '123-4567', 'location_type_id:name' => 'Home']) + ->addRecord(['phone' => '234-5678', 'location_type_id:name' => 'Work']) + ) + ->execute(); + + Contact::create(FALSE) + ->addValue('first_name', 'tester2') + ->addValue('last_name', __FUNCTION__) + ->addValue('source', 'afform_multi_test') + ->addChain('emails', Email::save() + ->addDefault('contact_id', '$id') + ->addRecord(['email' => 'other@test.com', 'location_type_id:name' => 'Other']) + ) + ->addChain('phones', Phone::save() + ->addDefault('contact_id', '$id') + ->addRecord(['phone' => '123-4567', 'location_type_id:name' => 'Home']) + ->addRecord(['phone' => '234-5678', 'location_type_id:name' => 'Work']) + ) + ->execute(); + + // Decoy contact just to make sure we don't get false-positives + Contact::create(FALSE) + ->addValue('first_name', 'tester3') + ->addValue('last_name', 'nobody') + ->addValue('source', 'decoy') + ->addChain('emails', Email::save() + ->addDefault('contact_id', '$id') + ->addRecord(['email' => $email, 'location_type_id:name' => 'Home']) + ) + ->addChain('phones', Phone::save() + ->addDefault('contact_id', '$id') + ->addRecord(['phone' => '123-4567', 'location_type_id:name' => 'Home']) + ->addRecord(['phone' => '234-5678', 'location_type_id:name' => 'Work']) + ) + ->execute(); + + $contactEmailSearch = SavedSearch::create(FALSE) + ->setValues([ + 'name' => 'TestContactEmailSearch', + 'label' => 'TestContactEmailSearch', + 'api_entity' => 'Contact', + 'api_params' => [ + 'version' => 4, + 'select' => [ + 'id', + 'display_name', + 'GROUP_CONCAT(DISTINCT Contact_Email_contact_id_01.email) AS GROUP_CONCAT_Contact_Email_contact_id_01_email', + ], + 'orderBy' => [], + 'where' => [ + ['contact_type:name', '=', 'Individual'], + ], + 'groupBy' => ['id'], + 'join' => [ + [ + 'Email AS Contact_Email_contact_id_01', + 'LEFT', + ['id', '=', 'Contact_Email_contact_id_01.contact_id'], + ], + ], + 'having' => [], + ], + ]) + ->execute()->first(); + + $contactEmailDisplay = SearchDisplay::create(FALSE) + ->setValues([ + 'name' => 'TestContactEmailDisplay', + 'label' => 'TestContactEmailDisplay', + 'saved_search_id.name' => 'TestContactEmailSearch', + 'type' => 'table', + 'settings' => [ + 'limit' => 50, + 'pager' => TRUE, + 'columns' => [ + [ + 'key' => 'id', + 'label' => 'Contact ID', + 'dataType' => 'Integer', + 'type' => 'field', + ], + [ + 'key' => 'display_name', + 'label' => 'Display Name', + 'dataType' => 'String', + 'type' => 'field', + ], + [ + 'key' => 'GROUP_CONCAT_Contact_Email_contact_id_01_email', + 'label' => 'Emails', + 'dataType' => 'String', + 'type' => 'field', + ], + ], + ], + 'acl_bypass' => FALSE, + ]) + ->execute()->first(); + + foreach (['Email', 'Phone'] as $entity) { + SavedSearch::create(FALSE) + ->setValues([ + 'name' => 'TestSearchFor' . $entity, + 'label' => 'TestSearchFor' . $entity, + 'api_entity' => $entity, + 'api_params' => [ + 'version' => 4, + 'select' => [ + 'id', + 'contact_id.display_name', + ], + 'orderBy' => [], + 'where' => [], + 'groupBy' => [], + 'join' => [], + 'having' => [], + ], + ]) + ->execute(); + } + + $params = [ + 'return' => 'page:1', + 'display' => NULL, + 'afform' => 'testMultipleSearchForm', + ]; + + // This filter will not work because the search display is not within an + $params['savedSearch'] = 'TestSearchForPhone'; + $params['filters'] = ['location_type_id' => 1]; + $result = civicrm_api4('SearchDisplay', 'run', $params); + $this->assertCount(4, $result); + + $params['savedSearch'] = 'TestSearchForEmail'; + $params['filters'] = ['location_type_id' => 1, 'contact_id.display_name' => __FUNCTION__]; + $result = civicrm_api4('SearchDisplay', 'run', $params); + $this->assertCount(1, $result); + + // Email filter will not work because it's in the wrong fieldset on the form + $params['filters'] = ['email' => $email, 'contact_id.display_name' => __FUNCTION__]; + $result = civicrm_api4('SearchDisplay', 'run', $params); + $this->assertCount(3, $result); + + // No filters will work; they are in the fieldset belonging to the non-default display + $params['savedSearch'] = 'TestContactEmailSearch'; + $params['filters'] = ['source' => 'afform_multi_test', 'Contact_Email_contact_id_01.location_type_id' => 1]; + $result = civicrm_api4('SearchDisplay', 'run', $params); + $this->assertGreaterThanOrEqual(3, $result->count()); + + // Now the filters will work because they are in the fieldset for this display + $params['display'] = 'TestContactEmailDisplay'; + $result = civicrm_api4('SearchDisplay', 'run', $params); + $this->assertCount(1, $result); + } + public function testSearchReferencesToAfform() { $search = SavedSearch::create(FALSE) ->setValues([