This improves link construction to propery replace tokens in explicit joins and implicit joins.
$apiParams['limit'] = $settings['limit'] ?? NULL;
$apiParams['offset'] = $page ? $apiParams['limit'] * ($page - 1) : 0;
$apiParams['orderBy'] = $this->getOrderByFromSort();
-
- // Select the ids of implicitly joined entities (helps with displaying links)
- foreach ($apiParams['select'] as $fieldName) {
- if (strstr($fieldName, '.') && !strstr($fieldName, ' AS ') && !strstr($fieldName, ':')) {
- $idField = substr($fieldName, 0, strrpos($fieldName, '.')) . '_id';
- $prefix = '';
- $id = $idField;
- if (strstr($id, '.')) {
- [$prefix, $idField] = explode(',', $id);
- $prefix .= '.';
- }
- if (!in_array($idField, $apiParams['select']) && !empty($this->getField($idField)['fk_entity']) && !$this->canAggregate($id, $prefix)) {
- $apiParams['select'][] = $idField;
- }
- }
- }
- // Select the ids of explicitly joined entities (helps with displaying links)
- foreach ($apiParams['join'] ?? [] as $join) {
- $joinEntity = explode(' AS ', $join[0])[1];
- $idField = $joinEntity . '.id';
- if (!in_array($idField, $apiParams['select']) && !$this->canAggregate('id', $joinEntity . '.')) {
- $apiParams['select'][] = $idField;
- }
- }
- // Select value fields for in-place editing
- foreach ($settings['columns'] ?? [] as $column) {
- if (isset($column['editable']['value']) && !in_array($column['editable']['value'], $apiParams['select'])) {
- $apiParams['select'][] = $column['editable']['value'];
- }
- }
+ $this->augmentSelectClause($apiParams);
}
$this->applyFilters();
return $this->_afform;
}
+ /**
+ * Adds additional useful fields to the select clause
+ *
+ * @param array $apiParams
+ */
+ private function augmentSelectClause(&$apiParams): void {
+ $joinAliases = [];
+ // Select the ids of explicitly joined entities (helps with displaying links)
+ foreach ($apiParams['join'] ?? [] as $join) {
+ $joinAliases[] = $joinAlias = explode(' AS ', $join[0])[1];
+ $idFieldName = $joinAlias . '.id';
+ if (!in_array($idFieldName, $apiParams['select']) && !$this->canAggregate('id', $joinAlias . '.')) {
+ $apiParams['select'][] = $idFieldName;
+ }
+ }
+ // Select the ids of implicitly joined entities (helps with displaying links)
+ foreach ($apiParams['select'] as $fieldName) {
+ if (strstr($fieldName, '.') && !strstr($fieldName, ' AS ') && !strstr($fieldName, ':')) {
+ $idFieldName = $fieldNameWithoutPrefix = substr($fieldName, 0, strrpos($fieldName, '.'));
+ $idField = $this->getField($idFieldName);
+ $explicitJoin = '';
+ if (strstr($idFieldName, '.')) {
+ [$prefix, $fieldNameWithoutPrefix] = explode('.', $idFieldName, 2);
+ if (in_array($prefix, $joinAliases, TRUE)) {
+ $explicitJoin = $prefix . '.';
+ }
+ }
+ if (!in_array($idFieldName, $apiParams['select']) && !empty($idField['fk_entity']) && !$this->canAggregate($fieldNameWithoutPrefix, $explicitJoin)) {
+ $apiParams['select'][] = $idFieldName;
+ }
+ }
+ }
+ // Select value fields for in-place editing
+ foreach ($this->display['settings']['columns'] ?? [] as $column) {
+ if (isset($column['editable']['value']) && !in_array($column['editable']['value'], $apiParams['select'])) {
+ $apiParams['select'][] = $column['editable']['value'];
+ }
+ }
+ }
+
}
->setChain([
'get' => ['$name', 'getActions', ['where' => [['name', '=', 'get']]], ['params']],
])->execute();
- $getFields = ['name', 'title', 'label', 'description', 'options', 'input_type', 'input_attrs', 'data_type', 'serialize', 'entity', 'fk_entity', 'readonly'];
foreach ($entities as $entity) {
// Skip if entity doesn't have a 'get' action or the user doesn't have permission to use get
if ($entity['get']) {
'action' => $action,
];
}
- $entity['fields'] = (array) civicrm_api4($entity['name'], 'getFields', [
- 'select' => $getFields,
+ $getFields = civicrm_api4($entity['name'], 'getFields', [
+ 'select' => ['name', 'title', 'label', 'description', 'options', 'input_type', 'input_attrs', 'data_type', 'serialize', 'entity', 'fk_entity', 'readonly'],
'where' => [['name', 'NOT IN', ['api_key', 'hash']]],
'orderBy' => ['label'],
]);
+ foreach ($getFields as $field) {
+ $field['fieldName'] = $field['name'];
+ $entity['fields'][] = $field;
+ }
$params = $entity['get'][0];
// Entity must support at least these params or it is too weird for search kit
if (!array_diff(['select', 'where', 'orderBy', 'limit', 'offset'], array_keys($params))) {
}
}
// Add in FK fields for implicit joins
- // For example, add a `campaign.title` field to the Contribution entity
+ // For example, add a `campaign_id.title` field to the Contribution entity
foreach ($schema as &$entity) {
if (in_array('DAOEntity', $entity['type'], TRUE) && !in_array('EntityBridge', $entity['type'], TRUE)) {
foreach (array_reverse($entity['fields'], TRUE) as $index => $field) {
if (!empty($field['fk_entity']) && !$field['options'] && !empty($schema[$field['fk_entity']]['label_field'])) {
$isCustom = strpos($field['name'], '.');
- // Custom fields: append "ID" to original field label
+ // Custom fields: append "Contact ID" to original field label
if ($isCustom) {
$entity['fields'][$index]['label'] .= ' ' . E::ts('Contact ID');
}
}
// Add the label field from the other entity to this entity's list of fields
$newField = \CRM_Utils_Array::findAll($schema[$field['fk_entity']]['fields'], ['name' => $schema[$field['fk_entity']]['label_field']])[0];
- // Due to string manipulation in \Civi\Api4\Service\Schema\SchemaMapBuilder::addJoins()
- $alias = $isCustom ? $field['name'] : str_replace('_id', '', $field['name']);
- $newField['name'] = $alias . '.' . $schema[$field['fk_entity']]['label_field'];
+ $newField['name'] = $field['name'] . '.' . $schema[$field['fk_entity']]['label_field'];
$newField['label'] = $field['label'] . ' ' . $newField['label'];
array_splice($entity['fields'], $index, 0, [$newField]);
}
_.each(params.select, function(fieldName) {
if (_.includes(fieldName, '.') && !_.includes(fieldName, ' AS ')) {
var info = searchMeta.parseExpr(fieldName);
- if (info.field && !info.suffix && !info.fn && (info.field.entity !== info.field.baseEntity)) {
- var idField = fieldName.substr(0, fieldName.lastIndexOf('.')) + '_id';
+ if (info.field && !info.suffix && !info.fn && (info.field.name !== info.field.fieldName)) {
+ var idField = fieldName.substr(0, fieldName.lastIndexOf('.'));
if (!_.includes(params.select, idField) && !ctrl.canAggregate(idField)) {
params.select.push(idField);
}
return value;
}
// Output user-facing name/label fields as a link, if possible
- if (info.field && _.last(info.field.name.split('.')) === searchMeta.getEntity(info.field.entity).label_field && !info.fn && typeof value === 'string') {
+ if (info.field && info.field.fieldName === searchMeta.getEntity(info.field.entity).label_field && !info.fn && typeof value === 'string') {
var link = getEntityUrl(row, info);
if (link) {
return '<a href="' + _.escape(link.url) + '" title="' + _.escape(link.title) + '">' + formatFieldValue(info.field, value) + '</a>';
// Replace tokens in the path (e.g. [id])
var tokens = path.match(/\[\w*]/g) || [],
prefix = info.prefix;
- // For implicit join fields
- if (info.field.name.split('.').length > 1) {
- prefix += info.field.name.split('.')[0] + '_';
- }
var replacements = _.transform(tokens, function(replacements, token) {
- var fieldName = prefix + token.slice(1, token.length - 1);
+ var fieldName = token.slice(1, token.length - 1);
+ // For implicit join fields
+ if (fieldName === 'id' && info.field.name !== info.field.fieldName) {
+ fieldName = info.field.name.substr(0, info.field.name.lastIndexOf('.'));
+ }
+ fieldName = prefix + fieldName;
if (row[fieldName]) {
replacements.push(row[fieldName]);
}
},
};
+ // Drag-n-drop settings for reordering columns
this.sortableOptions = {
connectWith: '.crm-search-admin-edit-columns',
- containment: '.crm-search-admin-edit-columns-wrapper'
+ containment: '.crm-search-admin-edit-columns-wrapper',
+ cancel: 'input,textarea,button,select,option,a,label'
};
this.styles = CRM.crmSearchAdmin.styles;
var info = searchMeta.parseExpr(col.key),
value = col.key.split(':')[0];
// If field is an implicit join, use the original fk field
- if (info.field.entity !== info.field.baseEntity) {
- value = value.substr(0, value.indexOf('.')) + '_id';
+ if (info.field.name !== info.field.fieldName) {
+ value = value.substr(0, value.lastIndexOf('.'));
info = searchMeta.parseExpr(value);
}
col.editable = {
_.each(ctrl.savedSearch.api_params.select, function(fieldName) {
if (!_.includes(fieldName, ' AS ')) {
var info = searchMeta.parseExpr(fieldName);
- if (info.field && !info.suffix && !info.fn && (info.field.fk_entity || info.field.entity !== info.field.baseEntity)) {
- var idField = info.field.fk_entity ? fieldName : fieldName.substr(0, fieldName.lastIndexOf('.')) + '_id';
+ if (info.field && !info.suffix && !info.fn && (info.field.fk_entity || info.field.name !== info.field.fieldName)) {
+ var idField = info.field.fk_entity ? fieldName : fieldName.substr(0, fieldName.lastIndexOf('.'));
if (!ctrl.crmSearchAdmin.canAggregate(idField)) {
var joinEntity = searchMeta.getEntity(info.field.fk_entity || info.field.entity);
_.each((joinEntity || {}).paths, function(path) {
}
}
});
- return links;
+ return _.uniq(links, 'path');
}
this.pickIcon = function(model, key) {