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

AdvancedSearchListModule::parsePatterns()   D

Complexity

Conditions 28
Paths 23

Size

Total Lines 62
Code Lines 53

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 28
eloc 53
nc 23
nop 2
dl 0
loc 62
rs 4.1666
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
	require_once(BASE_PATH . 'server/includes/core/class.indexsqlite.php');
3
4
	class AdvancedSearchListModule extends ListModule
5
	{
6
		/**
7
		 * Constructor
8
		 * @param		int		$id			unique id.
9
		 * @param		array		$data		list of all actions.
10
		 */
11
		function __construct($id, $data)
12
		{
13
			parent::__construct($id, $data);
14
			// TODO: create a new method in Properties class that will return only the properties we
15
			// need for search list (and perhaps for preview???)
16
			$this->properties = $GLOBALS["properties"]->getMailListProperties();
17
			$this->properties = array_merge($this->properties, $GLOBALS["properties"]->getAppointmentListProperties());
18
			$this->properties = array_merge($this->properties, $GLOBALS["properties"]->getContactListProperties());
19
			$this->properties = array_merge($this->properties, $GLOBALS["properties"]->getStickyNoteListProperties());
20
			$this->properties = array_merge($this->properties, $GLOBALS["properties"]->getTaskListProperties());
21
			$this->properties = array_merge($this->properties, array(
22
					'body' => PR_BODY,
23
					'html_body' => PR_HTML,
24
					'startdate' => "PT_SYSTIME:PSETID_Appointment:0x820d",
25
					'duedate' => "PT_SYSTIME:PSETID_Appointment:0x820e",
26
					'creation_time' => PR_CREATION_TIME,
27
					"task_duedate" => "PT_SYSTIME:PSETID_Task:0x8105"));
28
			$this->properties = getPropIdsFromStrings($GLOBALS["mapisession"]->getDefaultMessageStore(), $this->properties);
29
30
			
31
		}
32
33
		/**
34
		 * Executes all the actions in the $data variable.
35
		 * @return		boolean					true on success or false on failure.
36
		 */
37
		function execute()
38
		{
39
			foreach($this->data as $actionType => $action)
40
			{
41
				if(isset($actionType)) {
42
					try {
43
						$store = $this->getActionStore($action);
44
						$parententryid = $this->getActionParentEntryID($action);
0 ignored issues
show
Unused Code introduced by
The assignment to $parententryid is dead and can be removed.
Loading history...
45
						$entryid = $this->getActionEntryID($action);
46
47
						switch($actionType)
48
						{
49
							case "list":
50
							case "updatelist":
51
								$this->getDelegateFolderInfo($store);
52
								$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 AdvancedSearchListModule::messageList(). ( Ignorable by Annotation )

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

52
								$this->messageList($store, /** @scrutinizer ignore-type */ $entryid, $action, $actionType);
Loading history...
53
								break;
54
							case "search":
55
								$this->search($store, $entryid, $action, $actionType);
56
								break;
57
							case "updatesearch":
58
								$this->updatesearch($store, $entryid, $action);
59
								break;
60
							case "stopsearch":
61
								$this->stopSearch($store, $entryid, $action);
62
								break;
63
							case "delete_searchfolder":
64
								$this->deleteSearchFolder($store, $entryid, $action);
65
								break;
66
						}
67
					} catch (MAPIException $e) {
68
						// This is a very nasty hack that makes sure that grommunio Web doesn't show an error message when
69
						// search wants to throw an error. This is only done because a proper fix for this bug has not
70
						// been found yet. When WA-9161 is really solved, this should be removed again.
71
						if ( $actionType !== 'search' && $actionType !== 'updatesearch' && $actionType !== 'stopsearch' ){
72
							$this->processException($e, $actionType);
73
						} else {
74
							if ( DEBUG_LOADER === 'LOAD_SOURCE' ){
75
								// Log all info we can get about this error to the error log of the web server
76
								error_log("Error in search: \n" . var_export($e, true) . "\n\n" . var_export(debug_backtrace(), true));
77
							}
78
							// Send success feedback without data, as if nothing strange happened...
79
							$this->sendFeedback(true);
80
						}
81
					}
82
				}
83
			}
84
		}
85
86
		/**
87
		 * Function which retrieves a list of messages in a folder
88
		 * @param object $store MAPI Message Store Object
89
		 * @param string $entryid entryid of the folder
90
		 * @param array $action the action data, sent by the client
91
		 * @param string $actionType the action type, sent by the client
92
		 * @return boolean true on success or false on failure
93
		 */
94
		function messageList($store, $entryid, $action, $actionType)
95
		{
96
			$this->searchFolderList = false; // Set to indicate this is not the search result, but a normal folder content
97
98
			if($store && $entryid) {
99
				// Restriction
100
				$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

100
				$this->parseRestriction(/** @scrutinizer ignore-type */ $action);
Loading history...
101
102
				// Sort
103
				$this->parseSortOrder($action, null, true);
104
105
				$limit = false;
106
				if(isset($action['restriction']['limit'])){
107
					$limit = $action['restriction']['limit'];
108
				}
109
110
				$isSearchFolder = isset($action['search_folder_entryid']);
111
				$entryid = $isSearchFolder ? hex2bin($action['search_folder_entryid']) : $entryid;
112
113
				// Get the table and merge the arrays
114
				$data = $GLOBALS["operations"]->getTable($store, $entryid, $this->properties, $this->sort, $this->start, $limit, $this->restriction);
115
116
				// If the request come from search folder then no need to send folder information
117
				if (!$isSearchFolder) {
118
					// Open the folder.
119
					$folder = mapi_msgstore_openentry($store, $entryid);
120
					$data["folder"] = array();
121
122
					// Obtain some statistics from the folder contents
123
					$contentcount = mapi_getprops($folder, array(PR_CONTENT_COUNT, PR_CONTENT_UNREAD));
124
					if (isset($contentcount[PR_CONTENT_COUNT])) {
125
						$data["folder"]["content_count"] = $contentcount[PR_CONTENT_COUNT];
126
					}
127
128
					if (isset($contentcount[PR_CONTENT_UNREAD])) {
129
						$data["folder"]["content_unread"] = $contentcount[PR_CONTENT_UNREAD];
130
					}
131
				}
132
133
				$data = $this->filterPrivateItems($data);
134
135
				// Allowing to hook in just before the data sent away to be sent to the client
136
				$GLOBALS['PluginManager']->triggerHook('server.module.listmodule.list.after', array(
137
					'moduleObject' =>& $this,
138
					'store' => $store,
139
					'entryid' => $entryid,
140
					'action' => $action,
141
					'data' =>& $data
142
				));
143
144
				// unset will remove the value but will not regenerate array keys, so we need to
145
				// do it here
146
				$data["item"] = array_values($data["item"]);
147
				$this->addActionData($actionType, $data);
148
				$GLOBALS["bus"]->addData($this->getResponseData());
149
			}
150
		}
151
		
152
		private static function parsePatterns($restriction, &$patterns) {
153
			if (empty($restriction)) {
154
				return;
155
			}
156
			$type = $restriction[0];
157
			if ($type == RES_CONTENT) {
158
				$subres = $restriction[1];
159
				switch ($subres[ULPROPTAG]) {
160
				case PR_SUBJECT:
161
					$patterns['subject'] = $subres[VALUE][$subres[ULPROPTAG]];
162
					break;
163
				case PR_BODY:
164
					$patterns['content'] = $subres[VALUE][$subres[ULPROPTAG]];
165
					$patterns['attachments'] = $subres[VALUE][$subres[ULPROPTAG]];
166
					break;
167
				case PR_SENDER_NAME:
168
					$patterns['sender'] = $subres[VALUE][$subres[ULPROPTAG]];
169
					break;
170
				case PR_SENT_REPRESENTING_NAME:
171
					$patterns['from'] = $subres[VALUE][$subres[ULPROPTAG]];
172
					break;
173
				case PR_DISPLAY_TO:
174
				case PR_DISPLAY_CC:
175
					$patterns['recipients'] = $subres[VALUE][$subres[ULPROPTAG]];
176
					break;
177
				case PR_MESSAGE_CLASS:
178
					if (empty($patterns['message_classes'])) {
179
						$patterns['message_classes'] = array();
180
					}
181
					$patterns['message_classes'][] = $subres[VALUE][$subres[ULPROPTAG]];
182
					break;
183
				case PR_DISPLAY_NAME:
184
					$patterns['others'] = $subres[VALUE][$subres[ULPROPTAG]];
185
					break;
186
				}
187
			} else if ($type == RES_AND || $type == RES_OR) {
188
				foreach ($restriction[1] as $subres) {
189
					AdvancedSearchListModule::parsePatterns($subres, $patterns);
190
				}
191
			} else if ($type == RES_BITMASK) {
192
				$subres = $restriction[1];
193
				if ($subres[ULPROPTAG] == PR_MESSAGE_FLAGS && $subres[ULTYPE] == BMR_EQZ) {
194
					if (MSGFLAG_READ & $subres[ULMASK]) {
195
						$patterns['unread'] = true;
196
					}
197
				}
198
			} else if ($type == RES_PROPERTY) {
199
				$subres = $restriction[1];
200
				if (($subres[ULPROPTAG] == PR_MESSAGE_DELIVERY_TIME ||
201
					$subres[ULPROPTAG] == PR_LAST_MODIFICATION_TIME)) {
202
					if ($subres[RELOP] == RELOP_LT ||
203
						$subres[RELOP] == RELOP_LE) {
204
						$patterns['date_end'] = $subres[VALUE][$subres[ULPROPTAG]];
205
					} else if ($subres[RELOP] == RELOP_GT ||
206
						$subres[RELOP] == RELOP_GE) {
207
						$patterns['date_start'] = $subres[VALUE][$subres[ULPROPTAG]];	
208
					}
209
				}
210
			} else if ($type == RES_SUBRESTRICTION) {
211
				$subres = $restriction[1];
212
				if (($subres[ULPROPTAG] == PR_MESSAGE_ATTACHMENTS)) {
213
					$patterns['has_attachments'] = true;
214
				}
215
			}
216
		}
217
		
218
		/**
219
		 *	Function will set search restrictions on search folder and start search process
220
		 *	and it will also parse visible columns and sorting data when sending results to client
221
		 *	@param		object		$store		MAPI Message Store Object
222
		 *	@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...
223
		 *	@param		object		$action		the action data, sent by the client
224
		 *  @param		string		$actionType	the action type, sent by the client
225
		 */
226
		function search($store, $entryid, $action, $actionType)
227
		{
228
			$useSearchFolder = isset($action["use_searchfolder"]) ? $action["use_searchfolder"] : false;
229
			if (!$useSearchFolder) {
230
				/**
231
				 * store doesn't support search folders so we can't use this
232
				 * method instead we will pass restriction to messageList and
233
				 * it will give us the restricted results
234
				 */
235
				return parent::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

235
				return parent::messageList($store, $entryid, /** @scrutinizer ignore-type */ $action, "list");
Loading history...
236
			}
237
			$store_props = mapi_getprops($store, array(PR_MDB_PROVIDER, PR_DEFAULT_STORE));
238
			if ($store_props[PR_MDB_PROVIDER] == ZARAFA_STORE_PUBLIC_GUID ||
239
				empty($store_props[PR_DEFAULT_STORE]) || !$store_props[PR_DEFAULT_STORE]){
240
				// public store or share store do not support search folders
241
				return parent::messageList($store, $entryid, $action, "list");
242
			}
243
			if ($GLOBALS['entryid']->compareEntryIds(bin2hex($entryid), bin2hex(TodoList::getEntryId()))) {
244
				// todo list do not need to perform full text index search
245
				return parent::messageList($store, $entryid, $action, "list");
246
			}
247
			
248
			
249
			$this->searchFolderList = true; // Set to indicate this is not the normal folder, but a search folder
250
			$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...
251
			
252
253
			// Parse Restriction
254
			$this->parseRestriction($action);
255
			if($this->restriction == false) {
256
				// if error in creating restriction then send error to client
257
				$errorInfo = array();
258
				$errorInfo["error_message"] = _("Error in search, please try again") . ".";
259
				$errorInfo["original_error_message"] = "Error in parsing restrictions.";
260
261
				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

261
				return $this->sendSearchErrorToClient($store, $entryid, $action, /** @scrutinizer ignore-type */ $errorInfo);
Loading history...
262
			}
263
264
			$isSetSearchFolderEntryId = isset($action['search_folder_entryid']);
265
			if($isSetSearchFolderEntryId) {
266
				$this->sessionData['searchFolderEntryId'] = $action['search_folder_entryid'];
267
			}
268
269
			if (isset($action['forceCreateSearchFolder']) && $action['forceCreateSearchFolder']) {
270
				$isSetSearchFolderEntryId = false;
271
			}
272
273
			// create or open search folder
274
			$searchFolder = $this->createSearchFolder($store, $isSetSearchFolderEntryId);
275
			if ($searchFolder === false) {
276
				// if error in creating search folder then send error to client
277
				$errorInfo = array();
278
				switch(mapi_last_hresult()) {
279
				case MAPI_E_NO_ACCESS:
280
					$errorInfo["error_message"] = _("Unable to perform search query, no permissions to create search folder.");
281
					break;
282
				case MAPI_E_NOT_FOUND:
283
					$errorInfo["error_message"] = _("Unable to perform search query, search folder not found.");
284
					break;
285
				default:
286
					$errorInfo["error_message"] = _("Unable to perform search query, store might not support searching.");
287
				}
288
289
				$errorInfo["original_error_message"] = _("Error in creating search folder.");
290
291
				return $this->sendSearchErrorToClient($store, $entryid, $action, $errorInfo);
292
			}
293
294
			$subfolder_flag = 0;
295
			$recursive = false;
296
			if (isset($action["subfolders"]) && $action["subfolders"] == "true") {
297
				$recursive = true;
298
				$subfolder_flag = RECURSIVE_SEARCH;
299
			}
300
301
			if(!is_array($entryid)) {
0 ignored issues
show
introduced by
The condition is_array($entryid) is always false.
Loading history...
302
				$entryids = array($entryid);
303
			} else {
304
				$entryids = $entryid;
305
			}
306
307
			$searchFolderEntryId = $this->sessionData['searchFolderEntryId'];
308
309
			// check if searchcriteria has changed
310
			$restrictionCheck = md5(serialize($this->restriction) . $searchFolderEntryId . $subfolder_flag);
311
312
			// check if there is need to set searchcriteria again
313
			if(!isset($this->sessionData['searchCriteriaCheck']) || $restrictionCheck != $this->sessionData['searchCriteriaCheck']) {
314
				if (!empty($this->sessionData['searchOriginalEntryids'])) {
315
					// get entryids of original folders, and use it to set new search criteria
316
					$entryids = Array();
317
					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...
318
						$entryids[] = hex2bin($this->sessionData['searchOriginalEntryids'][$index]);
319
					}
320
				} else {
321
					// store entryids of original folders, so that can be used for re-setting the search criteria if needed
322
					$this->sessionData['searchOriginalEntryids'] = Array();
323
					for($index = 0, $len = count($entryids); $index < $len; $index++) {
324
						$this->sessionData['searchOriginalEntryids'][] = bin2hex($entryids[$index]);
325
					}
326
				}
327
				// we never start the search folder because we will populate the search folder by ourselves
328
				mapi_folder_setsearchcriteria($searchFolder, $this->restriction, $entryids, $subfolder_flag|STOP_SEARCH);
329
				$this->sessionData['searchCriteriaCheck'] = $restrictionCheck;
330
			}
331
332
			if(isset($this->sessionData['searchCriteriaCheck']) || $restrictionCheck == $this->sessionData['searchCriteriaCheck']) {
333
				$folderEntryid = bin2hex($entryid);
334
				if($this->sessionData['searchOriginalEntryids'][0] !== $folderEntryid) {
335
					$this->sessionData['searchOriginalEntryids'][0] = $folderEntryid;
336
					// we never start the search folder because we will populate the search folder by ourselves
337
					mapi_folder_setsearchcriteria($searchFolder, $this->restriction, array($entryid), $subfolder_flag|STOP_SEARCH);
338
				}
339
			}
340
341
			unset($action["restriction"]);
342
343
			// Sort
344
			$this->parseSortOrder($action);
345
			
346
			$search_patterns = array();
347
			AdvancedSearchListModule::parsePatterns($this->restriction, $search_patterns);
348
			if (isset($search_patterns['message_classes']) &&
349
				count($search_patterns['message_classes']) >= 7) {
350
				unset($search_patterns['message_classes']);
351
			}
352
			
353
			$indexDB = new IndexSqlite();
354
			if (!$indexDB->load()) {
355
				// if error in creating search folder then send error to client
356
				$errorInfo = array();
357
				$errorInfo["error_message"] = _("Unable to perform search query, store might not support searching.");
358
				$errorInfo["original_error_message"] = _("Error in creating search folder.");
359
				return $this->sendSearchErrorToClient($store, $entryid, $action, $errorInfo);
360
			}
361
			$search_result = $indexDB->search(hex2bin($searchFolderEntryId), $search_patterns['sender'], $search_patterns['from'],
362
				$search_patterns['recipients'], $search_patterns['subject'], $search_patterns['content'],
363
				$search_patterns['attachments'], $search_patterns['others'], $entryid, $recursive,
364
				$search_patterns['message_classes'], $search_patterns['date_start'], $search_patterns['date_end'],
365
				$search_patterns['unread'], $search_patterns['has_attachments']);
366
			if (false == $search_result) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
367
				// if error in creating search folder then send error to client
368
				$errorInfo = array();
369
				$errorInfo["error_message"] = _("Unable to perform search query, search folder not found.");
370
				$errorInfo["original_error_message"] = _("Error in creating search folder.");
371
				return $this->sendSearchErrorToClient($store, $entryid, $action, $errorInfo);
372
			}
373
			
374
			// Get the table and merge the arrays
375
			$table = $GLOBALS["operations"]->getTable($store, hex2bin($searchFolderEntryId), $this->properties, $this->sort, $this->start);
376
			// Create the data array, which will be send back to the client
377
			$data = array();
378
			$data = array_merge($data, $table);
379
			
380
			$this->getDelegateFolderInfo($store);
381
			$data = $this->filterPrivateItems($data);
382
383
			// remember which entryid's are send to the client
384
			$searchResults = array();
385
			foreach($table["item"] as $item) {
386
				// store entryid => last_modification_time mapping
387
				$searchResults[$item["entryid"]] = $item["props"]["last_modification_time"];
388
			}
389
390
			// store search results into session data
391
			if(!isset($this->sessionData['searchResults'])) {
392
				$this->sessionData['searchResults'] = array();
393
			}
394
			$this->sessionData['searchResults'][$searchFolderEntryId] = $searchResults;
395
396
			$result = mapi_folder_getsearchcriteria($searchFolder);
397
398
			$data["search_meta"] = array();
399
			$data["search_meta"]["searchfolder_entryid"] = $searchFolderEntryId;
400
			$data["search_meta"]["search_store_entryid"] = $action["store_entryid"];
401
			$data["search_meta"]["searchstate"] = $result["searchstate"];
402
			$data["search_meta"]["results"] = count($searchResults);
403
404
			// Reopen the search folder, because otherwise the suggestion property will
405
			// not have been updated
406
			$searchFolder = $this->createSearchFolder($store, true);
407
			$storeProps = mapi_getprops($searchFolder, array(PR_EC_SUGGESTION));
408
			if ( isset($storeProps[PR_EC_SUGGESTION]) ){
409
				$data["search_meta"]["suggestion"] = $storeProps[PR_EC_SUGGESTION];
410
			}
411
412
			$this->addActionData("search", $data);
413
			$GLOBALS["bus"]->addData($this->getResponseData());
414
415
			return true;
416
417
		}
418
419
	}
420
?>
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...
421