APIv4 - Fix multi-record custom entities FK join on entity_id
authorcolemanw <coleman@civicrm.org>
Fri, 17 Nov 2023 00:13:00 +0000 (19:13 -0500)
committercolemanw <coleman@civicrm.org>
Fri, 17 Nov 2023 00:23:22 +0000 (19:23 -0500)
This join was named contact instead of entity_id. Joins that don't match the field name are deprecated
in APIv4 so the old join has been marked as such. Renaming the join fixes SearchKit ability to join
from a custom entity to the parent record.

Civi/Api4/Action/Entity/GetLinks.php
Civi/Api4/Query/Api4SelectQuery.php
Civi/Api4/Service/Schema/Joinable/Joinable.php
Civi/Api4/Service/Schema/SchemaMapBuilder.php
tests/phpunit/api/v3/ACLPermissionTest.php

index 3408fea35be52113567bf5f1ca100d51a036dcf8..e05d66de1eab8e66c59fdf75c601bb307140421e 100644 (file)
@@ -36,7 +36,7 @@ class GetLinks extends \Civi\Api4\Generic\BasicGetAction {
           'links' => [],
         ];
         foreach ($table->getTableLinks() as $link) {
-          if (!$link->isDeprecated()) {
+          if (!$link->isDeprecatedBy()) {
             $item['links'][] = $link->toArray();
           }
         }
index 8887bc18348fee24e977d6d8037deee7d6d380cf..d6681de88d34d325f0733b4cda92fcf07829dabe 100644 (file)
@@ -833,9 +833,9 @@ class Api4SelectQuery extends Api4Query {
             return;
           }
         }
-        if ($link->isDeprecated()) {
+        if ($link->isDeprecatedBy()) {
           $deprecatedAlias = $link->getAlias();
-          \CRM_Core_Error::deprecatedWarning("Deprecated join alias '$deprecatedAlias' used in APIv4 get. Should be changed to '{$deprecatedAlias}_id'");
+          \CRM_Core_Error::deprecatedWarning("Deprecated join alias '$deprecatedAlias' used in APIv4 {$this->getEntity()} join to $joinEntity. Should be changed to '{$link->isDeprecatedBy()}'.");
         }
         $virtualField = $link->getSerialize();
         $baseTableAlias = $joinTreeNode['#table_alias'];
index 80ed04b6e80f6fdd8faf1429c79e725f39485ba4..5005e4338404439c137fa36f6ab262b62b21c7cd 100644 (file)
@@ -78,7 +78,7 @@ class Joinable {
   /**
    * @var bool
    */
-  protected $deprecated = FALSE;
+  protected $deprecatedBy = FALSE;
 
   /**
    * @param $targetTable
@@ -282,17 +282,16 @@ class Joinable {
   /**
    * @return bool
    */
-  public function isDeprecated() {
-    return $this->deprecated;
+  public function isDeprecatedBy() {
+    return $this->deprecatedBy;
   }
 
   /**
-   * @param bool $deprecated
-   *
+   * @param string|null $deprecatedBy
    * @return $this
    */
-  public function setDeprecated(bool $deprecated = TRUE) {
-    $this->deprecated = $deprecated;
+  public function setDeprecatedBy(string $deprecatedBy = NULL) {
+    $this->deprecatedBy = $deprecatedBy ?? $this->alias . '_id';
     return $this;
   }
 
index beb5862c6b6ac42fd45b48375c2ecdce17d74f08..49a1c2642a92a08e08150ea0a88c73296cbffbf1 100644 (file)
@@ -127,6 +127,15 @@ class SchemaMapBuilder extends AutoService {
       $customTable = $map->getTableByName($tableName);
       if (!$customTable) {
         $customTable = new Table($tableName);
+        // Add entity_id join from multi-record custom group to the
+        if (!empty($fieldData->is_multiple)) {
+          $newJoin = new Joinable($baseTable->getName(), $customInfo['column'], 'entity_id');
+          $customTable->addTableLink('entity_id', $newJoin);
+          // Deprecated "contact" join name
+          $oldJoin = new Joinable($baseTable->getName(), $customInfo['column'], AllCoreTables::convertEntityNameToLower($entityName));
+          $oldJoin->setDeprecatedBy('entity_id');
+          $customTable->addTableLink('entity_id', $oldJoin);
+        }
       }
 
       $map->addTable($customTable);
@@ -136,12 +145,6 @@ class SchemaMapBuilder extends AutoService {
       $links[$alias]['isMultiple'] = !empty($fieldData->is_multiple);
       $links[$alias]['columns'][$fieldData->name] = $fieldData->column_name;
 
-      // Add backreference
-      if (!empty($fieldData->is_multiple)) {
-        $joinable = new Joinable($baseTable->getName(), $customInfo['column'], AllCoreTables::convertEntityNameToLower($entityName));
-        $customTable->addTableLink('entity_id', $joinable);
-      }
-
       if ($fieldData->data_type === 'EntityReference' && isset($fieldData->fk_entity)) {
         $targetTable = self::getTableName($fieldData->fk_entity);
         $joinable = new Joinable($targetTable, 'id', $fieldData->name);
index 9836950b54b6e75fa1eb7431c342a62e2b89d4c6..9d75c2fded666f9a24dca8ce195ea4342a34d03c 100644 (file)
@@ -1154,10 +1154,10 @@ class api_v3_ACLPermissionTest extends CiviUnitTestCase {
     $this->hookClass->setHook('civicrm_aclWhereClause', [$this, 'aclWhereOnlyOne']);
     $this->cleanupCachedPermissions();
 
-    $customValues = CustomValue::get($group)->addSelect('*', 'contact.first_name')->execute();
+    $customValues = CustomValue::get($group)->addSelect('*', 'entity_id.first_name')->execute();
     $this->assertCount(1, $customValues);
     $this->assertEquals($c2, $customValues[0]['entity_id']);
-    $this->assertEquals('C2', $customValues[0]['contact.first_name']);
+    $this->assertEquals('C2', $customValues[0]['entity_id.first_name']);
 
     $customValues = Contact::get()
       ->addJoin('Custom_' . $group . ' AS cf')