From 85faeb4bb734a9e31625a28188958a7a336bd031 Mon Sep 17 00:00:00 2001 From: colemanw Date: Sat, 15 Jul 2023 12:10:48 -0400 Subject: [PATCH] SearchKit - Ensure task links work with relationships --- .../SearchDisplay/AbstractRunAction.php | 23 +++++- .../Action/SearchDisplay/GetSearchTasks.php | 5 ++ .../crmSearchAdminDisplay.component.js | 2 +- .../api/v4/SearchDisplay/SearchRunTest.php | 79 +++++++++++++++++++ 4 files changed, 106 insertions(+), 3 deletions(-) diff --git a/ext/search_kit/Civi/Api4/Action/SearchDisplay/AbstractRunAction.php b/ext/search_kit/Civi/Api4/Action/SearchDisplay/AbstractRunAction.php index 19b1f9a2bb..ea4475628e 100644 --- a/ext/search_kit/Civi/Api4/Action/SearchDisplay/AbstractRunAction.php +++ b/ext/search_kit/Civi/Api4/Action/SearchDisplay/AbstractRunAction.php @@ -468,12 +468,22 @@ abstract class AbstractRunAction extends \Civi\Api4\Generic\AbstractAction { return array_filter($link); } + /** + * Check access for edit/update/delete links + * + * (presumably if a record is shown in SearchKit the user already has view access, and the check is expensive) + * + * @param array $link + * @param array $data + * @param int $index + * @return bool + * @throws \CRM_Core_Exception + * @throws \Civi\API\Exception\NotImplementedException + */ private function checkLinkAccess(array $link, array $data, int $index = 0): bool { if (empty($link['path']) && empty($link['task'])) { return FALSE; } - // Check access for edit/update/delete links - // (presumably if a record is shown in SearchKit the user already has view access, and the check is expensive) if ($link['entity'] && !empty($link['action']) && !in_array($link['action'], ['view', 'preview'], TRUE)) { $idField = CoreUtil::getIdFieldName($link['entity']); $idKey = $this->getIdKeyName($link['entity']); @@ -585,12 +595,21 @@ abstract class AbstractRunAction extends \Civi\Api4\Generic\AbstractAction { return $link; } + /** + * Get fields needed by a link which should be added to the SELECT clause + * + * @param array $link + * @return array + */ private function getLinkTokens(array $link): array { $link = $this->getLinkInfo($link); $tokens = []; if (!$link['path'] && !empty($link['task'])) { $tokens[] = $link['prefix'] . $this->getIdKeyName($link['entity']); } + if (!empty($link['condition'][0])) { + $tokens[] = $link['condition'][0]; + } return array_merge($tokens, $this->getTokens($link['path'] . $link['text'] . $link['title'])); } diff --git a/ext/search_kit/Civi/Api4/Action/SearchDisplay/GetSearchTasks.php b/ext/search_kit/Civi/Api4/Action/SearchDisplay/GetSearchTasks.php index 9b04aaeeb2..56595b207a 100644 --- a/ext/search_kit/Civi/Api4/Action/SearchDisplay/GetSearchTasks.php +++ b/ext/search_kit/Civi/Api4/Action/SearchDisplay/GetSearchTasks.php @@ -33,6 +33,7 @@ class GetSearchTasks extends \Civi\Api4\Generic\AbstractAction { // Adding checkPermissions filters out actions the user is not allowed to perform $entityName = $this->savedSearch['api_entity']; + // Hack to support relationships $entityName = ($entityName === 'RelationshipCache') ? 'Relationship' : $entityName; $entity = Entity::get($this->checkPermissions)->addWhere('name', '=', $entityName) ->addSelect('name', 'title_plural') @@ -243,6 +244,7 @@ class GetSearchTasks extends \Civi\Api4\Generic\AbstractAction { foreach ($tasks[$entity['name']] as $name => &$task) { $task['name'] = $name; + $task['entity'] = $entity['name']; // Add default for number of rows action requires $task += ['number' => '> 0']; } @@ -271,6 +273,9 @@ class GetSearchTasks extends \Civi\Api4\Generic\AbstractAction { [ 'name' => 'number', ], + [ + 'name' => 'entity', + ], [ 'name' => 'apiBatch', 'data_type' => 'Array', diff --git a/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminDisplay.component.js b/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminDisplay.component.js index f41155596e..5fb2ced4af 100644 --- a/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminDisplay.component.js +++ b/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminDisplay.component.js @@ -257,7 +257,7 @@ text: task.title, icon: task.icon, task: task.name, - entity: ctrl.savedSearch.api_entity, + entity: task.entity, target: 'crm-popup', join: '', style: task.name === 'delete' ? 'danger' : 'default' 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 63758abd54..45aa3883f9 100644 --- a/ext/search_kit/tests/phpunit/api/v4/SearchDisplay/SearchRunTest.php +++ b/ext/search_kit/tests/phpunit/api/v4/SearchDisplay/SearchRunTest.php @@ -305,6 +305,85 @@ class SearchRunTest extends Api4TestBase implements TransactionalInterface { $this->assertEquals('crm-popup', $result[0]['columns'][1]['links'][2]['target']); } + public function testRelationshipCacheLinks():void { + $relationships = $this->saveTestRecords('Relationship', [ + 'records' => [ + ['contact_id_a' => $this->createTestRecord('Contact')['id'], 'is_active' => TRUE], + ['contact_id_a' => $this->createTestRecord('Contact')['id'], 'is_active' => FALSE], + ], + ]); + $params = [ + 'checkPermissions' => FALSE, + 'return' => 'page:1', + 'savedSearch' => [ + 'api_entity' => 'RelationshipCache', + 'api_params' => [ + 'version' => 4, + 'select' => ['near_contact_id.display_name'], + 'where' => [['relationship_id', 'IN', $relationships->column('id')]], + ], + ], + 'display' => [ + 'type' => 'table', + 'label' => '', + 'settings' => [ + 'actions' => TRUE, + 'pager' => [], + 'columns' => [ + [ + 'key' => 'near_contact_id.display_name', + 'label' => 'Contact', + 'dataType' => 'String', + 'type' => 'field', + ], + [ + 'type' => 'links', + 'links' => [ + [ + 'entity' => 'Relationship', + 'action' => 'view', + 'icon' => 'fa-external-link', + ], + [ + 'entity' => 'Relationship', + 'task' => 'delete', + 'icon' => 'fa-trash', + ], + [ + 'entity' => 'Relationship', + 'task' => 'enable', + 'condition' => ['is_active', '=', FALSE], + ], + [ + 'entity' => 'Relationship', + 'task' => 'disable', + 'condition' => ['is_active', '=', TRUE], + ], + ], + ], + ], + 'sort' => [ + ['relationship_id', 'ASC'], + ], + ], + ], + ]; + $result = civicrm_api4('SearchDisplay', 'run', $params); + $this->assertCount(4, $result); + $this->assertCount(3, $result[0]['columns'][1]['links']); + // 1st link is to a quickform-based action + $this->assertArrayNotHasKey('task', $result[0]['columns'][1]['links'][0]); + $this->assertStringContainsString('id=' . $relationships[0]['id'], $result[0]['columns'][1]['links'][0]['url']); + // 2nd link is to the native SK bulk-delete task + $this->assertArrayNotHasKey('url', $result[0]['columns'][1]['links'][1]); + $this->assertEquals('delete', $result[0]['columns'][1]['links'][1]['task']); + // 3rd link is the disable task for active relationships or the enable task for inactive ones + $this->assertEquals('disable', $result[0]['columns'][1]['links'][2]['task']); + $this->assertEquals('disable', $result[1]['columns'][1]['links'][2]['task']); + $this->assertEquals('enable', $result[2]['columns'][1]['links'][2]['task']); + $this->assertEquals('enable', $result[3]['columns'][1]['links'][2]['task']); + } + /** * Test smarty rewrite syntax. */ -- 2.25.1