From d617083a5f7de7f984d45616056b356592582479 Mon Sep 17 00:00:00 2001 From: Coleman Watts Date: Sat, 22 May 2021 21:00:15 -0400 Subject: [PATCH] Afform - Improve Gui, prefill & submit APIs Standardizes prefill & submit APIs to use the same logic for validating contacts. Displays correct form URL in GUI depending on is_frontend setting. Shows warning about url arguments to prevent unintentional permissions escalation. --- ext/afform/admin/afformEntities/Activity.php | 3 +- ext/afform/admin/afformEntities/Household.php | 3 +- .../admin/afformEntities/Individual.php | 3 +- .../admin/afformEntities/Organization.php | 3 +- ext/afform/admin/ang/afAdmin.js | 2 +- ext/afform/admin/ang/afAdmin/afAdminList.html | 2 +- .../ang/afGuiEditor/afGuiEditor.component.js | 61 +++++++----- .../ang/afGuiEditor/afGuiEditorCanvas.html | 6 +- .../ang/afGuiEditor/afGuiEditorPalette.html | 2 +- .../admin/ang/afGuiEditor/config-form.html | 28 +++--- .../ang/afGuiEditor/entityConfig/Contact.html | 8 +- .../ang/afGuiEditor/entityConfig/Generic.html | 30 ++++-- .../Civi/Afform/Event/AfformSubmitEvent.php | 2 +- .../Api4/Action/Afform/AbstractProcessor.php | 94 +++++++++++++++++-- .../core/Civi/Api4/Action/Afform/Prefill.php | 64 +------------ .../core/Civi/Api4/Action/Afform/Submit.php | 49 +++++----- ext/afform/mock/ang/mockPublicForm.test.php | 9 +- .../tests/phpunit/api/v4/AfformUsageTest.php | 80 ++++++++++++++-- 18 files changed, 278 insertions(+), 171 deletions(-) diff --git a/ext/afform/admin/afformEntities/Activity.php b/ext/afform/admin/afformEntities/Activity.php index 200ddeb7e8..860d68fd1d 100644 --- a/ext/afform/admin/afformEntities/Activity.php +++ b/ext/afform/admin/afformEntities/Activity.php @@ -4,8 +4,7 @@ return [ data: { source_contact_id: 'user_contact_id', activity_type_id: '' - }, - 'url-autofill': '1' + } }", 'boilerplate' => [ ['#tag' => 'af-field', 'name' => 'subject'], diff --git a/ext/afform/admin/afformEntities/Household.php b/ext/afform/admin/afformEntities/Household.php index 159388b072..20cbab0c6d 100644 --- a/ext/afform/admin/afformEntities/Household.php +++ b/ext/afform/admin/afformEntities/Household.php @@ -4,8 +4,7 @@ return [ data: { contact_type: 'Household', source: afform.title - }, - 'url-autofill': '1' + } }", 'icon' => 'fa-home', 'boilerplate' => [ diff --git a/ext/afform/admin/afformEntities/Individual.php b/ext/afform/admin/afformEntities/Individual.php index ef2d43b350..31e5ace425 100644 --- a/ext/afform/admin/afformEntities/Individual.php +++ b/ext/afform/admin/afformEntities/Individual.php @@ -4,8 +4,7 @@ return [ data: { contact_type: 'Individual', source: afform.title - }, - 'url-autofill': '1' + } }", 'icon' => 'fa-user', 'boilerplate' => [ diff --git a/ext/afform/admin/afformEntities/Organization.php b/ext/afform/admin/afformEntities/Organization.php index bd0f602b7e..ecbf7fdd70 100644 --- a/ext/afform/admin/afformEntities/Organization.php +++ b/ext/afform/admin/afformEntities/Organization.php @@ -4,8 +4,7 @@ return [ data: { contact_type: 'Organization', source: afform.title - }, - 'url-autofill': '1' + } }", 'icon' => 'fa-building', 'boilerplate' => [ diff --git a/ext/afform/admin/ang/afAdmin.js b/ext/afform/admin/ang/afAdmin.js index f50e46ddca..e118f5c8d3 100644 --- a/ext/afform/admin/ang/afAdmin.js +++ b/ext/afform/admin/ang/afAdmin.js @@ -11,7 +11,7 @@ // Load data for lists afforms: function(crmApi4) { return crmApi4('Afform', 'get', { - select: ['name', 'title', 'type', 'server_route', 'has_local', 'has_base', 'is_dashlet', 'contact_summary:label'] + select: ['name', 'title', 'type', 'server_route', 'is_public', 'has_local', 'has_base', 'is_dashlet', 'contact_summary:label'] }); } } diff --git a/ext/afform/admin/ang/afAdmin/afAdminList.html b/ext/afform/admin/ang/afAdmin/afAdminList.html index e8b50d7db1..3a61badce1 100644 --- a/ext/afform/admin/ang/afAdmin/afAdminList.html +++ b/ext/afform/admin/ang/afAdmin/afAdminList.html @@ -67,7 +67,7 @@ {{ afform.name }} - + {{ afform.server_route }} diff --git a/ext/afform/admin/ang/afGuiEditor/afGuiEditor.component.js b/ext/afform/admin/ang/afGuiEditor/afGuiEditor.component.js index ba46a74fb7..13a4bbe5ad 100644 --- a/ext/afform/admin/ang/afGuiEditor/afGuiEditor.component.js +++ b/ext/afform/admin/ang/afGuiEditor/afGuiEditor.component.js @@ -20,9 +20,8 @@ controllerAs: 'editor', controller: function($scope, crmApi4, afGui, $parse, $timeout, $location) { var ts = $scope.ts = CRM.ts('org.civicrm.afform_admin'); - $scope.crmUrl = CRM.url; - $scope.afform = null; + this.afform = null; $scope.saving = false; $scope.selectedEntityName = null; this.meta = afGui.meta; @@ -48,15 +47,15 @@ // Initialize the current form function initializeForm() { - $scope.afform = editor.data.definition; - if (!$scope.afform) { + editor.afform = editor.data.definition; + if (!editor.afform) { alert('Error: unknown form'); } if (editor.mode === 'clone') { - delete $scope.afform.name; - delete $scope.afform.server_route; - $scope.afform.is_dashlet = false; - $scope.afform.title += ' ' + ts('(copy)'); + delete editor.afform.name; + delete editor.afform.server_route; + editor.afform.is_dashlet = false; + editor.afform.title += ' ' + ts('(copy)'); } $scope.canvasTab = 'layout'; $scope.layoutHtml = ''; @@ -65,7 +64,7 @@ if (editor.getFormType() === 'form') { editor.allowEntityConfig = true; - editor.layout['#children'] = afGui.findRecursive($scope.afform.layout, {'#tag': 'af-form'})[0]['#children']; + editor.layout['#children'] = afGui.findRecursive(editor.afform.layout, {'#tag': 'af-form'})[0]['#children']; $scope.entities = _.mapValues(afGui.findRecursive(editor.layout['#children'], {'#tag': 'af-entity'}, 'name'), backfillEntityDefaults); if (editor.mode === 'create') { @@ -75,8 +74,8 @@ } else if (editor.getFormType() === 'block') { - editor.layout['#children'] = $scope.afform.layout; - editor.blockEntity = $scope.afform.join || $scope.afform.block; + editor.layout['#children'] = editor.afform.layout; + editor.blockEntity = editor.afform.join || editor.afform.block; $scope.entities[editor.blockEntity] = backfillEntityDefaults({ type: editor.blockEntity, name: editor.blockEntity, @@ -85,7 +84,7 @@ } else if (editor.getFormType() === 'search') { - editor.layout['#children'] = afGui.findRecursive($scope.afform.layout, {'af-fieldset': ''})[0]['#children']; + editor.layout['#children'] = afGui.findRecursive(editor.afform.layout, {'af-fieldset': ''})[0]['#children']; editor.searchDisplay = afGui.findRecursive(editor.layout['#children'], function(item) { return item['#tag'] && item['#tag'].indexOf('crm-search-display-') === 0; })[0]; @@ -94,18 +93,18 @@ // Set changesSaved to true on initial load, false thereafter whenever changes are made to the model $scope.changesSaved = editor.mode === 'edit' ? 1 : false; - $scope.$watch('afform', function () { + $scope.$watch('editor.afform', function () { $scope.changesSaved = $scope.changesSaved === 1; }, true); } this.getFormType = function() { - return $scope.afform.type; + return editor.afform.type; }; $scope.updateLayoutHtml = function() { $scope.layoutHtml = '...Loading...'; - crmApi4('Afform', 'convert', {layout: $scope.afform.layout, from: 'deep', to: 'html', formatWhitespace: true}) + crmApi4('Afform', 'convert', {layout: editor.afform.layout, from: 'deep', to: 'html', formatWhitespace: true}) .then(function(r){ $scope.layoutHtml = r[0].layout || '(Error)'; }) @@ -161,6 +160,7 @@ if (meta.fields) { addToCanvas(); } else { + $timeout(editor.adjustTabWidths); crmApi4('Afform', 'loadAdminData', { definition: {type: 'form'}, entity: type @@ -191,6 +191,13 @@ return $scope.selectedEntityName; }; + this.getEntityDefn = function(entity) { + if (entity.type === 'Contact' && entity.data.contact_type) { + return editor.meta.entities[entity.data.contact_type]; + } + return editor.meta.entities[entity.type]; + }; + // Scroll an entity's first fieldset into view of the canvas this.scrollToEntity = function(entityName) { var $canvas = $('#afGuiEditor-canvas-body'), @@ -204,7 +211,7 @@ }; this.getAfform = function() { - return $scope.afform; + return editor.afform; }; this.getEntities = function(filter) { @@ -212,14 +219,14 @@ }; this.toggleContactSummary = function() { - if ($scope.afform.contact_summary) { - $scope.afform.contact_summary = false; - if ($scope.afform.type === 'search') { + if (editor.afform.contact_summary) { + editor.afform.contact_summary = false; + if (editor.afform.type === 'search') { delete editor.searchDisplay.filters; } } else { - $scope.afform.contact_summary = 'block'; - if ($scope.afform.type === 'search') { + editor.afform.contact_summary = 'block'; + if (editor.afform.type === 'search') { editor.searchDisplay.filters = editor.searchFilters[0].key; } } @@ -260,6 +267,12 @@ return options; } + this.getLink = function() { + if (editor.afform.server_route) { + return CRM.url(editor.afform.server_route, null, editor.afform.is_public ? 'front' : 'back'); + } + }; + // Options for ui-sortable in field palette this.getSortableOptions = function(entityName) { if (!sortableOptions[entityName + '']) { @@ -301,21 +314,21 @@ }; $scope.save = function() { - var afform = JSON.parse(angular.toJson($scope.afform)); + var afform = JSON.parse(angular.toJson(editor.afform)); // This might be set to undefined by validation afform.server_route = afform.server_route || ''; $scope.saving = $scope.changesSaved = true; crmApi4('Afform', 'save', {formatWhitespace: true, records: [afform]}) .then(function (data) { $scope.saving = false; - $scope.afform.name = data[0].name; + editor.afform.name = data[0].name; if (editor.mode !== 'edit') { $location.url('/edit/' + data[0].name); } }); }; - $scope.$watch('afform.title', function(newTitle, oldTitle) { + $scope.$watch('editor.afform.title', function(newTitle, oldTitle) { if (typeof oldTitle === 'string') { _.each($scope.entities, function(entity) { if (entity.data && entity.data.source === oldTitle) { diff --git a/ext/afform/admin/ang/afGuiEditor/afGuiEditorCanvas.html b/ext/afform/admin/ang/afGuiEditor/afGuiEditorCanvas.html index 0b767376da..4321a8851a 100644 --- a/ext/afform/admin/ang/afGuiEditor/afGuiEditorCanvas.html +++ b/ext/afform/admin/ang/afGuiEditor/afGuiEditorCanvas.html @@ -2,14 +2,14 @@
-
+
-
+

{{:: ts('Allows CiviMail authors to easily link to this page') }}

@@ -53,7 +53,7 @@

{{:: ts('Allow CiviCRM users to add the form to their home dashboard.') }}

@@ -62,17 +62,17 @@
-

{{:: ts('Placement can be configured using the Contact Layout Editor.') }}

-
+
diff --git a/ext/afform/admin/ang/afGuiEditor/entityConfig/Contact.html b/ext/afform/admin/ang/afGuiEditor/entityConfig/Contact.html index cfa51ea41d..7d53b8e261 100644 --- a/ext/afform/admin/ang/afGuiEditor/entityConfig/Contact.html +++ b/ext/afform/admin/ang/afGuiEditor/entityConfig/Contact.html @@ -1,9 +1,9 @@
-
-