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. */