REF - Refactor ManagedEntities with the ability to reconcile a single module
authorColeman Watts <coleman@civicrm.org>
Thu, 17 Mar 2022 02:00:54 +0000 (22:00 -0400)
committerColeman Watts <coleman@civicrm.org>
Mon, 18 Apr 2022 22:26:09 +0000 (18:26 -0400)
Reconciling a single module (e.g. Afform) is more efficient.
Further refactoring of ManagedEntities for code simplification and efficiency.
Instead of fetching Managed records 3 times (for enabled, disabled and missing modules),
now they are fetched only once and sorted into the correct category.

CRM/Core/ManagedEntities.php
ext/afform/core/Civi/Api4/Action/Afform/Revert.php
ext/afform/core/Civi/Api4/Utils/AfformSaveTrait.php
tests/phpunit/api/v4/Entity/ManagedEntityTest.php

index bbae0344b51f257ad689f725e88cb6d8c3487c08..1e249fe76d45ed245dcd98ab0acd53082276ddbf 100644 (file)
@@ -29,11 +29,11 @@ class CRM_Core_ManagedEntities {
   protected $moduleIndex;
 
   /**
-   * Actions arising from the managed entities.
+   * Plan for what to do with each managed entity.
    *
    * @var array
    */
-  protected $managedActions = [];
+  protected $plan = [];
 
   /**
    * @var array
@@ -115,19 +115,15 @@ class CRM_Core_ManagedEntities {
    * Identify any enabled/disabled modules. Add new entities, update
    * existing entities, and remove orphaned (stale) entities.
    *
-   * @param bool $ignoreUpgradeMode
-   *   Unused.
+   * @param array $modules
+   *   Limits scope of reconciliation to specific module(s).
    * @throws \CRM_Core_Exception
    */
-  public function reconcile($ignoreUpgradeMode = FALSE) {
-    $this->loadDeclarations();
-    if ($error = $this->validate($this->getDeclarations())) {
-      throw new CRM_Core_Exception($error);
-    }
-    $this->loadManagedEntityActions();
-    $this->reconcileEnabledModules();
-    $this->reconcileDisabledModules();
-    $this->reconcileUnknownModules();
+  public function reconcile($modules = NULL) {
+    $modules = $modules ? (array) $modules : NULL;
+    $this->loadDeclarations($modules);
+    $this->createPlan($modules);
+    $this->reconcileEntities();
   }
 
   /**
@@ -139,230 +135,111 @@ class CRM_Core_ManagedEntities {
     $mgd = new \CRM_Core_DAO_Managed();
     $mgd->copyValues($params);
     $mgd->find(TRUE);
-    $this->loadDeclarations();
+    $this->loadDeclarations([$mgd->module]);
     $declarations = CRM_Utils_Array::findAll($this->declarations, [
       'module' => $mgd->module,
       'name' => $mgd->name,
       'entity' => $mgd->entity_type,
     ]);
     if ($mgd->id && isset($declarations[0])) {
-      $this->updateExistingEntity($mgd, ['update' => 'always'] + $declarations[0]);
+      $this->updateExistingEntity(['update' => 'always'] + $declarations[0] + $mgd->toArray());
       return TRUE;
     }
     return FALSE;
   }
 
   /**
-   * For all enabled modules, add new entities, update
-   * existing entities, and remove orphaned (stale) entities.
+   * Take appropriate action on every managed entity.
    */
-  protected function reconcileEnabledModules(): void {
-    // Note: any thing currently declared is necessarily from
-    // an active module -- because we got it from a hook!
-
-    // index by moduleName,name
-    $decls = $this->createDeclarationIndex($this->moduleIndex, $this->getDeclarations());
-    foreach ($decls as $moduleName => $todos) {
-      if ($this->isModuleEnabled($moduleName)) {
-        $this->reconcileEnabledModule($moduleName);
-      }
+  private function reconcileEntities(): void {
+    foreach ($this->getManagedAction('update') as $item) {
+      $this->updateExistingEntity($item);
     }
-  }
-
-  /**
-   * For one enabled module, add new entities, update existing entities,
-   * and remove orphaned (stale) entities.
-   *
-   * @param string $module
-   */
-  protected function reconcileEnabledModule(string $module): void {
-    foreach ($this->getManagedEntitiesToUpdate(['module' => $module]) as $todo) {
-      $dao = new CRM_Core_DAO_Managed();
-      $dao->module = $todo['module'];
-      $dao->name = $todo['name'];
-      $dao->entity_type = $todo['entity_type'];
-      $dao->entity_id = $todo['entity_id'];
-      $dao->entity_modified_date = $todo['entity_modified_date'];
-      $dao->id = $todo['id'];
-      $this->updateExistingEntity($dao, $todo);
+    // reverse-order so that child entities are cleaned up before their parents
+    foreach (array_reverse($this->getManagedAction('delete')) as $item) {
+      $this->removeStaleEntity($item);
     }
-
-    foreach ($this->getManagedEntitiesToDelete(['module' => $module]) as $todo) {
-      $dao = new CRM_Core_DAO_Managed();
-      $dao->module = $todo['module'];
-      $dao->name = $todo['name'];
-      $dao->entity_type = $todo['entity_type'];
-      $dao->id = $todo['id'];
-      $dao->cleanup = $todo['cleanup'];
-      $dao->entity_id = $todo['entity_id'];
-      $this->removeStaleEntity($dao);
+    foreach ($this->getManagedAction('create') as $item) {
+      $this->insertNewEntity($item);
     }
-    foreach ($this->getManagedEntitiesToCreate(['module' => $module]) as $todo) {
-      $this->insertNewEntity($todo);
+    foreach ($this->getManagedAction('disable') as $item) {
+      $this->disableEntity($item);
     }
   }
 
-  /**
-   * Get the managed entities to be created.
-   *
-   * @param array $filters
-   *
-   * @return array
-   */
-  protected function getManagedEntitiesToCreate(array $filters = []): array {
-    return $this->getManagedEntities(array_merge($filters, ['managed_action' => 'create']));
-  }
-
-  /**
-   * Get the managed entities to be updated.
-   *
-   * @param array $filters
-   *
-   * @return array
-   */
-  protected function getManagedEntitiesToUpdate(array $filters = []): array {
-    return $this->getManagedEntities(array_merge($filters, ['managed_action' => 'update']));
-  }
-
-  /**
-   * Get the managed entities to be deleted.
-   *
-   * @param array $filters
-   *
-   * @return array
-   */
-  protected function getManagedEntitiesToDelete(array $filters = []): array {
-    // Return array in reverse-order so that child entities are cleaned up before their parents
-    return array_reverse($this->getManagedEntities(array_merge($filters, ['managed_action' => 'delete'])));
-  }
-
   /**
    * Get the managed entities that fit the criteria.
    *
-   * @param array $filters
+   * @param string $action
    *
    * @return array
    */
-  protected function getManagedEntities(array $filters = []): array {
-    $return = [];
-    foreach ($this->managedActions as $actionKey => $action) {
-      foreach ($filters as $filterKey => $filterValue) {
-        if ($action[$filterKey] !== $filterValue) {
-          continue 2;
-        }
-      }
-      $return[$actionKey] = $action;
-    }
-    return $return;
-  }
-
-  /**
-   * For all disabled modules, disable any managed entities.
-   */
-  protected function reconcileDisabledModules() {
-    if (empty($this->moduleIndex[FALSE])) {
-      return;
-    }
-
-    $in = CRM_Core_DAO::escapeStrings(array_keys($this->moduleIndex[FALSE]));
-    $dao = new CRM_Core_DAO_Managed();
-    $dao->whereAdd("module in ($in)");
-    $dao->orderBy('id DESC');
-    $dao->find();
-    while ($dao->fetch()) {
-      $this->disableEntity($dao);
-
-    }
-  }
-
-  /**
-   * Remove any orphaned (stale) entities that are linked to
-   * unknown modules.
-   */
-  protected function reconcileUnknownModules() {
-    $knownModules = [];
-    if (array_key_exists(0, $this->moduleIndex) && is_array($this->moduleIndex[0])) {
-      $knownModules = array_merge($knownModules, array_keys($this->moduleIndex[0]));
-    }
-    if (array_key_exists(1, $this->moduleIndex) && is_array($this->moduleIndex[1])) {
-      $knownModules = array_merge($knownModules, array_keys($this->moduleIndex[1]));
-    }
-
-    $dao = new CRM_Core_DAO_Managed();
-    if (!empty($knownModules)) {
-      $in = CRM_Core_DAO::escapeStrings($knownModules);
-      $dao->whereAdd("module NOT IN ($in)");
-      $dao->orderBy('id DESC');
-    }
-    $dao->find();
-    while ($dao->fetch()) {
-      $this->removeStaleEntity($dao);
-    }
+  private function getManagedAction(string $action): array {
+    return CRM_Utils_Array::findAll($this->plan, ['managed_action' => $action]);
   }
 
   /**
    * Create a new entity.
    *
-   * @param array $todo
+   * @param array $item
    *   Entity specification (per hook_civicrm_managedEntities).
    */
-  protected function insertNewEntity($todo) {
-    $params = $todo['params'];
+  protected function insertNewEntity(array $item) {
+    $params = $item['params'];
     // APIv4
     if ($params['version'] == 4) {
       $params['checkPermissions'] = FALSE;
       // Use "save" instead of "create" action to accommodate a "match" param
       $params['records'] = [$params['values']];
       unset($params['values']);
-      $result = civicrm_api4($todo['entity_type'], 'save', $params);
+      $result = civicrm_api4($item['entity_type'], 'save', $params);
       $id = $result->first()['id'];
     }
     // APIv3
     else {
-      $result = civicrm_api($todo['entity_type'], 'create', $params);
+      $result = civicrm_api($item['entity_type'], 'create', $params);
       if (!empty($result['is_error'])) {
-        $this->onApiError($todo['entity_type'], 'create', $params, $result);
+        $this->onApiError($item['entity_type'], 'create', $params, $result);
       }
       $id = $result['id'];
     }
 
     $dao = new CRM_Core_DAO_Managed();
-    $dao->module = $todo['module'];
-    $dao->name = $todo['name'];
-    $dao->entity_type = $todo['entity_type'];
+    $dao->module = $item['module'];
+    $dao->name = $item['name'];
+    $dao->entity_type = $item['entity_type'];
     $dao->entity_id = $id;
-    $dao->cleanup = $todo['cleanup'] ?? NULL;
+    $dao->cleanup = $item['cleanup'] ?? NULL;
     $dao->save();
   }
 
   /**
    * Update an entity which is believed to exist.
    *
-   * @param CRM_Core_DAO_Managed $dao
-   * @param array $todo
+   * @param array $item
    *   Entity specification (per hook_civicrm_managedEntities).
    */
-  protected function updateExistingEntity($dao, $todo) {
-    $policy = $todo['update'] ?? 'always';
+  private function updateExistingEntity(array $item) {
+    $policy = $item['update'] ?? 'always';
     $doUpdate = ($policy === 'always');
 
     if ($policy === 'unmodified') {
       // If this is not an APIv4 managed entity, the entity_modidfied_date will always be null
-      if (!CRM_Core_BAO_Managed::isApi4ManagedType($dao->entity_type)) {
-        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".');
+      if (!CRM_Core_BAO_Managed::isApi4ManagedType($item['entity_type'])) {
+        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".');
       }
-      $doUpdate = empty($dao->entity_modified_date);
+      $doUpdate = empty($item['entity_modified_date']);
     }
 
-    if ($doUpdate && $todo['params']['version'] == 3) {
-      $defaults = ['id' => $dao->entity_id];
-      if ($this->isActivationSupported($dao->entity_type)) {
+    if ($doUpdate && $item['params']['version'] == 3) {
+      $defaults = ['id' => $item['entity_id']];
+      if ($this->isActivationSupported($item['entity_type'])) {
         $defaults['is_active'] = 1;
       }
-      $params = array_merge($defaults, $todo['params']);
+      $params = array_merge($defaults, $item['params']);
 
       $manager = CRM_Extension_System::singleton()->getManager();
-      if ($dao->entity_type === 'Job' && !$manager->extensionIsBeingInstalledOrEnabled($dao->module)) {
+      if ($item['entity_type'] === 'Job' && !$manager->extensionIsBeingInstalledOrEnabled($item['module'])) {
         // Special treatment for scheduled jobs:
         //
         // If we're being called as part of enabling/installing a module then
@@ -378,21 +255,23 @@ class CRM_Core_ManagedEntities {
         unset($params['is_active']);
       }
 
-      $result = civicrm_api($dao->entity_type, 'create', $params);
+      $result = civicrm_api($item['entity_type'], 'create', $params);
       if ($result['is_error']) {
-        $this->onApiError($dao->entity_type, 'create', $params, $result);
+        $this->onApiError($item['entity_type'], 'create', $params, $result);
       }
     }
-    elseif ($doUpdate && $todo['params']['version'] == 4) {
-      $params = ['checkPermissions' => FALSE] + $todo['params'];
-      $params['values']['id'] = $dao->entity_id;
+    elseif ($doUpdate && $item['params']['version'] == 4) {
+      $params = ['checkPermissions' => FALSE] + $item['params'];
+      $params['values']['id'] = $item['entity_id'];
       // 'match' param doesn't apply to "update" action
       unset($params['match']);
-      civicrm_api4($dao->entity_type, 'update', $params);
+      civicrm_api4($item['entity_type'], 'update', $params);
     }
 
-    if (isset($todo['cleanup']) || $doUpdate) {
-      $dao->cleanup = $todo['cleanup'] ?? NULL;
+    if (isset($item['cleanup']) || $doUpdate) {
+      $dao = new CRM_Core_DAO_Managed();
+      $dao->id = $item['id'];
+      $dao->cleanup = $item['cleanup'] ?? NULL;
       // Reset the `entity_modified_date` timestamp if reverting record.
       $dao->entity_modified_date = $doUpdate ? 'null' : NULL;
       $dao->update();
@@ -403,24 +282,26 @@ class CRM_Core_ManagedEntities {
    * Update an entity which (a) is believed to exist and which (b) ought to be
    * inactive.
    *
-   * @param CRM_Core_DAO_Managed $dao
+   * @param array $item
    *
    * @throws \CiviCRM_API3_Exception
    */
-  protected function disableEntity($dao): void {
-    $entity_type = $dao->entity_type;
+  protected function disableEntity(array $item): void {
+    $entity_type = $item['entity_type'];
     if ($this->isActivationSupported($entity_type)) {
       // FIXME cascading for payproc types?
       $params = [
         'version' => 3,
-        'id' => $dao->entity_id,
+        'id' => $item['entity_id'],
         'is_active' => 0,
       ];
-      $result = civicrm_api($dao->entity_type, 'create', $params);
+      $result = civicrm_api($item['entity_type'], 'create', $params);
       if ($result['is_error']) {
-        $this->onApiError($dao->entity_type, 'create', $params, $result);
+        $this->onApiError($item['entity_type'], 'create', $params, $result);
       }
       // Reset the `entity_modified_date` timestamp to indicate that the entity has not been modified by the user.
+      $dao = new CRM_Core_DAO_Managed();
+      $dao->id = $item['id'];
       $dao->entity_modified_date = 'null';
       $dao->update();
     }
@@ -429,11 +310,11 @@ class CRM_Core_ManagedEntities {
   /**
    * Remove a stale entity (if policy allows).
    *
-   * @param CRM_Core_DAO_Managed $dao
+   * @param array $item
    * @throws CRM_Core_Exception
    */
-  protected function removeStaleEntity($dao) {
-    $policy = empty($dao->cleanup) ? 'always' : $dao->cleanup;
+  protected function removeStaleEntity(array $item) {
+    $policy = empty($item['cleanup']) ? 'always' : $item['cleanup'];
     switch ($policy) {
       case 'always':
         $doDelete = TRUE;
@@ -444,12 +325,12 @@ class CRM_Core_ManagedEntities {
         break;
 
       case 'unused':
-        if (CRM_Core_BAO_Managed::isApi4ManagedType($dao->entity_type)) {
-          $getRefCount = \Civi\Api4\Utils\CoreUtil::getRefCount($dao->entity_type, $dao->entity_id);
+        if (CRM_Core_BAO_Managed::isApi4ManagedType($item['entity_type'])) {
+          $getRefCount = \Civi\Api4\Utils\CoreUtil::getRefCount($item['entity_type'], $item['entity_id']);
         }
         else {
-          $getRefCount = civicrm_api3($dao->entity_type, 'getrefcount', [
-            'id' => $dao->entity_id,
+          $getRefCount = civicrm_api3($item['entity_type'], 'getrefcount', [
+            'id' => $item['entity_id'],
           ])['values'];
         }
 
@@ -468,30 +349,30 @@ class CRM_Core_ManagedEntities {
 
     // APIv4 delete - deletion from `civicrm_managed` will be taken care of by
     // CRM_Core_BAO_Managed::on_hook_civicrm_post()
-    if ($doDelete && CRM_Core_BAO_Managed::isApi4ManagedType($dao->entity_type)) {
-      civicrm_api4($dao->entity_type, 'delete', [
+    if ($doDelete && CRM_Core_BAO_Managed::isApi4ManagedType($item['entity_type'])) {
+      civicrm_api4($item['entity_type'], 'delete', [
         'checkPermissions' => FALSE,
-        'where' => [['id', '=', $dao->entity_id]],
+        'where' => [['id', '=', $item['entity_id']]],
       ]);
     }
     // APIv3 delete
     elseif ($doDelete) {
       $params = [
         'version' => 3,
-        'id' => $dao->entity_id,
+        'id' => $item['entity_id'],
       ];
-      $check = civicrm_api3($dao->entity_type, 'get', $params);
+      $check = civicrm_api3($item['entity_type'], 'get', $params);
       if ($check['count']) {
-        $result = civicrm_api($dao->entity_type, 'delete', $params);
+        $result = civicrm_api($item['entity_type'], 'delete', $params);
         if ($result['is_error']) {
-          if (isset($dao->name)) {
-            $params['name'] = $dao->name;
+          if (isset($item['name'])) {
+            $params['name'] = $item['name'];
           }
-          $this->onApiError($dao->entity_type, 'delete', $params, $result);
+          $this->onApiError($item['entity_type'], 'delete', $params, $result);
         }
       }
       CRM_Core_DAO::executeQuery('DELETE FROM civicrm_managed WHERE id = %1', [
-        1 => [$dao->id, 'Integer'],
+        1 => [$item['id'], 'Integer'],
       ]);
     }
   }
@@ -547,22 +428,20 @@ class CRM_Core_ManagedEntities {
   /**
    * @param array $declarations
    *
-   * @return string|bool
-   *   string on error, or FALSE
+   * @throws CRM_Core_Exception
    */
   protected function validate($declarations) {
     foreach ($declarations as $module => $declare) {
       foreach (['name', 'module', 'entity', 'params'] as $key) {
         if (empty($declare[$key])) {
           $str = print_r($declare, TRUE);
-          return ts('Managed Entity (%1) is missing field "%2": %3', [$module, $key, $str]);
+          throw new CRM_Core_Exception(ts('Managed Entity (%1) is missing field "%2": %3', [$module, $key, $str]));
         }
       }
       if (!$this->isModuleRecognised($declare['module'])) {
-        return ts('Entity declaration references invalid or inactive module name [%1]', [$declare['module']]);
+        throw new CRM_Core_Exception(ts('Entity declaration references invalid or inactive module name [%1]', [$declare['module']]));
       }
     }
-    return FALSE;
   }
 
   /**
@@ -600,16 +479,17 @@ class CRM_Core_ManagedEntities {
 
   /**
    * @param array $declarations
-   *
-   * @return array
+   * @param string $moduleName
+   *   Filter to a single module.
    */
-  protected function cleanDeclarations(array $declarations): array {
-    foreach ($declarations as $name => &$declare) {
-      if (!array_key_exists('name', $declare)) {
-        $declare['name'] = $name;
+  protected function setDeclarations(array $declarations, $moduleName = NULL) {
+    $this->declarations = [];
+    foreach ($declarations as $name => $declare) {
+      $declare += ['name' => $name];
+      if (!$moduleName || $declare['module'] === $moduleName) {
+        $this->declarations[$name] = $declare;
       }
     }
-    return $declarations;
   }
 
   /**
@@ -655,35 +535,53 @@ class CRM_Core_ManagedEntities {
    * Load declarations into the class property.
    *
    * This picks it up from hooks and enabled components.
+   *
+   * @param array|null $modules
+   *   Limit reconciliation specified modules.
    */
-  protected function loadDeclarations(): void {
-    $this->declarations = [];
-    foreach (CRM_Core_Component::getEnabledComponents() as $component) {
-      $this->declarations = array_merge($this->declarations, $component->getManagedEntities());
+  protected function loadDeclarations($modules = NULL): void {
+    $declarations = [];
+    // Exclude components if given a module name.
+    if (!$modules || $modules === ['civicrm']) {
+      foreach (CRM_Core_Component::getEnabledComponents() as $component) {
+        $declarations = array_merge($declarations, $component->getManagedEntities());
+      }
     }
-    CRM_Utils_Hook::managed($this->declarations);
-    $this->declarations = $this->cleanDeclarations($this->declarations);
+    // Ideally, given a $moduleName like 'org.foo.demo' we'd just call the function `demo_civicrm_managed()`
+    // But alas, that only works with old-style hooks and not the event dispatcher.
+    // So here we go loading declarations from every module whether we need them or not.
+    CRM_Utils_Hook::managed($declarations);
+    $this->validate($declarations);
+    $this->setDeclarations($declarations);
   }
 
-  protected function loadManagedEntityActions(): void {
-    $managedEntities = Managed::get(FALSE)->addSelect('*')->execute();
-    $this->managedActions = [];
+  /**
+   * Builds $this->managedActions array
+   *
+   * @param array|null $modules
+   */
+  protected function createPlan($modules = NULL): void {
+    $where = $modules ? [['module', 'IN', $modules]] : [];
+    $managedEntities = Managed::get(FALSE)
+      ->setWhere($where)
+      ->execute();
+    $this->plan = [];
     foreach ($managedEntities as $managedEntity) {
       $key = "{$managedEntity['module']}_{$managedEntity['name']}_{$managedEntity['entity_type']}";
-      // Set to 'delete' - it will be overwritten below if it is to be updated.
-      $action = 'delete';
-      $this->managedActions[$key] = array_merge($managedEntity, ['managed_action' => $action]);
+      // Set to disable or delete if module is disabled or missing - it will be overwritten below module is active.
+      $action = $this->isModuleDisabled($managedEntity['module']) ? 'disable' : 'delete';
+      $this->plan[$key] = array_merge($managedEntity, ['managed_action' => $action]);
     }
     foreach ($this->declarations as $declaration) {
       $key = "{$declaration['module']}_{$declaration['name']}_{$declaration['entity']}";
-      if (isset($this->managedActions[$key])) {
-        $this->managedActions[$key]['params'] = $declaration['params'];
-        $this->managedActions[$key]['managed_action'] = 'update';
-        $this->managedActions[$key]['cleanup'] = $declaration['cleanup'] ?? NULL;
-        $this->managedActions[$key]['update'] = $declaration['update'] ?? 'always';
+      if (isset($this->plan[$key])) {
+        $this->plan[$key]['params'] = $declaration['params'];
+        $this->plan[$key]['managed_action'] = 'update';
+        $this->plan[$key]['cleanup'] = $declaration['cleanup'] ?? NULL;
+        $this->plan[$key]['update'] = $declaration['update'] ?? 'always';
       }
       else {
-        $this->managedActions[$key] = [
+        $this->plan[$key] = [
           'module' => $declaration['module'],
           'name' => $declaration['name'],
           'entity_type' => $declaration['entity'],
index 272d02d4068cefa8cd99534d342b47f57f2c7d69..0fbfb399d77ab850f456b86869c4b42b7d59f97f 100644 (file)
@@ -3,6 +3,7 @@
 namespace Civi\Api4\Action\Afform;
 
 use Civi\Api4\Generic\Result;
+use CRM_Afform_ExtensionUtil as E;
 
 /**
  * @inheritDoc
@@ -32,8 +33,7 @@ class Revert extends \Civi\Api4\Generic\BasicBatchAction {
     _afform_clear();
 
     if ($this->flushManaged) {
-      // FIXME: more targeted reconciliation
-      \CRM_Core_ManagedEntities::singleton()->reconcile();
+      \CRM_Core_ManagedEntities::singleton()->reconcile(E::LONG_NAME);
     }
     if ($this->flushMenu) {
       \CRM_Core_Menu::store();
index 286ca52c3ed450ff89a6a0b69a84742070aecd61..0d109351af5793c78e3def04dd89ea1c3c822d32 100644 (file)
@@ -2,6 +2,8 @@
 
 namespace Civi\Api4\Utils;
 
+use CRM_Afform_ExtensionUtil as E;
+
 /**
  * Class AfformSaveTrait
  * @package Civi\Api4\Action\Afform
@@ -70,8 +72,7 @@ trait AfformSaveTrait {
       $isChanged('is_dashlet') ||
       (!empty($meta['is_dashlet']) && $isChanged('title'))
     ) {
-      // FIXME: more targeted reconciliation
-      \CRM_Core_ManagedEntities::singleton()->reconcile();
+      \CRM_Core_ManagedEntities::singleton()->reconcile(E::LONG_NAME);
     }
 
     // Right now, permission-checks are completely on-demand.
index ee238e7797200854d27cbcf4478d22e5646874bf..946aed53c0db1d397c1f0f20268048e89641a1fe 100644 (file)
@@ -383,7 +383,7 @@ class ManagedEntityTest extends UnitTestCase implements TransactionalInterface,
   }
 
   public function testManagedNavigationWeights() {
-    $this->_managedEntities = [
+    $managedEntities = [
       [
         'module' => 'unit.test.fake.ext',
         'name' => 'Navigation_Test_Parent',
@@ -474,13 +474,14 @@ class ManagedEntityTest extends UnitTestCase implements TransactionalInterface,
         ],
       ],
     ];
+    $this->_managedEntities = $managedEntities;
 
     // Throw a monkey wrench by placing duplicates in another domain
     $d2 = Domain::create(FALSE)
       ->addValue('name', 'Decoy domain')
       ->addValue('version', \CRM_Utils_System::version())
       ->execute()->single();
-    foreach ($this->_managedEntities as $item) {
+    foreach ($managedEntities as $item) {
       $decoys[] = civicrm_api4('Navigation', 'create', [
         'checkPermissions' => FALSE,
         'values' => ['domain_id' => $d2['id']] + $item['params']['values'],
@@ -535,6 +536,8 @@ class ManagedEntityTest extends UnitTestCase implements TransactionalInterface,
     $allModules = [
       new \CRM_Core_Module('unit.test.fake.ext', FALSE),
     ];
+    // If module is disabled it will not run hook_civicrm_managed.
+    $this->_managedEntities = [];
     (new \CRM_Core_ManagedEntities($allModules))->reconcile();
 
     // Children's weight should have been unaffected, but they should be disabled
@@ -556,6 +559,7 @@ class ManagedEntityTest extends UnitTestCase implements TransactionalInterface,
     $allModules = [
       new \CRM_Core_Module('unit.test.fake.ext', TRUE),
     ];
+    $this->_managedEntities = $managedEntities;
     (new \CRM_Core_ManagedEntities($allModules))->reconcile();
 
     // Children's weight should have been unaffected, but they should be enabled