APIv4 - Add Relationship custom fields to RelationshipCache entity
authorColeman Watts <coleman@civicrm.org>
Mon, 8 Feb 2021 15:11:10 +0000 (10:11 -0500)
committerColeman Watts <coleman@civicrm.org>
Tue, 9 Feb 2021 03:08:10 +0000 (22:08 -0500)
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
Civi/Api4/Service/Schema/SchemaMapBuilder.php
Civi/Api4/Service/Spec/SpecGatherer.php
Civi/Api4/Utils/CoreUtil.php
tests/phpunit/api/v4/Action/BasicCustomFieldTest.php

index b81422698ff8b8c8d955d827cacb065dab7d140a..46c06ea3ec51b6443c97c32f3d0bc7d29898caf2 100644 (file)
@@ -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;
index e6f82e5ce27c34625ca60100b061f3e9ada83a04..bcaad8ee350c1d69e78195ac79255a72e618b2b5 100644 (file)
@@ -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);
     }
   }
 
index 37c74acee672f2dbdb063b7c4355dfe588a846ae..5eda054230d9614e358d65ba44c0af336c6dd6d4 100644 (file)
@@ -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)
index b9090c97dec26daaa80e33a57d09cfd42da2ad7d..25c01f0e74bd642c50bde61399f0f340f30181c6 100644 (file)
@@ -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;
+  }
+
 }
index 991f5609ff6f7b675e4bf47f9165f1b06e412037..26e9cfa5a6f783614936c578661f80c9ddd2e4cc 100644 (file)
@@ -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"]);
+  }
+
 }