Test Failed
Push — master ( 16d592...bcee4e )
by
unknown
10:59 queued 01:25
created

HierarchyModule::emptyFolder()   B

Complexity

Conditions 8
Paths 10

Size

Total Lines 39
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 8
eloc 19
c 2
b 0
f 0
nc 10
nop 2
dl 0
loc 39
rs 8.4444
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
		/**
11
		 * Constructor.
12
		 *
13
		 * @param int    $id            unique id
14
		 * @param string $folderentryid Entryid of the folder. Data will be selected from this folder.
15
		 * @param array  $data          list of all actions
16
		 */
17
		public function __construct($id, $data) {
18
			$this->properties = $GLOBALS["properties"]->getFolderProperties();
0 ignored issues
show
Bug Best Practice introduced by
The property properties does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
19
			$this->list_properties = $GLOBALS["properties"]->getFolderListProperties();
0 ignored issues
show
Bug Best Practice introduced by
The property list_properties does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
20
21
			parent::__construct($id, $data);
22
		}
23
24
		/**
25
		 * Creates the notifiers for this module,
26
		 * and register them to the Bus.
27
		 */
28
		public function createNotifiers() {
29
			$entryid = $this->getEntryID();
30
			$GLOBALS["bus"]->registerNotifier('hierarchynotifier', $entryid, true);
31
			$GLOBALS["bus"]->registerNotifier('hierarchynotifier', REQUEST_ENTRYID);
32
			$GLOBALS["bus"]->registerNotifier('newmailnotifier', REQUEST_ENTRYID);
33
		}
34
35
		/**
36
		 * Function which returns a list of entryids, which is used to register this module. It
37
		 * returns the ipm_subtree entryids of every message store.
38
		 *
39
		 * @return array list of entryids
40
		 */
41
		public function getEntryID() {
42
			$entryids = [];
43
			$storelist = $GLOBALS["mapisession"]->getAllMessageStores();
44
45
			foreach ($storelist as $entryid => $store) {
46
				$entryids[] = bin2hex($entryid);
47
			}
48
49
			return $entryids;
50
		}
51
52
		/**
53
		 * Executes all the actions in the $data variable.
54
		 *
55
		 * @return bool true on success or false on fialure
56
		 */
57
		public function execute() {
58
			foreach ($this->data as $actionType => $action) {
59
				if (isset($actionType)) {
60
					try {
61
						$store = $this->getActionStore($action);
62
						$parententryid = $this->getActionParentEntryID($action);
63
						$entryid = $this->getActionEntryID($action);
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
$entryid of type object is incompatible with the type string expected by parameter $entryid 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, $parententryid, /** @scrutinizer ignore-type */ $entryid, $action);
Loading history...
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);
0 ignored issues
show
Bug introduced by
$entryid of type object is incompatible with the type string expected by parameter $entryid of HierarchyModule::removeFromFavorite(). ( Ignorable by Annotation )

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

140
											$this->removeFromFavorite(/** @scrutinizer ignore-type */ $entryid);
Loading history...
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...
Bug introduced by
$entryid of type object is incompatible with the type string expected by parameter $entryid 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, $parententryid, /** @scrutinizer ignore-type */ $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...
Bug introduced by
$entryid of type object is incompatible with the type string expected by parameter $sourcefolderentryid 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, $parententryid, /** @scrutinizer ignore-type */ $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);
0 ignored issues
show
Bug introduced by
$entryid of type object is incompatible with the type string expected by parameter $entryid of HierarchyModule::emptyFolder(). ( Ignorable by Annotation )

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

187
													$this->emptyFolder($store, /** @scrutinizer ignore-type */ $entryid);
Loading history...
188
												break;
189
190
												case "readflags":
191
													$this->setReadFlags($store, $entryid);
0 ignored issues
show
Bug introduced by
$entryid of type object is incompatible with the type string expected by parameter $entryid of HierarchyModule::setReadFlags(). ( Ignorable by Annotation )

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

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

552
				$this->addFolderToResponseData(/** @scrutinizer ignore-type */ $store, $props[PR_ENTRYID], "update");
Loading history...
553
554
				// Add all response data to Bus
555
				$GLOBALS["bus"]->addData($this->getResponseData());
556
557
				// Notify parent folder that a folder has been added
558
				$props[PR_ENTRYID] = $parententryid;
559
				$GLOBALS["bus"]->notify(bin2hex($parententryid), OBJECT_SAVE, $props);
560
			}
561
562
			return $result;
563
		}
564
565
		/**
566
		 * returns properties of a folder, used by the properties dialog.
567
		 *
568
		 * @param mixed $store
569
		 * @param mixed $folder
570
		 */
571
		public function getFolderProps($store, $folder) {
572
			$data = $GLOBALS["operations"]->getProps($folder, $this->properties);
573
574
			// adding container_class if missing
575
			if (!isset($data["props"]["container_class"])) {
576
				$data["props"]["container_class"] = "IPF.Note";
577
			}
578
579
			// replace "IPM_SUBTREE" with the display name of the store, and use the store message size
580
			$store_props = mapi_getprops($store, [PR_IPM_SUBTREE_ENTRYID]);
581
			if ($data["entryid"] == bin2hex($store_props[PR_IPM_SUBTREE_ENTRYID])) {
582
				$store_props = mapi_getprops($store, [PR_DISPLAY_NAME, PR_MESSAGE_SIZE_EXTENDED,
583
					PR_CONTENT_COUNT, PR_QUOTA_WARNING_THRESHOLD, PR_QUOTA_SEND_THRESHOLD, PR_QUOTA_RECEIVE_THRESHOLD, ]);
584
				$data["props"]["display_name"] = $store_props[PR_DISPLAY_NAME];
585
				$data["props"]["message_size"] = round($store_props[PR_MESSAGE_SIZE_EXTENDED]);
586
				$data["props"]["content_count"] = $store_props[PR_CONTENT_COUNT];
587
				$data["props"]["store_size"] = round($store_props[PR_MESSAGE_SIZE_EXTENDED]);
588
589
				if (isset($store_props[PR_QUOTA_WARNING_THRESHOLD])) {
590
					$data["props"]["quota_warning"] = round($store_props[PR_QUOTA_WARNING_THRESHOLD]);
591
				}
592
				if (isset($store_props[PR_QUOTA_SEND_THRESHOLD])) {
593
					$data["props"]["quota_soft"] = round($store_props[PR_QUOTA_SEND_THRESHOLD]);
594
				}
595
				if (isset($store_props[PR_QUOTA_RECEIVE_THRESHOLD])) {
596
					$data["props"]["quota_hard"] = round($store_props[PR_QUOTA_RECEIVE_THRESHOLD]);
597
				}
598
			}
599
600
			// calculating missing message_size
601
			if (!isset($data["props"]["message_size"])) {
602
				$data["props"]["message_size"] = round($GLOBALS["operations"]->calcFolderMessageSize($folder, false));
603
			}
604
605
			// retrieving folder permissions
606
			$data["permissions"] = [
607
				"item" => $this->getFolderPermissions($folder),
608
			];
609
610
			return $data;
611
		}
612
613
		/**
614
		 * Returns the size and total_size of the given folder.
615
		 *
616
		 * @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...
617
		 * @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...
618
		 * @param string     $pathname    The path of the current folder
619
		 * @param array      &$subfolders The array in which all information for the subfolders are stored
620
		 * @param bool       $hidden      True to prevent the subfolders to be stored into the $subfolders argument
621
		 *
622
		 * @return array The response data
623
		 */
624
		public function getFolderSize($store, $folder, $pathname, &$subfolders, $hidden = false) {
625
			$columns = [PR_ENTRYID, PR_PARENT_ENTRYID, PR_STORE_ENTRYID, PR_OBJECT_TYPE, PR_DISPLAY_NAME, PR_ATTR_HIDDEN];
626
			$size = $GLOBALS["operations"]->calcFolderMessageSize($folder, false);
627
			$total_size = $size;
628
629
			$table = mapi_folder_gethierarchytable($folder, MAPI_DEFERRED_ERRORS);
630
631
			mapi_table_setcolumns($table, $columns);
632
			$columns = null;
633
634
			$rows = mapi_table_queryrows($table, $columns, 0, 0x7FFFFFF);
635
			foreach ($rows as $row) {
636
				$subfolder = mapi_msgstore_openentry($store, $row[PR_ENTRYID]);
637
				$subpath = (!empty($pathname) ? ($pathname . '\\') : '') . $row[PR_DISPLAY_NAME];
638
639
				/**
640
				 * Don't add  hidden folders, folders with PR_ATTR_HIDDEN property set
641
				 * should not be shown to the client.
642
				 */
643
				$hide = $hidden === true || (isset($row[PR_ATTR_HIDDEN]) && $row[PR_ATTR_HIDDEN] === true);
644
				$info = $this->getFolderSize($store, $subfolder, $subpath, $subfolders, $hide);
645
646
				if ($hide !== true) {
647
					array_push($subfolders, [
648
						"entryid" => bin2hex($row[PR_ENTRYID]),
649
						"parent_entryid" => bin2hex($row[PR_PARENT_ENTRYID]),
650
						"store_entryid" => bin2hex($row[PR_STORE_ENTRYID]),
651
						"props" => [
652
							"folder_pathname" => $subpath, // This equals PR_FOLDER_PATHNAME, which is not supported by Gromox
653
							"display_name" => $row[PR_DISPLAY_NAME],
654
							"object_type" => $row[PR_OBJECT_TYPE],
655
							"message_size" => $info["size"],
656
							"total_message_size" => $info["total_size"],
657
						],
658
					]);
659
				}
660
661
				$total_size += $info["total_size"];
662
			}
663
664
			return ["size" => $size, "total_size" => $total_size];
665
		}
666
667
		/**
668
		 * Function which saves changed properties to a folder.
669
		 *
670
		 * @param object $store  MAPI object of the store
671
		 * @param object $folder MAPI object of the folder
672
		 * @param array  $props  the properties to save
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
			$grants = mapi_zarafa_getpermissionrules($folder, ACCESS_TYPE_GRANT);
703
			foreach ($grants as $id => $grant) {
704
				// The mapi_zarafa_getpermissionrules returns the entryid in the userid key
705
				$userinfo = $this->getUserInfo($grant["userid"]);
706
707
				$rights = [];
708
				$rights["entryid"] = $userinfo["entryid"];
709
				$rights["props"] = [];
710
				$rights["props"]["type"] = ACCESS_TYPE_GRANT;
711
				$rights["props"]["display_name"] = $userinfo["fullname"];
712
				$rights["props"]["object_type"] = $userinfo["type"];
713
				$rights["props"]["entryid"] = $userinfo["entryid"];
714
				$rights["props"]["rights"] = $grant["rights"];
715
716
				$grants[$id] = $rights;
717
			}
718
719
			return $grants;
720
		}
721
722
		public function setFolderPermissions($folder, $permissions) {
723
			$folderProps = mapi_getprops($folder, [PR_DISPLAY_NAME, PR_STORE_ENTRYID, PR_ENTRYID]);
724
			$store = $GLOBALS["mapisession"]->openMessageStore($folderProps[PR_STORE_ENTRYID]);
725
			$storeProps = mapi_getprops($store, [PR_IPM_SUBTREE_ENTRYID]);
0 ignored issues
show
Unused Code introduced by
The assignment to $storeProps is dead and can be removed.
Loading history...
726
			$currentPermissions = $this->getFolderPermissions($folder);
727
728
			// check if the folder is the default calendar, if so we also need to set the same permissions on the freebusy folder
729
			$root = mapi_msgstore_openentry($store, null);
730
			if ($root) {
731
				$rootProps = mapi_getprops($root, [PR_IPM_APPOINTMENT_ENTRYID]);
732
				if ($folderProps[PR_ENTRYID] == $rootProps[PR_IPM_APPOINTMENT_ENTRYID]) {
733
					$freebusy = freebusy::getLocalFreeBusyFolder($store);
734
				}
735
			}
736
737
			// first, get the current permissions because we need to delete all current acl's
738
			$curAcls = mapi_zarafa_getpermissionrules($folder, ACCESS_TYPE_GRANT);
739
740
			// First check which permissions should be removed from the existing list
741
			if (isset($permissions['remove']) && !empty($permissions['remove'])) {
742
				foreach ($permissions['remove'] as $i => &$delAcl) {
743
					$userid = hex2bin($delAcl['entryid']);
744
					foreach ($curAcls as $aclIndex => &$curAcl) {
745
						if ($curAcl['userid'] === $userid) {
746
							$curAcl['rights'] = ecRightsNone;
747
							$curAcl['state'] = RIGHT_DELETED | RIGHT_AUTOUPDATE_DENIED;
748
						}
749
					}
750
					unset($curAcl);
751
				}
752
				unset($delAcl);
753
			}
754
755
			// Then we check which permissions must be updated in the existing list
756
			if (isset($permissions['modify']) && !empty($permissions['modify'])) {
757
				foreach ($permissions['modify'] as $i => &$modAcl) {
758
					$entryid = $modAcl['entryid'];
759
					// No permission for this user
760
					// This is necessary for recursive folder permissions.
761
					// If a subfolder does not have any permissions for the user yet,
762
					// they need to be added instead of modified.
763
					if (!$this->idInCurrentPermissions($currentPermissions, $entryid)) {
764
						if (!isset($permissions['add'])) {
765
							$permissions['add'] = [];
766
						}
767
						array_push($permissions['add'], $modAcl);
768
					}
769
					else {
770
						$userid = hex2bin($entryid);
771
						foreach ($curAcls as $aclIndex => &$curAcl) {
772
							if ($curAcl['userid'] === $userid) {
773
								$curAcl['rights'] = $modAcl['rights'];
774
								$curAcl['state'] = RIGHT_MODIFY | RIGHT_AUTOUPDATE_DENIED;
775
							}
776
						}
777
					}
778
					unset($curAcl);
779
				}
780
				unset($modAcl);
781
			}
782
783
			// Finally we check which permissions must be added to the existing list
784
			if (isset($permissions['add']) && !empty($permissions['add'])) {
785
				foreach ($permissions['add'] as $i => &$addAcl) {
786
					$curAcls[$addAcl['entryid']] = [
787
						'type' => ACCESS_TYPE_GRANT,
788
						'userid' => hex2bin($addAcl['entryid']),
789
						'rights' => $addAcl['rights'],
790
						'state' => RIGHT_NEW | RIGHT_AUTOUPDATE_DENIED,
791
					];
792
				}
793
				unset($addAcl);
794
			}
795
796
			if (!empty($curAcls)) {
797
				mapi_zarafa_setpermissionrules($folder, $curAcls);
798
799
				// $freebusy is only set when the calendar folder permissions is updated
800
				if (isset($freebusy)) {
801
					// set permissions on free/busy message
802
					foreach ($curAcls as $key => &$acl) {
803
						if ($acl['type'] == ACCESS_TYPE_GRANT && ($acl['rights'] & ecRightsEditOwned)) {
804
							$acl['rights'] |= ecRightsEditAny;
805
						}
806
					}
807
					unset($acl);
808
809
					mapi_zarafa_setpermissionrules($freebusy, $curAcls);
810
				}
811
			}
812
		}
813
814
		public function idInCurrentPermissions($currentPermissions, $entryid) {
815
			foreach ($currentPermissions as $key => $array) {
816
				if ($array['entryid'] === $entryid) {
817
					return true;
818
				}
819
			}
820
821
			return false;
822
		}
823
824
		public function getUserInfo($entryid) {
825
			// default return stuff
826
			$result = ["fullname" => _("Unknown user/group"),
827
				"username" => _("unknown"),
828
				"entryid" => null,
829
				"type" => MAPI_MAILUSER,
830
				"id" => $entryid,
831
			];
832
833
			// open the addressbook
834
			$ab = $GLOBALS["mapisession"]->getAddressbook();
835
836
			$user = mapi_ab_openentry($ab, $entryid);
837
838
			if ($user) {
839
				$props = mapi_getprops($user, [PR_ACCOUNT, PR_DISPLAY_NAME, PR_OBJECT_TYPE]);
840
				$result["username"] = $props[PR_ACCOUNT];
841
				$result["fullname"] = $props[PR_DISPLAY_NAME];
842
				$result["entryid"] = bin2hex($entryid);
843
				$result["type"] = $props[PR_OBJECT_TYPE];
844
			}
845
846
			return $result;
847
		}
848
849
		/**
850
		 * Function is used to get the IPM_COMMON_VIEWS folder from defaults store.
851
		 *
852
		 * @return object MAPI folder object
853
		 */
854
		public function getCommonViewsFolder() {
855
			$defaultStore = $GLOBALS["mapisession"]->getDefaultMessageStore();
856
			$commonViewsFolderEntryid = mapi_getprops($defaultStore, [PR_COMMON_VIEWS_ENTRYID]);
857
			$commonViewsFolder = mapi_msgstore_openentry($defaultStore, $commonViewsFolderEntryid[PR_COMMON_VIEWS_ENTRYID]);
858
859
			return $commonViewsFolder;
860
		}
861
862
		/**
863
		 * Remove favorites link message from associated contains table of IPM_COMMON_VIEWS.
864
		 * It will also remove favorites search folders of given store.
865
		 *
866
		 * @param string $entryid  entryid of the folder
867
		 * @param object $store    MAPI object of the store
868
		 * @param string $prop     property which is used to find record from associated contains table of
869
		 *                         IPM_COMMON_VIEWS folder
870
		 * @param bool   $doNotify true to notify the IPM_COMMO_VIEWS folder on client side
871
		 */
872
		public function removeFromFavorite($entryid, $store = false, $prop = PR_WLINK_ENTRYID, $doNotify = true) {
873
			$commonViewsFolder = $this->getCommonViewsFolder();
874
			$associatedTable = mapi_folder_getcontentstable($commonViewsFolder, MAPI_ASSOCIATED);
875
876
			$restriction = [RES_OR,
877
				[
878
					[RES_PROPERTY,
879
						[
880
							RELOP => RELOP_EQ,
881
							ULPROPTAG => PR_MESSAGE_CLASS,
882
							VALUE => [PR_MESSAGE_CLASS => "IPM.Microsoft.WunderBar.Link"],
883
						],
884
					],
885
					[RES_PROPERTY,
886
						[
887
							RELOP => RELOP_EQ,
888
							ULPROPTAG => PR_MESSAGE_CLASS,
889
							VALUE => [PR_MESSAGE_CLASS => "IPM.Microsoft.WunderBar.SFInfo"],
890
						],
891
					],
892
				],
893
			];
894
			$finderHierarchyTables = [];
895
			if (!empty($store)) {
896
				$props = mapi_getprops($store, [PR_FINDER_ENTRYID]);
897
898
				try {
899
					$finderFolder = mapi_msgstore_openentry($store, $props[PR_FINDER_ENTRYID]);
900
					$hierarchyTable = mapi_folder_gethierarchytable($finderFolder, MAPI_DEFERRED_ERRORS);
901
					$finderHierarchyTables[$props[PR_FINDER_ENTRYID]] = $hierarchyTable;
902
				}
903
				catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
904
				}
905
			}
906
907
			$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);
908
909
			if (!empty($messages)) {
910
				foreach ($messages as $message) {
911
					if ($message[PR_MESSAGE_CLASS] === "IPM.Microsoft.WunderBar.SFInfo" && !empty($finderHierarchyTables)) {
912
						$props = $GLOBALS["operations"]->getFavoritesLinkedSearchFolderProps($message[PR_WB_SF_ID], $finderHierarchyTables);
913
						if (!empty($props)) {
914
							$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

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

1185
				$this->addFolderToResponseData(/** @scrutinizer ignore-type */ $store, $entryid, "folders");
Loading history...
1186
1187
				// Add all response data to Bus
1188
				$GLOBALS["bus"]->addData($this->getResponseData());
1189
			}
1190
		}
1191
1192
		/**
1193
		 * Copies of moves a folder in the hierarchylist.
1194
		 *
1195
		 * @param object $store               message Store Object
1196
		 * @param string $parententryid       entryid of the parent folder
1197
		 * @param string $sourcefolderentryid entryid of the folder to be copied of moved
1198
		 * @param string $destfolderentryid   entryid of the destination folder
1199
		 * @param string $action              move or copy the folder
1200
		 * @param mixed  $deststore
1201
		 * @param mixed  $moveFolder
1202
		 *
1203
		 * @return bool true on success or false on failure
1204
		 */
1205
		public function copyFolder($store, $parententryid, $sourcefolderentryid, $destfolderentryid, $deststore, $moveFolder) {
1206
			$props = [];
1207
			$result = $GLOBALS["operations"]->copyFolder($store, $parententryid, $sourcefolderentryid, $destfolderentryid, $deststore, $moveFolder, $props);
1208
1209
			if ($result) {
1210
				if ($moveFolder) {
1211
					try {
1212
						// If destination folder is wastebasket then remove source folder from favorites list if
1213
						// it is present in it.
1214
						$defaultStore = $GLOBALS["mapisession"]->getDefaultMessageStore();
1215
						$wastebasketFolderEntryid = mapi_getprops($defaultStore, [PR_IPM_WASTEBASKET_ENTRYID]);
1216
						if ($GLOBALS["entryid"]->compareEntryIds($wastebasketFolderEntryid[PR_IPM_WASTEBASKET_ENTRYID], $destfolderentryid)) {
1217
							$this->removeFromFavorite($sourcefolderentryid);
1218
						}
1219
1220
						/*
1221
						 * Normally it works well within same store,
1222
						 * but entryid gets changed when different stores so we can't use old entryid anymore.
1223
						 * When moving to different store send delete notification.
1224
						 */
1225
						$this->addFolderToResponseData($deststore, $sourcefolderentryid, "folders");
1226
1227
						// Add all response data to Bus
1228
						$GLOBALS["bus"]->addData($this->getResponseData());
1229
					}
1230
					catch (MAPIException $e) {
1231
						if ($e->getCode() == MAPI_E_INVALID_ENTRYID) {
1232
							// Entryid of the folder might be change after move, so send delete notification for folder.
1233
							$GLOBALS["bus"]->notify(bin2hex($props[PR_ENTRYID]), OBJECT_DELETE, $props);
1234
						}
1235
					}
1236
1237
					// if move folder then refresh parent of source folder
1238
					$sourcefolder = mapi_msgstore_openentry($store, $parententryid);
1239
					$folderProps = mapi_getprops($sourcefolder, [PR_ENTRYID, PR_STORE_ENTRYID]);
1240
					$GLOBALS["bus"]->notify(bin2hex($folderProps[PR_ENTRYID]), OBJECT_SAVE, $folderProps);
1241
				}
1242
				else {
1243
					$this->sendFeedback(true);
1244
				}
1245
1246
				// Update subfolders of copy/move folder
1247
				$folder = mapi_msgstore_openentry($deststore, $destfolderentryid);
1248
				$hierarchyTable = mapi_folder_gethierarchytable($folder, CONVENIENT_DEPTH | MAPI_DEFERRED_ERRORS);
1249
				mapi_table_sort($hierarchyTable, [PR_DISPLAY_NAME => TABLE_SORT_ASCEND], TBL_BATCH);
1250
1251
				/**
1252
				 * remove hidden folders, folders with PR_ATTR_HIDDEN property set
1253
				 * should not be shown to the client.
1254
				 */
1255
				$restriction = [RES_OR,
1256
					[
1257
						[RES_PROPERTY,
1258
							[
1259
								RELOP => RELOP_EQ,
1260
								ULPROPTAG => PR_ATTR_HIDDEN,
1261
								VALUE => [PR_ATTR_HIDDEN => false],
1262
							],
1263
						],
1264
						[RES_NOT,
1265
							[
1266
								[RES_EXIST,
1267
									[
1268
										ULPROPTAG => PR_ATTR_HIDDEN,
1269
									],
1270
								],
1271
							],
1272
						],
1273
					],
1274
				];
1275
1276
				$subfolders = mapi_table_queryallrows($hierarchyTable, [PR_ENTRYID], $restriction);
1277
1278
				if (is_array($subfolders)) {
1279
					foreach ($subfolders as $subfolder) {
1280
						$folderObject = mapi_msgstore_openentry($deststore, $subfolder[PR_ENTRYID]);
1281
						$folderProps = mapi_getprops($folderObject, [PR_ENTRYID, PR_STORE_ENTRYID]);
1282
						$GLOBALS["bus"]->notify(bin2hex($subfolder[PR_ENTRYID]), OBJECT_SAVE, $folderProps);
1283
					}
1284
				}
1285
1286
				// Now update destination folder
1287
				$folder = mapi_msgstore_openentry($deststore, $destfolderentryid);
1288
				$folderProps = mapi_getprops($folder, [PR_ENTRYID, PR_STORE_ENTRYID]);
1289
				$GLOBALS["bus"]->notify(bin2hex($folderProps[PR_ENTRYID]), OBJECT_SAVE, $folderProps);
1290
			}
1291
			else {
1292
				if ($moveFolder) {
1293
					$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

1293
					$this->sendFeedback(false, /** @scrutinizer ignore-type */ _('Could not move folder'));
Loading history...
1294
				}
1295
				else {
1296
					$this->sendFeedback(false, _('Could not copy folder'));
1297
				}
1298
			}
1299
		}
1300
1301
		/**
1302
		 * Set all messages read.
1303
		 *
1304
		 * @param object $store   message Store Object
1305
		 * @param string $entryid entryid of the folder
1306
		 *
1307
		 * @return bool true on success or false on failure
1308
		 */
1309
		public function setReadFlags($store, $entryid) {
1310
			$props = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $props is dead and can be removed.
Loading history...
1311
			$folder = mapi_msgstore_openentry($store, $entryid);
1312
1313
			if (!$folder) {
1314
				return;
1315
			}
1316
1317
			if (mapi_folder_setreadflags($folder, [], SUPPRESS_RECEIPT)) {
1318
				$props = mapi_getprops($folder, [PR_ENTRYID, PR_STORE_ENTRYID]);
1319
1320
				if (!isset($props[PR_ENTRYID])) {
1321
					return;
1322
				}
1323
1324
				$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

1324
				$this->addFolderToResponseData(/** @scrutinizer ignore-type */ $store, $props[PR_ENTRYID], "folders");
Loading history...
1325
1326
				// Add all response data to Bus
1327
				$GLOBALS["bus"]->addData($this->getResponseData());
1328
			}
1329
		}
1330
	}
1331