Automatically create links for any implicitly joined entity linked through an fk field
$apiParams['offset'] = $page ? $apiParams['limit'] * ($page - 1) : 0;
$apiParams['orderBy'] = $this->getOrderByFromSort();
- // Select the ids of joined entities (helps with displaying links)
+ // 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';
->setChain([
'get' => ['$name', 'getActions', ['where' => [['name', '=', 'get']]], ['params']],
])->execute();
- $getFields = ['name', 'title', 'label', 'description', 'options', 'input_type', 'input_attrs', 'data_type', 'serialize', 'fk_entity'];
+ $getFields = ['name', 'title', 'label', 'description', 'options', 'input_type', 'input_attrs', 'data_type', 'serialize', 'entity', 'fk_entity'];
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']) {
if (dotSplit.length === 2) {
field = _.find(getEntity(entityName).fields, {name: dotSplit[0] + '.' + name});
if (field) {
- field.entity = entityName;
+ field.baseEntity = entityName;
return {field: field};
}
}
field = _.find(getEntity(join.bridge).fields, {name: name});
}
if (field) {
- field.entity = entityName;
+ field.baseEntity = entityName;
return {field: field, join: join};
}
}
function _loadResultsCallback() {
// Multiply limit to read 2 pages at once & save ajax requests
var params = _.merge(_.cloneDeep(ctrl.savedSearch.api_params), {debug: true, limit: ctrl.limit * 2});
- // Select the ids of joined entities (helps with displaying links)
+ // Select the ids of implicitly joined entities (helps with displaying links)
+ _.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 (!_.includes(params.select, idField) && !ctrl.canAggregate(idField)) {
+ params.select.push(idField);
+ }
+ }
+ }
+ });
+ // Select the ids of explicitly joined entities (helps with displaying links)
_.each(params.join, function(join) {
var idField = join[0].split(' AS ')[1] + '.id';
if (!_.includes(params.select, idField) && !ctrl.canAggregate(idField)) {
return value;
}
// Output user-facing name/label fields as a link, if possible
- if (info.field && info.field.name === searchMeta.getEntity(info.field.entity).label_field && !info.fn && typeof value === 'string') {
+ if (info.field && _.last(info.field.name.split('.')) === 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>';
if (path) {
// Replace tokens in the path (e.g. [id])
var tokens = path.match(/\[\w*]/g) || [],
- replacements = _.transform(tokens, function(replacements, token) {
- var fieldName = info.prefix + token.slice(1, token.length - 1);
- if (row[fieldName]) {
- replacements.push(row[fieldName]);
- }
- });
+ 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);
+ if (row[fieldName]) {
+ replacements.push(row[fieldName]);
+ }
+ });
// Only proceed if the row contains all the necessary data to resolve tokens
if (tokens.length === replacements.length) {
_.each(tokens, function(token, index) {
apiEntity: '<',
apiParams: '<'
},
+ require: {
+ crmSearchAdmin: '^^crmSearchAdmin'
+ },
templateUrl: '~/crmSearchAdmin/crmSearchAdminLinkSelect.html',
controller: function ($scope, $element, $timeout, searchMeta) {
var ts = $scope.ts = CRM.ts(),
// Return all possible links to main entity or join entities
function getLinks() {
+ // Links to main entity
var links = _.cloneDeep(searchMeta.getEntity(ctrl.apiEntity).paths || []);
+ // Links to explicitly joined entities
_.each(ctrl.apiParams.join, function(join) {
var joinName = join[0].split(' AS '),
joinEntity = searchMeta.getEntity(joinName[0]);
links.push(link);
});
});
+ // Links to implicit joins
+ _.each(ctrl.crmSearchAdmin.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 (!ctrl.crmSearchAdmin.canAggregate(idField)) {
+ var joinEntity = searchMeta.getEntity(info.field.fk_entity || info.field.entity);
+ _.each(joinEntity.paths, function(path) {
+ var link = _.cloneDeep(path);
+ link.path = link.path.replace(/\[id/g, '[' + idField);
+ links.push(link);
+ });
+ }
+ }
+ }
+ });
return links;
}