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

modules/class.advancedsearchlistmodule.php (1 issue)

Severity
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);
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);
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);
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
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");
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;
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);
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)) {
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++) {
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) {
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
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