Afform - fill entity based on existing entity selection
authorColeman Watts <coleman@civicrm.org>
Fri, 5 Aug 2022 22:02:19 +0000 (18:02 -0400)
committerColeman Watts <coleman@civicrm.org>
Wed, 10 Aug 2022 02:28:44 +0000 (22:28 -0400)
ext/afform/admin/ang/afGuiEditor/elements/afGuiField-menu.html
ext/afform/admin/ang/afGuiEditor/elements/afGuiField.component.js
ext/afform/core/Civi/Afform/AfformMetadataInjector.php
ext/afform/core/Civi/Api4/Action/Afform/AbstractProcessor.php
ext/afform/core/Civi/Api4/Subscriber/AutocompleteSubscriber.php
ext/afform/core/ang/af/afEntity.component.js
ext/afform/core/ang/af/afField.component.js
ext/afform/core/ang/af/afFieldset.directive.js
ext/afform/core/ang/af/afForm.component.js
ext/afform/core/ang/af/fields/Existing.html

index fa12d3f8432c65ec64e76c9fa908f636d7dfd611..6896abf1b20e78f6e62ef7f8f82fa63554c6fdd3 100644 (file)
     {{:: ts('Required') }}
   </a>
 </li>
-<li ng-if="$ctrl.fieldDefn.input_type === 'Existing'">
-  <a href ng-click="toggleSkipPermissions(); $event.stopPropagation(); $event.target.blur();" title="{{:: ts('Allows non-permissioned users to access any record returned by the saved search') }}">
-    <i class="crm-i fa-{{ getProp('skip_permissions') ? 'check-' : '' }}square-o"></i>
-    {{:: ts('Disable Permission Checks') }}
-  </a>
-</li>
 <li ng-if="$ctrl.fieldDefn.input_type !== 'Existing'">
   <a href ng-click="toggleDefaultValue(); $event.stopPropagation(); $event.target.blur();" title="{{:: ts('Pre-fill this field with a value') }}">
     <i class="crm-i fa-{{ $ctrl.hasDefaultValue ? 'check-' : '' }}square-o"></i>
index b17c0294427980c60ef0a501a48a25adbc524084..177e9b1700d97b1f5a429752abca4647f059ebd6 100644 (file)
         getSet('required', !getSet('required'));
       };
 
-      $scope.toggleSkipPermissions = function() {
-        getSet('skip_permissions', !getSet('skip_permissions'));
-      };
-
       $scope.toggleHelp = function(position) {
         getSet('help_' + position, $scope.propIsset('help_' + position) ? null : (ctrl.getDefn()['help_' + position] || ts('Enter text')));
       };
index be6cc1a16c777324cc6624ca68ffa53060c4f5eb..b84bdfb324c7b95886646644acd992532d1e7f48 100644 (file)
@@ -180,7 +180,7 @@ class AfformMetadataInjector {
       }
     }
     // Id field for selecting existing entity
-    if ($field['name'] === CoreUtil::getIdFieldName($entityName)) {
+    if ($action === 'update' && $field['name'] === CoreUtil::getIdFieldName($entityName)) {
       $entityTitle = CoreUtil::getInfoItem($entityName, 'title');
       $field['input_type'] = 'Existing';
       $field['entity'] = $entityName;
index 870254d1c161c35664f301220719c459012082bc..17c25b78f3542c119c782f1871bf7ca951dbccc5 100644 (file)
@@ -68,9 +68,13 @@ abstract class AbstractProcessor extends \Civi\Api4\Generic\AbstractAction {
   protected function loadEntities() {
     foreach ($this->_formDataModel->getEntities() as $entityName => $entity) {
       $this->_entityIds[$entityName] = [];
+      $idField = CoreUtil::getIdFieldName($entity['type']);
       if (!empty($entity['actions']['update'])) {
-        if (!empty($this->args[$entityName]) && !empty($entity['url-autofill'])) {
-          $ids = array_map('trim', explode(',', $this->args[$entityName]));
+        if (
+          !empty($this->args[$entityName]) &&
+          (!empty($entity['url-autofill']) || isset($entity['fields'][$idField]))
+        ) {
+          $ids = (array) $this->args[$entityName];
           // Limit number of records to 1 unless using af-repeat
           $ids = array_slice($ids, 0, !empty($entity['af-repeat']) ? $entity['max'] ?? NULL : 1);
           $this->loadEntity($entity, $ids);
@@ -90,10 +94,17 @@ abstract class AbstractProcessor extends \Civi\Api4\Generic\AbstractAction {
    */
   private function loadEntity(array $entity, array $ids) {
     $api4 = $this->_formDataModel->getSecureApi4($entity['name']);
+    $idField = CoreUtil::getIdFieldName($entity['type']);
+    if (!empty($entity['fields'][$idField]['saved_search'])) {
+      $ids = $this->validateBySavedSearch($entity, $idField, $ids);
+    }
+    if (!$ids) {
+      return;
+    }
     $result = $api4($entity['type'], 'get', [
       'where' => [['id', 'IN', $ids]],
       'select' => array_keys($entity['fields']),
-    ])->indexBy('id');
+    ])->indexBy($idField);
     foreach ($ids as $index => $id) {
       $this->_entityIds[$entity['name']][$index] = [
         'id' => isset($result[$id]) ? $id : NULL,
@@ -133,6 +144,23 @@ abstract class AbstractProcessor extends \Civi\Api4\Generic\AbstractAction {
     }
   }
 
+  private function validateBySavedSearch($entity, array $ids) {
+    $idField = CoreUtil::getIdFieldName($entity['type']);
+    $fetched = civicrm_api4($entity['type'], 'autocomplete', [
+      'ids' => $ids,
+      'formName' => 'afform:' . $this->name,
+      'fieldName' => $entity['name'] . ':' . $idField,
+    ])->indexBy($idField);
+    $validIds = [];
+    // Preserve keys
+    foreach ($ids as $index => $id) {
+      if (isset($fetched[$id])) {
+        $validIds[$index] = $id;
+      }
+    }
+    return $validIds;
+  }
+
   /**
    * @return array
    */
index 2b5335f4750194cb66540a9a44b085bfcdd56809..ece48d09321c9843d6e9cebad527ec80e3740664 100644 (file)
@@ -57,7 +57,7 @@ class AutocompleteSubscriber implements EventSubscriberInterface {
       if (!empty($entity['fields'][$fieldName]['defn'])) {
         $defn = \CRM_Utils_JS::decode($entity['fields'][$fieldName]['defn']);
       }
-      $apiRequest->setCheckPermissions(empty($defn['skip_permissions']));
+      $apiRequest->setCheckPermissions($entity['security'] !== 'FBAC');
       $apiRequest->setSavedSearch($defn['saved_search'] ?? NULL);
     }
   }
index 6f0fef14d2ccf265cef2f9b6fc9a0a5b2c4ac1f1..c778856ff288fee18fe8dbf4da0115b1f4820da0 100644 (file)
@@ -4,6 +4,7 @@
   var modelProps = {
     type: '@',
     data: '=',
+    actions: '=',
     modelName: '@name',
     label: '@',
     autofill: '@'
@@ -16,6 +17,7 @@
 
       this.$onInit = function() {
         var entity = _.pick(this, _.keys(modelProps));
+        entity.actions = entity.actions || {update: true, create: true};
         entity.id = null;
         this.afForm.registerEntity(entity);
       };
index aa83eac8fbae9dced69200437e697e5dc774f4c0..1e0cda0dbc9f842246b0bad852804cb959ea23c8 100644 (file)
         }
       };
 
+      ctrl.onSelectExisting = function() {
+        var val = $scope.getSetSelect();
+        var entity = ctrl.afFieldset.modelName;
+        var index = ctrl.getEntityIndex();
+        ctrl.afFieldset.afFormCtrl.loadData(entity, index, val);
+      };
+
       // Params for the Afform.submitFile API when uploading a file field
       ctrl.getFileUploadParams = function() {
         return {
index 384fad9b90f07c37c30f2db1ad9af9d414e8e236..d67216de0505a40cc5bc2a7cc755bc834040fa04 100644 (file)
@@ -24,6 +24,9 @@
             // If there is no Afform entity, get the name of embedded search display
             $element.find('[search-name][display-name]').attr('display-name');
         };
+        this.getEntity = function() {
+          return this.afFormCtrl.getEntity(this.modelName);
+        };
         this.getEntityType = function() {
           return this.afFormCtrl.getEntity(this.modelName).type;
         };
index 79514948a479e6f5aad17c0358083711a9826312..314d710cc515f95b5114c969a53a64544c99b122 100644 (file)
       this.getFormMeta = function getFormMeta() {
         return $scope.$parent.meta;
       };
-      this.loadData = function() {
-        var toLoad = 0;
-        args = _.assign({}, $scope.$parent.routeParams || {}, $scope.$parent.options || {});
-        _.each(schema, function(entity, entityName) {
-          if (args[entityName] || entity.autofill) {
-            toLoad++;
-          }
-        });
+      // With no arguments this will prefill the entire form based on url args
+      // With selectedEntity, selectedIndex & selectedId provided this will prefill a single entity
+      this.loadData = function(selectedEntity, selectedIndex, selectedId) {
+        var toLoad = 0,
+          params = {name: ctrl.getFormMeta().name, args: {}};
+        // Load single entity
+        if (selectedEntity) {
+          toLoad = selectedId;
+          params.args[selectedEntity] = {};
+          params.args[selectedEntity][selectedIndex] = selectedId;
+        } else {
+          args = _.assign({}, $scope.$parent.routeParams || {}, $scope.$parent.options || {});
+          _.each(schema, function (entity, entityName) {
+            if (args[entityName] || entity.autofill) {
+              toLoad++;
+            }
+            if (args[entityName] && typeof args[entityName] === 'string') {
+              args[entityName] = args[entityName].split(',');
+            }
+          });
+          params.args = args;
+        }
         if (toLoad) {
-          crmApi4('Afform', 'prefill', {name: ctrl.getFormMeta().name, args: args})
+          crmApi4('Afform', 'prefill', params)
             .then(function(result) {
               _.each(result, function(item) {
                 data[item.name] = data[item.name] || {};
       function replaceTokens(str, vars) {
         function recurse(stack, values) {
           _.each(values, function(value, key) {
-            console.log('value:' + value, stack);
             if (_.isArray(value) || _.isPlainObject(value)) {
               recurse(stack.concat([key]), value);
             } else {
index 10855d4927e60d3e9e14b1390c790f1eabc785a2..7824c6dfdc2ac11e3437cfdd17a18d23f2750366 100644 (file)
@@ -1 +1,9 @@
-<input class="form-control" id="{{:: fieldId }}" ng-model="getSetSelect" ng-model-options="{getterSetter: true}" crm-autocomplete="$ctrl.defn.entity" crm-autocomplete-params="{formName: 'afform:' + $ctrl.afFieldset.getFormName(), fieldName: $ctrl.afFieldset.modelName + ':' + $ctrl.fieldName}" placeholder="{{:: $ctrl.defn.input_attrs.placeholder }}" >
+<input id="{{:: fieldId }}"
+       class="form-control"
+       ng-disabled="$ctrl.afFieldset.getEntity().actions.update === false"
+       ng-model="getSetSelect"
+       ng-model-options="{getterSetter: true}"
+       crm-autocomplete="$ctrl.defn.entity"
+       crm-autocomplete-params="{formName: 'afform:' + $ctrl.afFieldset.getFormName(), fieldName: $ctrl.afFieldset.modelName + ':' + $ctrl.fieldName}"
+       placeholder="{{:: $ctrl.defn.input_attrs.placeholder }}"
+       ng-change="$ctrl.onSelectExisting()" >