Commit | Line | Data |
---|---|---|
ea04af0c CW |
1 | <?php |
2 | ||
3 | namespace Civi\Api4\Action\SearchDisplay; | |
4 | ||
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; | |
14 | ||
15 | /** | |
16 | * Return the default results table for a saved search. | |
17 | * | |
18 | * @package Civi\Api4\Action\SearchDisplay | |
19 | */ | |
20 | class GetDefault extends \Civi\Api4\Generic\AbstractAction { | |
21 | ||
22 | use SavedSearchInspectorTrait; | |
5c952e51 CW |
23 | use \Civi\Api4\Generic\Traits\ArrayQueryActionTrait; |
24 | use \Civi\Api4\Generic\Traits\SelectParamTrait; | |
ea04af0c CW |
25 | |
26 | /** | |
27 | * Either the name of the savedSearch or an array containing the savedSearch definition (for preview mode) | |
28 | * @var string|array|null | |
29 | */ | |
30 | protected $savedSearch; | |
31 | ||
32 | /** | |
33 | * @var array | |
34 | */ | |
35 | private $_joinMap; | |
36 | ||
37 | /** | |
38 | * @param \Civi\Api4\Generic\Result $result | |
39 | * @throws UnauthorizedException | |
40 | * @throws \API_Exception | |
41 | */ | |
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'); | |
46 | } | |
47 | $this->loadSavedSearch(); | |
5c952e51 | 48 | $this->expandSelectClauseWildcards(); |
ea04af0c CW |
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'); | |
54 | } | |
55 | $display = [ | |
5c952e51 | 56 | 'id' => NULL, |
ea04af0c | 57 | 'name' => NULL, |
5c952e51 | 58 | 'saved_search_id' => $this->savedSearch['id'] ?? NULL, |
ea04af0c CW |
59 | 'label' => $label, |
60 | 'type' => 'table', | |
5c952e51 CW |
61 | 'type:label' => E::ts('Table'), |
62 | 'type:name' => 'crm-search-display-table', | |
63 | 'type:icon' => 'fa-table', | |
ea04af0c CW |
64 | 'acl_bypass' => FALSE, |
65 | 'settings' => [ | |
ea04af0c CW |
66 | 'actions' => TRUE, |
67 | 'limit' => \Civi::settings()->get('default_pager_size'), | |
68 | 'classes' => ['table', 'table-striped'], | |
69 | 'pager' => [ | |
70 | 'show_count' => TRUE, | |
71 | 'expose_limit' => TRUE, | |
72 | ], | |
73 | 'columns' => [], | |
74 | ], | |
75 | ]; | |
5c952e51 CW |
76 | // Allow implicit-join-style selection of saved search fields |
77 | foreach ($this->savedSearch as $key => $val) { | |
78 | $display['saved_search_id.' . $key] = $val; | |
79 | } | |
ea04af0c CW |
80 | foreach ($this->getSelectClause() as $key => $clause) { |
81 | $display['settings']['columns'][] = $this->configureColumn($clause, $key); | |
82 | } | |
83 | $display['settings']['columns'][] = [ | |
84 | 'label' => '', | |
85 | 'type' => 'menu', | |
86 | 'icon' => 'fa-bars', | |
87 | 'size' => 'btn-xs', | |
88 | 'style' => 'secondary-outline', | |
89 | 'alignment' => 'text-right', | |
90 | 'links' => $this->getLinksMenu(), | |
91 | ]; | |
5c952e51 | 92 | $result->exchangeArray($this->selectArray([$display])); |
ea04af0c CW |
93 | } |
94 | ||
95 | /** | |
96 | * @param array{fields: array, expr: SqlExpression, dataType: string} $clause | |
97 | * @param string $key | |
98 | * @return array | |
99 | */ | |
100 | private function configureColumn($clause, $key) { | |
101 | $col = [ | |
102 | 'type' => 'field', | |
103 | 'key' => $key, | |
104 | 'sortable' => !empty($clause['fields']), | |
105 | 'label' => $this->getColumnLabel($clause['expr']), | |
106 | ]; | |
107 | $this->getColumnLink($col, $clause); | |
108 | return $col; | |
109 | } | |
110 | ||
111 | /** | |
112 | * @param \Civi\Api4\Query\SqlExpression $expr | |
113 | * @return string | |
114 | */ | |
115 | private function getColumnLabel(SqlExpression $expr) { | |
116 | if ($expr instanceof SqlFunction) { | |
117 | $args = []; | |
118 | foreach ($expr->getArgs() as $arg) { | |
119 | foreach ($arg['expr'] ?? [] as $ex) { | |
120 | $args[] = $this->getColumnLabel($ex); | |
121 | } | |
122 | } | |
123 | return '(' . $expr->getTitle() . ')' . ($args ? ' ' . implode(',', array_filter($args)) : ''); | |
124 | } | |
125 | if ($expr instanceof SqlEquation) { | |
126 | $args = []; | |
127 | foreach ($expr->getArgs() as $arg) { | |
128 | $args[] = $this->getColumnLabel($arg['expr']); | |
129 | } | |
130 | return '(' . implode(',', array_filter($args)) . ')'; | |
131 | } | |
132 | elseif ($expr instanceof SqlField) { | |
133 | $field = $this->getField($expr->getExpr()); | |
134 | $label = ''; | |
135 | if (!empty($field['explicit_join'])) { | |
136 | $label = $this->getJoinLabel($field['explicit_join']) . ': '; | |
137 | } | |
138 | if (!empty($field['implicit_join'])) { | |
139 | $field = $this->getField(substr($expr->getAlias(), 0, -1 - strlen($field['name']))); | |
140 | } | |
141 | return $label . $field['label']; | |
142 | } | |
143 | else { | |
144 | return NULL; | |
145 | } | |
146 | } | |
147 | ||
148 | /** | |
149 | * @param string $joinAlias | |
150 | * @return string | |
151 | */ | |
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]); | |
158 | $num = ''; | |
159 | if (!empty($joinCount[$entityName])) { | |
160 | $num = ' ' . (++$joinCount[$entityName]); | |
161 | } | |
162 | else { | |
163 | $joinCount[$entityName] = 1; | |
164 | } | |
165 | $label = CoreUtil::getInfoItem($entityName, 'title'); | |
166 | $this->_joinMap[$alias] = $label . $num; | |
167 | } | |
168 | } | |
169 | return $this->_joinMap[$joinAlias]; | |
170 | } | |
171 | ||
172 | /** | |
173 | * @param array $col | |
174 | * @param array{fields: array, expr: SqlExpression, dataType: string} $clause | |
175 | */ | |
176 | private function getColumnLink(&$col, $clause) { | |
177 | if ($clause['expr'] instanceof SqlField || $clause['expr'] instanceof SqlFunctionGROUP_CONCAT) { | |
178 | $field = $clause['fields'][0] ?? NULL; | |
179 | if ($field && | |
180 | CoreUtil::getInfoItem($field['entity'], 'label_field') === $field['name'] && | |
181 | !empty(CoreUtil::getInfoItem($field['entity'], 'paths')['view']) | |
182 | ) { | |
183 | $col['link'] = [ | |
184 | 'entity' => $field['entity'], | |
185 | 'join' => implode('.', array_filter([$field['explicit_join'], $field['implicit_join']])), | |
186 | 'action' => 'view', | |
187 | ]; | |
188 | // Hack to support links to relationships | |
189 | if ($col['link']['entity'] === 'RelationshipCache') { | |
190 | $col['link']['entity'] = 'Relationship'; | |
191 | } | |
192 | $col['title'] = E::ts('View %1', [1 => CoreUtil::getInfoItem($field['entity'], 'title')]); | |
193 | } | |
194 | } | |
195 | } | |
196 | ||
197 | /** | |
198 | * return array[] | |
199 | */ | |
200 | private function getLinksMenu() { | |
201 | $menu = []; | |
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); | |
206 | if ($link) { | |
207 | $menu[] = $link; | |
208 | } | |
209 | } | |
210 | } | |
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']); | |
217 | if ($link) { | |
218 | $menu[] = $link; | |
219 | } | |
220 | } | |
221 | } | |
222 | } | |
223 | } | |
224 | return $menu; | |
225 | } | |
226 | ||
227 | /** | |
228 | * @param string $entity | |
229 | * @param string $action | |
230 | * @param string $joinAlias | |
231 | * @return array|NULL | |
232 | */ | |
233 | private function formatMenuLink(string $entity, string $action, string $joinAlias = NULL) { | |
234 | if ($joinAlias && $entity === $this->getJoin($joinAlias)['entity']) { | |
235 | $entityLabel = $this->getJoinLabel($joinAlias); | |
236 | } | |
237 | else { | |
238 | $entityLabel = TRUE; | |
239 | } | |
240 | $link = Display::getEntityLinks($entity, $entityLabel)[$action] ?? NULL; | |
241 | return $link ? $link + ['join' => $joinAlias] : NULL; | |
242 | } | |
243 | ||
244 | } |