SearchKit Toolbar - Fix conditionals, add tests
authorcolemanw <coleman@civicrm.org>
Wed, 20 Sep 2023 02:38:35 +0000 (22:38 -0400)
committercolemanw <coleman@civicrm.org>
Wed, 20 Sep 2023 02:46:22 +0000 (22:46 -0400)
ext/search_kit/Civi/Api4/Action/SearchDisplay/AbstractRunAction.php
ext/search_kit/Civi/Api4/Action/SearchDisplay/Run.php
ext/search_kit/ang/crmSearchAdmin/crmSearchAdminLinkGroup.component.js
ext/search_kit/ang/crmSearchAdmin/crmSearchAdminLinkGroup.html
ext/search_kit/tests/phpunit/api/v4/SearchDisplay/SearchRunTest.php

index 401d8b1f54920441342c640b26c80947575819c4..427a34f71718d71c5da193cc2eece316e1eb36e0 100644 (file)
@@ -551,6 +551,10 @@ abstract class AbstractRunAction extends \Civi\Api4\Generic\AbstractAction {
       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 @@ abstract class AbstractRunAction extends \Civi\Api4\Generic\AbstractAction {
         $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 @@ abstract class AbstractRunAction extends \Civi\Api4\Generic\AbstractAction {
         '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 @@ abstract class AbstractRunAction extends \Civi\Api4\Generic\AbstractAction {
    * @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') {
index 0b51c78458842c56806077134a90c8326d957bb8..21fdf87428d63fe8a3fb26b74a5b617e033b0e0d 100644 (file)
@@ -158,6 +158,9 @@ class Run extends AbstractRunAction {
       $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;
index c766a4e86b563414b28a09be2ba5285ddf9eaa22..532541bcc6ab7c15675820ef8102dca01194b60b 100644 (file)
@@ -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;
index 4bb1f77e532327f1978f5b951f24ff129029f875..81622bab3995a4c92906adf4e3ebcf187a33cbad 100644 (file)
@@ -39,7 +39,7 @@
         <input ng-model="item.condition[0]" crm-ui-select="{placeholder: item.action ? ts('Allowed') : ts('Always'), data: $ctrl.fields}" ng-change="$ctrl.onChangeCondition(item)">
         <div class="form-group" ng-if="item.condition[0] === 'check user permission'">
           <select class="form-control api4-operator" ng-model="item.condition[1]" ng-options="o.key as o.value for o in $ctrl.permissionOperators"></select>
-          <input class="form-control" crm-ui-select="{data: $ctrl.permissions}" ng-model="item.condition[2]">
+          <input class="form-control" crm-ui-select="{data: $ctrl.permissions, multiple: true}" ng-model="item.condition[2]" ng-list>
         </div>
         <crm-search-condition class="form-group"
           ng-if="item.condition[0] && item.condition[0] !== 'check user permission'"
index 8a4dad8a3bb158f9504a2ccf0a168b9f66205c76..5251f16ac61599b388b0569cc4d572fa1e353932 100644 (file)
@@ -1906,7 +1906,6 @@ class SearchRunTest extends Api4TestBase implements TransactionalInterface {
 
   public function testRunWithToolbar(): void {
     $params = [
-      'checkPermissions' => FALSE,
       'return' => 'page:1',
       'savedSearch' => [
         'api_entity' => 'Contact',
@@ -1944,6 +1943,15 @@ class SearchRunTest extends Api4TestBase implements TransactionalInterface {
       ],
       '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 @@ class SearchRunTest extends Api4TestBase implements TransactionalInterface {
     $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', [