CRM_Contribution_BAO_Contribution - Fix test failure
[civicrm-core.git] / CRM / Core / ManagedEntities.php
index c06e06066a555096ac01187034a1426e3801f83f..10a7ff36f2080707a6fb2dc455d43e62f469258f 100644 (file)
@@ -6,15 +6,24 @@
  * deactivated, and deleted in tandem with their modules.
  */
 class CRM_Core_ManagedEntities {
+
+  public static function getCleanupOptions() {
+    return array(
+      'always' => ts('Always'),
+      'never' => ts('Never'),
+      'unused' => ts('If Unused'),
+    );
+  }
+
   /**
    * @var array($status => array($name => CRM_Core_Module))
    */
-  public $moduleIndex;
+  protected $moduleIndex;
 
   /**
    * @var array per hook_civicrm_managed
    */
-  public $declarations;
+  protected $declarations;
 
   /**
    * Get an instance
@@ -22,28 +31,43 @@ class CRM_Core_ManagedEntities {
   public static function singleton($fresh = FALSE) {
     static $singleton;
     if ($fresh || !$singleton) {
-      $declarations = array();
-      foreach (CRM_Core_Component::getEnabledComponents() as $component) {
-        /** @var CRM_Core_Component_Info $component */
-        $declarations = array_merge($declarations, $component->getManagedEntities());
-      }
-      CRM_Utils_Hook::managed($declarations);
-      $singleton = new CRM_Core_ManagedEntities(CRM_Core_Module::getAll(), $declarations);
+      $singleton = new CRM_Core_ManagedEntities(CRM_Core_Module::getAll(), NULL);
     }
     return $singleton;
   }
 
+  /**
+   * Perform an asynchronous reconciliation when the transaction ends.
+   */
+  public static function scheduleReconcilation() {
+    CRM_Core_Transaction::addCallback(
+      CRM_Core_Transaction::PHASE_POST_COMMIT,
+      function () {
+        CRM_Core_ManagedEntities::singleton(TRUE)->reconcile();
+      },
+      array(),
+      'ManagedEntities::reconcile'
+    );
+  }
+
   /**
    * @param array $modules CRM_Core_Module
    * @param array $declarations per hook_civicrm_managed
    */
   public function __construct($modules, $declarations) {
     $this->moduleIndex = self::createModuleIndex($modules);
-    $this->declarations = self::cleanDeclarations($declarations);
+
+    if ($declarations !== NULL) {
+      $this->declarations = self::cleanDeclarations($declarations);
+    } else {
+      $this->declarations = NULL;
+    }
   }
 
   /**
    * Read the managed entity
+   *
+   * @return array|NULL API representation, or NULL if the entity does not exist
    */
   public function get($moduleName, $name) {
     $dao = new CRM_Core_DAO_Managed();
@@ -58,7 +82,7 @@ class CRM_Core_ManagedEntities {
         $result = civicrm_api3($dao->entity_type, 'getsingle', $params);
       }
       catch (Exception $e) {
-        $this->onApiError($params, $result);
+        $this->onApiError($dao->entity_type, 'getsingle', $params, $result);
       }
       return $result;
     } else {
@@ -67,7 +91,7 @@ class CRM_Core_ManagedEntities {
   }
 
   public function reconcile() {
-    if ($error = $this->validate($this->declarations)) {
+    if ($error = $this->validate($this->getDeclarations())) {
       throw new Exception($error);
     }
     $this->reconcileEnabledModules();
@@ -81,7 +105,7 @@ class CRM_Core_ManagedEntities {
     // an active module -- because we got it from a hook!
 
     // index by moduleName,name
-    $decls = self::createDeclarationIndex($this->moduleIndex, $this->declarations);
+    $decls = self::createDeclarationIndex($this->moduleIndex, $this->getDeclarations());
     foreach ($decls as $moduleName => $todos) {
       if (isset($this->moduleIndex[TRUE][$moduleName])) {
         $this->reconcileEnabledModule($this->moduleIndex[TRUE][$moduleName], $todos);
@@ -164,7 +188,7 @@ class CRM_Core_ManagedEntities {
   public function insertNewEntity($todo) {
     $result = civicrm_api($todo['entity'], 'create', $todo['params']);
     if ($result['is_error']) {
-      $this->onApiError($todo['params'], $result);
+      $this->onApiError($todo['entity'], 'create', $todo['params'], $result);
     }
 
     $dao = new CRM_Core_DAO_Managed();
@@ -172,6 +196,7 @@ class CRM_Core_ManagedEntities {
     $dao->name = $todo['name'];
     $dao->entity_type = $todo['entity'];
     $dao->entity_id = $result['id'];
+    $dao->cleanup = CRM_Utils_Array::value('cleanup', $todo);
     $dao->save();
   }
 
@@ -193,9 +218,14 @@ class CRM_Core_ManagedEntities {
       $params = array_merge($defaults, $todo['params']);
       $result = civicrm_api($dao->entity_type, 'create', $params);
       if ($result['is_error']) {
-        $this->onApiError($params, $result);
+        $this->onApiError($dao->entity_type, 'create',$params, $result);
       }
     }
+
+    if (isset($todo['cleanup'])) {
+      $dao->cleanup = $todo['cleanup'];
+      $dao->update();
+    }
   }
 
   /**
@@ -215,7 +245,7 @@ class CRM_Core_ManagedEntities {
       );
       $result = civicrm_api($dao->entity_type, 'create', $params);
       if ($result['is_error']) {
-        $this->onApiError($params, $result);
+        $this->onApiError($dao->entity_type, 'create',$params, $result);
       }
     }
   }
@@ -226,18 +256,58 @@ class CRM_Core_ManagedEntities {
    * @param CRM_Core_DAO_Managed $dao
    */
   public function removeStaleEntity($dao) {
-    $params = array(
-      'version' => 3,
-      'id' => $dao->entity_id,
-    );
-    $result = civicrm_api($dao->entity_type, 'delete', $params);
-    if ($result['is_error']) {
-      $this->onApiError($params, $result);
+    $policy = empty($dao->cleanup) ? 'always' : $dao->cleanup;
+    switch ($policy) {
+      case 'always':
+        $doDelete = TRUE;
+        break;
+      case 'never':
+        $doDelete = FALSE;
+        break;
+      case 'unused':
+        $getRefCount = civicrm_api3($dao->entity_type, 'getrefcount', array(
+          'debug' => 1,
+          'id' => $dao->entity_id
+        ));
+
+        $total = 0;
+        foreach ($getRefCount['values'] as $refCount) {
+          $total += $refCount['count'];
+        }
+
+        $doDelete = ($total == 0);
+        break;
+      default:
+        throw new \Exception('Unrecognized cleanup policy: ' . $policy);
     }
 
-    CRM_Core_DAO::executeQuery('DELETE FROM civicrm_managed WHERE id = %1', array(
-      1 => array($dao->id, 'Integer')
-    ));
+    if ($doDelete) {
+      $params = array(
+        'version' => 3,
+        'id' => $dao->entity_id,
+      );
+      $result = civicrm_api($dao->entity_type, 'delete', $params);
+      if ($result['is_error']) {
+        $this->onApiError($dao->entity_type, 'delete', $params, $result);
+      }
+
+      CRM_Core_DAO::executeQuery('DELETE FROM civicrm_managed WHERE id = %1', array(
+        1 => array($dao->id, 'Integer')
+      ));
+    }
+  }
+
+  public function getDeclarations() {
+    if ($this->declarations === NULL) {
+      $this->declarations = array();
+      foreach (CRM_Core_Component::getEnabledComponents() as $component) {
+        /** @var CRM_Core_Component_Info $component */
+        $this->declarations = array_merge($this->declarations, $component->getManagedEntities());
+      }
+      CRM_Utils_Hook::managed($this->declarations);
+      $this->declarations = self::cleanDeclarations($this->declarations);
+    }
+    return $this->declarations;
   }
 
   /**
@@ -309,13 +379,17 @@ class CRM_Core_ManagedEntities {
   }
 
   /**
-   * @param $params
-   * @param $result
+   * @param string $entity
+   * @param string $action
+   * @param array $params
+   * @param array $result
    *
    * @throws Exception
    */
-  protected function onApiError($params, $result) {
+  protected function onApiError($entity, $action, $params, $result) {
     CRM_Core_Error::debug_var('ManagedEntities_failed', array(
+      'entity' => $entity,
+      'action' => $action,
       'params' => $params,
       'result' => $result,
     ));