Skip to content

Commit

Permalink
Afform - Set data values in quick-add popup
Browse files Browse the repository at this point in the history
Fixes https://lab.civicrm.org/dev/core/-/issues/5477
When using a quick-add form, this ensures the predetermined data values from the parent form's entity
will be copied to the newly-created entity in the popup form.
  • Loading branch information
colemanw committed Nov 13, 2024
1 parent 30359e1 commit 1ee3cac
Show file tree
Hide file tree
Showing 11 changed files with 172 additions and 55 deletions.
35 changes: 35 additions & 0 deletions Civi/Test/Api4TestTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Civi\Api4\Generic\AbstractAction;
use Civi\Api4\Generic\Result;
use Civi\Api4\UFMatch;
use Civi\Api4\Utils\CoreUtil;

/**
Expand Down Expand Up @@ -400,4 +401,38 @@ private function getRandomValue($dataType) {
return NULL;
}

/**
* Emulate a logged in user since certain functions use that.
* value to store a record in the DB (like activity)
*
* @see https://issues.civicrm.org/jira/browse/CRM-8180
*
* @return int
* Contact ID of the created user.
* @throws \CRM_Core_Exception
*/
public function createLoggedInUser(): int {
$contactID = $this->createTestRecord('Individual', [
'first_name' => 'Logged In',
'last_name' => 'User ' . mt_rand(),
])['id'];
UFMatch::delete(FALSE)->addWhere('uf_id', '=', 6)->execute();
$this->createTestRecord('UFMatch', [
'contact_id' => $contactID,
'uf_name' => 'superman',
'uf_id' => 6,
]);

$session = \CRM_Core_Session::singleton();
$session->set('userID', $contactID);
return $contactID;
}

public function userLogout() {
\CRM_Core_Session::singleton()->reset();
UFMatch::delete(FALSE)
->addWhere('uf_name', '=', 'superman')
->execute();
}

}
35 changes: 35 additions & 0 deletions ext/afform/core/Civi/Api4/Action/Afform/Submit.php
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,41 @@ private static function getEntityRefError(string $formName, string $entityName,
return NULL;
}

/**
* When using a "quick-add" form, this ensures the predetermined "data" values from the parent form's entity
* will be copied to the newly-created entity in the popup form.
*
* @param \Civi\Afform\Event\AfformSubmitEvent $event
*/
public static function preprocessParentFormValues(AfformSubmitEvent $event): void {
$entityType = $event->getEntityType();
$apiRequest = $event->getApiRequest();
$args = $apiRequest->getArgs();
if (str_starts_with($args['parentFormName'] ?? '', 'afform:') && str_contains($args['parentFormFieldName'] ?? '', ':')) {
[, $parentFormName] = explode(':', $args['parentFormName']);
[$parentFormEntityName, $parentFormFieldName] = explode(':', $args['parentFormFieldName']);
$parentForm = civicrm_api4('Afform', 'get', [
'select' => ['layout'],
'where' => [
['name', '=', $parentFormName],
['submit_currently_open', '=', TRUE],
],
])->first();
if ($parentForm) {
$parentFormDataModel = new FormDataModel($parentForm['layout']);
$entity = $parentFormDataModel->getEntity($parentFormEntityName);
if (!$entity || $entity['type'] !== $entityType || empty($entity['data'])) {
return;
}
$records = $event->getRecords();
foreach ($records as &$record) {
$record['fields'] = $entity['data'] + $record['fields'];
}
$event->setRecords($records);
}
}
}

/**
* Check if contact(s) meet the minimum requirements to be created (name and/or email).
*
Expand Down
1 change: 1 addition & 0 deletions ext/afform/core/afform.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ function afform_civicrm_config(&$config) {
$dispatcher->addListener('civi.afform.validate', ['\Civi\Api4\Action\Afform\Submit', 'validateEntityRefFields'], 45);
$dispatcher->addListener('civi.afform.submit', ['\Civi\Api4\Action\Afform\Submit', 'processGenericEntity'], 0);
$dispatcher->addListener('civi.afform.submit', ['\Civi\Api4\Action\Afform\Submit', 'preprocessContact'], 10);
$dispatcher->addListener('civi.afform.submit', ['\Civi\Api4\Action\Afform\Submit', 'preprocessParentFormValues'], 100);
$dispatcher->addListener('civi.afform.submit', ['\Civi\Api4\Action\Afform\Submit', 'processRelationships'], 1);
$dispatcher->addListener('hook_civicrm_angularModules', '_afform_hook_civicrm_angularModules', -1000);
$dispatcher->addListener('hook_civicrm_alterAngular', ['\Civi\Afform\AfformMetadataInjector', 'preprocess']);
Expand Down
4 changes: 2 additions & 2 deletions ext/afform/core/ang/af/afForm.component.js
Original file line number Diff line number Diff line change
Expand Up @@ -302,8 +302,8 @@
crmApi4('Afform', 'submit', {
name: ctrl.getFormMeta().name,
args: args,
values: data}
).then(function(response) {
values: data,
}).then(function(response) {
submissionResponse = response;
if (ctrl.fileUploader.getNotUploadedItems().length) {
_.each(ctrl.fileUploader.getNotUploadedItems(), function(file) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ public function testCheckEntityReferenceFieldsReplacement(): void {
// Check that the data overrides form submission
$this->assertEquals('Register A site', $contact['source']);
// Check that the contact and the activity were correctly linked up as per the form.
$this->callAPISuccessGetSingle('ActivityContact', ['contact_id' => $contact['id'], 'activity_id' => $activity['id']]);
$this->getTestRecord('ActivityContact', ['contact_id' => $contact['id'], 'activity_id' => $activity['id']]);
}

public function testCheckAccess(): void {
Expand Down Expand Up @@ -250,6 +250,7 @@ public function testAboutMeForbidden(): void {
catch (\CRM_Core_Exception $e) {
// Should fail permission check
}
$this->assertTrue(is_a($e, '\Civi\API\Exception\UnauthorizedException'));

try {
Afform::submit()
Expand All @@ -264,6 +265,7 @@ public function testAboutMeForbidden(): void {
catch (\CRM_Core_Exception $e) {
// Should fail permission check
}
$this->assertTrue(is_a($e, '\Civi\API\Exception\UnauthorizedException'));
}

public function testEmployerReference(): void {
Expand Down Expand Up @@ -595,6 +597,67 @@ public function testSubmissionLimit() {
}
catch (\Civi\API\Exception\UnauthorizedException $e) {
}
$this->assertTrue(is_a($e, '\Civi\API\Exception\UnauthorizedException'));
}

public function testQuickAddWithDataValues(): void {
$contactType = $this->createTestRecord('ContactType', [
'parent_id:name' => 'Individual',
])['name'];

$html = <<<EOHTML
<af-form ctrl="afform">
<af-entity type="Individual" data="{contact_sub_type: ['$contactType']}" name="me" label="Myself" url-autofill="1" autofill="user" />
<fieldset af-fieldset="me">
<af-field name="id" />
<af-field name="first_name" />
<af-field name="last_name" />
</fieldset>
</af-form>
EOHTML;

$this->useValues([
'layout' => $html,
'permission' => \CRM_Core_Permission::ALWAYS_ALLOW_PERMISSION,
]);

$lastName = uniqid(__FUNCTION__);

// We're not submitting the above form, we're creating a 'quick-add' Individual, e.g. from the "Existing Contact" popup
Afform::submit()
->setName('afformQuickAddIndividual')
->setValues([
'Individual1' => [
[
'fields' => ['first_name' => 'Jane', 'last_name' => $lastName],
],
],
])
->execute();
// This first submit we did not specify a parent form, so got a generic individual
$contact = $this->getTestRecord('Individual', ['first_name' => 'Jane', 'last_name' => $lastName]);
$this->assertNull($contact['contact_sub_type']);

// Now specify the above form as the parent

// We're not submitting the above form, we're creating a 'quick-add' Individual, e.g. from the "Existing Contact" popup
Afform::submit()
->setName('afformQuickAddIndividual')
->setValues([
'Individual1' => [
[
'fields' => ['first_name' => 'John', 'last_name' => $lastName],
],
],
])
->setArgs([
'parentFormName' => "afform:$this->formName",
'parentFormFieldName' => "me:id",
])
->execute();
// This first submit we did not specify a parent form, so got a generic individual
$contact = $this->getTestRecord('Individual', ['first_name' => 'John', 'last_name' => $lastName]);
$this->assertEquals([$contactType], $contact['contact_sub_type']);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ public function testMultiRecordCustomBlock(): void {
$this->assertEquals('my_text', $block['layout'][0]['name']);
$this->assertEquals('my_friend', $block['layout'][1]['name']);

$cid1 = $this->individualCreate([], 1);
$cid2 = $this->individualCreate([], 2);
$cid1 = $this->createTestRecord('Individual')['id'];
$cid2 = $this->createTestRecord('Individual')['id'];

$this->useValues([
'layout' => self::$layouts['customMulti'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,21 @@
* @group headless
*/
class AfformEventUsageTest extends AfformUsageTestCase implements TransactionalInterface {
use \Civi\Test\Api4TestTrait;

/**
* Tests prefilling an event from a template
*/
public function testEventTemplatePrefill(): void {
$locBlock1 = $this->createTestEntity('LocBlock', [
'email_id' => $this->createTestEntity('Email', ['email' => '[email protected]'])['id'],
'phone_id' => $this->createTestEntity('Phone', ['phone' => '1234567'])['id'],
$locBlock1 = $this->createTestRecord('LocBlock', [
'email_id' => $this->createTestRecord('Email', ['email' => '[email protected]'])['id'],
'phone_id' => $this->createTestRecord('Phone', ['phone' => '1234567'])['id'],
]);
$locBlock2 = $this->createTestEntity('LocBlock', [
'email_id' => $this->createTestEntity('Email', ['email' => '[email protected]'])['id'],
'phone_id' => $this->createTestEntity('Phone', ['phone' => '2234567'])['id'],
$locBlock2 = $this->createTestRecord('LocBlock', [
'email_id' => $this->createTestRecord('Email', ['email' => '[email protected]'])['id'],
'phone_id' => $this->createTestRecord('Phone', ['phone' => '2234567'])['id'],
]);

$eventTemplate = $this->createTestEntity('Event', [
$eventTemplate = $this->createTestRecord('Event', [
'template_title' => 'Test Template Title',
'title' => 'Test Me',
'event_type_id' => 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
* @group headless
*/
class AfformPrefillUsageTest extends AfformUsageTestCase {
use \Civi\Test\Api4TestTrait;

/**
* Ensure that Afform restricts autocomplete results when it's set to use a SavedSearch
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@
* @group headless
*/
abstract class AfformUsageTestCase extends AfformTestCase {
use \Civi\Test\Api3TestTrait;
use \Civi\Test\ContactTestTrait;
use \Civi\Test\Api4TestTrait;

protected static $layouts = [];

Expand Down
32 changes: 25 additions & 7 deletions js/Common.js
Original file line number Diff line number Diff line change
Expand Up @@ -543,12 +543,10 @@ if (!CRM.vars) CRM.vars = {};
return '';
}
let markup = '<div class="crm-entityref-links crm-entityref-quick-add">';
CRM.config.quickAdd.forEach((link) => {
if (quickAddLinks.includes(link.path)) {
markup += ' <a class="crm-hover-button" href="' + _.escape(CRM.url(link.path)) + '">' +
'<i class="crm-i ' + _.escape(link.icon) + '" aria-hidden="true"></i> ' +
_.escape(link.title) + '</a>';
}
quickAddLinks.forEach((link) => {
markup += ' <a class="crm-hover-button" href="' + _.escape(CRM.url(link.path)) + '">' +
'<i class="crm-i ' + _.escape(link.icon) + '" aria-hidden="true"></i> ' +
_.escape(link.title) + '</a>';
});
markup += '</div>';
return markup;
Expand Down Expand Up @@ -576,14 +574,34 @@ if (!CRM.vars) CRM.vars = {};
}
return apiParams || {};
}
function getQuickAddLinks(paths) {
const links = [];
if (paths && paths.length) {
const apiParams = getApiParams();
paths.forEach((path) => {
let link = CRM.config.quickAdd.find((link) => link.path === path);
if (link) {
links.push({
path: path + '#?' + $.param({
parentFormName: apiParams.formName,
parentFormFieldName: apiParams.fieldName,
}),
icon: link.icon,
title: link.title,
});
}
});
}
return links;
}
if (entityName === 'destroy') {
return $(this).off('.crmEntity').crmSelect2('destroy');
}
select2Options = select2Options || {};
return $(this).each(function() {
const $el = $(this).off('.crmEntity');
let staticItems = getStaticOptions(select2Options.static),
quickAddLinks = select2Options.quickAdd,
quickAddLinks = getQuickAddLinks(select2Options.quickAdd),
multiple = !!select2Options.multiple;

$el.crmSelect2(_.extend({
Expand Down
32 changes: 0 additions & 32 deletions tests/phpunit/api/v4/Api4TestBase.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@

namespace api\v4;

use Civi\Api4\UFMatch;
use Civi\Test;
use Civi\Test\Api4TestTrait;
use Civi\Test\CiviEnvBuilder;
Expand Down Expand Up @@ -78,35 +77,4 @@ public function cleanup(array $params): void {
\CRM_Core_DAO::executeQuery('SET FOREIGN_KEY_CHECKS = 1;');
}

/**
* Emulate a logged in user since certain functions use that.
* value to store a record in the DB (like activity)
*
* @see https://issues.civicrm.org/jira/browse/CRM-8180
*
* @return int
* Contact ID of the created user.
* @throws \CRM_Core_Exception
*/
public function createLoggedInUser(): int {
$contactID = $this->createTestRecord('Individual')['id'];
UFMatch::delete(FALSE)->addWhere('uf_id', '=', 6)->execute();
$this->createTestRecord('UFMatch', [
'contact_id' => $contactID,
'uf_name' => 'superman',
'uf_id' => 6,
]);

$session = \CRM_Core_Session::singleton();
$session->set('userID', $contactID);
return $contactID;
}

public function userLogout() {
\CRM_Core_Session::singleton()->reset();
UFMatch::delete(FALSE)
->addWhere('uf_name', '=', 'superman')
->execute();
}

}

0 comments on commit 1ee3cac

Please sign in to comment.