From 4bae88bfa3ab58b377ce0b3b6b04aa667005c3cb Mon Sep 17 00:00:00 2001 From: Coleman Watts Date: Fri, 4 Nov 2022 22:01:33 -0400 Subject: [PATCH] Afform - Configure SavedSearch, display, autoOpen settings for Autocomplete fields --- Civi/Api4/Generic/AutocompleteAction.php | 15 ++- ang/crmUi.js | 6 +- .../afGuiEditor/elements/afGuiField-menu.html | 16 ++- .../elements/afGuiField.component.js | 4 +- .../Subscriber/AutocompleteSubscriber.php | 127 ++++++++++++------ ext/afform/core/ang/af/fields/EntityRef.html | 1 + .../CRM/Search/DAO/SearchDisplay.php | 9 +- .../xml/schema/CRM/Search/SearchDisplay.xml | 1 + js/Common.js | 4 +- 9 files changed, 129 insertions(+), 54 deletions(-) diff --git a/Civi/Api4/Generic/AutocompleteAction.php b/Civi/Api4/Generic/AutocompleteAction.php index d900f2aa80..d249960007 100644 --- a/Civi/Api4/Generic/AutocompleteAction.php +++ b/Civi/Api4/Generic/AutocompleteAction.php @@ -28,6 +28,8 @@ use Civi\Api4\Utils\CoreUtil; * @method string getFormName() * @method $this setFieldName(string $fieldName) Set fieldName. * @method string getFieldName() + * @method $this setFilters(array $filters) + * @method array getFilters() */ class AutocompleteAction extends AbstractAction { use Traits\SavedSearchInspectorTrait; @@ -83,6 +85,14 @@ class AutocompleteAction extends AbstractAction { */ protected $key; + /** + * Search conditions that will be automatically added to the WHERE or HAVING clauses + * + * Format: [fieldName => value][] + * @var array + */ + public $filters = []; + /** * Filters set programmatically by `civi.api.prepare` listener. Automatically trusted. * @@ -138,7 +148,7 @@ class AutocompleteAction extends AbstractAction { $this->addFilter($labelField, $this->input); } - // Ensure SELECT param includes all fields & filters + // Ensure SELECT param includes all fields & trusted filters $select = [$idField]; foreach ($this->display['settings']['columns'] as $column) { if ($column['type'] === 'field') { @@ -159,7 +169,7 @@ class AutocompleteAction extends AbstractAction { $apiResult = \Civi\Api4\SearchDisplay::run(FALSE) ->setSavedSearch($this->savedSearch) ->setDisplay($this->display) - ->setFilters($this->trustedFilters) + ->setFilters($this->filters) ->setReturn($return) ->execute(); @@ -188,6 +198,7 @@ class AutocompleteAction extends AbstractAction { * @param mixed $value */ public function addFilter(string $fieldName, $value) { + $this->filters[$fieldName] = $value; $this->trustedFilters[$fieldName] = $value; } diff --git a/ang/crmUi.js b/ang/crmUi.js index 9a52b9c616..0980423758 100644 --- a/ang/crmUi.js +++ b/ang/crmUi.js @@ -721,13 +721,15 @@ bindToController: { crmAutocomplete: '<', crmAutocompleteParams: '<', - multiple: '<' + multiple: '<', + autoOpen: '<' }, controller: function($element, $timeout) { var ctrl = this; $timeout(function() { $element.crmAutocomplete(ctrl.crmAutocomplete, ctrl.crmAutocompleteParams, { - multiple: ctrl.multiple + multiple: ctrl.multiple, + minimumInputLength: ctrl.autoOpen ? 0 : 1 }); // Ensure widget is updated when model changes if (ctrl.ngModel) { diff --git a/ext/afform/admin/ang/afGuiEditor/elements/afGuiField-menu.html b/ext/afform/admin/ang/afGuiEditor/elements/afGuiField-menu.html index b0885f97bb..668ec720b7 100644 --- a/ext/afform/admin/ang/afGuiEditor/elements/afGuiField-menu.html +++ b/ext/afform/admin/ang/afGuiEditor/elements/afGuiField-menu.html @@ -8,8 +8,12 @@
  • - - + +
    +
  • +
  • +
    +
  • @@ -17,8 +21,14 @@
  • +
  • + + + {{:: ts('Auto Open') }} + +
  • - + {{:: ts('Required') }} diff --git a/ext/afform/admin/ang/afGuiEditor/elements/afGuiField.component.js b/ext/afform/admin/ang/afGuiEditor/elements/afGuiField.component.js index 86bbc8b9c8..1550f94faa 100644 --- a/ext/afform/admin/ang/afGuiEditor/elements/afGuiField.component.js +++ b/ext/afform/admin/ang/afGuiEditor/elements/afGuiField.component.js @@ -227,8 +227,8 @@ setDateOptions(); }; - $scope.toggleRequired = function() { - getSet('required', !getSet('required')); + $scope.toggleAttr = function(attr) { + getSet(attr, !getSet(attr)); }; $scope.toggleHelp = function(position) { diff --git a/ext/afform/core/Civi/Api4/Subscriber/AutocompleteSubscriber.php b/ext/afform/core/Civi/Api4/Subscriber/AutocompleteSubscriber.php index 24bec25337..7c8bb97f4b 100644 --- a/ext/afform/core/Civi/Api4/Subscriber/AutocompleteSubscriber.php +++ b/ext/afform/core/Civi/Api4/Subscriber/AutocompleteSubscriber.php @@ -14,6 +14,7 @@ namespace Civi\Api4\Subscriber; use Civi\Afform\FormDataModel; use Civi\API\Events; use Civi\Api4\Afform; +use Civi\Api4\Generic\AutocompleteAction; use Civi\Api4\Utils\CoreUtil; use Symfony\Component\EventDispatcher\EventSubscriberInterface; @@ -38,54 +39,96 @@ class AutocompleteSubscriber implements EventSubscriberInterface { public function onApiPrepare(\Civi\API\Event\PrepareEvent $event) { $apiRequest = $event->getApiRequest(); if (is_object($apiRequest) && is_a($apiRequest, 'Civi\Api4\Generic\AutocompleteAction')) { - $formName = $apiRequest->getFormName(); - if (!str_starts_with((string) $formName, 'afform:') || !strpos((string) $apiRequest->getFieldName(), ':')) { - return; - } - [$entityName, $fieldName] = explode(':', $apiRequest->getFieldName()); - // Load afform only if user has permission - $afform = Afform::get() - ->addWhere('name', '=', str_replace('afform:', '', $formName)) - ->addSelect('layout') - ->execute()->first(); - if (!$afform) { - return; - } - $formDataModel = new FormDataModel($afform['layout']); - $entity = $formDataModel->getEntity($entityName); - $isId = $fieldName === CoreUtil::getIdFieldName($entity['type']); - $fieldSpec = civicrm_api4($entity['type'], 'getFields', [ - 'checkPermissions' => FALSE, - 'where' => [['name', '=', $fieldName]], - ])->single(); - $formField = $entity['fields'][$fieldName]['defn'] ?? []; + [$formType, $formName] = array_pad(explode(':', (string) $apiRequest->getFormName()), 2, ''); + [$entityName, $fieldName] = array_pad(explode(':', (string) $apiRequest->getFieldName()), 2, ''); - // Auto-add filters defined in schema - foreach ($fieldSpec['input_attrs']['filter'] ?? [] as $key => $value) { - $apiRequest->addFilter($key, $value); + switch ($formType) { + case 'afform': + if ($formName && $entityName && $fieldName) { + $this->processAfformAutocomplete($formName, $entityName, $fieldName, $apiRequest); + } + return; + + case 'afformAdmin': + $this->processAfformAdminAutocomplete($entityName, $apiRequest); } + } + } - // For the "Existing Entity" selector, - // Look up the "type" fields (e.g. contact_type, activity_type_id, case_type_id, etc) - // And apply it as a filter if specified on the form. - if ($isId) { - if ($entity['type'] === 'Contact') { - $typeFields = ['contact_type', 'contact_sub_type']; - } - else { - $extends = array_column(\CRM_Core_BAO_CustomGroup::getCustomGroupExtendsOptions(), 'grouping', 'id'); - $typeFields = (array) ($extends[$entity['type']] ?? NULL); - } - // If entity has a type set in the values, auto-apply that to filters - foreach ($typeFields as $typeField) { - if (!empty($entity['data'][$typeField])) { - $apiRequest->addFilter($typeField, $entity['data'][$typeField]); - } + /** + * Preprocess autocomplete fields for afforms + * + * @param string $formName + * @param string $entityName + * @param string $fieldName + * @param \Civi\Api4\Generic\AutocompleteAction $apiRequest + */ + private function processAfformAutocomplete(string $formName, string $entityName, string $fieldName, AutocompleteAction $apiRequest):void { + // Load afform only if user has permission + $afform = Afform::get() + ->addWhere('name', '=', $formName) + ->addSelect('layout') + ->execute()->first(); + if (!$afform) { + return; + } + $formDataModel = new FormDataModel($afform['layout']); + $entity = $formDataModel->getEntity($entityName); + $isId = $fieldName === CoreUtil::getIdFieldName($entity['type']); + $fieldSpec = civicrm_api4($entity['type'], 'getFields', [ + 'checkPermissions' => FALSE, + 'where' => [['name', '=', $fieldName]], + ])->single(); + $formField = $entity['fields'][$fieldName]['defn'] ?? []; + + // Auto-add filters defined in schema + foreach ($fieldSpec['input_attrs']['filter'] ?? [] as $key => $value) { + $apiRequest->addFilter($key, $value); + } + + // For the "Existing Entity" selector, + // Look up the "type" fields (e.g. contact_type, activity_type_id, case_type_id, etc) + // And apply it as a filter if specified on the form. + if ($isId) { + if ($entity['type'] === 'Contact') { + $typeFields = ['contact_type', 'contact_sub_type']; + } + else { + $extends = array_column(\CRM_Core_BAO_CustomGroup::getCustomGroupExtendsOptions(), 'grouping', 'id'); + $typeFields = (array) ($extends[$entity['type']] ?? NULL); + } + // If entity has a type set in the values, auto-apply that to filters + foreach ($typeFields as $typeField) { + if (!empty($entity['data'][$typeField])) { + $apiRequest->addFilter($typeField, $entity['data'][$typeField]); } } + } + + $apiRequest->setCheckPermissions(($formField['security'] ?? NULL) !== 'FBAC'); + $apiRequest->setSavedSearch($formField['saved_search'] ?? NULL); + $apiRequest->setDisplay($formField['search_display'] ?? NULL); + } - $apiRequest->setCheckPermissions($formField['security'] !== 'FBAC'); - $apiRequest->setSavedSearch($formField['saved_search'] ?? NULL); + /** + * Preprocess autocomplete fields on AfformAdmin screens + * + * @param string $fieldName + * @param \Civi\Api4\Generic\AutocompleteAction $apiRequest + */ + private function processAfformAdminAutocomplete(string $fieldName, AutocompleteAction $apiRequest):void { + if (!\CRM_Core_Permission::check([['administer CiviCRM', 'administer afform']])) { + return; + } + switch ($fieldName) { + case 'autocompleteSavedSearch': + $apiRequest->addFilter('api_entity', $apiRequest->getFilters()['api_entity']); + return; + + case 'autocompleteDisplay': + $apiRequest->addFilter('saved_search_id.name', $apiRequest->getFilters()['saved_search_id.name']); + $apiRequest->addFilter('type', 'autocomplete'); + return; } } diff --git a/ext/afform/core/ang/af/fields/EntityRef.html b/ext/afform/core/ang/af/fields/EntityRef.html index e10509cdc5..e2d0a60414 100644 --- a/ext/afform/core/ang/af/fields/EntityRef.html +++ b/ext/afform/core/ang/af/fields/EntityRef.html @@ -7,5 +7,6 @@ crm-autocomplete="$ctrl.defn.fk_entity" crm-autocomplete-params="{formName: 'afform:' + $ctrl.afFieldset.getFormName(), fieldName: $ctrl.afFieldset.modelName + ':' + $ctrl.fieldName}" multiple="$ctrl.defn.input_attrs.multiple" + auto-open="$ctrl.defn.input_attrs.autoOpen" placeholder="{{:: $ctrl.defn.input_attrs.placeholder }}" ng-change="$ctrl.onSelectEntity()" > diff --git a/ext/search_kit/CRM/Search/DAO/SearchDisplay.php b/ext/search_kit/CRM/Search/DAO/SearchDisplay.php index 033d4b4816..5d779bec60 100644 --- a/ext/search_kit/CRM/Search/DAO/SearchDisplay.php +++ b/ext/search_kit/CRM/Search/DAO/SearchDisplay.php @@ -6,7 +6,7 @@ * * Generated from org.civicrm.search_kit/xml/schema/CRM/Search/SearchDisplay.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:1ba9d436c0e83839fe2b88a9d304762a) + * (GenCodeChecksum:5ca9cccecaa79803d415f288292376bb) */ use CRM_Search_ExtensionUtil as E; @@ -31,6 +31,13 @@ class CRM_Search_DAO_SearchDisplay extends CRM_Core_DAO { */ public static $_icon = 'fa-clone'; + /** + * Field to show when displaying a record. + * + * @var string + */ + public static $_labelField = 'label'; + /** * Should CiviCRM log any modifications to this table in the civicrm_log table. * diff --git a/ext/search_kit/xml/schema/CRM/Search/SearchDisplay.xml b/ext/search_kit/xml/schema/CRM/Search/SearchDisplay.xml index 1b1254db2c..ffb8336d5f 100644 --- a/ext/search_kit/xml/schema/CRM/Search/SearchDisplay.xml +++ b/ext/search_kit/xml/schema/CRM/Search/SearchDisplay.xml @@ -7,6 +7,7 @@ SearchKit - saved search displays true fa-clone + label id diff --git a/js/Common.js b/js/Common.js index 1c8d621b3f..2b13cd552a 100644 --- a/js/Common.js +++ b/js/Common.js @@ -527,7 +527,7 @@ if (!CRM.vars) CRM.vars = {}; $.fn.crmAutocomplete = function(entityName, apiParams, select2Options) { select2Options = select2Options || {}; return $(this).each(function() { - $(this).crmSelect2({ + $(this).crmSelect2(_.extend({ ajax: { quietMillis: 250, url: CRM.url('civicrm/ajax/api4/' + entityName + '/autocomplete'), @@ -560,7 +560,7 @@ if (!CRM.vars) CRM.vars = {}; callback(multiple ? result : result[0]); }); } - }); + }, select2Options)); }); }; -- 2.25.1