Skip to content
Snippets Groups Projects
Commit 73cda6c5 authored by colemanw's avatar colemanw
Browse files

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.
parent 073a8065
Branches
Tags
No related merge requests found
......@@ -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
......
......@@ -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;
}
......
......@@ -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);
......
......@@ -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
......
......@@ -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();
}
}
<?php
/**
* Test case for Afform.prefill and Afform.submit.
*
* @group headless
*/
class api_v4_AfformCustomFieldUsageTest extends api_v4_AfformUsageTestCase {
public static function setUpBeforeClass(): void {
parent::setUpBeforeClass();
self::$layouts['customMulti'] = <<<EOHTML
<af-form ctrl="afform">
<af-entity data="{contact_type: 'Individual'}" type="Contact" name="Individual1" label="Individual 1" actions="{create: true, update: true}" security="FBAC" />
<fieldset af-fieldset="Individual1">
<legend class="af-text">Individual 1</legend>
<afblock-name-individual></afblock-name-individual>
<div af-join="Custom_MyThings" af-repeat="Add" max="2">
<afjoin-custom-my-things></afjoin-custom-my-things>
</div>
</fieldset>
<button class="af-button btn-primary" crm-icon="fa-check" ng-click="afform.submit()">Submit</button>
</af-form>
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]));
}
}
<?php
/**
* Test case for Afform.prefill and Afform.submit.
*
* @group headless
*/
abstract class api_v4_AfformUsageTestCase extends api_v4_AfformTestCase {
use \Civi\Test\Api3TestTrait;
use \Civi\Test\ContactTestTrait;
protected static $layouts = [];
protected $formName;
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();
}
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();
}
}
......@@ -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() {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment