From 96e27fc0aa36c19df07df3d1bb003d2ffff58f9d Mon Sep 17 00:00:00 2001 From: colemanw Date: Fri, 2 Feb 2024 11:28:11 -0500 Subject: [PATCH] SearchKit - Hide invalid links and prevent error Fixes dev/core#4961 --- Civi/Api4/Action/GetLinks.php | 8 ++- .../api/v4/SearchDisplay/SearchRunTest.php | 61 +++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/Civi/Api4/Action/GetLinks.php b/Civi/Api4/Action/GetLinks.php index 32930ef39c..5751ddb14a 100644 --- a/Civi/Api4/Action/GetLinks.php +++ b/Civi/Api4/Action/GetLinks.php @@ -129,7 +129,7 @@ class GetLinks extends BasicGetAction { // Text was translated with `%1` placeholders preserved so it could be cached // Now we'll replace `%1` placeholders with the entityTitle, unless FALSE $entityTitle = $this->entityTitle === TRUE ? CoreUtil::getInfoItem($this->getEntityName(), 'title') : $this->entityTitle; - foreach ($links as &$link) { + foreach ($links as $index => &$link) { // Swap placeholders with $entityTitle (TRUE means use default title) if ($entityTitle !== FALSE && !empty($link['text'])) { $link['text'] = str_replace('%1', $entityTitle, $link['text']); @@ -142,6 +142,12 @@ class GetLinks extends BasicGetAction { if (isset($value)) { $link['path'] = str_replace($token['token'], $value, $link['path']); } + // If $values was supplied, treat all tokens as mandatory and remove links with null values + // This hides invalid links from SearchKit e.g. `civicrm/group/edit?id=null` + else { + unset($links[$index]); + break; + } } } } 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 4cd399708e..a56e78bae0 100644 --- a/ext/search_kit/tests/phpunit/api/v4/SearchDisplay/SearchRunTest.php +++ b/ext/search_kit/tests/phpunit/api/v4/SearchDisplay/SearchRunTest.php @@ -175,6 +175,67 @@ class SearchRunTest extends Api4TestBase implements TransactionalInterface { $this->assertCount(2, $count); } + public function testDefaultDisplayLinks(): void { + $group1 = $this->createTestRecord('Group', ['title' => uniqid('a')])['id']; + $group2 = $this->createTestRecord('Group', ['title' => uniqid('b')])['id']; + $contact1 = $this->createTestRecord('Individual', ['last_name' => 'b', 'first_name' => 'b'])['id']; + $contact2 = $this->createTestRecord('Individual', ['last_name' => 'a', 'first_name' => 'a'])['id']; + // Add both contacts to group2 + $this->saveTestRecords('GroupContact', [ + 'records' => [ + ['contact_id' => $contact1, 'group_id' => $group2], + ['contact_id' => $contact2, 'group_id' => $group2], + ], + ]); + + $params = [ + 'checkPermissions' => FALSE, + 'return' => 'page:1', + 'savedSearch' => [ + 'api_entity' => 'Group', + 'api_params' => [ + 'version' => 4, + 'select' => [ + 'title', + 'Group_GroupContact_Contact_01.sort_name', + ], + 'join' => [ + [ + 'Contact AS Group_GroupContact_Contact_01', + 'LEFT', + 'GroupContact', + ['id', '=', 'Group_GroupContact_Contact_01.group_id'], + ['Group_GroupContact_Contact_01.status:name', '=', '"Added"'], + ], + ], + 'where' => [], + ], + ], + 'display' => NULL, + 'sort' => [ + ['title', 'ASC'], + ['Group_GroupContact_Contact_01.sort_name', 'ASC'], + ], + 'filters' => ['id' => [$group1, $group2]], + ]; + + $result = civicrm_api4('SearchDisplay', 'run', $params); + $this->assertCount(1, $result[0]['columns'][0]['links']); + $this->assertNull($result[0]['columns'][1]['val']); + $this->assertArrayNotHasKey('links', $result[0]['columns'][1]); + $this->assertCount(1, $result[1]['columns'][0]['links']); + $this->assertCount(1, $result[1]['columns'][1]['links']); + $this->assertCount(1, $result[2]['columns'][0]['links']); + $this->assertCount(1, $result[2]['columns'][1]['links']); + // No contact links in 1st row since the group is empty + $this->assertNotContains('View Contact', array_column($result[0]['columns'][2]['links'], 'text')); + $this->assertNotContains('Delete Contact', array_column($result[0]['columns'][2]['links'], 'text')); + $this->assertContains('View Contact', array_column($result[1]['columns'][2]['links'], 'text')); + $this->assertContains('Delete Contact', array_column($result[1]['columns'][2]['links'], 'text')); + $this->assertContains('View Contact', array_column($result[2]['columns'][2]['links'], 'text')); + $this->assertContains('Delete Contact', array_column($result[2]['columns'][2]['links'], 'text')); + } + /** * Test return values are augmented by tokens. */ -- 2.25.1