public static function getJoins(array $allowedEntities):array {
$joins = [];
foreach ($allowedEntities as $entity) {
- // Multi-record custom field groups (to-date only the contact entity supports these)
- if (in_array('CustomValue', $entity['type'])) {
- // TODO: Lookup target entity from custom group if someday other entities support multi-record custom data
- $targetEntity = $allowedEntities['Contact'];
- // Join from Custom group to Contact (n-1)
- $alias = "{$entity['name']}_{$targetEntity['name']}_entity_id";
- $joins[$entity['name']][] = [
- 'label' => $entity['title'] . ' ' . $targetEntity['title'],
- 'description' => '',
- 'entity' => $targetEntity['name'],
- 'conditions' => self::getJoinConditions('entity_id', $alias . '.id'),
- 'defaults' => self::getJoinDefaults($alias, $targetEntity),
- 'alias' => $alias,
- 'multi' => FALSE,
- ];
- // Join from Contact to Custom group (n-n)
- $alias = "{$targetEntity['name']}_{$entity['name']}_entity_id";
- $joins[$targetEntity['name']][] = [
- 'label' => $entity['title_plural'],
- 'description' => '',
- 'entity' => $entity['name'],
- 'conditions' => self::getJoinConditions('id', $alias . '.entity_id'),
- 'defaults' => self::getJoinDefaults($alias, $entity),
- 'alias' => $alias,
- 'multi' => TRUE,
- ];
-
- // Allow joins via EntityRef fields in multi-value custom data
- foreach ($entity['fields'] as $field) {
- // Note: we skip entity_id field since it is handled below.
- if ($field['input_type'] === 'EntityRef' && $field['name'] !== 'entity_id') {
-
- // Join from the custom data entity to the referenced entity
- $joins[$entity['name']][] = self::getEntityRefJoins($entity, $field);
+ $isCustomEntity = in_array('CustomValue', $entity['type'], TRUE);
- // Join from referenced entity to the custom data entity
- $joins[$field['fk_entity']][] = [
- 'label' => $entity["title_plural"],
- 'description' => $entity["description"],
- 'entity' => $entity["name"],
- 'conditions' => [],
- 'defaults' => [],
- 'alias' => $entity['name'],
- 'multi' => TRUE,
- ];
- }
- }
- }
// Non-custom DAO entities
- elseif (!empty($entity['dao'])) {
+ if (!$isCustomEntity && !empty($entity['dao'])) {
/** @var \CRM_Core_DAO $daoClass */
$daoClass = $entity['dao'];
$references = $daoClass::getReferenceColumns();
}
}
}
- // Custom EntityRef joins
- foreach ($fields as $field) {
- if ($field['type'] === 'Custom' && $field['input_type'] === 'EntityRef') {
- $joins[$entity['name']][] = self::getEntityRefJoins($entity, $field);
+ }
+
+ // Custom EntityRef joins
+ foreach ($entity['fields'] as $field) {
+ if (($field['type'] === 'Custom' || $isCustomEntity) && $field['fk_entity'] && $field['input_type'] === 'EntityRef') {
+ $entityRefJoins = self::getEntityRefJoins($entity, $field);
+ foreach ($entityRefJoins as $joinEntity => $joinInfo) {
+ $joins[$joinEntity][] = $joinInfo;
}
}
}
}
/**
- * Get a join for an entity reference
+ * Get joins for entity reference custom fields, and the entity_id fields in multi-record custom groups.
*
- * @return void
+ * @return array[]
*/
- public static function getEntityRefJoins($entity, $field) {
+ public static function getEntityRefJoins(array $entity, array $field): array {
$exploded = explode('.', $field['name']);
$bareFieldName = array_reverse($exploded)[0];
- $alias = $entity['name'] . '_' . $field['fk_entity'] . '_' . $bareFieldName;
- $join = [
- 'label' => $entity['title'] . ' ' . $field['title'],
+ $alias = "{$entity['name']}_{$field['fk_entity']}_$bareFieldName";
+ $joins[$entity['name']] = [
+ 'label' => $entity['title'] . ' ' . $field['label'],
'description' => $field['description'],
'entity' => $field['fk_entity'],
'conditions' => self::getJoinConditions($field['name'], $alias . '.id'),
'alias' => $alias,
'multi' => FALSE,
];
- return $join;
+ // Do reverse join if not the same entity
+ if ($entity['name'] !== $field['fk_entity']) {
+ $alias = "{$field['fk_entity']}_{$entity['name']}_$bareFieldName";
+ $joins[$field['fk_entity']] = [
+ 'label' => $entity['title_plural'],
+ 'description' => $entity['description'],
+ 'entity' => $entity['name'],
+ 'conditions' => self::getJoinConditions('id', "$alias.{$field['name']}"),
+ 'defaults' => [],
+ 'alias' => $alias,
+ 'multi' => TRUE,
+ ];
+ }
+ return $joins;
}
/**
<?php
namespace Civi\Search;
-use Civi\Test\HeadlessInterface;
-use Civi\Test\TransactionalInterface;
+use api\v4\Api4TestBase;
+use Civi\Test\CiviEnvBuilder;
+
+require_once __DIR__ . '/../../../../../../tests/phpunit/api/v4/Api4TestBase.php';
/**
* @group headless
*/
-class AdminTest extends \PHPUnit\Framework\TestCase implements HeadlessInterface, TransactionalInterface {
+class AdminTest extends Api4TestBase {
- public function setUpHeadless() {
+ public function setUpHeadless(): CiviEnvBuilder {
return \Civi\Test::headless()->installMe(__DIR__)->apply();
}
}
public function testEntityRefGetJoins(): void {
- \Civi\Api4\CustomGroup::create()->setValues([
+ $this->createTestRecord('CustomGroup', [
'title' => 'EntityRefFields',
'extends' => 'Individual',
- ])->execute();
- \Civi\Api4\CustomField::create()->setValues([
+ ]);
+ $this->createTestRecord('CustomField', [
'label' => 'Favorite Nephew',
'name' => 'favorite_nephew',
'custom_group_id.name' => 'EntityRefFields',
'html_type' => 'Autocomplete-Select',
'data_type' => 'EntityReference',
'fk_entity' => 'Contact',
- ])->execute();
+ ]);
+ $allowedEntities = Admin::getSchema();
+ $joins = Admin::getJoins($allowedEntities);
+
+ $entityRefJoin = \CRM_Utils_Array::findAll($joins['Contact'], ['alias' => 'Contact_Contact_favorite_nephew']);
+ $this->assertCount(1, $entityRefJoin);
+ $this->assertEquals([['EntityRefFields.favorite_nephew', '=', 'Contact_Contact_favorite_nephew.id']], $entityRefJoin[0]['conditions']);
+ $this->assertStringContainsString('Favorite Nephew', $entityRefJoin[0]['label']);
+ }
+
+ public function testMultiRecordCustomGetJoins(): void {
+ $this->createTestRecord('CustomGroup', [
+ 'title' => 'Multiple Things',
+ 'name' => 'MultiRecordActivity',
+ 'extends' => 'Activity',
+ 'is_multiple' => TRUE,
+ ]);
+ $this->createTestRecord('CustomField', [
+ 'label' => 'Ref Group',
+ 'name' => 'ref_group',
+ 'custom_group_id.name' => 'MultiRecordActivity',
+ 'html_type' => 'Autocomplete-Select',
+ 'data_type' => 'EntityReference',
+ 'fk_entity' => 'Group',
+ ]);
$allowedEntities = Admin::getSchema();
$joins = Admin::getJoins($allowedEntities);
- $this->assertContains('Contact Favorite Nephew', array_column($joins['Contact'], 'label'));
+
+ $entityRefJoin = \CRM_Utils_Array::findAll($joins['Custom_MultiRecordActivity'], ['alias' => 'Custom_MultiRecordActivity_Group_ref_group']);
+ $this->assertCount(1, $entityRefJoin);
+ $this->assertEquals([['ref_group', '=', 'Custom_MultiRecordActivity_Group_ref_group.id']], $entityRefJoin[0]['conditions']);
+
+ $reverseJoin = \CRM_Utils_Array::findAll($joins['Group'], ['alias' => 'Group_Custom_MultiRecordActivity_ref_group']);
+ $this->assertCount(1, $reverseJoin);
+ $this->assertEquals([['id', '=', 'Group_Custom_MultiRecordActivity_ref_group.ref_group']], $reverseJoin[0]['conditions']);
+
+ $activityToCustomJoin = \CRM_Utils_Array::findAll($joins['Activity'], ['alias' => 'Activity_Custom_MultiRecordActivity_entity_id']);
+ $this->assertCount(1, $activityToCustomJoin);
+ $this->assertEquals([['id', '=', 'Activity_Custom_MultiRecordActivity_entity_id.entity_id']], $activityToCustomJoin[0]['conditions']);
+ $this->assertEquals('Multiple Things', $activityToCustomJoin[0]['label']);
+
+ $customToActivityJoin = \CRM_Utils_Array::findAll($joins['Custom_MultiRecordActivity'], ['alias' => 'Custom_MultiRecordActivity_Activity_entity_id']);
+ $this->assertCount(1, $customToActivityJoin);
+ $this->assertEquals([['entity_id', '=', 'Custom_MultiRecordActivity_Activity_entity_id.id']], $customToActivityJoin[0]['conditions']);
+ $this->assertEquals('Multiple Things Activity', $customToActivityJoin[0]['label']);
}
}