Commit | Line | Data |
---|---|---|
490565d0 CW |
1 | <?php |
2 | ||
3 | namespace Civi\Api4\Action\Afform; | |
4 | ||
5 | use Civi\AfformAdmin\AfformAdminMeta; | |
6 | use Civi\Api4\Afform; | |
2167da5f | 7 | use Civi\Api4\Utils\CoreUtil; |
cc555786 | 8 | use Civi\Api4\Query\SqlExpression; |
490565d0 CW |
9 | |
10 | /** | |
11 | * This action is used by the Afform Admin extension to load metadata for the Admin GUI. | |
12 | * | |
13 | * @package Civi\Api4\Action\Afform | |
14 | */ | |
15 | class LoadAdminData extends \Civi\Api4\Generic\AbstractAction { | |
16 | ||
17 | /** | |
18 | * Any properties already known about the afform | |
19 | * @var array | |
2b8e5ee4 | 20 | * @required |
490565d0 CW |
21 | */ |
22 | protected $definition; | |
23 | ||
24 | /** | |
25 | * Entity type when creating a new form | |
26 | * @var string | |
27 | */ | |
28 | protected $entity; | |
29 | ||
2b8e5ee4 CW |
30 | /** |
31 | * A list of entities whose blocks & fields are not needed | |
32 | * @var array | |
33 | */ | |
34 | protected $skipEntities = []; | |
35 | ||
490565d0 | 36 | public function _run(\Civi\Api4\Generic\Result $result) { |
2b8e5ee4 | 37 | $info = ['entities' => [], 'fields' => [], 'blocks' => []]; |
490565d0 CW |
38 | $entities = []; |
39 | $newForm = empty($this->definition['name']); | |
40 | ||
41 | if (!$newForm) { | |
42 | // Load existing afform if name provided | |
43 | $info['definition'] = $this->loadForm($this->definition['name']); | |
44 | } | |
45 | else { | |
46 | // Create new blank afform | |
47 | switch ($this->definition['type']) { | |
48 | case 'form': | |
49 | $info['definition'] = $this->definition + [ | |
50 | 'title' => '', | |
51 | 'permission' => 'access CiviCRM', | |
52 | 'layout' => [ | |
53 | [ | |
54 | '#tag' => 'af-form', | |
55 | 'ctrl' => 'afform', | |
56 | '#children' => [], | |
57 | ], | |
58 | ], | |
59 | ]; | |
60 | break; | |
61 | ||
62 | case 'block': | |
63 | $info['definition'] = $this->definition + [ | |
64 | 'title' => '', | |
fa8dc3f2 | 65 | 'entity_type' => $this->entity, |
490565d0 CW |
66 | 'layout' => [], |
67 | ]; | |
68 | break; | |
2ef64700 CW |
69 | |
70 | case 'search': | |
71 | $info['definition'] = $this->definition + [ | |
72 | 'title' => '', | |
73 | 'permission' => 'access CiviCRM', | |
74 | 'layout' => [ | |
75 | [ | |
76 | '#tag' => 'div', | |
77 | 'af-fieldset' => '', | |
78 | '#children' => [], | |
79 | ], | |
80 | ], | |
81 | ]; | |
82 | break; | |
490565d0 CW |
83 | } |
84 | } | |
85 | ||
86 | $getFieldsMode = 'create'; | |
87 | ||
88 | // Generate list of possibly embedded afform tags to search for | |
89 | $allAfforms = \Civi::service('afform_scanner')->findFilePaths(); | |
90 | foreach ($allAfforms as $name => $path) { | |
91 | $allAfforms[$name] = _afform_angular_module_name($name, 'dash'); | |
92 | } | |
2b8e5ee4 CW |
93 | |
94 | /** | |
95 | * Find all entities by recursing into embedded afforms | |
96 | * @param array $layout | |
97 | */ | |
490565d0 CW |
98 | $scanBlocks = function($layout) use (&$scanBlocks, &$info, &$entities, $allAfforms) { |
99 | // Find declared af-entity tags | |
100 | foreach (\CRM_Utils_Array::findAll($layout, ['#tag' => 'af-entity']) as $afEntity) { | |
101 | // Convert "Contact" to "Individual", "Organization" or "Household" | |
102 | if ($afEntity['type'] === 'Contact' && !empty($afEntity['data'])) { | |
103 | $data = \CRM_Utils_JS::decode($afEntity['data']); | |
104 | $entities[] = $data['contact_type'] ?? $afEntity['type']; | |
105 | } | |
106 | else { | |
107 | $entities[] = $afEntity['type']; | |
108 | } | |
109 | } | |
110 | $joins = array_column(\CRM_Utils_Array::findAll($layout, 'af-join'), 'af-join'); | |
111 | $entities = array_unique(array_merge($entities, $joins)); | |
112 | $blockTags = array_unique(array_column(\CRM_Utils_Array::findAll($layout, function($el) use ($allAfforms) { | |
ef098a53 | 113 | return isset($el['#tag']) && in_array($el['#tag'], $allAfforms); |
490565d0 CW |
114 | }), '#tag')); |
115 | foreach ($blockTags as $blockTag) { | |
116 | if (!isset($info['blocks'][$blockTag])) { | |
117 | // Load full contents of block used on the form, then recurse into it | |
118 | $embeddedForm = Afform::get($this->checkPermissions) | |
a4630f3b | 119 | ->addSelect('*', 'directive_name') |
490565d0 CW |
120 | ->setFormatWhitespace(TRUE) |
121 | ->setLayoutFormat('shallow') | |
122 | ->addWhere('directive_name', '=', $blockTag) | |
123 | ->execute()->first(); | |
124 | if ($embeddedForm['type'] === 'block') { | |
125 | $info['blocks'][$blockTag] = $embeddedForm; | |
126 | } | |
fa8dc3f2 CW |
127 | if (!empty($embeddedForm['join_entity'])) { |
128 | $entities = array_unique(array_merge($entities, [$embeddedForm['join_entity']])); | |
490565d0 CW |
129 | } |
130 | $scanBlocks($embeddedForm['layout']); | |
131 | } | |
132 | } | |
133 | }; | |
134 | ||
135 | if ($info['definition']['type'] === 'form') { | |
136 | if ($newForm) { | |
137 | $entities[] = $this->entity; | |
138 | $defaultEntity = AfformAdminMeta::getAfformEntity($this->entity); | |
139 | if (!empty($defaultEntity['boilerplate'])) { | |
140 | $scanBlocks($defaultEntity['boilerplate']); | |
141 | } | |
142 | } | |
143 | else { | |
144 | $scanBlocks($info['definition']['layout']); | |
145 | } | |
146 | ||
2d35bc78 | 147 | if (array_intersect($entities, \CRM_Contact_BAO_ContactType::basicTypes(TRUE))) { |
490565d0 CW |
148 | $entities[] = 'Contact'; |
149 | } | |
150 | ||
151 | // The full contents of blocks used on the form have been loaded. Get basic info about others relevant to these entities. | |
de9fdf9f | 152 | $this->loadAvailableBlocks($entities, $info); |
490565d0 CW |
153 | } |
154 | ||
155 | if ($info['definition']['type'] === 'block') { | |
fa8dc3f2 | 156 | $blockEntity = $info['definition']['join_entity'] ?? $info['definition']['entity_type']; |
a191be25 CW |
157 | if ($blockEntity !== '*') { |
158 | $entities[] = $blockEntity; | |
159 | } | |
de9fdf9f CW |
160 | $scanBlocks($info['definition']['layout']); |
161 | $this->loadAvailableBlocks($entities, $info); | |
490565d0 CW |
162 | } |
163 | ||
2ef64700 | 164 | if ($info['definition']['type'] === 'search') { |
0d2e207f | 165 | $getFieldsMode = 'get'; |
2ef64700 CW |
166 | $displayTags = []; |
167 | if ($newForm) { | |
168 | [$searchName, $displayName] = array_pad(explode('.', $this->entity ?? ''), 2, ''); | |
169 | $displayTags[] = ['search-name' => $searchName, 'display-name' => $displayName]; | |
170 | } | |
171 | else { | |
172 | foreach (\Civi\Search\Display::getDisplayTypes(['name']) as $displayType) { | |
173 | $displayTags = array_merge($displayTags, \CRM_Utils_Array::findAll($info['definition']['layout'], ['#tag' => $displayType['name']])); | |
174 | } | |
175 | } | |
176 | foreach ($displayTags as $displayTag) { | |
5c952e51 CW |
177 | if (isset($displayTag['display-name']) && strlen($displayTag['display-name'])) { |
178 | $displayGet = \Civi\Api4\SearchDisplay::get(FALSE) | |
179 | ->addWhere('name', '=', $displayTag['display-name']) | |
180 | ->addWhere('saved_search_id.name', '=', $displayTag['search-name']); | |
181 | } | |
182 | else { | |
183 | $displayGet = \Civi\Api4\SearchDisplay::getDefault(FALSE) | |
184 | ->setSavedSearch($displayTag['search-name']); | |
185 | } | |
186 | $display = $displayGet | |
7cc347c2 | 187 | ->addSelect('*', 'type:name', 'type:icon', 'saved_search_id.name', 'saved_search_id.label', 'saved_search_id.api_entity', 'saved_search_id.api_params') |
2ef64700 | 188 | ->execute()->first(); |
5c952e51 | 189 | $display['calc_fields'] = $this->getCalcFields($display['saved_search_id.api_entity'], $display['saved_search_id.api_params']); |
4f5b0626 | 190 | $display['filters'] = empty($displayTag['filters']) ? NULL : (\CRM_Utils_JS::getRawProps($displayTag['filters']) ?: NULL); |
2ef64700 CW |
191 | $info['search_displays'][] = $display; |
192 | if ($newForm) { | |
193 | $info['definition']['layout'][0]['#children'][] = $displayTag + ['#tag' => $display['type:name']]; | |
194 | } | |
5c952e51 CW |
195 | $entities[] = $display['saved_search_id.api_entity']; |
196 | foreach ($display['saved_search_id.api_params']['join'] ?? [] as $join) { | |
2ef64700 CW |
197 | $entities[] = explode(' AS ', $join[0])[0]; |
198 | } | |
199 | } | |
2b8e5ee4 CW |
200 | if (!$newForm) { |
201 | $scanBlocks($info['definition']['layout']); | |
202 | } | |
fa8dc3f2 | 203 | $this->loadAvailableBlocks($entities, $info, [['join_entity', 'IS NULL']]); |
2ef64700 CW |
204 | } |
205 | ||
490565d0 CW |
206 | // Optimization - since contact fields are a combination of these three, |
207 | // we'll combine them client-side rather than sending them via ajax. | |
2d35bc78 | 208 | elseif (array_intersect($entities, \CRM_Contact_BAO_ContactType::basicTypes(TRUE))) { |
490565d0 CW |
209 | $entities = array_diff($entities, ['Contact']); |
210 | } | |
211 | ||
2b8e5ee4 | 212 | foreach (array_diff($entities, $this->skipEntities) as $entity) { |
490565d0 CW |
213 | $info['entities'][$entity] = AfformAdminMeta::getApiEntity($entity); |
214 | $info['fields'][$entity] = AfformAdminMeta::getFields($entity, ['action' => $getFieldsMode]); | |
215 | } | |
2b8e5ee4 | 216 | $info['blocks'] = array_values($info['blocks']); |
490565d0 CW |
217 | |
218 | $result[] = $info; | |
219 | } | |
220 | ||
2167da5f CW |
221 | /** |
222 | * @param string $name | |
223 | * @return array|null | |
224 | */ | |
490565d0 CW |
225 | private function loadForm($name) { |
226 | return Afform::get($this->checkPermissions) | |
2fd66f49 | 227 | ->addSelect('*', 'directive_name') |
490565d0 CW |
228 | ->setFormatWhitespace(TRUE) |
229 | ->setLayoutFormat('shallow') | |
230 | ->addWhere('name', '=', $name) | |
231 | ->execute()->first(); | |
232 | } | |
233 | ||
de9fdf9f CW |
234 | /** |
235 | * Get basic info about blocks relevant to these entities. | |
236 | * | |
2b8e5ee4 CW |
237 | * @param array $entities |
238 | * @param array $info | |
239 | * @param array $where | |
de9fdf9f CW |
240 | * @throws \API_Exception |
241 | * @throws \Civi\API\Exception\UnauthorizedException | |
242 | */ | |
2b8e5ee4 CW |
243 | private function loadAvailableBlocks($entities, &$info, $where = []) { |
244 | $entities = array_diff($entities, $this->skipEntities); | |
a191be25 CW |
245 | if (!$this->skipEntities) { |
246 | $entities[] = '*'; | |
247 | } | |
2b8e5ee4 CW |
248 | if ($entities) { |
249 | $blockInfo = Afform::get($this->checkPermissions) | |
41597179 | 250 | ->addSelect('name', 'title', 'entity_type', 'join_entity', 'directive_name') |
2b8e5ee4 CW |
251 | ->setWhere($where) |
252 | ->addWhere('type', '=', 'block') | |
fa8dc3f2 | 253 | ->addWhere('entity_type', 'IN', $entities) |
2b8e5ee4 CW |
254 | ->addWhere('directive_name', 'NOT IN', array_keys($info['blocks'])) |
255 | ->execute(); | |
256 | $info['blocks'] = array_merge(array_values($info['blocks']), (array) $blockInfo); | |
257 | } | |
de9fdf9f CW |
258 | } |
259 | ||
cc555786 CW |
260 | /** |
261 | * @param string $apiEntity | |
262 | * @param array $apiParams | |
263 | * @return array | |
264 | */ | |
265 | private function getCalcFields($apiEntity, $apiParams) { | |
266 | $calcFields = []; | |
267 | $api = \Civi\API\Request::create($apiEntity, 'get', $apiParams); | |
268 | $selectQuery = new \Civi\Api4\Query\Api4SelectQuery($api); | |
269 | $joinMap = $joinCount = []; | |
270 | foreach ($apiParams['join'] ?? [] as $join) { | |
271 | [$entityName, $alias] = explode(' AS ', $join[0]); | |
272 | $num = ''; | |
273 | if (!empty($joinCount[$entityName])) { | |
274 | $num = ' ' . (++$joinCount[$entityName]); | |
275 | } | |
276 | else { | |
277 | $joinCount[$entityName] = 1; | |
278 | } | |
2167da5f | 279 | $label = CoreUtil::getInfoItem($entityName, 'title'); |
cc555786 CW |
280 | $joinMap[$alias] = $label . $num; |
281 | } | |
282 | ||
283 | foreach ($apiParams['select'] ?? [] as $select) { | |
284 | if (strstr($select, ' AS ')) { | |
285 | $expr = SqlExpression::convert($select, TRUE); | |
7891ca94 CW |
286 | $label = $expr::getTitle(); |
287 | foreach ($expr->getFields() as $num => $fieldName) { | |
288 | $field = $selectQuery->getField($fieldName); | |
289 | $joinName = explode('.', $fieldName)[0]; | |
290 | $label .= ($num ? ', ' : ': ') . (isset($joinMap[$joinName]) ? $joinMap[$joinName] . ' ' : '') . $field['title']; | |
291 | } | |
cc555786 CW |
292 | $calcFields[] = [ |
293 | '#tag' => 'af-field', | |
294 | 'name' => $expr->getAlias(), | |
295 | 'defn' => [ | |
296 | 'label' => $label, | |
297 | 'input_type' => 'Text', | |
298 | ], | |
299 | ]; | |
300 | } | |
301 | } | |
302 | return $calcFields; | |
303 | } | |
304 | ||
2167da5f CW |
305 | /** |
306 | * @return array[] | |
307 | */ | |
490565d0 CW |
308 | public function fields() { |
309 | return [ | |
310 | [ | |
311 | 'name' => 'definition', | |
312 | 'data_type' => 'Array', | |
313 | ], | |
314 | [ | |
315 | 'name' => 'blocks', | |
316 | 'data_type' => 'Array', | |
317 | ], | |
318 | [ | |
319 | 'name' => 'fields', | |
320 | 'data_type' => 'Array', | |
321 | ], | |
2ef64700 CW |
322 | [ |
323 | 'name' => 'search_displays', | |
324 | 'data_type' => 'Array', | |
325 | ], | |
490565d0 CW |
326 | ]; |
327 | } | |
328 | ||
329 | /** | |
330 | * @return array | |
331 | */ | |
332 | public function getDefinition():array { | |
333 | return $this->definition; | |
334 | } | |
335 | ||
336 | /** | |
337 | * @param array $definition | |
338 | */ | |
339 | public function setDefinition(array $definition) { | |
340 | $this->definition = $definition; | |
341 | return $this; | |
342 | } | |
343 | ||
344 | } |