From 73cda6c50c959c4e4e396c32dc93c9fb69c92fcd Mon Sep 17 00:00:00 2001 From: Coleman Watts Date: Wed, 2 Jun 2021 11:15:02 -0400 Subject: [PATCH] Afform - Fix custom field handling and add tests This ensures custom fields are handled properly by Afform, including multi-record custom field groups & their autogenerated blocks, and contact reference fields. --- CRM/Custom/Form/Group.php | 2 +- Civi/Api4/Service/Spec/SpecFormatter.php | 3 + ext/afform/core/CRM/Afform/AfformScanner.php | 2 +- .../core/Civi/Api4/Action/Afform/Submit.php | 10 ++ ...ageTest.php => AfformContactUsageTest.php} | 35 +----- .../api/v4/AfformCustomFieldUsageTest.php | 102 ++++++++++++++++++ .../phpunit/api/v4/AfformUsageTestCase.php | 43 ++++++++ tests/phpunit/api/v4/Utils/CoreUtilTest.php | 1 + 8 files changed, 162 insertions(+), 36 deletions(-) rename ext/afform/mock/tests/phpunit/api/v4/{AfformUsageTest.php => AfformContactUsageTest.php} (91%) create mode 100644 ext/afform/mock/tests/phpunit/api/v4/AfformCustomFieldUsageTest.php create mode 100644 ext/afform/mock/tests/phpunit/api/v4/AfformUsageTestCase.php diff --git a/CRM/Custom/Form/Group.php b/CRM/Custom/Form/Group.php index 9bf4be5e6f..ebb516f799 100644 --- a/CRM/Custom/Form/Group.php +++ b/CRM/Custom/Form/Group.php @@ -318,7 +318,7 @@ class CRM_Custom_Form_Group extends CRM_Core_Form { // $min_multiple = $this->add('text', 'min_multiple', ts('Minimum number of multiple records'), $attributes['min_multiple'] ); // $this->addRule('min_multiple', ts('is a numeric field') , 'numeric'); - $max_multiple = $this->add('text', 'max_multiple', ts('Maximum number of multiple records'), $attributes['max_multiple']); + $max_multiple = $this->add('number', 'max_multiple', ts('Maximum number of multiple records'), $attributes['max_multiple']); $this->addRule('max_multiple', ts('is a numeric field'), 'numeric'); //allow to edit settings if custom set is empty CRM-5258 diff --git a/Civi/Api4/Service/Spec/SpecFormatter.php b/Civi/Api4/Service/Spec/SpecFormatter.php index 4a4fc9838d..bcff7945b1 100644 --- a/Civi/Api4/Service/Spec/SpecFormatter.php +++ b/Civi/Api4/Service/Spec/SpecFormatter.php @@ -248,6 +248,9 @@ class SpecFormatter { 'Link' => 'Url', ]; $inputType = $map[$inputType] ?? $inputType; + if ($dataTypeName === 'ContactReference') { + $inputType = 'EntityRef'; + } if (in_array($inputType, ['Select', 'EntityRef'], TRUE) && !empty($data['serialize'])) { $inputAttrs['multiple'] = TRUE; } diff --git a/ext/afform/core/CRM/Afform/AfformScanner.php b/ext/afform/core/CRM/Afform/AfformScanner.php index f4e4bd8634..c7e30aa4d8 100644 --- a/ext/afform/core/CRM/Afform/AfformScanner.php +++ b/ext/afform/core/CRM/Afform/AfformScanner.php @@ -164,7 +164,7 @@ class CRM_Afform_AfformScanner { public function addComputedFields(&$record) { $name = $record['name']; // Ex: $allPaths['viewIndividual'][0] == '/var/www/foo/afform/view-individual']. - $allPaths = $this->findFilePaths()[$name]; + $allPaths = $this->findFilePaths()[$name] ?? []; // $activeLayoutPath = $this->findFilePath($name, self::LAYOUT_FILE); // $activeMetaPath = $this->findFilePath($name, self::METADATA_FILE); $localLayoutPath = $this->createSiteLocalPath($name, self::LAYOUT_FILE); diff --git a/ext/afform/core/Civi/Api4/Action/Afform/Submit.php b/ext/afform/core/Civi/Api4/Action/Afform/Submit.php index b00753dffb..17818184be 100644 --- a/ext/afform/core/Civi/Api4/Action/Afform/Submit.php +++ b/ext/afform/core/Civi/Api4/Action/Afform/Submit.php @@ -28,6 +28,16 @@ class Submit extends AbstractProcessor { foreach ($this->values[$entityName] ?? [] as $values) { // Only accept values from fields on the form $values['fields'] = array_intersect_key($values['fields'] ?? [], $entity['fields']); + // Only accept joins set on the form + $values['joins'] = array_intersect_key($values['joins'] ?? [], $entity['joins']); + foreach ($values['joins'] as $joinEntity => &$joinValues) { + // Enforce the limit set by join[max] + $joinValues = array_slice($joinValues, 0, $entity['joins'][$joinEntity]['max'] ?? NULL); + // Only accept values from join fields on the form + foreach ($joinValues as $index => $vals) { + $joinValues[$index] = array_intersect_key($vals, $entity['joins'][$joinEntity]['fields']); + } + } $entityValues[$entityName][] = $values; } // Predetermined values override submitted values diff --git a/ext/afform/mock/tests/phpunit/api/v4/AfformUsageTest.php b/ext/afform/mock/tests/phpunit/api/v4/AfformContactUsageTest.php similarity index 91% rename from ext/afform/mock/tests/phpunit/api/v4/AfformUsageTest.php rename to ext/afform/mock/tests/phpunit/api/v4/AfformContactUsageTest.php index fd8506608d..45222a343d 100644 --- a/ext/afform/mock/tests/phpunit/api/v4/AfformUsageTest.php +++ b/ext/afform/mock/tests/phpunit/api/v4/AfformContactUsageTest.php @@ -5,13 +5,7 @@ * * @group headless */ -class api_v4_AfformUsageTest extends api_v4_AfformTestCase { - use \Civi\Test\Api3TestTrait; - use \Civi\Test\ContactTestTrait; - - protected static $layouts = []; - - protected $formName; +class api_v4_AfformContactUsageTest extends api_v4_AfformUsageTestCase { public static function setUpBeforeClass(): void { parent::setUpBeforeClass(); @@ -63,21 +57,6 @@ EOHTML; EOHTML; } - public function setUp(): void { - parent::setUp(); - Civi\Api4\Afform::revert(FALSE) - ->addWhere('type', '=', 'block') - ->execute(); - $this->formName = 'mock' . rand(0, 100000); - } - - public function tearDown(): void { - Civi\Api4\Afform::revert(FALSE) - ->addWhere('name', '=', $this->formName) - ->execute(); - parent::tearDown(); - } - public function testAboutMeAllowed(): void { $this->useValues([ 'layout' => self::$layouts['aboutMe'], @@ -312,16 +291,4 @@ EOHTML; $this->assertEquals($orgEmail, $contact['org.display_name']); } - protected function useValues($values) { - $defaults = [ - 'title' => 'My form', - 'name' => $this->formName, - ]; - $full = array_merge($defaults, $values); - Civi\Api4\Afform::create(FALSE) - ->setLayoutFormat('html') - ->setValues($full) - ->execute(); - } - } diff --git a/ext/afform/mock/tests/phpunit/api/v4/AfformCustomFieldUsageTest.php b/ext/afform/mock/tests/phpunit/api/v4/AfformCustomFieldUsageTest.php new file mode 100644 index 0000000000..4249d393b0 --- /dev/null +++ b/ext/afform/mock/tests/phpunit/api/v4/AfformCustomFieldUsageTest.php @@ -0,0 +1,102 @@ + + +
+ Individual 1 + +
+ +
+
+ + +EOHTML; + } + + /** + * Checks that by creating a multi-record field group, + * Afform has automatically generated a block to go with it, + * which can be submitted multiple times + */ + public function testMultiRecordCustomBlock(): void { + $customGroup = \Civi\Api4\CustomGroup::create(FALSE) + ->addValue('name', 'MyThings') + ->addValue('title', 'My Things') + ->addValue('style', 'Tab with table') + ->addValue('extends', 'Contact') + ->addValue('is_multiple', TRUE) + ->addValue('max_multiple', 2) + ->addChain('fields', \Civi\Api4\CustomField::save() + ->addDefault('custom_group_id', '$id') + ->setRecords([ + ['name' => 'my_text', 'label' => 'My Text', 'data_type' => 'String', 'html_type' => 'Text'], + ['name' => 'my_friend', 'label' => 'My Friend', 'data_type' => 'ContactReference', 'html_type' => 'Autocomplete-Select'], + ]) + ) + ->execute(); + + // Creating a custom group should automatically create an afform block + $block = \Civi\Api4\Afform::get() + ->addWhere('name', '=', 'afjoinCustom_MyThings') + ->setLayoutFormat('shallow') + ->setFormatWhitespace(TRUE) + ->execute()->first(); + $this->assertEquals(2, $block['repeat']); + $this->assertEquals('my_text', $block['layout'][0]['name']); + $this->assertEquals('my_friend', $block['layout'][1]['name']); + + $cid1 = $this->individualCreate([], 1); + $cid2 = $this->individualCreate([], 2); + + $this->useValues([ + 'layout' => self::$layouts['customMulti'], + 'permission' => CRM_Core_Permission::ALWAYS_ALLOW_PERMISSION, + ]); + $firstName = uniqid(__FUNCTION__); + $values = [ + 'Individual1' => [ + [ + 'fields' => [ + 'first_name' => $firstName, + 'last_name' => 'tester', + ], + 'joins' => [ + 'Custom_MyThings' => [ + ['my_text' => 'One', 'my_friend' => $cid1], + ['my_text' => 'Two', 'my_friend' => $cid2], + ['my_text' => 'Not allowed', 'my_friend' => $cid2], + ], + ], + ], + ], + ]; + Civi\Api4\Afform::submit() + ->setName($this->formName) + ->setValues($values) + ->execute(); + $contact = \Civi\Api4\Contact::get(FALSE) + ->addWhere('first_name', '=', $firstName) + ->addJoin('Custom_MyThings AS Custom_MyThings', 'LEFT', ['id', '=', 'Custom_MyThings.entity_id']) + ->addSelect('Custom_MyThings.my_text', 'Custom_MyThings.my_friend') + ->addOrderBy('Custom_MyThings.id') + ->execute(); + $this->assertEquals('One', $contact[0]['Custom_MyThings.my_text']); + $this->assertEquals($cid1, $contact[0]['Custom_MyThings.my_friend']); + $this->assertEquals('Two', $contact[1]['Custom_MyThings.my_text']); + $this->assertEquals($cid2, $contact[1]['Custom_MyThings.my_friend']); + $this->assertTrue(empty($contact[2])); + } + +} diff --git a/ext/afform/mock/tests/phpunit/api/v4/AfformUsageTestCase.php b/ext/afform/mock/tests/phpunit/api/v4/AfformUsageTestCase.php new file mode 100644 index 0000000000..9e121c5e72 --- /dev/null +++ b/ext/afform/mock/tests/phpunit/api/v4/AfformUsageTestCase.php @@ -0,0 +1,43 @@ +addWhere('type', '=', 'block') + ->execute(); + $this->formName = 'mock' . rand(0, 100000); + } + + public function tearDown(): void { + Civi\Api4\Afform::revert(FALSE) + ->addWhere('name', '=', $this->formName) + ->execute(); + parent::tearDown(); + } + + protected function useValues($values) { + $defaults = [ + 'title' => 'My form', + 'name' => $this->formName, + ]; + $full = array_merge($defaults, $values); + Civi\Api4\Afform::create(FALSE) + ->setLayoutFormat('html') + ->setValues($full) + ->execute(); + } + +} diff --git a/tests/phpunit/api/v4/Utils/CoreUtilTest.php b/tests/phpunit/api/v4/Utils/CoreUtilTest.php index 2f81dd15fd..31b419d816 100644 --- a/tests/phpunit/api/v4/Utils/CoreUtilTest.php +++ b/tests/phpunit/api/v4/Utils/CoreUtilTest.php @@ -48,6 +48,7 @@ class CoreUtilTest extends UnitTestCase { ->execute()->first(); $this->assertEquals('Custom_' . $multiGroup['name'], CoreUtil::getApiNameFromTableName($multiGroup['table_name'])); + $this->assertEquals($multiGroup['table_name'], CoreUtil::getTableName('Custom_' . $multiGroup['name'])); } public function testGetApiClass() { -- 2.25.1