Commit | Line | Data |
---|---|---|
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 | */ | |
8 | class 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(); | |
3bd831aa TO |
26 | foreach (CRM_Core_Component::getEnabledComponents() as $component) { |
27 | $declarations = array_merge($declarations, $component->getManagedEntities()); | |
28 | } | |
6a488035 TO |
29 | CRM_Utils_Hook::managed($declarations); |
30 | $singleton = new CRM_Core_ManagedEntities(CRM_Core_Module::getAll(), $declarations); | |
31 | } | |
32 | return $singleton; | |
33 | } | |
34 | ||
35 | /** | |
36 | * @param $modules array CRM_Core_Module | |
37 | * @param $declarations array per hook_civicrm_managed | |
38 | */ | |
39 | public function __construct($modules, $declarations) { | |
40 | $this->moduleIndex = self::createModuleIndex($modules); | |
41 | $this->declarations = self::cleanDeclarations($declarations); | |
42 | } | |
43 | ||
44 | /** | |
45 | * Read the managed entity | |
46 | */ | |
47 | public function get($moduleName, $name) { | |
48 | $dao = new CRM_Core_DAO_Managed(); | |
49 | $dao->module = $moduleName; | |
50 | $dao->name = $name; | |
51 | if ($dao->find(TRUE)) { | |
52 | $params = array( | |
53 | 'version' => 3, | |
54 | 'id' => $dao->entity_id, | |
55 | ); | |
56 | $result = civicrm_api($dao->entity_type, 'getsingle', $params); | |
57 | if ($result['is_error']) { | |
58 | $this->onApiError($params, $result); | |
59 | } else { | |
60 | return $result; | |
61 | } | |
62 | } else { | |
63 | return NULL; | |
64 | } | |
65 | } | |
66 | ||
67 | public function reconcile() { | |
68 | if ($error = $this->validate($this->declarations)) { | |
69 | throw new Exception($error); | |
70 | } | |
71 | $this->reconcileEnabledModules(); | |
72 | $this->reconcileDisabledModules(); | |
73 | $this->reconcileUnknownModules(); | |
74 | } | |
75 | ||
76 | ||
77 | public function reconcileEnabledModules() { | |
78 | // Note: any thing currently declared is necessarily from | |
79 | // an active module -- because we got it from a hook! | |
80 | ||
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 | |
88 | } else { | |
89 | throw new Exception("Entity declaration references invalid or inactive module name [$moduleName]"); | |
90 | } | |
91 | } | |
92 | } | |
93 | ||
94 | /** | |
95 | * Create, update, and delete entities declared by an active module | |
96 | * | |
97 | * @param $module string | |
98 | * @param $todos array $name => array() | |
99 | */ | |
100 | public function reconcileEnabledModule(CRM_Core_Module $module, $todos) { | |
101 | $dao = new CRM_Core_DAO_Managed(); | |
102 | $dao->module = $module->name; | |
103 | $dao->find(); | |
104 | while ($dao->fetch()) { | |
3bd831aa | 105 | if (isset($todos[$dao->name]) && $todos[$dao->name]) { |
6a488035 TO |
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); | |
112 | } | |
113 | ||
114 | unset($todos[$dao->name]); | |
115 | } else { | |
116 | // remove stale entity; not in $todos | |
117 | $params = array( | |
118 | 'version' => 3, | |
119 | 'id' => $dao->entity_id, | |
120 | ); | |
121 | $result = civicrm_api($dao->entity_type, 'delete', $params); | |
122 | if ($result['is_error']) { | |
123 | $this->onApiError($params, $result); | |
124 | } | |
125 | ||
126 | CRM_Core_DAO::executeQuery('DELETE FROM civicrm_managed WHERE id = %1', array( | |
127 | 1 => array($dao->id, 'Integer') | |
128 | )); | |
129 | } | |
130 | } | |
131 | ||
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); | |
137 | } | |
138 | ||
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']; | |
144 | $dao->save(); | |
145 | } | |
146 | } | |
147 | ||
148 | public function reconcileDisabledModules() { | |
149 | if (empty($this->moduleIndex[FALSE])) { | |
150 | return; | |
151 | } | |
152 | ||
153 | $in = CRM_Core_DAO::escapeStrings(array_keys($this->moduleIndex[FALSE])); | |
154 | $dao = new CRM_Core_DAO_Managed(); | |
155 | $dao->whereAdd("module in ($in)"); | |
156 | $dao->find(); | |
157 | while ($dao->fetch()) { | |
158 | // FIXME: if ($dao->entity_type supports is_active) { | |
159 | if (TRUE) { | |
160 | // FIXME cascading for payproc types? | |
161 | $params = array( | |
162 | 'version' => 3, | |
163 | 'id' => $dao->entity_id, | |
164 | 'is_active' => 0, | |
165 | ); | |
166 | $result = civicrm_api($dao->entity_type, 'create', $params); | |
167 | if ($result['is_error']) { | |
168 | $this->onApiError($params, $result); | |
169 | } | |
170 | } | |
171 | } | |
172 | } | |
173 | ||
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])); | |
178 | } | |
179 | if (array_key_exists(1, $this->moduleIndex) && is_array($this->moduleIndex[1])) { | |
180 | $knownModules = array_merge($knownModules, array_keys($this->moduleIndex[1])); | |
181 | ||
182 | } | |
183 | ||
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)"); | |
188 | } | |
189 | $dao->find(); | |
190 | while ($dao->fetch()) { | |
191 | $params = array( | |
192 | 'version' => 3, | |
193 | 'id' => $dao->entity_id, | |
194 | ); | |
195 | $result = civicrm_api($dao->entity_type, 'delete', $params); | |
196 | if ($result['is_error']) { | |
197 | $this->onApiError($params, $result); | |
198 | } | |
199 | ||
200 | CRM_Core_DAO::executeQuery('DELETE FROM civicrm_managed WHERE id = %1', array( | |
201 | 1 => array($dao->id, 'Integer') | |
202 | )); | |
203 | } | |
204 | } | |
205 | ||
206 | /** | |
207 | * @return array indexed by is_active,name | |
208 | */ | |
209 | protected static function createModuleIndex($modules) { | |
210 | $result = array(); | |
211 | foreach ($modules as $module) { | |
212 | $result[$module->is_active][$module->name] = $module; | |
213 | } | |
214 | return $result; | |
215 | } | |
216 | ||
217 | /** | |
218 | * @return array indexed by module,name | |
219 | */ | |
220 | protected static function createDeclarationIndex($moduleIndex, $declarations) { | |
221 | $result = array(); | |
222 | if (!isset($moduleIndex[TRUE])) { | |
223 | return $result; | |
224 | } | |
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(); | |
229 | } | |
230 | } | |
231 | foreach ($declarations as $declaration) { | |
232 | $result[$declaration['module']][$declaration['name']] = $declaration; | |
233 | } | |
234 | return $result; | |
235 | } | |
236 | ||
237 | /** | |
238 | * @return mixed string on error, or FALSE | |
239 | */ | |
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"); | |
246 | } | |
247 | } | |
248 | // FIXME: validate that each 'module' is known | |
249 | } | |
250 | return FALSE; | |
251 | } | |
252 | ||
253 | protected static function cleanDeclarations($declarations) { | |
254 | foreach ($declarations as $name => &$declare) { | |
255 | if (!array_key_exists('name', $declare)) { | |
256 | $declare['name'] = $name; | |
257 | } | |
258 | } | |
259 | return $declarations; | |
260 | } | |
261 | ||
262 | protected function onApiError($params, $result) { | |
263 | CRM_Core_Error::debug_var('ManagedEntities_failed', array( | |
264 | 'params' => $params, | |
265 | 'result' => $result, | |
266 | )); | |
267 | throw new Exception('API error: ' . $result['error_message']); | |
cbb7c7e0 | 268 | } |
6a488035 TO |
269 | } |
270 |