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

ListModule::getDelegateFolderInfo()   A

Complexity

Conditions 3
Paths 5

Size

Total Lines 22
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 12
nc 5
nop 1
dl 0
loc 22
rs 9.8666
c 0
b 0
f 0
1
<?php
2
	/**
3
	 * ListModule
4
	 * Superclass of every module, which retrieves a MAPI message list. It
5
	 * extends the Module class.
6
	 */
7
	class ListModule extends Module
8
	{
9
		/**
10
		 * @var array list of columns which are selected in the previous request.
11
		 */
12
		var $properties;
13
14
		/**
15
		 * @var array sort.
16
		 */
17
		var $sort;
18
19
		/**
20
		 * @var int startrow in the table.
21
		 */
22
		var $start;
23
24
		/**
25
		 * @var array contains (when needed) a restriction used when searching and filtering the records
26
		 */
27
		var $restriction;
28
29
		/**
30
		 * @var bool contains check whether a search result is listed or just the contents of a normal folder
31
		 */
32
		var $searchFolderList;
33
34
		/**
35
		 * @var array stores entryids and last modification time of
36
		 * messages that are already sent to the server
37
		 */
38
		var $searchResults;
39
40
		/**
41
		 * @var MAPIMessage resource of the freebusy message which holds
0 ignored issues
show
Bug introduced by
The type MAPIMessage 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...
42
		 * information regarding delegation details, this variable will
43
		 * only be populated when user is a delegate
44
		 */
45
		var $localFreeBusyMessage;
46
47
		/**
48
		 * @var BinString binary string of PR_MDB_PROVIDER property
0 ignored issues
show
Bug introduced by
The type BinString 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...
49
		 * of a store, this variable will only be populated when user is a delegate
50
		 */
51
		var $storeProviderGuid;
52
53
		/**
54
		 * Constructor
55
		 * @param int $id unique id.
56
		 * @param array $data list of all actions.
57
		 */
58
		function __construct($id, $data, $events = false)
59
		{
60
			$this->start = 0;
61
62
			$this->restriction = false;
0 ignored issues
show
Documentation Bug introduced by
It seems like false of type false is incompatible with the declared type array of property $restriction.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
63
			$this->searchFolderList = false;
64
			$this->localFreeBusyMessage = false;
0 ignored issues
show
Documentation Bug introduced by
It seems like false of type false is incompatible with the declared type MAPIMessage of property $localFreeBusyMessage.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
65
			$this->storeProviderGuid = false;
0 ignored issues
show
Documentation Bug introduced by
It seems like false of type false is incompatible with the declared type BinString of property $storeProviderGuid.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
66
67
			$this->sort = array();
68
69
			parent::__construct($id, $data);
70
		}
71
72
		/**
73
		 * Executes all the actions in the $data variable.
74
		 * @return boolean true on success of false on fialure.
75
		 */
76
		function execute()
77
		{
78
			foreach($this->data as $actionType => $action)
79
			{
80
				if(isset($actionType)) {
81
					try {
82
						$store = $this->getActionStore($action);
83
						$parententryid = $this->getActionParentEntryID($action);
84
						$entryid = $this->getActionEntryID($action);
85
86
						switch($actionType)
87
						{
88
							case "list":
89
								$this->getDelegateFolderInfo($store);
90
								$this->messageList($store, $entryid, $action, $actionType);
0 ignored issues
show
Bug introduced by
$entryid of type object is incompatible with the type string expected by parameter $entryid of ListModule::messageList(). ( Ignorable by Annotation )

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

90
								$this->messageList($store, /** @scrutinizer ignore-type */ $entryid, $action, $actionType);
Loading history...
91
								break;
92
							default:
93
								$this->handleUnknownActionType($actionType);
94
						}
95
					} catch (MAPIException $e) {
96
						$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...
97
					} catch (SearchException $e) {
98
						$this->processException($e, $actionType, $store, $parententryid, $entryid, $action);
99
					}
100
				}
101
			}
102
		}
103
104
		/**
105
		 * Function does customization of MAPIException based on module data.
106
		 * like, here it will generate display message based on actionType
107
		 * for particular exception.
108
		 *
109
		 * @param object $e Exception object
110
		 * @param string $actionType the action type, sent by the client
111
		 * @param MAPIobject $store Store object of the current user.
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...
112
		 * @param string $parententryid parent entryid of the message.
113
		 * @param string $entryid entryid of the message/folder.
114
		 * @param array $action the action data, sent by the client
115
		 */
116
		function handleException(&$e, $actionType = null, $store = null, $parententryid = null, $entryid = null, $action = null)
117
		{
118
			if (is_null($e->displayMessage)) {
119
				switch($actionType) {
120
					case "list":
121
						if ($e->getCode() == MAPI_E_NO_ACCESS) {
122
							$e->setDisplayMessage(_("You have insufficient privileges to see the contents of this folder."));
123
						} else {
124
							$e->setDisplayMessage(_("Could not load the contents of this folder."));
125
						}
126
						break;
127
				}
128
			}
129
130
			parent::handleException($e, $actionType, $store, $parententryid, $entryid, $action);
131
		}
132
133
		/**
134
		 * Function which retrieves a list of messages in a folder
135
		 * @param object $store MAPI Message Store Object
136
		 * @param string $entryid entryid of the folder
137
		 * @param array $action the action data, sent by the client
138
		 * @param string $actionType the action type, sent by the client
139
		 * @return boolean true on success or false on failure
140
		 */
141
		function messageList($store, $entryid, $action, $actionType)
142
		{
143
			$this->searchFolderList = false; // Set to indicate this is not the search result, but a normal folder content
144
145
			if($store && $entryid) {
146
				// Restriction
147
				$this->parseRestriction($action);
0 ignored issues
show
Bug introduced by
$action of type array is incompatible with the type object expected by parameter $action of ListModule::parseRestriction(). ( Ignorable by Annotation )

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

147
				$this->parseRestriction(/** @scrutinizer ignore-type */ $action);
Loading history...
148
149
				// Sort
150
				$this->parseSortOrder($action, null, true);
151
152
				$limit = false;
153
				if(isset($action['restriction']['limit'])){
154
					$limit = $action['restriction']['limit'];
155
				} else {
156
					$limit = $GLOBALS['settings']->get('zarafa/v1/main/page_size', 50);
157
				}
158
159
				$isSearchFolder = isset($action['search_folder_entryid']);
160
				$entryid = $isSearchFolder ? hex2bin($action['search_folder_entryid']) : $entryid;
161
162
				// Get the table and merge the arrays
163
				$data = $GLOBALS["operations"]->getTable($store, $entryid, $this->properties, $this->sort, $this->start, $limit, $this->restriction);
164
165
				// If the request come from search folder then no need to send folder information
166
				if (!$isSearchFolder) {
167
					// Open the folder.
168
					$folder = mapi_msgstore_openentry($store, $entryid);
169
					$data["folder"] = array();
170
171
					// Obtain some statistics from the folder contents
172
					$contentcount = mapi_getprops($folder, array(PR_CONTENT_COUNT, PR_CONTENT_UNREAD));
173
					if (isset($contentcount[PR_CONTENT_COUNT])) {
174
						$data["folder"]["content_count"] = $contentcount[PR_CONTENT_COUNT];
175
					}
176
177
					if (isset($contentcount[PR_CONTENT_UNREAD])) {
178
						$data["folder"]["content_unread"] = $contentcount[PR_CONTENT_UNREAD];
179
					}
180
				}
181
182
				$data = $this->filterPrivateItems($data);
183
184
				// Allowing to hook in just before the data sent away to be sent to the client
185
				$GLOBALS['PluginManager']->triggerHook('server.module.listmodule.list.after', array(
186
					'moduleObject' =>& $this,
187
					'store' => $store,
188
					'entryid' => $entryid,
189
					'action' => $action,
190
					'data' =>& $data
191
				));
192
193
				// unset will remove the value but will not regenerate array keys, so we need to
194
				// do it here
195
				$data["item"] = array_values($data["item"]);
196
197
				$this->addActionData($actionType, $data);
198
				$GLOBALS["bus"]->addData($this->getResponseData());
199
			}
200
		}
201
202
		/**
203
		 *	Function will set search restrictions on search folder and start search process
204
		 *	and it will also parse visible columns and sorting data when sending results to client
205
		 *	@param		object		$store		MAPI Message Store Object
206
		 *	@param		hexString	$entryid	entryid of the folder
0 ignored issues
show
Bug introduced by
The type hexString 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...
207
		 *	@param		object		$action		the action data, sent by the client
208
		 *  @param		string		$actionType	the action type, sent by the client
209
		 */
210
		function search($store, $entryid, $action, $actionType)
211
		{
212
			$useSearchFolder = isset($action["use_searchfolder"]) ? $action["use_searchfolder"] : false;
213
			if(!$useSearchFolder) {
214
				/**
215
				 * store doesn't support search folders so we can't use this
216
				 * method instead we will pass restriction to messageList and
217
				 * it will give us the restricted results
218
				 */
219
				return $this->messageList($store, $entryid, $action, "list");
0 ignored issues
show
Bug introduced by
$action of type object is incompatible with the type array expected by parameter $action of ListModule::messageList(). ( Ignorable by Annotation )

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

219
				return $this->messageList($store, $entryid, /** @scrutinizer ignore-type */ $action, "list");
Loading history...
220
			}
221
222
			$this->searchFolderList = true; // Set to indicate this is not the normal folder, but a search folder
223
			$this->restriction = false;
0 ignored issues
show
Documentation Bug introduced by
It seems like false of type false is incompatible with the declared type array of property $restriction.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
224
			$searchInTodoList = $GLOBALS['entryid']->compareEntryIds(bin2hex($entryid), bin2hex(TodoList::getEntryId()));
225
226
			// Parse Restriction
227
			$this->parseRestriction($action);
228
			if($this->restriction == false) {
229
				// if error in creating restriction then send error to client
230
				$errorInfo = array();
231
				$errorInfo["error_message"] = _("Error in search, please try again") . ".";
232
				$errorInfo["original_error_message"] = "Error in parsing restrictions.";
233
234
				return $this->sendSearchErrorToClient($store, $entryid, $action, $errorInfo);
0 ignored issues
show
Bug introduced by
$errorInfo of type array is incompatible with the type object expected by parameter $errorInfo of ListModule::sendSearchErrorToClient(). ( Ignorable by Annotation )

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

234
				return $this->sendSearchErrorToClient($store, $entryid, $action, /** @scrutinizer ignore-type */ $errorInfo);
Loading history...
235
			}
236
237
			if ( $searchInTodoList ){
238
				// Since we cannot search in a search folder, we will combine the search restriction
239
				// with the search restriction of the to-do list to mimic searching in the To-do list
240
				$this->restriction = array(
241
		            RES_AND,
242
					array(
243
						$this->restriction,
244
						TodoList::_createRestriction()
245
					)
246
				);
247
248
				// When searching in the To-do list we will actually always search in the IPM subtree, so
249
				// set the entryid to that.
250
		        $userStore = WebAppAuthentication::getMapiSession()->getDefaultMessageStore();
251
		        $props = mapi_getprops($userStore, array(PR_IPM_SUBTREE_ENTRYID));
252
		        $entryid = $props[PR_IPM_SUBTREE_ENTRYID];
253
			}
254
255
			$isSetSearchFolderEntryId = isset($action['search_folder_entryid']);
256
			if($isSetSearchFolderEntryId) {
257
				$this->sessionData['searchFolderEntryId'] = $action['search_folder_entryid'];
258
			}
259
260
			if (isset($action['forceCreateSearchFolder']) && $action['forceCreateSearchFolder']) {
261
				$isSetSearchFolderEntryId = false;
262
			}
263
264
			// create or open search folder
265
			$searchFolder = $this->createSearchFolder($store, $isSetSearchFolderEntryId);
266
			if ($searchFolder === false) {
267
				// if error in creating search folder then send error to client
268
				$errorInfo = array();
269
				switch(mapi_last_hresult()) {
270
				case MAPI_E_NO_ACCESS:
271
					$errorInfo["error_message"] = _("Unable to perform search query, no permissions to create search folder.");
272
					break;
273
				case MAPI_E_NOT_FOUND:
274
					$errorInfo["error_message"] = _("Unable to perform search query, search folder not found.");
275
					break;
276
				default:
277
					$errorInfo["error_message"] = _("Unable to perform search query, store might not support searching.");
278
				}
279
280
				$errorInfo["original_error_message"] = _("Error in creating search folder.");
281
282
				return $this->sendSearchErrorToClient($store, $entryid, $action, $errorInfo);
283
			}
284
285
			$subfolder_flag = 0;
286
			if ($searchInTodoList || (isset($action["subfolders"]) && $action["subfolders"] == "true")) {
287
				$subfolder_flag = RECURSIVE_SEARCH;
288
			}
289
290
			if(!is_array($entryid)) {
291
				$entryids = array($entryid);
292
			} else {
293
				$entryids = $entryid;
294
			}
295
296
			$searchFolderEntryId = $this->sessionData['searchFolderEntryId'];
297
298
			// check if searchcriteria has changed
299
			$restrictionCheck = md5(serialize($this->restriction) . $searchFolderEntryId . $subfolder_flag);
300
301
			// check if there is need to set searchcriteria again
302
			if(!isset($this->sessionData['searchCriteriaCheck']) || $restrictionCheck != $this->sessionData['searchCriteriaCheck']) {
303
				if (!empty($this->sessionData['searchOriginalEntryids'])) {
304
					// get entryids of original folders, and use it to set new search criteria
305
					$entryids = Array();
306
					for($index = 0; $index < count($this->sessionData['searchOriginalEntryids']); $index++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
307
						$entryids[] = hex2bin($this->sessionData['searchOriginalEntryids'][$index]);
308
					}
309
				} else {
310
					// store entryids of original folders, so that can be used for re-setting the search criteria if needed
311
					$this->sessionData['searchOriginalEntryids'] = Array();
312
					for($index = 0, $len = count($entryids); $index < $len; $index++) {
313
						$this->sessionData['searchOriginalEntryids'][] = bin2hex($entryids[$index]);
314
					}
315
				}
316
317
				mapi_folder_setsearchcriteria($searchFolder, $this->restriction, $entryids, $subfolder_flag);
318
				$this->sessionData['searchCriteriaCheck'] = $restrictionCheck;
319
			}
320
321
			if(isset($this->sessionData['searchCriteriaCheck']) || $restrictionCheck == $this->sessionData['searchCriteriaCheck']) {
322
				$folderEntryid = bin2hex($entryid);
323
				if($this->sessionData['searchOriginalEntryids'][0] !== $folderEntryid) {
324
					$this->sessionData['searchOriginalEntryids'][0] = $folderEntryid;
325
					mapi_folder_setsearchcriteria($searchFolder, $this->restriction, array($entryid), $subfolder_flag);
326
				}
327
			}
328
329
			unset($action["restriction"]);
330
331
			// Sort
332
			$this->parseSortOrder($action);
333
334
			// Create the data array, which will be send back to the client
335
			$data = array();
336
337
			// Wait until we have some data, no point in returning before we have data. Stop waiting after 10 seconds
338
			$start = time();
339
			$table = mapi_folder_getcontentstable($searchFolder, MAPI_DEFERRED_ERRORS);
340
341
			sleep(1);
342
343
			while(time() - $start < 10) {
344
				$count = mapi_table_getrowcount($table);
345
				$result = mapi_folder_getsearchcriteria($searchFolder);
346
347
				// Stop looping if we have data or the search is finished
348
				if($count > 0)
349
					break;
350
351
				if(($result["searchstate"] & SEARCH_REBUILD) == 0)
352
					break; // Search is done
353
354
				sleep(1);
355
			}
356
357
			// Get the table and merge the arrays
358
			$table = $GLOBALS["operations"]->getTable($store, hex2bin($searchFolderEntryId), $this->properties, $this->sort, $this->start);
359
			$data = array_merge($data, $table);
360
361
			$this->getDelegateFolderInfo($store);
362
			$data = $this->filterPrivateItems($data);
363
364
			// remember which entryid's are send to the client
365
			$searchResults = array();
366
			foreach($table["item"] as $item) {
367
				// store entryid => last_modification_time mapping
368
				$searchResults[$item["entryid"]] = $item["props"]["last_modification_time"];
369
			}
370
371
			// store search results into session data
372
			if(!isset($this->sessionData['searchResults'])) {
373
				$this->sessionData['searchResults'] = array();
374
			}
375
			$this->sessionData['searchResults'][$searchFolderEntryId] = $searchResults;
376
377
			$result = mapi_folder_getsearchcriteria($searchFolder);
378
379
			$data["search_meta"] = array();
380
			$data["search_meta"]["searchfolder_entryid"] = $searchFolderEntryId;
381
			$data["search_meta"]["search_store_entryid"] = $action["store_entryid"];
382
			$data["search_meta"]["searchstate"] = $result["searchstate"];
383
			$data["search_meta"]["results"] = count($searchResults);
384
385
			// Reopen the search folder, because otherwise the suggestion property will
386
			// not have been updated
387
			$searchFolder = $this->createSearchFolder($store, true);
388
			$storeProps = mapi_getprops($searchFolder, array(PR_EC_SUGGESTION));
389
			if ( isset($storeProps[PR_EC_SUGGESTION]) ){
390
				$data["search_meta"]["suggestion"] = $storeProps[PR_EC_SUGGESTION];
391
			}
392
393
			$this->addActionData("search", $data);
394
			$GLOBALS["bus"]->addData($this->getResponseData());
395
396
			return true;
397
		}
398
399
		/**
400
		 *	Function will check for the status of the search on server
401
		 *	and it will also send intermediate results of search, so we don't have to wait
402
		 *	until search is finished on server to send results
403
		 *	@param		object		$store		MAPI Message Store Object
404
		 *	@param		hexString	$entryid	entryid of the folder
405
		 *	@param		object		$action		the action data, sent by the client
406
		 */
407
		function updatesearch($store, $entryid, $action)
408
		{
409
			if(!isset($entryid) || !$entryid) {
410
				// if no entryid is present then we can't do anything here
411
				return;
412
			}
413
414
			$listData = array();
415
			if(isset($action['search_folder_entryid'])) {
416
				$entryid = hex2bin($action['search_folder_entryid']);
417
			}
418
			$searchFolder = mapi_msgstore_openentry($store, $entryid);
419
			$searchResult = mapi_folder_getsearchcriteria($searchFolder);
420
			$searchState = $searchResult["searchstate"];
421
			$table = mapi_folder_getcontentstable($searchFolder, MAPI_DEFERRED_ERRORS);
422
423
			if(is_array($this->sort) && !empty($this->sort)) {
424
				// this sorting will be done on currently fetched results, not all results
425
				// @TODO find a way to do sorting on all search results
426
				mapi_table_sort($table, $this->sort, TBL_BATCH);
427
			}
428
429
			$rowCount = $GLOBALS['settings']->get('zarafa/v1/main/page_size', 50);
430
431
			$searchResults = array();
432
			$entryid = bin2hex($entryid);
433
			if(isset($this->sessionData['searchResults'][$entryid])) {
434
				$searchResults = $this->sessionData['searchResults'][$entryid];
435
			}
436
437
			// searchResults contains entryids of messages
438
			// that are already sent to the server
439
			$numberOfResults = count($searchResults);
440
441
			if($numberOfResults < $rowCount) {
442
				$items = mapi_table_queryallrows($table, array(PR_ENTRYID, PR_LAST_MODIFICATION_TIME));
443
444
				foreach($items as $props) {
445
					$sendItemToClient = false;
446
447
					if(!array_key_exists(bin2hex($props[PR_ENTRYID]), $searchResults)) {
448
						$sendItemToClient = true;
449
					} else {
450
						/**
451
						 * it could happen that an item in search folder has been changed
452
						 * after we have sent it to client, so we have to again send it
453
						 * so we will have to use last_modification_time of item to check
454
						 * that item has been modified since we have sent it to client
455
						 */
456
						// TODO if any item is deleted from search folder it will be not notified to client
457
						if($searchResults[bin2hex($props[PR_ENTRYID])] < $props[PR_LAST_MODIFICATION_TIME]) {
458
							$sendItemToClient = true;
459
						}
460
					}
461
462
					if($sendItemToClient) {
463
						// only get primitive properties, no need to get body, attachments or recipient information
464
						$message = $GLOBALS["operations"]->openMessage($store, $props[PR_ENTRYID]);
465
						array_push($listData, $GLOBALS["operations"]->getProps($message, $this->properties));
466
467
						// store entryid => last_modification_time mapping
468
						$searchResults[bin2hex($props[PR_ENTRYID])] = $props[PR_LAST_MODIFICATION_TIME];
469
					}
470
471
					// when we have more results then fit in the client, we break here,
472
					// we only need to update the counters from this point
473
					$numberOfResults = count($searchResults);
474
					if($numberOfResults >= $rowCount) {
475
						break;
476
					}
477
				}
478
			}
479
480
			$totalRowCount = mapi_table_getrowcount($table);
481
482
			$data = array();
483
			$data["search_meta"] = array();
484
			$data["search_meta"]["searchfolder_entryid"] = $entryid;
485
			$data["search_meta"]["search_store_entryid"] = $action["store_entryid"];
486
			$data["search_meta"]["searchstate"] = $searchState;
487
			$data["search_meta"]["results"] = $numberOfResults;		// actual number of items that we are sending to client
488
489
			$data["page"] = array();
490
			$data["page"]["start"] = 0;
491
			$data["page"]["rowcount"] = $rowCount;
492
			$data["page"]["totalrowcount"] = $totalRowCount;	// total number of items
493
494
			if(!empty($listData)) {
495
				$data["item"] = array_merge(array(), $listData);
496
			}
497
498
			// search is finished so we no more need entryids of search results so clear it up
499
			if($searchState & SEARCH_REBUILD === 0) {
500
				// remove search result entryids stored in session
501
				unset($this->sessionData['searchResults'][$entryid]);
502
			} else {
503
				// store data for next request
504
				$this->sessionData['searchResults'][$entryid] = $searchResults;
505
			}
506
507
			$this->addActionData("updatesearch", $data);
508
			$GLOBALS["bus"]->addData($this->getResponseData());
509
510
			return true;
511
		}
512
513
		/**
514
		 *	Function will stop search on the server if search folder exists
515
		 *	@param		object		$store		MAPI Message Store Object
516
		 *	@param		hexString	$entryid	entryid of the folder
517
		 *	@param		object		$action		the action data, sent by the client
518
		 */
519
		function stopSearch($store, $entryid, $action)
520
		{
521
			// if no entryid is present in the request then get the search folder entryid from session data
522
			$entryid = !empty($entryid) ? $entryid : hex2bin($action['search_folder_entryid']);
523
524
			if(empty($entryid)) {
525
				// still no entryid? sorry i can't help you anymore
526
				$this->addActionData("stopsearch", array( 'success' => false ));
527
				$GLOBALS["bus"]->addData($this->getResponseData());
528
				return;
529
			}
530
531
			// remove search result entryids stored in session
532
			unset($this->sessionData['searchResults'][bin2hex($entryid)]);
533
			unset($this->sessionData['searchCriteriaCheck']);
534
			unset($this->sessionData['searchFolderEntryId']);
535
			unset($this->sessionData['searchOriginalEntryids']);
536
537
			$searchFolder = mapi_msgstore_openentry($store, $entryid);
538
			$searchResult = mapi_folder_getsearchcriteria($searchFolder);
539
540
			// check if search folder exists and search is in progress
541
			if($searchResult !== false && ($searchResult["searchstate"] & SEARCH_REBUILD !== 0)) {
542
				mapi_folder_setsearchcriteria($searchFolder, $searchResult['restriction'], $searchResult['folderlist'], STOP_SEARCH);
543
			}
544
545
			/**
546
			 * when stopping search process, we have to remove search folder also,
547
			 * so next search request with same restriction will not get uncompleted results
548
			 */
549
			$this->deleteSearchFolder($store, $entryid, $action);
0 ignored issues
show
Bug introduced by
$action of type object is incompatible with the type array expected by parameter $action of ListModule::deleteSearchFolder(). ( Ignorable by Annotation )

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

549
			$this->deleteSearchFolder($store, $entryid, /** @scrutinizer ignore-type */ $action);
Loading history...
Bug introduced by
It seems like $entryid can also be of type string; however, parameter $entryid of ListModule::deleteSearchFolder() does only seem to accept hexString, 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

549
			$this->deleteSearchFolder($store, /** @scrutinizer ignore-type */ $entryid, $action);
Loading history...
550
551
			// send success message to client
552
			$this->addActionData("stopsearch", array( 'success' => true ));
553
			$GLOBALS["bus"]->addData($this->getResponseData());
554
		}
555
556
		/**
557
		 * Function will delete search folder
558
		 * @param		object			$store		MAPI Message Store Object
559
		 * @param		hexString		$entryid	entryid of the folder
560
		 * @param		array			$action		the action data, sent by the client
561
		 * @return		boolean						true on success or false on failure
562
		 */
563
		function deleteSearchFolder($store, $entryid, $action)
564
		{
565
			if($entryid && $store) {
0 ignored issues
show
introduced by
$store is of type object, thus it always evaluated to true.
Loading history...
566
				$storeProps = mapi_getprops($store, array(PR_FINDER_ENTRYID));
567
568
				$finderFolder = mapi_msgstore_openentry($store, $storeProps[PR_FINDER_ENTRYID]);
569
570
				if(mapi_last_hresult() != NOERROR){
571
					return;
572
				}
573
574
				$hierarchyTable = mapi_folder_gethierarchytable($finderFolder, MAPI_DEFERRED_ERRORS);
575
576
				$restriction = array(RES_CONTENT,
577
										array(
578
											FUZZYLEVEL	=> FL_FULLSTRING,
579
											ULPROPTAG	=> PR_ENTRYID,
580
											VALUE		=> array(PR_ENTRYID => $entryid)
581
											)
582
									);
583
584
				mapi_table_restrict($hierarchyTable, $restriction, TBL_BATCH);
585
586
				// entryids are unique so there would be only one matching row,
587
				// so only fetch first row
588
				$folders = mapi_table_queryrows($hierarchyTable, array(PR_ENTRYID), 0, 1);
589
590
				// delete search folder
591
				if(is_array($folders) && is_array($folders[0])) {
592
					mapi_folder_deletefolder($finderFolder, $folders[0][PR_ENTRYID]);
593
				}
594
595
				return true;
596
			}
597
598
			return false;
599
		}
600
601
		/**
602
		 *	Function will create a search folder in FINDER_ROOT folder
603
		 *	if folder exists then it will open it
604
		 *	@param		object				$store			MAPI Message Store Object
605
		 *	@param		boolean				$openIfExists		open if folder exists
606
		 *	@return		resource|boolean		$folder			created search folder
607
		 */
608
		function createSearchFolder($store, $openIfExists = true)
609
		{
610
			if(isset($this->sessionData['searchFolderEntryId']) && $openIfExists) {
611
				try {
612
					$searchFolder = mapi_msgstore_openentry($store, hex2bin($this->sessionData['searchFolderEntryId']));
613
614
					if($searchFolder !== false) {
615
						// search folder exists, don't create new search folder
616
						return $searchFolder;
617
					}
618
				} catch (MAPIException $e) {
619
					// ignore error and continue creation of search folder
620
					unset($this->sessionData['searchFolderEntryId']);
621
				}
622
			}
623
624
			// create new search folder
625
			$searchFolderRoot = $this->getSearchFoldersRoot($store);
626
			if($searchFolderRoot === false) {
627
				// error in finding search root folder
628
				// or store doesn't support search folders
629
				return false;
630
			}
631
632
			// check for folder name, if exists then delete it
633
			$folderName = "grommunio Web Search Folder";
634
			try {
635
				$table = mapi_folder_gethierarchytable($searchFolderRoot, 0);
636
				$rows = mapi_table_queryrows($table, array(PR_DISPLAY_NAME, PR_ENTRYID), 0, 0xFFFF);
637
				foreach ($rows as $row) {
638
					if (0 == strcasecmp($folderName, $row[PR_DISPLAY_NAME])) {
639
						mapi_folder_deletefolder($searchFolderRoot, $row[PR_ENTRYID], DEL_FOLDERS|DEL_MESSAGES);
640
						break;
641
					}
642
				}
643
				$searchFolder = mapi_folder_createfolder($searchFolderRoot, $folderName, null, 0, FOLDER_SEARCH);
644
645
				$props = mapi_getprops($searchFolder, array(PR_ENTRYID));
646
				$this->sessionData['searchFolderEntryId'] = bin2hex($props[PR_ENTRYID]);
647
648
				// we have created new search folder so search criteria check should be removed
649
				unset($this->sessionData['searchCriteriaCheck']);
650
651
				return $searchFolder;
652
			} catch (MAPIException $e) {
653
				// don't propagate the event to higher level exception handlers
654
				$e->setHandled();
655
			}
656
657
			return false;
658
		}
659
660
		/**
661
		 *	Function will open FINDER_ROOT folder in root container
662
		 *	public folder's don't have FINDER_ROOT folder
663
		 *	@param		object			store MAPI message store object
0 ignored issues
show
Bug introduced by
The type store 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...
664
		 *	@return		resource|boolean	finder root folder for search folders
665
		 */
666
		function getSearchFoldersRoot($store)
667
		{
668
			$searchRootFolder = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $searchRootFolder is dead and can be removed.
Loading history...
669
670
			// check if we can create search folders
671
			$storeProps = mapi_getprops($store, array(PR_STORE_SUPPORT_MASK, PR_FINDER_ENTRYID, PR_DISPLAY_NAME));
672
			if (($storeProps[PR_STORE_SUPPORT_MASK] & STORE_SEARCH_OK) !== STORE_SEARCH_OK) {
673
				// store doesn't support search folders, public store don't have FINDER_ROOT folder
674
				return false;
675
			}
676
677
			try {
678
				$searchRootFolder = mapi_msgstore_openentry($store, $storeProps[PR_FINDER_ENTRYID]);
679
			} catch (MAPIException $e) {
680
				$msg ="Unable to open FINDER_ROOT for store: %s.";
681
				error_log(sprintf($msg, $storeProps[PR_DISPLAY_NAME]));
682
				// don't propagate the event to higher level exception handlers
683
				$e->setHandled();
684
			}
685
686
			return $searchRootFolder;
687
		}
688
689
		/**
690
		 *	Function will send error message to client if any error has occurred in search
691
		 *	@param		object		$store		MAPI Message Store Object
692
		 *	@param		hexString	$entryid	entryid of the folder
693
		 *	@param		object		$action		the action data, sent by the client
694
		 *	@param		object		$errorInfo	the error information object
695
		 */
696
		function sendSearchErrorToClient($store, $entryid, $action, $errorInfo)
697
		{
698
			if($errorInfo) {
0 ignored issues
show
introduced by
$errorInfo is of type object, thus it always evaluated to true.
Loading history...
699
				$exception = new SearchException(isset($errorInfo["original_error_message"]) ? $errorInfo["original_error_message"] : $errorInfo['error_message'], mapi_last_hresult());
700
				$exception->setDisplayMessage($errorInfo['error_message']);
701
702
				// after sending error, remove error data
703
				$errorInfo = array();
0 ignored issues
show
Unused Code introduced by
The assignment to $errorInfo is dead and can be removed.
Loading history...
704
705
				throw $exception;
706
			}
707
708
			return false;
709
		}
710
711
		/**
712
		 *	Function will create restriction based on restriction array.
713
		 *	@param	object		$action		the action data, sent by the client
714
		 */
715
		function parseRestriction($action)
716
		{
717
			if (isset($action["restriction"]) && is_array($action['restriction'])) {
718
				if(isset($action["restriction"]["start"])) {
719
					// Set start variable
720
					$this->start = $action["restriction"]["start"];
721
				}
722
				foreach ($action["restriction"] as $key => $value) {
723
					$props = $this->properties;
724
					if (!empty($value)) {
725
						switch ($key) {
726
							case "search":
727
								$props = array_merge($this->properties, array('body' => PR_BODY));
728
							case "task":
729
							case "note":
730
							case "filter":
731
								$this->restriction = Conversion::json2restriction($props, $value);
732
								break;
733
						}
734
					}
735
				}
736
			}
737
		}
738
739
		/**
740
		 * Parses the incoming sort request and builds a MAPI sort order. Normally
741
		 * properties are mapped from the XML to MAPI by the standard $this->properties mapping. However,
742
		 * if you want other mappings, you can specify them in the optional $map mapping.
743
		 *
744
		 * $allow_multi_instance is used for creating multiple instance of MV property related items.
745
		 * $properties is used for using a custom set of properties instead of properties stored in module
746
		 */
747
		function parseSortOrder($action, $map = false, $allow_multi_instance = false, $properties = false)
748
		{
749
			if(isset($action["sort"])) {
750
				$this->sort = array();
751
752
				if(!$properties) {
753
					$properties = $this->properties;
754
				}
755
756
				// Unshift MVI_FLAG of MV properties. So the table is not sort on it anymore.
757
				// Otherwise the server would generate multiple rows for one item (categories).
758
				foreach($properties as $id => $property)
759
				{
760
					switch(mapi_prop_type($property))
761
					{
762
						case (PT_MV_STRING8 | MVI_FLAG):
763
						case (PT_MV_LONG | MVI_FLAG):
764
							$properties[$id] = $properties[$id] &~ MV_INSTANCE;
765
							break;
766
					}
767
				}
768
769
				// Loop through the sort columns
770
				foreach($action["sort"] as $column)
771
				{
772
					if(isset($column["direction"])) {
773
						if(isset($properties[$column["field"]]) || ($map && isset($map[$column["field"]]))) {
774
							if($map && isset($map[$column["field"]]))
775
								$property = $map[$column["field"]];
776
							else
777
								$property = $properties[$column["field"]];
778
779
							// Check if column is a MV property
780
							switch(mapi_prop_type($property))
781
							{
782
								case PT_MV_STRING8:
783
								case PT_MV_LONG:
784
									// Set MVI_FLAG.
785
									// The server will generate multiple rows for one item (for example: categories)
786
									if($allow_multi_instance){
787
										$properties[$column["field"]] = $properties[$column["field"]] | MVI_FLAG;
788
									}
789
									$property = $properties[$column["field"]];
790
									break;
791
							}
792
793
							// Set sort direction
794
							switch(strtolower($column["direction"]))
795
							{
796
								default:
797
								case "asc":
798
									$this->sort[$property] = TABLE_SORT_ASCEND;
799
									break;
800
								case "desc":
801
									$this->sort[$property] = TABLE_SORT_DESCEND;
802
									break;
803
							}
804
						}
805
					}
806
				}
807
			}
808
		}
809
810
		/**
811
		 * Function which gets the delegation details from localfreebusy message to use in
812
		 * processPrivateItems function.
813
		 * @param {MAPIStore} $store MAPI Message Store Object
0 ignored issues
show
Documentation Bug introduced by
The doc comment {MAPIStore} at position 0 could not be parsed: Unknown type name '{' at position 0 in {MAPIStore}.
Loading history...
814
		 */
815
		function getDelegateFolderInfo($store)
816
		{
817
			$this->localFreeBusyMessage = false;
0 ignored issues
show
Documentation Bug introduced by
It seems like false of type false is incompatible with the declared type MAPIMessage of property $localFreeBusyMessage.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
818
			$this->storeProviderGuid = false;
0 ignored issues
show
Documentation Bug introduced by
It seems like false of type false is incompatible with the declared type BinString of property $storeProviderGuid.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
819
820
			try {
821
				$this->storeProviderGuid = mapi_getprops($store, array(PR_MDB_PROVIDER));
822
				$this->storeProviderGuid = $this->storeProviderGuid[PR_MDB_PROVIDER];
823
824
				if($this->storeProviderGuid !== ZARAFA_STORE_DELEGATE_GUID) {
825
					// user is not a delegate, so no point of processing further
826
					return;
827
				}
828
829
				// get localfreebusy message
830
				$this->localFreeBusyMessage = freebusy::getLocalFreeBusyMessage($store);
831
			} catch(MAPIException $e) {
832
				// we got some error, but we don't care about that error instead just continue
833
				$e->setHandled();
834
835
				$this->localFreeBusyMessage = false;
836
				$this->storeProviderGuid = false;
837
			}
838
		}
839
840
		/**
841
		 * Helper function which loop through each item and filter out
842
		 * private items, if any.
843
		 * @param {array} array structure with row search data
844
		 * @return {array} array structure with row search data
0 ignored issues
show
Documentation Bug introduced by
The doc comment {array} at position 0 could not be parsed: Unknown type name '{' at position 0 in {array}.
Loading history...
845
		 */
846
		function filterPrivateItems($data)
847
		{
848
			// Disable private items
849
			for($index = 0, $len = count($data["item"]); $index < $len; $index++) {
850
				$data["item"][$index] = $this->processPrivateItem($data["item"][$index]);
851
852
				if(empty($data["item"][$index])) {
853
					// remove empty results from data
854
					unset($data["item"][$index]);
855
				}
856
			}
857
858
			return $data;
859
		}
860
861
		/**
862
		 * Function will be used to process private items in a list response, modules can
863
		 * can decide what to do with the private items, remove the entire row or just
864
		 * hide the data. This function will entirely remove the private message but
865
		 * if any child class needs different behavior then this can be overridden.
866
		 * @param {Object} $item item properties
0 ignored issues
show
Documentation Bug introduced by
The doc comment {Object} at position 0 could not be parsed: Unknown type name '{' at position 0 in {Object}.
Loading history...
867
		 * @return {Object} item properties if its non private item otherwise empty array
0 ignored issues
show
Documentation Bug introduced by
The doc comment {Object} at position 0 could not be parsed: Unknown type name '{' at position 0 in {Object}.
Loading history...
868
		 */
869
		function processPrivateItem($item)
870
		{
871
			if($this->checkPrivateItem($item)) {
872
				// hide the item by returning empty array, that can be removed from response
873
				return array();
874
			}
875
876
			return $item;
877
		}
878
879
		/**
880
		 * Function will be used check if any item is private or not and if it private then
881
		 * we should process it as private, because you don't want to process private items in
882
		 * user's default store.
883
		 * This function will check we are dealing with delegate stores or not if it is then
884
		 * the delegator has permission to see private items of delegate.
885
		 * @param {Object} $item item properties
0 ignored issues
show
Documentation Bug introduced by
The doc comment {Object} at position 0 could not be parsed: Unknown type name '{' at position 0 in {Object}.
Loading history...
886
		 * @return {Boolean} true if items should be processed as private else false.
0 ignored issues
show
Documentation Bug introduced by
The doc comment {Boolean} at position 0 could not be parsed: Unknown type name '{' at position 0 in {Boolean}.
Loading history...
887
		 */
888
		function checkPrivateItem($item)
889
		{
890
			// flag to indicate that item should be considered as private
891
			$private = false;
892
893
			$isPrivate = (isset($item['props']['private']) && $item['props']['private'] === true);
894
			$isSensitive = (isset($item['props']['sensitivity']) && $item['props']['sensitivity'] === SENSITIVITY_PRIVATE);
895
896
			if($isPrivate || $isSensitive) {
897
				// check for delegate permissions for delegate store
898
				if($this->storeProviderGuid !== false && $this->storeProviderGuid === ZARAFA_STORE_DELEGATE_GUID) {
899
					// by default we should always hide the item if we are in delegate store
900
					$private = true;
901
902
					// find delegate properties
903
					if($this->localFreeBusyMessage !== false) {
0 ignored issues
show
introduced by
The condition $this->localFreeBusyMessage !== false is always true.
Loading history...
904
						try {
905
							$localFreeBusyMessageProps = mapi_getprops($this->localFreeBusyMessage, array(PR_SCHDINFO_DELEGATE_ENTRYIDS, PR_DELEGATES_SEE_PRIVATE));
906
907
							if(isset($localFreeBusyMessageProps[PR_SCHDINFO_DELEGATE_ENTRYIDS]) && isset($localFreeBusyMessageProps[PR_DELEGATES_SEE_PRIVATE])) {
908
								// if more then one delegates info is stored then find index of
909
								// current user
910
								$userEntryId = $GLOBALS['mapisession']->getUserEntryID();
911
912
								$userFound = false;
913
								$seePrivate = false;
914
								foreach($localFreeBusyMessageProps[PR_SCHDINFO_DELEGATE_ENTRYIDS] as $key => $entryId) {
915
									if ($GLOBALS['entryid']->compareABEntryIds(bin2hex($userEntryId), bin2hex($entryId))) {
916
										$userFound = true;
917
										$seePrivate = $localFreeBusyMessageProps[PR_DELEGATES_SEE_PRIVATE][$key];
918
										break;
919
									}
920
								}
921
922
								if($userFound !== false && $seePrivate === 1) {
923
									// if delegate has permission then don't hide the item
924
									$private = false;
925
								}
926
							}
927
						} catch (MAPIException $e) {
928
							if($e->getCode() === MAPI_E_NOT_FOUND) {
929
								// no information available for delegates, ignore error
930
								$e->setHandled();
931
							}
932
						}
933
					}
934
				}
935
			}
936
937
			return $private;
938
		}
939
	}
940
?>
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...
941