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 | |
6a488035 | 31 | /** |
d09edf64 | 32 | * Get an instance. |
ba3228d1 EM |
33 | * @param bool $fresh |
34 | * @return \CRM_Core_ManagedEntities | |
6a488035 TO |
35 | */ |
36 | public static function singleton($fresh = FALSE) { | |
37 | static $singleton; | |
38 | if ($fresh || !$singleton) { | |
8cb5ca10 | 39 | $singleton = new CRM_Core_ManagedEntities(CRM_Core_Module::getAll()); |
6a488035 TO |
40 | } |
41 | return $singleton; | |
42 | } | |
43 | ||
9c19292b TO |
44 | /** |
45 | * Perform an asynchronous reconciliation when the transaction ends. | |
46 | */ | |
ba3228d1 | 47 | public static function scheduleReconciliation() { |
9c19292b TO |
48 | CRM_Core_Transaction::addCallback( |
49 | CRM_Core_Transaction::PHASE_POST_COMMIT, | |
50 | function () { | |
e70a7fc0 | 51 | CRM_Core_ManagedEntities::singleton(TRUE)->reconcile(); |
9c19292b | 52 | }, |
be2fb01f | 53 | [], |
9c19292b TO |
54 | 'ManagedEntities::reconcile' |
55 | ); | |
56 | } | |
57 | ||
6a488035 | 58 | /** |
6a0b768e TO |
59 | * @param array $modules |
60 | * CRM_Core_Module. | |
6a488035 | 61 | */ |
8cb5ca10 | 62 | public function __construct(array $modules) { |
85917c3b | 63 | $this->moduleIndex = $this->createModuleIndex($modules); |
6a488035 TO |
64 | } |
65 | ||
66 | /** | |
88718db2 | 67 | * Read a managed entity using APIv3. |
378e2654 | 68 | * |
a092536e EM |
69 | * @deprecated |
70 | * | |
88718db2 TO |
71 | * @param string $moduleName |
72 | * The name of the module which declared entity. | |
73 | * @param string $name | |
74 | * The symbolic name of the entity. | |
72b3a70c CW |
75 | * @return array|NULL |
76 | * API representation, or NULL if the entity does not exist | |
6a488035 TO |
77 | */ |
78 | public function get($moduleName, $name) { | |
79 | $dao = new CRM_Core_DAO_Managed(); | |
80 | $dao->module = $moduleName; | |
81 | $dao->name = $name; | |
82 | if ($dao->find(TRUE)) { | |
be2fb01f | 83 | $params = [ |
6a488035 | 84 | 'id' => $dao->entity_id, |
be2fb01f | 85 | ]; |
bbf66e9c | 86 | $result = NULL; |
637ea2cf E |
87 | try { |
88 | $result = civicrm_api3($dao->entity_type, 'getsingle', $params); | |
89 | } | |
90 | catch (Exception $e) { | |
e9b95545 | 91 | $this->onApiError($dao->entity_type, 'getsingle', $params, $result); |
6a488035 | 92 | } |
637ea2cf | 93 | return $result; |
0db6c3e1 TO |
94 | } |
95 | else { | |
6a488035 TO |
96 | return NULL; |
97 | } | |
98 | } | |
99 | ||
88718db2 TO |
100 | /** |
101 | * Identify any enabled/disabled modules. Add new entities, update | |
102 | * existing entities, and remove orphaned (stale) entities. | |
8cb5ca10 | 103 | * |
a7db5554 CW |
104 | * @param array $modules |
105 | * Limits scope of reconciliation to specific module(s). | |
8cb5ca10 | 106 | * @throws \CRM_Core_Exception |
88718db2 | 107 | */ |
a7db5554 CW |
108 | public function reconcile($modules = NULL) { |
109 | $modules = $modules ? (array) $modules : NULL; | |
12485e4f CW |
110 | $declarations = $this->getDeclarations($modules); |
111 | $plan = $this->createPlan($declarations, $modules); | |
13169d44 | 112 | $this->reconcileEntities($plan); |
6a488035 TO |
113 | } |
114 | ||
9626d0a1 CW |
115 | /** |
116 | * Force-revert a record back to its original state. | |
117 | * @param array $params | |
118 | * Key->value properties of CRM_Core_DAO_Managed used to match an existing record | |
119 | */ | |
120 | public function revert(array $params) { | |
121 | $mgd = new \CRM_Core_DAO_Managed(); | |
122 | $mgd->copyValues($params); | |
123 | $mgd->find(TRUE); | |
12485e4f CW |
124 | $declarations = $this->getDeclarations([$mgd->module]); |
125 | $declarations = CRM_Utils_Array::findAll($declarations, [ | |
9626d0a1 CW |
126 | 'module' => $mgd->module, |
127 | 'name' => $mgd->name, | |
128 | 'entity' => $mgd->entity_type, | |
129 | ]); | |
130 | if ($mgd->id && isset($declarations[0])) { | |
a7db5554 | 131 | $this->updateExistingEntity(['update' => 'always'] + $declarations[0] + $mgd->toArray()); |
9626d0a1 CW |
132 | return TRUE; |
133 | } | |
134 | return FALSE; | |
135 | } | |
136 | ||
88718db2 | 137 | /** |
a7db5554 | 138 | * Take appropriate action on every managed entity. |
13169d44 CW |
139 | * |
140 | * @param array[] $plan | |
88718db2 | 141 | */ |
13169d44 CW |
142 | private function reconcileEntities(array $plan): void { |
143 | foreach ($this->filterPlanByAction($plan, 'update') as $item) { | |
a7db5554 | 144 | $this->updateExistingEntity($item); |
6a488035 | 145 | } |
a7db5554 | 146 | // reverse-order so that child entities are cleaned up before their parents |
13169d44 | 147 | foreach (array_reverse($this->filterPlanByAction($plan, 'delete')) as $item) { |
a7db5554 | 148 | $this->removeStaleEntity($item); |
6a488035 | 149 | } |
13169d44 | 150 | foreach ($this->filterPlanByAction($plan, 'create') as $item) { |
a7db5554 | 151 | $this->insertNewEntity($item); |
a092536e | 152 | } |
13169d44 | 153 | foreach ($this->filterPlanByAction($plan, 'disable') as $item) { |
a7db5554 | 154 | $this->disableEntity($item); |
6a488035 TO |
155 | } |
156 | } | |
157 | ||
a092536e EM |
158 | /** |
159 | * Get the managed entities that fit the criteria. | |
160 | * | |
13169d44 | 161 | * @param array[] $plan |
a7db5554 | 162 | * @param string $action |
a092536e EM |
163 | * |
164 | * @return array | |
165 | */ | |
13169d44 | 166 | private function filterPlanByAction(array $plan, string $action): array { |
12485e4f | 167 | return CRM_Utils_Array::findAll($plan, ['managed_action' => $action]); |
bbf66e9c | 168 | } |
6a488035 | 169 | |
7cddb4ae | 170 | /** |
88718db2 | 171 | * Create a new entity. |
7cddb4ae | 172 | * |
a7db5554 | 173 | * @param array $item |
6a0b768e | 174 | * Entity specification (per hook_civicrm_managedEntities). |
7cddb4ae | 175 | */ |
a7db5554 CW |
176 | protected function insertNewEntity(array $item) { |
177 | $params = $item['params']; | |
f43ca71f CW |
178 | // APIv4 |
179 | if ($params['version'] == 4) { | |
180 | $params['checkPermissions'] = FALSE; | |
181 | // Use "save" instead of "create" action to accommodate a "match" param | |
182 | $params['records'] = [$params['values']]; | |
183 | unset($params['values']); | |
a7db5554 | 184 | $result = civicrm_api4($item['entity_type'], 'save', $params); |
f43ca71f | 185 | $id = $result->first()['id']; |
9626d0a1 | 186 | } |
f43ca71f CW |
187 | // APIv3 |
188 | else { | |
a7db5554 | 189 | $result = civicrm_api($item['entity_type'], 'create', $params); |
f43ca71f | 190 | if (!empty($result['is_error'])) { |
a7db5554 | 191 | $this->onApiError($item['entity_type'], 'create', $params, $result); |
f43ca71f CW |
192 | } |
193 | $id = $result['id']; | |
7cddb4ae TO |
194 | } |
195 | ||
196 | $dao = new CRM_Core_DAO_Managed(); | |
a7db5554 CW |
197 | $dao->module = $item['module']; |
198 | $dao->name = $item['name']; | |
199 | $dao->entity_type = $item['entity_type']; | |
f43ca71f | 200 | $dao->entity_id = $id; |
a7db5554 | 201 | $dao->cleanup = $item['cleanup'] ?? NULL; |
7cddb4ae TO |
202 | $dao->save(); |
203 | } | |
204 | ||
205 | /** | |
20429eb9 | 206 | * Update an entity which is believed to exist. |
7cddb4ae | 207 | * |
a7db5554 | 208 | * @param array $item |
6a0b768e | 209 | * Entity specification (per hook_civicrm_managedEntities). |
7cddb4ae | 210 | */ |
a7db5554 CW |
211 | private function updateExistingEntity(array $item) { |
212 | $policy = $item['update'] ?? 'always'; | |
35c1c211 | 213 | $doUpdate = ($policy === 'always'); |
0dd54586 | 214 | |
095e8ae4 CW |
215 | if ($policy === 'unmodified') { |
216 | // If this is not an APIv4 managed entity, the entity_modidfied_date will always be null | |
a7db5554 CW |
217 | if (!CRM_Core_BAO_Managed::isApi4ManagedType($item['entity_type'])) { |
218 | Civi::log()->warning('ManagedEntity update policy "unmodified" specified for entity type ' . $item['entity_type'] . ' which is not an APIv4 ManagedEntity. Falling back to policy "always".'); | |
095e8ae4 | 219 | } |
a7db5554 | 220 | $doUpdate = empty($item['entity_modified_date']); |
095e8ae4 CW |
221 | } |
222 | ||
a7db5554 CW |
223 | if ($doUpdate && $item['params']['version'] == 3) { |
224 | $defaults = ['id' => $item['entity_id']]; | |
225 | if ($this->isActivationSupported($item['entity_type'])) { | |
3cf02556 TO |
226 | $defaults['is_active'] = 1; |
227 | } | |
a7db5554 | 228 | $params = array_merge($defaults, $item['params']); |
20429eb9 RL |
229 | |
230 | $manager = CRM_Extension_System::singleton()->getManager(); | |
a7db5554 | 231 | if ($item['entity_type'] === 'Job' && !$manager->extensionIsBeingInstalledOrEnabled($item['module'])) { |
20429eb9 RL |
232 | // Special treatment for scheduled jobs: |
233 | // | |
234 | // If we're being called as part of enabling/installing a module then | |
235 | // we want the default behaviour of setting is_active = 1. | |
236 | // | |
237 | // However, if we're just being called by a normal cache flush then we | |
238 | // should not re-enable a job that an administrator has decided to disable. | |
239 | // | |
240 | // Without this logic there was a problem: site admin might disable | |
241 | // a job, but then when there was a flush op, the job was re-enabled | |
242 | // which can cause significant embarrassment, depending on the job | |
243 | // ("Don't worry, sending mailings is disabled right now..."). | |
244 | unset($params['is_active']); | |
245 | } | |
246 | ||
a7db5554 | 247 | $result = civicrm_api($item['entity_type'], 'create', $params); |
0dd54586 | 248 | if ($result['is_error']) { |
a7db5554 | 249 | $this->onApiError($item['entity_type'], 'create', $params, $result); |
0dd54586 | 250 | } |
7cddb4ae | 251 | } |
a7db5554 CW |
252 | elseif ($doUpdate && $item['params']['version'] == 4) { |
253 | $params = ['checkPermissions' => FALSE] + $item['params']; | |
254 | $params['values']['id'] = $item['entity_id']; | |
f43ca71f CW |
255 | // 'match' param doesn't apply to "update" action |
256 | unset($params['match']); | |
a7db5554 | 257 | civicrm_api4($item['entity_type'], 'update', $params); |
9626d0a1 | 258 | } |
1f103dc4 | 259 | |
a7db5554 CW |
260 | if (isset($item['cleanup']) || $doUpdate) { |
261 | $dao = new CRM_Core_DAO_Managed(); | |
262 | $dao->id = $item['id']; | |
263 | $dao->cleanup = $item['cleanup'] ?? NULL; | |
69e13f9b CW |
264 | // Reset the `entity_modified_date` timestamp if reverting record. |
265 | $dao->entity_modified_date = $doUpdate ? 'null' : NULL; | |
1f103dc4 TO |
266 | $dao->update(); |
267 | } | |
7cddb4ae TO |
268 | } |
269 | ||
270 | /** | |
271 | * Update an entity which (a) is believed to exist and which (b) ought to be | |
272 | * inactive. | |
273 | * | |
a7db5554 | 274 | * @param array $item |
8b91d849 | 275 | * |
276 | * @throws \CiviCRM_API3_Exception | |
7cddb4ae | 277 | */ |
a7db5554 CW |
278 | protected function disableEntity(array $item): void { |
279 | $entity_type = $item['entity_type']; | |
fc625166 | 280 | if ($this->isActivationSupported($entity_type)) { |
7cddb4ae | 281 | // FIXME cascading for payproc types? |
be2fb01f | 282 | $params = [ |
7cddb4ae | 283 | 'version' => 3, |
a7db5554 | 284 | 'id' => $item['entity_id'], |
7cddb4ae | 285 | 'is_active' => 0, |
be2fb01f | 286 | ]; |
a7db5554 | 287 | $result = civicrm_api($item['entity_type'], 'create', $params); |
7cddb4ae | 288 | if ($result['is_error']) { |
a7db5554 | 289 | $this->onApiError($item['entity_type'], 'create', $params, $result); |
7cddb4ae | 290 | } |
69e13f9b | 291 | // Reset the `entity_modified_date` timestamp to indicate that the entity has not been modified by the user. |
a7db5554 CW |
292 | $dao = new CRM_Core_DAO_Managed(); |
293 | $dao->id = $item['id']; | |
69e13f9b CW |
294 | $dao->entity_modified_date = 'null'; |
295 | $dao->update(); | |
7cddb4ae TO |
296 | } |
297 | } | |
298 | ||
bbf66e9c | 299 | /** |
88718db2 | 300 | * Remove a stale entity (if policy allows). |
bbf66e9c | 301 | * |
a7db5554 | 302 | * @param array $item |
a092536e | 303 | * @throws CRM_Core_Exception |
bbf66e9c | 304 | */ |
a7db5554 CW |
305 | protected function removeStaleEntity(array $item) { |
306 | $policy = empty($item['cleanup']) ? 'always' : $item['cleanup']; | |
378e2654 TO |
307 | switch ($policy) { |
308 | case 'always': | |
309 | $doDelete = TRUE; | |
310 | break; | |
ea100cb5 | 311 | |
378e2654 TO |
312 | case 'never': |
313 | $doDelete = FALSE; | |
314 | break; | |
ea100cb5 | 315 | |
378e2654 | 316 | case 'unused': |
a7db5554 CW |
317 | if (CRM_Core_BAO_Managed::isApi4ManagedType($item['entity_type'])) { |
318 | $getRefCount = \Civi\Api4\Utils\CoreUtil::getRefCount($item['entity_type'], $item['entity_id']); | |
095e8ae4 CW |
319 | } |
320 | else { | |
a7db5554 CW |
321 | $getRefCount = civicrm_api3($item['entity_type'], 'getrefcount', [ |
322 | 'id' => $item['entity_id'], | |
095e8ae4 CW |
323 | ])['values']; |
324 | } | |
378e2654 | 325 | |
095e8ae4 | 326 | // FIXME: This extra counting should be unnecessary, because getRefCount only returns values if count > 0 |
378e2654 | 327 | $total = 0; |
095e8ae4 | 328 | foreach ($getRefCount as $refCount) { |
378e2654 TO |
329 | $total += $refCount['count']; |
330 | } | |
331 | ||
332 | $doDelete = ($total == 0); | |
333 | break; | |
ea100cb5 | 334 | |
378e2654 | 335 | default: |
a092536e | 336 | throw new CRM_Core_Exception('Unrecognized cleanup policy: ' . $policy); |
378e2654 | 337 | } |
bbf66e9c | 338 | |
a2bf5923 CW |
339 | // APIv4 delete - deletion from `civicrm_managed` will be taken care of by |
340 | // CRM_Core_BAO_Managed::on_hook_civicrm_post() | |
a7db5554 CW |
341 | if ($doDelete && CRM_Core_BAO_Managed::isApi4ManagedType($item['entity_type'])) { |
342 | civicrm_api4($item['entity_type'], 'delete', [ | |
081629dc | 343 | 'checkPermissions' => FALSE, |
a7db5554 | 344 | 'where' => [['id', '=', $item['entity_id']]], |
a2bf5923 CW |
345 | ]); |
346 | } | |
347 | // APIv3 delete | |
348 | elseif ($doDelete) { | |
be2fb01f | 349 | $params = [ |
1f103dc4 | 350 | 'version' => 3, |
a7db5554 | 351 | 'id' => $item['entity_id'], |
be2fb01f | 352 | ]; |
a7db5554 | 353 | $check = civicrm_api3($item['entity_type'], 'get', $params); |
a2bf5923 | 354 | if ($check['count']) { |
a7db5554 | 355 | $result = civicrm_api($item['entity_type'], 'delete', $params); |
a60c0bc8 | 356 | if ($result['is_error']) { |
a7db5554 CW |
357 | if (isset($item['name'])) { |
358 | $params['name'] = $item['name']; | |
9f4f065a | 359 | } |
a7db5554 | 360 | $this->onApiError($item['entity_type'], 'delete', $params, $result); |
a60c0bc8 | 361 | } |
a60c0bc8 | 362 | } |
be2fb01f | 363 | CRM_Core_DAO::executeQuery('DELETE FROM civicrm_managed WHERE id = %1', [ |
a7db5554 | 364 | 1 => [$item['id'], 'Integer'], |
be2fb01f | 365 | ]); |
1f103dc4 | 366 | } |
6a488035 TO |
367 | } |
368 | ||
369 | /** | |
88718db2 TO |
370 | * @param array $modules |
371 | * Array<CRM_Core_Module>. | |
77b97be7 | 372 | * |
a6c01b45 CW |
373 | * @return array |
374 | * indexed by is_active,name | |
6a488035 | 375 | */ |
85917c3b | 376 | protected function createModuleIndex($modules) { |
be2fb01f | 377 | $result = []; |
6a488035 TO |
378 | foreach ($modules as $module) { |
379 | $result[$module->is_active][$module->name] = $module; | |
380 | } | |
381 | return $result; | |
382 | } | |
383 | ||
384 | /** | |
88718db2 TO |
385 | * @param array $moduleIndex |
386 | * @param array $declarations | |
77b97be7 | 387 | * |
a6c01b45 CW |
388 | * @return array |
389 | * indexed by module,name | |
6a488035 | 390 | */ |
85917c3b | 391 | protected function createDeclarationIndex($moduleIndex, $declarations) { |
be2fb01f | 392 | $result = []; |
6a488035 TO |
393 | if (!isset($moduleIndex[TRUE])) { |
394 | return $result; | |
395 | } | |
396 | foreach ($moduleIndex[TRUE] as $moduleName => $module) { | |
397 | if ($module->is_active) { | |
398 | // need an empty array() for all active modules, even if there are no current $declarations | |
be2fb01f | 399 | $result[$moduleName] = []; |
6a488035 TO |
400 | } |
401 | } | |
402 | foreach ($declarations as $declaration) { | |
403 | $result[$declaration['module']][$declaration['name']] = $declaration; | |
404 | } | |
405 | return $result; | |
406 | } | |
407 | ||
408 | /** | |
fa3fdebc | 409 | * @param array $declarations |
fd31fa4c | 410 | * |
a7db5554 | 411 | * @throws CRM_Core_Exception |
6a488035 | 412 | */ |
85917c3b | 413 | protected function validate($declarations) { |
9cf68fcc | 414 | foreach ($declarations as $module => $declare) { |
be2fb01f | 415 | foreach (['name', 'module', 'entity', 'params'] as $key) { |
6a488035 TO |
416 | if (empty($declare[$key])) { |
417 | $str = print_r($declare, TRUE); | |
a7db5554 | 418 | throw new CRM_Core_Exception(ts('Managed Entity (%1) is missing field "%2": %3', [$module, $key, $str])); |
6a488035 TO |
419 | } |
420 | } | |
9cf68fcc | 421 | if (!$this->isModuleRecognised($declare['module'])) { |
a7db5554 | 422 | throw new CRM_Core_Exception(ts('Entity declaration references invalid or inactive module name [%1]', [$declare['module']])); |
9cf68fcc | 423 | } |
6a488035 | 424 | } |
6a488035 TO |
425 | } |
426 | ||
9cf68fcc EM |
427 | /** |
428 | * Is the module recognised (as an enabled or disabled extension in the system). | |
429 | * | |
430 | * @param string $module | |
431 | * | |
432 | * @return bool | |
433 | */ | |
434 | protected function isModuleRecognised(string $module): bool { | |
435 | return $this->isModuleDisabled($module) || $this->isModuleEnabled($module); | |
436 | } | |
437 | ||
438 | /** | |
439 | * Is the module enabled. | |
440 | * | |
441 | * @param string $module | |
442 | * | |
443 | * @return bool | |
444 | */ | |
445 | protected function isModuleEnabled(string $module): bool { | |
446 | return isset($this->moduleIndex[TRUE][$module]); | |
447 | } | |
448 | ||
449 | /** | |
450 | * Is the module disabled. | |
451 | * | |
452 | * @param string $module | |
453 | * | |
454 | * @return bool | |
455 | */ | |
456 | protected function isModuleDisabled(string $module): bool { | |
457 | return isset($this->moduleIndex[FALSE][$module]); | |
458 | } | |
459 | ||
a0ee3941 | 460 | /** |
e9b95545 TO |
461 | * @param string $entity |
462 | * @param string $action | |
463 | * @param array $params | |
464 | * @param array $result | |
a0ee3941 EM |
465 | * |
466 | * @throws Exception | |
467 | */ | |
e9b95545 | 468 | protected function onApiError($entity, $action, $params, $result) { |
be2fb01f | 469 | CRM_Core_Error::debug_var('ManagedEntities_failed', [ |
e9b95545 TO |
470 | 'entity' => $entity, |
471 | 'action' => $action, | |
6a488035 TO |
472 | 'params' => $params, |
473 | 'result' => $result, | |
be2fb01f | 474 | ]); |
35c1c211 | 475 | throw new Exception('API error: ' . $result['error_message'] . ' on ' . $entity . '.' . $action |
9f4f065a | 476 | . (!empty($params['name']) ? '( entity name ' . $params['name'] . ')' : '') |
35c1c211 | 477 | ); |
cbb7c7e0 | 478 | } |
96025800 | 479 | |
fc625166 TO |
480 | /** |
481 | * Determine if an entity supports APIv3-based activation/de-activation. | |
482 | * @param string $entity_type | |
483 | * | |
484 | * @return bool | |
485 | * @throws \CiviCRM_API3_Exception | |
486 | */ | |
487 | private function isActivationSupported(string $entity_type): bool { | |
488 | if (!isset(Civi::$statics[__CLASS__][__FUNCTION__][$entity_type])) { | |
489 | $actions = civicrm_api3($entity_type, 'getactions', [])['values']; | |
490 | Civi::$statics[__CLASS__][__FUNCTION__][$entity_type] = FALSE; | |
491 | if (in_array('create', $actions, TRUE) && in_array('getfields', $actions)) { | |
492 | $fields = civicrm_api3($entity_type, 'getfields', ['action' => 'create'])['values']; | |
493 | Civi::$statics[__CLASS__][__FUNCTION__][$entity_type] = array_key_exists('is_active', $fields); | |
494 | } | |
495 | } | |
496 | return Civi::$statics[__CLASS__][__FUNCTION__][$entity_type]; | |
497 | } | |
498 | ||
78ce6ebb | 499 | /** |
12485e4f | 500 | * Load managed entity declarations. |
78ce6ebb EM |
501 | * |
502 | * This picks it up from hooks and enabled components. | |
a7db5554 CW |
503 | * |
504 | * @param array|null $modules | |
505 | * Limit reconciliation specified modules. | |
12485e4f | 506 | * @return array[] |
78ce6ebb | 507 | */ |
12485e4f | 508 | protected function getDeclarations($modules = NULL): array { |
a7db5554 CW |
509 | $declarations = []; |
510 | // Exclude components if given a module name. | |
511 | if (!$modules || $modules === ['civicrm']) { | |
512 | foreach (CRM_Core_Component::getEnabledComponents() as $component) { | |
513 | $declarations = array_merge($declarations, $component->getManagedEntities()); | |
514 | } | |
78ce6ebb | 515 | } |
fdc67a75 | 516 | CRM_Utils_Hook::managed($declarations, $modules); |
a7db5554 | 517 | $this->validate($declarations); |
12485e4f CW |
518 | foreach (array_keys($declarations) as $name) { |
519 | $declarations[$name] += ['name' => $name]; | |
520 | } | |
521 | return $declarations; | |
78ce6ebb EM |
522 | } |
523 | ||
a7db5554 CW |
524 | /** |
525 | * Builds $this->managedActions array | |
526 | * | |
12485e4f | 527 | * @param array $declarations |
a7db5554 | 528 | * @param array|null $modules |
13169d44 | 529 | * @return array[] |
a7db5554 | 530 | */ |
12485e4f | 531 | protected function createPlan(array $declarations, $modules = NULL): array { |
a7db5554 CW |
532 | $where = $modules ? [['module', 'IN', $modules]] : []; |
533 | $managedEntities = Managed::get(FALSE) | |
534 | ->setWhere($where) | |
535 | ->execute(); | |
13169d44 | 536 | $plan = []; |
a092536e EM |
537 | foreach ($managedEntities as $managedEntity) { |
538 | $key = "{$managedEntity['module']}_{$managedEntity['name']}_{$managedEntity['entity_type']}"; | |
a7db5554 CW |
539 | // Set to disable or delete if module is disabled or missing - it will be overwritten below module is active. |
540 | $action = $this->isModuleDisabled($managedEntity['module']) ? 'disable' : 'delete'; | |
13169d44 | 541 | $plan[$key] = array_merge($managedEntity, ['managed_action' => $action]); |
a092536e | 542 | } |
12485e4f | 543 | foreach ($declarations as $declaration) { |
a092536e | 544 | $key = "{$declaration['module']}_{$declaration['name']}_{$declaration['entity']}"; |
13169d44 CW |
545 | if (isset($plan[$key])) { |
546 | $plan[$key]['params'] = $declaration['params']; | |
547 | $plan[$key]['managed_action'] = 'update'; | |
548 | $plan[$key]['cleanup'] = $declaration['cleanup'] ?? NULL; | |
549 | $plan[$key]['update'] = $declaration['update'] ?? 'always'; | |
a092536e EM |
550 | } |
551 | else { | |
13169d44 | 552 | $plan[$key] = [ |
a092536e EM |
553 | 'module' => $declaration['module'], |
554 | 'name' => $declaration['name'], | |
555 | 'entity_type' => $declaration['entity'], | |
556 | 'managed_action' => 'create', | |
557 | 'params' => $declaration['params'], | |
558 | 'cleanup' => $declaration['cleanup'] ?? NULL, | |
559 | 'update' => $declaration['update'] ?? 'always', | |
560 | ]; | |
561 | } | |
562 | } | |
13169d44 | 563 | return $plan; |
a092536e EM |
564 | } |
565 | ||
6a488035 | 566 | } |