Adds @ui_join_filters annotations to APIv4 entities which automatically shows that field when adding a join
https://lab.civicrm.org/dev/core/-/issues/2313
namespace Civi\Api4;
/**
- * ActivityContact Entity.
+ * ActivityContact BridgeEntity.
*
- * This entity adds a record which relate a contact to activity.
+ * This connects a contact to an activity.
*
- * Creating a new ActivityContact requires at minimum a contact_id and activity_id.
+ * The record_type_id field determines the contact's role in the activity (source, target, or assignee).
+ * @ui_join_filters record_type_id
*
* @see \Civi\Api4\Activity
* @package Civi\Api4
* Creating a new address requires at minimum a contact's ID and location type ID
* and other attributes (although optional) like street address, city, country etc.
*
+ * @ui_join_filters location_type_id
+ *
* @package Civi\Api4
*/
class Address extends Generic\DAOEntity {
'data_type' => 'Array',
'description' => 'Connecting fields for EntityBridge types',
],
+ [
+ 'name' => 'ui_join_filters',
+ 'data_type' => 'Array',
+ 'description' => 'When joining entities in the UI, which fields should be presented by default in the ON clause',
+ ],
];
}))->setCheckPermissions($checkPermissions);
}
*
* @see \Civi\Api4\Relationship
* @bridge near_contact_id far_contact_id
+ * @ui_join_filters near_relation
* @package Civi\Api4
*/
class RelationshipCache extends Generic\AbstractEntity {
elseif ($key == 'return') {
$info['return'] = explode('|', $words[0]);
}
- elseif ($key == 'options') {
+ elseif ($key == 'options' || $key == 'ui_join_filters') {
$val = str_replace(', ', ',', implode(' ', $words));
- $info['options'] = explode(',', $val);
+ $info[$key] = explode(',', $val);
}
elseif ($key == 'throws' || $key == 'see') {
$info[$key][] = implode(' ', $words);
public static function getSchema() {
$schema = [];
$entities = \Civi\Api4\Entity::get()
- ->addSelect('name', 'title', 'type', 'title_plural', 'description', 'icon', 'paths', 'dao', 'bridge')
+ ->addSelect('name', 'title', 'type', 'title_plural', 'description', 'icon', 'paths', 'dao', 'bridge', 'ui_join_filters')
->addWhere('searchable', '=', TRUE)
->addOrderBy('title_plural')
->setChain([
) {
continue;
}
+ // For dynamic references getTargetEntities will return multiple targets; for normal joins this loop will only run once
foreach ($reference->getTargetEntities() as $targetTable => $targetEntityName) {
if (!isset($allowedEntities[$targetEntityName]) || $targetEntityName === $entity['name']) {
continue;
'description' => $dynamicCol ? '' : $keyField['label'],
'entity' => $targetEntityName,
'conditions' => self::getJoinConditions($keyField['name'], $alias . '.' . $reference->getTargetKey(), $targetTable, $dynamicCol),
+ 'defaults' => self::getJoinDefaults($alias, $targetEntity),
'alias' => $alias,
'multi' => FALSE,
];
'description' => $dynamicCol ? '' : $keyField['label'],
'entity' => $entity['name'],
'conditions' => self::getJoinConditions($reference->getTargetKey(), $alias . '.' . $keyField['name'], $targetTable, $dynamicCol ? $alias . '.' . $dynamicCol : NULL),
+ 'defaults' => self::getJoinDefaults($alias, $entity),
'alias' => $alias,
'multi' => TRUE,
];
[$bridge],
self::getJoinConditions('id', $alias . '.' . $baseKey, NULL, NULL)
),
+ 'defaults' => self::getJoinDefaults($alias, $targetEntity, $entity),
'bridge' => $bridge,
'alias' => $alias,
'multi' => TRUE,
[$bridge],
self::getJoinConditions($reference->getTargetKey(), $alias . '.' . $keyField['name'], $targetTable, $dynamicCol ? $alias . '.' . $dynamicCol : NULL)
),
+ 'defaults' => self::getJoinDefaults($alias, $baseEntity, $entity),
'bridge' => $bridge,
'alias' => $alias,
'multi' => TRUE,
return $conditions;
}
+ /**
+ * @param $alias
+ * @param array ...$entities
+ * @return array
+ */
+ private static function getJoinDefaults($alias, ...$entities):array {
+ $conditions = [];
+ foreach ($entities as $entity) {
+ foreach ($entity['ui_join_filters'] ?? [] as $fieldName) {
+ $field = civicrm_api4($entity['name'], 'getFields', [
+ 'select' => ['options'],
+ 'where' => [['name', '=', $fieldName]],
+ 'loadOptions' => ['name'],
+ ])->first();
+ $value = isset($field['options'][0]) ? json_encode($field['options'][0]['name']) : '';
+ $conditions[] = [
+ $alias . '.' . $fieldName . ($value ? ':name' : ''),
+ '=',
+ $value,
+ ];
+ }
+ }
+ return $conditions;
+ }
+
}
// Add the numbered suffix to the join conditions
// If this is a deep join, also add the base entity prefix
var prefix = alias.replace(new RegExp('_?' + join.alias + '_?\\d?\\d?$'), '');
- _.each(result.conditions, function(condition) {
+ function replaceRefs(condition) {
if (_.isArray(condition)) {
_.each(condition, function(ref, side) {
- if (side !== 1 && _.includes(ref, '.')) {
- condition[side] = ref.replace(join.alias + '.', alias + '.');
- } else if (side !== 1 && prefix.length && !_.includes(ref, '"') && !_.includes(ref, "'")) {
- condition[side] = prefix + '.' + ref;
+ if (side !== 1 && typeof ref === 'string') {
+ if (_.includes(ref, '.')) {
+ condition[side] = ref.replace(join.alias + '.', alias + '.');
+ } else if (prefix.length && !_.includes(ref, '"') && !_.includes(ref, "'")) {
+ condition[side] = prefix + '.' + ref;
+ }
}
});
}
- });
+ }
+ _.each(result.conditions, replaceRefs);
+ _.each(result.defaults, replaceRefs);
return result;
}
function getFieldAndJoin(fieldName, entityName) {
_.each(_.cloneDeep(join.conditions), function(condition) {
params.push(condition);
});
+ _.each(_.cloneDeep(join.defaults), function(condition) {
+ params.push(condition);
+ });
ctrl.savedSearch.api_params.join.push(params);
loadFieldOptions();
}