Test Failed
Push — master ( 7d85b5...2dd165 )
by
unknown
15:15 queued 03:07
created

HierarchyModule::setFolderPermissions()   F

Complexity

Conditions 32
Paths 480

Size

Total Lines 102
Code Lines 61

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 32
eloc 61
c 4
b 0
f 0
nc 480
nop 2
dl 0
loc 102
rs 0.7221

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * Hierarchy Module.
5
 *
6
 * @todo
7
 * - Check the code at deleteFolder and at copyFolder. Looks the same.
8
 */
9
class HierarchyModule extends Module {
10
	private $store_entryid;
11
12
	/**
13
	 * Constructor.
14
	 *
15
	 * @param int   $id   unique id
16
	 * @param array $data list of all actions
17
	 */
18
	public function __construct($id, $data) {
19
		$this->properties = $GLOBALS["properties"]->getFolderProperties();
20
		$this->list_properties = $GLOBALS["properties"]->getFolderListProperties();
21
22
		parent::__construct($id, $data);
23
	}
24
25
	/**
26
	 * Creates the notifiers for this module,
27
	 * and register them to the Bus.
28
	 */
29
	public function createNotifiers() {
30
		$entryid = $this->getEntryID();
31
		$GLOBALS["bus"]->registerNotifier('hierarchynotifier', $entryid, true);
32
		$GLOBALS["bus"]->registerNotifier('hierarchynotifier', REQUEST_ENTRYID);
33
		$GLOBALS["bus"]->registerNotifier('newmailnotifier', REQUEST_ENTRYID);
34
	}
35
36
	/**
37
	 * Function which returns a list of entryids, which is used to register this module. It
38
	 * returns the ipm_subtree entryids of every message store.
39
	 *
40
	 * @return array list of entryids
41
	 */
42
	public function getEntryID() {
43
		$entryids = [];
44
		$storelist = $GLOBALS["mapisession"]->getAllMessageStores();
45
46
		foreach ($storelist as $entryid => $store) {
47
			$entryids[] = bin2hex($entryid);
48
		}
49
50
		return $entryids;
51
	}
52
53
	/**
54
	 * Executes all the actions in the $data variable.
55
	 */
56
	public function execute() {
57
		foreach ($this->data as $actionType => $action) {
58
			if (isset($actionType)) {
59
				try {
60
					$store = $this->getActionStore($action);
61
					$parententryid = $this->getActionParentEntryID($action);
62
					$entryid = $this->getActionEntryID($action);
63
					$this->store_entryid = $action["store_entryid"] ?? '';
64
65
					switch ($actionType) {
66
						case "keepalive":
67
							/*
68
							 * as we haven't done any processing here but still we need to send
69
							 * success message to client so client can know that there isn't any problem
70
							 * on server side (this will also make bus class happy as it will cry when
71
							 * there isn't any data to send to client).
72
							 */
73
							$this->sendFeedback(true);
74
							break;
75
76
						case "destroysession":
77
							// This actiontype should never get this far, but should already have been
78
							// intercepted by the Session class.
79
							// Nevertheless implement processing here for unforeseen cases.
80
							$this->sendFeedback(true);
81
							break;
82
83
						case "list":
84
							$this->hierarchyList();
85
							break;
86
87
						case "open":
88
							$folder = mapi_msgstore_openentry($store, $entryid);
89
							$data = $this->getFolderProps($store, $folder);
90
91
							// return response
92
							$this->addActionData("item", $data);
93
							$GLOBALS["bus"]->addData($this->getResponseData());
94
							break;
95
96
						case "foldersize":
97
							$folders = [];
98
99
							$folder = mapi_msgstore_openentry($store, $entryid);
100
							$data = $this->getFolderProps($store, $folder);
101
102
							$info = $this->getFolderSize($store, $folder, '', $folders);
103
104
							// It could be that the $props already contains the data,
105
							// this happens when the folder is the IPM_SUBTREE and the
106
							// folder size is read directly from the store. Adjust
107
							// total_size accordingly.
108
							if (isset($data["props"]["store_size"])) {
109
								if (!isset($data["props"]["message_size"])) {
110
									$data["props"]["message_size"] = $data["props"]["store_size"];
111
								}
112
								$data["props"]["total_message_size"] = $data["props"]["store_size"] + $info["total_size"];
113
							}
114
							else {
115
								$data["props"]["message_size"] = $info["size"];
116
								$data["props"]["total_message_size"] = $info["total_size"];
117
							}
118
							$data["folders"] = [
119
								"item" => $folders,
120
							];
121
122
							// return response
123
							$this->addActionData("item", $data);
124
							$GLOBALS["bus"]->addData($this->getResponseData());
125
							break;
126
127
						case "delete":
128
							if ($store && $parententryid && $entryid) {
129
								if (isset($action["message_action"], $action["message_action"]["action_type"]) &&
130
									$action["message_action"]["action_type"] === "removefavorites") {
131
									if (isset($action["message_action"]["isSearchFolder"]) &&
132
										$action["message_action"]["isSearchFolder"]) {
133
										$result = $this->deleteSearchFolder($store, $parententryid, $entryid, $action);
0 ignored issues
show
Bug introduced by
$parententryid of type object is incompatible with the type array expected by parameter $parententryid of HierarchyModule::deleteSearchFolder(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

133
										$result = $this->deleteSearchFolder($store, /** @scrutinizer ignore-type */ $parententryid, $entryid, $action);
Loading history...
134
										dump($result, '$result');
135
										if ($result) {
136
											$this->sendFeedback(true);
137
										}
138
									}
139
									else {
140
										$this->removeFromFavorite($entryid);
141
									}
142
								}
143
								else {
144
									$this->deleteFolder($store, $parententryid, $entryid, $action);
0 ignored issues
show
Bug introduced by
$parententryid of type object is incompatible with the type string expected by parameter $parententryid of HierarchyModule::deleteFolder(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

144
									$this->deleteFolder($store, /** @scrutinizer ignore-type */ $parententryid, $entryid, $action);
Loading history...
145
								}
146
							}
147
							break;
148
149
						case "save":
150
							if ($store && $parententryid) {
151
								if ($entryid) {
152
									// The "message_action" object has been set, check the action_type field for
153
									// the exact action which must be taken.
154
									// Supported actions:
155
									//   - copy: Copy the folder to the new destination folder
156
									//   - move: Move the folder to the new destination folder
157
									//   - emptyfolder: Delete all items within the folder
158
									//   - readflags: Mark all items within the folder as read
159
									//   - addtofavorites: Add the folder to "favorites"
160
									if (!isset($action["message_action"]["isSearchFolder"])) {
161
										$folder = mapi_msgstore_openentry($store, $entryid);
162
										$data = $this->getFolderProps($store, $folder);
163
									}
164
									if (isset($action["message_action"], $action["message_action"]["action_type"])) {
165
										switch ($action["message_action"]["action_type"]) {
166
											case "copy":
167
											case "move":
168
												$destentryid = false;
169
												if (isset($action["message_action"]["destination_parent_entryid"])) {
170
													$destentryid = hex2bin($action["message_action"]["destination_parent_entryid"]);
171
												}
172
173
												$deststore = $store;
174
												if (isset($action["message_action"]["destination_store_entryid"])) {
175
													$deststore = $GLOBALS['mapisession']->openMessageStore(hex2bin($action["message_action"]["destination_store_entryid"]));
176
												}
177
178
												if ($destentryid && $deststore) {
179
													$this->copyFolder($store, $parententryid, $entryid, $destentryid, $deststore, $action["message_action"]["action_type"] == "move");
0 ignored issues
show
Bug introduced by
$parententryid of type object is incompatible with the type string expected by parameter $parententryid of HierarchyModule::copyFolder(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

179
													$this->copyFolder($store, /** @scrutinizer ignore-type */ $parententryid, $entryid, $destentryid, $deststore, $action["message_action"]["action_type"] == "move");
Loading history...
180
												}
181
												if ($data["props"]["container_class"] === "IPF.Contact") {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $data does not seem to be defined for all execution paths leading up to this point.
Loading history...
182
													$GLOBALS["bus"]->notify(ADDRESSBOOK_ENTRYID, OBJECT_SAVE);
183
												}
184
												break;
185
186
											case "emptyfolder":
187
												$this->emptyFolder($store, $entryid);
188
												break;
189
190
											case "readflags":
191
												$this->setReadFlags($store, $entryid);
192
												break;
193
194
											case "addtofavorites":
195
												if (isset($action["message_action"]["isSearchFolder"]) && $action["message_action"]["isSearchFolder"]) {
196
													$searchStoreEntryId = $action["message_action"]["search_store_entryid"];
197
													// Set display name to search folder.
198
													$searchStore = $GLOBALS["mapisession"]->openMessageStore(hex2bin($searchStoreEntryId));
199
													$searchFolder = mapi_msgstore_openentry($searchStore, $entryid);
200
													mapi_setprops($searchFolder, [
201
														PR_DISPLAY_NAME => $action["props"]["display_name"],
202
													]);
203
													mapi_savechanges($searchFolder);
204
													$this->createLinkedSearchFolder($searchFolder);
205
												}
206
												else {
207
													$this->addToFavorite($store, $entryid);
0 ignored issues
show
Bug introduced by
$store of type object is incompatible with the type string expected by parameter $store of HierarchyModule::addToFavorite(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

207
													$this->addToFavorite(/** @scrutinizer ignore-type */ $store, $entryid);
Loading history...
208
												}
209
												break;
210
										}
211
									}
212
									else {
213
										// save folder
214
										$folder = mapi_msgstore_openentry($store, hex2bin($action["entryid"]));
215
										$this->save($store, $folder, $action);
216
										if ($data["props"]["container_class"] === "IPF.Contact") {
217
											$GLOBALS["bus"]->notify(ADDRESSBOOK_ENTRYID, OBJECT_SAVE);
218
										}
219
										$this->sendFeedback(true, []);
220
									}
221
								}
222
								else {
223
									// no entryid, create new folder
224
									if ($store && $parententryid && isset($action["props"]["display_name"], $action["props"]["container_class"])) {
225
										if (isset($action["message_action"], $action["message_action"]["action_type"])) {
226
											// We need to create new search folder under the favorites folder
227
											// based on give search folder info.
228
											if ($action["message_action"]["action_type"] === "addtofavorites") {
229
												$storeEntryId = $action["message_action"]["search_store_entryid"];
230
												$searchFolderEntryId = $action["message_action"]["search_folder_entryid"];
231
232
												// Get the search folder and search criteria using $storeEntryId and $searchFolderEntryId.
233
												$Store = $GLOBALS["mapisession"]->openMessageStore(hex2bin($storeEntryId));
234
												$searchFolder = mapi_msgstore_openentry($Store, hex2bin($searchFolderEntryId));
235
												$searchCriteria = mapi_folder_getsearchcriteria($searchFolder);
236
237
												// Get FINDERS_ROOT folder from store.
238
												$finderRootFolder = mapi_getprops($Store, [PR_FINDER_ENTRYID]);
239
												$searchFolderRoot = mapi_msgstore_openentry($Store, $finderRootFolder[PR_FINDER_ENTRYID]);
240
241
												// Create new search folder in FINDERS_ROOT folder and set the search
242
												// criteria in newly created search folder.
243
												$newSearchFolder = mapi_folder_createfolder($searchFolderRoot, $action["props"]["display_name"], null, 0, FOLDER_SEARCH);
244
												$subfolder_flag = 0;
245
												if (isset($action["subfolders"]) && $action["subfolders"] == "true") {
246
													$subfolder_flag = RECURSIVE_SEARCH;
247
												}
248
												mapi_folder_setsearchcriteria($newSearchFolder, $searchCriteria['restriction'], $searchCriteria['folderlist'], $subfolder_flag);
249
250
												// Sleep for 1 seconds initially, since it usually takes ~  1 seconds to fill the search folder.
251
												sleep(1);
252
												$this->createLinkedSearchFolder($newSearchFolder);
253
											}
254
										}
255
										else {
256
											$this->addFolder($store, $parententryid, $action["props"]["display_name"], $action["props"]["container_class"]);
0 ignored issues
show
Bug introduced by
$parententryid of type object is incompatible with the type string expected by parameter $parententryid of HierarchyModule::addFolder(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

256
											$this->addFolder($store, /** @scrutinizer ignore-type */ $parententryid, $action["props"]["display_name"], $action["props"]["container_class"]);
Loading history...
257
										}
258
									}
259
									if ($action["props"]["container_class"] === "IPF.Contact") {
260
										$GLOBALS["bus"]->notify(ADDRESSBOOK_ENTRYID, OBJECT_SAVE);
261
									}
262
								}
263
							}
264
							break;
265
266
						case "closesharedfolder":
267
							if (isset($action["folder_type"]) && $action["folder_type"] != "all") {
268
								// We're closing a Shared folder, check if we still have other
269
								// folders for the same user opened, if not we can safely close
270
								// the usrstore.
271
								$stores = $GLOBALS["settings"]->get("zarafa/v1/contexts/hierarchy/shared_stores/" . strtolower(bin2hex($action["user_name"])));
272
								if (!isset($stores) || empty($stores) || (count($stores) == 1 && isset($stores[$action["folder_type"]]))) {
273
									$entryid = $GLOBALS["mapisession"]->removeUserStore($action["user_name"]);
274
								}
275
								else {
276
									$entryid = $GLOBALS["mapisession"]->getStoreEntryIdOfUser($action["user_name"]);
277
									$this->removeFromFavorite(hex2bin($action["entryid"]), $store, PR_WLINK_ENTRYID, false);
278
								}
279
							}
280
							else {
281
								// We're closing a Shared Store, simply remove it from the session.
282
								$entryid = $GLOBALS["mapisession"]->removeUserStore($action["user_name"]);
283
284
								if (isset($action["remove_favorites"]) && $action["remove_favorites"]) {
285
									$this->removeFromFavorite(hex2bin($action["store_entryid"]), $store, PR_WLINK_STORE_ENTRYID, false);
286
								}
287
							}
288
289
							$data = [];
290
							$data["store_entryid"] = bin2hex($entryid);
291
							if (isset($action["folder_type"])) {
292
								$data["folder_type"] = $action["folder_type"];
293
							}
294
295
							$this->addActionData("delete", $data);
296
							$GLOBALS["bus"]->addData($this->getResponseData());
297
							$GLOBALS["bus"]->notify(ADDRESSBOOK_ENTRYID, OBJECT_SAVE);
298
							break;
299
300
						case "opensharedfolder":
301
							$username = strtolower($action["user_name"]);
302
							$store = $GLOBALS["mapisession"]->addUserStore($username);
303
							if (!$store) {
304
								break;
305
							}
306
307
							$options = [$username => [$action["folder_type"] => $action]];
308
							$data = $GLOBALS["operations"]->getHierarchyList($this->list_properties, HIERARCHY_GET_ONE, $store, $options, $username);
309
310
							if (empty($data["item"][0]["folders"]["item"])) {
311
								throw new MAPIException(null, MAPI_E_NO_ACCESS);
312
							}
313
314
							$folders = count($data["item"][0]["folders"]["item"]);
315
							if ($folders === 0) {
316
								throw new MAPIException(null, MAPI_E_NO_ACCESS);
317
							}
318
319
							$noPermissionFolders = array_filter($data['item'][0]['folders']['item'], function ($item) {
320
								return $item['props']['access'] === 0;
321
							});
322
							if (count($noPermissionFolders) >= $folders) {
323
								// Throw an exception that we couldn't open the shared store,
324
								// lets have processException() fill in our error message.
325
								throw new MAPIException(null, MAPI_E_NO_ACCESS);
326
							}
327
328
							$this->addActionData("list", $data);
329
							$GLOBALS["bus"]->addData($this->getResponseData());
330
							$GLOBALS["bus"]->notify(ADDRESSBOOK_ENTRYID, OBJECT_SAVE);
331
							break;
332
333
						case "sharedstoreupdate":
334
							$supported_types = ['inbox' => 1, 'all' => 1];
335
							$users = $GLOBALS["settings"]->get("zarafa/v1/contexts/hierarchy/shared_stores", []);
336
337
							foreach ($users as $username => $data) {
338
								$key = array_keys($data)[0];
339
								$folder_type = $data[$key]['folder_type'];
340
341
								if (!isset($supported_types[$folder_type])) {
342
									continue;
343
								}
344
345
								$GLOBALS["bus"]->notify(REQUEST_ENTRYID, HIERARCHY_UPDATE, [strtolower(hex2bin($username)), $folder_type]);
346
							}
347
348
							$this->sendFeedback(true);
349
							break;
350
351
						default:
352
							$this->handleUnknownActionType($actionType);
353
					}
354
				}
355
				catch (MAPIException $e) {
356
					$this->processException($e, $actionType, $store, $parententryid, $entryid, $action);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $entryid does not seem to be defined for all execution paths leading up to this point.
Loading history...
Comprehensibility Best Practice introduced by
The variable $parententryid does not seem to be defined for all execution paths leading up to this point.
Loading history...
357
				}
358
			}
359
		}
360
	}
361
362
	/**
363
	 * Function does customization of exception based on module data.
364
	 * like, here it will generate display message based on actionType
365
	 * for particular exception, and send feedback to the client.
366
	 *
367
	 * @param object     $e             Exception object
368
	 * @param string     $actionType    the action type, sent by the client
369
	 * @param MAPIobject $store         store object of the folder
0 ignored issues
show
Bug introduced by
The type MAPIobject 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. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
370
	 * @param string     $parententryid parent entryid of the message
371
	 * @param string     $entryid       entryid of the folder
372
	 * @param array      $action        the action data, sent by the client
373
	 */
374
	public function handleException(&$e, $actionType = null, $store = null, $parententryid = null, $entryid = null, $action = null) {
375
		if (is_null($e->displayMessage)) {
376
			switch ($actionType) {
377
				case "list":
378
					$e->setDisplayMessage(_("Could not load the hierarchy."));
379
					break;
380
381
				case "open":
382
					$e->setDisplayMessage(_("Could not load folder properties."));
383
					break;
384
385
				case "delete":
386
					if (isset($action["message_action"], $action["message_action"]["action_type"]) &&
387
						$action["message_action"]["action_type"] === "removefavorites") {
388
						$e->setDisplayMessage(_("Could not remove folder from favorites."));
389
					}
390
					else {
391
						if ($e->getCode() == MAPI_E_NO_ACCESS) {
392
							$e->setDisplayMessage(_("You have insufficient privileges to delete folder."));
393
						}
394
						else {
395
							$e->setDisplayMessage(_("Could not delete folder."));
396
						}
397
						break;
398
					}
399
400
					// no break
401
				case "save":
402
					if ($entryid) {
403
						if (isset($action["message_action"], $action["message_action"]["action_type"])) {
404
							switch ($action["message_action"]["action_type"]) {
405
								case "copy":
406
									if ($e->getCode() == MAPI_E_NO_ACCESS) {
407
										$e->setDisplayMessage(_("You have insufficient privileges to copy folder."));
408
									}
409
									else {
410
										$e->setDisplayMessage(_("Could not copy folder."));
411
									}
412
									break;
413
414
								case "move":
415
									if ($e->getCode() == MAPI_E_NO_ACCESS) {
416
										$e->setDisplayMessage(_("You have insufficient privileges to move this folder."));
417
									}
418
									else {
419
										$e->setDisplayMessage(_("Could not move folder."));
420
									}
421
									break;
422
423
								case "emptyfolder":
424
									if ($e->getCode() == MAPI_E_NO_ACCESS) {
425
										$e->setDisplayMessage(_("You have insufficient privileges to delete items."));
426
									}
427
									else {
428
										$e->setDisplayMessage(_("Could not empty folder."));
429
									}
430
									break;
431
432
								case "readflags":
433
									$e->setDisplayMessage(_("Could not perform action correctly."));
434
									break;
435
436
								case "addtofavorites":
437
									if ($e->getCode() == MAPI_E_COLLISION) {
438
										$e->setDisplayMessage(_("A favorite folder with this name already exists, please use another name."));
439
									}
440
									else {
441
										$e->setDisplayMessage(_("Could not add folder to favorites."));
442
									}
443
									break;
444
							}
445
						}
446
						else {
447
							// Exception generated while setting folder permissions.
448
							if (isset($action["permissions"])) {
449
								if ($e->getCode() == MAPI_E_NO_ACCESS) {
450
									$e->setDisplayMessage(_("You have insufficient privileges to set permissions for this folder."));
451
								}
452
								else {
453
									$e->setDisplayMessage(_("Could not set folder permissions."));
454
								}
455
							}
456
							else {
457
								// Exception generated while renaming folder.
458
								switch ($e->getCode()) {
459
									case MAPI_E_NO_ACCESS:
460
										$e->setDisplayMessage(_("You have insufficient privileges to rename this folder."));
461
										break;
462
463
									case MAPI_E_COLLISION:
464
										$e->setDisplayMessage(_("A folder with this name already exists. Use another name."));
465
										break;
466
467
									default:
468
										$e->setDisplayMessage(_("Could not rename folder."));
469
								}
470
							}
471
						}
472
					}
473
					else {
474
						// Exception generated while creating new folder.
475
						switch ($e->getCode()) {
476
							case MAPI_E_NO_ACCESS:
477
								$e->setDisplayMessage(_("You have insufficient privileges to create this folder."));
478
								break;
479
480
							case MAPI_E_COLLISION:
481
								$e->setDisplayMessage(_("A folder with this name already exists. Use another name."));
482
								break;
483
484
							default:
485
								$e->setDisplayMessage(_("Could not create folder."));
486
						}
487
					}
488
					break;
489
490
				case "closesharedfolder":
491
					$e->setDisplayMessage(_("Could not close shared folder."));
492
					break;
493
494
				case "opensharedfolder":
495
					if ($e->getCode() == MAPI_E_NOT_FOUND) {
496
						$e->setDisplayMessage(_("User could not be resolved."));
497
					}
498
					else {
499
						$folderType = $action["folder_type"];
500
						if ($folderType == "all") {
501
							$folderType = 'entire inbox';
502
						}
503
						$e->setDisplayMessage(sprintf(_('You have insufficient privileges to open this %1$s folder. The folder owner can set these using the \'permissions\'-tab of the folder properties (right click the %1$s folder > properties > permissions).'), $folderType));
504
					}
505
					break;
506
			}
507
		}
508
509
		parent::handleException($e, $actionType, $store, $parententryid, $entryid, $action);
510
	}
511
512
	/**
513
	 * Generates the hierarchy list. All folders and subfolders are added to response data.
514
	 */
515
	public function hierarchyList() {
516
		$data = $GLOBALS["operations"]->getHierarchyList($this->list_properties);
517
518
		$this->addActionData("list", $data);
519
		$GLOBALS["bus"]->addData($this->getResponseData());
520
	}
521
522
	/**
523
	 * Add folder's properties to response data. This function doesn't add persmission details yet.
524
	 *
525
	 * @param resource $store      mapi store of the folder
526
	 * @param string   $entryid    entryid of the folder
527
	 * @param string   $actionType type of action
528
	 */
529
	public function addFolderToResponseData($store, $entryid, $actionType) {
530
		$folder = mapi_msgstore_openentry($store, $entryid);
531
		$folderProps = mapi_getprops($folder, $this->list_properties);
532
533
		$data = $GLOBALS["operations"]->setFolder($folderProps);
534
		$this->addActionData($actionType, $data);
535
	}
536
537
	/**
538
	 * Adds a folder to the hierarchylist.
539
	 *
540
	 * @param object $store         message Store Object
541
	 * @param string $parententryid entryid of the parent folder
542
	 * @param string $name          name of the new folder
543
	 * @param string $type          type of the folder (calendar, mail, ...).
544
	 *
545
	 * @return bool true on success or false on failure
546
	 */
547
	public function addFolder($store, $parententryid, $name, $type) {
548
		$props = [];
549
		$result = $GLOBALS["operations"]->createFolder($store, $parententryid, $name, $type, $props);
550
551
		if ($result && isset($props[PR_ENTRYID])) {
552
			// Notify about this newly created folder
553
			$this->addFolderToResponseData($store, $props[PR_ENTRYID], "update");
0 ignored issues
show
Bug introduced by
$store of type object is incompatible with the type resource expected by parameter $store of HierarchyModule::addFolderToResponseData(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

553
			$this->addFolderToResponseData(/** @scrutinizer ignore-type */ $store, $props[PR_ENTRYID], "update");
Loading history...
554
555
			// Add all response data to Bus
556
			$GLOBALS["bus"]->addData($this->getResponseData());
557
558
			// Notify parent folder that a folder has been added
559
			$props[PR_ENTRYID] = $parententryid;
560
			$GLOBALS["bus"]->notify(bin2hex($parententryid), OBJECT_SAVE, $props);
561
		}
562
563
		return $result;
564
	}
565
566
	/**
567
	 * returns properties of a folder, used by the properties dialog.
568
	 *
569
	 * @param mixed $store
570
	 * @param mixed $folder
571
	 */
572
	public function getFolderProps($store, $folder) {
573
		$data = $GLOBALS["operations"]->getProps($folder, $this->properties);
574
575
		// adding container_class if missing
576
		if (!isset($data["props"]["container_class"])) {
577
			$data["props"]["container_class"] = "IPF.Note";
578
		}
579
580
		// replace "IPM_SUBTREE" with the display name of the store, and use the store message size
581
		$store_props = mapi_getprops($store, [PR_IPM_SUBTREE_ENTRYID]);
582
		if ($data["entryid"] == bin2hex($store_props[PR_IPM_SUBTREE_ENTRYID])) {
583
			$store_props = mapi_getprops($store, [PR_DISPLAY_NAME, PR_MESSAGE_SIZE_EXTENDED,
584
				PR_CONTENT_COUNT, PR_QUOTA_WARNING_THRESHOLD, PR_PROHIBIT_SEND_QUOTA, PR_PROHIBIT_RECEIVE_QUOTA, ]);
585
			$data["props"]["display_name"] = $store_props[PR_DISPLAY_NAME];
586
			$data["props"]["message_size"] = round($store_props[PR_MESSAGE_SIZE_EXTENDED]);
587
			$data["props"]["content_count"] = $store_props[PR_CONTENT_COUNT];
588
			$data["props"]["store_size"] = round($store_props[PR_MESSAGE_SIZE_EXTENDED]);
589
590
			if (isset($store_props[PR_QUOTA_WARNING_THRESHOLD])) {
591
				$data["props"]["quota_warning"] = round($store_props[PR_QUOTA_WARNING_THRESHOLD]);
592
			}
593
			if (isset($store_props[PR_PROHIBIT_SEND_QUOTA])) {
594
				$data["props"]["quota_soft"] = round($store_props[PR_PROHIBIT_SEND_QUOTA]);
595
			}
596
			if (isset($store_props[PR_PROHIBIT_RECEIVE_QUOTA])) {
597
				$data["props"]["quota_hard"] = round($store_props[PR_PROHIBIT_RECEIVE_QUOTA]);
598
			}
599
		}
600
601
		// calculating missing message_size
602
		if (!isset($data["props"]["message_size"])) {
603
			$data["props"]["message_size"] = round($GLOBALS["operations"]->calcFolderMessageSize($folder, false));
604
		}
605
606
		// retrieving folder permissions
607
		$data["permissions"] = [
608
			"item" => $this->getFolderPermissions($folder),
609
		];
610
611
		return $data;
612
	}
613
614
	/**
615
	 * Returns the size and total_size of the given folder.
616
	 *
617
	 * @param mapistore  $store       The store to which the folder belongs
0 ignored issues
show
Bug introduced by
The type mapistore 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. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
618
	 * @param mapifolder $folder      The folder for which the size must be calculated
0 ignored issues
show
Bug introduced by
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. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
619
	 * @param string     $pathname    The path of the current folder
620
	 * @param array      &$subfolders The array in which all information for the subfolders are stored
621
	 * @param bool       $hidden      True to prevent the subfolders to be stored into the $subfolders argument
622
	 *
623
	 * @return array The response data
624
	 */
625
	public function getFolderSize($store, $folder, $pathname, &$subfolders, $hidden = false) {
626
		$columns = [PR_ENTRYID, PR_PARENT_ENTRYID, PR_STORE_ENTRYID, PR_OBJECT_TYPE, PR_DISPLAY_NAME, PR_ATTR_HIDDEN];
627
		$size = $GLOBALS["operations"]->calcFolderMessageSize($folder, false);
628
		$total_size = $size;
629
630
		$table = mapi_folder_gethierarchytable($folder, MAPI_DEFERRED_ERRORS);
631
632
		mapi_table_setcolumns($table, $columns);
633
		$columns = null;
634
635
		$rows = mapi_table_queryrows($table, $columns, 0, 0x7FFFFFF);
636
		foreach ($rows as $row) {
637
			$subfolder = mapi_msgstore_openentry($store, $row[PR_ENTRYID]);
638
			$subpath = (!empty($pathname) ? ($pathname . '\\') : '') . $row[PR_DISPLAY_NAME];
639
640
			/**
641
			 * Don't add  hidden folders, folders with PR_ATTR_HIDDEN property set
642
			 * should not be shown to the client.
643
			 */
644
			$hide = $hidden === true || (isset($row[PR_ATTR_HIDDEN]) && $row[PR_ATTR_HIDDEN] === true);
645
			$info = $this->getFolderSize($store, $subfolder, $subpath, $subfolders, $hide);
646
647
			if ($hide !== true) {
648
				array_push($subfolders, [
649
					"entryid" => bin2hex($row[PR_ENTRYID]),
650
					"parent_entryid" => bin2hex($row[PR_PARENT_ENTRYID]),
651
					"store_entryid" => bin2hex($row[PR_STORE_ENTRYID]),
652
					"props" => [
653
						"folder_pathname" => $subpath, // This equals PR_FOLDER_PATHNAME, which is not supported by Gromox
654
						"display_name" => $row[PR_DISPLAY_NAME],
655
						"object_type" => $row[PR_OBJECT_TYPE],
656
						"message_size" => $info["size"],
657
						"total_message_size" => $info["total_size"],
658
					],
659
				]);
660
			}
661
662
			$total_size += $info["total_size"];
663
		}
664
665
		return ["size" => $size, "total_size" => $total_size];
666
	}
667
668
	/**
669
	 * Function which saves changed properties to a folder.
670
	 *
671
	 * @param object $store  MAPI object of the store
672
	 * @param object $folder MAPI object of the folder
673
	 * @param mixed  $action
674
	 */
675
	public function save($store, $folder, $action) {
676
		// Rename folder
677
		if (isset($action["props"]["display_name"])) {
678
			$this->modifyFolder($store, hex2bin($action["entryid"]), $action["props"]["display_name"]);
679
		}
680
681
		if (isset($action["props"]["comment"])) {
682
			mapi_setprops($folder, [PR_COMMENT => $action["props"]["comment"]]);
683
		}
684
685
		if (isset($action["permissions"])) {
686
			$this->setFolderPermissions($folder, $action["permissions"]);
687
			if (isset($action['props']['recursive'])) {
688
				$hierarchyTable = mapi_folder_gethierarchytable($folder, CONVENIENT_DEPTH | MAPI_DEFERRED_ERRORS);
689
				$subfolders = mapi_table_queryallrows($hierarchyTable, [PR_ENTRYID]);
690
				foreach ($subfolders as $subfolder) {
691
					$folderObject = mapi_msgstore_openentry($store, $subfolder[PR_ENTRYID]);
692
					$this->setFolderPermissions($folderObject, $action["permissions"]);
693
					mapi_savechanges($folderObject);
694
				}
695
			}
696
		}
697
698
		mapi_savechanges($folder);
699
	}
700
701
	public function getFolderPermissions($folder) {
702
		$eidObj = $GLOBALS["entryid"]->createMsgStoreEntryIdObj(hex2bin($this->store_entryid));
703
		$cnUserPos = strrpos($eidObj['MailboxDN'], '/cn=');
704
		$cnUserBase = ($cnUserPos !== false) ? substr($eidObj['MailboxDN'], 0, $cnUserPos) : '';
705
		$grants = mapi_zarafa_getpermissionrules($folder, ACCESS_TYPE_GRANT);
706
		foreach ($grants as $id => $grant) {
707
			// The mapi_zarafa_getpermissionrules returns the entryid in the userid key
708
			$userinfo = $this->getUserInfo($grant, $cnUserBase);
709
710
			$rights = [];
711
			$rights["entryid"] = $userinfo["entryid"];
712
			$rights["props"] = [];
713
			$rights["props"]["type"] = ACCESS_TYPE_GRANT;
714
			$rights["props"]["display_name"] = $userinfo["fullname"];
715
			$rights["props"]["object_type"] = $userinfo["type"];
716
			$rights["props"]["entryid"] = $userinfo["entryid"];
717
			$rights["props"]["rights"] = $grant["rights"];
718
719
			$grants[$id] = $rights;
720
		}
721
722
		return $grants;
723
	}
724
725
	public function setFolderPermissions($folder, $permissions) {
726
		$folderProps = mapi_getprops($folder, [PR_DISPLAY_NAME, PR_STORE_ENTRYID, PR_ENTRYID]);
727
		$store = $GLOBALS["mapisession"]->openMessageStore($folderProps[PR_STORE_ENTRYID]);
728
		$currentPermissions = $this->getFolderPermissions($folder);
729
730
		// check if the folder is the default calendar, if so we also need to set the same permissions on the freebusy folder
731
		$root = mapi_msgstore_openentry($store, null);
732
		if ($root) {
733
			$rootProps = mapi_getprops($root, [PR_IPM_APPOINTMENT_ENTRYID]);
734
			if ($folderProps[PR_ENTRYID] == $rootProps[PR_IPM_APPOINTMENT_ENTRYID]) {
735
				$freebusy = FreeBusy::getLocalFreeBusyFolder($store);
736
			}
737
		}
738
739
		// first, get the current permissions because we need to delete all current acl's
740
		$curAcls = mapi_zarafa_getpermissionrules($folder, ACCESS_TYPE_GRANT);
741
		$eidObj = $GLOBALS["entryid"]->createMsgStoreEntryIdObj(hex2bin($this->store_entryid));
742
		$cnUserPos = strrpos($eidObj['MailboxDN'], '/cn=');
743
		$cnUserBase = ($cnUserPos !== false) ? substr($eidObj['MailboxDN'], 0, $cnUserPos) : '';
744
		foreach ($curAcls as &$curAcl) {
745
			$curAcl = $this->getUserInfo($curAcl, $cnUserBase);
746
		}
747
748
		// First check which permissions should be removed from the existing list
749
		if (isset($permissions['remove']) && !empty($permissions['remove'])) {
750
			foreach ($permissions['remove'] as $i => &$delAcl) {
751
				$userid = hex2bin($delAcl['entryid']);
752
				foreach ($curAcls as $aclIndex => &$curAcl) {
753
					// do not remove default and anonymous grants
754
					if ($curAcl['userid'] === $userid && $curAcl['memberid'] != 0 && $curAcl['memberid'] != 0xFFFFFFFF) {
755
						$curAcl['rights'] = ecRightsNone;
756
						$curAcl['state'] = RIGHT_DELETED | RIGHT_AUTOUPDATE_DENIED;
757
					}
758
				}
759
				unset($curAcl);
760
			}
761
			unset($delAcl);
762
		}
763
764
		// Then we check which permissions must be updated in the existing list
765
		if (isset($permissions['modify']) && !empty($permissions['modify'])) {
766
			foreach ($permissions['modify'] as $i => &$modAcl) {
767
				$entryid = $modAcl['entryid'];
768
				// No permission for this user
769
				// This is necessary for recursive folder permissions.
770
				// If a subfolder does not have any permissions for the user yet,
771
				// they need to be added instead of modified.
772
				if (!$this->idInCurrentPermissions($currentPermissions, $entryid)) {
773
					if (!isset($permissions['add'])) {
774
						$permissions['add'] = [];
775
					}
776
					array_push($permissions['add'], $modAcl);
777
				}
778
				else {
779
					$userid = hex2bin($entryid);
780
					foreach ($curAcls as $aclIndex => &$curAcl) {
781
						if ($curAcl['userid'] === $userid) {
782
							$curAcl['rights'] = $modAcl['rights'];
783
							$curAcl['state'] = RIGHT_MODIFY | RIGHT_AUTOUPDATE_DENIED;
784
						}
785
						if (isset($curAcl['memberid']) && ($curAcl['memberid'] == 0 || $curAcl['memberid'] == 0xFFFFFFFF)) {
786
							$curAcl['userid'] = null;
787
						}
788
					}
789
				}
790
				unset($curAcl);
791
			}
792
			unset($modAcl);
793
		}
794
795
		// Finally we check which permissions must be added to the existing list
796
		if (isset($permissions['add']) && !empty($permissions['add'])) {
797
			$cnt = count($curAcls);
798
			foreach ($permissions['add'] as $i => &$addAcl) {
799
				$memberid = $GLOBALS['entryid']->getMemberidFromEntryid(hex2bin($addAcl['entryid']));
800
				if ($memberid === false) {
801
					continue;
802
				}
803
				$curAcls[$cnt++] = [
804
					'type' => ACCESS_TYPE_GRANT,
805
					'userid' => hex2bin($addAcl['entryid']),
806
					'rights' => $addAcl['rights'],
807
					'state' => RIGHT_NEW | RIGHT_AUTOUPDATE_DENIED,
808
					'memberid' => $memberid,
809
				];
810
			}
811
			unset($addAcl);
812
		}
813
814
		if (!empty($curAcls)) {
815
			mapi_zarafa_setpermissionrules($folder, $curAcls);
816
817
			// $freebusy is only set when the calendar folder permissions is updated
818
			if (isset($freebusy) && $freebusy !== false) {
819
				// set permissions on free/busy message
820
				foreach ($curAcls as $key => &$acl) {
821
					if ($acl['type'] == ACCESS_TYPE_GRANT && ($acl['rights'] & ecRightsEditOwned)) {
822
						$acl['rights'] |= ecRightsEditAny;
823
					}
824
				}
825
				unset($acl);
826
				mapi_zarafa_setpermissionrules($freebusy, $curAcls);
827
			}
828
		}
829
	}
830
831
	public function idInCurrentPermissions($currentPermissions, $entryid) {
832
		foreach ($currentPermissions as $permission) {
833
			if ($permission['entryid'] === $entryid) {
834
				return true;
835
			}
836
		}
837
838
		return false;
839
	}
840
841
	public function getUserInfo($grant, $cnUserBase) {
842
		// Create fake entryids for default and anonymous permissions
843
		if ($grant["memberid"] == 0) {
844
			$entryid = $GLOBALS["entryid"]->createMuidemsabEntryid($cnUserBase . '/cn=0000000000000000-default');
845
			$grant["fullname"] = _("default");
846
			$grant["username"] = _("default");
847
			$grant["entryid"] = $entryid;
848
			$grant["userid"] = hex2bin($entryid);
849
850
			return $grant;
851
		}
852
853
		if ($grant["memberid"] == 0xFFFFFFFF) {
854
			$entryid = $GLOBALS["entryid"]->createMuidemsabEntryid($cnUserBase . '/cn=ffffffffffffffff-anonymous');
855
			$grant["fullname"] = _("anonymous");
856
			$grant["username"] = _("anonymous");
857
			$grant["entryid"] = $entryid;
858
			$grant["userid"] = hex2bin($entryid);
859
860
			return $grant;
861
		}
862
863
		// open the addressbook
864
		$ab = $GLOBALS["mapisession"]->getAddressbook();
865
		$user = mapi_ab_openentry($ab, $grant["userid"]);
866
		if ($user) {
867
			$props = mapi_getprops($user, [PR_ACCOUNT, PR_DISPLAY_NAME, PR_OBJECT_TYPE]);
868
869
			$grant["fullname"] = $props[PR_DISPLAY_NAME];
870
			$grant["username"] = $props[PR_ACCOUNT];
871
			$grant["entryid"] = bin2hex($grant["userid"]);
872
873
			return $grant;
874
		}
875
876
		error_log(sprintf("No user with the entryid %s found (memberid: %s)", bin2hex($grant["userid"]), $grant["memberid"]));
877
878
		// default return stuff
879
		return [
880
			"fullname" => _("Unknown user/group"),
881
			"username" => _("unknown"),
882
			"entryid" => null,
883
			"type" => MAPI_MAILUSER,
884
			"id" => $grant["userid"],
885
			"userid" => null,
886
		];
887
	}
888
889
	/**
890
	 * Function is used to get the IPM_COMMON_VIEWS folder from defaults store.
891
	 *
892
	 * @return object MAPI folder object
893
	 */
894
	public function getCommonViewsFolder() {
895
		$defaultStore = $GLOBALS["mapisession"]->getDefaultMessageStore();
896
		$commonViewsFolderEntryid = mapi_getprops($defaultStore, [PR_COMMON_VIEWS_ENTRYID]);
897
		$commonViewsFolder = mapi_msgstore_openentry($defaultStore, $commonViewsFolderEntryid[PR_COMMON_VIEWS_ENTRYID]);
898
899
		return $commonViewsFolder;
900
	}
901
902
	/**
903
	 * Remove favorites link message from associated contains table of IPM_COMMON_VIEWS.
904
	 * It will also remove favorites search folders of given store.
905
	 *
906
	 * @param string $entryid  entryid of the folder
907
	 * @param object $store    MAPI object of the store
908
	 * @param string $prop     property which is used to find record from associated contains table of
909
	 *                         IPM_COMMON_VIEWS folder
910
	 * @param bool   $doNotify true to notify the IPM_COMMO_VIEWS folder on client side
911
	 */
912
	public function removeFromFavorite($entryid, $store = false, $prop = PR_WLINK_ENTRYID, $doNotify = true) {
913
		$commonViewsFolder = $this->getCommonViewsFolder();
914
		$associatedTable = mapi_folder_getcontentstable($commonViewsFolder, MAPI_ASSOCIATED);
915
916
		$restriction = [RES_OR,
917
			[
918
				[RES_PROPERTY,
919
					[
920
						RELOP => RELOP_EQ,
921
						ULPROPTAG => PR_MESSAGE_CLASS,
922
						VALUE => [PR_MESSAGE_CLASS => "IPM.Microsoft.WunderBar.Link"],
923
					],
924
				],
925
				[RES_PROPERTY,
926
					[
927
						RELOP => RELOP_EQ,
928
						ULPROPTAG => PR_MESSAGE_CLASS,
929
						VALUE => [PR_MESSAGE_CLASS => "IPM.Microsoft.WunderBar.SFInfo"],
930
					],
931
				],
932
			],
933
		];
934
		$finderHierarchyTables = [];
935
		if (!empty($store)) {
936
			$props = mapi_getprops($store, [PR_FINDER_ENTRYID]);
937
938
			try {
939
				$finderFolder = mapi_msgstore_openentry($store, $props[PR_FINDER_ENTRYID]);
940
				$hierarchyTable = mapi_folder_gethierarchytable($finderFolder, MAPI_DEFERRED_ERRORS);
941
				$finderHierarchyTables[$props[PR_FINDER_ENTRYID]] = $hierarchyTable;
942
			}
943
			catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
944
			}
945
		}
946
947
		$messages = mapi_table_queryallrows($associatedTable, [PR_ENTRYID, PR_MESSAGE_CLASS, PR_WB_SF_ID, PR_WLINK_ENTRYID, PR_WLINK_STORE_ENTRYID, PR_PARENT_ENTRYID, PR_STORE_ENTRYID], $restriction);
948
949
		if (!empty($messages)) {
950
			foreach ($messages as $message) {
951
				if ($message[PR_MESSAGE_CLASS] === "IPM.Microsoft.WunderBar.SFInfo" && !empty($finderHierarchyTables)) {
952
					$props = $GLOBALS["operations"]->getFavoritesLinkedSearchFolderProps($message[PR_WB_SF_ID], $finderHierarchyTables);
953
					if (!empty($props)) {
954
						$this->deleteSearchFolder($store, $props[PR_PARENT_ENTRYID], $props[PR_ENTRYID], []);
0 ignored issues
show
Bug introduced by
It seems like $store can also be of type false; however, parameter $store of HierarchyModule::deleteSearchFolder() does only seem to accept object, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

954
						$this->deleteSearchFolder(/** @scrutinizer ignore-type */ $store, $props[PR_PARENT_ENTRYID], $props[PR_ENTRYID], []);
Loading history...
955
					}
956
				}
957
				elseif ($message[PR_MESSAGE_CLASS] === "IPM.Microsoft.WunderBar.Link") {
958
					if (isset($message[$prop]) && $GLOBALS['entryid']->compareEntryIds($message[$prop], $entryid)) {
959
						mapi_folder_deletemessages($commonViewsFolder, [$message[PR_ENTRYID]]);
960
						if ($doNotify) {
961
							$GLOBALS["bus"]->notify(bin2hex($message[PR_ENTRYID]), OBJECT_SAVE, $message);
962
						}
963
					}
964
					elseif (isset($message[PR_WLINK_STORE_ENTRYID])) {
965
						$storeObj = $GLOBALS["mapisession"]->openMessageStore($message[PR_WLINK_STORE_ENTRYID]);
966
						$storeProps = mapi_getprops($storeObj, [PR_ENTRYID]);
967
						if ($GLOBALS['entryid']->compareEntryIds($message[PR_WLINK_ENTRYID], $storeProps[PR_ENTRYID])) {
968
							mapi_folder_deletemessages($commonViewsFolder, [$message[PR_ENTRYID]]);
969
							$this->sendFeedback(true);
970
						}
971
					}
972
				}
973
			}
974
		}
975
	}
976
977
	/**
978
	 * Function which is used to remove the search link message(IPM.Microsoft.WunderBar.SFInfo)
979
	 * from associated contains table of IPM_COMMON_VIEWS folder.
980
	 *
981
	 * @param string $searchFolderId GUID that identifies the search folder
982
	 */
983
	public function removeSearchLinkMessage($searchFolderId) {
984
		$commonViewsFolder = $this->getCommonViewsFolder();
985
		$associatedTable = mapi_folder_getcontentstable($commonViewsFolder, MAPI_ASSOCIATED);
986
987
		$restriction = [RES_AND,
988
			[
989
				[RES_PROPERTY,
990
					[
991
						RELOP => RELOP_EQ,
992
						ULPROPTAG => PR_MESSAGE_CLASS,
993
						VALUE => [PR_MESSAGE_CLASS => "IPM.Microsoft.WunderBar.SFInfo"],
994
					],
995
				],
996
				[RES_PROPERTY,
997
					[
998
						RELOP => RELOP_EQ,
999
						ULPROPTAG => PR_WB_SF_ID,
1000
						VALUE => [PR_WB_SF_ID => hex2bin($searchFolderId)],
1001
					],
1002
				],
1003
			],
1004
		];
1005
1006
		$messages = mapi_table_queryallrows($associatedTable, [PR_WB_SF_ID, PR_ENTRYID], $restriction);
1007
1008
		if (!empty($messages)) {
1009
			foreach ($messages as $message) {
1010
				if (bin2hex($message[PR_WB_SF_ID]) === $searchFolderId) {
1011
					mapi_folder_deletemessages($commonViewsFolder, [$message[PR_ENTRYID]]);
1012
				}
1013
			}
1014
		}
1015
	}
1016
1017
	/**
1018
	 * Function is used to create link message for the selected folder
1019
	 * in associated contains of IPM_COMMON_VIEWS folder.
1020
	 *
1021
	 * @param string $store   $store entryid of the store
1022
	 * @param string $entryid $entryid entryid of the MAPI folder
1023
	 */
1024
	public function addToFavorite($store, $entryid) {
1025
		$commonViewsFolder = $this->getCommonViewsFolder();
1026
1027
		// In Favorites list all folders are must be sibling of other folders.
1028
		// whether it is sub folder of any other folders.
1029
		// So unset "subfolders" property as we don't required to show sub folders in favorites list.
1030
		unset($this->properties["subfolders"]);
1031
		$folder = mapi_msgstore_openentry($store, $entryid);
1032
		$folderProps = mapi_getprops($folder, $this->properties);
1033
		$GLOBALS["operations"]->createFavoritesLink($commonViewsFolder, $folderProps);
1034
1035
		$this->addActionData("update", $GLOBALS["operations"]->setFavoritesFolder($folderProps));
1036
		$GLOBALS["bus"]->addData($this->getResponseData());
1037
	}
1038
1039
	/**
1040
	 * Function which is used delete the search folder from respective store.
1041
	 *
1042
	 * @param object $store         $store $store MAPI store in which search folder is belongs
1043
	 * @param array  $parententryid $parententryid parent folder to search folder it is FIND_ROOT folder which
1044
	 *                              treated as search root folder
1045
	 * @param string $entryid       $entryid search folder entryid which is going to remove
1046
	 * @param array  $action        the action data, sent by the client
1047
	 */
1048
	public function deleteSearchFolder($store, $parententryid, $entryid, $action) {
1049
		$folder = mapi_msgstore_openentry($store, $entryid);
1050
		$props = mapi_getprops($folder, [PR_EXTENDED_FOLDER_FLAGS]);
1051
		// for more information about PR_EXTENDED_FOLDER_FLAGS go through this link
1052
		// https://msdn.microsoft.com/en-us/library/ee203919(v=exchg.80).aspx
1053
		$flags = unpack("H2ExtendedFlags-Id/H2ExtendedFlags-Cb/H8ExtendedFlags-Data/H2SearchFolderTag-Id/H2SearchFolderTag-Cb/H8SearchFolderTag-Data/H2SearchFolderId-Id/H2SearchFolderId-Cb/H32SearchFolderId-Data", $props[PR_EXTENDED_FOLDER_FLAGS]);
1054
		$searchFolderId = $flags["SearchFolderId-Data"];
1055
		$this->removeSearchLinkMessage($searchFolderId);
1056
1057
		// Do not remove the search folder when the 'keepSearchFolder' flag is set.
1058
		// This flag indicates there is currently an open search tab which uses this search folder.
1059
		if (!isset($action["message_action"]["keepSearchFolder"])) {
1060
			$finderFolder = mapi_msgstore_openentry($store, $parententryid);
1061
1062
			return mapi_folder_deletefolder($finderFolder, $entryid, DEL_FOLDERS | DEL_MESSAGES | DELETE_HARD_DELETE);
1063
		}
1064
		// Rename search folder to default search folder name otherwise,
1065
		// It will not be picked up by our search folder cleanup logic.
1066
		$storeProps = mapi_getprops($store, [PR_FINDER_ENTRYID]);
1067
		$props = [];
1068
		$folder = mapi_msgstore_openentry($store, $storeProps[PR_FINDER_ENTRYID]);
1069
		$folderName = $GLOBALS["operations"]->checkFolderNameConflict($store, $folder, "grommunio Web Search Folder");
1070
1071
		return $GLOBALS["operations"]->renameFolder($store, $entryid, $folderName, $props);
1072
	}
1073
1074
	/**
1075
	 * Function which is used create link message for the created search folder.
1076
	 * in associated contains table of IPM_COMMON_VIEWS folder.
1077
	 *
1078
	 * @param object $folder MAPI search folder for which link message needs to
1079
	 *                       create in associated contains table of IPM_COMMON_VIEWS folder
1080
	 */
1081
	public function createLinkedSearchFolder($folder) {
1082
		$searchFolderTag = openssl_random_pseudo_bytes(4);
1083
		$searchFolderId = openssl_random_pseudo_bytes(16);
1084
1085
		// PR_EXTENDED_FOLDER_FLAGS used to create permanent/persistent search folder in MAPI.
1086
		// also it used to identify/get the linked search folder from FINDER_ROOT folder.
1087
		// PR_EXTENDED_FOLDER_FLAGS contains at least the SearchFolderTag, SearchFolderID,
1088
		// and ExtendedFlags subproperties.
1089
		// For more information about PR_EXTENDED_FOLDER_FLAGS go through this link
1090
		// https://msdn.microsoft.com/en-us/library/ee203919(v=exchg.80).aspx
1091
		$extendedFolderFlags = "0104000000010304" . bin2hex($searchFolderTag) . "0210" . bin2hex($searchFolderId);
1092
1093
		mapi_setprops($folder, [
1094
			PR_EXTENDED_FOLDER_FLAGS => hex2bin($extendedFolderFlags),
1095
		]);
1096
		mapi_savechanges($folder);
1097
1098
		$folderProps = mapi_getprops($folder, $this->properties);
1099
		$commonViewsFolder = $this->getCommonViewsFolder();
1100
		$GLOBALS["operations"]->createFavoritesLink($commonViewsFolder, $folderProps, $searchFolderId);
1101
1102
		$folderProps = mapi_getprops($folder, $this->properties);
1103
		$this->addActionData("update", $GLOBALS["operations"]->setFavoritesFolder($folderProps));
1104
		$GLOBALS["bus"]->addData($this->getResponseData());
1105
	}
1106
1107
	/**
1108
	 * Modifies a folder off the hierarchylist.
1109
	 *
1110
	 * @param object $store   message Store Object
1111
	 * @param string $entryid entryid of the folder
1112
	 * @param string $name    name of the folder
1113
	 */
1114
	public function modifyFolder($store, $entryid, $name) {
1115
		$props = [];
1116
		$result = $GLOBALS["operations"]->renameFolder($store, $entryid, $name, $props);
1117
1118
		if ($result && isset($props[PR_ENTRYID])) {
1119
			$GLOBALS["bus"]->notify(bin2hex($props[PR_ENTRYID]), OBJECT_SAVE, $props);
1120
		}
1121
	}
1122
1123
	/**
1124
	 * Deletes a folder in the hierarchylist.
1125
	 *
1126
	 * @param object $store         message Store Object
1127
	 * @param string $parententryid entryid of the parent folder
1128
	 * @param string $entryid       entryid of the folder
1129
	 * @param array  $action        the action data, sent by the client
1130
	 */
1131
	public function deleteFolder($store, $parententryid, $entryid, $action) {
1132
		$props = [];
1133
		$pubStore = $GLOBALS["mapisession"]->isPublicStore($action['store_entryid'] ?? '');
1134
		$result = $GLOBALS["operations"]->deleteFolder($store, $parententryid, $entryid, $props, isset($action['soft_delete']) ? $action['soft_delete'] : false, $pubStore ? true : false);
1135
1136
		// Indicate if the delete succeedded
1137
		$this->sendFeedback($result);
1138
1139
		if (isset($props[PR_ENTRYID])) {
1140
			if ($result) {
1141
				$GLOBALS["bus"]->notify(bin2hex($parententryid), OBJECT_SAVE, $props);
1142
1143
				$props = [];
1144
				$props[PR_PARENT_ENTRYID] = $parententryid;
1145
1146
				$storeprops = mapi_getprops($store, [PR_ENTRYID, PR_IPM_WASTEBASKET_ENTRYID]);
1147
				$props[PR_STORE_ENTRYID] = $storeprops[PR_ENTRYID];
1148
				$GLOBALS["bus"]->notify(bin2hex($parententryid), OBJECT_SAVE, $props);
1149
1150
				$props[PR_PARENT_ENTRYID] = $storeprops[PR_IPM_WASTEBASKET_ENTRYID];
1151
				$GLOBALS["bus"]->notify(bin2hex($parententryid), OBJECT_SAVE, $props);
1152
			}
1153
		}
1154
		else {
1155
			$props[PR_ENTRYID] = $entryid;
1156
			$props[PR_PARENT_ENTRYID] = $parententryid;
1157
1158
			if ($result) {
1159
				$this->removeFromFavorite($props[PR_ENTRYID]);
1160
1161
				$storeprops = mapi_getprops($store, [PR_ENTRYID, PR_IPM_FAVORITES_ENTRYID]);
1162
				$props[PR_STORE_ENTRYID] = $storeprops[PR_ENTRYID];
1163
1164
				// Notify about that folder is deleted
1165
				$GLOBALS["bus"]->notify(bin2hex($parententryid), OBJECT_DELETE, $props);
1166
1167
				// Notify its parent about the delete
1168
				$GLOBALS["bus"]->notify(bin2hex($parententryid), OBJECT_SAVE, $props);
1169
1170
				// Notifying corresponding folder in 'Favorites'
1171
				if (isset($storeprops[PR_IPM_FAVORITES_ENTRYID])) {
1172
					$folderEntryID = "00000001" . substr(bin2hex($entryid), 8);
1173
					$props[PR_ENTRYID] = hex2bin($folderEntryID);
1174
					$props[PR_PARENT_ENTRYID] = $storeprops[PR_IPM_FAVORITES_ENTRYID];
1175
					$GLOBALS["bus"]->notify(bin2hex($parententryid), OBJECT_DELETE, $props);
1176
				}
1177
			}
1178
		}
1179
	}
1180
1181
	/**
1182
	 * Deletes all messages in a folder.
1183
	 *
1184
	 * @param object $store   message Store Object
1185
	 * @param string $entryid entryid of the folder
1186
	 */
1187
	public function emptyFolder($store, $entryid) {
1188
		$props = [];
1189
1190
		$result = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $result is dead and can be removed.
Loading history...
1191
1192
		// False will only remove the message of
1193
		// selected folder only and can't remove the
1194
		// child folders.
1195
		$emptySubFolders = false;
1196
		$storeProps = mapi_getprops($store, [PR_IPM_WASTEBASKET_ENTRYID]);
1197
		// Check that selected folder is Waste basket or Junk folder then empty folder by removing
1198
		// the child folders.
1199
		if (isset($storeProps[PR_IPM_WASTEBASKET_ENTRYID]) && $storeProps[PR_IPM_WASTEBASKET_ENTRYID] === $entryid) {
1200
			$emptySubFolders = true;
1201
		}
1202
		else {
1203
			$root = mapi_msgstore_openentry($store, null);
1204
			$rootProps = mapi_getprops($root, [PR_ADDITIONAL_REN_ENTRYIDS]);
1205
			// check if selected folder is junk folder then make junk folder empty with
1206
			// it's child folder and it's contains.
1207
			if (isset($rootProps[PR_ADDITIONAL_REN_ENTRYIDS]) && is_array($rootProps[PR_ADDITIONAL_REN_ENTRYIDS])) {
1208
				// Checking if folder is junk folder or not.
1209
				$emptySubFolders = $GLOBALS['entryid']->compareEntryIds($rootProps[PR_ADDITIONAL_REN_ENTRYIDS][4], $entryid);
1210
			}
1211
1212
			if ($emptySubFolders === false) {
1213
				$folder = mapi_msgstore_openentry($store, $entryid);
1214
				$folderProps = mapi_getprops($folder, [PR_SUBFOLDERS]);
1215
				$emptySubFolders = $folderProps[PR_SUBFOLDERS] === false;
1216
			}
1217
		}
1218
1219
		$result = $GLOBALS["operations"]->emptyFolder($store, $entryid, $props, false, $emptySubFolders);
1220
1221
		if ($result && isset($props[PR_ENTRYID])) {
1222
			$this->addFolderToResponseData($store, $entryid, "folders");
0 ignored issues
show
Bug introduced by
$store of type object is incompatible with the type resource expected by parameter $store of HierarchyModule::addFolderToResponseData(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1222
			$this->addFolderToResponseData(/** @scrutinizer ignore-type */ $store, $entryid, "folders");
Loading history...
1223
1224
			// Add all response data to Bus
1225
			$GLOBALS["bus"]->addData($this->getResponseData());
1226
		}
1227
	}
1228
1229
	/**
1230
	 * Copies of moves a folder in the hierarchylist.
1231
	 *
1232
	 * @param object $store               message Store Object
1233
	 * @param string $parententryid       entryid of the parent folder
1234
	 * @param string $sourcefolderentryid entryid of the folder to be copied of moved
1235
	 * @param string $destfolderentryid   entryid of the destination folder
1236
	 * @param mixed  $deststore
1237
	 * @param mixed  $moveFolder
1238
	 */
1239
	public function copyFolder($store, $parententryid, $sourcefolderentryid, $destfolderentryid, $deststore, $moveFolder) {
1240
		$props = [];
1241
		$result = $GLOBALS["operations"]->copyFolder($store, $parententryid, $sourcefolderentryid, $destfolderentryid, $deststore, $moveFolder, $props);
1242
1243
		if ($result) {
1244
			if ($moveFolder) {
1245
				try {
1246
					// If destination folder is wastebasket then remove source folder from favorites list if
1247
					// it is present in it.
1248
					$defaultStore = $GLOBALS["mapisession"]->getDefaultMessageStore();
1249
					$wastebasketFolderEntryid = mapi_getprops($defaultStore, [PR_IPM_WASTEBASKET_ENTRYID]);
1250
					if ($GLOBALS["entryid"]->compareEntryIds($wastebasketFolderEntryid[PR_IPM_WASTEBASKET_ENTRYID], $destfolderentryid)) {
1251
						$this->removeFromFavorite($sourcefolderentryid);
1252
					}
1253
1254
					/*
1255
					 * Normally it works well within same store,
1256
					 * but entryid gets changed when different stores so we can't use old entryid anymore.
1257
					 * When moving to different store send delete notification.
1258
					 */
1259
					$this->addFolderToResponseData($deststore, $sourcefolderentryid, "folders");
1260
1261
					// Add all response data to Bus
1262
					$GLOBALS["bus"]->addData($this->getResponseData());
1263
				}
1264
				catch (MAPIException $e) {
1265
					$exCode = $e->getCode();
1266
					// gromox throws MAPI_E_INVALID_PARAMETER if it's not able to open an entry
1267
					if ($exCode == MAPI_E_INVALID_ENTRYID || $exCode == MAPI_E_INVALID_PARAMETER) {
1268
						// Entryid of the folder might be change after move, so send delete notification for folder.
1269
						$GLOBALS["bus"]->notify(bin2hex($props[PR_ENTRYID]), OBJECT_DELETE, $props);
1270
					}
1271
				}
1272
1273
				// if move folder then refresh parent of source folder
1274
				$sourcefolder = mapi_msgstore_openentry($store, $parententryid);
1275
				$folderProps = mapi_getprops($sourcefolder, [PR_ENTRYID, PR_STORE_ENTRYID]);
1276
				$GLOBALS["bus"]->notify(bin2hex($folderProps[PR_ENTRYID]), OBJECT_SAVE, $folderProps);
1277
			}
1278
			else {
1279
				$this->sendFeedback(true);
1280
			}
1281
1282
			// Update subfolders of copy/move folder
1283
			$folder = mapi_msgstore_openentry($deststore, $destfolderentryid);
1284
			$hierarchyTable = mapi_folder_gethierarchytable($folder, CONVENIENT_DEPTH | MAPI_DEFERRED_ERRORS);
1285
			mapi_table_sort($hierarchyTable, [PR_DISPLAY_NAME => TABLE_SORT_ASCEND], TBL_BATCH);
1286
1287
			/**
1288
			 * remove hidden folders, folders with PR_ATTR_HIDDEN property set
1289
			 * should not be shown to the client.
1290
			 */
1291
			$restriction = [RES_OR,
1292
				[
1293
					[RES_PROPERTY,
1294
						[
1295
							RELOP => RELOP_EQ,
1296
							ULPROPTAG => PR_ATTR_HIDDEN,
1297
							VALUE => [PR_ATTR_HIDDEN => false],
1298
						],
1299
					],
1300
					[RES_NOT,
1301
						[
1302
							[RES_EXIST,
1303
								[
1304
									ULPROPTAG => PR_ATTR_HIDDEN,
1305
								],
1306
							],
1307
						],
1308
					],
1309
				],
1310
			];
1311
1312
			$subfolders = mapi_table_queryallrows($hierarchyTable, [PR_ENTRYID], $restriction);
1313
1314
			if (is_array($subfolders)) {
1315
				foreach ($subfolders as $subfolder) {
1316
					$folderObject = mapi_msgstore_openentry($deststore, $subfolder[PR_ENTRYID]);
1317
					$folderProps = mapi_getprops($folderObject, [PR_ENTRYID, PR_STORE_ENTRYID]);
1318
					$GLOBALS["bus"]->notify(bin2hex($subfolder[PR_ENTRYID]), OBJECT_SAVE, $folderProps);
1319
				}
1320
			}
1321
1322
			// Now update destination folder
1323
			$folder = mapi_msgstore_openentry($deststore, $destfolderentryid);
1324
			$folderProps = mapi_getprops($folder, [PR_ENTRYID, PR_STORE_ENTRYID]);
1325
			$GLOBALS["bus"]->notify(bin2hex($folderProps[PR_ENTRYID]), OBJECT_SAVE, $folderProps);
1326
		}
1327
		else {
1328
			if ($moveFolder) {
1329
				$this->sendFeedback(false, _('Could not move folder'));
0 ignored issues
show
Bug introduced by
_('Could not move folder') of type string is incompatible with the type array expected by parameter $data of Module::sendFeedback(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1329
				$this->sendFeedback(false, /** @scrutinizer ignore-type */ _('Could not move folder'));
Loading history...
1330
			}
1331
			else {
1332
				$this->sendFeedback(false, _('Could not copy folder'));
1333
			}
1334
		}
1335
	}
1336
1337
	/**
1338
	 * Set all messages read.
1339
	 *
1340
	 * @param object $store   message Store Object
1341
	 * @param string $entryid entryid of the folder
1342
	 */
1343
	public function setReadFlags($store, $entryid) {
1344
		$props = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $props is dead and can be removed.
Loading history...
1345
		$folder = mapi_msgstore_openentry($store, $entryid);
1346
1347
		if (!$folder) {
1348
			return;
1349
		}
1350
1351
		if (mapi_folder_setreadflags($folder, [], SUPPRESS_RECEIPT)) {
1352
			$props = mapi_getprops($folder, [PR_ENTRYID, PR_STORE_ENTRYID]);
1353
1354
			if (!isset($props[PR_ENTRYID])) {
1355
				return;
1356
			}
1357
1358
			$this->addFolderToResponseData($store, $props[PR_ENTRYID], "folders");
0 ignored issues
show
Bug introduced by
$store of type object is incompatible with the type resource expected by parameter $store of HierarchyModule::addFolderToResponseData(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1358
			$this->addFolderToResponseData(/** @scrutinizer ignore-type */ $store, $props[PR_ENTRYID], "folders");
Loading history...
1359
1360
			// Add all response data to Bus
1361
			$GLOBALS["bus"]->addData($this->getResponseData());
1362
		}
1363
	}
1364
}
1365