From f712b2a88b23ed8cb7f2358b8876efcc01185220 Mon Sep 17 00:00:00 2001 From: Coleman Watts Date: Sun, 18 Sep 2022 15:23:39 -0400 Subject: [PATCH] Afform - Add AfformBehavior entity A "Behavior" is a PHP class which adds additional configuration and functionality to an Afform entity. --- .../Civi/Api4/Action/Afform/LoadAdminData.php | 8 ++ ext/afform/admin/ang/afGuiEditor.js | 5 + .../ang/afGuiEditor/afGuiEntity.component.js | 4 +- .../admin/ang/afGuiEditor/afGuiEntity.html | 11 ++- .../ang/afGuiEditor/entityConfig/Contact.html | 10 -- .../{Generic.html => Options.html} | 0 .../core/Civi/Afform/AbstractBehavior.php | 53 ++++++++++ .../core/Civi/Afform/BehaviorInterface.php | 50 ++++++++++ .../Afform/Event/AfformEventEntityTrait.php | 98 +++++++++++++++++++ .../Civi/Afform/Event/AfformPrefillEvent.php | 28 ++++++ .../Civi/Afform/Event/AfformSubmitEvent.php | 90 +---------------- .../Api4/Action/Afform/AbstractProcessor.php | 5 +- .../Civi/Api4/Action/AfformBehavior/Get.php | 41 ++++++++ ext/afform/core/Civi/Api4/AfformBehavior.php | 69 +++++++++++++ ext/afform/core/ang/af/afForm.component.js | 2 +- 15 files changed, 370 insertions(+), 104 deletions(-) delete mode 100644 ext/afform/admin/ang/afGuiEditor/entityConfig/Contact.html rename ext/afform/admin/ang/afGuiEditor/entityConfig/{Generic.html => Options.html} (100%) create mode 100644 ext/afform/core/Civi/Afform/AbstractBehavior.php create mode 100644 ext/afform/core/Civi/Afform/BehaviorInterface.php create mode 100644 ext/afform/core/Civi/Afform/Event/AfformEventEntityTrait.php create mode 100644 ext/afform/core/Civi/Afform/Event/AfformPrefillEvent.php create mode 100644 ext/afform/core/Civi/Api4/Action/AfformBehavior/Get.php create mode 100644 ext/afform/core/Civi/Api4/AfformBehavior.php diff --git a/ext/afform/admin/Civi/Api4/Action/Afform/LoadAdminData.php b/ext/afform/admin/Civi/Api4/Action/Afform/LoadAdminData.php index c1b0f1e8b1..0828b562b2 100644 --- a/ext/afform/admin/Civi/Api4/Action/Afform/LoadAdminData.php +++ b/ext/afform/admin/Civi/Api4/Action/Afform/LoadAdminData.php @@ -4,6 +4,7 @@ namespace Civi\Api4\Action\Afform; use Civi\AfformAdmin\AfformAdminMeta; use Civi\Api4\Afform; +use Civi\Api4\AfformBehavior; use Civi\Api4\Utils\CoreUtil; /** @@ -221,6 +222,13 @@ class LoadAdminData extends \Civi\Api4\Generic\AbstractAction { foreach (array_diff($entities, $this->skipEntities) as $entity) { $info['entities'][$entity] = AfformAdminMeta::getApiEntity($entity); $info['fields'][$entity] = AfformAdminMeta::getFields($entity, ['action' => $getFieldsMode]); + $behaviors = AfformBehavior::get(FALSE) + ->addWhere('entities', 'CONTAINS', $entity) + ->execute(); + foreach ($behaviors as $behavior) { + $behavior['modes'] = $behavior['modes'][$entity]; + $info['behaviors'][$entity][] = $behavior; + } } $info['blocks'] = array_values($info['blocks']); diff --git a/ext/afform/admin/ang/afGuiEditor.js b/ext/afform/admin/ang/afGuiEditor.js index b0e6605982..120798c721 100644 --- a/ext/afform/admin/ang/afGuiEditor.js +++ b/ext/afform/admin/ang/afGuiEditor.js @@ -112,11 +112,16 @@ CRM.afGuiEditor.blocks[block.directive_name] = block; } }); + // Add behavior data + CRM.afGuiEditor.behaviors = CRM.afGuiEditor.behaviors || {}; + _.extend(CRM.afGuiEditor.behaviors, data.behaviors); + // Add entities _.each(data.entities, function(entity, entityName) { if (!CRM.afGuiEditor.entities[entityName]) { CRM.afGuiEditor.entities[entityName] = entity; } }); + // Combine entities with fields _.each(data.fields, function(fields, entityName) { if (CRM.afGuiEditor.entities[entityName]) { CRM.afGuiEditor.entities[entityName].fields = fields; diff --git a/ext/afform/admin/ang/afGuiEditor/afGuiEntity.component.js b/ext/afform/admin/ang/afGuiEditor/afGuiEntity.component.js index f8a051f675..2a783443db 100644 --- a/ext/afform/admin/ang/afGuiEditor/afGuiEntity.component.js +++ b/ext/afform/admin/ang/afGuiEditor/afGuiEntity.component.js @@ -26,8 +26,8 @@ return afGui.meta.entities[ctrl.getEntityType()]; }; - $scope.getAdminTpl = function() { - return $scope.getMeta().admin_tpl || '~/afGuiEditor/entityConfig/Generic.html'; + this.getBehaviors = function() { + return CRM.afGuiEditor.behaviors[ctrl.getEntityType()]; }; $scope.getField = afGui.getField; diff --git a/ext/afform/admin/ang/afGuiEditor/afGuiEntity.html b/ext/afform/admin/ang/afGuiEditor/afGuiEntity.html index 712e648dfb..9da4dd5505 100644 --- a/ext/afform/admin/ang/afGuiEditor/afGuiEntity.html +++ b/ext/afform/admin/ang/afGuiEditor/afGuiEntity.html @@ -56,5 +56,14 @@
{{:: ts('Options') }} -
+
+
+ +
+ + +

{{:: behavior.description }}

diff --git a/ext/afform/admin/ang/afGuiEditor/entityConfig/Contact.html b/ext/afform/admin/ang/afGuiEditor/entityConfig/Contact.html deleted file mode 100644 index 7d53b8e261..0000000000 --- a/ext/afform/admin/ang/afGuiEditor/entityConfig/Contact.html +++ /dev/null @@ -1,10 +0,0 @@ -
-
- - -
diff --git a/ext/afform/admin/ang/afGuiEditor/entityConfig/Generic.html b/ext/afform/admin/ang/afGuiEditor/entityConfig/Options.html similarity index 100% rename from ext/afform/admin/ang/afGuiEditor/entityConfig/Generic.html rename to ext/afform/admin/ang/afGuiEditor/entityConfig/Options.html diff --git a/ext/afform/core/Civi/Afform/AbstractBehavior.php b/ext/afform/core/Civi/Afform/AbstractBehavior.php new file mode 100644 index 0000000000..96b1175274 --- /dev/null +++ b/ext/afform/core/Civi/Afform/AbstractBehavior.php @@ -0,0 +1,53 @@ +` all entities are treated as if they may be multi) + * E.g. $entityIds['Individual1'] = [1]; + * + * @var array + */ + private $entityIds; + + /** + * Get the entity type associated with this event + * @return string + */ + public function getEntityType(): string { + return $this->entityType; + } + + /** + * Get the entity name associated with this event + * @return string + */ + public function getEntityName(): string { + return $this->entityName; + } + + /** + * @return array{type: string, fields: array, joins: array, security: string, actions: array} + */ + public function getEntity() { + return $this->getFormDataModel()->getEntity($this->entityName); + } + + /** + * @return callable + * API4-style + */ + public function getSecureApi4() { + return $this->getFormDataModel()->getSecureApi4($this->entityName); + } + + /** + * @param int $index + * @param int|string $entityId + * @return $this + */ + public function setEntityId($index, $entityId) { + $idField = CoreUtil::getIdFieldName($this->entityName); + $this->entityIds[$this->entityName][$index][$idField] = $entityId; + $this->records[$index]['fields'][$idField] = $entityId; + return $this; + } + + /** + * Get the id of a saved record + * @param int $index + * @return mixed + */ + public function getEntityId(int $index = 0) { + $idField = CoreUtil::getIdFieldName($this->entityName); + return $this->entityIds[$this->entityName][$index][$idField] ?? NULL; + } + + /** + * @param int $index + * @param string $joinEntity + * @param array $joinIds + * @return $this + */ + public function setJoinIds($index, $joinEntity, $joinIds) { + $idField = CoreUtil::getIdFieldName($joinEntity); + $this->entityIds[$this->entityName][$index]['_joins'][$joinEntity] = \CRM_Utils_Array::filterColumns($joinIds, [$idField]); + return $this; + } + +} diff --git a/ext/afform/core/Civi/Afform/Event/AfformPrefillEvent.php b/ext/afform/core/Civi/Afform/Event/AfformPrefillEvent.php new file mode 100644 index 0000000000..7f02c9423e --- /dev/null +++ b/ext/afform/core/Civi/Afform/Event/AfformPrefillEvent.php @@ -0,0 +1,28 @@ +entityType = $entityType; + $this->entityName = $entityName; + $this->entityIds =& $entityIds; + } + +} diff --git a/ext/afform/core/Civi/Afform/Event/AfformSubmitEvent.php b/ext/afform/core/Civi/Afform/Event/AfformSubmitEvent.php index 8107e38f3a..522cf21900 100644 --- a/ext/afform/core/Civi/Afform/Event/AfformSubmitEvent.php +++ b/ext/afform/core/Civi/Afform/Event/AfformSubmitEvent.php @@ -3,7 +3,6 @@ namespace Civi\Afform\Event; use Civi\Afform\FormDataModel; use Civi\Api4\Action\Afform\Submit; -use Civi\Api4\Utils\CoreUtil; /** * Handle submission of an "" entity (or set of entities in the case of ``). @@ -21,6 +20,7 @@ use Civi\Api4\Utils\CoreUtil; * @package Civi\Afform\Event */ class AfformSubmitEvent extends AfformBaseEvent { + use AfformEventEntityTrait; /** * One or more records to be saved for this entity. @@ -29,30 +29,6 @@ class AfformSubmitEvent extends AfformBaseEvent { */ public $records; - /** - * @var string - * entityType - */ - private $entityType; - - /** - * @var string - * entityName e.g. Individual1, Activity1, - */ - private $entityName; - - /** - * Ids of each saved entity. - * - * Each key in the array corresponds to the name of an entity, - * and the value is an array of ids - * (because of `` all entities are treated as if they may be multi) - * E.g. $entityIds['Individual1'] = [1]; - * - * @var array - */ - private $entityIds; - /** * AfformSubmitEvent constructor. * @@ -72,58 +48,6 @@ class AfformSubmitEvent extends AfformBaseEvent { $this->entityIds =& $entityIds; } - /** - * Get the entity type associated with this event - * @return string - */ - public function getEntityType(): string { - return $this->entityType; - } - - /** - * Get the entity name associated with this event - * @return string - */ - public function getEntityName(): string { - return $this->entityName; - } - - /** - * @return array{type: string, fields: array, joins: array, security: string, actions: array} - */ - public function getEntity() { - return $this->getFormDataModel()->getEntity($this->entityName); - } - - /** - * @return callable - * API4-style - */ - public function getSecureApi4() { - return $this->getFormDataModel()->getSecureApi4($this->entityName); - } - - /** - * @param int $index - * @param int|string $entityId - * @return $this - */ - public function setEntityId($index, $entityId) { - $idField = CoreUtil::getIdFieldName($this->entityName); - $this->entityIds[$this->entityName][$index][$idField] = $entityId; - return $this; - } - - /** - * Get the id of a saved record - * @param int $index - * @return mixed - */ - public function getEntityId(int $index = 0) { - $idField = CoreUtil::getIdFieldName($this->entityName); - return $this->entityIds[$this->entityName][$index][$idField] ?? NULL; - } - /** * Get records to be saved * @return array @@ -141,16 +65,4 @@ class AfformSubmitEvent extends AfformBaseEvent { return $this; } - /** - * @param int $index - * @param string $joinEntity - * @param array $joinIds - * @return $this - */ - public function setJoinIds($index, $joinEntity, $joinIds) { - $idField = CoreUtil::getIdFieldName($joinEntity); - $this->entityIds[$this->entityName][$index]['_joins'][$joinEntity] = \CRM_Utils_Array::filterColumns($joinIds, [$idField]); - return $this; - } - } diff --git a/ext/afform/core/Civi/Api4/Action/Afform/AbstractProcessor.php b/ext/afform/core/Civi/Api4/Action/Afform/AbstractProcessor.php index 737c0b224b..323f3e7323 100644 --- a/ext/afform/core/Civi/Api4/Action/Afform/AbstractProcessor.php +++ b/ext/afform/core/Civi/Api4/Action/Afform/AbstractProcessor.php @@ -2,6 +2,7 @@ namespace Civi\Api4\Action\Afform; +use Civi\Afform\Event\AfformPrefillEvent; use Civi\Afform\FormDataModel; use Civi\Api4\Generic\Result; use Civi\Api4\Utils\CoreUtil; @@ -93,6 +94,8 @@ abstract class AbstractProcessor extends \Civi\Api4\Generic\AbstractAction { $this->autofillEntity($entity, $entity['autofill']); } } + $event = new AfformPrefillEvent($this->_afform, $this->_formDataModel, $this, $entity['type'], $entityName, $this->_entityIds); + \Civi::dispatcher()->dispatch('civi.afform.prefill', $event); } } @@ -102,7 +105,7 @@ abstract class AbstractProcessor extends \Civi\Api4\Generic\AbstractAction { * @param array $entity * @param array $ids */ - private function loadEntity(array $entity, array $ids) { + public function loadEntity(array $entity, array $ids) { $api4 = $this->_formDataModel->getSecureApi4($entity['name']); $idField = CoreUtil::getIdFieldName($entity['type']); if (!empty($entity['fields'][$idField]['saved_search'])) { diff --git a/ext/afform/core/Civi/Api4/Action/AfformBehavior/Get.php b/ext/afform/core/Civi/Api4/Action/AfformBehavior/Get.php new file mode 100644 index 0000000000..d37cd74d1b --- /dev/null +++ b/ext/afform/core/Civi/Api4/Action/AfformBehavior/Get.php @@ -0,0 +1,41 @@ +_itemsToGet('entity'); + + $classes = ClassScanner::get(['interface' => BehaviorInterface::class]); + /** @var \Civi\Afform\BehaviorInterface $behaviorClass */ + foreach ($classes as $behaviorClass) { + $entities = $behaviorClass::getEntities(); + // Optimization + if ($entitiesToGet && !array_intersect($entities, $entitiesToGet)) { + continue; + } + $result[] = [ + 'key' => $behaviorClass::getKey(), + 'title' => $behaviorClass::getTitle(), + 'description' => $behaviorClass::getDescription(), + 'entities' => $entities, + // Get modes for every supported entity + 'modes' => array_map([$behaviorClass, 'getModes'], array_combine($entities, $entities)), + ]; + } + return $result; + } + +} diff --git a/ext/afform/core/Civi/Api4/AfformBehavior.php b/ext/afform/core/Civi/Api4/AfformBehavior.php new file mode 100644 index 0000000000..f5a6574cee --- /dev/null +++ b/ext/afform/core/Civi/Api4/AfformBehavior.php @@ -0,0 +1,69 @@ +setCheckPermissions($checkPermissions); + } + + /** + * @return array + */ + public static function permissions() { + return [ + 'meta' => ['access CiviCRM'], + 'get' => [['administer CiviCRM', 'administer afform']], + ]; + } + + /** + * @inheritDoc + */ + public static function getFields($checkPermissions = TRUE) { + return (new Generic\BasicGetFieldsAction(__CLASS__, __FUNCTION__, function() { + return [ + [ + 'name' => 'key', + 'data_type' => 'String', + 'description' => 'Unique identifier in dashed-format, name of entity attribute for selected mode', + ], + [ + 'name' => 'title', + 'data_type' => 'String', + 'description' => 'Localized title displayed on admin screen', + ], + [ + 'name' => 'description', + 'data_type' => 'String', + 'description' => 'Optional localized description displayed on admin screen', + ], + [ + 'name' => 'entities', + 'data_type' => 'Array', + 'description' => 'Afform entities this behavior supports', + ], + [ + 'name' => 'modes', + 'data_type' => 'Array', + 'description' => 'Nested array of supported behavior modes, keyed by entity name', + ], + ]; + }))->setCheckPermissions(TRUE); + } + +} diff --git a/ext/afform/core/ang/af/afForm.component.js b/ext/afform/core/ang/af/afForm.component.js index eae28a02c7..19fb77e26c 100644 --- a/ext/afform/core/ang/af/afForm.component.js +++ b/ext/afform/core/ang/af/afForm.component.js @@ -56,7 +56,7 @@ else { args = _.assign({}, $scope.$parent.routeParams || {}, $scope.$parent.options || {}); _.each(schema, function (entity, entityName) { - if (args[entityName] || entity.autofill) { + if (args[entityName] || entity.actions.update) { toLoad++; } if (args[entityName] && typeof args[entityName] === 'string') { -- 2.25.1