Commit | Line | Data |
---|---|---|
66aa0f5e TO |
1 | <?php |
2 | ||
3 | require_once 'afform.civix.php'; | |
4 | use CRM_Afform_ExtensionUtil as E; | |
fb388832 | 5 | use Civi\Api4\Action\Afform\Submit; |
66aa0f5e | 6 | |
bc3b7c5b TO |
7 | /** |
8 | * Filter the content of $params to only have supported afform fields. | |
9 | * | |
10 | * @param array $params | |
11 | * @return array | |
12 | */ | |
13 | function _afform_fields_filter($params) { | |
5591cfbf | 14 | $result = []; |
e38db494 | 15 | $fields = \Civi\Api4\Afform::getfields()->setCheckPermissions(FALSE)->setAction('create')->execute()->indexBy('name'); |
50868e8d CW |
16 | foreach ($fields as $fieldName => $field) { |
17 | if (isset($params[$fieldName])) { | |
18 | $result[$fieldName] = $params[$fieldName]; | |
d1ec770c | 19 | |
e38db494 CW |
20 | if ($field['data_type'] === 'Boolean' && !is_bool($params[$fieldName])) { |
21 | $result[$fieldName] = CRM_Utils_String::strtobool($params[$fieldName]); | |
d1ec770c TO |
22 | } |
23 | } | |
bc3b7c5b TO |
24 | } |
25 | return $result; | |
26 | } | |
27 | ||
8f4a0ee9 | 28 | /** |
5591cfbf | 29 | * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container |
8f4a0ee9 TO |
30 | */ |
31 | function afform_civicrm_container($container) { | |
77dccccb | 32 | $container->addResource(new \Symfony\Component\Config\Resource\FileResource(__FILE__)); |
8f4a0ee9 TO |
33 | $container->setDefinition('afform_scanner', new \Symfony\Component\DependencyInjection\Definition( |
34 | 'CRM_Afform_AfformScanner', | |
5591cfbf | 35 | [] |
b53fe171 | 36 | ))->setPublic(TRUE); |
8f4a0ee9 TO |
37 | } |
38 | ||
66aa0f5e TO |
39 | /** |
40 | * Implements hook_civicrm_config(). | |
41 | * | |
42 | * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_config | |
43 | */ | |
44 | function afform_civicrm_config(&$config) { | |
45 | _afform_civix_civicrm_config($config); | |
77dccccb TO |
46 | |
47 | if (isset(Civi::$statics[__FUNCTION__])) { | |
48 | return; | |
49 | } | |
50 | Civi::$statics[__FUNCTION__] = 1; | |
51 | ||
e1aca853 | 52 | Civi::dispatcher()->addListener(Submit::EVENT_NAME, [Submit::class, 'processContacts'], 500); |
fb388832 | 53 | Civi::dispatcher()->addListener(Submit::EVENT_NAME, [Submit::class, 'processGenericEntity'], -1000); |
77dccccb | 54 | Civi::dispatcher()->addListener('hook_civicrm_angularModules', '_afform_civicrm_angularModules_autoReq', -1000); |
66aa0f5e TO |
55 | } |
56 | ||
57 | /** | |
58 | * Implements hook_civicrm_xmlMenu(). | |
59 | * | |
60 | * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_xmlMenu | |
61 | */ | |
62 | function afform_civicrm_xmlMenu(&$files) { | |
63 | _afform_civix_civicrm_xmlMenu($files); | |
64 | } | |
65 | ||
66 | /** | |
67 | * Implements hook_civicrm_install(). | |
68 | * | |
69 | * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_install | |
70 | */ | |
71 | function afform_civicrm_install() { | |
72 | _afform_civix_civicrm_install(); | |
73 | } | |
74 | ||
75 | /** | |
76 | * Implements hook_civicrm_postInstall(). | |
77 | * | |
78 | * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_postInstall | |
79 | */ | |
80 | function afform_civicrm_postInstall() { | |
81 | _afform_civix_civicrm_postInstall(); | |
82 | } | |
83 | ||
84 | /** | |
85 | * Implements hook_civicrm_uninstall(). | |
86 | * | |
87 | * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_uninstall | |
88 | */ | |
89 | function afform_civicrm_uninstall() { | |
90 | _afform_civix_civicrm_uninstall(); | |
91 | } | |
92 | ||
93 | /** | |
94 | * Implements hook_civicrm_enable(). | |
95 | * | |
96 | * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_enable | |
97 | */ | |
98 | function afform_civicrm_enable() { | |
99 | _afform_civix_civicrm_enable(); | |
100 | } | |
101 | ||
102 | /** | |
103 | * Implements hook_civicrm_disable(). | |
104 | * | |
105 | * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_disable | |
106 | */ | |
107 | function afform_civicrm_disable() { | |
108 | _afform_civix_civicrm_disable(); | |
109 | } | |
110 | ||
111 | /** | |
112 | * Implements hook_civicrm_upgrade(). | |
113 | * | |
114 | * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_upgrade | |
115 | */ | |
116 | function afform_civicrm_upgrade($op, CRM_Queue_Queue $queue = NULL) { | |
117 | return _afform_civix_civicrm_upgrade($op, $queue); | |
118 | } | |
119 | ||
120 | /** | |
121 | * Implements hook_civicrm_managed(). | |
122 | * | |
123 | * Generate a list of entities to create/deactivate/delete when this module | |
124 | * is installed, disabled, uninstalled. | |
125 | * | |
126 | * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_managed | |
127 | */ | |
128 | function afform_civicrm_managed(&$entities) { | |
129 | _afform_civix_civicrm_managed($entities); | |
521b232a TO |
130 | |
131 | /** @var \CRM_Afform_AfformScanner $scanner */ | |
132 | if (\Civi::container()->has('afform_scanner')) { | |
133 | $scanner = \Civi::service('afform_scanner'); | |
134 | } | |
135 | else { | |
136 | // This might happen at oddballs points - e.g. while you're in the middle of re-enabling the ext. | |
137 | // This AfformScanner instance only lives during this method call, and it feeds off the regular cache. | |
138 | $scanner = new CRM_Afform_AfformScanner(); | |
139 | } | |
140 | ||
141 | foreach ($scanner->getMetas() as $afform) { | |
142 | if (empty($afform['is_dashlet']) || empty($afform['name'])) { | |
143 | continue; | |
144 | } | |
145 | $entities[] = [ | |
146 | 'module' => E::LONG_NAME, | |
147 | 'name' => 'afform_dashlet_' . $afform['name'], | |
148 | 'entity' => 'Dashboard', | |
149 | 'update' => 'always', | |
150 | // ideal cleanup policy might be to (a) deactivate if used and (b) remove if unused | |
151 | 'cleanup' => 'always', | |
152 | 'params' => [ | |
153 | 'version' => 3, | |
154 | // Q: Should we loop through all domains? | |
155 | 'domain_id' => CRM_Core_BAO_Domain::getDomain()->id, | |
156 | 'is_active' => TRUE, | |
157 | 'name' => $afform['name'], | |
158 | 'label' => $afform['title'] ?? ts('(Untitled)'), | |
159 | 'directive' => _afform_angular_module_name($afform['name'], 'dash'), | |
160 | 'permission' => "@afform:" . $afform['name'], | |
161 | ], | |
162 | ]; | |
163 | } | |
66aa0f5e TO |
164 | } |
165 | ||
166 | /** | |
167 | * Implements hook_civicrm_caseTypes(). | |
168 | * | |
169 | * Generate a list of case-types. | |
170 | * | |
171 | * Note: This hook only runs in CiviCRM 4.4+. | |
172 | * | |
173 | * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_caseTypes | |
174 | */ | |
175 | function afform_civicrm_caseTypes(&$caseTypes) { | |
176 | _afform_civix_civicrm_caseTypes($caseTypes); | |
177 | } | |
178 | ||
179 | /** | |
180 | * Implements hook_civicrm_angularModules(). | |
181 | * | |
e1aca853 | 182 | * Generate a list of Afform Angular modules. |
66aa0f5e TO |
183 | */ |
184 | function afform_civicrm_angularModules(&$angularModules) { | |
185 | _afform_civix_civicrm_angularModules($angularModules); | |
bb56ac78 | 186 | |
5e04a2d4 | 187 | $afforms = \Civi\Api4\Afform::get(FALSE) |
e38db494 | 188 | ->setSelect(['name', 'requires', 'module_name', 'directive_name']) |
2d4bfef1 CW |
189 | ->execute(); |
190 | ||
191 | foreach ($afforms as $afform) { | |
e38db494 | 192 | $angularModules[$afform['module_name']] = [ |
bb56ac78 | 193 | 'ext' => E::LONG_NAME, |
2d4bfef1 CW |
194 | 'js' => ['assetBuilder://afform.js?name=' . urlencode($afform['name'])], |
195 | 'requires' => $afform['requires'], | |
bb56ac78 | 196 | 'basePages' => [], |
aa6abb77 | 197 | 'partialsCallback' => '_afform_get_partials', |
2d4bfef1 | 198 | '_afform' => $afform['name'], |
77dccccb | 199 | 'exports' => [ |
e38db494 | 200 | $afform['directive_name'] => 'AE', |
77dccccb | 201 | ], |
bb56ac78 | 202 | ]; |
bb56ac78 | 203 | } |
66aa0f5e TO |
204 | } |
205 | ||
aa6abb77 | 206 | /** |
2d4bfef1 CW |
207 | * Callback to retrieve partials for a given afform/angular module. |
208 | * | |
209 | * @see afform_civicrm_angularModules | |
aa6abb77 TO |
210 | * |
211 | * @param string $moduleName | |
212 | * The module name. | |
213 | * @param array $module | |
214 | * The module definition. | |
215 | * @return array | |
216 | * Array(string $filename => string $html). | |
2d4bfef1 | 217 | * @throws API_Exception |
aa6abb77 TO |
218 | */ |
219 | function _afform_get_partials($moduleName, $module) { | |
2d4bfef1 CW |
220 | $afform = civicrm_api4('Afform', 'get', [ |
221 | 'where' => [['name', '=', $module['_afform']]], | |
222 | 'select' => ['layout'], | |
223 | 'layoutFormat' => 'html', | |
224 | 'checkPermissions' => FALSE, | |
225 | ], 0); | |
aa6abb77 | 226 | return [ |
2d4bfef1 | 227 | "~/$moduleName/$moduleName.aff.html" => $afform['layout'], |
aa6abb77 TO |
228 | ]; |
229 | } | |
230 | ||
77dccccb TO |
231 | /** |
232 | * Scan the list of Angular modules and inject automatic-requirements. | |
233 | * | |
234 | * TLDR: if an afform uses element "<other-el/>", and if another module defines | |
235 | * `$angularModules['otherMod']['exports']['el'][0] === 'other-el'`, then | |
236 | * the 'otherMod' is automatically required. | |
237 | * | |
238 | * @param \Civi\Core\Event\GenericHookEvent $e | |
239 | * @see CRM_Utils_Hook::angularModules() | |
240 | */ | |
241 | function _afform_civicrm_angularModules_autoReq($e) { | |
242 | /** @var CRM_Afform_AfformScanner $scanner */ | |
243 | $scanner = Civi::service('afform_scanner'); | |
244 | $moduleEnvId = md5(\CRM_Core_Config_Runtime::getId() . implode(',', array_keys($e->angularModules))); | |
245 | $depCache = CRM_Utils_Cache::create([ | |
246 | 'name' => 'afdep_' . substr($moduleEnvId, 0, 32 - 6), | |
247 | 'type' => ['*memory*', 'SqlGroup', 'ArrayCache'], | |
248 | 'withArray' => 'fast', | |
249 | 'prefetch' => TRUE, | |
250 | ]); | |
251 | $depCacheTtl = 2 * 60 * 60; | |
252 | ||
253 | $revMap = _afform_reverse_deps($e->angularModules); | |
254 | ||
255 | $formNames = array_keys($scanner->findFilePaths()); | |
256 | foreach ($formNames as $formName) { | |
257 | $angModule = _afform_angular_module_name($formName, 'camel'); | |
258 | $cacheLine = $depCache->get($formName, NULL); | |
259 | ||
260 | $jFile = $scanner->findFilePath($formName, 'aff.json'); | |
261 | $hFile = $scanner->findFilePath($formName, 'aff.html'); | |
262 | ||
263 | $jStat = stat($jFile); | |
264 | $hStat = stat($hFile); | |
265 | ||
266 | if ($cacheLine === NULL) { | |
267 | $needsUpdate = TRUE; | |
268 | } | |
269 | elseif ($jStat !== FALSE && $jStat['size'] !== $cacheLine['js']) { | |
270 | $needsUpdate = TRUE; | |
271 | } | |
272 | elseif ($jStat !== FALSE && $jStat['mtime'] > $cacheLine['jm']) { | |
273 | $needsUpdate = TRUE; | |
274 | } | |
275 | elseif ($hStat !== FALSE && $hStat['size'] !== $cacheLine['hs']) { | |
276 | $needsUpdate = TRUE; | |
277 | } | |
278 | elseif ($hStat !== FALSE && $hStat['mtime'] > $cacheLine['hm']) { | |
279 | $needsUpdate = TRUE; | |
280 | } | |
281 | else { | |
282 | $needsUpdate = FALSE; | |
283 | } | |
284 | ||
285 | if ($needsUpdate) { | |
286 | $cacheLine = [ | |
287 | 'js' => $jStat['size'] ?? NULL, | |
288 | 'jm' => $jStat['mtime'] ?? NULL, | |
289 | 'hs' => $hStat['size'] ?? NULL, | |
290 | 'hm' => $hStat['mtime'] ?? NULL, | |
291 | 'r' => array_values(array_unique(array_merge( | |
292 | [CRM_Afform_AfformScanner::DEFAULT_REQUIRES], | |
293 | $e->angularModules[$angModule]['requires'] ?? [], | |
294 | _afform_reverse_deps_find($formName, file_get_contents($hFile), $revMap) | |
295 | ))), | |
296 | ]; | |
297 | // print_r(['cache update:' . $formName => $cacheLine]); | |
298 | $depCache->set($formName, $cacheLine, $depCacheTtl); | |
299 | } | |
300 | ||
301 | $e->angularModules[$angModule]['requires'] = $cacheLine['r']; | |
302 | } | |
303 | } | |
304 | ||
305 | /** | |
306 | * @param $angularModules | |
307 | * @return array | |
308 | * 'attr': array(string $attrName => string $angModuleName) | |
309 | * 'el': array(string $elementName => string $angModuleName) | |
310 | */ | |
311 | function _afform_reverse_deps($angularModules) { | |
23315411 TO |
312 | $revMap = ['attr' => [], 'el' => []]; |
313 | foreach (array_keys($angularModules) as $module) { | |
314 | if (!isset($angularModules[$module]['exports'])) { | |
315 | continue; | |
316 | } | |
317 | foreach ($angularModules[$module]['exports'] as $symbolName => $symbolTypes) { | |
318 | if (strpos($symbolTypes, 'A') !== FALSE) { | |
319 | $revMap['attr'][$symbolName] = $module; | |
320 | } | |
321 | if (strpos($symbolTypes, 'E') !== FALSE) { | |
322 | $revMap['el'][$symbolName] = $module; | |
77dccccb TO |
323 | } |
324 | } | |
325 | } | |
326 | return $revMap; | |
327 | } | |
328 | ||
23315411 TO |
329 | /** |
330 | * @param string $formName | |
331 | * @param string $html | |
332 | * @param array $revMap | |
333 | * The reverse-dependencies map from _afform_reverse_deps(). | |
334 | * @return array | |
335 | * @see _afform_reverse_deps() | |
336 | */ | |
77dccccb TO |
337 | function _afform_reverse_deps_find($formName, $html, $revMap) { |
338 | $symbols = \Civi\Afform\Symbols::scan($html); | |
339 | $elems = array_intersect_key($revMap['el'], $symbols->elements); | |
340 | $attrs = array_intersect_key($revMap['attr'], $symbols->attributes); | |
341 | return array_values(array_unique(array_merge($elems, $attrs))); | |
342 | } | |
343 | ||
9384980c TO |
344 | /** |
345 | * @param \Civi\Angular\Manager $angular | |
346 | * @see CRM_Utils_Hook::alterAngular() | |
347 | */ | |
348 | function afform_civicrm_alterAngular($angular) { | |
349 | $fieldMetadata = \Civi\Angular\ChangeSet::create('fieldMetadata') | |
dd10599c | 350 | ->alterHtml(';\\.aff\\.html$;', function($doc, $path) { |
9c84a124 CW |
351 | try { |
352 | $module = \Civi::service('angular')->getModule(basename($path, '.aff.html')); | |
344e8290 | 353 | $meta = \Civi\Api4\Afform::get()->addWhere('name', '=', $module['_afform'])->setSelect(['join', 'block'])->setCheckPermissions(FALSE)->execute()->first(); |
9c84a124 CW |
354 | } |
355 | catch (Exception $e) { | |
356 | } | |
357 | ||
344e8290 | 358 | $blockEntity = $meta['join'] ?? $meta['block'] ?? NULL; |
e1aca853 CW |
359 | if (!$blockEntity) { |
360 | $entities = _afform_getMetadata($doc); | |
361 | } | |
9384980c TO |
362 | |
363 | foreach (pq('af-field', $doc) as $afField) { | |
364 | /** @var DOMElement $afField */ | |
56b9319d | 365 | $entityName = pq($afField)->parents('[af-fieldset]')->attr('af-fieldset'); |
344e8290 | 366 | $joinName = pq($afField)->parents('[af-join]')->attr('af-join'); |
e1aca853 | 367 | if (!$blockEntity && !preg_match(';^[a-zA-Z0-9\_\-\. ]+$;', $entityName)) { |
9384980c TO |
368 | throw new \CRM_Core_Exception("Cannot process $path: malformed entity name ($entityName)"); |
369 | } | |
e1aca853 | 370 | $entityType = $blockEntity ?? $entities[$entityName]['type']; |
344e8290 | 371 | _af_fill_field_metadata($joinName ? $joinName : $entityType, $afField); |
9384980c TO |
372 | } |
373 | }); | |
374 | $angular->add($fieldMetadata); | |
375 | } | |
376 | ||
e1aca853 CW |
377 | /** |
378 | * Merge field definition metadata into an afform field's definition | |
379 | * | |
380 | * @param $entityType | |
381 | * @param DOMElement $afField | |
382 | * @throws API_Exception | |
383 | */ | |
384 | function _af_fill_field_metadata($entityType, DOMElement $afField) { | |
eb4f581a | 385 | $params = [ |
e1aca853 | 386 | 'action' => 'create', |
eb4f581a | 387 | 'where' => [['name', '=', $afField->getAttribute('name')]], |
44f7db4c | 388 | 'select' => ['label', 'input_type', 'input_attrs', 'options'], |
e1aca853 | 389 | 'loadOptions' => TRUE, |
eb4f581a CW |
390 | ]; |
391 | if (in_array($entityType, CRM_Contact_BAO_ContactType::basicTypes(TRUE))) { | |
392 | $params['values'] = ['contact_type' => $entityType]; | |
393 | $entityType = 'Contact'; | |
394 | } | |
395 | $getFields = civicrm_api4($entityType, 'getFields', $params); | |
e1aca853 CW |
396 | // Merge field definition data with whatever's already in the markup |
397 | $deep = ['input_attrs']; | |
398 | foreach ($getFields as $fieldInfo) { | |
399 | $existingFieldDefn = trim(pq($afField)->attr('defn') ?: ''); | |
400 | if ($existingFieldDefn && $existingFieldDefn[0] != '{') { | |
401 | // If it's not an object, don't mess with it. | |
402 | continue; | |
403 | } | |
404 | // TODO: Teach the api to return options in this format | |
405 | if (!empty($fieldInfo['options'])) { | |
406 | $fieldInfo['options'] = CRM_Utils_Array::makeNonAssociative($fieldInfo['options'], 'key', 'label'); | |
407 | } | |
408 | // Default placeholder for select inputs | |
409 | if ($fieldInfo['input_type'] === 'Select') { | |
410 | $fieldInfo['input_attrs'] = ($fieldInfo['input_attrs'] ?? []) + ['placeholder' => ts('Select')]; | |
411 | } | |
412 | ||
413 | $fieldDefn = $existingFieldDefn ? CRM_Utils_JS::getRawProps($existingFieldDefn) : []; | |
414 | foreach ($fieldInfo as $name => $prop) { | |
415 | // Merge array props 1 level deep | |
416 | if (in_array($name, $deep) && !empty($fieldDefn[$name])) { | |
417 | $fieldDefn[$name] = CRM_Utils_JS::writeObject(CRM_Utils_JS::getRawProps($fieldDefn[$name]) + array_map(['CRM_Utils_JS', 'encode'], $prop)); | |
418 | } | |
419 | elseif (!isset($fieldDefn[$name])) { | |
420 | $fieldDefn[$name] = CRM_Utils_JS::encode($prop); | |
421 | } | |
422 | } | |
423 | pq($afField)->attr('defn', htmlspecialchars(CRM_Utils_JS::writeObject($fieldDefn))); | |
424 | } | |
425 | } | |
426 | ||
9384980c TO |
427 | function _afform_getMetadata(phpQueryObject $doc) { |
428 | $entities = []; | |
c37151d2 | 429 | foreach ($doc->find('af-entity') as $afmModelProp) { |
8410442c | 430 | $entities[$afmModelProp->getAttribute('name')] = [ |
bbd6df21 | 431 | 'type' => $afmModelProp->getAttribute('type'), |
9384980c TO |
432 | ]; |
433 | } | |
434 | return $entities; | |
435 | } | |
436 | ||
66aa0f5e TO |
437 | /** |
438 | * Implements hook_civicrm_alterSettingsFolders(). | |
439 | * | |
440 | * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_alterSettingsFolders | |
441 | */ | |
442 | function afform_civicrm_alterSettingsFolders(&$metaDataFolders = NULL) { | |
443 | _afform_civix_civicrm_alterSettingsFolders($metaDataFolders); | |
444 | } | |
445 | ||
446 | /** | |
447 | * Implements hook_civicrm_entityTypes(). | |
448 | * | |
449 | * Declare entity types provided by this module. | |
450 | * | |
451 | * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_entityTypes | |
452 | */ | |
453 | function afform_civicrm_entityTypes(&$entityTypes) { | |
454 | _afform_civix_civicrm_entityTypes($entityTypes); | |
455 | } | |
456 | ||
98f4a7cb TO |
457 | /** |
458 | * Implements hook_civicrm_themes(). | |
459 | */ | |
460 | function afform_civicrm_themes(&$themes) { | |
461 | _afform_civix_civicrm_themes($themes); | |
462 | } | |
463 | ||
bb56ac78 TO |
464 | /** |
465 | * Implements hook_civicrm_buildAsset(). | |
466 | */ | |
467 | function afform_civicrm_buildAsset($asset, $params, &$mimeType, &$content) { | |
468 | if ($asset !== 'afform.js') { | |
469 | return; | |
470 | } | |
471 | ||
472 | if (empty($params['name'])) { | |
473 | throw new RuntimeException("Missing required parameter: afform.js?name=NAME"); | |
474 | } | |
475 | ||
2d4bfef1 | 476 | $moduleName = _afform_angular_module_name($params['name'], 'camel'); |
bb56ac78 TO |
477 | $smarty = CRM_Core_Smarty::singleton(); |
478 | $smarty->assign('afform', [ | |
aa6abb77 | 479 | 'camel' => $moduleName, |
2d4bfef1 | 480 | 'meta' => ['name' => $params['name']], |
aa6abb77 | 481 | 'templateUrl' => "~/$moduleName/$moduleName.aff.html", |
bb56ac78 TO |
482 | ]); |
483 | $mimeType = 'text/javascript'; | |
9ec944f2 | 484 | $content = $smarty->fetch('afform/AfformAngularModule.tpl'); |
bb56ac78 TO |
485 | } |
486 | ||
8775c48a TO |
487 | /** |
488 | * Implements hook_civicrm_alterMenu(). | |
489 | */ | |
490 | function afform_civicrm_alterMenu(&$items) { | |
8f4a0ee9 TO |
491 | if (Civi::container()->has('afform_scanner')) { |
492 | $scanner = Civi::service('afform_scanner'); | |
493 | } | |
494 | else { | |
495 | // During installation... | |
496 | $scanner = new CRM_Afform_AfformScanner(); | |
497 | } | |
8775c48a TO |
498 | foreach ($scanner->getMetas() as $name => $meta) { |
499 | if (!empty($meta['server_route'])) { | |
500 | $items[$meta['server_route']] = [ | |
501 | 'page_callback' => 'CRM_Afform_Page_AfformBase', | |
502 | 'page_arguments' => 'afform=' . urlencode($name), | |
13bdd6d2 | 503 | 'title' => $meta['title'] ?? '', |
f16b2aee | 504 | 'access_arguments' => [["@afform:$name"], 'and'], |
254f01f0 | 505 | 'is_public' => $meta['is_public'], |
8775c48a TO |
506 | ]; |
507 | } | |
508 | } | |
f16b2aee TO |
509 | } |
510 | ||
511 | /** | |
512 | * Implements hook_civicrm_permission_check(). | |
513 | * | |
586344a7 TO |
514 | * This extends the list of permissions available in `CRM_Core_Permission:check()` |
515 | * by introducing virtual-permissions named `@afform:myForm`. The evaluation | |
516 | * of these virtual-permissions is dependent on the settings for `myForm`. | |
517 | * `myForm` may be exposed/integrated through multiple subsystems (routing, | |
518 | * nav-menu, API, etc), and the use of virtual-permissions makes easy to enforce | |
519 | * consistent permissions across any relevant subsystems. | |
520 | * | |
f16b2aee TO |
521 | * @see CRM_Utils_Hook::permission_check() |
522 | */ | |
523 | function afform_civicrm_permission_check($permission, &$granted, $contactId) { | |
14b26ac5 | 524 | if ($permission[0] !== '@') { |
f16b2aee TO |
525 | // Micro-optimization - this function may get hit a lot. |
526 | return; | |
527 | } | |
528 | ||
529 | if (preg_match('/^@afform:(.*)/', $permission, $m)) { | |
530 | $name = $m[1]; | |
531 | ||
2d4bfef1 CW |
532 | $afform = \Civi\Api4\Afform::get() |
533 | ->setCheckPermissions(FALSE) | |
534 | ->addWhere('name', '=', $name) | |
535 | ->setSelect(['permission']) | |
536 | ->execute() | |
537 | ->first(); | |
538 | if ($afform) { | |
539 | $granted = CRM_Core_Permission::check($afform['permission'], $contactId); | |
540 | } | |
f16b2aee | 541 | } |
8775c48a TO |
542 | } |
543 | ||
74f862e4 TO |
544 | /** |
545 | * Clear any local/in-memory caches based on afform data. | |
546 | */ | |
547 | function _afform_clear() { | |
548 | $container = \Civi::container(); | |
549 | $container->get('afform_scanner')->clear(); | |
76b9562a | 550 | $container->get('angular')->clear(); |
74f862e4 TO |
551 | } |
552 | ||
bb56ac78 | 553 | /** |
87dde5eb TO |
554 | * @param string $fileBaseName |
555 | * Ex: foo-bar | |
841850b1 TO |
556 | * @param string $format |
557 | * 'camel' or 'dash'. | |
bb56ac78 | 558 | * @return string |
841850b1 | 559 | * Ex: 'FooBar' or 'foo-bar'. |
3cd5c38b | 560 | * @throws \Exception |
bb56ac78 | 561 | */ |
87dde5eb | 562 | function _afform_angular_module_name($fileBaseName, $format = 'camel') { |
841850b1 TO |
563 | switch ($format) { |
564 | case 'camel': | |
87dde5eb | 565 | $camelCase = ''; |
9c84a124 | 566 | foreach (preg_split('/[-_ ]/', $fileBaseName, NULL, PREG_SPLIT_NO_EMPTY) as $shortNamePart) { |
87dde5eb TO |
567 | $camelCase .= ucfirst($shortNamePart); |
568 | } | |
14b26ac5 | 569 | return strtolower($camelCase[0]) . substr($camelCase, 1); |
841850b1 TO |
570 | |
571 | case 'dash': | |
9c84a124 | 572 | return strtolower(implode('-', preg_split('/[-_ ]|(?=[A-Z])/', $fileBaseName, NULL, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE))); |
841850b1 TO |
573 | |
574 | default: | |
575 | throw new \Exception("Unrecognized format"); | |
576 | } | |
bb56ac78 | 577 | } |