diff --git a/ext/afform/core/Civi/Api4/Action/Afform/AbstractProcessor.php b/ext/afform/core/Civi/Api4/Action/Afform/AbstractProcessor.php index fc0451bf34ee..df07e413f0e3 100644 --- a/ext/afform/core/Civi/Api4/Action/Afform/AbstractProcessor.php +++ b/ext/afform/core/Civi/Api4/Action/Afform/AbstractProcessor.php @@ -102,7 +102,13 @@ protected function loadEntities() { public function loadEntity(array $entity, array $ids) { // Limit number of records based on af-repeat settings // If 'min' is set then it is repeatable, and max will either be a number or NULL for unlimited. - $ids = array_slice($ids, 0, isset($entity['min']) ? $entity['max'] : 1); + if (isset($entity['min']) && isset($entity['max'])) { + foreach (array_keys($ids) as $index) { + if ($index >= $entity['max']) { + unset($ids[$index]); + } + } + } $api4 = $this->_formDataModel->getSecureApi4($entity['name']); $idField = CoreUtil::getIdFieldName($entity['type']); diff --git a/ext/afform/core/ang/af/afForm.component.js b/ext/afform/core/ang/af/afForm.component.js index d711b00b8aea..3208ff8b1351 100644 --- a/ext/afform/core/ang/af/afForm.component.js +++ b/ext/afform/core/ang/af/afForm.component.js @@ -43,11 +43,11 @@ // 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, selectedField) { - var toLoad = 0, - params = {name: ctrl.getFormMeta().name, args: {}}; + let toLoad = false; + const params = {name: ctrl.getFormMeta().name, args: {}}; // Load single entity if (selectedEntity) { - toLoad = 1; + toLoad = !!selectedId; params.matchField = selectedField; params.args[selectedEntity] = {}; params.args[selectedEntity][selectedIndex] = selectedId; @@ -57,7 +57,7 @@ args = _.assign({}, $scope.$parent.routeParams || {}, $scope.$parent.options || {}); _.each(schema, function (entity, entityName) { if (args[entityName] || entity.actions.update) { - toLoad++; + toLoad = true; } if (args[entityName] && typeof args[entityName] === 'string') { args[entityName] = args[entityName].split(','); @@ -67,19 +67,23 @@ } if (toLoad) { crmApi4('Afform', 'prefill', params) - .then(function(result) { - _.each(result, function(item) { - data[item.name] = data[item.name] || {}; - _.extend(data[item.name], item.values, schema[item.name].data || {}); + .then((result) => { + result.forEach((item) => { + // Use _.each() because item.values could be cast as an object if array keys are not sequential + _.each(item.values, (values, index) => { + data[item.name][index].joins = {}; + angular.merge(data[item.name][index], values, {fields: _.cloneDeep(schema[item.name].data || {})}); + }); }); }); } // Clear existing contact selection else if (selectedEntity) { - data[selectedEntity][selectedIndex].fields = {}; - if (data[selectedEntity][selectedIndex].joins) { - data[selectedEntity][selectedIndex].joins = {}; - } + // Delete object keys without breaking object references + Object.keys(data[selectedEntity][selectedIndex].fields).forEach(key => delete data[selectedEntity][selectedIndex].fields[key]); + // Fill pre-set values + angular.merge(data[selectedEntity][selectedIndex].fields, _.cloneDeep(schema[selectedEntity].data || {})); + data[selectedEntity][selectedIndex].joins = {}; } }; diff --git a/ext/afform/mock/tests/phpunit/api/v4/AfformPrefillUsageTest.php b/ext/afform/mock/tests/phpunit/api/v4/AfformPrefillUsageTest.php new file mode 100644 index 000000000000..11e97469689b --- /dev/null +++ b/ext/afform/mock/tests/phpunit/api/v4/AfformPrefillUsageTest.php @@ -0,0 +1,103 @@ + + +
+
+ + + +
+
+ +
+
+ +
+
+ +EOHTML; + + $this->useValues([ + 'layout' => $layout, + 'permission' => CRM_Core_Permission::ALWAYS_ALLOW_PERMISSION, + ]); + + $cid = $this->saveTestRecords('Contact', [ + 'records' => [ + ['first_name' => 'A', 'last_name' => '_A', 'preferred_communication_method' => [1, 3]], + ['first_name' => 'B', 'last_name' => '_B', 'email_primary.email' => 'b@afform.test'], + ['first_name' => 'C', 'last_name' => '_C'], + ['first_name' => 'D', 'last_name' => '_D', 'email_primary.email' => 'd@afform.test'], + ], + ])->column('id'); + + $this->saveTestRecords('Phone', [ + 'records' => [ + ['contact_id' => $cid[0], 'phone' => '0-1'], + ['contact_id' => $cid[0], 'phone' => '0-2'], + ['contact_id' => $cid[0], 'phone' => '0-3'], + ['contact_id' => $cid[2], 'phone' => '2-1'], + ['contact_id' => $cid[3], 'phone' => '3-1'], + ], + ]); + + $prefill = Civi\Api4\Afform::prefill() + ->setName($this->formName) + ->setArgs(['Individual1' => $cid]) + ->execute() + ->indexBy('name'); + + // Form entity has `max="3"` + $this->assertCount(3, $prefill['Individual1']['values']); + $this->assertEquals('A', $prefill['Individual1']['values'][0]['fields']['first_name']); + $this->assertEquals([1, 3], $prefill['Individual1']['values'][0]['fields']['preferred_communication_method']); + $this->assertEquals('B', $prefill['Individual1']['values'][1]['fields']['first_name']); + $this->assertEquals('C', $prefill['Individual1']['values'][2]['fields']['first_name']); + + // One email should have been filled + $this->assertCount(1, $prefill['Individual1']['values'][1]['joins']['Email']); + $this->assertEquals('b@afform.test', $prefill['Individual1']['values'][1]['joins']['Email'][0]['email']); + $this->assertEmpty($prefill['Individual1']['values'][0]['joins']['Email']); + $this->assertEmpty($prefill['Individual1']['values'][2]['joins']['Email']); + + // Phone join has `max="2"` + $this->assertCount(2, $prefill['Individual1']['values'][0]['joins']['Phone']); + $this->assertCount(1, $prefill['Individual1']['values'][2]['joins']['Phone']); + $this->assertEquals('2-1', $prefill['Individual1']['values'][2]['joins']['Phone'][0]['phone']); + $this->assertEmpty($prefill['Individual1']['values'][1]['joins']['Phone']); + + // Prefill a specific contact for the af-repeat entity + $prefill = Civi\Api4\Afform::prefill() + ->setName($this->formName) + ->setArgs(['Individual1' => [1 => $cid[3]]]) + ->execute() + ->indexBy('name'); + $this->assertCount(1, $prefill['Individual1']['values']); + $this->assertEquals('D', $prefill['Individual1']['values'][1]['fields']['first_name']); + $this->assertEquals('_D', $prefill['Individual1']['values'][1]['fields']['last_name']); + $this->assertEquals('d@afform.test', $prefill['Individual1']['values'][1]['joins']['Email'][0]['email']); + $this->assertEquals('3-1', $prefill['Individual1']['values'][1]['joins']['Phone'][0]['phone']); + + // Form entity has `max="3"` so a forth contact (index 3) is out-of-bounds + $prefill = Civi\Api4\Afform::prefill() + ->setName($this->formName) + ->setArgs(['Individual1' => [3 => $cid[0]]]) + ->execute(); + $this->assertTrue(empty($prefill['Individual1']['values'])); + } + +}