SearchKit - Support conditional links
authorColeman Watts <coleman@civicrm.org>
Tue, 18 Jan 2022 02:41:32 +0000 (21:41 -0500)
committerColeman Watts <coleman@civicrm.org>
Fri, 21 Jan 2022 02:14:47 +0000 (21:14 -0500)
Advanced feature to conditionally show/hide links in a links/buttons/menu column,
based on user permissions or row values.

ext/search_kit/Civi/Api4/Action/SearchDisplay/AbstractRunAction.php
ext/search_kit/Civi/Search/Admin.php
ext/search_kit/ang/crmSearchAdmin/crmSearchAdminLinkGroup.component.js
ext/search_kit/ang/crmSearchAdmin/crmSearchAdminLinkGroup.html
ext/search_kit/ang/crmSearchAdmin/crmSearchAdminTokenSelect.component.js

index f6957086d0790aef50d9d150153a6f75c2ab2b1f..3e6450edde21ad32306a55d215c722bc5143f3f2 100644 (file)
@@ -373,6 +373,9 @@ abstract class AbstractRunAction extends \Civi\Api4\Generic\AbstractAction {
       $out['text'] = $this->replaceTokens($column['text'], $data, 'view');
     }
     foreach ($column['links'] as $item) {
+      if (!$this->checkLinkCondition($item, $data)) {
+        continue;
+      }
       $path = $this->replaceTokens($this->getLinkPath($item, $data), $data, 'url');
       if ($path) {
         $link = [
@@ -390,6 +393,30 @@ abstract class AbstractRunAction extends \Civi\Api4\Generic\AbstractAction {
     return $out;
   }
 
+  /**
+   * Check if a link should be shown based on its conditions.
+   *
+   * Given a link, check if it is set to be displayed conditionally.
+   * If so, evaluate the condition, else return TRUE.
+   *
+   * @param array $item
+   * @param array $data
+   * @return bool
+   */
+  private 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 !== '=';
+      }
+      return TRUE;
+    }
+    return ArrayQueryActionTrait::filterCompare($data, $item['condition']);
+  }
+
   /**
    * @param array $link
    * @param array $data
index 2fb9d9f49782b41e03351b5157787325c569bda9..e6346394119cc57d8c0320f6e2c287c4ab628819 100644 (file)
@@ -30,11 +30,12 @@ class Admin {
   public static function getAdminSettings():array {
     $schema = self::getSchema();
     $extensions = \CRM_Extension_System::singleton()->getMapper();
-    return [
+    $data = [
       'schema' => self::addImplicitFKFields($schema),
       'joins' => self::getJoins($schema),
       'pseudoFields' => AbstractRunAction::getPseudoFields(),
       'operators' => \CRM_Utils_Array::makeNonAssociative(self::getOperators()),
+      'permissions' => [],
       'functions' => self::getSqlFunctions(),
       'displayTypes' => Display::getDisplayTypes(['id', 'name', 'label', 'description', 'icon']),
       'styles' => \CRM_Utils_Array::makeNonAssociative(self::getStyles()),
@@ -49,6 +50,19 @@ class Admin {
         ->addWhere('used_for', 'CONTAINS', 'civicrm_saved_search')
         ->execute(),
     ];
+    $perms = \Civi\Api4\Permission::get()
+      ->addWhere('group', 'IN', ['civicrm', 'cms'])
+      ->addWhere('is_active', '=', 1)
+      ->setOrderBy(['title' => 'ASC'])
+      ->execute();
+    foreach ($perms as $perm) {
+      $data['permissions'][] = [
+        'id' => $perm['name'],
+        'text' => $perm['title'],
+        'description' => $perm['description'] ?? NULL,
+      ];
+    }
+    return $data;
   }
 
   /**
index 6623f2933dcf0ebbaa009289eaccf21608496617..524eb5f6b54754973ed1875b182d7c428b2ea260 100644 (file)
@@ -8,11 +8,26 @@
       apiParams: '<',
       links: '<'
     },
+    require: {
+      crmSearchAdmin: '^crmSearchAdmin'
+    },
     templateUrl: '~/crmSearchAdmin/crmSearchAdminLinkGroup.html',
     controller: function ($scope, $element, $timeout, searchMeta) {
       var ts = $scope.ts = CRM.ts('org.civicrm.search_kit'),
         ctrl = this,
-        linkProps = ['path', 'entity', 'action', 'join', 'target', 'icon', 'text', 'style'];
+        linkProps = ['path', 'entity', 'action', 'join', 'target', 'icon', 'text', 'style', 'condition'];
+
+      var permissionOperators = [
+        {key: '=', value: ts('Has')},
+        {key: '!=', value: ts('Lacks')}
+      ];
+
+      this.getOperators = function(clause) {
+        if (clause[0] === 'check user permission') {
+          return permissionOperators;
+        }
+        return CRM.crmSearchAdmin.operators;
+      };
 
       this.styles = CRM.crmSearchAdmin.styles;
 
         return _.findWhere(this.styles, {key: item.style});
       };
 
+      this.getField = searchMeta.getField;
+
+      this.fields = function() {
+        var selectFields = ctrl.crmSearchAdmin.getSelectFields();
+        var permissionField = [{
+          text: ts('Current User Permission'),
+          id: 'check user permission',
+          description: ts('Check permission of logged-in user')
+        }];
+        return {results: permissionField.concat(selectFields)};
+      };
+
+      this.onChangeCondition = function(item) {
+        if (item.condition[0]) {
+          item.condition[1] = '=';
+        } else {
+          item.condition = [];
+        }
+      };
+
       this.sortableOptions = {
         containment: 'tbody',
         direction: 'vertical',
         }
       };
 
+      this.permissions = CRM.crmSearchAdmin.permissions;
+
       $scope.pickIcon = function(index) {
         searchMeta.pickIcon().then(function(icon) {
           ctrl.group[index].icon = icon;
         });
       };
 
+      function setDefaults(item, newValue) {
+        _.each(linkProps, function(prop) {
+          item[prop] = newValue[prop] || (prop === 'condition' ? [] : '');
+        });
+      }
+
       this.addItem = function(item) {
-        ctrl.group.push(_.pick(item, linkProps));
+        var newItem = _.pick(item, linkProps);
+        setDefaults(newItem, newItem);
+        ctrl.group.push(newItem);
       };
 
       this.onChangeLink = function(item, newValue) {
         if (newValue.path === 'civicrm/') {
           newValue = JSON.parse(this.default);
         }
-        _.each(linkProps, function(prop) {
-          item[prop] = newValue[prop] || '';
-        });
+        setDefaults(item, newValue);
       };
 
       this.serialize = JSON.stringify;
           style: 'default',
           text: ts('Link'),
           icon: 'fa-external-link',
+          condition: [],
           path: 'civicrm/'
         });
         var defaultLinks = _.filter(ctrl.links, function(link) {
           return !link.join;
         });
+        _.each(ctrl.group, function(item) {
+          setDefaults(item, item);
+        });
         if (!ctrl.group.length) {
           if (defaultLinks.length) {
             _.each(defaultLinks, ctrl.addItem);
index fc67712617b117b73cd60d7ced13ad77c7ce980d..86af85b404809f71f074120f9c3c064283ba786f 100644 (file)
@@ -6,6 +6,7 @@
       <th>{{:: ts('Open') }}</th>
       <th>{{:: ts('Text') }}</th>
       <th>{{:: ts('Link') }}</th>
+      <th>{{:: ts('Show if') }}</th>
       <th>{{:: ts('Style') }}</th>
       <th class="crm-search-admin-icon-col"></th>
     </tr>
       <td class="form-inline">
         <crm-search-admin-link-select api-entity="$ctrl.apiEntity" api-params="$ctrl.apiParams" link="item" links="$ctrl.links" on-change="$ctrl.onChangeLink(item, newLink)"></crm-search-admin-link-select>
       </td>
+      <td class="form-inline">
+        <input ng-model="item.condition[0]" crm-ui-select="{placeholder: item.action ? ts('Allowed') : ts('Always'), data: $ctrl.fields}" ng-change="$ctrl.onChangeCondition(item)">
+        <select ng-if="item.condition[0]" class="form-control api4-operator" ng-model="item.condition[1]" ng-options="o.key as o.value for o in $ctrl.getOperators(item.condition)">
+        </select>
+        <div class="form-group" ng-if="item.condition[0] === 'check user permission'">
+          <input class="form-control" crm-ui-select="{data: $ctrl.permissions}" ng-model="item.condition[2]">
+        </div>
+        <div class="form-group" ng-if="item.condition[0] && item.condition[0] !== 'check user permission' && item.condition[1].indexOf('IS ') !== 0">
+          <crm-search-input ng-model="item.condition[2]" field="$ctrl.getField(item.condition[0])" option-key="'name'" op="item.condition[1]" class="form-group"></crm-search-input>
+        </div>
+      </td>
       <td>
         <div class="btn-group">
           <button type="button" style="min-width: 85px" class="btn btn-{{ item.style }} dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
index 3b4642dd9b065f45f5775e4f30428c0d25ada808..b5942976fbee7a29107a70b5feba6c3ce7f6975d 100644 (file)
 
       this.getTokens = function() {
         var allFields = ctrl.admin.getAllFields(ctrl.suffix || '', ['Field', 'Custom', 'Extra', 'Pseudo']);
-        _.eachRight(ctrl.admin.savedSearch.api_params.select, function(fieldName) {
-          allFields.unshift({
-            id: _.last(fieldName.split(' AS ')),
-            text: searchMeta.getDefaultLabel(fieldName)
-          });
-        });
         return {
-          results: allFields
+          results: ctrl.admin.getSelectFields().concat(allFields)
         };
       };