Test Failed
Push — master ( 647c72...cd42b5 )
by
unknown
10:25
created

HierarchyModule   F

Complexity

Total Complexity 214

Size/Duplication

Total Lines 1282
Duplicated Lines 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
eloc 647
dl 0
loc 1282
rs 1.953
c 5
b 0
f 0
wmc 214

26 Methods

Rating   Name   Duplication   Size   Complexity  
A modifyFolder() 0 7 3
C removeFromFavorite() 0 56 14
B getFolderSize() 0 42 6
B getFolderProps() 0 38 7
A idInCurrentPermissions() 0 7 3
A __construct() 0 6 1
B emptyFolder() 0 39 8
A deleteSearchFolder() 0 23 2
A getCommonViewsFolder() 0 6 1
A getEntryID() 0 10 2
A getFolderPermissions() 0 28 3
A createNotifiers() 0 6 1
B deleteFolder() 0 44 6
F setFolderPermissions() 0 94 24
D handleException() 0 118 32
A save() 0 23 6
A getUserInfo() 0 23 2
A addFolder() 0 18 3
A addFolderToResponseData() 0 7 1
A hierarchyList() 0 6 1
A setReadFlags() 0 20 4
A addToFavorite() 0 14 1
A removeSearchLinkMessage() 0 30 4
F execute() 0 285 69
A createLinkedSearchFolder() 0 25 1
B copyFolder() 0 89 9

How to fix   Complexity   

Complex Class

Complex classes like HierarchyModule often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use HierarchyModule, and based on these observations, apply Extract Interface, too.

1
<?php
2
	/**
3
	 * Hierarchy Module
4
	 *
5
	 * @todo
6
	 * - Check the code at deleteFolder and at copyFolder. Looks the same.
7
	 */
8
	class HierarchyModule extends Module
9
	{
10
		/**
11
		 * Constructor
12
		 * @param int $id unique id.
13
		 * @param string $folderentryid Entryid of the folder. Data will be selected from this folder.
14
		 * @param array $data list of all actions.
15
		 */
16
		function __construct($id, $data)
17
		{
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
		function createNotifiers()
29
		{
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
		 * @return array list of entryids
40
		 */
41
		function getEntryID()
42
		{
43
			$entryids = array();
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
		 * @return boolean true on success or false on fialure.
56
		 */
57
		function execute()
58
		{
59
			foreach($this->data as $actionType => $action)
60
			{
61
				if(isset($actionType)) {
62
					try {
63
						$store = $this->getActionStore($action);
64
						$parententryid = $this->getActionParentEntryID($action);
65
						$entryid = $this->getActionEntryID($action);
66
67
						switch($actionType)
68
						{
69
							case "keepalive":
70
								/**
71
								 * as we haven't done any processing here but still we need to send
72
								 * success message to client so client can know that there isn't any problem
73
								 * on server side (this will also make bus class happy as it will cry when
74
								 * there isn't any data to send to client).
75
								 */
76
								$this->sendFeedback(true);
77
								break;
78
							case "destroysession":
79
								// This actiontype should never get this far, but should already have been
80
								// intercepted by the Session class.
81
								// Nevertheless implement processing here for unforeseen cases.
82
								$this->sendFeedback(true);
83
								break;
84
							case "list":
85
								$this->hierarchyList();
86
								break;
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
							case "foldersize":
96
								$folders = array();
97
98
								$folder = mapi_msgstore_openentry($store, $entryid);
99
								$data = $this->getFolderProps($store, $folder);
100
101
								$info = $this->getFolderSize($store, $folder, '', $folders);
102
103
								// It could be that the $props already contains the data,
104
								// this happens when the folder is the IPM_SUBTREE and the
105
								// folder size is read directly from the store. Adjust
106
								// total_size accordingly.
107
								if (isset($data["props"]["store_size"])) {
108
									if (!isset($data["props"]["message_size"])) {
109
										$data["props"]["message_size"] = $data["props"]["store_size"];
110
									}
111
									$data["props"]["total_message_size"] = $data["props"]["store_size"] + $info["total_size"];
112
								} else {
113
									$data["props"]["message_size"] = $info["size"];
114
									$data["props"]["total_message_size"] = $info["total_size"];
115
								}
116
								$data["folders"] = array(
117
									"item" => $folders
118
								);
119
120
								// return response
121
								$this->addActionData("item", $data);
122
								$GLOBALS["bus"]->addData($this->getResponseData());
123
								break;
124
125
							case "delete":
126
								if ($store && $parententryid && $entryid) {
127
									if (isset($action["message_action"]) && isset($action["message_action"]["action_type"])
128
										&& $action["message_action"]["action_type"] === "removefavorites" ) {
129
130
										if (isset($action["message_action"]["isSearchFolder"])
131
											&& $action["message_action"]["isSearchFolder"]) {
132
											$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

132
											$result = $this->deleteSearchFolder($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::deleteSearchFolder(). ( Ignorable by Annotation )

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

132
											$result = $this->deleteSearchFolder($store, $parententryid, /** @scrutinizer ignore-type */ $entryid, $action);
Loading history...
133
											dump($result, '$result');
134
											if ($result) {
135
												$this->sendFeedback(true);
136
											}
137
										} else {
138
											$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

138
											$this->removeFromFavorite(/** @scrutinizer ignore-type */ $entryid);
Loading history...
139
										}
140
									} else {
141
										$this->deleteFolder($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::deleteFolder(). ( Ignorable by Annotation )

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

141
										$this->deleteFolder($store, $parententryid, /** @scrutinizer ignore-type */ $entryid, $action);
Loading history...
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

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

175
														$this->copyFolder($store, $parententryid, /** @scrutinizer ignore-type */ $entryid, $destentryid, $deststore, ($action["message_action"]["action_type"] == "move"));
Loading history...
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

175
														$this->copyFolder($store, /** @scrutinizer ignore-type */ $parententryid, $entryid, $destentryid, $deststore, ($action["message_action"]["action_type"] == "move"));
Loading history...
176
													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...
177
														$GLOBALS["bus"]->notify(ADDRESSBOOK_ENTRYID, OBJECT_SAVE);
178
													}
179
												break;
180
181
												case "emptyfolder":
182
													$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

182
													$this->emptyFolder($store, /** @scrutinizer ignore-type */ $entryid);
Loading history...
183
												break;
184
185
												case "readflags":
186
													$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

186
													$this->setReadFlags($store, /** @scrutinizer ignore-type */ $entryid);
Loading history...
187
												break;
188
189
												case "addtofavorites":
190
													if (isset($action["message_action"]["isSearchFolder"]) && $action["message_action"]["isSearchFolder"]){
191
														$searchStoreEntryId = $action["message_action"]["search_store_entryid"];
192
														// Set display name to search folder.
193
														$searchStore = $GLOBALS["mapisession"]->openMessageStore(hex2bin($searchStoreEntryId));
194
														$searchFolder = mapi_msgstore_openentry($searchStore, $entryid);
195
														mapi_setprops($searchFolder, array(
196
															PR_DISPLAY_NAME => $action["props"]["display_name"]
197
														));
198
														mapi_savechanges($searchFolder);
199
														$this->createLinkedSearchFolder($searchFolder);
200
													} else {
201
														$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

201
														$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

201
														$this->addToFavorite(/** @scrutinizer ignore-type */ $store, $entryid);
Loading history...
202
													}
203
												break;
204
											}
205
										} else {
206
											// save folder
207
											$folder = mapi_msgstore_openentry($store, hex2bin($action["entryid"]));
208
											$this->save($store, $folder, $action);
209
											if ($data["props"]["container_class"] === "IPF.Contact") {
210
												$GLOBALS["bus"]->notify(ADDRESSBOOK_ENTRYID, OBJECT_SAVE);
211
											}
212
											$this->sendFeedback(true, array());
213
										}
214
									} else {
215
										// no entryid, create new folder
216
										if($store && $parententryid && isset($action["props"]["display_name"]) && isset($action["props"]["container_class"]))
217
											if (isset($action["message_action"]) && isset($action["message_action"]["action_type"])) {
218
												// We need to create new search folder under the favorites folder
219
												// based on give search folder info.
220
												if($action["message_action"]["action_type"] === "addtofavorites") {
221
													$storeEntryId = $action["message_action"]["search_store_entryid"];
222
													$searchFolderEntryId = $action["message_action"]["search_folder_entryid"];
223
224
													// Get the search folder and search criteria using $storeEntryId and $searchFolderEntryId.
225
													$Store = $GLOBALS["mapisession"]->openMessageStore(hex2bin($storeEntryId));
226
													$searchFolder = mapi_msgstore_openentry($Store, hex2bin($searchFolderEntryId));
227
													$searchCriteria = mapi_folder_getsearchcriteria($searchFolder);
228
229
													// Get FINDERS_ROOT folder from store.
230
													$finderRootFolder = mapi_getprops($Store, array(PR_FINDER_ENTRYID));
231
													$searchFolderRoot = mapi_msgstore_openentry($Store, $finderRootFolder[PR_FINDER_ENTRYID]);
232
233
													// Create new search folder in FINDERS_ROOT folder and set the search
234
													// criteria in newly created search folder.
235
													$newSearchFolder = mapi_folder_createfolder($searchFolderRoot, $action["props"]["display_name"], null, 0, FOLDER_SEARCH);
236
													$subfolder_flag = 0;
237
													if (isset($action["subfolders"]) && $action["subfolders"] == "true") {
238
														$subfolder_flag = RECURSIVE_SEARCH;
239
													}
240
													mapi_folder_setsearchcriteria($newSearchFolder, $searchCriteria['restriction'], $searchCriteria['folderlist'], $subfolder_flag);
241
242
													// Sleep for 1 seconds initially, since it usually takes ~  1 seconds to fill the search folder.
243
													sleep(1);
244
													$this->createLinkedSearchFolder($newSearchFolder);
245
												}
246
											} else {
247
												$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

247
												$this->addFolder($store, /** @scrutinizer ignore-type */ $parententryid, $action["props"]["display_name"], $action["props"]["container_class"]);
Loading history...
248
											}
249
										if($action["props"]["container_class"] === "IPF.Contact"){
250
											$GLOBALS["bus"]->notify(ADDRESSBOOK_ENTRYID,OBJECT_SAVE);
251
										}
252
									}
253
								}
254
								break;
255
256
							case "closesharedfolder":
257
								if (isset($action["folder_type"]) && $action["folder_type"] != "all") {
258
									// We're closing a Shared folder, check if we still have other
259
									// folders for the same user opened, if not we can safely close
260
									// the usrstore.
261
									$stores = $GLOBALS["settings"]->get("zarafa/v1/contexts/hierarchy/shared_stores/" . strtolower(bin2hex($action["user_name"])));
262
									if (!isset($stores) || empty($stores) || (count($stores) == 1 && isset($stores[$action["folder_type"]]))) {
263
										$entryid = $GLOBALS["mapisession"]->removeUserStore($action["user_name"]);
264
									} else {
265
										$entryid = $GLOBALS["mapisession"]->getStoreEntryIdOfUser($action["user_name"]);
266
										$this->removeFromFavorite(hex2bin($action["entryid"]), $store, PR_WLINK_ENTRYID, false);
267
									}
268
								} else {
269
									// We're closing a Shared Store, simply remove it from the session.
270
									$entryid = $GLOBALS["mapisession"]->removeUserStore($action["user_name"]);
271
272
									if (isset($action["remove_favorites"]) && $action["remove_favorites"]) {
273
										$this->removeFromFavorite(hex2bin($action["store_entryid"]), $store, PR_WLINK_STORE_ENTRYID, false);
274
									}
275
								}
276
277
								$data = array();
278
								$data["store_entryid"] = bin2hex($entryid);
279
								if(isset($action["folder_type"])) {
280
									$data["folder_type"] =$action["folder_type"];
281
								}
282
283
								$this->addActionData("delete", $data);
284
								$GLOBALS["bus"]->addData($this->getResponseData());
285
								$GLOBALS["bus"]->notify(ADDRESSBOOK_ENTRYID,OBJECT_SAVE);
286
								break;
287
288
							case "opensharedfolder":
289
								$username = strtolower($action["user_name"]);
290
								$store = $GLOBALS["mapisession"]->addUserStore($username);
291
								if (!$store) {
292
									break;
293
								}
294
295
								$options = array( $username => array( $action["folder_type"] => $action ));
296
								$data = $GLOBALS["operations"]->getHierarchyList($this->list_properties, HIERARCHY_GET_ONE, $store, $options, $username);
297
298
								if (empty($data["item"][0]["folders"]["item"])) {
299
									throw new MAPIException(null, MAPI_E_NO_ACCESS);
300
								}
301
302
								$folders = count($data["item"][0]["folders"]["item"]);
303
								if ($folders === 0) {
304
									throw new MAPIException(null, MAPI_E_NO_ACCESS);
305
								}
306
307
								$noPermissionFolders = array_filter($data['item'][0]['folders']['item'], function($item) {
308
									return $item['props']['access'] === 0;
309
								});
310
								if (count($noPermissionFolders) >= $folders) {
311
									// Throw an exception that we couldn't open the shared store,
312
									// lets have processException() fill in our error message.
313
									throw new MAPIException(null, MAPI_E_NO_ACCESS);
314
								}
315
316
								$this->addActionData("list", $data);
317
								$GLOBALS["bus"]->addData($this->getResponseData());
318
								$GLOBALS["bus"]->notify(ADDRESSBOOK_ENTRYID,OBJECT_SAVE);
319
								break;
320
							case "sharedstoreupdate":
321
								$supported_types = ['inbox' => 1, 'all' => 1];
322
								$users = $GLOBALS["settings"]->get("zarafa/v1/contexts/hierarchy/shared_stores", []);
323
324
								foreach($users as $username => $data) {
325
									$key = array_keys($data)[0];
326
									$folder_type = $data[$key]['folder_type'];
327
328
									if (!isset($supported_types[$folder_type])) {
329
										continue;
330
									}
331
332
									$GLOBALS["bus"]->notify(REQUEST_ENTRYID, HIERARCHY_UPDATE, array(strtolower(hex2bin($username)), $folder_type));
333
								}
334
335
								$this->sendFeedback(true);
336
								break;
337
							default:
338
								$this->handleUnknownActionType($actionType);
339
						}
340
					} catch (MAPIException $e) {
341
						$this->processException($e, $actionType, $store, $parententryid, $entryid, $action);
0 ignored issues
show
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...
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...
342
					}
343
				}
344
			}
345
		}
346
347
		/**
348
		 * Function does customization of exception based on module data.
349
		 * like, here it will generate display message based on actionType
350
		 * for particular exception, and send feedback to the client.
351
		 *
352
		 * @param object $e Exception object
353
		 * @param string $actionType the action type, sent by the client
354
		 * @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...
355
		 * @param string $parententryid parent entryid of the message.
356
		 * @param string $entryid entryid of the folder.
357
		 * @param array $action the action data, sent by the client
358
		 */
359
		function handleException(&$e, $actionType = null, $store = null, $parententryid = null, $entryid = null, $action = null)
360
		{
361
			if(is_null($e->displayMessage)) {
362
				switch($actionType) {
363
					case "list":
364
						$e->setDisplayMessage(_("Could not load the hierarchy."));
365
						break;
366
367
					case "open":
368
						$e->setDisplayMessage(_("Could not load folder properties."));
369
						break;
370
371
					case "delete":
372
						if (isset($action["message_action"])
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment if this fall-through is intended.
Loading history...
373
							&& isset($action["message_action"]["action_type"])
374
							&& $action["message_action"]["action_type"] === "removefavorites") {
375
								$e->setDisplayMessage(_("Could not remove folder from favorites."));
376
						} else {
377
							if($e->getCode() == MAPI_E_NO_ACCESS)
378
								$e->setDisplayMessage(_("You have insufficient privileges to delete folder."));
379
							else
380
								$e->setDisplayMessage(_("Could not delete folder."));
381
							break;
382
						}
383
					case "save":
384
						if($entryid) {
385
							if (isset($action["message_action"]) && isset($action["message_action"]["action_type"])) {
386
								switch($action["message_action"]["action_type"])
387
								{
388
									case "copy":
389
										if($e->getCode() == MAPI_E_NO_ACCESS)
390
											$e->setDisplayMessage(_("You have insufficient privileges to copy folder."));
391
										else
392
											$e->setDisplayMessage(_("Could not copy folder."));
393
										break;
394
395
									case "move":
396
										if($e->getCode() == MAPI_E_NO_ACCESS)
397
											$e->setDisplayMessage(_("You have insufficient privileges to move this folder."));
398
										else
399
											$e->setDisplayMessage(_("Could not move folder."));
400
										break;
401
402
									case "emptyfolder":
403
										if($e->getCode() == MAPI_E_NO_ACCESS)
404
											$e->setDisplayMessage(_("You have insufficient privileges to delete items."));
405
										else
406
											$e->setDisplayMessage(_("Could not empty folder."));
407
										break;
408
409
									case "readflags":
410
										$e->setDisplayMessage(_("Could not perform action correctly."));
411
										break;
412
413
									case "addtofavorites":
414
										if ($e->getCode() == MAPI_E_COLLISION) {
415
											$e->setDisplayMessage(_("A favorite folder with this name already exists, please use another name."));
416
										} else {
417
											$e->setDisplayMessage(_("Could not add folder to favorites."));
418
										}
419
										break;
420
								}
421
							} else {
422
								// Exception generated while setting folder permissions.
423
								if (isset($action["permissions"])){
424
									if($e->getCode() == MAPI_E_NO_ACCESS)
425
											$e->setDisplayMessage(_("You have insufficient privileges to set permissions for this folder."));
426
										else
427
											$e->setDisplayMessage(_("Could not set folder permissions."));
428
								} else {
429
									// Exception generated while renaming folder.
430
									switch($e->getCode()){
431
										case MAPI_E_NO_ACCESS:
432
											$e->setDisplayMessage(_("You have insufficient privileges to rename this folder."));
433
										break;
434
										case MAPI_E_COLLISION:
435
											$e->setDisplayMessage(_("A folder with this name already exists. Use another name."));
436
										break;
437
										default:
438
											$e->setDisplayMessage(_("Could not rename folder."));
439
									}
440
441
								}
442
							}
443
						} else {
444
							// Exception generated while creating new folder.
445
							switch($e->getCode()){
446
								case MAPI_E_NO_ACCESS:
447
									$e->setDisplayMessage(_("You have insufficient privileges to create this folder."));
448
								break;
449
								case MAPI_E_COLLISION:
450
									$e->setDisplayMessage(_("A folder with this name already exists. Use another name."));
451
								break;
452
								default:
453
									$e->setDisplayMessage(_("Could not create folder."));
454
							}
455
						}
456
						break;
457
458
					case "closesharedfolder":
459
						$e->setDisplayMessage(_("Could not close shared folder."));
460
						break;
461
462
					case "opensharedfolder":
463
						if($e->getCode() == MAPI_E_NOT_FOUND) {
464
							$e->setDisplayMessage(_("User could not be resolved."));
465
						} else {
466
							$folderType = $action["folder_type"];
467
							if ($folderType == "all") {
468
								$folderType = 'entire inbox';
469
							}
470
							$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));
471
						}
472
						break;
473
				}
474
			}
475
476
			parent::handleException($e, $actionType, $store, $parententryid, $entryid, $action);
477
		}
478
479
		/**
480
		 * Generates the hierarchy list. All folders and subfolders are added to response data.
481
		 */
482
		function hierarchyList()
483
		{
484
			$data = $GLOBALS["operations"]->getHierarchyList($this->list_properties);
485
486
			$this->addActionData("list", $data);
487
			$GLOBALS["bus"]->addData($this->getResponseData());
488
		}
489
490
		/**
491
		 * Add folder's properties to response data. This function doesn't add persmission details yet.
492
		 *@param Resource $store mapi store of the folder
493
		 *@param String $entryid entryid of the folder
494
		 *@param String $actionType type of action
495
		 */
496
		function addFolderToResponseData($store, $entryid, $actionType)
497
		{
498
			$folder = mapi_msgstore_openentry($store, $entryid);
499
			$folderProps = mapi_getprops($folder, $this->list_properties);
500
501
			$data = $GLOBALS["operations"]->setFolder($folderProps);
502
			$this->addActionData($actionType, $data);
503
		}
504
505
		/**
506
		 * Adds a folder to the hierarchylist.
507
		 * @param object $store Message Store Object.
508
		 * @param string $parententryid entryid of the parent folder.
509
		 * @param string $name name of the new folder.
510
		 * @param string $type type of the folder (calendar, mail, ...).
511
		 * @return boolean true on success or false on failure.
512
		 */
513
		function addFolder($store, $parententryid, $name, $type)
514
		{
515
			$props = array();
516
			$result = $GLOBALS["operations"]->createFolder($store, $parententryid, $name, $type, $props);
517
518
			if ($result && isset($props[PR_ENTRYID])) {
519
				// Notify about this newly created folder
520
				$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

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

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

1155
				$this->addFolderToResponseData(/** @scrutinizer ignore-type */ $store, $entryid, "folders");
Loading history...
1156
1157
				// Add all response data to Bus
1158
				$GLOBALS["bus"]->addData($this->getResponseData());
1159
			}
1160
		}
1161
1162
		/**
1163
		 * Copies of moves a folder in the hierarchylist.
1164
		 * @param object $store Message Store Object.
1165
		 * @param string $parententryid entryid of the parent folder.
1166
		 * @param string $sourcefolderentryid entryid of the folder to be copied of moved.
1167
		 * @param string $destfolderentryid entryid of the destination folder.
1168
		 * @param string $action move or copy the folder.
1169
		 * @return boolean true on success or false on failure.
1170
		 */
1171
		function copyFolder($store, $parententryid, $sourcefolderentryid, $destfolderentryid, $deststore, $moveFolder)
1172
		{
1173
			$props = array();
1174
			$result = $GLOBALS["operations"]->copyFolder($store, $parententryid, $sourcefolderentryid, $destfolderentryid, $deststore, $moveFolder, $props);
1175
1176
			if($result) {
1177
				if($moveFolder) {
1178
					try {
1179
						// If destination folder is wastebasket then remove source folder from favorites list if
1180
						// it is present in it.
1181
						$defaultStore = $GLOBALS["mapisession"]->getDefaultMessageStore();
1182
						$wastebasketFolderEntryid = mapi_getprops($defaultStore, array(PR_IPM_WASTEBASKET_ENTRYID));
1183
						if($GLOBALS["entryid"]->compareEntryIds($wastebasketFolderEntryid[PR_IPM_WASTEBASKET_ENTRYID], $destfolderentryid)) {
1184
							$this->removeFromFavorite($sourcefolderentryid);
1185
						}
1186
1187
						/*
1188
						 * Normally it works well within same store,
1189
						 * but entryid gets changed when different stores so we can't use old entryid anymore.
1190
						 * When moving to different store send delete notification.
1191
						 */
1192
						$this->addFolderToResponseData($deststore, $sourcefolderentryid, "folders");
1193
1194
						// Add all response data to Bus
1195
						$GLOBALS["bus"]->addData($this->getResponseData());
1196
					} catch (MAPIException $e) {
1197
						if($e->getCode() == MAPI_E_INVALID_ENTRYID) {
1198
							// Entryid of the folder might be change after move, so send delete notification for folder.
1199
							$GLOBALS["bus"]->notify(bin2hex($props[PR_ENTRYID]), OBJECT_DELETE, $props);
1200
						}
1201
					}
1202
1203
					// if move folder then refresh parent of source folder
1204
					$sourcefolder = mapi_msgstore_openentry($store, $parententryid);
1205
					$folderProps = mapi_getprops($sourcefolder, array(PR_ENTRYID, PR_STORE_ENTRYID));
1206
					$GLOBALS["bus"]->notify(bin2hex($folderProps[PR_ENTRYID]), OBJECT_SAVE, $folderProps);
1207
				} else {
1208
					$this->sendFeedback(true);
1209
				}
1210
1211
				// Update subfolders of copy/move folder
1212
				$folder = mapi_msgstore_openentry($deststore, $destfolderentryid);
1213
				$hierarchyTable = mapi_folder_gethierarchytable($folder, CONVENIENT_DEPTH | MAPI_DEFERRED_ERRORS);
1214
				mapi_table_sort($hierarchyTable, array(PR_DISPLAY_NAME => TABLE_SORT_ASCEND), TBL_BATCH);
1215
1216
				/**
1217
				 * remove hidden folders, folders with PR_ATTR_HIDDEN property set
1218
				 * should not be shown to the client
1219
				 */
1220
				$restriction =	Array(RES_OR,
1221
									Array(
1222
										Array(RES_PROPERTY,
1223
											Array(
1224
												RELOP => RELOP_EQ,
1225
												ULPROPTAG => PR_ATTR_HIDDEN,
1226
												VALUE => Array( PR_ATTR_HIDDEN => false )
1227
											)
1228
										),
1229
										Array(RES_NOT,
1230
											Array(
1231
												Array(RES_EXIST,
1232
													Array(
1233
														ULPROPTAG => PR_ATTR_HIDDEN
1234
													)
1235
												)
1236
											)
1237
										)
1238
									)
1239
								);
1240
1241
				$subfolders = mapi_table_queryallrows($hierarchyTable, array(PR_ENTRYID), $restriction);
1242
1243
				if (is_array($subfolders)) {
1244
					foreach($subfolders as $subfolder) {
1245
						$folderObject = mapi_msgstore_openentry($deststore, $subfolder[PR_ENTRYID]);
1246
						$folderProps = mapi_getprops($folderObject, array(PR_ENTRYID, PR_STORE_ENTRYID));
1247
						$GLOBALS["bus"]->notify(bin2hex($subfolder[PR_ENTRYID]), OBJECT_SAVE, $folderProps);
1248
					}
1249
				}
1250
1251
				// Now update destination folder
1252
				$folder = mapi_msgstore_openentry($deststore, $destfolderentryid);
1253
				$folderProps = mapi_getprops($folder, array(PR_ENTRYID, PR_STORE_ENTRYID));
1254
				$GLOBALS["bus"]->notify(bin2hex($folderProps[PR_ENTRYID]), OBJECT_SAVE, $folderProps);
1255
			} else {
1256
				if ($moveFolder) {
1257
					$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

1257
					$this->sendFeedback(false, /** @scrutinizer ignore-type */ _('Could not move folder'));
Loading history...
1258
				} else {
1259
					$this->sendFeedback(false, _('Could not copy folder'));
1260
				}
1261
			}
1262
		}
1263
1264
		/**
1265
		 * Set all messages read.
1266
		 * @param object $store Message Store Object.
1267
		 * @param string $entryid entryid of the folder.
1268
		 * @return boolean true on success or false on failure.
1269
		 */
1270
		function setReadFlags($store, $entryid)
1271
		{
1272
			$props = array();
0 ignored issues
show
Unused Code introduced by
The assignment to $props is dead and can be removed.
Loading history...
1273
			$folder = mapi_msgstore_openentry($store, $entryid);
1274
1275
			if (!$folder) {
1276
				return;
1277
			}
1278
1279
			if (mapi_folder_setreadflags($folder, array(), SUPPRESS_RECEIPT)) {
1280
				$props = mapi_getprops($folder, array(PR_ENTRYID, PR_STORE_ENTRYID));
1281
1282
				if (!isset($props[PR_ENTRYID])) {
1283
					return;
1284
				}
1285
1286
				$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

1286
				$this->addFolderToResponseData(/** @scrutinizer ignore-type */ $store, $props[PR_ENTRYID], "folders");
Loading history...
1287
1288
				// Add all response data to Bus
1289
				$GLOBALS["bus"]->addData($this->getResponseData());
1290
			}
1291
		}
1292
	}
1293
?>
0 ignored issues
show
Best Practice introduced by
It is not recommended to use PHP's closing tag ?> in files other than templates.

Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore.

A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever.

Loading history...
1294