preliminary whitespace cleanup
[civicrm-core.git] / CRM / Core / ManagedEntities.php
CommitLineData
6a488035
TO
1<?php
2
3/**
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.
7 */
8class CRM_Core_ManagedEntities {
9 /**
10 * @var array($status => array($name => CRM_Core_Module))
11 */
12 public $moduleIndex;
13
14 /**
15 * @var array per hook_civicrm_managed
16 */
17 public $declarations;
18
19 /**
20 * Get an instance
21 */
22 public static function singleton($fresh = FALSE) {
23 static $singleton;
24 if ($fresh || !$singleton) {
25 $declarations = array();
26 CRM_Utils_Hook::managed($declarations);
27 $singleton = new CRM_Core_ManagedEntities(CRM_Core_Module::getAll(), $declarations);
28 }
29 return $singleton;
30 }
31
32 /**
33 * @param $modules array CRM_Core_Module
34 * @param $declarations array per hook_civicrm_managed
35 */
36 public function __construct($modules, $declarations) {
37 $this->moduleIndex = self::createModuleIndex($modules);
38 $this->declarations = self::cleanDeclarations($declarations);
39 }
40
41 /**
42 * Read the managed entity
43 */
44 public function get($moduleName, $name) {
45 $dao = new CRM_Core_DAO_Managed();
46 $dao->module = $moduleName;
47 $dao->name = $name;
48 if ($dao->find(TRUE)) {
49 $params = array(
50 'version' => 3,
51 'id' => $dao->entity_id,
52 );
53 $result = civicrm_api($dao->entity_type, 'getsingle', $params);
54 if ($result['is_error']) {
55 $this->onApiError($params, $result);
56 } else {
57 return $result;
58 }
59 } else {
60 return NULL;
61 }
62 }
63
64 public function reconcile() {
65 if ($error = $this->validate($this->declarations)) {
66 throw new Exception($error);
67 }
68 $this->reconcileEnabledModules();
69 $this->reconcileDisabledModules();
70 $this->reconcileUnknownModules();
71 }
72
73
74 public function reconcileEnabledModules() {
75 // Note: any thing currently declared is necessarily from
76 // an active module -- because we got it from a hook!
77
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
85 } else {
86 throw new Exception("Entity declaration references invalid or inactive module name [$moduleName]");
87 }
88 }
89 }
90
91 /**
92 * Create, update, and delete entities declared by an active module
93 *
94 * @param $module string
95 * @param $todos array $name => array()
96 */
97 public function reconcileEnabledModule(CRM_Core_Module $module, $todos) {
98 $dao = new CRM_Core_DAO_Managed();
99 $dao->module = $module->name;
100 $dao->find();
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);
109 }
110
111 unset($todos[$dao->name]);
112 } else {
113 // remove stale entity; not in $todos
114 $params = array(
115 'version' => 3,
116 'id' => $dao->entity_id,
117 );
118 $result = civicrm_api($dao->entity_type, 'delete', $params);
119 if ($result['is_error']) {
120 $this->onApiError($params, $result);
121 }
122
123 CRM_Core_DAO::executeQuery('DELETE FROM civicrm_managed WHERE id = %1', array(
124 1 => array($dao->id, 'Integer')
125 ));
126 }
127 }
128
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);
134 }
135
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'];
141 $dao->save();
142 }
143 }
144
145 public function reconcileDisabledModules() {
146 if (empty($this->moduleIndex[FALSE])) {
147 return;
148 }
149
150 $in = CRM_Core_DAO::escapeStrings(array_keys($this->moduleIndex[FALSE]));
151 $dao = new CRM_Core_DAO_Managed();
152 $dao->whereAdd("module in ($in)");
153 $dao->find();
154 while ($dao->fetch()) {
155 // FIXME: if ($dao->entity_type supports is_active) {
156 if (TRUE) {
157 // FIXME cascading for payproc types?
158 $params = array(
159 'version' => 3,
160 'id' => $dao->entity_id,
161 'is_active' => 0,
162 );
163 $result = civicrm_api($dao->entity_type, 'create', $params);
164 if ($result['is_error']) {
165 $this->onApiError($params, $result);
166 }
167 }
168 }
169 }
170
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]));
175 }
176 if (array_key_exists(1, $this->moduleIndex) && is_array($this->moduleIndex[1])) {
177 $knownModules = array_merge($knownModules, array_keys($this->moduleIndex[1]));
178
179 }
180
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)");
185 }
186 $dao->find();
187 while ($dao->fetch()) {
188 $params = array(
189 'version' => 3,
190 'id' => $dao->entity_id,
191 );
192 $result = civicrm_api($dao->entity_type, 'delete', $params);
193 if ($result['is_error']) {
194 $this->onApiError($params, $result);
195 }
196
197 CRM_Core_DAO::executeQuery('DELETE FROM civicrm_managed WHERE id = %1', array(
198 1 => array($dao->id, 'Integer')
199 ));
200 }
201 }
202
203 /**
204 * @return array indexed by is_active,name
205 */
206 protected static function createModuleIndex($modules) {
207 $result = array();
208 foreach ($modules as $module) {
209 $result[$module->is_active][$module->name] = $module;
210 }
211 return $result;
212 }
213
214 /**
215 * @return array indexed by module,name
216 */
217 protected static function createDeclarationIndex($moduleIndex, $declarations) {
218 $result = array();
219 if (!isset($moduleIndex[TRUE])) {
220 return $result;
221 }
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();
226 }
227 }
228 foreach ($declarations as $declaration) {
229 $result[$declaration['module']][$declaration['name']] = $declaration;
230 }
231 return $result;
232 }
233
234 /**
235 * @return mixed string on error, or FALSE
236 */
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");
243 }
244 }
245 // FIXME: validate that each 'module' is known
246 }
247 return FALSE;
248 }
249
250 protected static function cleanDeclarations($declarations) {
251 foreach ($declarations as $name => &$declare) {
252 if (!array_key_exists('name', $declare)) {
253 $declare['name'] = $name;
254 }
255 }
256 return $declarations;
257 }
258
259 protected function onApiError($params, $result) {
260 CRM_Core_Error::debug_var('ManagedEntities_failed', array(
261 'params' => $params,
262 'result' => $result,
263 ));
264 throw new Exception('API error: ' . $result['error_message']);
cbb7c7e0 265 }
6a488035
TO
266}
267