diff --git a/Civi/Test/Api4TestTrait.php b/Civi/Test/Api4TestTrait.php index 17a9adbefb26..78c0f6a01300 100644 --- a/Civi/Test/Api4TestTrait.php +++ b/Civi/Test/Api4TestTrait.php @@ -4,6 +4,7 @@ use Civi\Api4\Generic\AbstractAction; use Civi\Api4\Generic\Result; +use Civi\Api4\UFMatch; use Civi\Api4\Utils\CoreUtil; /** @@ -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(); + } + } diff --git a/ext/afform/core/Civi/Api4/Action/Afform/Submit.php b/ext/afform/core/Civi/Api4/Action/Afform/Submit.php index 4315e35d5e4c..13c13250fd5e 100644 --- a/ext/afform/core/Civi/Api4/Action/Afform/Submit.php +++ b/ext/afform/core/Civi/Api4/Action/Afform/Submit.php @@ -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). * diff --git a/ext/afform/core/afform.php b/ext/afform/core/afform.php index c50f5bd518f5..99d310ebe2cb 100644 --- a/ext/afform/core/afform.php +++ b/ext/afform/core/afform.php @@ -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']); diff --git a/ext/afform/core/ang/af/afForm.component.js b/ext/afform/core/ang/af/afForm.component.js index 50a8ccffb7c1..643f24aa0886 100644 --- a/ext/afform/core/ang/af/afForm.component.js +++ b/ext/afform/core/ang/af/afForm.component.js @@ -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) { diff --git a/ext/afform/mock/tests/phpunit/api/v4/Afform/AfformContactUsageTest.php b/ext/afform/mock/tests/phpunit/api/v4/Afform/AfformContactUsageTest.php index 2bc32f356ca3..5923145a56b2 100644 --- a/ext/afform/mock/tests/phpunit/api/v4/Afform/AfformContactUsageTest.php +++ b/ext/afform/mock/tests/phpunit/api/v4/Afform/AfformContactUsageTest.php @@ -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 { @@ -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() @@ -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 { @@ -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; + + $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']); } } diff --git a/ext/afform/mock/tests/phpunit/api/v4/Afform/AfformCustomFieldUsageTest.php b/ext/afform/mock/tests/phpunit/api/v4/Afform/AfformCustomFieldUsageTest.php index 27a4e9c1b3b0..c83424060874 100644 --- a/ext/afform/mock/tests/phpunit/api/v4/Afform/AfformCustomFieldUsageTest.php +++ b/ext/afform/mock/tests/phpunit/api/v4/Afform/AfformCustomFieldUsageTest.php @@ -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'], diff --git a/ext/afform/mock/tests/phpunit/api/v4/Afform/AfformEventUsageTest.php b/ext/afform/mock/tests/phpunit/api/v4/Afform/AfformEventUsageTest.php index 2160ef3cc6fc..f3eed70f8caf 100644 --- a/ext/afform/mock/tests/phpunit/api/v4/Afform/AfformEventUsageTest.php +++ b/ext/afform/mock/tests/phpunit/api/v4/Afform/AfformEventUsageTest.php @@ -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' => '1@te.st'])['id'], - 'phone_id' => $this->createTestEntity('Phone', ['phone' => '1234567'])['id'], + $locBlock1 = $this->createTestRecord('LocBlock', [ + 'email_id' => $this->createTestRecord('Email', ['email' => '1@te.st'])['id'], + 'phone_id' => $this->createTestRecord('Phone', ['phone' => '1234567'])['id'], ]); - $locBlock2 = $this->createTestEntity('LocBlock', [ - 'email_id' => $this->createTestEntity('Email', ['email' => '2@te.st'])['id'], - 'phone_id' => $this->createTestEntity('Phone', ['phone' => '2234567'])['id'], + $locBlock2 = $this->createTestRecord('LocBlock', [ + 'email_id' => $this->createTestRecord('Email', ['email' => '2@te.st'])['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, diff --git a/ext/afform/mock/tests/phpunit/api/v4/Afform/AfformPrefillUsageTest.php b/ext/afform/mock/tests/phpunit/api/v4/Afform/AfformPrefillUsageTest.php index 4e3e8f827b3c..8b80807a4150 100644 --- a/ext/afform/mock/tests/phpunit/api/v4/Afform/AfformPrefillUsageTest.php +++ b/ext/afform/mock/tests/phpunit/api/v4/Afform/AfformPrefillUsageTest.php @@ -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 diff --git a/ext/afform/mock/tests/phpunit/api/v4/Afform/AfformUsageTestCase.php b/ext/afform/mock/tests/phpunit/api/v4/Afform/AfformUsageTestCase.php index 378f56825a3d..0579e405c643 100644 --- a/ext/afform/mock/tests/phpunit/api/v4/Afform/AfformUsageTestCase.php +++ b/ext/afform/mock/tests/phpunit/api/v4/Afform/AfformUsageTestCase.php @@ -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 = []; diff --git a/js/Common.js b/js/Common.js index 74173ec1390c..8c4f68e9fdb9 100644 --- a/js/Common.js +++ b/js/Common.js @@ -543,12 +543,10 @@ if (!CRM.vars) CRM.vars = {}; return ''; } let markup = ''; return markup; @@ -576,6 +574,26 @@ 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'); } @@ -583,7 +601,7 @@ if (!CRM.vars) CRM.vars = {}; 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({ diff --git a/tests/phpunit/api/v4/Api4TestBase.php b/tests/phpunit/api/v4/Api4TestBase.php index 0d7489b65cf3..2770768e98bd 100644 --- a/tests/phpunit/api/v4/Api4TestBase.php +++ b/tests/phpunit/api/v4/Api4TestBase.php @@ -19,7 +19,6 @@ namespace api\v4; -use Civi\Api4\UFMatch; use Civi\Test; use Civi\Test\Api4TestTrait; use Civi\Test\CiviEnvBuilder; @@ -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(); - } - }