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 CRM_Utils_Hook
::managed($declarations);
27 $singleton = new CRM_Core_ManagedEntities(CRM_Core_Module
::getAll(), $declarations);
33 * @param $modules array CRM_Core_Module
34 * @param $declarations array per hook_civicrm_managed
36 public function __construct($modules, $declarations) {
37 $this->moduleIndex
= self
::createModuleIndex($modules);
38 $this->declarations
= self
::cleanDeclarations($declarations);
42 * Read the managed entity
44 public function get($moduleName, $name) {
45 $dao = new CRM_Core_DAO_Managed();
46 $dao->module
= $moduleName;
48 if ($dao->find(TRUE)) {
51 'id' => $dao->entity_id
,
53 $result = civicrm_api($dao->entity_type
, 'getsingle', $params);
54 if ($result['is_error']) {
55 $this->onApiError($params, $result);
64 public function reconcile() {
65 if ($error = $this->validate($this->declarations
)) {
66 throw new Exception($error);
68 $this->reconcileEnabledModules();
69 $this->reconcileDisabledModules();
70 $this->reconcileUnknownModules();
74 public function reconcileEnabledModules() {
75 // Note: any thing currently declared is necessarily from
76 // an active module -- because we got it from a hook!
78 // index by moduleName,name
79 $decls = self
::createDeclarationIndex($this->moduleIndex
, $this->declarations
);
80 foreach ($decls as $moduleName => $todos) {
81 if (isset($this->moduleIndex
[TRUE][$moduleName])) {
82 $this->reconcileEnabledModule($this->moduleIndex
[TRUE][$moduleName], $todos);
83 } elseif (isset($this->moduleIndex
[FALSE][$moduleName])) {
84 // do nothing -- module should get swept up later
86 throw new Exception("Entity declaration references invalid or inactive module name [$moduleName]");
92 * Create, update, and delete entities declared by an active module
94 * @param $module string
95 * @param $todos array $name => array()
97 public function reconcileEnabledModule(CRM_Core_Module
$module, $todos) {
98 $dao = new CRM_Core_DAO_Managed();
99 $dao->module
= $module->name
;
101 while ($dao->fetch()) {
102 if ($todos[$dao->name
]) {
103 // update existing entity; remove from $todos
104 $defaults = array('id' => $dao->entity_id
, 'is_active' => 1); // FIXME: test whether is_active is valid
105 $params = array_merge($defaults, $todos[$dao->name
]['params']);
106 $result = civicrm_api($dao->entity_type
, 'create', $params);
107 if ($result['is_error']) {
108 $this->onApiError($params, $result);
111 unset($todos[$dao->name
]);
113 // remove stale entity; not in $todos
116 'id' => $dao->entity_id
,
118 $result = civicrm_api($dao->entity_type
, 'delete', $params);
119 if ($result['is_error']) {
120 $this->onApiError($params, $result);
123 CRM_Core_DAO
::executeQuery('DELETE FROM civicrm_managed WHERE id = %1', array(
124 1 => array($dao->id
, 'Integer')
129 // create new entities from leftover $todos
130 foreach ($todos as $name => $todo) {
131 $result = civicrm_api($todo['entity'], 'create', $todo['params']);
132 if ($result['is_error']) {
133 $this->onApiError($todo['params'], $result);
136 $dao = new CRM_Core_DAO_Managed();
137 $dao->module
= $todo['module'];
138 $dao->name
= $todo['name'];
139 $dao->entity_type
= $todo['entity'];
140 $dao->entity_id
= $result['id'];
145 public function reconcileDisabledModules() {
146 if (empty($this->moduleIndex
[FALSE])) {
150 $in = CRM_Core_DAO
::escapeStrings(array_keys($this->moduleIndex
[FALSE]));
151 $dao = new CRM_Core_DAO_Managed();
152 $dao->whereAdd("module in ($in)");
154 while ($dao->fetch()) {
155 // FIXME: if ($dao->entity_type supports is_active) {
157 // FIXME cascading for payproc types?
160 'id' => $dao->entity_id
,
163 $result = civicrm_api($dao->entity_type
, 'create', $params);
164 if ($result['is_error']) {
165 $this->onApiError($params, $result);
171 public function reconcileUnknownModules() {
172 $knownModules = array();
173 if (array_key_exists(0, $this->moduleIndex
) && is_array($this->moduleIndex
[0])) {
174 $knownModules = array_merge($knownModules, array_keys($this->moduleIndex
[0]));
176 if (array_key_exists(1, $this->moduleIndex
) && is_array($this->moduleIndex
[1])) {
177 $knownModules = array_merge($knownModules, array_keys($this->moduleIndex
[1]));
181 $dao = new CRM_Core_DAO_Managed();
182 if (!empty($knownModules)) {
183 $in = CRM_Core_DAO
::escapeStrings($knownModules);
184 $dao->whereAdd("module NOT IN ($in)");
187 while ($dao->fetch()) {
190 'id' => $dao->entity_id
,
192 $result = civicrm_api($dao->entity_type
, 'delete', $params);
193 if ($result['is_error']) {
194 $this->onApiError($params, $result);
197 CRM_Core_DAO
::executeQuery('DELETE FROM civicrm_managed WHERE id = %1', array(
198 1 => array($dao->id
, 'Integer')
204 * @return array indexed by is_active,name
206 protected static function createModuleIndex($modules) {
208 foreach ($modules as $module) {
209 $result[$module->is_active
][$module->name
] = $module;
215 * @return array indexed by module,name
217 protected static function createDeclarationIndex($moduleIndex, $declarations) {
219 if (!isset($moduleIndex[TRUE])) {
222 foreach ($moduleIndex[TRUE] as $moduleName => $module) {
223 if ($module->is_active
) {
224 // need an empty array() for all active modules, even if there are no current $declarations
225 $result[$moduleName] = array();
228 foreach ($declarations as $declaration) {
229 $result[$declaration['module']][$declaration['name']] = $declaration;
235 * @return mixed string on error, or FALSE
237 protected static function validate($declarations) {
238 foreach ($declarations as $declare) {
239 foreach (array('name', 'module', 'entity', 'params') as $key) {
240 if (empty($declare[$key])) {
241 $str = print_r($declare, TRUE);
242 return ("Managed Entity is missing field \"$key\": $str");
245 // FIXME: validate that each 'module' is known
250 protected static function cleanDeclarations($declarations) {
251 foreach ($declarations as $name => &$declare) {
252 if (!array_key_exists('name', $declare)) {
253 $declare['name'] = $name;
256 return $declarations;
259 protected function onApiError($params, $result) {
260 CRM_Core_Error
::debug_var('ManagedEntities_failed', array(
264 throw new Exception('API error: ' . $result['error_message']);