Merge pull request #23173 from braders/escape-on-output-event-links
[civicrm-core.git] / CRM / Core / ManagedEntities.php
CommitLineData
6a488035
TO
1<?php
2
a092536e
EM
3use 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 */
10class 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}