public static function getSchema() {
$schema = [];
$entities = \Civi\Api4\Entity::get()
- ->addSelect('name', 'title', 'title_plural', 'bridge_title', 'type', 'primary_key', 'description', 'label_field', 'icon', 'paths', 'dao', 'bridge', 'ui_join_filters', 'searchable')
+ ->addSelect('name', 'title', 'title_plural', 'bridge_title', 'type', 'primary_key', 'description', 'label_field', 'icon', 'dao', 'bridge', 'ui_join_filters', 'searchable')
->addWhere('searchable', '!=', 'none')
->addOrderBy('title_plural')
->setChain([
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']) {
- // Add paths (but only RUD actions) with translated titles
- foreach ($entity['paths'] as $action => $path) {
- unset($entity['paths'][$action]);
- if (in_array($action, ['view', 'update', 'delete'], TRUE)) {
- $entity['paths'][] = [
- 'path' => $path,
- 'action' => $action,
- ];
- }
+ // Add links with translatable titles
+ $links = Display::getEntityLinks($entity['name']);
+ if ($links) {
+ $entity['links'] = array_values($links);
}
$getFields = civicrm_api4($entity['name'], 'getFields', [
'select' => ['name', 'title', 'label', 'description', 'type', 'options', 'input_type', 'input_attrs', 'data_type', 'serialize', 'entity', 'fk_entity', 'readonly', 'operators'],
}
// Build a list of all possible links to main entity & join entities
+ // @return {Array}
this.buildLinks = function() {
function addTitle(link, entityName) {
- switch (link.action) {
- case 'view':
- link.title = ts('View %1', {1: entityName});
- link.icon = 'fa-external-link';
- link.style = 'default';
- break;
-
- case 'update':
- link.title = ts('Edit %1', {1: entityName});
- link.icon = 'fa-pencil';
- link.style = 'default';
- break;
-
- case 'delete':
- link.title = ts('Delete %1', {1: entityName});
- link.icon = 'fa-trash';
- link.style = 'danger';
- break;
- }
+ link.text = link.text.replace('%1', entityName);
}
// Links to main entity
- // @return {Array}
var mainEntity = searchMeta.getEntity(ctrl.savedSearch.api_entity),
- links = _.cloneDeep(mainEntity.paths || []);
+ links = _.cloneDeep(mainEntity.links || []);
_.each(links, function(link) {
link.join = '';
addTitle(link, mainEntity.title);
_.each(ctrl.savedSearch.api_params.join, function(joinClause) {
var join = searchMeta.getJoin(joinClause[0]),
joinEntity = searchMeta.getEntity(join.entity),
- primaryKey = joinEntity.primary_key[0],
- // Links for aggregate columns get aggregated using GROUP_CONCAT
- isAggregate = ctrl.canAggregate(join.alias + '.' + primaryKey),
- joinPrefix = (isAggregate ? ctrl.DEFAULT_AGGREGATE_FN + '_' : '') + join.alias + '.',
bridgeEntity = _.isString(joinClause[2]) ? searchMeta.getEntity(joinClause[2]) : null;
- _.each(joinEntity.paths, function(path) {
- var link = _.cloneDeep(path);
- link.path = link.path.replace(/\[/g, '[' + joinPrefix);
- if (isAggregate) {
- link.path = link.path.replace(/[.:]/g, '_');
- }
+ _.each(_.cloneDeep(joinEntity.links), function(link) {
link.join = join.alias;
addTitle(link, join.label);
links.push(link);
});
- _.each(bridgeEntity && bridgeEntity.paths, function(path) {
- var link = _.cloneDeep(path);
- link.path = link.path.replace(/\[/g, '[' + join.alias + '.');
+ _.each(_.cloneDeep(bridgeEntity && bridgeEntity.links), function(link) {
link.join = join.alias;
addTitle(link, join.label + (bridgeEntity.bridge_title ? ' ' + bridgeEntity.bridge_title : ''));
links.push(link);
if (!ctrl.canAggregate(idFieldName)) {
var joinEntity = searchMeta.getEntity(idField.fk_entity),
label = (idField.join ? idField.join.label + ': ' : '') + (idField.input_attrs && idField.input_attrs.label || idField.label);
- _.each((joinEntity || {}).paths, function(path) {
- var link = _.cloneDeep(path);
- link.path = link.path.replace(/\[id/g, '[' + idFieldName);
+ _.each(_.cloneDeep(joinEntity && joinEntity.links), function(link) {
link.join = idFieldName;
addTitle(link, label);
links.push(link);
}
}
});
- return _.uniq(links, 'path');
+ return links;
};
function loadAfforms() {
return !info.fn || info.fn.category !== 'aggregate' || info.fn.name === 'GROUP_CONCAT';
};
+ var linkProps = ['path', 'entity', 'action', 'join', 'target'];
+
this.toggleLink = function(column) {
if (column.link) {
- ctrl.onChangeLink(column, column.link.path, '');
+ ctrl.onChangeLink(column, {});
} else {
delete column.editable;
var defaultLink = ctrl.getLinks(column.key)[0];
- column.link = {path: defaultLink ? defaultLink.path : 'civicrm/'};
- ctrl.onChangeLink(column, null, column.link.path);
+ ctrl.onChangeLink(column, defaultLink || {path: 'civicrm/'});
}
};
- this.onChangeLink = function(column, before, after) {
- var beforeLink = before && _.findWhere(ctrl.getLinks(), {path: before}),
- afterLink = after && _.findWhere(ctrl.getLinks(), {path: after});
- if (!after) {
- if (beforeLink && column.title === beforeLink.title) {
+ this.onChangeLink = function(column, afterLink) {
+ column.link = column.link || {};
+ var beforeLink = column.link.action && _.findWhere(ctrl.getLinks(column.key), {action: column.link.action});
+ if (!afterLink.action && !afterLink.path) {
+ if (beforeLink && beforeLink.text === column.title) {
delete column.title;
}
delete column.link;
- } else if (afterLink && ((!column.title && !before) || (beforeLink && beforeLink.title === column.title))) {
- column.title = afterLink.title;
- } else if (!afterLink && (beforeLink && beforeLink.title === column.title)) {
+ return;
+ }
+ if (afterLink.text && ((!column.title && !beforeLink) || (beforeLink && beforeLink.text === column.title))) {
+ column.title = afterLink.text;
+ } else if (!afterLink.text && (beforeLink && beforeLink.text === column.title)) {
delete column.title;
}
+ _.each(linkProps, function(prop) {
+ column.link[prop] = afterLink[prop] || '';
+ });
};
this.getLinks = function(columnKey) {
templateUrl: '~/crmSearchAdmin/crmSearchAdminLinkGroup.html',
controller: function ($scope, $element, $timeout, searchMeta) {
var ts = $scope.ts = CRM.ts('org.civicrm.search_kit'),
- ctrl = this;
+ ctrl = this,
+ linkProps = ['path', 'entity', 'action', 'join', 'target', 'icon', 'text', 'style'];
this.styles = CRM.crmSearchAdmin.styles;
});
};
- this.addItem = function(path) {
- var link = ctrl.getLink(path);
- ctrl.group.push({
- path: path,
- style: link && link.style || 'default',
- text: link ? link.title : ts('Link'),
- icon: link && link.icon || 'fa-external-link'
- });
+ this.addItem = function(item) {
+ ctrl.group.push(_.pick(item, linkProps));
};
- this.onChangeLink = function(item, before, after) {
- var beforeLink = before && ctrl.getLink(before),
- beforeTitle = beforeLink ? beforeLink.title : ts('Link'),
- afterLink = after && ctrl.getLink(after);
- if (afterLink && (!item.text || beforeTitle === item.text)) {
- item.text = afterLink.title;
+ this.onChangeLink = function(item, newValue) {
+ if (newValue.path === 'civicrm/') {
+ newValue = JSON.parse(this.default);
}
+ _.each(linkProps, function(prop) {
+ item[prop] = newValue[prop] || '';
+ });
};
+ this.serialize = JSON.stringify;
+
this.$onInit = function() {
+ this.default = this.serialize({
+ style: 'default',
+ text: ts('Link'),
+ icon: 'fa-external-link',
+ path: 'civicrm/'
+ });
var defaultLinks = _.filter(ctrl.links, function(link) {
return !link.join;
});
if (!ctrl.group.length) {
if (defaultLinks.length) {
- _.each(_.pluck(defaultLinks, 'path'), ctrl.addItem);
+ _.each(defaultLinks, ctrl.addItem);
} else {
- ctrl.addItem('civicrm/');
+ ctrl.addItem(JSON.parse(this.default));
}
}
$element.on('change', 'select.crm-search-admin-add-link', function() {
var $select = $(this);
$scope.$apply(function() {
- ctrl.addItem($select.val());
+ ctrl.addItem(JSON.parse($select.val()));
$select.val('');
});
});
};
- this.getLink = function(path) {
- return _.findWhere(ctrl.links, {path: path});
- };
-
}
});
<crm-search-admin-token-select model="item" field="text" suffix=":label"></crm-search-admin-token-select>
</td>
<td class="form-inline">
- <crm-search-admin-link-select api-entity="$ctrl.apiEntity" api-params="$ctrl.apiParams" link="item" links="$ctrl.links" on-change="$ctrl.onChangeLink(item, before, after)"></crm-search-admin-link-select>
+ <crm-search-admin-link-select api-entity="$ctrl.apiEntity" api-params="$ctrl.apiParams" link="item" links="$ctrl.links" on-change="$ctrl.onChangeLink(item, newLink)"></crm-search-admin-link-select>
</td>
<td>
<div class="btn-group">
<option value="">
+ {{:: ts('Add...') }}
</option>
- <option ng-repeat="link in $ctrl.links" value="{{ link.path }}">
- {{ link.title }}
+ <option ng-repeat="link in $ctrl.links" value="{{:: $ctrl.serialize(link) }}">
+ {{ link.text }}
</option>
- <option value="civicrm/">
+ <option value="{{:: $ctrl.default }}">
{{:: ts('Other...') }}
</option>
</select>
ctrl = this;
this.setValue = function(val) {
- ctrl.link = ctrl.link || {};
- var link = ctrl.getLink(val),
- oldVal = ctrl.link.path;
- ctrl.link.path = val;
- if (!link) {
+ if (val.path) {
$timeout(function () {
$('input[type=text]', $element).focus();
});
}
- ctrl.onChange({before: oldVal, after: val});
+ ctrl.onChange({newLink: val});
};
- this.getLink = function(path) {
- return _.findWhere(ctrl.links, {path: path});
+ this.getLink = function() {
+ return _.findWhere(ctrl.links, {action: ctrl.link.action, join: ctrl.link.join, entity: ctrl.link.entity});
};
}
<div class="crm-flex-1 input-group" >
- <input type="text" class="form-control" ng-if="!$ctrl.getLink($ctrl.link.path)" ng-model="$ctrl.link.path" ng-model-options="{updateOn: 'blur'}" ng-change="$ctrl.onChange({before: 'civicrm/', after: $ctrl.link.path})">
- <div class="input-group-btn" style="{{ $ctrl.getLink($ctrl.link.path) ? '' : 'width:27px' }}">
- <button type="button" class="btn btn-sm btn-secondary-outline dropdown-toggle" style="min-width: 200px; text-align: left;" ng-if="$ctrl.getLink($ctrl.link.path)" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
- {{ $ctrl.getLink($ctrl.link.path).title }}
+ <input type="text" class="form-control" ng-if="!$ctrl.link.action" ng-model="$ctrl.link.path" ng-model-options="{updateOn: 'blur'}" ng-change="$ctrl.onChange({newLink: $ctrl.link})">
+ <div class="input-group-btn" style="{{ $ctrl.link.action ? '' : 'width:27px' }}">
+ <button type="button" class="btn btn-sm btn-secondary-outline dropdown-toggle" style="min-width: 200px; text-align: left;" ng-if="$ctrl.link.action" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+ {{ $ctrl.getLink().text }}
</button>
<button type="button" class="btn btn-sm btn-secondary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="caret"></span>
</button>
- <ul class="dropdown-menu {{ $ctrl.getLink($ctrl.link.path) ? '' : 'dropdown-menu-right' }}" style="min-width: 223px;">
- <li ng-repeat="link in $ctrl.links" ng-class="{disabled: $ctrl.link.path === link.path}">
- <a href ng-click="$ctrl.setValue(link.path)">{{:: link.title }}</a>
+ <ul class="dropdown-menu {{ $ctrl.link.action ? '' : 'dropdown-menu-right' }}" style="min-width: 223px;">
+ <li ng-repeat="link in $ctrl.links" ng-class="{disabled: link === $ctrl.getLink()}">
+ <a href ng-click="$ctrl.setValue(link)">{{:: link.text }}</a>
</li>
- <li ng-class="{disabled: !$ctrl.getLink($ctrl.link.path)}">
- <a href ng-click="$ctrl.setValue('civicrm/')">{{:: ts('Other...') }}</a>
+ <li ng-class="{disabled: !$ctrl.link.action}">
+ <a href ng-click="$ctrl.setValue({path: 'civicrm/'})">
+ {{:: ts('Other...') }}
+ </a>
</li>
</ul>
</div>
</div>
-<crm-search-admin-token-select ng-if="!$ctrl.getLink($ctrl.link.path)" model="$ctrl.link" field="path"></crm-search-admin-token-select>
+<crm-search-admin-token-select ng-if="!$ctrl.link.action" model="$ctrl.link" field="path"></crm-search-admin-token-select>
<option value="_blank">{{:: ts('New tab') }}</option>
<option value="crm-popup">{{:: ts('Popup dialog') }}</option>
</select>
- <crm-search-admin-link-select ng-if="col.link" link="col.link" on-change="$ctrl.parent.onChangeLink(col, before, after)" api-entity="$ctrl.apiEntity" api-params="$ctrl.apiParams" links=":: $ctrl.parent.getLinks(col.key)">
+ <crm-search-admin-link-select ng-if="col.link" link="col.link" on-change="$ctrl.parent.onChangeLink(col, newLink)" api-entity="$ctrl.apiEntity" api-params="$ctrl.apiParams" links=":: $ctrl.parent.getLinks(col.key)">
</crm-search-admin-link-select>
</div>
<div class="form-inline crm-search-admin-flex-row">