1 | <?php |
||||||
2 | |||||||
3 | require_once BASE_PATH . 'server/includes/core/class.encryptionstore.php'; |
||||||
4 | require_once 'zpushprops.php'; |
||||||
5 | |||||||
6 | /** |
||||||
7 | * PluginMDMModule Module. |
||||||
8 | */ |
||||||
9 | class PluginMDMModule extends Module { |
||||||
10 | // content data |
||||||
11 | public const FOLDERUUID = 1; |
||||||
12 | public const FOLDERTYPE = 2; |
||||||
13 | public const FOLDERBACKENDID = 5; |
||||||
14 | |||||||
15 | private $stateFolder; |
||||||
16 | private $deviceStates; |
||||||
17 | private $devices; |
||||||
18 | |||||||
19 | /** |
||||||
20 | * Constructor. |
||||||
21 | * |
||||||
22 | * @param int $id unique id |
||||||
23 | * @param array $data list of all actions |
||||||
24 | */ |
||||||
25 | public function __construct($id, $data) { |
||||||
26 | parent::__construct($id, $data); |
||||||
27 | $this->stateFolder = null; |
||||||
28 | $this->deviceStates = []; |
||||||
29 | $this->devices = []; |
||||||
30 | $this->setupDevices(); |
||||||
31 | } |
||||||
32 | |||||||
33 | /** |
||||||
34 | * Function sets up the array with the user's devices. |
||||||
35 | */ |
||||||
36 | public function setupDevices() { |
||||||
37 | $devices = []; |
||||||
38 | $stateFolder = $this->getStoreStateFolder(); |
||||||
39 | if ($stateFolder) { |
||||||
0 ignored issues
–
show
introduced
by
![]() |
|||||||
40 | $store = $GLOBALS["mapisession"]->getDefaultMessageStore(); |
||||||
41 | $username = $GLOBALS["mapisession"]->getUserName(); |
||||||
42 | $hierarchyTable = mapi_folder_gethierarchytable($stateFolder, CONVENIENT_DEPTH | MAPI_DEFERRED_ERRORS); |
||||||
43 | $rows = mapi_table_queryallrows($hierarchyTable, [PR_ENTRYID, PR_DISPLAY_NAME]); |
||||||
44 | foreach ($rows as $row) { |
||||||
45 | $deviceStateFolder = mapi_msgstore_openentry($store, $row[PR_ENTRYID]); |
||||||
46 | if (mapi_last_hresult() == 0) { |
||||||
47 | $this->deviceStates[$row[PR_DISPLAY_NAME]] = $deviceStateFolder; |
||||||
48 | |||||||
49 | $deviceStateFolderContents = mapi_folder_getcontentstable($deviceStateFolder, MAPI_ASSOCIATED); |
||||||
50 | $restriction = $this->getStateMessageRestriction("devicedata"); |
||||||
51 | mapi_table_restrict($deviceStateFolderContents, $restriction); |
||||||
52 | if (mapi_table_getrowcount($deviceStateFolderContents) == 1) { |
||||||
53 | $rows = mapi_table_queryrows($deviceStateFolderContents, [PR_ENTRYID], 0, 1); |
||||||
54 | $message = mapi_msgstore_openentry($store, $rows[0][PR_ENTRYID]); |
||||||
55 | $state = base64_decode(streamProperty($message, PR_BODY)); |
||||||
56 | $unserializedState = json_decode($state); |
||||||
57 | // fallback for "old-style" states |
||||||
58 | if (isset($unserializedState->data->devices)) { |
||||||
59 | $devices[$unserializedState->data->devices->{$username}->data->deviceid] = $unserializedState->data->devices->{$username}->data; |
||||||
60 | } |
||||||
61 | else { |
||||||
62 | $devices[$unserializedState->data->deviceid] = $unserializedState->data; |
||||||
63 | } |
||||||
64 | } |
||||||
65 | } |
||||||
66 | } |
||||||
67 | } |
||||||
68 | $this->devices = $devices; |
||||||
69 | } |
||||||
70 | |||||||
71 | /** |
||||||
72 | * Function which triggers full resync of a device. |
||||||
73 | * |
||||||
74 | * @param string $deviceid of phone which has to be resynced |
||||||
75 | * |
||||||
76 | * @return bool $response true if removing states succeeded or false on failure |
||||||
77 | */ |
||||||
78 | public function resyncDevice($deviceid) { |
||||||
79 | $deviceStateFolder = $this->deviceStates[$deviceid]; |
||||||
80 | if ($deviceStateFolder) { |
||||||
81 | try { |
||||||
82 | // find all messages that are not 'devicedata' and remove them |
||||||
83 | $deviceStateFolderContents = mapi_folder_getcontentstable($deviceStateFolder, MAPI_ASSOCIATED); |
||||||
84 | $restriction = $this->getStateMessageRestriction("devicedata", RELOP_NE); |
||||||
85 | mapi_table_restrict($deviceStateFolderContents, $restriction); |
||||||
86 | |||||||
87 | $rows = mapi_table_queryallrows($deviceStateFolderContents, [PR_ENTRYID, PR_DISPLAY_NAME]); |
||||||
88 | $messages = []; |
||||||
89 | foreach ($rows as $row) { |
||||||
90 | $messages[] = $row[PR_ENTRYID]; |
||||||
91 | } |
||||||
92 | mapi_folder_deletemessages($deviceStateFolder, $messages, DEL_ASSOCIATED | DELETE_HARD_DELETE); |
||||||
93 | if (mapi_last_hresult() == NOERROR) { |
||||||
94 | return true; |
||||||
95 | } |
||||||
96 | } |
||||||
97 | catch (Exception $e) { |
||||||
98 | error_log(sprintf("mdm plugin resyncDevice Exception: %s", $e)); |
||||||
99 | |||||||
100 | return false; |
||||||
101 | } |
||||||
102 | } |
||||||
103 | error_log(sprintf("mdm plugin resyncDevice device state folder %s", $deviceStateFolder)); |
||||||
104 | |||||||
105 | return false; |
||||||
106 | } |
||||||
107 | |||||||
108 | /** |
||||||
109 | * Function which triggers remote wipe of a device. |
||||||
110 | * |
||||||
111 | * @param string $deviceid of phone which has to be wiped |
||||||
112 | * @param string $password user password |
||||||
113 | * @param int $wipeType remove account only or all data |
||||||
114 | * |
||||||
115 | * @return bool true if the request was successful, false otherwise |
||||||
116 | */ |
||||||
117 | public function wipeDevice($deviceid, $password, $wipeType = SYNC_PROVISION_RWSTATUS_PENDING) { |
||||||
118 | $opts = ['http' => [ |
||||||
119 | 'method' => 'POST', |
||||||
120 | 'header' => 'Content-Type: application/json', |
||||||
121 | 'ignore_errors' => true, |
||||||
122 | 'content' => json_encode( |
||||||
123 | [ |
||||||
124 | 'password' => $password, |
||||||
125 | 'remoteIP' => '[::1]', |
||||||
126 | 'status' => $wipeType, |
||||||
127 | 'time' => time(), |
||||||
128 | ] |
||||||
129 | ), |
||||||
130 | ], |
||||||
131 | ]; |
||||||
132 | $ret = file_get_contents(PLUGIN_MDM_ADMIN_API_WIPE_ENDPOINT . $GLOBALS["mapisession"]->getUserName() . "?devices=" . $deviceid, false, stream_context_create($opts)); |
||||||
133 | $ret = json_decode($ret, true); |
||||||
134 | |||||||
135 | return strncasecmp('success', (string) $ret['message'], 7) === 0; |
||||||
136 | } |
||||||
137 | |||||||
138 | /** |
||||||
139 | * Function which triggers removal of a device. |
||||||
140 | * |
||||||
141 | * @param string $deviceid of phone which has to be removed |
||||||
142 | * @param string $password user password |
||||||
143 | * |
||||||
144 | * @return bool|string $response object contains the response of the soap request from grommunio-sync or false on failure |
||||||
145 | */ |
||||||
146 | public function removeDevice($deviceid, $password) { |
||||||
147 | // TODO remove the device from device / user list |
||||||
148 | $deviceStateFolder = $this->deviceStates[$deviceid]; |
||||||
149 | $stateFolder = $this->getStoreStateFolder(); |
||||||
150 | if ($stateFolder && $deviceStateFolder) { |
||||||
151 | $props = mapi_getprops($deviceStateFolder, [PR_ENTRYID]); |
||||||
152 | |||||||
153 | try { |
||||||
154 | mapi_folder_deletefolder($stateFolder, $props[PR_ENTRYID], DEL_MESSAGES); |
||||||
155 | $opts = ['http' => [ |
||||||
156 | 'method' => 'POST', |
||||||
157 | 'header' => 'Content-Type: application/json', |
||||||
158 | 'ignore_errors' => true, |
||||||
159 | 'content' => json_encode( |
||||||
160 | [ |
||||||
161 | 'password' => $password, |
||||||
162 | 'remoteIP' => '[::1]', |
||||||
163 | 'status' => SYNC_PROVISION_RWSTATUS_NA, |
||||||
164 | 'time' => time(), |
||||||
165 | ] |
||||||
166 | ), |
||||||
167 | ], |
||||||
168 | ]; |
||||||
169 | $ret = file_get_contents(PLUGIN_MDM_ADMIN_API_WIPE_ENDPOINT . $GLOBALS["mapisession"]->getUserName() . "?devices=" . $deviceid, false, stream_context_create($opts)); |
||||||
170 | |||||||
171 | return $ret; |
||||||
172 | } |
||||||
173 | catch (Exception $e) { |
||||||
174 | error_log(sprintf("mdm plugin removeDevice Exception: %s", $e)); |
||||||
175 | |||||||
176 | return false; |
||||||
177 | } |
||||||
178 | } |
||||||
179 | error_log(sprintf( |
||||||
180 | "mdm plugin removeDevice state folder %s device state folder %s", |
||||||
181 | $stateFolder, |
||||||
182 | $deviceStateFolder |
||||||
183 | )); |
||||||
184 | |||||||
185 | return false; |
||||||
186 | } |
||||||
187 | |||||||
188 | /** |
||||||
189 | * Function to get details of the given device. |
||||||
190 | * |
||||||
191 | * @param string $deviceid id of device |
||||||
192 | * |
||||||
193 | * @return array contains device props |
||||||
194 | */ |
||||||
195 | public function getDeviceDetails($deviceid) { |
||||||
196 | $device = []; |
||||||
197 | $device['props'] = $this->getDeviceProps($this->devices[$deviceid]); |
||||||
198 | $device['sharedfolders'] = ['item' => $this->getAdditionalFolderList($deviceid)]; |
||||||
199 | |||||||
200 | return $device; |
||||||
201 | } |
||||||
202 | |||||||
203 | /** |
||||||
204 | * Executes all the actions in the $data variable. |
||||||
205 | * |
||||||
206 | * @return bool true on success or false on failure |
||||||
207 | */ |
||||||
208 | #[Override] |
||||||
209 | public function execute() { |
||||||
210 | foreach ($this->data as $actionType => $actionData) { |
||||||
211 | if (isset($actionType)) { |
||||||
212 | try { |
||||||
213 | switch ($actionType) { |
||||||
214 | case 'wipe': |
||||||
215 | $this->addActionData('wipe', [ |
||||||
216 | 'type' => 3, |
||||||
217 | 'wipe' => $this->wipeDevice($actionData['deviceid'], $actionData['password'], $actionData['wipetype']), |
||||||
218 | ]); |
||||||
219 | $GLOBALS['bus']->addData($this->getResponseData()); |
||||||
220 | break; |
||||||
221 | |||||||
222 | case 'resync': |
||||||
223 | $this->addActionData('resync', [ |
||||||
224 | 'type' => 3, |
||||||
225 | 'resync' => $this->resyncDevice($actionData['deviceid']), |
||||||
226 | ]); |
||||||
227 | $GLOBALS['bus']->addData($this->getResponseData()); |
||||||
228 | break; |
||||||
229 | |||||||
230 | case 'remove': |
||||||
231 | $this->addActionData('remove', [ |
||||||
232 | 'type' => 3, |
||||||
233 | 'remove' => $this->removeDevice($actionData['deviceid'], $actionData['password']), |
||||||
234 | ]); |
||||||
235 | $GLOBALS['bus']->addData($this->getResponseData()); |
||||||
236 | break; |
||||||
237 | |||||||
238 | case 'list': |
||||||
239 | $items = []; |
||||||
240 | $data['page'] = []; |
||||||
241 | |||||||
242 | foreach ($this->devices as $device) { |
||||||
243 | array_push($items, ['props' => $this->getDeviceProps($device)]); |
||||||
244 | } |
||||||
245 | $data['page']['start'] = 0; |
||||||
246 | $data['page']['rowcount'] = count($this->devices); |
||||||
247 | $data['page']['totalrowcount'] = $data['page']['rowcount']; |
||||||
248 | $data = array_merge($data, ['item' => $items]); |
||||||
249 | $this->addActionData('list', $data); |
||||||
250 | $GLOBALS['bus']->addData($this->getResponseData()); |
||||||
251 | break; |
||||||
252 | |||||||
253 | case 'open': |
||||||
254 | $device = $this->getDeviceDetails($actionData["entryid"]); |
||||||
255 | $item = ["item" => $device]; |
||||||
256 | $this->addActionData('item', $item); |
||||||
257 | $GLOBALS['bus']->addData($this->getResponseData()); |
||||||
258 | break; |
||||||
259 | |||||||
260 | case 'save': |
||||||
261 | $this->saveDevice($actionData); |
||||||
262 | $device = $this->getDeviceDetails($actionData["entryid"]); |
||||||
263 | $item = ["item" => $device]; |
||||||
264 | $this->addActionData('update', $item); |
||||||
265 | $GLOBALS['bus']->addData($this->getResponseData()); |
||||||
266 | break; |
||||||
267 | |||||||
268 | default: |
||||||
269 | $this->handleUnknownActionType($actionType); |
||||||
270 | } |
||||||
271 | } |
||||||
272 | catch (Exception $e) { |
||||||
273 | $title = _('Mobile device management plugin'); |
||||||
274 | $display_message = sprintf(_('Unexpected error occurred. Please contact your system administrator. Error code: %s'), $e->getMessage()); |
||||||
275 | $this->sendFeedback(true, ["type" => ERROR_GENERAL, "info" => ['title' => $title, 'display_message' => $display_message]]); |
||||||
276 | } |
||||||
277 | } |
||||||
278 | } |
||||||
279 | } |
||||||
280 | |||||||
281 | /** |
||||||
282 | * Function which is use to get device properties. |
||||||
283 | * |
||||||
284 | * @param object $device array of device properties |
||||||
285 | * |
||||||
286 | * @return array |
||||||
287 | */ |
||||||
288 | public function getDeviceProps($device) { |
||||||
289 | $item = []; |
||||||
290 | $propsList = ['devicetype', 'deviceos', 'devicefriendlyname', 'useragent', 'asversion', 'firstsynctime', |
||||||
291 | 'lastsynctime', 'lastupdatetime', 'policyname', ]; |
||||||
292 | |||||||
293 | $item['entryid'] = $device->deviceid; |
||||||
294 | $item['message_class'] = "IPM.MDM"; |
||||||
295 | foreach ($propsList as $prop) { |
||||||
296 | if (isset($device->{$prop})) { |
||||||
297 | $item[$prop] = $device->{$prop}; |
||||||
298 | } |
||||||
299 | } |
||||||
300 | $item['wipestatus'] = $this->getProvisioningWipeStatus($device->deviceid); |
||||||
301 | |||||||
302 | return array_merge($item, $this->getSyncFoldersProps($device)); |
||||||
0 ignored issues
–
show
$device of type object is incompatible with the type array expected by parameter $device of PluginMDMModule::getSyncFoldersProps() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
303 | } |
||||||
304 | |||||||
305 | /** |
||||||
306 | * Function which is use to gather some statistics about synchronized folders. |
||||||
307 | * |
||||||
308 | * @param array $device array of device props |
||||||
309 | * |
||||||
310 | * @return array $syncFoldersProps has list of properties related to synchronized folders |
||||||
311 | */ |
||||||
312 | public function getSyncFoldersProps($device) { |
||||||
313 | $synchedFolderTypes = []; |
||||||
314 | $synchronizedFolders = 0; |
||||||
315 | |||||||
316 | foreach ($device->contentdata as $folderid => $folderdata) { |
||||||
317 | if (isset($folderdata->{self::FOLDERUUID})) { |
||||||
318 | $type = $folderdata->{self::FOLDERTYPE}; |
||||||
319 | |||||||
320 | $folderType = $this->getSyncFolderType($type); |
||||||
321 | if (isset($synchedFolderTypes[$folderType])) { |
||||||
322 | ++$synchedFolderTypes[$folderType]; |
||||||
323 | } |
||||||
324 | else { |
||||||
325 | $synchedFolderTypes[$folderType] = 1; |
||||||
326 | } |
||||||
327 | } |
||||||
328 | } |
||||||
329 | $syncFoldersProps = []; |
||||||
330 | foreach ($synchedFolderTypes as $key => $value) { |
||||||
331 | $synchronizedFolders += $value; |
||||||
332 | $syncFoldersProps[strtolower($key) . 'folder'] = $value; |
||||||
333 | } |
||||||
334 | /* |
||||||
335 | TODO getAdditionalFolderList |
||||||
336 | $client = $this->getSoapClient(); |
||||||
337 | $items = $client->AdditionalFolderList($device['deviceid']); |
||||||
338 | $syncFoldersProps['sharedfolders'] = count($items); |
||||||
339 | $syncFoldersProps["shortfolderids"] = $device['hasfolderidmapping'] ? _("Yes") : _("No"); |
||||||
340 | $syncFoldersProps['synchronizedfolders'] = $synchronizedFolders + count($items); |
||||||
341 | */ |
||||||
342 | $syncFoldersProps['synchronizedfolders'] = $synchronizedFolders; |
||||||
343 | |||||||
344 | return $syncFoldersProps; |
||||||
345 | } |
||||||
346 | |||||||
347 | /** |
||||||
348 | * Function which is use to get general type like Mail,Calendar,Contacts,etc. from folder type. |
||||||
349 | * |
||||||
350 | * @param int $type foldertype for a folder already known to the mobile |
||||||
351 | * |
||||||
352 | * @return string general folder type |
||||||
353 | */ |
||||||
354 | public function getSyncFolderType($type) { |
||||||
355 | return match ($type) { |
||||||
356 | SYNC_FOLDER_TYPE_APPOINTMENT, SYNC_FOLDER_TYPE_USER_APPOINTMENT => "Calendars", |
||||||
357 | SYNC_FOLDER_TYPE_CONTACT, SYNC_FOLDER_TYPE_USER_CONTACT => "Contacts", |
||||||
358 | SYNC_FOLDER_TYPE_TASK, SYNC_FOLDER_TYPE_USER_TASK => "Tasks", |
||||||
359 | SYNC_FOLDER_TYPE_NOTE, SYNC_FOLDER_TYPE_USER_NOTE => "Notes", |
||||||
360 | default => "Emails", |
||||||
361 | }; |
||||||
362 | } |
||||||
363 | |||||||
364 | /** |
||||||
365 | * Function which is use to get list of additional folders which was shared with given device. |
||||||
366 | * |
||||||
367 | * @param string $devid device id |
||||||
368 | * |
||||||
369 | * @return array has list of properties related to shared folders |
||||||
370 | */ |
||||||
371 | public function getAdditionalFolderList($devid) { |
||||||
372 | return []; |
||||||
373 | // TODO implement |
||||||
374 | $stores = $GLOBALS["mapisession"]->getAllMessageStores(); |
||||||
0 ignored issues
–
show
$stores = $GLOBALS['mapi...->getAllMessageStores() is not reachable.
This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed. Unreachable code is most often the result of function fx() {
try {
doSomething();
return true;
}
catch (\Exception $e) {
return false;
}
return false;
}
In the above example, the last ![]() |
|||||||
375 | $client = $this->getSoapClient(); |
||||||
376 | $items = $client->AdditionalFolderList($devid); |
||||||
377 | $data = []; |
||||||
378 | foreach ($items as $item) { |
||||||
379 | foreach ($stores as $store) { |
||||||
380 | try { |
||||||
381 | $entryid = mapi_msgstore_entryidfromsourcekey($store, hex2bin((string) $item->folderid)); |
||||||
382 | } |
||||||
383 | catch (MAPIException) { |
||||||
384 | continue; |
||||||
385 | } |
||||||
386 | } |
||||||
387 | if (isset($entryid)) { |
||||||
388 | $item->entryid = bin2hex($entryid); |
||||||
389 | } |
||||||
390 | array_push($data, ["props" => $item]); |
||||||
391 | } |
||||||
392 | |||||||
393 | return $data; |
||||||
394 | } |
||||||
395 | |||||||
396 | /** |
||||||
397 | * Function which is use to remove additional folder which was shared with given device. |
||||||
398 | * |
||||||
399 | * @param string $entryId id of device |
||||||
400 | * @param string $folderid id of folder which will remove from device |
||||||
401 | */ |
||||||
402 | public function additionalFolderRemove($entryId, $folderid) { |
||||||
403 | $client = $this->getSoapClient(); |
||||||
0 ignored issues
–
show
The method
getSoapClient() does not exist on PluginMDMModule .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed. ![]() |
|||||||
404 | $client->AdditionalFolderRemove($entryId, $folderid); |
||||||
405 | } |
||||||
406 | |||||||
407 | /** |
||||||
408 | * Function which is use to add additional folder which will share with given device. |
||||||
409 | * |
||||||
410 | * @param string $entryId id of device |
||||||
411 | * @param array $folder folder which will share with device |
||||||
412 | */ |
||||||
413 | public function additionalFolderAdd($entryId, $folder) { |
||||||
414 | $client = $this->getSoapClient(); |
||||||
415 | $containerClass = $folder[PR_CONTAINER_CLASS] ?? "IPF.Note"; |
||||||
416 | $folderId = bin2hex((string) $folder[PR_SOURCE_KEY]); |
||||||
417 | $userName = $folder["user"]; |
||||||
418 | $folderName = $userName === "SYSTEM" ? $folder[PR_DISPLAY_NAME] : $folder[PR_DISPLAY_NAME] . " - " . $userName; |
||||||
419 | $folderType = $this->getFolderTypeFromContainerClass($containerClass); |
||||||
420 | $client->AdditionalFolderAdd($entryId, $userName, $folderId, $folderName, $folderType, FLD_FLAGS_REPLYASUSER); |
||||||
421 | } |
||||||
422 | |||||||
423 | /** |
||||||
424 | * Function which use to save the device. |
||||||
425 | * It will use to add or remove folders in the device. |
||||||
426 | * |
||||||
427 | * @param array $data array of added and removed folders |
||||||
428 | */ |
||||||
429 | public function saveDevice($data) { |
||||||
430 | $entryid = $data["entryid"]; |
||||||
431 | if (isset($data['sharedfolders'])) { |
||||||
432 | if (isset($data['sharedfolders']['remove'])) { |
||||||
433 | $deletedFolders = $data['sharedfolders']['remove']; |
||||||
434 | foreach ($deletedFolders as $folder) { |
||||||
435 | $this->additionalFolderRemove($entryid, $folder["folderid"]); |
||||||
436 | } |
||||||
437 | } |
||||||
438 | if (isset($data['sharedfolders']['add'])) { |
||||||
439 | $addFolders = $data['sharedfolders']['add']; |
||||||
440 | $hierarchyFolders = $this->getHierarchyList(); |
||||||
441 | foreach ($addFolders as $folder) { |
||||||
442 | foreach ($hierarchyFolders as $hierarchyFolder) { |
||||||
443 | $folderEntryid = bin2hex((string) $hierarchyFolder[PR_ENTRYID]); |
||||||
444 | if ($folderEntryid === $folder["entryid"]) { |
||||||
445 | $this->additionalFolderAdd($entryid, $hierarchyFolder); |
||||||
446 | |||||||
447 | continue 2; |
||||||
448 | } |
||||||
449 | } |
||||||
450 | } |
||||||
451 | } |
||||||
452 | } |
||||||
453 | } |
||||||
454 | |||||||
455 | /** |
||||||
456 | * Gets the hierarchy list of all required stores. |
||||||
457 | * Function which is use to get the hierarchy list with PR_SOURCE_KEY. |
||||||
458 | * |
||||||
459 | * @return array the array of all hierarchy folders |
||||||
460 | */ |
||||||
461 | public function getHierarchyList() { |
||||||
462 | $storeList = $GLOBALS["mapisession"]->getAllMessageStores(); |
||||||
463 | $properties = $GLOBALS["properties"]->getFolderListProperties(); |
||||||
464 | $otherUsers = $GLOBALS["mapisession"]->retrieveOtherUsersFromSettings(); |
||||||
465 | $properties["source_key"] = PR_SOURCE_KEY; |
||||||
466 | $openWholeStore = true; |
||||||
467 | $storeData = []; |
||||||
468 | |||||||
469 | foreach ($storeList as $store) { |
||||||
470 | $msgstore_props = mapi_getprops($store, [PR_MDB_PROVIDER, PR_ENTRYID, PR_IPM_SUBTREE_ENTRYID, PR_USER_NAME]); |
||||||
471 | $storeType = $msgstore_props[PR_MDB_PROVIDER]; |
||||||
472 | |||||||
473 | if ($storeType == ZARAFA_SERVICE_GUID) { |
||||||
474 | continue; |
||||||
475 | } |
||||||
476 | if ($storeType == ZARAFA_STORE_DELEGATE_GUID) { |
||||||
477 | $storeUserName = $GLOBALS["mapisession"]->getUserNameOfStore($msgstore_props[PR_ENTRYID]); |
||||||
478 | } |
||||||
479 | elseif ($storeType == ZARAFA_STORE_PUBLIC_GUID) { |
||||||
480 | $storeUserName = "SYSTEM"; |
||||||
481 | } |
||||||
482 | else { |
||||||
483 | $storeUserName = $msgstore_props[PR_USER_NAME]; |
||||||
484 | } |
||||||
485 | |||||||
486 | if (is_array($otherUsers)) { |
||||||
487 | if (isset($otherUsers[$storeUserName])) { |
||||||
488 | $sharedFolders = $otherUsers[$storeUserName]; |
||||||
489 | if (!isset($otherUsers[$storeUserName]['all'])) { |
||||||
490 | $openWholeStore = false; |
||||||
491 | $a = $this->getSharedFolderList($store, $sharedFolders, $properties, $storeUserName); |
||||||
492 | $storeData = array_merge($storeData, $a); |
||||||
493 | } |
||||||
494 | } |
||||||
495 | } |
||||||
496 | |||||||
497 | if ($openWholeStore) { |
||||||
498 | if (isset($msgstore_props[PR_IPM_SUBTREE_ENTRYID])) { |
||||||
499 | $subtreeFolderEntryID = $msgstore_props[PR_IPM_SUBTREE_ENTRYID]; |
||||||
500 | |||||||
501 | try { |
||||||
502 | $subtreeFolder = mapi_msgstore_openentry($store, $subtreeFolderEntryID); |
||||||
503 | } |
||||||
504 | catch (MAPIException $e) { |
||||||
505 | // We've handled the event |
||||||
506 | $e->setHandled(); |
||||||
507 | } |
||||||
508 | |||||||
509 | $this->getSubFolders($subtreeFolder, $store, $properties, $storeData, $storeUserName); |
||||||
510 | } |
||||||
511 | } |
||||||
512 | } |
||||||
513 | |||||||
514 | return $storeData; |
||||||
515 | } |
||||||
516 | |||||||
517 | /** |
||||||
518 | * Helper function to get the shared folder list. |
||||||
519 | * |
||||||
520 | * @param object $store message Store Object |
||||||
521 | * @param object $sharedFolders mapi Folder Object |
||||||
522 | * @param array $properties MAPI property mappings for folders |
||||||
523 | * @param string $storeUserName owner name of store |
||||||
524 | * |
||||||
525 | * @return array shared folders list |
||||||
526 | */ |
||||||
527 | public function getSharedFolderList($store, $sharedFolders, $properties, $storeUserName) { |
||||||
528 | $msgstore_props = mapi_getprops($store, [PR_ENTRYID, PR_DISPLAY_NAME, PR_IPM_SUBTREE_ENTRYID, PR_IPM_OUTBOX_ENTRYID, PR_IPM_SENTMAIL_ENTRYID, PR_IPM_WASTEBASKET_ENTRYID, PR_MDB_PROVIDER, PR_IPM_PUBLIC_FOLDERS_ENTRYID, PR_IPM_FAVORITES_ENTRYID, PR_OBJECT_TYPE, PR_STORE_SUPPORT_MASK, PR_MAILBOX_OWNER_ENTRYID, PR_MAILBOX_OWNER_NAME, PR_USER_ENTRYID, PR_USER_NAME, PR_QUOTA_WARNING_THRESHOLD, PR_QUOTA_SEND_THRESHOLD, PR_QUOTA_RECEIVE_THRESHOLD, PR_MESSAGE_SIZE_EXTENDED, PR_MAPPING_SIGNATURE, PR_COMMON_VIEWS_ENTRYID, PR_FINDER_ENTRYID]); |
||||||
529 | $storeData = []; |
||||||
530 | $folders = []; |
||||||
531 | |||||||
532 | try { |
||||||
533 | $inbox = mapi_msgstore_getreceivefolder($store); |
||||||
534 | $inboxProps = mapi_getprops($inbox, [PR_ENTRYID]); |
||||||
535 | } |
||||||
536 | catch (MAPIException $e) { |
||||||
537 | // don't propagate this error to parent handlers, if store doesn't support it |
||||||
538 | if ($e->getCode() === MAPI_E_NO_SUPPORT) { |
||||||
539 | $e->setHandled(); |
||||||
540 | } |
||||||
541 | } |
||||||
542 | |||||||
543 | $root = mapi_msgstore_openentry($store); |
||||||
544 | $rootProps = mapi_getprops($root, [PR_IPM_APPOINTMENT_ENTRYID, PR_IPM_CONTACT_ENTRYID, PR_IPM_DRAFTS_ENTRYID, PR_IPM_JOURNAL_ENTRYID, PR_IPM_NOTE_ENTRYID, PR_IPM_TASK_ENTRYID, PR_ADDITIONAL_REN_ENTRYIDS]); |
||||||
545 | |||||||
546 | $additional_ren_entryids = []; |
||||||
547 | if (isset($rootProps[PR_ADDITIONAL_REN_ENTRYIDS])) { |
||||||
548 | $additional_ren_entryids = $rootProps[PR_ADDITIONAL_REN_ENTRYIDS]; |
||||||
549 | } |
||||||
550 | |||||||
551 | $defaultfolders = [ |
||||||
552 | "default_folder_inbox" => ["inbox" => PR_ENTRYID], |
||||||
553 | "default_folder_outbox" => ["store" => PR_IPM_OUTBOX_ENTRYID], |
||||||
554 | "default_folder_sent" => ["store" => PR_IPM_SENTMAIL_ENTRYID], |
||||||
555 | "default_folder_wastebasket" => ["store" => PR_IPM_WASTEBASKET_ENTRYID], |
||||||
556 | "default_folder_favorites" => ["store" => PR_IPM_FAVORITES_ENTRYID], |
||||||
557 | "default_folder_publicfolders" => ["store" => PR_IPM_PUBLIC_FOLDERS_ENTRYID], |
||||||
558 | "default_folder_calendar" => ["root" => PR_IPM_APPOINTMENT_ENTRYID], |
||||||
559 | "default_folder_contact" => ["root" => PR_IPM_CONTACT_ENTRYID], |
||||||
560 | "default_folder_drafts" => ["root" => PR_IPM_DRAFTS_ENTRYID], |
||||||
561 | "default_folder_journal" => ["root" => PR_IPM_JOURNAL_ENTRYID], |
||||||
562 | "default_folder_note" => ["root" => PR_IPM_NOTE_ENTRYID], |
||||||
563 | "default_folder_task" => ["root" => PR_IPM_TASK_ENTRYID], |
||||||
564 | "default_folder_junk" => ["additional" => 4], |
||||||
565 | "default_folder_syncissues" => ["additional" => 1], |
||||||
566 | "default_folder_conflicts" => ["additional" => 0], |
||||||
567 | "default_folder_localfailures" => ["additional" => 2], |
||||||
568 | "default_folder_serverfailures" => ["additional" => 3], |
||||||
569 | ]; |
||||||
570 | |||||||
571 | foreach ($defaultfolders as $key => $prop) { |
||||||
572 | $tag = reset($prop); |
||||||
573 | $from = key($prop); |
||||||
574 | |||||||
575 | switch ($from) { |
||||||
576 | case "inbox": |
||||||
577 | if (isset($inboxProps[$tag])) { |
||||||
578 | $storeData["props"][$key] = bin2hex((string) $inboxProps[$tag]); |
||||||
579 | } |
||||||
580 | break; |
||||||
581 | |||||||
582 | case "store": |
||||||
583 | if (isset($msgstore_props[$tag])) { |
||||||
584 | $storeData["props"][$key] = bin2hex((string) $msgstore_props[$tag]); |
||||||
585 | } |
||||||
586 | break; |
||||||
587 | |||||||
588 | case "root": |
||||||
589 | if (isset($rootProps[$tag])) { |
||||||
590 | $storeData["props"][$key] = bin2hex((string) $rootProps[$tag]); |
||||||
591 | } |
||||||
592 | break; |
||||||
593 | |||||||
594 | case "additional": |
||||||
595 | if (isset($additional_ren_entryids[$tag])) { |
||||||
596 | $storeData["props"][$key] = bin2hex((string) $additional_ren_entryids[$tag]); |
||||||
597 | } |
||||||
598 | break; |
||||||
599 | } |
||||||
600 | } |
||||||
601 | |||||||
602 | $store_access = true; |
||||||
603 | $openSubFolders = false; |
||||||
604 | foreach ($sharedFolders as $type => $sharedFolder) { |
||||||
605 | $openSubFolders = ($sharedFolder["show_subfolders"] == true); |
||||||
606 | $folderEntryID = hex2bin($storeData["props"]["default_folder_" . $sharedFolder["folder_type"]]); |
||||||
607 | |||||||
608 | try { |
||||||
609 | // load folder props |
||||||
610 | $folder = mapi_msgstore_openentry($store, $folderEntryID); |
||||||
611 | } |
||||||
612 | catch (MAPIException $e) { |
||||||
613 | // Indicate that we don't have access to the store, |
||||||
614 | // so no more attempts to read properties or open entries. |
||||||
615 | $store_access = false; |
||||||
616 | |||||||
617 | // We've handled the event |
||||||
618 | $e->setHandled(); |
||||||
619 | } |
||||||
620 | } |
||||||
621 | |||||||
622 | if ($store_access === true) { |
||||||
623 | $folderProps = mapi_getprops($folder, $properties); |
||||||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||||||
624 | $folderProps["user"] = $storeUserName; |
||||||
625 | array_push($folders, $folderProps); |
||||||
626 | |||||||
627 | // If folder has sub folders then add its. |
||||||
628 | if ($openSubFolders === true) { |
||||||
629 | if ($folderProps[PR_SUBFOLDERS] != false) { |
||||||
630 | $subFoldersData = []; |
||||||
631 | $this->getSubFolders($folder, $store, $properties, $subFoldersData, $storeUserName); |
||||||
632 | $folders = array_merge($folders, $subFoldersData); |
||||||
633 | } |
||||||
634 | } |
||||||
635 | } |
||||||
636 | |||||||
637 | return $folders; |
||||||
638 | } |
||||||
639 | |||||||
640 | /** |
||||||
641 | * Helper function to get the sub folders of a given folder. |
||||||
642 | * |
||||||
643 | * @param object $folder mapi Folder Object |
||||||
644 | * @param object $store Message Store Object |
||||||
645 | * @param array $properties MAPI property mappings for folders |
||||||
646 | * @param array $storeData Reference to an array. The folder properties are added to this array. |
||||||
647 | * @param string $storeUserName owner name of store |
||||||
648 | */ |
||||||
649 | public function getSubFolders($folder, $store, $properties, &$storeData, $storeUserName) { |
||||||
650 | /** |
||||||
651 | * remove hidden folders, folders with PR_ATTR_HIDDEN property set |
||||||
652 | * should not be shown to the client. |
||||||
653 | */ |
||||||
654 | $restriction = [RES_OR, [ |
||||||
655 | [RES_PROPERTY, |
||||||
656 | [ |
||||||
657 | RELOP => RELOP_EQ, |
||||||
658 | ULPROPTAG => PR_ATTR_HIDDEN, |
||||||
659 | VALUE => [PR_ATTR_HIDDEN => false], |
||||||
660 | ], |
||||||
661 | ], |
||||||
662 | [RES_NOT, |
||||||
663 | [ |
||||||
664 | [RES_EXIST, |
||||||
665 | [ |
||||||
666 | ULPROPTAG => PR_ATTR_HIDDEN, |
||||||
667 | ], |
||||||
668 | ], |
||||||
669 | ], |
||||||
670 | ], |
||||||
671 | ]]; |
||||||
672 | |||||||
673 | $expand = [ |
||||||
674 | [ |
||||||
675 | 'folder' => $folder, |
||||||
676 | 'props' => mapi_getprops($folder, [PR_ENTRYID, PR_SUBFOLDERS]), |
||||||
677 | ], |
||||||
678 | ]; |
||||||
679 | |||||||
680 | // Start looping through the $expand array, during each loop we grab the first item in |
||||||
681 | // the array and obtain the hierarchy table for that particular folder. If one of those |
||||||
682 | // subfolders has subfolders of its own, it will be appended to $expand again to ensure |
||||||
683 | // it will be expanded later. |
||||||
684 | while (!empty($expand)) { |
||||||
685 | $item = array_shift($expand); |
||||||
686 | $columns = $properties; |
||||||
687 | |||||||
688 | $hierarchyTable = mapi_folder_gethierarchytable($item['folder'], MAPI_DEFERRED_ERRORS); |
||||||
689 | mapi_table_restrict($hierarchyTable, $restriction, TBL_BATCH); |
||||||
690 | |||||||
691 | mapi_table_setcolumns($hierarchyTable, $columns); |
||||||
692 | $columns = null; |
||||||
693 | |||||||
694 | // Load the hierarchy in small batches |
||||||
695 | $batchcount = 100; |
||||||
696 | do { |
||||||
697 | $rows = mapi_table_queryrows($hierarchyTable, $columns, 0, $batchcount); |
||||||
698 | |||||||
699 | foreach ($rows as $subfolder) { |
||||||
700 | // If the subfolders has subfolders of its own, append the folder |
||||||
701 | // to the $expand array, so it can be expanded in the next loop. |
||||||
702 | if ($subfolder[PR_SUBFOLDERS]) { |
||||||
703 | $folderObject = mapi_msgstore_openentry($store, $subfolder[PR_ENTRYID]); |
||||||
704 | array_push($expand, ['folder' => $folderObject, 'props' => $subfolder]); |
||||||
705 | } |
||||||
706 | $subfolder["user"] = $storeUserName; |
||||||
707 | // Add the folder to the return list. |
||||||
708 | array_push($storeData, $subfolder); |
||||||
709 | } |
||||||
710 | |||||||
711 | // When the server returned a different number of rows then was requested, |
||||||
712 | // we have reached the end of the table and we should exit the loop. |
||||||
713 | } |
||||||
714 | while (count($rows) === $batchcount); |
||||||
715 | } |
||||||
716 | } |
||||||
717 | |||||||
718 | /** |
||||||
719 | * Function which is use get folder types from the container class. |
||||||
720 | * |
||||||
721 | * @param string $containerClass container class of folder |
||||||
722 | * |
||||||
723 | * @return int folder type |
||||||
724 | */ |
||||||
725 | public function getFolderTypeFromContainerClass($containerClass) { |
||||||
726 | return match ($containerClass) { |
||||||
727 | "IPF.Note" => SYNC_FOLDER_TYPE_USER_MAIL, |
||||||
728 | "IPF.Appointment" => SYNC_FOLDER_TYPE_USER_APPOINTMENT, |
||||||
729 | "IPF.Contact" => SYNC_FOLDER_TYPE_USER_CONTACT, |
||||||
730 | "IPF.StickyNote" => SYNC_FOLDER_TYPE_USER_NOTE, |
||||||
731 | "IPF.Task" => SYNC_FOLDER_TYPE_USER_TASK, |
||||||
732 | "IPF.Journal" => SYNC_FOLDER_TYPE_USER_JOURNAL, |
||||||
733 | default => SYNC_FOLDER_TYPE_UNKNOWN, |
||||||
734 | }; |
||||||
735 | } |
||||||
736 | |||||||
737 | /** |
||||||
738 | * Returns MAPIFolder object which contains the state information. |
||||||
739 | * Creates this folder if it is not available yet. |
||||||
740 | * |
||||||
741 | * @return MAPIFolder |
||||||
0 ignored issues
–
show
The type
MAPIFolder was not found. Maybe you did not declare it correctly or list all dependencies?
The issue could also be caused by a filter entry in the build configuration.
If the path has been excluded in your configuration, e.g. filter:
dependency_paths: ["lib/*"]
For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths ![]() |
|||||||
742 | */ |
||||||
743 | public function getStoreStateFolder() { |
||||||
744 | if (!$this->stateFolder) { |
||||||
745 | $store = $GLOBALS["mapisession"]->getDefaultMessageStore(); |
||||||
746 | $rootFolder = mapi_msgstore_openentry($store); |
||||||
747 | $hierarchy = mapi_folder_gethierarchytable($rootFolder, CONVENIENT_DEPTH | MAPI_DEFERRED_ERRORS); |
||||||
748 | $restriction = $this->getStateFolderRestriction(PLUGIN_MDM_STORE_STATE_FOLDER); |
||||||
749 | mapi_table_restrict($hierarchy, $restriction); |
||||||
750 | if (mapi_table_getrowcount($hierarchy) == 1) { |
||||||
751 | $rows = mapi_table_queryrows($hierarchy, [PR_ENTRYID], 0, 1); |
||||||
752 | $this->stateFolder = mapi_msgstore_openentry($store, $rows[0][PR_ENTRYID]); |
||||||
753 | } |
||||||
754 | } |
||||||
755 | |||||||
756 | return $this->stateFolder; |
||||||
757 | } |
||||||
758 | |||||||
759 | /** |
||||||
760 | * Returns the restriction for the state folder name. |
||||||
761 | * |
||||||
762 | * @param string $folderName the state folder name |
||||||
763 | * |
||||||
764 | * @return array |
||||||
765 | */ |
||||||
766 | public function getStateFolderRestriction($folderName) { |
||||||
767 | return [RES_AND, [ |
||||||
768 | [RES_PROPERTY, |
||||||
769 | [RELOP => RELOP_EQ, |
||||||
770 | ULPROPTAG => PR_DISPLAY_NAME, |
||||||
771 | VALUE => $folderName, |
||||||
772 | ], |
||||||
773 | ], |
||||||
774 | [RES_PROPERTY, |
||||||
775 | [RELOP => RELOP_EQ, |
||||||
776 | ULPROPTAG => PR_ATTR_HIDDEN, |
||||||
777 | VALUE => true, |
||||||
778 | ], |
||||||
779 | ], |
||||||
780 | ]]; |
||||||
781 | } |
||||||
782 | |||||||
783 | /** |
||||||
784 | * Returns the restriction for the associated message in the state folder. |
||||||
785 | * |
||||||
786 | * @param string $messageName the message name |
||||||
787 | * @param int $op comparison operation |
||||||
788 | * |
||||||
789 | * @return array |
||||||
790 | */ |
||||||
791 | public function getStateMessageRestriction($messageName, $op = RELOP_EQ) { |
||||||
792 | return [RES_AND, [ |
||||||
793 | [RES_PROPERTY, |
||||||
794 | [RELOP => $op, |
||||||
795 | ULPROPTAG => PR_DISPLAY_NAME, |
||||||
796 | VALUE => $messageName, |
||||||
797 | ], |
||||||
798 | ], |
||||||
799 | [RES_PROPERTY, |
||||||
800 | [RELOP => RELOP_EQ, |
||||||
801 | ULPROPTAG => PR_MESSAGE_CLASS, |
||||||
802 | VALUE => 'IPM.Note.GrommunioState', |
||||||
803 | ], |
||||||
804 | ], |
||||||
805 | ]]; |
||||||
806 | } |
||||||
807 | |||||||
808 | /** |
||||||
809 | * Returns the status of the remote wipe policy. |
||||||
810 | * |
||||||
811 | * @param mixed $deviceid |
||||||
812 | * |
||||||
813 | * @return int returns the current status of the device - SYNC_PROVISION_RWSTATUS_* |
||||||
814 | */ |
||||||
815 | public function getProvisioningWipeStatus($deviceid) { |
||||||
816 | // retrieve the WIPE STATUS from the Admin API |
||||||
817 | $api_response = file_get_contents(PLUGIN_MDM_ADMIN_API_WIPE_ENDPOINT . $GLOBALS["mapisession"]->getUserName() . "?devices=" . $deviceid); |
||||||
818 | if ($api_response) { |
||||||
819 | $data = json_decode($api_response, true); |
||||||
820 | if (isset($data['data'][$deviceid]["status"])) { |
||||||
821 | return $data['data'][$deviceid]["status"]; |
||||||
822 | } |
||||||
823 | } |
||||||
824 | |||||||
825 | return SYNC_PROVISION_RWSTATUS_NA; |
||||||
826 | } |
||||||
827 | } |
||||||
828 |