X-Git-Url: https://vcs.fsf.org/?a=blobdiff_plain;ds=sidebyside;f=CRM%2FCore%2FManagedEntities.php;h=a0935dcf28414dad3803ec998ab5c050588767ab;hb=5a4f674292060ff1f07ec653e2ec4bf915f6f6ef;hp=6d110fb95010d6f92d1d8d5d85f96ab5aecb5a79;hpb=03298d98322f2da05e2ab30cb0e2d5b90df47ab3;p=civicrm-core.git diff --git a/CRM/Core/ManagedEntities.php b/CRM/Core/ManagedEntities.php index 6d110fb950..a0935dcf28 100644 --- a/CRM/Core/ManagedEntities.php +++ b/CRM/Core/ManagedEntities.php @@ -6,43 +6,73 @@ * 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 + * @param bool $fresh + * @return \CRM_Core_ManagedEntities */ public static function singleton($fresh = FALSE) { static $singleton; if ($fresh || !$singleton) { - $declarations = array(); - foreach (CRM_Core_Component::getEnabledComponents() as $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; } /** - * @param $modules array CRM_Core_Module - * @param $declarations array per hook_civicrm_managed + * Perform an asynchronous reconciliation when the transaction ends. + */ + public static function scheduleReconciliation() { + 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(); @@ -52,20 +82,22 @@ class CRM_Core_ManagedEntities { $params = array( 'id' => $dao->entity_id, ); + $result = NULL; try { $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 { + } + else { return NULL; } } public function reconcile() { - if ($error = $this->validate($this->declarations)) { + if ($error = $this->validate($this->getDeclarations())) { throw new Exception($error); } $this->reconcileEnabledModules(); @@ -79,13 +111,15 @@ 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); - } elseif (isset($this->moduleIndex[FALSE][$moduleName])) { + } + elseif (isset($this->moduleIndex[FALSE][$moduleName])) { // do nothing -- module should get swept up later - } else { + } + else { throw new Exception("Entity declaration references invalid or inactive module name [$moduleName]"); } } @@ -95,7 +129,8 @@ class CRM_Core_ManagedEntities { * Create, update, and delete entities declared by an active module * * @param \CRM_Core_Module|string $module string - * @param $todos array $name => array() + * @param array $todos + * $name => array(). */ public function reconcileEnabledModule(CRM_Core_Module $module, $todos) { $dao = new CRM_Core_DAO_Managed(); @@ -104,44 +139,18 @@ class CRM_Core_ManagedEntities { while ($dao->fetch()) { if (isset($todos[$dao->name]) && $todos[$dao->name]) { // update existing entity; remove from $todos - $defaults = array('id' => $dao->entity_id, 'is_active' => 1); // FIXME: test whether is_active is valid - $params = array_merge($defaults, $todos[$dao->name]['params']); - $result = civicrm_api($dao->entity_type, 'create', $params); - if ($result['is_error']) { - $this->onApiError($params, $result); - } - + $this->updateExistingEntity($dao, $todos[$dao->name]); unset($todos[$dao->name]); - } else { + } + else { // remove stale entity; not in $todos - $params = array( - 'version' => 3, - 'id' => $dao->entity_id, - ); - $result = civicrm_api($dao->entity_type, 'delete', $params); - if ($result['is_error']) { - $this->onApiError($params, $result); - } - - CRM_Core_DAO::executeQuery('DELETE FROM civicrm_managed WHERE id = %1', array( - 1 => array($dao->id, 'Integer') - )); + $this->removeStaleEntity($dao); } } // create new entities from leftover $todos foreach ($todos as $name => $todo) { - $result = civicrm_api($todo['entity'], 'create', $todo['params']); - if ($result['is_error']) { - $this->onApiError($todo['params'], $result); - } - - $dao = new CRM_Core_DAO_Managed(); - $dao->module = $todo['module']; - $dao->name = $todo['name']; - $dao->entity_type = $todo['entity']; - $dao->entity_id = $result['id']; - $dao->save(); + $this->insertNewEntity($todo); } } @@ -155,19 +164,8 @@ class CRM_Core_ManagedEntities { $dao->whereAdd("module in ($in)"); $dao->find(); while ($dao->fetch()) { - // FIXME: if ($dao->entity_type supports is_active) { - if (TRUE) { - // FIXME cascading for payproc types? - $params = array( - 'version' => 3, - 'id' => $dao->entity_id, - 'is_active' => 0, - ); - $result = civicrm_api($dao->entity_type, 'create', $params); - if ($result['is_error']) { - $this->onApiError($params, $result); - } - } + $this->disableEntity($dao); + } } @@ -188,21 +186,142 @@ class CRM_Core_ManagedEntities { } $dao->find(); while ($dao->fetch()) { + $this->removeStaleEntity($dao); + } + } + + /** + * Create a new entity + * + * @param array $todo + * Entity specification (per hook_civicrm_managedEntities). + */ + public function insertNewEntity($todo) { + $result = civicrm_api($todo['entity'], 'create', $todo['params']); + if ($result['is_error']) { + $this->onApiError($todo['entity'], 'create', $todo['params'], $result); + } + + $dao = new CRM_Core_DAO_Managed(); + $dao->module = $todo['module']; + $dao->name = $todo['name']; + $dao->entity_type = $todo['entity']; + $dao->entity_id = $result['id']; + $dao->cleanup = CRM_Utils_Array::value('cleanup', $todo); + $dao->save(); + } + + /** + * Update an entity which (a) is believed to exist and which (b) ought to be active. + * + * @param CRM_Core_DAO_Managed $dao + * @param array $todo + * Entity specification (per hook_civicrm_managedEntities). + */ + public function updateExistingEntity($dao, $todo) { + $policy = CRM_Utils_Array::value('update', $todo, 'always'); + $doUpdate = ($policy == 'always'); + + if ($doUpdate) { + $defaults = array( + 'id' => $dao->entity_id, + 'is_active' => 1, // FIXME: test whether is_active is valid + ); + $params = array_merge($defaults, $todo['params']); + $result = civicrm_api($dao->entity_type, 'create', $params); + if ($result['is_error']) { + $this->onApiError($dao->entity_type, 'create',$params, $result); + } + } + + if (isset($todo['cleanup'])) { + $dao->cleanup = $todo['cleanup']; + $dao->update(); + } + } + + /** + * Update an entity which (a) is believed to exist and which (b) ought to be + * inactive. + * + * @param CRM_Core_DAO_Managed $dao + */ + public function disableEntity($dao) { + // FIXME: if ($dao->entity_type supports is_active) { + if (TRUE) { + // FIXME cascading for payproc types? + $params = array( + 'version' => 3, + 'id' => $dao->entity_id, + 'is_active' => 0, + ); + $result = civicrm_api($dao->entity_type, 'create', $params); + if ($result['is_error']) { + $this->onApiError($dao->entity_type, 'create',$params, $result); + } + } + } + + /** + * Remove a stale entity (if policy allows) + * + * @param CRM_Core_DAO_Managed $dao + */ + public function removeStaleEntity($dao) { + $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); + } + + if ($doDelete) { $params = array( 'version' => 3, 'id' => $dao->entity_id, ); $result = civicrm_api($dao->entity_type, 'delete', $params); if ($result['is_error']) { - $this->onApiError($params, $result); + $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') + 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; + } + /** * @param $modules * @@ -272,17 +391,20 @@ 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, )); throw new Exception('API error: ' . $result['error_message']); } } -