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(); | |
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']); | |
265 | } | |
266 | } | |
267 |