From d8129118ea0358d0b557db7256078944c89af835 Mon Sep 17 00:00:00 2001 From: colemanw Date: Tue, 19 Sep 2023 22:38:35 -0400 Subject: [PATCH] SearchKit Toolbar - Fix conditionals, add tests --- .../SearchDisplay/AbstractRunAction.php | 25 +++++- .../Civi/Api4/Action/SearchDisplay/Run.php | 3 + .../crmSearchAdminLinkGroup.component.js | 5 +- .../crmSearchAdminLinkGroup.html | 2 +- .../api/v4/SearchDisplay/SearchRunTest.php | 84 ++++++++++++++++++- 5 files changed, 111 insertions(+), 8 deletions(-) diff --git a/ext/search_kit/Civi/Api4/Action/SearchDisplay/AbstractRunAction.php b/ext/search_kit/Civi/Api4/Action/SearchDisplay/AbstractRunAction.php index 401d8b1f5492..427a34f71718 100644 --- a/ext/search_kit/Civi/Api4/Action/SearchDisplay/AbstractRunAction.php +++ b/ext/search_kit/Civi/Api4/Action/SearchDisplay/AbstractRunAction.php @@ -551,6 +551,10 @@ private function checkLinkAccess(array $link, array $data, int $index = 0): bool if (!$actionName) { return FALSE; } + if ($actionName === 'create') { + // No record to check for this action and getPermittedLinkAction says it's allowed; we're good. + return TRUE; + } $idField = CoreUtil::getIdFieldName($link['entity']); $idKey = $this->getIdKeyName($link['entity']); $id = $data[$link['prefix'] . $idKey] ?? NULL; @@ -566,6 +570,8 @@ private function checkLinkAccess(array $link, array $data, int $index = 0): bool $apiRequest = Request::create($link['entity'], $actionName, ['version' => 4]); return CoreUtil::checkAccessRecord($apiRequest, $values); } + // No id so cannot possibly update or delete record + return FALSE; } return TRUE; } @@ -589,6 +595,11 @@ private function getPermittedLinkAction(string $entityName, string $actionName): 'allowed' => civicrm_api4($entityName, 'getActions', ['checkPermissions' => TRUE])->column('name'), ]; } + // Map CRM_Core_Action names to API action names :/ + $map = [ + 'add' => 'create', + ]; + $actionName = $map[$actionName] ?? $actionName; // Action exists and is permitted if (in_array($actionName, $this->entityActions[$entityName]['allowed'], TRUE)) { return $actionName; @@ -616,16 +627,22 @@ private function getPermittedLinkAction(string $entityName, string $actionName): * @param array $data * @return bool */ - private function checkLinkCondition(array $item, array $data): bool { + protected function checkLinkCondition(array $item, array $data): bool { if (empty($item['condition'][0]) || empty($item['condition'][1])) { return TRUE; } $op = $item['condition'][1]; if ($item['condition'][0] === 'check user permission') { - if (!empty($item['condition'][2]) && !\CRM_Core_Permission::check($item['condition'][2])) { - return $op !== '='; + // No permission == open access + if (empty($item['condition'][2])) { + return TRUE; } - return TRUE; + $permissions = (array) $item['condition'][2]; + if ($op === 'CONTAINS') { + // Place conditions in OR array for CONTAINS operator + $permissions = [$permissions]; + } + return \CRM_Core_Permission::check($permissions) == ($op !== '!='); } // Convert the conditional value of 'current_domain' into an actual value that filterCompare can work with if ($item['condition'][2] === 'current_domain') { diff --git a/ext/search_kit/Civi/Api4/Action/SearchDisplay/Run.php b/ext/search_kit/Civi/Api4/Action/SearchDisplay/Run.php index 0b51c7845884..21fdf87428d6 100644 --- a/ext/search_kit/Civi/Api4/Action/SearchDisplay/Run.php +++ b/ext/search_kit/Civi/Api4/Action/SearchDisplay/Run.php @@ -158,6 +158,9 @@ private function formatToolbar(): array { $settings['toolbar'][] = $settings['addButton'] + ['style' => 'primary', 'target' => 'crm-popup']; } foreach ($settings['toolbar'] ?? [] as $button) { + if (!$this->checkLinkCondition($button, $data)) { + continue; + } $button = $this->formatLink($button, $data); if ($button) { $toolbar[] = $button; diff --git a/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminLinkGroup.component.js b/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminLinkGroup.component.js index c766a4e86b56..532541bcc6ab 100644 --- a/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminLinkGroup.component.js +++ b/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminLinkGroup.component.js @@ -18,8 +18,9 @@ linkProps = ['path', 'task', 'entity', 'action', 'join', 'target', 'icon', 'text', 'style', 'condition']; ctrl.permissionOperators = [ - {key: '=', value: ts('Has')}, - {key: '!=', value: ts('Lacks')} + {key: 'CONTAINS', value: ts('Includes')}, + {key: '=', value: ts('Has All')}, + {key: '!=', value: ts('Lacks All')} ]; this.styles = CRM.crmSearchAdmin.styles; diff --git a/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminLinkGroup.html b/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminLinkGroup.html index 4bb1f77e5323..81622bab3995 100644 --- a/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminLinkGroup.html +++ b/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminLinkGroup.html @@ -39,7 +39,7 @@
- +
FALSE, 'return' => 'page:1', 'savedSearch' => [ 'api_entity' => 'Contact', @@ -1944,6 +1943,15 @@ public function testRunWithToolbar(): void { ], 'filters' => ['contact_type' => 'Individual'], ]; + // No 'add contacts' permission == no "Add contacts" button + \CRM_Core_Config::singleton()->userPermissionClass->permissions = [ + 'access CiviCRM', + 'administer search_kit', + ]; + $result = civicrm_api4('SearchDisplay', 'run', $params); + $this->assertCount(0, $result->toolbar); + // With 'add contacts' permission the button will be shown + \CRM_Core_Config::singleton()->userPermissionClass->permissions[] = 'add contacts'; $result = civicrm_api4('SearchDisplay', 'run', $params); $this->assertCount(1, $result->toolbar); $button = $result->toolbar[0]; @@ -1978,6 +1986,80 @@ public function testRunWithToolbar(): void { $this->assertTrue($button['autoOpen']); } + public static function toolbarLinkPermissions(): array { + $sets = []; + $sets[] = [ + 'CONTAINS', + ['access CiviCRM', 'administer CiviCRM'], + ['access CiviCRM'], + TRUE, + ]; + $sets[] = [ + '=', + ['access CiviCRM', 'administer CiviCRM'], + ['access CiviCRM'], + FALSE, + ]; + $sets[] = [ + '!=', + ['access CiviCRM', 'administer CiviCRM'], + ['access CiviCRM'], + TRUE, + ]; + $sets[] = [ + 'CONTAINS', + ['access CiviCRM', 'administer CiviCRM'], + [], + FALSE, + ]; + $sets[] = [ + '=', + [], + [], + TRUE, + ]; + return $sets; + } + + /** + * @dataProvider toolbarLinkPermissions + */ + public function testToolbarLinksPermissionOperators($linkOperator, $linkPerms, $userPerms, $shouldBeVisible): void { + $params = [ + 'return' => 'page:1', + 'savedSearch' => [ + 'api_entity' => 'Contact', + 'api_params' => [ + 'version' => 4, + 'select' => ['first_name', 'contact_type'], + ], + ], + 'display' => [ + 'type' => 'table', + 'label' => '', + 'settings' => [ + 'actions' => TRUE, + 'pager' => [], + 'toolbar' => [ + [ + 'path' => 'civicrm/test', + 'text' => 'Test', + 'condition' => [ + 'check user permission', + $linkOperator, + $linkPerms, + ], + ], + ], + 'columns' => [], + ], + ], + ]; + \CRM_Core_Config::singleton()->userPermissionClass->permissions = array_merge(['administer search_kit'], $userPerms); + $result = civicrm_api4('SearchDisplay', 'run', $params); + $this->assertCount((int) $shouldBeVisible, $result->toolbar); + } + public function testRunWithEntityFile(): void { $cid = $this->createTestRecord('Contact')['id']; $notes = $this->saveTestRecords('Note', [