$select[$expr->getAlias()] = $item;
}
$formatted = [];
- foreach ($result as $data) {
+ foreach ($result as $index => $data) {
$row = [];
foreach ($select as $key => $item) {
- $raw = $data[$key] ?? NULL;
- $row[$key] = [
- 'raw' => $raw,
- 'view' => $this->formatViewValue($item['dataType'], $raw),
- ];
+ $row[$key] = $this->getValue($key, $data, $item['dataType'], $index);
}
$formatted[] = $row;
}
return $formatted;
}
+ /**
+ * @param $key
+ * @param $data
+ * @param $dataType
+ * @param $index
+ * @return array
+ */
+ private function getValue($key, $data, $dataType, $index) {
+ // Get value from api result unless this is a pseudo-field which gets a calculated value
+ switch ($key) {
+ case 'result_row_num':
+ $raw = $index + 1 + ($this->savedSearch['api_params']['offset'] ?? 0);
+ break;
+
+ case 'user_contact_id':
+ $raw = \CRM_Core_Session::getLoggedInContactID();
+ break;
+
+ default:
+ $raw = $data[$key] ?? NULL;
+ }
+ return [
+ 'raw' => $raw,
+ 'view' => $this->formatViewValue($dataType, $raw),
+ ];
+ }
+
/**
* Returns field definition for a given field or NULL if not found
* @param $fieldName
return $this->_afform;
}
+ /**
+ * Extra calculated fields provided by SearchKit
+ * @return array[]
+ */
+ public static function getPseudoFields(): array {
+ return [
+ [
+ 'name' => 'result_row_num',
+ 'fieldName' => 'result_row_num',
+ 'title' => ts('Row Number'),
+ 'label' => ts('Row Number'),
+ 'description' => ts('Index of each row, starting from 1 on the first page'),
+ 'type' => 'Pseudo',
+ 'data_type' => 'Integer',
+ 'readonly' => TRUE,
+ ],
+ [
+ 'name' => 'user_contact_id',
+ 'fieldName' => 'result_row_num',
+ 'title' => ts('Current User ID'),
+ 'label' => ts('Current User ID'),
+ 'description' => ts('Contact ID of the current user if logged in'),
+ 'type' => 'Pseudo',
+ 'data_type' => 'Integer',
+ 'readonly' => TRUE,
+ ],
+ ];
+ }
+
}
namespace Civi\Search;
+use Civi\Api4\Action\SearchDisplay\AbstractRunAction;
use Civi\Api4\Tag;
use CRM_Search_ExtensionUtil as E;
return [
'schema' => self::addImplicitFKFields($schema),
'joins' => self::getJoins($schema),
+ 'pseudoFields' => AbstractRunAction::getPseudoFields(),
'operators' => \CRM_Utils_Array::makeNonAssociative(self::getOperators()),
'functions' => \CRM_Api4_Page_Api4Explorer::getSqlFunctions(),
'displayTypes' => Display::getDisplayTypes(['id', 'name', 'label', 'description', 'icon']),
if (!field && join && join.bridge) {
field = _.find(getEntity(join.bridge).fields, {name: name});
}
+ // Might be a pseudoField
+ if (!field) {
+ field = _.cloneDeep(_.find(CRM.crmSearchAdmin.pseudoFields, {name: name}));
+ }
if (field) {
field.baseEntity = entityName;
return {field: field, join: join};
{{:: ts('Field Transformations') }}
</legend>
<div ng-if="!!controls.showFunctions">
- <fieldset ng-repeat="col in $ctrl.savedSearch.api_params.select">
+ <fieldset ng-repeat="col in $ctrl.savedSearch.api_params.select" ng-if="!$ctrl.isPseudoField(col)">
<crm-search-function expr="$ctrl.savedSearch.api_params.select[$index]"></crm-search-function>
</fieldset>
</div>
savedSearch: '<'
},
templateUrl: '~/crmSearchAdmin/crmSearchAdmin.html',
- controller: function($scope, $element, $location, $timeout, crmApi4, dialogService, searchMeta, formatForSelect2) {
+ controller: function($scope, $element, $location, $timeout, crmApi4, dialogService, searchMeta) {
var ts = $scope.ts = CRM.ts('org.civicrm.search_kit'),
ctrl = this,
fieldsForJoinGetters = {};
this.getAllFields = function(suffix, allowedTypes, disabledIf, topJoin) {
disabledIf = disabledIf || _.noop;
- function formatFields(entityName, join) {
+
+ function formatEntityFields(entityName, join) {
var prefix = join ? join.alias + '.' : '',
result = [];
- function addFields(fields) {
- _.each(fields, function(field) {
- var item = {
- id: prefix + field.name + (field.options ? suffix : ''),
- text: field.label,
- description: field.description
- };
- if (disabledIf(item.id)) {
- item.disabled = true;
- }
- if (!allowedTypes || _.includes(allowedTypes, field.type)) {
- result.push(item);
- }
- });
- }
-
// Add extra searchable fields from bridge entity
if (join && join.bridge) {
- addFields(_.filter(searchMeta.getEntity(join.bridge).fields, function(field) {
+ formatFields(_.filter(searchMeta.getEntity(join.bridge).fields, function(field) {
return (field.name !== 'id' && field.name !== 'entity_id' && field.name !== 'entity_table' && !field.fk_entity);
- }));
+ }), result, prefix);
}
- addFields(searchMeta.getEntity(entityName).fields);
+ formatFields(searchMeta.getEntity(entityName).fields, result, prefix);
+ return result;
+ }
+
+ function formatFields(fields, result, prefix) {
+ prefix = typeof prefix === 'undefined' ? '' : prefix;
+ _.each(fields, function(field) {
+ var item = {
+ id: prefix + field.name + (field.options ? suffix : ''),
+ text: field.label,
+ description: field.description
+ };
+ if (disabledIf(item.id)) {
+ item.disabled = true;
+ }
+ if (!allowedTypes || _.includes(allowedTypes, field.type)) {
+ result.push(item);
+ }
+ });
return result;
}
text: joinInfo.label,
description: joinInfo.description,
icon: joinEntity.icon,
- children: formatFields(joinEntity.name, joinInfo)
+ children: formatEntityFields(joinEntity.name, joinInfo)
});
}
result.push({
text: mainEntity.title_plural,
icon: mainEntity.icon,
- children: formatFields(ctrl.savedSearch.api_entity)
+ children: formatEntityFields(ctrl.savedSearch.api_entity)
});
+
+ // Include SearchKit's pseudo-fields if specifically requested
+ if (allowedTypes && _.includes(allowedTypes, 'Pseudo')) {
+ result.push({
+ text: ts('Extra'),
+ icon: 'fa-gear',
+ children: formatFields(CRM.crmSearchAdmin.pseudoFields, [])
+ });
+ }
+
_.each(joinEntities, addJoin);
return result;
};
});
};
+ this.isPseudoField = function(name) {
+ return _.findIndex(CRM.crmSearchAdmin.pseudoFields, {name: name}) >= 0;
+ };
+
/**
* Fetch pseudoconstants for main entity + joined entities
*
_.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.name !== info.field.fieldName)) {
+ if (info.field && !info.suffix && !info.fn && info.field.type === 'Field' && (info.field.fk_entity || info.field.name !== info.field.fieldName)) {
var idFieldName = info.field.fk_entity ? fieldName : fieldName.substr(0, fieldName.lastIndexOf('.')),
idField = searchMeta.parseExpr(idFieldName).field;
if (!ctrl.canAggregate(idFieldName)) {
};
this.getTokens = function() {
- var allFields = ctrl.admin.getAllFields(ctrl.suffix || '', ['Field', 'Custom', 'Extra']);
+ var allFields = ctrl.admin.getAllFields(ctrl.suffix || '', ['Field', 'Custom', 'Extra', 'Pseudo']);
_.eachRight(ctrl.admin.savedSearch.api_params.select, function(fieldName) {
allFields.unshift({
id: fieldName,
};
$scope.fieldsForSelect = function() {
- return {results: ctrl.crmSearchAdmin.getAllFields(':label', ['Field', 'Custom', 'Extra'], function(key) {
+ return {results: ctrl.crmSearchAdmin.getAllFields(':label', ['Field', 'Custom', 'Extra', 'Pseudo'], function(key) {
return _.contains(ctrl.search.api_params.select, key);
})
};
</span>
</th>
<th class="form-inline text-right">
- <input class="form-control crm-action-menu fa-plus"
+ <input class="form-control crm-action-menu fa-plus collapsible-optgroups"
crm-ui-select="::{data: fieldsForSelect, placeholder: ts('Add'), width: '80px', containerCss: {minWidth: '80px'}, dropdownCss: {width: '300px'}}"
on-crm-ui-select="addColumn(selection)" >
</th>