Commit | Line | Data |
---|---|---|
ea04af0c CW |
1 | <?php |
2 | ||
3 | namespace Civi\Api4\Action\SearchDisplay; | |
4 | ||
afb75a23 CW |
5 | use Civi\Api4\SavedSearch; |
6 | use Civi\Api4\Utils\FormattingUtil; | |
ea04af0c CW |
7 | use Civi\Search\Display; |
8 | use CRM_Search_ExtensionUtil as E; | |
9 | use Civi\Api4\Query\SqlEquation; | |
10 | use Civi\Api4\Query\SqlExpression; | |
11 | use Civi\Api4\Query\SqlField; | |
12 | use Civi\Api4\Query\SqlFunction; | |
13 | use Civi\Api4\Query\SqlFunctionGROUP_CONCAT; | |
14 | use Civi\Api4\Utils\CoreUtil; | |
15 | use Civi\API\Exception\UnauthorizedException; | |
16 | ||
17 | /** | |
18 | * Return the default results table for a saved search. | |
19 | * | |
20 | * @package Civi\Api4\Action\SearchDisplay | |
21 | */ | |
22 | class GetDefault extends \Civi\Api4\Generic\AbstractAction { | |
23 | ||
24 | use SavedSearchInspectorTrait; | |
5c952e51 CW |
25 | use \Civi\Api4\Generic\Traits\ArrayQueryActionTrait; |
26 | use \Civi\Api4\Generic\Traits\SelectParamTrait; | |
ea04af0c CW |
27 | |
28 | /** | |
29 | * Either the name of the savedSearch or an array containing the savedSearch definition (for preview mode) | |
30 | * @var string|array|null | |
31 | */ | |
32 | protected $savedSearch; | |
33 | ||
34 | /** | |
35 | * @var array | |
36 | */ | |
37 | private $_joinMap; | |
38 | ||
39 | /** | |
40 | * @param \Civi\Api4\Generic\Result $result | |
41 | * @throws UnauthorizedException | |
42 | * @throws \API_Exception | |
43 | */ | |
44 | public function _run(\Civi\Api4\Generic\Result $result) { | |
45 | // Only administrators can use this in unsecured "preview mode" | |
46 | if (is_array($this->savedSearch) && $this->checkPermissions && !\CRM_Core_Permission::check('administer CiviCRM data')) { | |
47 | throw new UnauthorizedException('Access denied'); | |
48 | } | |
49 | $this->loadSavedSearch(); | |
5c952e51 | 50 | $this->expandSelectClauseWildcards(); |
ea04af0c CW |
51 | // Use label from saved search |
52 | $label = $this->savedSearch['label'] ?? ''; | |
53 | // Fall back on entity title as label | |
54 | if (!strlen($label) && !empty($this->savedSearch['api_entity'])) { | |
55 | $label = CoreUtil::getInfoItem($this->savedSearch['api_entity'], 'title_plural'); | |
56 | } | |
57 | $display = [ | |
5c952e51 | 58 | 'id' => NULL, |
ea04af0c | 59 | 'name' => NULL, |
5c952e51 | 60 | 'saved_search_id' => $this->savedSearch['id'] ?? NULL, |
ea04af0c CW |
61 | 'label' => $label, |
62 | 'type' => 'table', | |
63 | 'acl_bypass' => FALSE, | |
64 | 'settings' => [ | |
ea04af0c CW |
65 | 'actions' => TRUE, |
66 | 'limit' => \Civi::settings()->get('default_pager_size'), | |
67 | 'classes' => ['table', 'table-striped'], | |
68 | 'pager' => [ | |
69 | 'show_count' => TRUE, | |
70 | 'expose_limit' => TRUE, | |
71 | ], | |
089402ed | 72 | 'sort' => [], |
ea04af0c CW |
73 | 'columns' => [], |
74 | ], | |
75 | ]; | |
089402ed CW |
76 | // Supply default sort if no orderBy given in api params |
77 | if (!empty($this->savedSearch['api_entity']) && empty($this->savedSearch['api_params']['orderBy'])) { | |
78 | $defaultSort = CoreUtil::getInfoItem($this->savedSearch['api_entity'], 'order_by'); | |
79 | if ($defaultSort) { | |
80 | $display['settings']['sort'][] = [$defaultSort, 'ASC']; | |
81 | } | |
82 | } | |
ea04af0c CW |
83 | foreach ($this->getSelectClause() as $key => $clause) { |
84 | $display['settings']['columns'][] = $this->configureColumn($clause, $key); | |
85 | } | |
86 | $display['settings']['columns'][] = [ | |
87 | 'label' => '', | |
88 | 'type' => 'menu', | |
89 | 'icon' => 'fa-bars', | |
90 | 'size' => 'btn-xs', | |
91 | 'style' => 'secondary-outline', | |
92 | 'alignment' => 'text-right', | |
93 | 'links' => $this->getLinksMenu(), | |
94 | ]; | |
afb75a23 CW |
95 | $fields = $this->entityFields(); |
96 | // Allow implicit-join-style selection of saved search fields | |
97 | if ($this->savedSearch) { | |
98 | $display += \CRM_Utils_Array::prefixKeys($this->savedSearch, 'saved_search_id.'); | |
99 | $fields += \CRM_Utils_Array::prefixKeys(SavedSearch::get()->entityFields(), 'saved_search_id.'); | |
100 | } | |
101 | // Fill pseudoconstant keys with raw values for replacement | |
102 | foreach ($this->select as $fieldExpr) { | |
103 | [$fieldName, $suffix] = array_pad(explode(':', $fieldExpr), 2, NULL); | |
104 | if ($suffix && array_key_exists($fieldName, $display)) { | |
105 | $display[$fieldExpr] = $display[$fieldName]; | |
106 | } | |
107 | } | |
108 | $results = [$display]; | |
109 | // Replace pseudoconstants | |
110 | FormattingUtil::formatOutputValues($results, $fields); | |
111 | $result->exchangeArray($this->selectArray($results)); | |
ea04af0c CW |
112 | } |
113 | ||
114 | /** | |
115 | * @param array{fields: array, expr: SqlExpression, dataType: string} $clause | |
116 | * @param string $key | |
117 | * @return array | |
118 | */ | |
119 | private function configureColumn($clause, $key) { | |
120 | $col = [ | |
121 | 'type' => 'field', | |
122 | 'key' => $key, | |
123 | 'sortable' => !empty($clause['fields']), | |
124 | 'label' => $this->getColumnLabel($clause['expr']), | |
125 | ]; | |
126 | $this->getColumnLink($col, $clause); | |
127 | return $col; | |
128 | } | |
129 | ||
130 | /** | |
131 | * @param \Civi\Api4\Query\SqlExpression $expr | |
132 | * @return string | |
133 | */ | |
134 | private function getColumnLabel(SqlExpression $expr) { | |
135 | if ($expr instanceof SqlFunction) { | |
136 | $args = []; | |
137 | foreach ($expr->getArgs() as $arg) { | |
138 | foreach ($arg['expr'] ?? [] as $ex) { | |
139 | $args[] = $this->getColumnLabel($ex); | |
140 | } | |
141 | } | |
142 | return '(' . $expr->getTitle() . ')' . ($args ? ' ' . implode(',', array_filter($args)) : ''); | |
143 | } | |
144 | if ($expr instanceof SqlEquation) { | |
145 | $args = []; | |
146 | foreach ($expr->getArgs() as $arg) { | |
147 | $args[] = $this->getColumnLabel($arg['expr']); | |
148 | } | |
149 | return '(' . implode(',', array_filter($args)) . ')'; | |
150 | } | |
151 | elseif ($expr instanceof SqlField) { | |
152 | $field = $this->getField($expr->getExpr()); | |
153 | $label = ''; | |
154 | if (!empty($field['explicit_join'])) { | |
155 | $label = $this->getJoinLabel($field['explicit_join']) . ': '; | |
156 | } | |
ef33b55a | 157 | if (!empty($field['implicit_join']) && empty($field['custom_field_id'])) { |
ea04af0c CW |
158 | $field = $this->getField(substr($expr->getAlias(), 0, -1 - strlen($field['name']))); |
159 | } | |
160 | return $label . $field['label']; | |
161 | } | |
162 | else { | |
163 | return NULL; | |
164 | } | |
165 | } | |
166 | ||
167 | /** | |
168 | * @param string $joinAlias | |
169 | * @return string | |
170 | */ | |
171 | private function getJoinLabel($joinAlias) { | |
172 | if (!isset($this->_joinMap)) { | |
173 | $this->_joinMap = []; | |
174 | $joinCount = [$this->savedSearch['api_entity'] => 1]; | |
175 | foreach ($this->savedSearch['api_params']['join'] ?? [] as $join) { | |
176 | [$entityName, $alias] = explode(' AS ', $join[0]); | |
177 | $num = ''; | |
178 | if (!empty($joinCount[$entityName])) { | |
179 | $num = ' ' . (++$joinCount[$entityName]); | |
180 | } | |
181 | else { | |
182 | $joinCount[$entityName] = 1; | |
183 | } | |
184 | $label = CoreUtil::getInfoItem($entityName, 'title'); | |
185 | $this->_joinMap[$alias] = $label . $num; | |
186 | } | |
187 | } | |
188 | return $this->_joinMap[$joinAlias]; | |
189 | } | |
190 | ||
191 | /** | |
192 | * @param array $col | |
193 | * @param array{fields: array, expr: SqlExpression, dataType: string} $clause | |
194 | */ | |
195 | private function getColumnLink(&$col, $clause) { | |
196 | if ($clause['expr'] instanceof SqlField || $clause['expr'] instanceof SqlFunctionGROUP_CONCAT) { | |
197 | $field = $clause['fields'][0] ?? NULL; | |
198 | if ($field && | |
199 | CoreUtil::getInfoItem($field['entity'], 'label_field') === $field['name'] && | |
200 | !empty(CoreUtil::getInfoItem($field['entity'], 'paths')['view']) | |
201 | ) { | |
202 | $col['link'] = [ | |
203 | 'entity' => $field['entity'], | |
204 | 'join' => implode('.', array_filter([$field['explicit_join'], $field['implicit_join']])), | |
205 | 'action' => 'view', | |
206 | ]; | |
207 | // Hack to support links to relationships | |
208 | if ($col['link']['entity'] === 'RelationshipCache') { | |
209 | $col['link']['entity'] = 'Relationship'; | |
210 | } | |
211 | $col['title'] = E::ts('View %1', [1 => CoreUtil::getInfoItem($field['entity'], 'title')]); | |
212 | } | |
213 | } | |
214 | } | |
215 | ||
216 | /** | |
217 | * return array[] | |
218 | */ | |
219 | private function getLinksMenu() { | |
220 | $menu = []; | |
221 | $mainEntity = $this->savedSearch['api_entity'] ?? NULL; | |
222 | if ($mainEntity && !$this->canAggregate(CoreUtil::getIdFieldName($mainEntity))) { | |
223 | foreach (CoreUtil::getInfoItem($mainEntity, 'paths') as $action => $path) { | |
224 | $link = $this->formatMenuLink($mainEntity, $action); | |
225 | if ($link) { | |
226 | $menu[] = $link; | |
227 | } | |
228 | } | |
229 | } | |
230 | $keys = ['entity' => TRUE, 'bridge' => TRUE]; | |
231 | foreach ($this->getJoins() as $join) { | |
232 | if (!$this->canAggregate($join['alias'] . '.' . CoreUtil::getIdFieldName($join['entity']))) { | |
233 | foreach (array_filter(array_intersect_key($join, $keys)) as $joinEntity) { | |
234 | foreach (CoreUtil::getInfoItem($joinEntity, 'paths') as $action => $path) { | |
235 | $link = $this->formatMenuLink($joinEntity, $action, $join['alias']); | |
236 | if ($link) { | |
237 | $menu[] = $link; | |
238 | } | |
239 | } | |
240 | } | |
241 | } | |
242 | } | |
243 | return $menu; | |
244 | } | |
245 | ||
246 | /** | |
247 | * @param string $entity | |
248 | * @param string $action | |
249 | * @param string $joinAlias | |
250 | * @return array|NULL | |
251 | */ | |
252 | private function formatMenuLink(string $entity, string $action, string $joinAlias = NULL) { | |
253 | if ($joinAlias && $entity === $this->getJoin($joinAlias)['entity']) { | |
254 | $entityLabel = $this->getJoinLabel($joinAlias); | |
255 | } | |
256 | else { | |
257 | $entityLabel = TRUE; | |
258 | } | |
259 | $link = Display::getEntityLinks($entity, $entityLabel)[$action] ?? NULL; | |
260 | return $link ? $link + ['join' => $joinAlias] : NULL; | |
261 | } | |
262 | ||
263 | } |