3 namespace Civi\Api4\Action\SearchDisplay
;
5 use Civi\Search\Display
;
6 use CRM_Search_ExtensionUtil
as E
;
7 use Civi\Api4\Query\SqlEquation
;
8 use Civi\Api4\Query\SqlExpression
;
9 use Civi\Api4\Query\SqlField
;
10 use Civi\Api4\Query\SqlFunction
;
11 use Civi\Api4\Query\SqlFunctionGROUP_CONCAT
;
12 use Civi\Api4\Utils\CoreUtil
;
13 use Civi\API\Exception\UnauthorizedException
;
16 * Return the default results table for a saved search.
18 * @package Civi\Api4\Action\SearchDisplay
20 class GetDefault
extends \Civi\Api4\Generic\AbstractAction
{
22 use SavedSearchInspectorTrait
;
23 use \Civi\Api4\Generic\Traits\ArrayQueryActionTrait
;
24 use \Civi\Api4\Generic\Traits\SelectParamTrait
;
27 * Either the name of the savedSearch or an array containing the savedSearch definition (for preview mode)
28 * @var string|array|null
30 protected $savedSearch;
38 * @param \Civi\Api4\Generic\Result $result
39 * @throws UnauthorizedException
40 * @throws \API_Exception
42 public function _run(\Civi\Api4\Generic\Result
$result) {
43 // Only administrators can use this in unsecured "preview mode"
44 if (is_array($this->savedSearch
) && $this->checkPermissions
&& !\CRM_Core_Permission
::check('administer CiviCRM data')) {
45 throw new UnauthorizedException('Access denied');
47 $this->loadSavedSearch();
48 $this->expandSelectClauseWildcards();
49 // Use label from saved search
50 $label = $this->savedSearch
['label'] ??
'';
51 // Fall back on entity title as label
52 if (!strlen($label) && !empty($this->savedSearch
['api_entity'])) {
53 $label = CoreUtil
::getInfoItem($this->savedSearch
['api_entity'], 'title_plural');
58 'saved_search_id' => $this->savedSearch
['id'] ??
NULL,
61 'type:label' => E
::ts('Table'),
62 'type:name' => 'crm-search-display-table',
63 'type:icon' => 'fa-table',
64 'acl_bypass' => FALSE,
67 'limit' => \Civi
::settings()->get('default_pager_size'),
68 'classes' => ['table', 'table-striped'],
71 'expose_limit' => TRUE,
76 // Allow implicit-join-style selection of saved search fields
77 foreach ($this->savedSearch
as $key => $val) {
78 $display['saved_search_id.' . $key] = $val;
80 foreach ($this->getSelectClause() as $key => $clause) {
81 $display['settings']['columns'][] = $this->configureColumn($clause, $key);
83 $display['settings']['columns'][] = [
88 'style' => 'secondary-outline',
89 'alignment' => 'text-right',
90 'links' => $this->getLinksMenu(),
92 $result->exchangeArray($this->selectArray([$display]));
96 * @param array{fields: array, expr: SqlExpression, dataType: string} $clause
100 private function configureColumn($clause, $key) {
104 'sortable' => !empty($clause['fields']),
105 'label' => $this->getColumnLabel($clause['expr']),
107 $this->getColumnLink($col, $clause);
112 * @param \Civi\Api4\Query\SqlExpression $expr
115 private function getColumnLabel(SqlExpression
$expr) {
116 if ($expr instanceof SqlFunction
) {
118 foreach ($expr->getArgs() as $arg) {
119 foreach ($arg['expr'] ??
[] as $ex) {
120 $args[] = $this->getColumnLabel($ex);
123 return '(' . $expr->getTitle() . ')' . ($args ?
' ' . implode(',', array_filter($args)) : '');
125 if ($expr instanceof SqlEquation
) {
127 foreach ($expr->getArgs() as $arg) {
128 $args[] = $this->getColumnLabel($arg['expr']);
130 return '(' . implode(',', array_filter($args)) . ')';
132 elseif ($expr instanceof SqlField
) {
133 $field = $this->getField($expr->getExpr());
135 if (!empty($field['explicit_join'])) {
136 $label = $this->getJoinLabel($field['explicit_join']) . ': ';
138 if (!empty($field['implicit_join'])) {
139 $field = $this->getField(substr($expr->getAlias(), 0, -1 - strlen($field['name'])));
141 return $label . $field['label'];
149 * @param string $joinAlias
152 private function getJoinLabel($joinAlias) {
153 if (!isset($this->_joinMap
)) {
154 $this->_joinMap
= [];
155 $joinCount = [$this->savedSearch
['api_entity'] => 1];
156 foreach ($this->savedSearch
['api_params']['join'] ??
[] as $join) {
157 [$entityName, $alias] = explode(' AS ', $join[0]);
159 if (!empty($joinCount[$entityName])) {
160 $num = ' ' . (++
$joinCount[$entityName]);
163 $joinCount[$entityName] = 1;
165 $label = CoreUtil
::getInfoItem($entityName, 'title');
166 $this->_joinMap
[$alias] = $label . $num;
169 return $this->_joinMap
[$joinAlias];
174 * @param array{fields: array, expr: SqlExpression, dataType: string} $clause
176 private function getColumnLink(&$col, $clause) {
177 if ($clause['expr'] instanceof SqlField ||
$clause['expr'] instanceof SqlFunctionGROUP_CONCAT
) {
178 $field = $clause['fields'][0] ??
NULL;
180 CoreUtil
::getInfoItem($field['entity'], 'label_field') === $field['name'] &&
181 !empty(CoreUtil
::getInfoItem($field['entity'], 'paths')['view'])
184 'entity' => $field['entity'],
185 'join' => implode('.', array_filter([$field['explicit_join'], $field['implicit_join']])),
188 // Hack to support links to relationships
189 if ($col['link']['entity'] === 'RelationshipCache') {
190 $col['link']['entity'] = 'Relationship';
192 $col['title'] = E
::ts('View %1', [1 => CoreUtil
::getInfoItem($field['entity'], 'title')]);
200 private function getLinksMenu() {
202 $mainEntity = $this->savedSearch
['api_entity'] ??
NULL;
203 if ($mainEntity && !$this->canAggregate(CoreUtil
::getIdFieldName($mainEntity))) {
204 foreach (CoreUtil
::getInfoItem($mainEntity, 'paths') as $action => $path) {
205 $link = $this->formatMenuLink($mainEntity, $action);
211 $keys = ['entity' => TRUE, 'bridge' => TRUE];
212 foreach ($this->getJoins() as $join) {
213 if (!$this->canAggregate($join['alias'] . '.' . CoreUtil
::getIdFieldName($join['entity']))) {
214 foreach (array_filter(array_intersect_key($join, $keys)) as $joinEntity) {
215 foreach (CoreUtil
::getInfoItem($joinEntity, 'paths') as $action => $path) {
216 $link = $this->formatMenuLink($joinEntity, $action, $join['alias']);
228 * @param string $entity
229 * @param string $action
230 * @param string $joinAlias
233 private function formatMenuLink(string $entity, string $action, string $joinAlias = NULL) {
234 if ($joinAlias && $entity === $this->getJoin($joinAlias)['entity']) {
235 $entityLabel = $this->getJoinLabel($joinAlias);
240 $link = Display
::getEntityLinks($entity, $entityLabel)[$action] ??
NULL;
241 return $link ?
$link +
['join' => $joinAlias] : NULL;