Commit | Line | Data |
---|---|---|
6a488035 TO |
1 | <?php |
2 | ||
a092536e EM |
3 | use Civi\Api4\Managed; |
4 | ||
6a488035 TO |
5 | /** |
6 | * The ManagedEntities system allows modules to add records to the database | |
7 | * declaratively. Those records will be automatically inserted, updated, | |
8 | * deactivated, and deleted in tandem with their modules. | |
9 | */ | |
10 | class CRM_Core_ManagedEntities { | |
1f103dc4 | 11 | |
4b62bc4f EM |
12 | /** |
13 | * Get clean up options. | |
14 | * | |
15 | * @return array | |
16 | */ | |
1f103dc4 | 17 | public static function getCleanupOptions() { |
be2fb01f | 18 | return [ |
1f103dc4 TO |
19 | 'always' => ts('Always'), |
20 | 'never' => ts('Never'), | |
21 | 'unused' => ts('If Unused'), | |
be2fb01f | 22 | ]; |
1f103dc4 TO |
23 | } |
24 | ||
6a488035 | 25 | /** |
88718db2 TO |
26 | * @var array |
27 | * Array($status => array($name => CRM_Core_Module)). | |
6a488035 | 28 | */ |
e9b95545 | 29 | protected $moduleIndex; |
6a488035 | 30 | |
a092536e EM |
31 | /** |
32 | * Actions arising from the managed entities. | |
33 | * | |
34 | * @var array | |
35 | */ | |
36 | protected $managedActions = []; | |
37 | ||
6a488035 | 38 | /** |
88718db2 TO |
39 | * @var array |
40 | * List of all entity declarations. | |
41 | * @see CRM_Utils_Hook::managed() | |
6a488035 | 42 | */ |
e9b95545 | 43 | protected $declarations; |
6a488035 TO |
44 | |
45 | /** | |
d09edf64 | 46 | * Get an instance. |
ba3228d1 EM |
47 | * @param bool $fresh |
48 | * @return \CRM_Core_ManagedEntities | |
6a488035 TO |
49 | */ |
50 | public static function singleton($fresh = FALSE) { | |
51 | static $singleton; | |
52 | if ($fresh || !$singleton) { | |
8cb5ca10 | 53 | $singleton = new CRM_Core_ManagedEntities(CRM_Core_Module::getAll()); |
6a488035 TO |
54 | } |
55 | return $singleton; | |
56 | } | |
57 | ||
9c19292b TO |
58 | /** |
59 | * Perform an asynchronous reconciliation when the transaction ends. | |
60 | */ | |
ba3228d1 | 61 | public static function scheduleReconciliation() { |
9c19292b TO |
62 | CRM_Core_Transaction::addCallback( |
63 | CRM_Core_Transaction::PHASE_POST_COMMIT, | |
64 | function () { | |
e70a7fc0 | 65 | CRM_Core_ManagedEntities::singleton(TRUE)->reconcile(); |
9c19292b | 66 | }, |
be2fb01f | 67 | [], |
9c19292b TO |
68 | 'ManagedEntities::reconcile' |
69 | ); | |
70 | } | |
71 | ||
6a488035 | 72 | /** |
6a0b768e TO |
73 | * @param array $modules |
74 | * CRM_Core_Module. | |
6a488035 | 75 | */ |
8cb5ca10 | 76 | public function __construct(array $modules) { |
85917c3b | 77 | $this->moduleIndex = $this->createModuleIndex($modules); |
6a488035 TO |
78 | } |
79 | ||
80 | /** | |
88718db2 | 81 | * Read a managed entity using APIv3. |
378e2654 | 82 | * |
a092536e EM |
83 | * @deprecated |
84 | * | |
88718db2 TO |
85 | * @param string $moduleName |
86 | * The name of the module which declared entity. | |
87 | * @param string $name | |
88 | * The symbolic name of the entity. | |
72b3a70c CW |
89 | * @return array|NULL |
90 | * API representation, or NULL if the entity does not exist | |
6a488035 TO |
91 | */ |
92 | public function get($moduleName, $name) { | |
93 | $dao = new CRM_Core_DAO_Managed(); | |
94 | $dao->module = $moduleName; | |
95 | $dao->name = $name; | |
96 | if ($dao->find(TRUE)) { | |
be2fb01f | 97 | $params = [ |
6a488035 | 98 | 'id' => $dao->entity_id, |
be2fb01f | 99 | ]; |
bbf66e9c | 100 | $result = NULL; |
637ea2cf E |
101 | try { |
102 | $result = civicrm_api3($dao->entity_type, 'getsingle', $params); | |
103 | } | |
104 | catch (Exception $e) { | |
e9b95545 | 105 | $this->onApiError($dao->entity_type, 'getsingle', $params, $result); |
6a488035 | 106 | } |
637ea2cf | 107 | return $result; |
0db6c3e1 TO |
108 | } |
109 | else { | |
6a488035 TO |
110 | return NULL; |
111 | } | |
112 | } | |
113 | ||
88718db2 TO |
114 | /** |
115 | * Identify any enabled/disabled modules. Add new entities, update | |
116 | * existing entities, and remove orphaned (stale) entities. | |
8cb5ca10 | 117 | * |
912511a3 | 118 | * @param bool $ignoreUpgradeMode |
221b40df | 119 | * Unused. |
8cb5ca10 | 120 | * @throws \CRM_Core_Exception |
88718db2 | 121 | */ |
912511a3 | 122 | public function reconcile($ignoreUpgradeMode = FALSE) { |
f9ee37c9 | 123 | $this->loadDeclarations(); |
e9b95545 | 124 | if ($error = $this->validate($this->getDeclarations())) { |
8cb5ca10 | 125 | throw new CRM_Core_Exception($error); |
6a488035 | 126 | } |
a092536e | 127 | $this->loadManagedEntityActions(); |
6a488035 TO |
128 | $this->reconcileEnabledModules(); |
129 | $this->reconcileDisabledModules(); | |
130 | $this->reconcileUnknownModules(); | |
131 | } | |
132 | ||
9626d0a1 CW |
133 | /** |
134 | * Force-revert a record back to its original state. | |
135 | * @param array $params | |
136 | * Key->value properties of CRM_Core_DAO_Managed used to match an existing record | |
137 | */ | |
138 | public function revert(array $params) { | |
139 | $mgd = new \CRM_Core_DAO_Managed(); | |
140 | $mgd->copyValues($params); | |
141 | $mgd->find(TRUE); | |
3bbce6e9 | 142 | $this->loadDeclarations(); |
9626d0a1 CW |
143 | $declarations = CRM_Utils_Array::findAll($this->declarations, [ |
144 | 'module' => $mgd->module, | |
145 | 'name' => $mgd->name, | |
146 | 'entity' => $mgd->entity_type, | |
147 | ]); | |
148 | if ($mgd->id && isset($declarations[0])) { | |
149 | $this->updateExistingEntity($mgd, ['update' => 'always'] + $declarations[0]); | |
150 | return TRUE; | |
151 | } | |
152 | return FALSE; | |
153 | } | |
154 | ||
88718db2 TO |
155 | /** |
156 | * For all enabled modules, add new entities, update | |
157 | * existing entities, and remove orphaned (stale) entities. | |
88718db2 | 158 | */ |
5c095937 | 159 | protected function reconcileEnabledModules(): void { |
6a488035 TO |
160 | // Note: any thing currently declared is necessarily from |
161 | // an active module -- because we got it from a hook! | |
162 | ||
163 | // index by moduleName,name | |
85917c3b | 164 | $decls = $this->createDeclarationIndex($this->moduleIndex, $this->getDeclarations()); |
6a488035 | 165 | foreach ($decls as $moduleName => $todos) { |
9cf68fcc | 166 | if ($this->isModuleEnabled($moduleName)) { |
a092536e | 167 | $this->reconcileEnabledModule($moduleName); |
0db6c3e1 | 168 | } |
6a488035 TO |
169 | } |
170 | } | |
171 | ||
172 | /** | |
88718db2 TO |
173 | * For one enabled module, add new entities, update existing entities, |
174 | * and remove orphaned (stale) entities. | |
6a488035 | 175 | * |
a092536e | 176 | * @param string $module |
6a488035 | 177 | */ |
5c095937 | 178 | protected function reconcileEnabledModule(string $module): void { |
a092536e EM |
179 | foreach ($this->getManagedEntitiesToUpdate(['module' => $module]) as $todo) { |
180 | $dao = new CRM_Core_DAO_Managed(); | |
181 | $dao->module = $todo['module']; | |
182 | $dao->name = $todo['name']; | |
183 | $dao->entity_type = $todo['entity_type']; | |
184 | $dao->entity_id = $todo['entity_id']; | |
095e8ae4 | 185 | $dao->entity_modified_date = $todo['entity_modified_date']; |
a092536e EM |
186 | $dao->id = $todo['id']; |
187 | $this->updateExistingEntity($dao, $todo); | |
6a488035 TO |
188 | } |
189 | ||
a092536e EM |
190 | foreach ($this->getManagedEntitiesToDelete(['module' => $module]) as $todo) { |
191 | $dao = new CRM_Core_DAO_Managed(); | |
192 | $dao->module = $todo['module']; | |
193 | $dao->name = $todo['name']; | |
194 | $dao->entity_type = $todo['entity_type']; | |
195 | $dao->id = $todo['id']; | |
196 | $dao->cleanup = $todo['cleanup']; | |
197 | $dao->entity_id = $todo['entity_id']; | |
198 | $this->removeStaleEntity($dao); | |
199 | } | |
200 | foreach ($this->getManagedEntitiesToCreate(['module' => $module]) as $todo) { | |
7cddb4ae | 201 | $this->insertNewEntity($todo); |
6a488035 TO |
202 | } |
203 | } | |
204 | ||
a092536e EM |
205 | /** |
206 | * Get the managed entities to be created. | |
207 | * | |
208 | * @param array $filters | |
209 | * | |
210 | * @return array | |
211 | */ | |
212 | protected function getManagedEntitiesToCreate(array $filters = []): array { | |
213 | return $this->getManagedEntities(array_merge($filters, ['managed_action' => 'create'])); | |
214 | } | |
215 | ||
216 | /** | |
9626d0a1 | 217 | * Get the managed entities to be updated. |
a092536e EM |
218 | * |
219 | * @param array $filters | |
220 | * | |
221 | * @return array | |
222 | */ | |
223 | protected function getManagedEntitiesToUpdate(array $filters = []): array { | |
224 | return $this->getManagedEntities(array_merge($filters, ['managed_action' => 'update'])); | |
225 | } | |
226 | ||
227 | /** | |
228 | * Get the managed entities to be deleted. | |
229 | * | |
230 | * @param array $filters | |
231 | * | |
232 | * @return array | |
233 | */ | |
234 | protected function getManagedEntitiesToDelete(array $filters = []): array { | |
095e8ae4 CW |
235 | // Return array in reverse-order so that child entities are cleaned up before their parents |
236 | return array_reverse($this->getManagedEntities(array_merge($filters, ['managed_action' => 'delete']))); | |
a092536e EM |
237 | } |
238 | ||
239 | /** | |
240 | * Get the managed entities that fit the criteria. | |
241 | * | |
242 | * @param array $filters | |
243 | * | |
244 | * @return array | |
245 | */ | |
246 | protected function getManagedEntities(array $filters = []): array { | |
247 | $return = []; | |
248 | foreach ($this->managedActions as $actionKey => $action) { | |
249 | foreach ($filters as $filterKey => $filterValue) { | |
250 | if ($action[$filterKey] !== $filterValue) { | |
251 | continue 2; | |
252 | } | |
253 | } | |
254 | $return[$actionKey] = $action; | |
255 | } | |
256 | return $return; | |
257 | } | |
258 | ||
88718db2 TO |
259 | /** |
260 | * For all disabled modules, disable any managed entities. | |
261 | */ | |
5c095937 | 262 | protected function reconcileDisabledModules() { |
6a488035 TO |
263 | if (empty($this->moduleIndex[FALSE])) { |
264 | return; | |
265 | } | |
266 | ||
267 | $in = CRM_Core_DAO::escapeStrings(array_keys($this->moduleIndex[FALSE])); | |
268 | $dao = new CRM_Core_DAO_Managed(); | |
269 | $dao->whereAdd("module in ($in)"); | |
04b08baa | 270 | $dao->orderBy('id DESC'); |
6a488035 TO |
271 | $dao->find(); |
272 | while ($dao->fetch()) { | |
7cddb4ae TO |
273 | $this->disableEntity($dao); |
274 | ||
6a488035 TO |
275 | } |
276 | } | |
277 | ||
88718db2 TO |
278 | /** |
279 | * Remove any orphaned (stale) entities that are linked to | |
280 | * unknown modules. | |
281 | */ | |
5c095937 | 282 | protected function reconcileUnknownModules() { |
be2fb01f | 283 | $knownModules = []; |
6a488035 TO |
284 | if (array_key_exists(0, $this->moduleIndex) && is_array($this->moduleIndex[0])) { |
285 | $knownModules = array_merge($knownModules, array_keys($this->moduleIndex[0])); | |
286 | } | |
287 | if (array_key_exists(1, $this->moduleIndex) && is_array($this->moduleIndex[1])) { | |
288 | $knownModules = array_merge($knownModules, array_keys($this->moduleIndex[1])); | |
6a488035 TO |
289 | } |
290 | ||
291 | $dao = new CRM_Core_DAO_Managed(); | |
292 | if (!empty($knownModules)) { | |
293 | $in = CRM_Core_DAO::escapeStrings($knownModules); | |
294 | $dao->whereAdd("module NOT IN ($in)"); | |
04b08baa | 295 | $dao->orderBy('id DESC'); |
6a488035 TO |
296 | } |
297 | $dao->find(); | |
298 | while ($dao->fetch()) { | |
bbf66e9c TO |
299 | $this->removeStaleEntity($dao); |
300 | } | |
301 | } | |
6a488035 | 302 | |
7cddb4ae | 303 | /** |
88718db2 | 304 | * Create a new entity. |
7cddb4ae | 305 | * |
6a0b768e TO |
306 | * @param array $todo |
307 | * Entity specification (per hook_civicrm_managedEntities). | |
7cddb4ae | 308 | */ |
d80c6631 | 309 | protected function insertNewEntity($todo) { |
f43ca71f CW |
310 | $params = $todo['params']; |
311 | // APIv4 | |
312 | if ($params['version'] == 4) { | |
313 | $params['checkPermissions'] = FALSE; | |
314 | // Use "save" instead of "create" action to accommodate a "match" param | |
315 | $params['records'] = [$params['values']]; | |
316 | unset($params['values']); | |
317 | $result = civicrm_api4($todo['entity_type'], 'save', $params); | |
318 | $id = $result->first()['id']; | |
9626d0a1 | 319 | } |
f43ca71f CW |
320 | // APIv3 |
321 | else { | |
322 | $result = civicrm_api($todo['entity_type'], 'create', $params); | |
323 | if (!empty($result['is_error'])) { | |
324 | $this->onApiError($todo['entity_type'], 'create', $params, $result); | |
325 | } | |
326 | $id = $result['id']; | |
7cddb4ae TO |
327 | } |
328 | ||
329 | $dao = new CRM_Core_DAO_Managed(); | |
330 | $dao->module = $todo['module']; | |
331 | $dao->name = $todo['name']; | |
a092536e | 332 | $dao->entity_type = $todo['entity_type']; |
f43ca71f | 333 | $dao->entity_id = $id; |
9c1bc317 | 334 | $dao->cleanup = $todo['cleanup'] ?? NULL; |
7cddb4ae TO |
335 | $dao->save(); |
336 | } | |
337 | ||
338 | /** | |
20429eb9 | 339 | * Update an entity which is believed to exist. |
7cddb4ae TO |
340 | * |
341 | * @param CRM_Core_DAO_Managed $dao | |
6a0b768e TO |
342 | * @param array $todo |
343 | * Entity specification (per hook_civicrm_managedEntities). | |
7cddb4ae | 344 | */ |
5c095937 | 345 | protected function updateExistingEntity($dao, $todo) { |
69e13f9b | 346 | $policy = $todo['update'] ?? 'always'; |
35c1c211 | 347 | $doUpdate = ($policy === 'always'); |
0dd54586 | 348 | |
095e8ae4 CW |
349 | if ($policy === 'unmodified') { |
350 | // If this is not an APIv4 managed entity, the entity_modidfied_date will always be null | |
351 | if (!CRM_Core_BAO_Managed::isApi4ManagedType($dao->entity_type)) { | |
352 | Civi::log()->warning('ManagedEntity update policy "unmodified" specified for entity type ' . $dao->entity_type . ' which is not an APIv4 ManagedEntity. Falling back to policy "always".'); | |
353 | } | |
354 | $doUpdate = empty($dao->entity_modified_date); | |
355 | } | |
356 | ||
9626d0a1 | 357 | if ($doUpdate && $todo['params']['version'] == 3) { |
3cf02556 TO |
358 | $defaults = ['id' => $dao->entity_id]; |
359 | if ($this->isActivationSupported($dao->entity_type)) { | |
360 | $defaults['is_active'] = 1; | |
361 | } | |
0dd54586 | 362 | $params = array_merge($defaults, $todo['params']); |
20429eb9 RL |
363 | |
364 | $manager = CRM_Extension_System::singleton()->getManager(); | |
365 | if ($dao->entity_type === 'Job' && !$manager->extensionIsBeingInstalledOrEnabled($dao->module)) { | |
366 | // Special treatment for scheduled jobs: | |
367 | // | |
368 | // If we're being called as part of enabling/installing a module then | |
369 | // we want the default behaviour of setting is_active = 1. | |
370 | // | |
371 | // However, if we're just being called by a normal cache flush then we | |
372 | // should not re-enable a job that an administrator has decided to disable. | |
373 | // | |
374 | // Without this logic there was a problem: site admin might disable | |
375 | // a job, but then when there was a flush op, the job was re-enabled | |
376 | // which can cause significant embarrassment, depending on the job | |
377 | // ("Don't worry, sending mailings is disabled right now..."). | |
378 | unset($params['is_active']); | |
379 | } | |
380 | ||
0dd54586 TO |
381 | $result = civicrm_api($dao->entity_type, 'create', $params); |
382 | if ($result['is_error']) { | |
353ffa53 | 383 | $this->onApiError($dao->entity_type, 'create', $params, $result); |
0dd54586 | 384 | } |
7cddb4ae | 385 | } |
9626d0a1 CW |
386 | elseif ($doUpdate && $todo['params']['version'] == 4) { |
387 | $params = ['checkPermissions' => FALSE] + $todo['params']; | |
388 | $params['values']['id'] = $dao->entity_id; | |
f43ca71f CW |
389 | // 'match' param doesn't apply to "update" action |
390 | unset($params['match']); | |
9626d0a1 CW |
391 | civicrm_api4($dao->entity_type, 'update', $params); |
392 | } | |
1f103dc4 | 393 | |
69e13f9b CW |
394 | if (isset($todo['cleanup']) || $doUpdate) { |
395 | $dao->cleanup = $todo['cleanup'] ?? NULL; | |
396 | // Reset the `entity_modified_date` timestamp if reverting record. | |
397 | $dao->entity_modified_date = $doUpdate ? 'null' : NULL; | |
1f103dc4 TO |
398 | $dao->update(); |
399 | } | |
7cddb4ae TO |
400 | } |
401 | ||
402 | /** | |
403 | * Update an entity which (a) is believed to exist and which (b) ought to be | |
404 | * inactive. | |
405 | * | |
406 | * @param CRM_Core_DAO_Managed $dao | |
8b91d849 | 407 | * |
408 | * @throws \CiviCRM_API3_Exception | |
7cddb4ae | 409 | */ |
d80c6631 | 410 | protected function disableEntity($dao): void { |
fc625166 TO |
411 | $entity_type = $dao->entity_type; |
412 | if ($this->isActivationSupported($entity_type)) { | |
7cddb4ae | 413 | // FIXME cascading for payproc types? |
be2fb01f | 414 | $params = [ |
7cddb4ae TO |
415 | 'version' => 3, |
416 | 'id' => $dao->entity_id, | |
417 | 'is_active' => 0, | |
be2fb01f | 418 | ]; |
7cddb4ae TO |
419 | $result = civicrm_api($dao->entity_type, 'create', $params); |
420 | if ($result['is_error']) { | |
353ffa53 | 421 | $this->onApiError($dao->entity_type, 'create', $params, $result); |
7cddb4ae | 422 | } |
69e13f9b CW |
423 | // Reset the `entity_modified_date` timestamp to indicate that the entity has not been modified by the user. |
424 | $dao->entity_modified_date = 'null'; | |
425 | $dao->update(); | |
7cddb4ae TO |
426 | } |
427 | } | |
428 | ||
bbf66e9c | 429 | /** |
88718db2 | 430 | * Remove a stale entity (if policy allows). |
bbf66e9c TO |
431 | * |
432 | * @param CRM_Core_DAO_Managed $dao | |
a092536e | 433 | * @throws CRM_Core_Exception |
bbf66e9c | 434 | */ |
d80c6631 | 435 | protected function removeStaleEntity($dao) { |
1f103dc4 | 436 | $policy = empty($dao->cleanup) ? 'always' : $dao->cleanup; |
378e2654 TO |
437 | switch ($policy) { |
438 | case 'always': | |
439 | $doDelete = TRUE; | |
440 | break; | |
ea100cb5 | 441 | |
378e2654 TO |
442 | case 'never': |
443 | $doDelete = FALSE; | |
444 | break; | |
ea100cb5 | 445 | |
378e2654 | 446 | case 'unused': |
095e8ae4 CW |
447 | if (CRM_Core_BAO_Managed::isApi4ManagedType($dao->entity_type)) { |
448 | $getRefCount = \Civi\Api4\Utils\CoreUtil::getRefCount($dao->entity_type, $dao->entity_id); | |
449 | } | |
450 | else { | |
451 | $getRefCount = civicrm_api3($dao->entity_type, 'getrefcount', [ | |
452 | 'id' => $dao->entity_id, | |
453 | ])['values']; | |
454 | } | |
378e2654 | 455 | |
095e8ae4 | 456 | // FIXME: This extra counting should be unnecessary, because getRefCount only returns values if count > 0 |
378e2654 | 457 | $total = 0; |
095e8ae4 | 458 | foreach ($getRefCount as $refCount) { |
378e2654 TO |
459 | $total += $refCount['count']; |
460 | } | |
461 | ||
462 | $doDelete = ($total == 0); | |
463 | break; | |
ea100cb5 | 464 | |
378e2654 | 465 | default: |
a092536e | 466 | throw new CRM_Core_Exception('Unrecognized cleanup policy: ' . $policy); |
378e2654 | 467 | } |
bbf66e9c | 468 | |
a2bf5923 CW |
469 | // APIv4 delete - deletion from `civicrm_managed` will be taken care of by |
470 | // CRM_Core_BAO_Managed::on_hook_civicrm_post() | |
471 | if ($doDelete && CRM_Core_BAO_Managed::isApi4ManagedType($dao->entity_type)) { | |
472 | civicrm_api4($dao->entity_type, 'delete', [ | |
081629dc | 473 | 'checkPermissions' => FALSE, |
a2bf5923 CW |
474 | 'where' => [['id', '=', $dao->entity_id]], |
475 | ]); | |
476 | } | |
477 | // APIv3 delete | |
478 | elseif ($doDelete) { | |
be2fb01f | 479 | $params = [ |
1f103dc4 TO |
480 | 'version' => 3, |
481 | 'id' => $dao->entity_id, | |
be2fb01f | 482 | ]; |
a60c0bc8 | 483 | $check = civicrm_api3($dao->entity_type, 'get', $params); |
a2bf5923 | 484 | if ($check['count']) { |
a60c0bc8 SL |
485 | $result = civicrm_api($dao->entity_type, 'delete', $params); |
486 | if ($result['is_error']) { | |
9f4f065a MW |
487 | if (isset($dao->name)) { |
488 | $params['name'] = $dao->name; | |
489 | } | |
a60c0bc8 SL |
490 | $this->onApiError($dao->entity_type, 'delete', $params, $result); |
491 | } | |
a60c0bc8 | 492 | } |
be2fb01f CW |
493 | CRM_Core_DAO::executeQuery('DELETE FROM civicrm_managed WHERE id = %1', [ |
494 | 1 => [$dao->id, 'Integer'], | |
495 | ]); | |
1f103dc4 | 496 | } |
6a488035 TO |
497 | } |
498 | ||
2e2605fe EM |
499 | /** |
500 | * Get declarations. | |
501 | * | |
502 | * @return array|null | |
503 | */ | |
d80c6631 | 504 | protected function getDeclarations() { |
e9b95545 TO |
505 | return $this->declarations; |
506 | } | |
507 | ||
6a488035 | 508 | /** |
88718db2 TO |
509 | * @param array $modules |
510 | * Array<CRM_Core_Module>. | |
77b97be7 | 511 | * |
a6c01b45 CW |
512 | * @return array |
513 | * indexed by is_active,name | |
6a488035 | 514 | */ |
85917c3b | 515 | protected function createModuleIndex($modules) { |
be2fb01f | 516 | $result = []; |
6a488035 TO |
517 | foreach ($modules as $module) { |
518 | $result[$module->is_active][$module->name] = $module; | |
519 | } | |
520 | return $result; | |
521 | } | |
522 | ||
523 | /** | |
88718db2 TO |
524 | * @param array $moduleIndex |
525 | * @param array $declarations | |
77b97be7 | 526 | * |
a6c01b45 CW |
527 | * @return array |
528 | * indexed by module,name | |
6a488035 | 529 | */ |
85917c3b | 530 | protected function createDeclarationIndex($moduleIndex, $declarations) { |
be2fb01f | 531 | $result = []; |
6a488035 TO |
532 | if (!isset($moduleIndex[TRUE])) { |
533 | return $result; | |
534 | } | |
535 | foreach ($moduleIndex[TRUE] as $moduleName => $module) { | |
536 | if ($module->is_active) { | |
537 | // need an empty array() for all active modules, even if there are no current $declarations | |
be2fb01f | 538 | $result[$moduleName] = []; |
6a488035 TO |
539 | } |
540 | } | |
541 | foreach ($declarations as $declaration) { | |
542 | $result[$declaration['module']][$declaration['name']] = $declaration; | |
543 | } | |
544 | return $result; | |
545 | } | |
546 | ||
547 | /** | |
fa3fdebc | 548 | * @param array $declarations |
fd31fa4c | 549 | * |
72b3a70c CW |
550 | * @return string|bool |
551 | * string on error, or FALSE | |
6a488035 | 552 | */ |
85917c3b | 553 | protected function validate($declarations) { |
9cf68fcc | 554 | foreach ($declarations as $module => $declare) { |
be2fb01f | 555 | foreach (['name', 'module', 'entity', 'params'] as $key) { |
6a488035 TO |
556 | if (empty($declare[$key])) { |
557 | $str = print_r($declare, TRUE); | |
9cf68fcc | 558 | return ts('Managed Entity (%1) is missing field "%2": %3', [$module, $key, $str]); |
6a488035 TO |
559 | } |
560 | } | |
9cf68fcc EM |
561 | if (!$this->isModuleRecognised($declare['module'])) { |
562 | return ts('Entity declaration references invalid or inactive module name [%1]', [$declare['module']]); | |
563 | } | |
6a488035 TO |
564 | } |
565 | return FALSE; | |
566 | } | |
567 | ||
9cf68fcc EM |
568 | /** |
569 | * Is the module recognised (as an enabled or disabled extension in the system). | |
570 | * | |
571 | * @param string $module | |
572 | * | |
573 | * @return bool | |
574 | */ | |
575 | protected function isModuleRecognised(string $module): bool { | |
576 | return $this->isModuleDisabled($module) || $this->isModuleEnabled($module); | |
577 | } | |
578 | ||
579 | /** | |
580 | * Is the module enabled. | |
581 | * | |
582 | * @param string $module | |
583 | * | |
584 | * @return bool | |
585 | */ | |
586 | protected function isModuleEnabled(string $module): bool { | |
587 | return isset($this->moduleIndex[TRUE][$module]); | |
588 | } | |
589 | ||
590 | /** | |
591 | * Is the module disabled. | |
592 | * | |
593 | * @param string $module | |
594 | * | |
595 | * @return bool | |
596 | */ | |
597 | protected function isModuleDisabled(string $module): bool { | |
598 | return isset($this->moduleIndex[FALSE][$module]); | |
599 | } | |
600 | ||
a0ee3941 | 601 | /** |
72b3a70c | 602 | * @param array $declarations |
a0ee3941 | 603 | * |
72b3a70c | 604 | * @return array |
a0ee3941 | 605 | */ |
85917c3b | 606 | protected function cleanDeclarations(array $declarations): array { |
6a488035 TO |
607 | foreach ($declarations as $name => &$declare) { |
608 | if (!array_key_exists('name', $declare)) { | |
609 | $declare['name'] = $name; | |
610 | } | |
611 | } | |
612 | return $declarations; | |
613 | } | |
614 | ||
a0ee3941 | 615 | /** |
e9b95545 TO |
616 | * @param string $entity |
617 | * @param string $action | |
618 | * @param array $params | |
619 | * @param array $result | |
a0ee3941 EM |
620 | * |
621 | * @throws Exception | |
622 | */ | |
e9b95545 | 623 | protected function onApiError($entity, $action, $params, $result) { |
be2fb01f | 624 | CRM_Core_Error::debug_var('ManagedEntities_failed', [ |
e9b95545 TO |
625 | 'entity' => $entity, |
626 | 'action' => $action, | |
6a488035 TO |
627 | 'params' => $params, |
628 | 'result' => $result, | |
be2fb01f | 629 | ]); |
35c1c211 | 630 | throw new Exception('API error: ' . $result['error_message'] . ' on ' . $entity . '.' . $action |
9f4f065a | 631 | . (!empty($params['name']) ? '( entity name ' . $params['name'] . ')' : '') |
35c1c211 | 632 | ); |
cbb7c7e0 | 633 | } |
96025800 | 634 | |
fc625166 TO |
635 | /** |
636 | * Determine if an entity supports APIv3-based activation/de-activation. | |
637 | * @param string $entity_type | |
638 | * | |
639 | * @return bool | |
640 | * @throws \CiviCRM_API3_Exception | |
641 | */ | |
642 | private function isActivationSupported(string $entity_type): bool { | |
643 | if (!isset(Civi::$statics[__CLASS__][__FUNCTION__][$entity_type])) { | |
644 | $actions = civicrm_api3($entity_type, 'getactions', [])['values']; | |
645 | Civi::$statics[__CLASS__][__FUNCTION__][$entity_type] = FALSE; | |
646 | if (in_array('create', $actions, TRUE) && in_array('getfields', $actions)) { | |
647 | $fields = civicrm_api3($entity_type, 'getfields', ['action' => 'create'])['values']; | |
648 | Civi::$statics[__CLASS__][__FUNCTION__][$entity_type] = array_key_exists('is_active', $fields); | |
649 | } | |
650 | } | |
651 | return Civi::$statics[__CLASS__][__FUNCTION__][$entity_type]; | |
652 | } | |
653 | ||
78ce6ebb EM |
654 | /** |
655 | * Load declarations into the class property. | |
656 | * | |
657 | * This picks it up from hooks and enabled components. | |
658 | */ | |
659 | protected function loadDeclarations(): void { | |
660 | $this->declarations = []; | |
661 | foreach (CRM_Core_Component::getEnabledComponents() as $component) { | |
662 | $this->declarations = array_merge($this->declarations, $component->getManagedEntities()); | |
663 | } | |
664 | CRM_Utils_Hook::managed($this->declarations); | |
665 | $this->declarations = $this->cleanDeclarations($this->declarations); | |
666 | } | |
667 | ||
a092536e EM |
668 | protected function loadManagedEntityActions(): void { |
669 | $managedEntities = Managed::get(FALSE)->addSelect('*')->execute(); | |
cc9b2a95 | 670 | $this->managedActions = []; |
a092536e EM |
671 | foreach ($managedEntities as $managedEntity) { |
672 | $key = "{$managedEntity['module']}_{$managedEntity['name']}_{$managedEntity['entity_type']}"; | |
673 | // Set to 'delete' - it will be overwritten below if it is to be updated. | |
674 | $action = 'delete'; | |
675 | $this->managedActions[$key] = array_merge($managedEntity, ['managed_action' => $action]); | |
676 | } | |
677 | foreach ($this->declarations as $declaration) { | |
678 | $key = "{$declaration['module']}_{$declaration['name']}_{$declaration['entity']}"; | |
679 | if (isset($this->managedActions[$key])) { | |
680 | $this->managedActions[$key]['params'] = $declaration['params']; | |
681 | $this->managedActions[$key]['managed_action'] = 'update'; | |
682 | $this->managedActions[$key]['cleanup'] = $declaration['cleanup'] ?? NULL; | |
683 | $this->managedActions[$key]['update'] = $declaration['update'] ?? 'always'; | |
684 | } | |
685 | else { | |
686 | $this->managedActions[$key] = [ | |
687 | 'module' => $declaration['module'], | |
688 | 'name' => $declaration['name'], | |
689 | 'entity_type' => $declaration['entity'], | |
690 | 'managed_action' => 'create', | |
691 | 'params' => $declaration['params'], | |
692 | 'cleanup' => $declaration['cleanup'] ?? NULL, | |
693 | 'update' => $declaration['update'] ?? 'always', | |
694 | ]; | |
695 | } | |
696 | } | |
697 | } | |
698 | ||
6a488035 | 699 | } |