if (!$customInfo) {
return;
}
- // If a contact_type was passed in, exclude custom groups for other contact types
- if ($entity === 'Contact' && $spec->getValue('contact_type')) {
- $extends = ['Contact', $spec->getValue('contact_type')];
+ $values = $spec->getValues();
+ $extends = $customInfo['extends'];
+ $grouping = $customInfo['grouping'];
+
+ $query = CustomField::get(FALSE)
+ ->setSelect(['custom_group_id.name', 'custom_group_id.title', '*'])
+ ->addWhere('custom_group_id.is_multiple', '=', '0');
+
+ // Contact custom groups are extra complicated because contact_type can be a value for extends
+ if ($entity === 'Contact') {
+ if (array_key_exists('contact_type', $values)) {
+ $extends = ['Contact'];
+ if ($values['contact_type']) {
+ $extends[] = $values['contact_type'];
+ }
+ }
+ // Now grouping can be treated normally
+ $grouping = 'contact_sub_type';
}
- else {
- $extends = $customInfo['extends'];
+ if (is_string($grouping) && array_key_exists($grouping, $values)) {
+ if (empty($values[$grouping])) {
+ $query->addWhere('custom_group_id.extends_entity_column_value', 'IS EMPTY');
+ }
+ else {
+ $clause = [];
+ foreach ((array) $values[$grouping] as $value) {
+ $clause[] = ['custom_group_id.extends_entity_column_value', 'CONTAINS', $value];
+ }
+ $query->addClause('OR', $clause);
+ }
}
- // FIXME: filter by entity sub-type if passed in values
- $customFields = CustomField::get(FALSE)
- ->addWhere('custom_group_id.extends', 'IN', $extends)
- ->addWhere('custom_group_id.is_multiple', '=', '0')
- ->setSelect(['custom_group_id.name', 'custom_group_id.title', '*'])
- ->execute();
+ // Handle multiple groupings
+ // (In core, only Participant custom fields have multiple groupings)
+ elseif (is_array($grouping)) {
+ $clauses = [];
+ foreach ($grouping as $columnId => $group) {
+ if (array_key_exists($group, $values)) {
+ if (empty($values[$group])) {
+ $clauses[] = [
+ 'AND',
+ [
+ ['custom_group_id.extends_entity_column_id', '=', $columnId],
+ ['custom_group_id.extends_entity_column_value', 'IS EMPTY'],
+ ],
+ ];
+ }
+ else {
+ $clause = [];
+ foreach ((array) $values[$group] as $value) {
+ $clause[] = ['custom_group_id.extends_entity_column_value', 'CONTAINS', $value];
+ }
+ $clauses[] = [
+ 'AND',
+ [
+ ['custom_group_id.extends_entity_column_id', '=', $columnId],
+ ['OR', $clause],
+ ],
+ ];
+ }
+ }
+ }
+ if ($clauses) {
+ $query->addClause('OR', $clauses);
+ }
+ }
+ $query->addWhere('custom_group_id.extends', 'IN', $extends);
- foreach ($customFields as $fieldArray) {
+ foreach ($query->execute() as $fieldArray) {
$field = SpecFormatter::arrayToField($fieldArray, $entity);
$spec->addFieldSpec($field);
}
--- /dev/null
+<?php
+
+/*
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC. All rights reserved. |
+ | |
+ | This work is published under the GNU AGPLv3 license with some |
+ | permitted exceptions and without any warranty. For full license |
+ | and copyright information, see https://civicrm.org/licensing |
+ +--------------------------------------------------------------------+
+ */
+
+/**
+ *
+ * @package CRM
+ * @copyright CiviCRM LLC https://civicrm.org/licensing
+ */
+
+
+namespace api\v4\Custom;
+
+use Civi\Api4\Contact;
+use Civi\Api4\ContactType;
+use Civi\Api4\CustomField;
+use Civi\Api4\CustomGroup;
+use Civi\Api4\Event;
+use Civi\Api4\Participant;
+
+/**
+ * @group headless
+ */
+class CustomFieldGetFieldsTest extends CustomTestBase {
+
+ private $subTypeName = 'Sub_Tester';
+
+ public function tearDown(): void {
+ parent::tearDown();
+ Contact::delete(FALSE)
+ ->addWhere('id', '>', 0)
+ ->execute();
+ Participant::delete(FALSE)
+ ->addWhere('id', '>', 0)
+ ->execute();
+ Event::delete(FALSE)
+ ->addWhere('id', '>', 0)
+ ->execute();
+ ContactType::delete(FALSE)
+ ->addWhere('name', '=', $this->subTypeName)
+ ->execute();
+ }
+
+ public function testCustomGetFieldsWithContactSubType() {
+ ContactType::create(FALSE)
+ ->addValue('name', $this->subTypeName)
+ ->addValue('label', $this->subTypeName)
+ ->addValue('parent_id:name', 'Individual')
+ ->execute();
+
+ $contact1 = Contact::create(FALSE)
+ ->execute()->first();
+ $contact2 = Contact::create(FALSE)->addValue('contact_sub_type', [$this->subTypeName])
+ ->execute()->first();
+ $org = Contact::create(FALSE)->addValue('contact_type', 'Organization')
+ ->execute()->first();
+
+ // Individual sub-type custom group
+ CustomGroup::create(FALSE)
+ ->addValue('extends', 'Individual')
+ ->addValue('extends_entity_column_value', [$this->subTypeName])
+ ->addValue('title', 'contact_sub')
+ ->execute();
+ CustomField::create(FALSE)
+ ->addValue('custom_group_id.name', 'contact_sub')
+ ->addValue('label', 'sub_field')
+ ->addValue('html_type', 'Text')
+ ->execute();
+
+ // Organization custom group
+ CustomGroup::create(FALSE)
+ ->addValue('extends', 'Organization')
+ ->addValue('title', 'org_group')
+ ->execute();
+ CustomField::create(FALSE)
+ ->addValue('custom_group_id.name', 'org_group')
+ ->addValue('label', 'sub_field')
+ ->addValue('html_type', 'Text')
+ ->execute();
+
+ $allFields = Contact::getFields(FALSE)
+ ->execute()->indexBy('name');
+ $this->assertArrayHasKey('contact_sub.sub_field', $allFields);
+ $this->assertArrayHasKey('org_group.sub_field', $allFields);
+
+ $fieldsWithSubtype = Contact::getFields(FALSE)
+ ->addValue('id', $contact2['id'])
+ ->execute()->indexBy('name');
+ $this->assertArrayHasKey('contact_sub.sub_field', $fieldsWithSubtype);
+ $this->assertArrayNotHasKey('org_group.sub_field', $fieldsWithSubtype);
+
+ $fieldsNoSubtype = Contact::getFields(FALSE)
+ ->addValue('id', $contact1['id'])
+ ->execute()->indexBy('name');
+ $this->assertArrayNotHasKey('contact_sub.sub_field', $fieldsNoSubtype);
+ $this->assertArrayNotHasKey('org_group.sub_field', $fieldsNoSubtype);
+
+ $groupFields = Contact::getFields(FALSE)
+ ->addValue('id', $org['id'])
+ ->execute()->indexBy('name');
+ $this->assertArrayNotHasKey('contact_sub.sub_field', $groupFields);
+ $this->assertArrayHasKey('org_group.sub_field', $groupFields);
+ }
+
+ public function testCustomGetFieldsForParticipantSubTypes() {
+ $event1 = Event::create(FALSE)
+ ->addValue('title', 'Test1')
+ ->addValue('event_type_id:name', 'Meeting')
+ ->addValue('start_date', 'now')
+ ->execute()->first();
+ $event2 = Event::create(FALSE)
+ ->addValue('title', 'Test2')
+ ->addValue('event_type_id:name', 'Meeting')
+ ->addValue('start_date', 'now')
+ ->execute()->first();
+ $event3 = Event::create(FALSE)
+ ->addValue('title', 'Test3')
+ ->addValue('event_type_id:name', 'Conference')
+ ->addValue('start_date', 'now')
+ ->execute()->first();
+ $event4 = Event::create(FALSE)
+ ->addValue('title', 'Test4')
+ ->addValue('event_type_id:name', 'Fundraiser')
+ ->addValue('start_date', 'now')
+ ->execute()->first();
+
+ $cid = Contact::create(FALSE)->execute()->single()['id'];
+
+ $sampleData = [
+ ['event_id' => $event1['id'], 'role_id:name' => ['Attendee']],
+ ['event_id' => $event2['id'], 'role_id:name' => ['Attendee', 'Volunteer']],
+ ['event_id' => $event3['id'], 'role_id:name' => ['Attendee']],
+ ['event_id' => $event4['id'], 'role_id:name' => ['Host']],
+ ];
+ $participants = Participant::save(FALSE)
+ ->addDefault('contact_id', $cid)
+ ->addDefault('status_id:name', 'Registered')
+ ->setRecords($sampleData)
+ ->execute();
+
+ // CustomGroup based on Event Type
+ CustomGroup::create(FALSE)
+ ->addValue('extends', 'Participant')
+ ->addValue('extends_entity_column_id:name', 'ParticipantEventType')
+ ->addValue('extends_entity_column_value:name', ['Meeting', 'Conference'])
+ ->addValue('title', 'meeting_conference')
+ ->addChain('field', CustomField::create()
+ ->addValue('custom_group_id', '$id')
+ ->addValue('label', 'sub_field')
+ ->addValue('html_type', 'Text')
+ )
+ ->execute();
+
+ // CustomGroup based on Participant Status
+ CustomGroup::create(FALSE)
+ ->addValue('extends', 'Participant')
+ ->addValue('extends_entity_column_id:name', 'ParticipantRole')
+ ->addValue('extends_entity_column_value:name', ['Volunteer', 'Host'])
+ ->addValue('title', 'volunteer_host')
+ ->addChain('field', CustomField::create()
+ ->addValue('custom_group_id', '$id')
+ ->addValue('label', 'sub_field')
+ ->addValue('html_type', 'Text')
+ )
+ ->execute();
+
+ // CustomGroup based on Specific Events
+ CustomGroup::create(FALSE)
+ ->addValue('extends', 'Participant')
+ ->addValue('extends_entity_column_id:name', 'ParticipantEventName')
+ ->addValue('extends_entity_column_value', [$event2['id'], $event3['id']])
+ ->addValue('title', 'event_2_and_3')
+ ->addChain('field', CustomField::create()
+ ->addValue('custom_group_id', '$id')
+ ->addValue('label', 'sub_field')
+ ->addValue('html_type', 'Text')
+ )
+ ->execute();
+
+ $allFields = Participant::getFields(FALSE)->execute()->indexBy('name');
+ $this->assertArrayHasKey('meeting_conference.sub_field', $allFields);
+ $this->assertArrayHasKey('volunteer_host.sub_field', $allFields);
+ $this->assertArrayHasKey('event_2_and_3.sub_field', $allFields);
+
+ $participant0Fields = Participant::getFields(FALSE)
+ ->addValue('id', $participants[0]['id'])
+ ->execute()->indexBy('name');
+ $this->assertArrayHasKey('meeting_conference.sub_field', $participant0Fields);
+ $this->assertArrayNotHasKey('volunteer_host.sub_field', $participant0Fields);
+ $this->assertArrayNotHasKey('event_2_and_3.sub_field', $participant0Fields);
+
+ $participant1Fields = Participant::getFields(FALSE)
+ ->addValue('id', $participants[1]['id'])
+ ->execute()->indexBy('name');
+ $this->assertArrayHasKey('meeting_conference.sub_field', $participant1Fields);
+ $this->assertArrayHasKey('volunteer_host.sub_field', $participant1Fields);
+ $this->assertArrayHasKey('event_2_and_3.sub_field', $participant1Fields);
+
+ $participant2Fields = Participant::getFields(FALSE)
+ ->addValue('id', $participants[2]['id'])
+ ->execute()->indexBy('name');
+ $this->assertArrayHasKey('meeting_conference.sub_field', $participant2Fields);
+ $this->assertArrayNotHasKey('volunteer_host.sub_field', $participant2Fields);
+ $this->assertArrayHasKey('event_2_and_3.sub_field', $participant2Fields);
+
+ $participant3Fields = Participant::getFields(FALSE)
+ ->addValue('id', $participants[3]['id'])
+ ->execute()->indexBy('name');
+ $this->assertArrayNotHasKey('meeting_conference.sub_field', $participant3Fields);
+ $this->assertArrayHasKey('volunteer_host.sub_field', $participant3Fields);
+ $this->assertArrayNotHasKey('event_3_and_3.sub_field', $participant3Fields);
+ }
+
+}