From cbda979058bb291d19efad73b703d3fcda991191 Mon Sep 17 00:00:00 2001 From: Coleman Watts Date: Mon, 8 Feb 2021 10:11:10 -0500 Subject: [PATCH] APIv4 - Add Relationship custom fields to RelationshipCache entity Because the RelationshipCache entity is meant to be used without requiring a join to the Relationship table, this permits direct access to Relationship custom fields as if they belong to the RelationshipCache table. --- CRM/Core/SelectValues.php | 3 +- Civi/Api4/Service/Schema/SchemaMapBuilder.php | 17 +++---- Civi/Api4/Service/Spec/SpecGatherer.php | 18 +++---- Civi/Api4/Utils/CoreUtil.php | 38 +++++++++++++++ .../api/v4/Action/BasicCustomFieldTest.php | 48 +++++++++++++++++++ 5 files changed, 104 insertions(+), 20 deletions(-) diff --git a/CRM/Core/SelectValues.php b/CRM/Core/SelectValues.php index b81422698f..46c06ea3ec 100644 --- a/CRM/Core/SelectValues.php +++ b/CRM/Core/SelectValues.php @@ -204,8 +204,7 @@ class CRM_Core_SelectValues { 'Address' => ts('Addresses'), 'Campaign' => ts('Campaigns'), ]; - $contactTypes = self::contactType(); - $contactTypes = !empty($contactTypes) ? ['Contact' => 'Contacts'] + $contactTypes : []; + $contactTypes = ['Contact' => ts('Contacts')] + self::contactType(); $extendObjs = CRM_Core_OptionGroup::values('cg_extend_objects'); $customGroupExtends = array_merge($contactTypes, $customGroupExtends, $extendObjs); return $customGroupExtends; diff --git a/Civi/Api4/Service/Schema/SchemaMapBuilder.php b/Civi/Api4/Service/Schema/SchemaMapBuilder.php index e6f82e5ce2..bcaad8ee35 100644 --- a/Civi/Api4/Service/Schema/SchemaMapBuilder.php +++ b/Civi/Api4/Service/Schema/SchemaMapBuilder.php @@ -144,21 +144,18 @@ class SchemaMapBuilder { /** * @param \Civi\Api4\Service\Schema\SchemaMap $map * @param \Civi\Api4\Service\Schema\Table $baseTable - * @param string $entity + * @param string $entityName */ - private function addCustomFields(SchemaMap $map, Table $baseTable, $entity) { + private function addCustomFields(SchemaMap $map, Table $baseTable, string $entityName) { + $customInfo = \Civi\Api4\Utils\CoreUtil::getCustomGroupExtends($entityName); // Don't be silly - if (!array_key_exists($entity, \CRM_Core_SelectValues::customGroupExtends())) { + if (!$customInfo) { return; } - $queryEntity = (array) $entity; - if ($entity == 'Contact') { - $queryEntity = ['Contact', 'Individual', 'Organization', 'Household']; - } $fieldData = \CRM_Utils_SQL_Select::from('civicrm_custom_field f') ->join('custom_group', 'INNER JOIN civicrm_custom_group g ON g.id = f.custom_group_id') ->select(['g.name as custom_group_name', 'g.table_name', 'g.is_multiple', 'f.name', 'label', 'column_name', 'option_group_id']) - ->where('g.extends IN (@entity)', ['@entity' => $queryEntity]) + ->where('g.extends IN (@entity)', ['@entity' => $customInfo['extends']]) ->where('g.is_active') ->where('f.is_active') ->execute(); @@ -182,14 +179,14 @@ class SchemaMapBuilder { // Add backreference if (!empty($fieldData->is_multiple)) { - $joinable = new Joinable($baseTable->getName(), 'id', AllCoreTables::convertEntityNameToLower($entity)); + $joinable = new Joinable($baseTable->getName(), $customInfo['column'], AllCoreTables::convertEntityNameToLower($entityName)); $customTable->addTableLink('entity_id', $joinable); } } foreach ($links as $alias => $link) { $joinable = new CustomGroupJoinable($link['tableName'], $alias, $link['isMultiple'], $link['columns']); - $baseTable->addTableLink('id', $joinable); + $baseTable->addTableLink($customInfo['column'], $joinable); } } diff --git a/Civi/Api4/Service/Spec/SpecGatherer.php b/Civi/Api4/Service/Spec/SpecGatherer.php index 37c74acee6..5eda054230 100644 --- a/Civi/Api4/Service/Spec/SpecGatherer.php +++ b/Civi/Api4/Service/Spec/SpecGatherer.php @@ -48,7 +48,7 @@ class SpecGatherer { // Real entities if (strpos($entity, 'Custom_') !== 0) { $this->addDAOFields($entity, $action, $specification, $values); - if ($includeCustom && array_key_exists($entity, \CRM_Core_SelectValues::customGroupExtends())) { + if ($includeCustom) { $this->addCustomFields($entity, $specification, $values); } } @@ -118,14 +118,16 @@ class SpecGatherer { * @throws \API_Exception */ private function addCustomFields($entity, RequestSpec $specification, $values = []) { - // Custom_group.extends pretty much maps 1-1 with entity names, except for a couple oddballs (Contact, Participant). - $extends = [$entity]; - if ($entity === 'Contact') { - $contactType = !empty($values['contact_type']) ? [$values['contact_type']] : \CRM_Contact_BAO_ContactType::basicTypes(); - $extends = array_merge(['Contact'], $contactType); + $customInfo = \Civi\Api4\Utils\CoreUtil::getCustomGroupExtends($entity); + if (!$customInfo) { + return; } - if ($entity === 'Participant') { - $extends = ['Participant', 'ParticipantRole', 'ParticipantEventName', 'ParticipantEventType']; + // If a contact_type was passed in, exclude custom groups for other contact types + if ($entity === 'Contact' && !empty($values['contact_type'])) { + $extends = ['Contact', $values['contact_type']]; + } + else { + $extends = $customInfo['extends']; } $customFields = CustomField::get(FALSE) ->addWhere('custom_group.extends', 'IN', $extends) diff --git a/Civi/Api4/Utils/CoreUtil.php b/Civi/Api4/Utils/CoreUtil.php index b9090c97de..25c01f0e74 100644 --- a/Civi/Api4/Utils/CoreUtil.php +++ b/Civi/Api4/Utils/CoreUtil.php @@ -80,4 +80,42 @@ class CoreUtil { return $operators; } + /** + * For a given API Entity, return the types of custom fields it supports and the column they join to. + * + * @param string $entityName + * @return array|mixed|null + * @throws \API_Exception + * @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). + switch ($entityName) { + case 'Contact': + return [ + 'extends' => array_merge(['Contact'], array_keys(\CRM_Core_SelectValues::contactType())), + 'column' => 'id', + ]; + + case 'Participant': + return [ + 'extends' => ['Participant', 'ParticipantRole', 'ParticipantEventName', 'ParticipantEventType'], + 'column' => 'id', + ]; + + case 'RelationshipCache': + return [ + 'extends' => ['Relationship'], + 'column' => 'relationship_id', + ]; + } + if (array_key_exists($entityName, \CRM_Core_SelectValues::customGroupExtends())) { + return [ + 'extends' => [$entityName], + 'column' => 'id', + ]; + } + return NULL; + } + } diff --git a/tests/phpunit/api/v4/Action/BasicCustomFieldTest.php b/tests/phpunit/api/v4/Action/BasicCustomFieldTest.php index 991f5609ff..26e9cfa5a6 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\Relationship; +use Civi\Api4\RelationshipCache; /** * @group headless @@ -222,4 +224,50 @@ class BasicCustomFieldTest extends BaseCustomValueTest { $this->assertNotContains($contactId2, array_keys((array) $search)); } + public function testRelationshipCacheCustomFields() { + $cgName = uniqid('RelFields'); + + $customGroup = CustomGroup::create(FALSE) + ->addValue('name', $cgName) + ->addValue('extends', 'Relationship') + ->execute() + ->first(); + + CustomField::create(FALSE) + ->addValue('label', 'PetName') + ->addValue('custom_group_id', $customGroup['id']) + ->addValue('html_type', 'Text') + ->addValue('data_type', 'String') + ->execute(); + + $parent = Contact::create(FALSE) + ->addValue('first_name', 'Parent') + ->addValue('last_name', 'Tester') + ->addValue('contact_type', 'Individual') + ->execute() + ->first()['id']; + + $child = Contact::create(FALSE) + ->addValue('first_name', 'Child') + ->addValue('last_name', 'Tester') + ->addValue('contact_type', 'Individual') + ->execute() + ->first()['id']; + + $relationship = Relationship::create(FALSE) + ->addValue('contact_id_a', $parent) + ->addValue('contact_id_b', $child) + ->addValue('relationship_type_id', 1) + ->addValue("$cgName.PetName", 'Buddy') + ->execute(); + + $results = RelationshipCache::get(FALSE) + ->addSelect("$cgName.PetName") + ->addWhere("$cgName.PetName", '=', 'Buddy') + ->execute(); + + $this->assertCount(2, $results); + $this->assertEquals('Buddy', $results[0]["$cgName.PetName"]); + } + } -- 2.25.1