Merge pull request #23016 from pradpnayak/optionValue
[civicrm-core.git] / ext / afform / admin / Civi / Api4 / Action / Afform / LoadAdminData.php
CommitLineData
490565d0
CW
1<?php
2
3namespace Civi\Api4\Action\Afform;
4
5use Civi\AfformAdmin\AfformAdminMeta;
6use Civi\Api4\Afform;
2167da5f 7use Civi\Api4\Utils\CoreUtil;
cc555786 8use 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 */
15class 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}