diff --git a/ext/search_kit/Civi/Api4/Action/SearchDisplay/Run.php b/ext/search_kit/Civi/Api4/Action/SearchDisplay/Run.php
index a3cecdafac57..7ecfa7e2b0f4 100644
--- a/ext/search_kit/Civi/Api4/Action/SearchDisplay/Run.php
+++ b/ext/search_kit/Civi/Api4/Action/SearchDisplay/Run.php
@@ -281,27 +281,6 @@ private function getSelectAliases() {
return $result;
}
- /**
- * Determines if a column is eligible to use an aggregate function
- * @param string $fieldName
- * @param string $prefix
- * @return bool
- */
- private function canAggregate($fieldName, $prefix = '') {
- $apiParams = $this->savedSearch['api_params'];
-
- // If the query does not use grouping, never
- if (empty($apiParams['groupBy'])) {
- return FALSE;
- }
- // If the column is used for a groupBy, no
- if (in_array($prefix . $fieldName, $apiParams['groupBy'])) {
- return FALSE;
- }
- // If the entity this column belongs to is being grouped by id, then also no
- return !in_array($prefix . 'id', $apiParams['groupBy']);
- }
-
/**
* Returns field definition for a given field or NULL if not found
* @param $fieldName
@@ -376,70 +355,28 @@ private function loadAfform() {
}
/**
- * Adds additional useful fields to the select clause
+ * Adds additional fields to the select clause required to render the display
*
* @param array $apiParams
*/
private function augmentSelectClause(&$apiParams): void {
- foreach ($this->getExtraEntityFields($this->savedSearch['api_entity']) as $extraFieldName) {
- if (!in_array($extraFieldName, $apiParams['select']) && !$this->canAggregate($extraFieldName)) {
- $apiParams['select'][] = $extraFieldName;
- }
- }
- $joinAliases = [];
- // Select the ids, etc. of explicitly joined entities (helps with displaying links)
- foreach ($apiParams['join'] ?? [] as $join) {
- [$joinEntity, $joinAlias] = explode(' AS ', $join[0]);
- $joinAliases[] = $joinAlias;
- foreach ($this->getExtraEntityFields($joinEntity) as $extraField) {
- $extraFieldName = $joinAlias . '.' . $extraField;
- if (!in_array($extraFieldName, $apiParams['select']) && !$this->canAggregate($extraField, $joinAlias . '.')) {
- $apiParams['select'][] = $extraFieldName;
- }
- }
- }
- // 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
+ $possibleTokens = '';
foreach ($this->display['settings']['columns'] ?? [] as $column) {
- if (isset($column['editable']['value']) && !in_array($column['editable']['value'], $apiParams['select'])) {
- $apiParams['select'][] = $column['editable']['value'];
+ // Collect display values in which a token is allowed
+ $possibleTokens .= ($column['rewrite'] ?? '') . ($column['link']['path'] ?? '');
+ if (!empty($column['links'])) {
+ $possibleTokens .= implode('', array_column($column['links'], 'path'));
}
- }
- }
- /**
- * Get list of extra fields needed for displaying links for a given entity
- *
- * @param string $entityName
- * @return array
- */
- private function getExtraEntityFields(string $entityName): array {
- if (!isset($this->_extraEntityFields[$entityName])) {
- $id = CoreUtil::getInfoItem($entityName, 'primary_key');
- $this->_extraEntityFields[$entityName] = $id;
- foreach (CoreUtil::getInfoItem($entityName, 'paths') ?? [] as $path) {
- $matches = [];
- preg_match_all('#\[(\w+)]#', $path, $matches);
- $this->_extraEntityFields[$entityName] = array_unique(array_merge($this->_extraEntityFields[$entityName], $matches[1] ?? []));
+ // Select value fields for in-place editing
+ if (isset($column['editable']['value']) && !in_array($column['editable']['value'], $apiParams['select'])) {
+ $apiParams['select'][] = $column['editable']['value'];
}
}
- return $this->_extraEntityFields[$entityName];
+ // Add fields referenced via token
+ $tokens = [];
+ preg_match_all('/\\[([^]]+)\\]/', $possibleTokens, $tokens);
+ $apiParams['select'] = array_unique(array_merge($apiParams['select'], $tokens[1]));
}
}
diff --git a/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminLinkSelect.html b/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminLinkSelect.html
index 9cd67a5a6c33..8fcdfbb73ed5 100644
--- a/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminLinkSelect.html
+++ b/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminLinkSelect.html
@@ -17,4 +17,4 @@
-
+
diff --git a/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminTokenSelect.component.js b/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminTokenSelect.component.js
index 5ee4b2dd5e32..bdc96368c06c 100644
--- a/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminTokenSelect.component.js
+++ b/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminTokenSelect.component.js
@@ -3,47 +3,34 @@
angular.module('crmSearchAdmin').component('crmSearchAdminTokenSelect', {
bindings: {
- apiEntity: '<',
- apiParams: '<',
model: '<',
- field: '@'
+ field: '@',
+ suffix: '@'
+ },
+ require: {
+ admin: '^crmSearchAdmin'
},
templateUrl: '~/crmSearchAdmin/crmSearchAdminTokenSelect.html',
controller: function ($scope, $element, searchMeta) {
var ts = $scope.ts = CRM.ts('org.civicrm.search_kit'),
ctrl = this;
- this.initTokens = function() {
- ctrl.tokens = ctrl.tokens || getTokens();
- };
-
this.insertToken = function(key) {
- ctrl.model[ctrl.field] = (ctrl.model[ctrl.field] || '') + ctrl.tokens[key].token;
+ ctrl.model[ctrl.field] = (ctrl.model[ctrl.field] || '') + '[' + key + ']';
};
- function getTokens() {
- var tokens = {
- id: {
- token: '[id]',
- label: searchMeta.getField('id', ctrl.apiEntity).label
- }
- };
- _.each(ctrl.apiParams.join, function(joinParams) {
- var info = searchMeta.parseExpr(joinParams[0].split(' AS ')[1] + '.id');
- tokens[info.alias] = {
- token: '[' + info.alias + ']',
- label: info.field ? info.field.label : info.alias
- };
+ this.getTokens = function() {
+ var allFields = ctrl.admin.getAllFields(ctrl.suffix || '', ['Field', 'Custom', 'Extra']);
+ _.eachRight(ctrl.admin.savedSearch.api_params.select, function(fieldName) {
+ allFields.unshift({
+ id: fieldName,
+ text: searchMeta.getDefaultLabel(fieldName)
+ });
});
- _.each(ctrl.apiParams.select, function(expr) {
- var info = searchMeta.parseExpr(expr);
- tokens[info.alias] = {
- token: '[' + info.alias + ']',
- label: info.field ? info.field.label : info.alias
- };
- });
- return tokens;
- }
+ return {
+ results: allFields
+ };
+ };
}
});
diff --git a/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminTokenSelect.html b/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminTokenSelect.html
index d66fcf8d4d5e..09a3ebbdaa13 100644
--- a/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminTokenSelect.html
+++ b/ext/search_kit/ang/crmSearchAdmin/crmSearchAdminTokenSelect.html
@@ -1,10 +1,4 @@
-
-
-
-
+
diff --git a/ext/search_kit/ang/crmSearchAdmin/displays/colType/field.html b/ext/search_kit/ang/crmSearchAdmin/displays/colType/field.html
index 08f16d139f47..8b0cbff69ec8 100644
--- a/ext/search_kit/ang/crmSearchAdmin/displays/colType/field.html
+++ b/ext/search_kit/ang/crmSearchAdmin/displays/colType/field.html
@@ -17,7 +17,7 @@
{{:: ts('Tooltip') }}
-
+
-
+
-
+
diff --git a/ext/search_kit/tests/phpunit/api/v4/SearchDisplay/SearchRunTest.php b/ext/search_kit/tests/phpunit/api/v4/SearchDisplay/SearchRunTest.php
index be4585365db5..bf0364a3ccd8 100644
--- a/ext/search_kit/tests/phpunit/api/v4/SearchDisplay/SearchRunTest.php
+++ b/ext/search_kit/tests/phpunit/api/v4/SearchDisplay/SearchRunTest.php
@@ -27,7 +27,7 @@ public function setUpHeadless() {
/**
* Test running a searchDisplay with various filters.
*/
- public function testRunDisplay() {
+ public function testRunWithFilters() {
foreach (['Tester', 'Bot'] as $type) {
ContactType::create(FALSE)
->addValue('parent_id.name', 'Individual')
@@ -122,6 +122,84 @@ public function testRunDisplay() {
$this->assertCount(2, $result);
}
+ /**
+ * Test return values are augmented by tokens.
+ */
+ public function testWithTokens() {
+ $lastName = uniqid(__FUNCTION__);
+ $sampleData = [
+ ['first_name' => 'One', 'last_name' => $lastName, 'source' => 'Unit test'],
+ ['first_name' => 'Two', 'last_name' => $lastName, 'source' => 'Unit test'],
+ ];
+ Contact::save(FALSE)->setRecords($sampleData)->execute();
+
+ $params = [
+ 'checkPermissions' => FALSE,
+ 'return' => 'page:1',
+ 'savedSearch' => [
+ 'api_entity' => 'Contact',
+ 'api_params' => [
+ 'version' => 4,
+ 'select' => ['id', 'display_name'],
+ 'where' => [['last_name', '=', $lastName]],
+ ],
+ ],
+ 'display' => [
+ 'type' => 'table',
+ 'label' => '',
+ 'settings' => [
+ 'limit' => 20,
+ 'pager' => TRUE,
+ 'columns' => [
+ [
+ 'key' => 'id',
+ 'label' => 'Contact ID',
+ 'dataType' => 'Integer',
+ 'type' => 'field',
+ ],
+ [
+ 'key' => 'display_name',
+ 'label' => 'Display Name',
+ 'dataType' => 'String',
+ 'type' => 'field',
+ 'link' => [
+ 'path' => 'civicrm/test/token-[sort_name]',
+ ],
+ ],
+ ],
+ 'sort' => [
+ ['id', 'ASC'],
+ ],
+ ],
+ ],
+ ];
+
+ $result = civicrm_api4('SearchDisplay', 'run', $params);
+ $this->assertCount(2, $result);
+ $this->assertNotEmpty($result->first()['display_name']);
+ // Assert that display name was added to the search due to the link token
+ $this->assertNotEmpty($result->first()['sort_name']);
+
+ // These items are not part of the search, but will be added via links
+ $this->assertArrayNotHasKey('contact_type', $result->first());
+ $this->assertArrayNotHasKey('source', $result->first());
+ $this->assertArrayNotHasKey('last_name', $result->first());
+
+ // Add links
+ $params['display']['settings']['columns'][] = [
+ 'type' => 'links',
+ 'label' => 'Links',
+ 'links' => [
+ ['path' => 'civicrm/test-[source]-[contact_type]'],
+ ['path' => 'civicrm/test-[last_name]'],
+ ],
+ ];
+ $result = civicrm_api4('SearchDisplay', 'run', $params);
+ $this->assertEquals('Individual', $result->first()['contact_type']);
+ $this->assertEquals('Unit test', $result->first()['source']);
+ $this->assertEquals($lastName, $result->first()['last_name']);
+ }
+
/**
* Test running a searchDisplay as a restricted user.
*/