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

includes/modules/class.resolvenamesmodule.php (1 issue)

Severity
1
<?php
2
	/**
3
	 * ResolveNames Module
4
	 */
5
	class ResolveNamesModule extends Module
6
	{
7
		/**
8
		 * Constructor
9
		 */
10
		function __construct($id, $data)
11
		{
12
			parent::__construct($id, $data);
13
		}
14
15
		/**
16
		 * Executes all the actions in the $data variable.
17
		 * @return boolean true on success of false on fialure.
18
		 */
19
		function execute()
20
		{
21
			foreach($this->data as $actionType => $action)
22
			{
23
				if(isset($actionType)) {
24
					try {
25
						switch($actionType)
26
						{
27
							case 'checknames':
28
								$this->checkNames($action);
29
								break;
30
							default:
31
								$this->handleUnknownActionType($actionType);
32
						}
33
					} catch (MAPIException $e) {
34
						$this->processException($e, $actionType);
35
					}
36
				}
37
			}
38
		}
39
40
		/**
41
		 * Function which checks the names, sent by the client. This function is used
42
		 * when a user wants to sent an email and want to check the names filled in
43
		 * by the user in the to, cc and bcc field. This function uses the global
44
		 * user list to check if the names are correct.
45
		 * @param array $action the action data, sent by the client
46
		 * @return boolean true on success or false on failure
47
		 */
48
		function checkNames($action)
49
		{
50
			if(isset($action['resolverequests'])) {
51
				$data = array();
52
				$excludeLocalContacts = !empty($action['exclude_local_contacts']) ? $action['exclude_local_contacts'] : false;
53
				$excludeGABGroups = !empty($action['exclude_gab_groups']) ? $action['exclude_gab_groups'] : false;
54
55
				$resolveRequest = $action['resolverequests'];
56
				if(!is_array($resolveRequest)) {
57
					$resolveRequest = array($resolveRequest);
58
				}
59
60
				// open addressbook
61
				// When local contacts need to be excluded we have pass true as the first argument
62
				// so we will not have any Contact Providers set on the Addressbook resource.
63
				$ab = $GLOBALS['mapisession']->getAddressbook($excludeLocalContacts);
64
65
				$ab_entryid = mapi_ab_getdefaultdir($ab);
66
				$ab_dir = mapi_ab_openentry($ab, $ab_entryid);
67
				$resolveResponse = Array();
68
69
				// check names
70
				foreach($resolveRequest as $query) {
71
					if (is_array($query) && isset($query['id'])) {
72
						$responseEntry = Array();
73
						$responseEntry['id'] = $query['id'];
74
75
						if (!empty($query['display_name']) || !empty($query['email_address'])) {
76
							$responseEntry['result'] = $this->searchAddressBook($ab, $ab_dir, $query, $excludeGABGroups);
77
							$resolveResponse[] = $responseEntry;
78
						}
79
					}
80
				}
81
82
				$data['resolveresponse'] = $resolveResponse;
83
84
				$this->addActionData('checknames', $data);
85
				$GLOBALS['bus']->addData($this->getResponseData());
86
			}
87
		}
88
89
		/**
90
		 * This function searches the addressbook specified for users and returns an array with data
91
		 * Please note that the returning array must be UTF8
92
		 *
93
		 * @param {MAPIAddressbook} $ab The addressbook
94
		 * @param {MAPIAbContainer} $ab_dir The addressbook container
95
		 * @param {String} $query The search query, case is ignored
96
		 * @param {Boolean} $excludeGABGroups flag to exclude groups from resolving
97
		 */
98
		function searchAddressBook($ab, $ab_dir, $query, $excludeGABGroups)
99
		{
100
			// Prefer resolving the email_address. This allows the user
101
			// to resolve recipients with a display name that matches a EX
102
			// user with an alternative (external) email address.
103
			$searchstr = empty($query['email_address']) ? $query['display_name'] : $query['email_address'];
104
			// If the address_type is 'EX' then we are resolving something which must be found in
105
			// the GAB as an exact match. So add the flag EMS_AB_ADDRESS_LOOKUP to ensure we will not
106
			// get multiple results when multiple items have a partial match.
107
			$flags = $query['address_type'] === 'EX' ? EMS_AB_ADDRESS_LOOKUP : 0;
108
109
			try {
110
				// First, try an addressbook lookup
111
				$rows = mapi_ab_resolvename($ab, array ( array(PR_DISPLAY_NAME => $searchstr) ) , $flags);
112
			} catch (MAPIException $e) {
113
				if ($e->getCode() == MAPI_E_AMBIGUOUS_RECIP) {
114
					// Ambiguous, show possibilities:
115
					$table = mapi_folder_getcontentstable($ab_dir, MAPI_DEFERRED_ERRORS);
116
					$restriction = $this->getAmbigiousContactRestriction($searchstr, $excludeGABGroups, PR_ACCOUNT);
117
118
					mapi_table_restrict($table, $restriction, TBL_BATCH);
119
					mapi_table_sort($table, array(PR_DISPLAY_NAME => TABLE_SORT_ASCEND), TBL_BATCH);
120
121
					$rows = mapi_table_queryallrows($table, array(PR_ACCOUNT, PR_ADDRTYPE, PR_DISPLAY_NAME, PR_ENTRYID, PR_SEARCH_KEY, PR_OBJECT_TYPE, PR_SMTP_ADDRESS, PR_DISPLAY_TYPE_EX, PR_EMAIL_ADDRESS, PR_OBJECT_TYPE, PR_DISPLAY_TYPE));
122
123
					$rows = array_merge($rows, $this->getAmbigiousContactResolveResults($ab, $searchstr, $excludeGABGroups));
124
				} else if ($e->getCode() == MAPI_E_NOT_FOUND) {
125
					$rows = array();
126
					// If we still can't find anything, and we were searching for a SMTP user
127
					// we can generate a oneoff entry which contains the information of the user.
128
					if ($query['address_type'] === 'SMTP' && !empty($query['email_address'])) {
129
						$rows[] = array(
130
							PR_ACCOUNT => $query['email_address'], PR_ADDRTYPE => 'SMTP', PR_EMAIL_ADDRESS => $query['email_address'],
131
							PR_DISPLAY_NAME => $query['display_name'], PR_DISPLAY_TYPE_EX => DT_REMOTE_MAILUSER, PR_DISPLAY_TYPE => DT_MAILUSER,
132
							PR_SMTP_ADDRESS => $query['email_address'], PR_OBJECT_TYPE => MAPI_MAILUSER,
133
							PR_ENTRYID => mapi_createoneoff($query['display_name'], 'SMTP', $query['email_address'])
134
						);
135
					}
136
				} else {
137
					// all other errors should be propagated to higher level exception handlers
138
					throw $e;
139
				}
140
			}
141
142
			$items = array();
143
			if ($rows) {
144
				foreach($rows as $user_data) {
145
					$item = array();
146
147
					if (!isset($user_data[PR_ACCOUNT])) {
148
						$abitem = mapi_ab_openentry($ab, $user_data[PR_ENTRYID]);
149
						$user_data = mapi_getprops($abitem, array(PR_ACCOUNT, PR_ADDRTYPE, PR_DISPLAY_NAME, PR_DISPLAY_TYPE_EX, PR_ENTRYID, PR_SEARCH_KEY, PR_EMAIL_ADDRESS, PR_SMTP_ADDRESS, PR_OBJECT_TYPE, PR_DISPLAY_TYPE));
150
					}
151
152
					if($excludeGABGroups && $user_data[PR_OBJECT_TYPE] === MAPI_DISTLIST) {
153
						// exclude groups from result
154
						continue;
155
					}
156
157
					$item = array();
158
					$item['object_type'] = isset($user_data[PR_OBJECT_TYPE]) ? $user_data[PR_OBJECT_TYPE] : MAPI_MAILUSER;
159
					$item['entryid'] = isset($user_data[PR_ENTRYID]) ? bin2hex($user_data[PR_ENTRYID]) : '';
160
					$item['display_name'] = isset($user_data[PR_DISPLAY_NAME]) ? $user_data[PR_DISPLAY_NAME] : '';
161
					$item['display_type'] = isset($user_data[PR_DISPLAY_TYPE]) ? $user_data[PR_DISPLAY_TYPE] : DT_MAILUSER;
162
163
					// Test whether the GUID in the entryid is from the Contact Provider
164
					if($GLOBALS['entryid']->hasContactProviderGUID($item['entryid'])){
165
						// The properties for a Distribution List differs from the other objects
166
						if($item['object_type'] == MAPI_DISTLIST) {
167
							$item['address_type'] = 'MAPIPDL';
168
							// The email_address is empty for DistList, using display name for resolving
169
							$item['email_address'] = $item['display_name'];
170
							$item['smtp_address'] = isset($item['smtp_address']) ? $item['smtp_address'] : '';
171
						} else {
172
							$item['address_type'] = 'EX';
173
							if (isset($user_data['address_type']) && $user_data['address_type'] === 'EX') {
174
								$item['email_address'] = isset($user_data[PR_EMAIL_ADDRESS]) ? $user_data[PR_EMAIL_ADDRESS] : '';
175
							} else {
176
								// Fake being an EX account, since it's actually an SMTP addrtype the email address is in a different property.
177
								$item['smtp_address'] = isset($user_data[PR_EMAIL_ADDRESS]) ? $user_data[PR_EMAIL_ADDRESS] : '';
178
								// Keep the old scenario happy.
179
								$item['email_address'] = isset($user_data[PR_EMAIL_ADDRESS]) ? $user_data[PR_EMAIL_ADDRESS] : '';
180
							}
181
						}
182
					// It can be considered a GAB entry
183
					} else {
184
						$item['user_name'] = isset($user_data[PR_ACCOUNT]) ? $user_data[PR_ACCOUNT] : $item['display_name'];
185
						$item['display_type_ex'] = isset($user_data[PR_DISPLAY_TYPE_EX]) ? $user_data[PR_DISPLAY_TYPE_EX] : MAPI_MAILUSER;
186
						$item['email_address'] = isset($user_data[PR_EMAIL_ADDRESS]) ? $user_data[PR_EMAIL_ADDRESS] : '';
187
						$item['smtp_address'] = isset($user_data[PR_SMTP_ADDRESS]) ? $user_data[PR_SMTP_ADDRESS] : $item['email_address'];
188
						$item['address_type'] = isset($user_data[PR_ADDRTYPE]) ? $user_data[PR_ADDRTYPE] : 'SMTP';
189
					}
190
191
					if (isset($user_data[PR_SEARCH_KEY])) {
192
						$item['search_key'] = bin2hex($user_data[PR_SEARCH_KEY]);
193
					} else {
194
						$emailAddress = isset($item['smtp_address']) ? $item['smtp_address'] : $item['email_address'];
195
						$item['search_key'] = bin2hex(strtoupper($item['address_type'] . ':' . $emailAddress)) . '00';
196
					}
197
198
					array_push($items, $item);
199
				}
200
			}
201
202
			return $items;
203
		}
204
205
		/**
206
		 * Used to find multiple entries from the contact folders in the Addressbook when resolving
207
		 * returned an ambiguous result. It will find the Contact folders in the Addressbook and
208
		 * apply a restriction to extract the entries.
209
		 * @param {MAPIAddressbook} $ab The addressbook
210
		 * @param {String} $query The search query, case is ignored
211
		 * @param {Boolean} $excludeGABGroups flag to exclude groups from resolving
212
		 */
213
		function getAmbigiousContactResolveResults($ab, $query, $excludeGABGroups)
214
		{
215
			/* We need to look for the Contact folders at the bottom of the following tree.
216
			*
217
			 * IAddrBook
218
			 *  - Root Container
219
			 *     - HIERARCHY TABLE
220
			 *        - Contacts Folders    (Contact Container)
221
			 *           - HIERARCHY TABLE         (Contact Container Hierarchy)
222
			 *              - Contact folder 1
223
			 *              - Contact folder 2
224
			 **/
225
226
			$rows = Array();
227
			$contactFolderRestriction = $this->getAmbigiousContactRestriction($query, $excludeGABGroups, PR_EMAIL_ADDRESS);
228
			// Open the AB Root Container by not supplying an entryid
229
			$abRootContainer = mapi_ab_openentry($ab);
230
231
			// Get the 'Contact Folders'
232
			$hierarchyTable = mapi_folder_gethierarchytable($abRootContainer, MAPI_DEFERRED_ERRORS);
233
			$abHierarchyRows = mapi_table_queryallrows($hierarchyTable, array(PR_AB_PROVIDER_ID, PR_ENTRYID));
234
235
			// Look for the 'Contacts Folders'
236
			for($i=0,$len=count($abHierarchyRows);$i<$len;$i++){
237
				// Check if the folder matches the Contact Provider GUID
238
				if($abHierarchyRows[$i][PR_AB_PROVIDER_ID] == MUIDZCSAB){
239
					$abContactContainerEntryid = $abHierarchyRows[$i][PR_ENTRYID];
240
					break;
241
				}
242
			}
243
244
			// Next go into the 'Contacts Folders' and look in the hierarchy table for the Contact folders.
245
			if($abContactContainerEntryid){
246
				// Get the rows from hierarchy table of the 'Contacts Folders'
247
				$abContactContainer = mapi_ab_openentry($ab, $abContactContainerEntryid);
248
				$abContactContainerHierarchyTable = mapi_folder_gethierarchytable($abContactContainer, MAPI_DEFERRED_ERRORS);
249
				$abContactContainerHierarchyRows = mapi_table_queryallrows($abContactContainerHierarchyTable, array(PR_DISPLAY_NAME, PR_OBJECT_TYPE, PR_ENTRYID));
250
251
				// Loop through all the contact folders found under the 'Contacts Folders' hierarchy
252
				for($j=0,$len=count($abContactContainerHierarchyRows);$j<$len;$j++){
253
254
					// Open, get contents table, restrict, sort and then merge the result in the list of $rows
255
					$abContactFolder = mapi_ab_openentry($ab, $abContactContainerHierarchyRows[$j][PR_ENTRYID]);
256
					$abContactFolderTable = mapi_folder_getcontentstable($abContactFolder, MAPI_DEFERRED_ERRORS);
257
258
					mapi_table_restrict($abContactFolderTable, $contactFolderRestriction, TBL_BATCH);
259
					mapi_table_sort($abContactFolderTable, array(PR_DISPLAY_NAME => TABLE_SORT_ASCEND), TBL_BATCH);
260
261
					// Go go gadget, merge!
262
					$rows = array_merge($rows, mapi_table_queryallrows($abContactFolderTable, array(PR_ACCOUNT, PR_DISPLAY_NAME, PR_ENTRYID, PR_OBJECT_TYPE, PR_SMTP_ADDRESS, PR_DISPLAY_TYPE_EX, PR_EMAIL_ADDRESS, PR_OBJECT_TYPE, PR_DISPLAY_TYPE)));
263
				}
264
			}
265
266
			return $rows;
267
		}
268
269
		/**
270
		 * Setup the restriction used for resolving in the Contact folders or GAB.
271
		 * @param {String} $query The search query, case is ignored
272
		 * @param {Boolean} $excludeGABGroups flag to exclude groups from resolving
273
		 * @param {Number} $content the PROPTAG to search in.
274
		 */
275
		function getAmbigiousContactRestriction($query, $excludeGABGroups, $content)
276
		{
277
			// only return users from who the displayName or the username starts with $name
278
			// TODO: use PR_ANR for this restriction instead of PR_DISPLAY_NAME and $content.
279
			$resAnd = array(
280
				array(RES_OR,
281
					array(
282
						array(RES_CONTENT,
283
							array(
284
								FUZZYLEVEL => FL_PREFIX | FL_IGNORECASE,
285
								ULPROPTAG => PR_DISPLAY_NAME,
286
								VALUE => $query
287
							)
288
						),
289
						array(RES_CONTENT,
290
							array(
291
								FUZZYLEVEL => FL_PREFIX | FL_IGNORECASE,
292
								ULPROPTAG => $content,
293
								VALUE => $query
294
							)
295
						)
296
					) // RES_OR
297
				)
298
			);
299
300
			// create restrictions based on excludeGABGroups flag
301
			if($excludeGABGroups) {
302
				array_push($resAnd, array(
303
					RES_PROPERTY,
304
					array(
305
						RELOP => RELOP_EQ,
306
						ULPROPTAG => PR_OBJECT_TYPE,
307
						VALUE => MAPI_MAILUSER
308
					)
309
				));
310
			} else {
311
				array_push($resAnd, array(RES_OR,
312
					array(
313
						array(RES_PROPERTY,
314
							array(
315
								RELOP => RELOP_EQ,
316
								ULPROPTAG => PR_OBJECT_TYPE,
317
								VALUE => MAPI_MAILUSER
318
							)
319
						),
320
						array(RES_PROPERTY,
321
							array(
322
								RELOP => RELOP_EQ,
323
								ULPROPTAG => PR_OBJECT_TYPE,
324
								VALUE => MAPI_DISTLIST
325
							)
326
						)
327
					)
328
				));
329
			}
330
			$restriction = array(RES_AND, $resAnd);
331
332
			return $restriction;
333
		}
334
335
		/**
336
		 * Function does customization of exception based on module data.
337
		 * like, here it will generate display message based on actionType
338
		 * for particular exception.
339
		 *
340
		 * @param object $e Exception object
341
		 * @param string $actionType the action type, sent by the client
342
		 * @param MAPIobject $store Store object of message.
343
		 * @param string $parententryid parent entryid of the message.
344
		 * @param string $entryid entryid of the message.
345
		 * @param array $action the action data, sent by the client
346
		 */
347
		function handleException(&$e, $actionType = null, $store = null, $parententryid = null, $entryid = null, $action = null)
348
		{
349
			if(is_null($e->displayMessage)) {
350
				switch($actionType)
351
				{
352
					case 'checknames':
353
						if($e->getCode() == MAPI_E_NO_ACCESS) {
354
							$e->setDisplayMessage(_('You have insufficient privileges to perform this action.'));
355
						} else {
356
							$e->setDisplayMessage(_('Could not resolve user.'));
357
						}
358
						break;
359
				}
360
			}
361
362
			parent::handleException($e, $actionType, $store, $parententryid, $entryid, $action);
363
		}
364
	}
365
?>
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...
366