From c100cf44625fd465a7a6c1a76dc7ace6c9e7163b Mon Sep 17 00:00:00 2001 From: Coleman Watts Date: Mon, 2 May 2022 08:08:31 -0400 Subject: [PATCH] CustomFields - Improve metadata for custom_group_extends This allows the API to filter custom groups by entity type and other values. --- CRM/Core/BAO/CustomGroup.php | 317 ++++++++++++++---- CRM/Core/DAO/CustomGroup.php | 4 +- CRM/Core/SelectValues.php | 25 +- ...ovider.php => CustomGroupSpecProvider.php} | 10 +- Civi/Api4/Service/Spec/SpecFormatter.php | 8 + Civi/Api4/Utils/CoreUtil.php | 2 +- Civi/Api4/Utils/FormattingUtil.php | 2 +- api/v3/CustomGroup.php | 10 +- .../phpunit/CRM/Core/BAO/CustomGroupTest.php | 7 + .../api/v4/Action/BasicCustomFieldTest.php | 105 +++++- xml/schema/Core/CustomGroup.xml | 2 +- 11 files changed, 380 insertions(+), 112 deletions(-) rename Civi/Api4/Service/Spec/Provider/{CustomGroupCreationSpecProvider.php => CustomGroupSpecProvider.php} (73%) diff --git a/CRM/Core/BAO/CustomGroup.php b/CRM/Core/BAO/CustomGroup.php index 1bcb41f893..d53aac1c70 100644 --- a/CRM/Core/BAO/CustomGroup.php +++ b/CRM/Core/BAO/CustomGroup.php @@ -1693,75 +1693,19 @@ ORDER BY civicrm_custom_group.weight, } /** - * @param $table + * Get table name for extends. + * + * @param string $entityName * * @return string * @throws Exception */ - public static function mapTableName($table) { - switch ($table) { - case 'Contact': - case 'Individual': - case 'Household': - case 'Organization': - return 'civicrm_contact'; - - case 'Activity': - return 'civicrm_activity'; - - case 'Group': - return 'civicrm_group'; - - case 'Contribution': - return 'civicrm_contribution'; - - case 'ContributionRecur': - return 'civicrm_contribution_recur'; - - case 'Relationship': - return 'civicrm_relationship'; - - case 'Event': - return 'civicrm_event'; - - case 'Membership': - return 'civicrm_membership'; - - case 'Participant': - case 'ParticipantRole': - case 'ParticipantEventName': - case 'ParticipantEventType': - return 'civicrm_participant'; - - case 'Grant': - return 'civicrm_grant'; - - case 'Pledge': - return 'civicrm_pledge'; - - case 'Address': - return 'civicrm_address'; - - case 'Campaign': - return 'civicrm_campaign'; - - default: - $query = " -SELECT IF( EXISTS(SELECT name FROM civicrm_contact_type WHERE name like %1), 1, 0 )"; - $qParams = [1 => [$table, 'String']]; - $result = CRM_Core_DAO::singleValueQuery($query, $qParams); - - if ($result) { - return 'civicrm_contact'; - } - else { - $extendObjs = CRM_Core_OptionGroup::values('cg_extend_objects', FALSE, FALSE, FALSE, NULL, 'name'); - if (array_key_exists($table, $extendObjs)) { - return $extendObjs[$table]; - } - throw new CRM_Core_Exception('Unknown error'); - } + public static function mapTableName($entityName) { + $options = array_column(self::getCustomGroupExtendsOptions(), 'table_name', 'id'); + if (isset($options[$entityName])) { + return $options[$entityName]; } + throw new CRM_Core_Exception('Unknown error'); } /** @@ -2120,28 +2064,133 @@ SELECT civicrm_custom_group.id as groupID, civicrm_custom_group.title as groupT */ public static function getExtendsEntityColumnValueOptions($context, $params, $props = []) { // Requesting this option list only makes sense if the value of 'extends' is known or can be looked up - if (!empty($props['id']) || !empty($props['name']) || !empty($props['extends'])) { + if (!empty($props['id']) || !empty($props['name']) || !empty($props['extends']) || !empty($props['extends_entity_column_id'])) { $id = $props['id'] ?? NULL; $name = $props['name'] ?? NULL; $extends = $props['extends'] ?? NULL; $entityColumnId = $props['extends_entity_column_id'] ?? NULL; - if (!$extends) { - $extends = CRM_Core_DAO::getFieldValue(parent::class, $id ?: $name, 'extends', $id ? 'id' : 'name'); - } + if (!array_key_exists('extends_entity_column_id', $props) && ($id || $name)) { $entityColumnId = CRM_Core_DAO::getFieldValue(parent::class, $id ?: $name, 'extends_entity_column_id', $id ? 'id' : 'name'); } - // If there is an entityColumnId (currently only used by Participants) filter by that type. + // If there is an entityColumnId (currently only used by Participants) use grouping from that sub-type. if ($entityColumnId) { - $pseudoSelectors = CRM_Core_OptionGroup::values('custom_data_type', FALSE, FALSE, FALSE, NULL, 'name'); - $extends = $pseudoSelectors[$entityColumnId]; + $pseudoSelectors = array_column(self::getExtendsEntityColumnIdOptions(), NULL, 'id'); + $grouping = $pseudoSelectors[$entityColumnId]['grouping']; + $extends = $pseudoSelectors[$entityColumnId]['extends']; + } + else { + if (!$extends) { + $extends = CRM_Core_DAO::getFieldValue(parent::class, $id ?: $name, 'extends', $id ? 'id' : 'name'); + } + $allTypes = array_column(self::getCustomGroupExtendsOptions(), NULL, 'id'); + $grouping = $allTypes[$extends]['grouping'] ?? NULL; + } + if (!$grouping) { + return []; + } + $getFieldsValues = []; + // For contact types + if (in_array($extends, CRM_Contact_BAO_ContactType::basicTypes(TRUE), TRUE)) { + $getFieldsValues['contact_type'] = $extends; + $extends = 'Contact'; + } + try { + $field = civicrm_api4($extends, 'getFields', [ + 'checkPermissions' => FALSE, + 'loadOptions' => ['id', 'name', 'label'], + 'where' => [['name', '=', $grouping]], + 'values' => $getFieldsValues, + ])->first(); + if ($field['options']) { + return $field['options']; + } + // This is to support the ParticipantEventName which groups by event_id, a field with no pseudoconstant + elseif ($field['fk_entity']) { + $fkFields = civicrm_api4($field['entity'], 'getFields', [ + 'checkPermissions' => FALSE, + ])->indexBy('name'); + $fkIdField = \Civi\Api4\Utils\CoreUtil::getIdFieldName($field['fk_entity']); + $fkLabelField = \Civi\Api4\Utils\CoreUtil::getInfoItem($field['fk_entity'], 'label_field'); + $fkNameField = isset($fkFields['name']) ? 'name' : 'id'; + $select = [$fkIdField, $fkNameField, $fkLabelField]; + $where = []; + if (isset($fkFields['is_active'])) { + $where[] = ['is_active', '=', TRUE]; + } + $fkEntities = civicrm_api4($field['fk_entity'], 'get', [ + 'checkPermissions' => !(isset($params['check_permissions']) && !$params['check_permissions']), + 'select' => $select, + 'where' => $where, + ]); + $fkOptions = []; + foreach ($fkEntities as $item) { + $fkOptions[] = [ + 'id' => $item[$fkIdField], + 'name' => $item[$fkNameField], + 'label' => $item[$fkLabelField], + ]; + } + return $fkOptions; + } + } + catch (\Civi\API\Exception\NotImplementedException $e) { + // Component disabled + return []; } - $allOptions = self::getSubTypes(); - return $allOptions[$extends] ?? []; } return []; } + /** + * Loads pseudoconstant option values for the `extends_entity_column_id` field. + * + * @param string $context + * @param array $params + * @param array $props + * @return array + */ + public static function getExtendsEntityColumnIdOptions($context = NULL, $params = [], $props = []) { + $ogId = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionGroup', 'custom_data_type', 'id', 'name'); + $optionValues = CRM_Core_BAO_OptionValue::getOptionValuesArray($ogId); + + // There is no explicit link between the result in the custom_data_type group and the entity + // they correspond to. So we rely on the convention of each option's 'name' beginning with + // the name of the entity. + $extendsEntities = array_column(self::getCustomGroupExtendsOptions(), 'id'); + // Sort enties by strlen to ensure the correct one is matched + usort($extendsEntities, function($a, $b) { + return strlen($b) <=> strlen($a); + }); + // Match the entity which is at the beginning of the 'name'. Note: at the time of this writing there + // are only 3 items in the option_group and they are all for "Participant". So we could just return + // "Participant" but that would prevent extensions from creating enties with complex custom fields. + $getEntityName = function($optionValueName) use ($extendsEntities) { + foreach ($extendsEntities as $entityName) { + if (strpos($optionValueName, $entityName) === 0) { + return $entityName; + } + } + }; + + $result = []; + foreach ($optionValues as $optionValue) { + $result[] = [ + 'id' => $optionValue['value'], + 'name' => $optionValue['name'], + 'label' => $optionValue['label'], + 'grouping' => $optionValue['grouping'] ?? NULL, + // For internal use & filtering. Not returned by APIv4 getFields.options. + 'extends' => $getEntityName($optionValue['name']), + ]; + } + + if (!empty($props['extends'])) { + $result = CRM_Utils_Array::findAll($result, ['extends' => $props['extends']]); + } + return $result; + } + /** * @inheritDoc */ @@ -2151,6 +2200,16 @@ SELECT civicrm_custom_group.id as groupID, civicrm_custom_group.title as groupT $options = self::getExtendsEntityColumnValueOptions($context, [], $props); return CRM_Core_PseudoConstant::formatArrayOptions($context, $options); } + // Provides filtering by 'extends' entity + if ($fieldName === 'extends_entity_column_id') { + $options = self::getExtendsEntityColumnIdOptions($context, [], $props); + return CRM_Core_PseudoConstant::formatArrayOptions($context, $options); + } + // Legacy support for APIv3 which also needs the ParticipantEventName etc pseudo-selectors + if ($fieldName === 'extends' && ($props['version'] ?? NULL) == 3) { + $options = CRM_Core_SelectValues::customGroupExtends(); + return CRM_Core_PseudoConstant::formatArrayOptions($context, $options); + } return parent::buildOptions($fieldName, $context, $props); } @@ -2344,4 +2403,116 @@ SELECT civicrm_custom_group.id as groupID, civicrm_custom_group.title as groupT return in_array($groupId, $allowedGroups); } + /** + * List all possible values for `CustomGroup.extends`. + * + * This includes the fake entities "Individual", "Organization", "Household" + * but not the extra options from `custom_data_type` used on the form ("ParticipantStatus", etc). + * + * Returns a mix of hard-coded array and `cg_extend_objects` OptionValues. + * The 'id' return key maps to the 'value' in `cg_extend_objects`. + * The 'grouping' key refers to the entity field used to select a sub-type. + * The 'table_name' key is for internal use (is not returned by getFields.loadOptions), and + * maps to the 'name' in `cg_extend_objects`. We don't return it as the 'name' in getFields because + * it is not always unique (since contact types are pseudo-entities in this list). + * + * @return array{id: string, label: string, grouping: string, table_name: string}[] + */ + public static function getCustomGroupExtendsOptions() { + $options = [ + [ + 'id' => 'Activity', + 'label' => ts('Activities'), + 'grouping' => 'activity_type_id', + 'table_name' => 'civicrm_activity', + ], + [ + 'id' => 'Relationship', + 'label' => ts('Relationships'), + 'grouping' => 'relationship_type_id', + 'table_name' => 'civicrm_relationship', + ], + [ + 'id' => 'Contribution', + 'label' => ts('Contributions'), + 'grouping' => 'financial_type_id', + 'table_name' => 'civicrm_contribution', + ], + [ + 'id' => 'ContributionRecur', + 'label' => ts('Recurring Contributions'), + 'grouping' => NULL, + 'table_name' => 'civicrm_contribution_recur', + ], + [ + 'id' => 'Group', + 'label' => ts('Groups'), + 'grouping' => NULL, + 'table_name' => 'civicrm_group', + ], + [ + 'id' => 'Membership', + 'label' => ts('Memberships'), + 'grouping' => 'membership_type_id', + 'table_name' => 'civicrm_membership', + ], + [ + 'id' => 'Event', + 'label' => ts('Events'), + 'grouping' => 'event_type_id', + 'table_name' => 'civicrm_event', + ], + [ + 'id' => 'Participant', + 'label' => ts('Participants'), + 'grouping' => NULL, + 'table_name' => 'civicrm_participant', + ], + [ + 'id' => 'Pledge', + 'label' => ts('Pledges'), + 'grouping' => 'TODO', + 'table_name' => 'civicrm_pledge', + ], + [ + 'id' => 'Address', + 'label' => ts('Addresses'), + 'grouping' => NULL, + 'table_name' => 'civicrm_address', + ], + [ + 'id' => 'Campaign', + 'label' => ts('Campaigns'), + 'grouping' => 'campaign_type_id', + 'table_name' => 'civicrm_campaign', + ], + [ + 'id' => 'Contact', + 'label' => ts('Contacts'), + 'grouping' => NULL, + 'table_name' => 'civicrm_contact', + ], + ]; + // `CustomGroup.extends` stores contact type as if it were an entity. + foreach (CRM_Contact_BAO_ContactType::basicTypePairs(TRUE) as $contactType => $contactTypeLabel) { + $options[] = [ + 'id' => $contactType, + 'label' => $contactTypeLabel, + 'grouping' => 'contact_sub_type', + 'table_name' => 'civicrm_contact', + ]; + } + $ogId = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionGroup', 'cg_extend_objects', 'id', 'name'); + $ogValues = CRM_Core_BAO_OptionValue::getOptionValuesArray($ogId); + foreach ($ogValues as $ogValue) { + $options[] = [ + 'id' => $ogValue['value'], + 'label' => $ogValue['label'], + 'grouping' => $ogValue['grouping'] ?? NULL, + 'table_name' => $ogValue['name'], + ]; + } + return $options; + } + } diff --git a/CRM/Core/DAO/CustomGroup.php b/CRM/Core/DAO/CustomGroup.php index ecf8b4d503..56f2fa07c4 100644 --- a/CRM/Core/DAO/CustomGroup.php +++ b/CRM/Core/DAO/CustomGroup.php @@ -6,7 +6,7 @@ * * Generated from xml/schema/CRM/Core/CustomGroup.xml * DO NOT EDIT. Generated by CRM_Core_CodeGen - * (GenCodeChecksum:c007e857cfaec7c07a5923125af29474) + * (GenCodeChecksum:fb6ed39a4e35bd2bcdc1a6cd549f4976) */ /** @@ -348,7 +348,7 @@ class CRM_Core_DAO_CustomGroup extends CRM_Core_DAO { 'bao' => 'CRM_Core_BAO_CustomGroup', 'localizable' => 0, 'pseudoconstant' => [ - 'callback' => 'CRM_Core_SelectValues::customGroupExtends', + 'callback' => 'CRM_Core_BAO_CustomGroup::getCustomGroupExtendsOptions', ], 'add' => '1.1', ], diff --git a/CRM/Core/SelectValues.php b/CRM/Core/SelectValues.php index 495786cfcf..389e988553 100644 --- a/CRM/Core/SelectValues.php +++ b/CRM/Core/SelectValues.php @@ -223,34 +223,17 @@ class CRM_Core_SelectValues { } /** - * List of all entities that can be extended by custom fields. + * List of entities to present on the Custom Group form. * - * Includes pseudo-entities for Contact and Participant, in order to present sub-types on the form. + * Includes pseudo-entities for Participant, in order to present sub-types on the form. * * @return array */ public static function customGroupExtends() { - $customGroupExtends = [ - 'Activity' => ts('Activities'), - 'Relationship' => ts('Relationships'), - 'Contribution' => ts('Contributions'), - 'ContributionRecur' => ts('Recurring Contributions'), - 'Group' => ts('Groups'), - 'Membership' => ts('Memberships'), - 'Event' => ts('Events'), - 'Participant' => ts('Participants'), - 'Pledge' => ts('Pledges'), - 'Address' => ts('Addresses'), - 'Campaign' => ts('Campaigns'), - ]; - // Contact, Individual, - $contactTypes = ['Contact' => ts('Contacts')] + self::contactType(); + $customGroupExtends = array_column(CRM_Core_BAO_CustomGroup::getCustomGroupExtendsOptions(), 'label', 'id'); // ParticipantRole, ParticipantEventName, etc. $pseudoSelectors = CRM_Core_OptionGroup::values('custom_data_type', FALSE, FALSE, FALSE, NULL, 'label', TRUE, FALSE, 'name'); - // OptionValues provided by extensions - $extendObjs = CRM_Core_OptionGroup::values('cg_extend_objects'); - $customGroupExtends = array_merge($contactTypes, $customGroupExtends, $extendObjs, $pseudoSelectors); - return $customGroupExtends; + return array_merge($customGroupExtends, $pseudoSelectors); } /** diff --git a/Civi/Api4/Service/Spec/Provider/CustomGroupCreationSpecProvider.php b/Civi/Api4/Service/Spec/Provider/CustomGroupSpecProvider.php similarity index 73% rename from Civi/Api4/Service/Spec/Provider/CustomGroupCreationSpecProvider.php rename to Civi/Api4/Service/Spec/Provider/CustomGroupSpecProvider.php index 726134b381..86498fc6fe 100644 --- a/Civi/Api4/Service/Spec/Provider/CustomGroupCreationSpecProvider.php +++ b/Civi/Api4/Service/Spec/Provider/CustomGroupSpecProvider.php @@ -14,20 +14,24 @@ namespace Civi\Api4\Service\Spec\Provider; use Civi\Api4\Service\Spec\RequestSpec; -class CustomGroupCreationSpecProvider implements Generic\SpecProviderInterface { +class CustomGroupSpecProvider implements Generic\SpecProviderInterface { /** * @inheritDoc */ public function modifySpec(RequestSpec $spec) { - $spec->getFieldByName('extends')->setRequired(TRUE); + $action = $spec->getAction(); + + $spec->getFieldByName('extends') + ->setRequired($action === 'create') + ->setSuffixes(['name', 'label', 'grouping']); } /** * @inheritDoc */ public function applies($entity, $action) { - return $entity === 'CustomGroup' && $action === 'create'; + return $entity === 'CustomGroup'; } } diff --git a/Civi/Api4/Service/Spec/SpecFormatter.php b/Civi/Api4/Service/Spec/SpecFormatter.php index 54b1019090..ad7a79b3c7 100644 --- a/Civi/Api4/Service/Spec/SpecFormatter.php +++ b/Civi/Api4/Service/Spec/SpecFormatter.php @@ -262,6 +262,14 @@ class SpecFormatter { } } } + elseif ($returnFormat && !empty($pseudoconstant['callback'])) { + $callbackOptions = call_user_func(\Civi\Core\Resolver::singleton()->get($pseudoconstant['callback']), NULL, [], $values); + foreach ($callbackOptions as $callbackOption) { + if (is_array($callbackOption) && !empty($callbackOption['id']) && isset($optionIndex[$callbackOption['id']])) { + $options[$optionIndex[$callbackOption['id']]] += $callbackOption; + } + } + } } } if (isset($props)) { diff --git a/Civi/Api4/Utils/CoreUtil.php b/Civi/Api4/Utils/CoreUtil.php index 7c54903268..8bb34dedce 100644 --- a/Civi/Api4/Utils/CoreUtil.php +++ b/Civi/Api4/Utils/CoreUtil.php @@ -115,7 +115,7 @@ class CoreUtil { * @throws \Civi\API\Exception\UnauthorizedException */ public static function getCustomGroupExtends(string $entityName) { - // Custom_group.extends pretty much maps 1-1 with entity names, except for a couple oddballs (Contact, Participant). + // Custom_group.extends pretty much maps 1-1 with entity names, except for Contact. switch ($entityName) { case 'Contact': return [ diff --git a/Civi/Api4/Utils/FormattingUtil.php b/Civi/Api4/Utils/FormattingUtil.php index e8bdbcb6b7..5d6991622e 100644 --- a/Civi/Api4/Utils/FormattingUtil.php +++ b/Civi/Api4/Utils/FormattingUtil.php @@ -30,7 +30,7 @@ class FormattingUtil { /** * @var string[] */ - public static $pseudoConstantSuffixes = ['name', 'abbr', 'label', 'color', 'description', 'icon']; + public static $pseudoConstantSuffixes = ['name', 'abbr', 'label', 'color', 'description', 'icon', 'grouping']; /** * Massage values into the format the BAO expects for a write operation diff --git a/api/v3/CustomGroup.php b/api/v3/CustomGroup.php index f45c9ca302..e80f232480 100644 --- a/api/v3/CustomGroup.php +++ b/api/v3/CustomGroup.php @@ -16,16 +16,10 @@ */ /** - * Use this API to create a new group. - * - * The 'extends' value accepts an array or a comma separated string. - * e.g array( - * 'Individual','Contact') or 'Individual,Contact' - * See the CRM Data Model for custom_group property definitions - * $params['class_name'] is a required field, class being extended. + * Create or modify a custom field group. * * @param array $params - * Array per getfields metadata. + * For legacy reasons, 'extends' can be passed as an array (for setting Participant column_value) * * @return array * @todo $params['extends'] is array format - is that std compatible diff --git a/tests/phpunit/CRM/Core/BAO/CustomGroupTest.php b/tests/phpunit/CRM/Core/BAO/CustomGroupTest.php index 1f001a412d..a575281adf 100644 --- a/tests/phpunit/CRM/Core/BAO/CustomGroupTest.php +++ b/tests/phpunit/CRM/Core/BAO/CustomGroupTest.php @@ -701,4 +701,11 @@ class CRM_Core_BAO_CustomGroupTest extends CiviUnitTestCase { $this->assertArrayHasKey('ParticipantEventType', $extends); } + public function testMapTableName() { + $this->assertEquals('civicrm_case', CRM_Core_BAO_CustomGroup::mapTableName('Case')); + $this->assertEquals('civicrm_contact', CRM_Core_BAO_CustomGroup::mapTableName('Contact')); + $this->assertEquals('civicrm_contact', CRM_Core_BAO_CustomGroup::mapTableName('Individual')); + $this->assertEquals('civicrm_participant', CRM_Core_BAO_CustomGroup::mapTableName('Participant')); + } + } diff --git a/tests/phpunit/api/v4/Action/BasicCustomFieldTest.php b/tests/phpunit/api/v4/Action/BasicCustomFieldTest.php index d773074061..9285117d0d 100644 --- a/tests/phpunit/api/v4/Action/BasicCustomFieldTest.php +++ b/tests/phpunit/api/v4/Action/BasicCustomFieldTest.php @@ -22,6 +22,8 @@ namespace api\v4\Action; use Civi\Api4\Contact; use Civi\Api4\CustomField; use Civi\Api4\CustomGroup; +use Civi\Api4\Event; +use Civi\Api4\FinancialType; use Civi\Api4\OptionGroup; use Civi\Api4\Relationship; use Civi\Api4\RelationshipCache; @@ -31,6 +33,16 @@ use Civi\Api4\RelationshipCache; */ class BasicCustomFieldTest extends BaseCustomValueTest { + public function tearDown(): void { + FinancialType::delete(FALSE) + ->addWhere('name', '=', 'Test_Type') + ->execute(); + Event::delete(FALSE) + ->addWhere('id', '>', 0) + ->execute(); + parent::tearDown(); + } + /** * @throws \API_Exception */ @@ -519,13 +531,102 @@ class BasicCustomFieldTest extends BaseCustomValueTest { $this->assertEquals('2025-06-11 12:15:30', $contact["$cgName.DateTime"]); } + public function testExtendsIdFilter() { + $fieldUnfiltered = \Civi\Api4\CustomGroup::getFields(FALSE) + ->setLoadOptions(['id', 'name', 'grouping']) + ->addWhere('name', '=', 'extends_entity_column_id') + ->execute()->first(); + $this->assertCount(3, $fieldUnfiltered['options']); + + $fieldFilteredByParticipant = \Civi\Api4\CustomGroup::getFields(FALSE) + ->setLoadOptions(['id', 'name', 'grouping']) + ->addWhere('name', '=', 'extends_entity_column_id') + ->addValue('extends', 'Participant') + ->execute()->first(); + $this->assertEquals($fieldUnfiltered['options'], $fieldFilteredByParticipant['options']); + + $participantOptions = array_column($fieldFilteredByParticipant['options'], 'grouping', 'name'); + $this->assertEquals('event_id', $participantOptions['ParticipantEventName']); + $this->assertEquals('event_id.event_type_id', $participantOptions['ParticipantEventType']); + $this->assertEquals('role_id', $participantOptions['ParticipantRole']); + + $fieldFilteredByContact = \Civi\Api4\CustomGroup::getFields(FALSE) + ->setLoadOptions(['id', 'name', 'grouping']) + ->addWhere('name', '=', 'extends_entity_column_id') + ->addValue('extends', 'Contact') + ->execute()->first(); + $this->assertFalse($fieldFilteredByContact['options']); + } + public function testExtendsMetadata() { $field = \Civi\Api4\CustomGroup::getFields(FALSE) - ->setLoadOptions(['id', 'name']) + ->setLoadOptions(['id', 'name', 'grouping']) ->addWhere('name', '=', 'extends') ->execute()->first(); - $options = array_column($field['options'], 'name', 'id'); + $options = array_column($field['options'], 'grouping', 'id'); + $this->assertArrayNotHasKey('ParticipantRole', $options); $this->assertArrayHasKey('Participant', $options); + $this->assertEquals('contact_sub_type', $options['Individual']); + $this->assertEquals('case_type_id', $options['Case']); + + // Test contribution type + $financialType = FinancialType::create(FALSE) + ->addValue('name', 'Test_Type') + ->addValue('is_deductible', TRUE) + ->addValue('is_reserved', FALSE) + ->execute()->single(); + $contributionGroup = CustomGroup::create(FALSE) + ->addValue('extends', 'Contribution') + ->addValue('title', 'Contribution Fields') + ->addValue('extends_entity_column_value:name', ['Test_Type']) + ->execute()->single(); + $this->assertContains($financialType['id'], $contributionGroup['extends_entity_column_value']); + } + + public function testExtendsParticipantMetadata() { + $event1 = Event::create(FALSE) + ->addValue('event_type_id:name', 'Fundraiser') + ->addValue('title', 'Test Fun Event') + ->addValue('start_date', '2022-05-02 18:24:00') + ->execute()->first(); + $event2 = Event::create(FALSE) + ->addValue('event_type_id:name', 'Fundraiser') + ->addValue('title', 'Test Fun Event2') + ->addValue('start_date', '2022-05-02 18:24:00') + ->execute()->first(); + $event3 = Event::create(FALSE) + ->addValue('event_type_id:name', 'Meeting') + ->addValue('title', 'Test Me Event') + ->addValue('start_date', '2022-05-02 18:24:00') + ->execute()->first(); + + $field = \Civi\Api4\CustomGroup::getFields(FALSE) + ->setLoadOptions(['id', 'name', 'label']) + ->addValue('extends_entity_column_id:name', 'ParticipantEventName') + ->addWhere('name', '=', 'extends_entity_column_value') + ->execute()->first(); + $eventOptions = array_column($field['options'], 'label', 'id'); + $this->assertEquals('Test Fun Event', $eventOptions[$event1['id']]); + $this->assertEquals('Test Fun Event2', $eventOptions[$event2['id']]); + $this->assertEquals('Test Me Event', $eventOptions[$event3['id']]); + + $field = \Civi\Api4\CustomGroup::getFields(FALSE) + ->setLoadOptions(['id', 'name', 'label']) + ->addValue('extends_entity_column_id:name', 'ParticipantEventType') + ->addWhere('name', '=', 'extends_entity_column_value') + ->execute()->first(); + $eventOptions = array_column($field['options'], 'name'); + $this->assertContains('Meeting', $eventOptions); + $this->assertContains('Fundraiser', $eventOptions); + + $field = \Civi\Api4\CustomGroup::getFields(FALSE) + ->setLoadOptions(['id', 'name', 'label']) + ->addValue('extends_entity_column_id:name', 'ParticipantRole') + ->addWhere('name', '=', 'extends_entity_column_value') + ->execute()->first(); + $roleOptions = array_column($field['options'], 'name'); + $this->assertContains('Volunteer', $roleOptions); + $this->assertContains('Attendee', $roleOptions); } } diff --git a/xml/schema/Core/CustomGroup.xml b/xml/schema/Core/CustomGroup.xml index a2ed26e80f..ca3d0cc5e7 100644 --- a/xml/schema/Core/CustomGroup.xml +++ b/xml/schema/Core/CustomGroup.xml @@ -58,7 +58,7 @@ Type of object this group extends (can add other options later e.g. contact_address, etc.). 1.1 - CRM_Core_SelectValues::customGroupExtends + CRM_Core_BAO_CustomGroup::getCustomGroupExtendsOptions -- 2.25.1