Afform - Fix prefill action to return correct index when using af-repeat
authorcolemanw <coleman@civicrm.org>
Tue, 22 Aug 2023 17:45:21 +0000 (13:45 -0400)
committercolemanw <coleman@civicrm.org>
Tue, 22 Aug 2023 18:46:00 +0000 (14:46 -0400)
Before: Index was ignored so only the first entity in an af-repeat set could be autofilled
After: Index handled correctly

ext/afform/core/Civi/Api4/Action/Afform/AbstractProcessor.php
ext/afform/mock/tests/phpunit/api/v4/AfformPrefillUsageTest.php [new file with mode: 0644]

index fc0451bf34ee1eeecfda2f340e7a8d4cadd17d67..df07e413f0e3e02987608e9c809a6730b4952520 100644 (file)
@@ -102,7 +102,13 @@ abstract class AbstractProcessor extends \Civi\Api4\Generic\AbstractAction {
   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/mock/tests/phpunit/api/v4/AfformPrefillUsageTest.php b/ext/afform/mock/tests/phpunit/api/v4/AfformPrefillUsageTest.php
new file mode 100644 (file)
index 0000000..11e9746
--- /dev/null
@@ -0,0 +1,103 @@
+<?php
+
+/**
+ * Test case for Afform with autocomplete.
+ *
+ * @group headless
+ */
+class api_v4_AfformPrefillUsageTest extends api_v4_AfformUsageTestCase {
+  use \Civi\Test\Api4TestTrait;
+
+  /**
+   * Ensure that Afform restricts autocomplete results when it's set to use a SavedSearch
+   */
+  public function testPrefillWithRepeat(): void {
+    $layout = <<<EOHTML
+<af-form ctrl="afform">
+  <af-entity data="{contact_type: 'Individual'}" type="Contact" name="Individual1" label="Individual 1" actions="{create: true, update: true}" security="RBAC" url-autofill="1" />
+  <fieldset af-fieldset="Individual1" class="af-container" af-title="Individual 1" af-repeat="Add" min="1" max="3">
+    <div class="af-container">
+      <af-field name="id"></af-field>
+      <af-field name="preferred_communication_method"></af-field>
+      <afblock-name-individual></afblock-name-individual>
+    </div>
+    <div af-join="Email" af-repeat="Add" af-copy="Copy" min="1">
+      <afblock-contact-email></afblock-contact-email>
+    </div>
+    <div af-join="Phone" af-repeat="Add" af-copy="Copy" min="1" max="2">
+      <afblock-contact-phone></afblock-contact-phone>
+    </div>
+  </fieldset>
+</af-form>
+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']));
+  }
+
+}