diff --git a/CRM/Contact/DAO/RelationshipCache.php b/CRM/Contact/DAO/RelationshipCache.php
index ba2d97d00223..5701d5c3e673 100644
--- a/CRM/Contact/DAO/RelationshipCache.php
+++ b/CRM/Contact/DAO/RelationshipCache.php
@@ -6,7 +6,7 @@
*
* Generated from xml/schema/CRM/Contact/RelationshipCache.xml
* DO NOT EDIT. Generated by CRM_Core_CodeGen
- * (GenCodeChecksum:3bee16c8388728d3e391e9dd7c17abb8)
+ * (GenCodeChecksum:ba039fcadc13e48749f965343301ec1d)
*/
/**
@@ -129,7 +129,7 @@ public function __construct() {
* Whether to return the plural version of the title.
*/
public static function getEntityTitle($plural = FALSE) {
- return $plural ? ts('Relationship Caches') : ts('Relationship Cache');
+ return $plural ? ts('Related Contacts') : ts('Related Contact');
}
/**
@@ -237,7 +237,7 @@ public static function &fields() {
'near_relation' => [
'name' => 'near_relation',
'type' => CRM_Utils_Type::T_STRING,
- 'title' => ts('Relationship Name (Near side)'),
+ 'title' => ts('Relationship Name (to related contact)'),
'description' => ts('name for relationship of near_contact to far_contact.'),
'maxlength' => 64,
'size' => CRM_Utils_Type::BIG,
@@ -271,7 +271,7 @@ public static function &fields() {
'far_relation' => [
'name' => 'far_relation',
'type' => CRM_Utils_Type::T_STRING,
- 'title' => ts('Relationship Name (Near side)'),
+ 'title' => ts('Relationship Name (from related contact)'),
'description' => ts('name for relationship of far_contact to near_contact.'),
'maxlength' => 64,
'size' => CRM_Utils_Type::BIG,
diff --git a/CRM/Core/DAO.php b/CRM/Core/DAO.php
index 2d40afd07363..79457ed5d91d 100644
--- a/CRM/Core/DAO.php
+++ b/CRM/Core/DAO.php
@@ -530,8 +530,7 @@ public function sequenceKey() {
* Returns list of FK relationships.
*
*
- * @return array
- * Array of CRM_Core_Reference_Interface
+ * @return CRM_Core_Reference_Basic[]
*/
public static function getReferenceColumns() {
return [];
diff --git a/Civi/Api4/Entity.php b/Civi/Api4/Entity.php
index 8dec4cebaf21..ac61fcf3892b 100644
--- a/Civi/Api4/Entity.php
+++ b/Civi/Api4/Entity.php
@@ -98,6 +98,11 @@ public static function getFields($checkPermissions = TRUE) {
'data_type' => 'Array',
'description' => 'Any @see annotations from docblock',
],
+ [
+ 'name' => 'bridge',
+ 'data_type' => 'Array',
+ 'description' => 'Connecting fields for EntityBridge types',
+ ],
];
}))->setCheckPermissions($checkPermissions);
}
diff --git a/Civi/Api4/Generic/AbstractEntity.php b/Civi/Api4/Generic/AbstractEntity.php
index d4e775ecefe1..1c69eae15ebe 100644
--- a/Civi/Api4/Generic/AbstractEntity.php
+++ b/Civi/Api4/Generic/AbstractEntity.php
@@ -86,7 +86,9 @@ protected static function getEntityName() {
* @return string
*/
protected static function getEntityTitle($plural = FALSE) {
- return static::getEntityName();
+ $name = static::getEntityName();
+ $dao = \CRM_Core_DAO_AllCoreTables::getFullName($name);
+ return $dao ? $dao::getEntityTitle($plural) : ($plural ? \CRM_Utils_String::pluralize($name) : $name);
}
/**
@@ -136,6 +138,13 @@ public static function getInfo() {
'type' => [self::stripNamespace(get_parent_class(static::class))],
'paths' => static::getEntityPaths(),
];
+ // Add info for entities with a corresponding DAO
+ $dao = \CRM_Core_DAO_AllCoreTables::getFullName($info['name']);
+ if ($dao) {
+ $info['paths'] = $dao::getEntityPaths();
+ $info['icon'] = $dao::$_icon;
+ $info['dao'] = $dao;
+ }
foreach (ReflectionUtils::getTraits(static::class) as $trait) {
$info['type'][] = self::stripNamespace($trait);
}
diff --git a/Civi/Api4/Generic/DAOEntity.php b/Civi/Api4/Generic/DAOEntity.php
index fa0352593868..ad34c1a7538b 100644
--- a/Civi/Api4/Generic/DAOEntity.php
+++ b/Civi/Api4/Generic/DAOEntity.php
@@ -90,29 +90,4 @@ public static function replace($checkPermissions = TRUE) {
->setCheckPermissions($checkPermissions);
}
- /**
- * @param bool $plural
- * Whether to return a plural title.
- * @return string
- */
- protected static function getEntityTitle($plural = FALSE) {
- $name = static::getEntityName();
- $dao = \CRM_Core_DAO_AllCoreTables::getFullName($name);
- return $dao ? $dao::getEntityTitle($plural) : $name;
- }
-
- /**
- * @return array
- */
- public static function getInfo() {
- $info = parent::getInfo();
- $dao = \CRM_Core_DAO_AllCoreTables::getFullName($info['name']);
- if ($dao) {
- $info['paths'] = $dao::getEntityPaths();
- $info['icon'] = $dao::$_icon;
- $info['dao'] = $dao;
- }
- return $info;
- }
-
}
diff --git a/Civi/Api4/Generic/Traits/EntityBridge.php b/Civi/Api4/Generic/Traits/EntityBridge.php
index 49b17680cd4e..ff0d4120d211 100644
--- a/Civi/Api4/Generic/Traits/EntityBridge.php
+++ b/Civi/Api4/Generic/Traits/EntityBridge.php
@@ -20,4 +20,24 @@
*/
trait EntityBridge {
+ /**
+ * Adds "bridge" info, which should specify an array of two field names from this entity
+ *
+ * This automatic function can be overridden by annotating the APIv4 entity like
+ * `@bridge contact_id group_id`
+ *
+ * @return array
+ */
+ public static function getInfo() {
+ $info = parent::getInfo();
+ if (!empty($info['dao']) && empty($info['bridge'])) {
+ foreach (($info['dao'])::fields() as $field) {
+ if (!empty($field['FKClassName']) || $field['name'] === 'entity_id') {
+ $info['bridge'][] = $field['name'];
+ }
+ }
+ }
+ return $info;
+ }
+
}
diff --git a/Civi/Api4/GroupContact.php b/Civi/Api4/GroupContact.php
index 808c1a92fd3c..086fb9b27394 100644
--- a/Civi/Api4/GroupContact.php
+++ b/Civi/Api4/GroupContact.php
@@ -24,8 +24,9 @@
* A contact can either be "Added" "Removed" or "Pending" in a group.
* CiviCRM only considers them to be "in" a group if their status is "Added".
*
+ * @bridge group_id contact_id
* @see \Civi\Api4\Group
- *
+ * @searchable false
* @package Civi\Api4
*/
class GroupContact extends Generic\DAOEntity {
diff --git a/Civi/Api4/RelationshipCache.php b/Civi/Api4/RelationshipCache.php
index a206cd2c2776..4c6281c3a877 100644
--- a/Civi/Api4/RelationshipCache.php
+++ b/Civi/Api4/RelationshipCache.php
@@ -23,7 +23,7 @@
* RelationshipCache - readonly table to facilitate joining and finding contacts by relationship.
*
* @see \Civi\Api4\Relationship
- *
+ * @bridge near_contact_id far_contact_id
* @package Civi\Api4
*/
class RelationshipCache extends Generic\AbstractEntity {
diff --git a/Civi/Api4/Utils/ReflectionUtils.php b/Civi/Api4/Utils/ReflectionUtils.php
index cb7ddb01dc5b..ce3f709a2c35 100644
--- a/Civi/Api4/Utils/ReflectionUtils.php
+++ b/Civi/Api4/Utils/ReflectionUtils.php
@@ -98,6 +98,9 @@ public static function parseDocBlock($comment) {
elseif ($key == 'searchable') {
$info[$key] = strtolower($words[0]) !== 'false';
}
+ elseif ($key == 'bridge') {
+ $info[$key] = $words;
+ }
elseif ($key == 'param' && $words) {
$type = $words[0][0] !== '$' ? explode('|', array_shift($words)) : NULL;
$param = rtrim(array_shift($words), '-:()/');
diff --git a/xml/schema/Contact/RelationshipCache.xml b/xml/schema/Contact/RelationshipCache.xml
index dd6c2c401a7d..7de6ac766e4d 100644
--- a/xml/schema/Contact/RelationshipCache.xml
+++ b/xml/schema/Contact/RelationshipCache.xml
@@ -8,7 +8,7 @@