4 * The ManagedEntities system allows modules to add records to the database
5 * declaratively. Those records will be automatically inserted, updated,
6 * deactivated, and deleted in tandem with their modules.
8 class CRM_Core_ManagedEntities
{
10 * @var array($status => array($name => CRM_Core_Module))
15 * @var array per hook_civicrm_managed
22 public static function singleton($fresh = FALSE) {
24 if ($fresh ||
!$singleton) {
25 $declarations = array();
26 foreach (CRM_Core_Component
::getEnabledComponents() as $component) {
27 $declarations = array_merge($declarations, $component->getManagedEntities());
29 CRM_Utils_Hook
::managed($declarations);
30 $singleton = new CRM_Core_ManagedEntities(CRM_Core_Module
::getAll(), $declarations);
36 * @param $modules array CRM_Core_Module
37 * @param $declarations array per hook_civicrm_managed
39 public function __construct($modules, $declarations) {
40 $this->moduleIndex
= self
::createModuleIndex($modules);
41 $this->declarations
= self
::cleanDeclarations($declarations);
45 * Read the managed entity
47 public function get($moduleName, $name) {
48 $dao = new CRM_Core_DAO_Managed();
49 $dao->module
= $moduleName;
51 if ($dao->find(TRUE)) {
53 'id' => $dao->entity_id
,
56 $result = civicrm_api3($dao->entity_type
, 'getsingle', $params);
58 catch (Exception
$e) {
59 $this->onApiError($params, $result);
67 public function reconcile() {
68 if ($error = $this->validate($this->declarations
)) {
69 throw new Exception($error);
71 $this->reconcileEnabledModules();
72 $this->reconcileDisabledModules();
73 $this->reconcileUnknownModules();
77 public function reconcileEnabledModules() {
78 // Note: any thing currently declared is necessarily from
79 // an active module -- because we got it from a hook!
81 // index by moduleName,name
82 $decls = self
::createDeclarationIndex($this->moduleIndex
, $this->declarations
);
83 foreach ($decls as $moduleName => $todos) {
84 if (isset($this->moduleIndex
[TRUE][$moduleName])) {
85 $this->reconcileEnabledModule($this->moduleIndex
[TRUE][$moduleName], $todos);
86 } elseif (isset($this->moduleIndex
[FALSE][$moduleName])) {
87 // do nothing -- module should get swept up later
89 throw new Exception("Entity declaration references invalid or inactive module name [$moduleName]");
95 * Create, update, and delete entities declared by an active module
97 * @param $module string
98 * @param $todos array $name => array()
100 public function reconcileEnabledModule(CRM_Core_Module
$module, $todos) {
101 $dao = new CRM_Core_DAO_Managed();
102 $dao->module
= $module->name
;
104 while ($dao->fetch()) {
105 if (isset($todos[$dao->name
]) && $todos[$dao->name
]) {
106 // update existing entity; remove from $todos
107 $defaults = array('id' => $dao->entity_id
, 'is_active' => 1); // FIXME: test whether is_active is valid
108 $params = array_merge($defaults, $todos[$dao->name
]['params']);
109 $result = civicrm_api($dao->entity_type
, 'create', $params);
110 if ($result['is_error']) {
111 $this->onApiError($params, $result);
114 unset($todos[$dao->name
]);
116 // remove stale entity; not in $todos
119 'id' => $dao->entity_id
,
121 $result = civicrm_api($dao->entity_type
, 'delete', $params);
122 if ($result['is_error']) {
123 $this->onApiError($params, $result);
126 CRM_Core_DAO
::executeQuery('DELETE FROM civicrm_managed WHERE id = %1', array(
127 1 => array($dao->id
, 'Integer')
132 // create new entities from leftover $todos
133 foreach ($todos as $name => $todo) {
134 $result = civicrm_api($todo['entity'], 'create', $todo['params']);
135 if ($result['is_error']) {
136 $this->onApiError($todo['params'], $result);
139 $dao = new CRM_Core_DAO_Managed();
140 $dao->module
= $todo['module'];
141 $dao->name
= $todo['name'];
142 $dao->entity_type
= $todo['entity'];
143 $dao->entity_id
= $result['id'];
148 public function reconcileDisabledModules() {
149 if (empty($this->moduleIndex
[FALSE])) {
153 $in = CRM_Core_DAO
::escapeStrings(array_keys($this->moduleIndex
[FALSE]));
154 $dao = new CRM_Core_DAO_Managed();
155 $dao->whereAdd("module in ($in)");
157 while ($dao->fetch()) {
158 // FIXME: if ($dao->entity_type supports is_active) {
160 // FIXME cascading for payproc types?
163 'id' => $dao->entity_id
,
166 $result = civicrm_api($dao->entity_type
, 'create', $params);
167 if ($result['is_error']) {
168 $this->onApiError($params, $result);
174 public function reconcileUnknownModules() {
175 $knownModules = array();
176 if (array_key_exists(0, $this->moduleIndex
) && is_array($this->moduleIndex
[0])) {
177 $knownModules = array_merge($knownModules, array_keys($this->moduleIndex
[0]));
179 if (array_key_exists(1, $this->moduleIndex
) && is_array($this->moduleIndex
[1])) {
180 $knownModules = array_merge($knownModules, array_keys($this->moduleIndex
[1]));
184 $dao = new CRM_Core_DAO_Managed();
185 if (!empty($knownModules)) {
186 $in = CRM_Core_DAO
::escapeStrings($knownModules);
187 $dao->whereAdd("module NOT IN ($in)");
190 while ($dao->fetch()) {
193 'id' => $dao->entity_id
,
195 $result = civicrm_api($dao->entity_type
, 'delete', $params);
196 if ($result['is_error']) {
197 $this->onApiError($params, $result);
200 CRM_Core_DAO
::executeQuery('DELETE FROM civicrm_managed WHERE id = %1', array(
201 1 => array($dao->id
, 'Integer')
207 * @return array indexed by is_active,name
209 protected static function createModuleIndex($modules) {
211 foreach ($modules as $module) {
212 $result[$module->is_active
][$module->name
] = $module;
218 * @return array indexed by module,name
220 protected static function createDeclarationIndex($moduleIndex, $declarations) {
222 if (!isset($moduleIndex[TRUE])) {
225 foreach ($moduleIndex[TRUE] as $moduleName => $module) {
226 if ($module->is_active
) {
227 // need an empty array() for all active modules, even if there are no current $declarations
228 $result[$moduleName] = array();
231 foreach ($declarations as $declaration) {
232 $result[$declaration['module']][$declaration['name']] = $declaration;
238 * @return mixed string on error, or FALSE
240 protected static function validate($declarations) {
241 foreach ($declarations as $declare) {
242 foreach (array('name', 'module', 'entity', 'params') as $key) {
243 if (empty($declare[$key])) {
244 $str = print_r($declare, TRUE);
245 return ("Managed Entity is missing field \"$key\": $str");
248 // FIXME: validate that each 'module' is known
253 protected static function cleanDeclarations($declarations) {
254 foreach ($declarations as $name => &$declare) {
255 if (!array_key_exists('name', $declare)) {
256 $declare['name'] = $name;
259 return $declarations;
262 protected function onApiError($params, $result) {
263 CRM_Core_Error
::debug_var('ManagedEntities_failed', array(
267 throw new Exception('API error: ' . $result['error_message']);