Total Complexity | 68 |
Total Lines | 489 |
Duplicated Lines | 0 % |
Changes | 2 | ||
Bugs | 1 | Features | 0 |
Complex classes like ResolveNamesModule often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use ResolveNamesModule, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
6 | class ResolveNamesModule extends Module { |
||
7 | /** |
||
8 | * Constructor. |
||
9 | * |
||
10 | * @param mixed $id |
||
11 | * @param mixed $data |
||
12 | */ |
||
13 | public function __construct($id, $data) { |
||
15 | } |
||
16 | |||
17 | /** |
||
18 | * Executes all the actions in the $data variable. |
||
19 | */ |
||
20 | #[Override] |
||
21 | public function execute() { |
||
22 | foreach ($this->data as $actionType => $action) { |
||
23 | if (isset($actionType)) { |
||
24 | try { |
||
25 | match ($actionType) { |
||
26 | 'checknames' => $this->checkNames($action), |
||
|
|||
27 | default => $this->handleUnknownActionType($actionType), |
||
28 | }; |
||
29 | } |
||
30 | catch (MAPIException $e) { |
||
31 | $this->processException($e, $actionType); |
||
32 | } |
||
33 | } |
||
34 | } |
||
35 | } |
||
36 | |||
37 | /** |
||
38 | * Function which checks the names, sent by the client. This function is used |
||
39 | * when a user wants to sent an email and want to check the names filled in |
||
40 | * by the user in the to, cc and bcc field. This function uses the global |
||
41 | * user list to check if the names are correct. |
||
42 | * |
||
43 | * @param array $action the action data, sent by the client |
||
44 | */ |
||
45 | public function checkNames($action) { |
||
80 | } |
||
81 | } |
||
82 | |||
83 | /** |
||
84 | * This function searches the addressbook specified for users and returns an array with data |
||
85 | * Please note that the returning array must be UTF8. |
||
86 | * |
||
87 | * @param resource $ab The addressbook |
||
88 | * @param resource $ab_dir The addressbook container |
||
89 | * @param string $query The search query, case is ignored |
||
90 | * @param bool $excludeGABGroups flag to exclude groups from resolving |
||
91 | */ |
||
92 | public function searchAddressBook($ab, $ab_dir, $query, $excludeGABGroups) { |
||
93 | // Prefer resolving the email_address. This allows the user |
||
94 | // to resolve recipients with a display name that matches a EX |
||
95 | // user with an alternative (external) email address. |
||
96 | $searchstr = empty($query['email_address']) ? $query['display_name'] : $query['email_address']; |
||
97 | // If the address_type is 'EX' then we are resolving something which must be found in |
||
98 | // the GAB as an exact match. So add the flag EMS_AB_ADDRESS_LOOKUP to ensure we will not |
||
99 | // get multiple results when multiple items have a partial match. |
||
100 | $flags = $query['address_type'] === 'EX' ? EMS_AB_ADDRESS_LOOKUP : 0; |
||
101 | |||
102 | try { |
||
103 | // First, try an addressbook lookup |
||
104 | $rows = mapi_ab_resolvename($ab, [[PR_DISPLAY_NAME => $searchstr]], $flags); |
||
105 | $this->searchContactsFolders($ab, $ab_dir, $searchstr, $rows); |
||
106 | } |
||
107 | catch (MAPIException $e) { |
||
108 | if ($e->getCode() == MAPI_E_AMBIGUOUS_RECIP) { |
||
109 | $ab_entryid = mapi_ab_getdefaultdir($ab); |
||
110 | $ab_dir = mapi_ab_openentry($ab, $ab_entryid); |
||
111 | // Ambiguous, show possibilities: |
||
112 | $table = mapi_folder_getcontentstable($ab_dir, MAPI_DEFERRED_ERRORS); |
||
113 | $restriction = $this->getAmbigiousContactRestriction($searchstr, $excludeGABGroups, PR_ACCOUNT); |
||
114 | |||
115 | mapi_table_restrict($table, $restriction, TBL_BATCH); |
||
116 | mapi_table_sort($table, [PR_DISPLAY_NAME => TABLE_SORT_ASCEND], TBL_BATCH); |
||
117 | |||
118 | $rows = mapi_table_queryallrows($table, [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]); |
||
119 | |||
120 | $rows = array_merge($rows, $this->getAmbigiousContactResolveResults($ab, $searchstr, $excludeGABGroups)); |
||
121 | } |
||
122 | elseif ($e->getCode() == MAPI_E_NOT_FOUND) { |
||
123 | $rows = []; |
||
124 | |||
125 | if (defined('ENABLE_RESOLVE_HIDDEN_USERS') && ENABLE_RESOLVE_HIDDEN_USERS) { |
||
126 | $rows = $this->resolveHiddenExactMatches($ab, $query); |
||
127 | } |
||
128 | |||
129 | if (empty($rows) && (($query['address_type'] ?? '') === 'SMTP')) { |
||
130 | // If we still can't find anything, and we were searching for a SMTP user |
||
131 | // we can generate a oneoff entry which contains the information of the user. |
||
132 | if (!empty($query['email_address'])) { |
||
133 | $rows[] = [ |
||
134 | PR_ACCOUNT => $query['email_address'], PR_ADDRTYPE => 'SMTP', PR_EMAIL_ADDRESS => $query['email_address'], |
||
135 | PR_DISPLAY_NAME => $query['display_name'], PR_DISPLAY_TYPE_EX => DT_REMOTE_MAILUSER, PR_DISPLAY_TYPE => DT_MAILUSER, |
||
136 | PR_SMTP_ADDRESS => $query['email_address'], PR_OBJECT_TYPE => MAPI_MAILUSER, |
||
137 | PR_ENTRYID => mapi_createoneoff($query['display_name'], 'SMTP', $query['email_address']), |
||
138 | ]; |
||
139 | } |
||
140 | // Check also the user's contacts folders |
||
141 | else { |
||
142 | $this->searchContactsFolders($ab, $ab_dir, $searchstr, $rows); |
||
143 | } |
||
144 | } |
||
145 | elseif (empty($rows)) { |
||
146 | $this->searchContactsFolders($ab, $ab_dir, $searchstr, $rows); |
||
147 | } |
||
148 | } |
||
149 | else { |
||
150 | // all other errors should be propagated to higher level exception handlers |
||
151 | throw $e; |
||
152 | } |
||
153 | } |
||
154 | |||
155 | $items = []; |
||
156 | if ($rows) { |
||
157 | foreach ($rows as $user_data) { |
||
158 | $item = []; |
||
159 | |||
160 | if (!isset($user_data[PR_ACCOUNT])) { |
||
161 | $abitem = mapi_ab_openentry($ab, $user_data[PR_ENTRYID]); |
||
162 | $user_data = mapi_getprops($abitem, [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]); |
||
163 | } |
||
164 | |||
165 | if ($excludeGABGroups && $user_data[PR_OBJECT_TYPE] === MAPI_DISTLIST) { |
||
166 | // exclude groups from result |
||
167 | continue; |
||
168 | } |
||
169 | |||
170 | $item = []; |
||
171 | $item['object_type'] = $user_data[PR_OBJECT_TYPE] ?? MAPI_MAILUSER; |
||
172 | $item['entryid'] = isset($user_data[PR_ENTRYID]) ? bin2hex($user_data[PR_ENTRYID]) : ''; |
||
173 | $item['display_name'] = $user_data[PR_DISPLAY_NAME] ?? ''; |
||
174 | $item['display_type'] = $user_data[PR_DISPLAY_TYPE] ?? DT_MAILUSER; |
||
175 | |||
176 | // Test whether the GUID in the entryid is from the Contact Provider |
||
177 | if ($GLOBALS['entryid']->hasContactProviderGUID($item['entryid'])) { |
||
178 | // The properties for a Distribution List differs from the other objects |
||
179 | if ($item['object_type'] == MAPI_DISTLIST) { |
||
180 | $item['address_type'] = 'MAPIPDL'; |
||
181 | // The email_address is empty for DistList, using display name for resolving |
||
182 | $item['email_address'] = $item['display_name']; |
||
183 | $item['smtp_address'] ??= ''; |
||
184 | } |
||
185 | else { |
||
186 | $item['address_type'] = $user_data[PR_ADDRTYPE] ?? 'SMTP'; |
||
187 | if (isset($user_data['address_type']) && $user_data['address_type'] === 'EX') { |
||
188 | $item['email_address'] = $user_data[PR_EMAIL_ADDRESS] ?? ''; |
||
189 | } |
||
190 | else { |
||
191 | // Fake being an EX account, since it's actually an SMTP addrtype the email address is in a different property. |
||
192 | $item['smtp_address'] = $user_data[PR_EMAIL_ADDRESS] ?? ''; |
||
193 | // Keep the old scenario happy. |
||
194 | $item['email_address'] = $user_data[PR_EMAIL_ADDRESS] ?? ''; |
||
195 | } |
||
196 | } |
||
197 | // It can be considered a GAB entry |
||
198 | } |
||
199 | else { |
||
200 | $item['user_name'] = $user_data[PR_ACCOUNT] ?? $item['display_name']; |
||
201 | $item['display_type_ex'] = $user_data[PR_DISPLAY_TYPE_EX] ?? MAPI_MAILUSER; |
||
202 | $item['email_address'] = $user_data[PR_EMAIL_ADDRESS] ?? ''; |
||
203 | $item['smtp_address'] = $user_data[PR_SMTP_ADDRESS] ?? $item['email_address']; |
||
204 | $item['address_type'] = $user_data[PR_ADDRTYPE] ?? 'SMTP'; |
||
205 | } |
||
206 | |||
207 | if (isset($user_data[PR_SEARCH_KEY])) { |
||
208 | $item['search_key'] = bin2hex($user_data[PR_SEARCH_KEY]); |
||
209 | } |
||
210 | else { |
||
211 | $emailAddress = $item['smtp_address'] ?? $item['email_address']; |
||
212 | $item['search_key'] = bin2hex(strtoupper($item['address_type'] . ':' . $emailAddress)) . '00'; |
||
213 | } |
||
214 | |||
215 | array_push($items, $item); |
||
216 | } |
||
217 | } |
||
218 | |||
219 | return $items; |
||
220 | } |
||
221 | |||
222 | /** |
||
223 | * Try to resolve a hidden user by matching the provided query exactly against |
||
224 | * common address book properties. Hidden entries are not returned in regular |
||
225 | * searches, but the EMS_AB_ADDRESS_LOOKUP flag allows us to perform an exact |
||
226 | * lookup that also returns hidden entries. |
||
227 | * |
||
228 | * @param resource $ab The addressbook |
||
229 | * @param array $query Resolve query sent by the client |
||
230 | * |
||
231 | * @return array |
||
232 | */ |
||
233 | protected function resolveHiddenExactMatches($ab, $query) { |
||
234 | if (!defined('ENABLE_RESOLVE_HIDDEN_USERS') || !ENABLE_RESOLVE_HIDDEN_USERS) { |
||
235 | return []; |
||
236 | } |
||
237 | |||
238 | $candidates = []; |
||
239 | |||
240 | if (!empty($query['display_name'])) { |
||
241 | $candidates[] = trim((string) $query['display_name']); |
||
242 | } |
||
243 | |||
244 | if (!empty($query['email_address'])) { |
||
245 | $candidates[] = trim((string) $query['email_address']); |
||
246 | } |
||
247 | |||
248 | $candidates = array_values(array_unique(array_filter($candidates, 'strlen'))); |
||
249 | $addressType = strtoupper((string) ($query['address_type'] ?? '')); |
||
250 | |||
251 | foreach ($candidates as $candidate) { |
||
252 | $lookups = [PR_DISPLAY_NAME]; |
||
253 | |||
254 | if ($addressType === 'EX') { |
||
255 | $lookups[] = PR_ACCOUNT; |
||
256 | } |
||
257 | |||
258 | if (str_contains($candidate, '@')) { |
||
259 | $lookups[] = PR_EMAIL_ADDRESS; |
||
260 | $lookups[] = PR_SMTP_ADDRESS; |
||
261 | |||
262 | if ($addressType !== 'EX') { |
||
263 | $lookups[] = PR_ACCOUNT; |
||
264 | } |
||
265 | } |
||
266 | |||
267 | foreach (array_unique($lookups) as $property) { |
||
268 | $rows = $this->resolveNameByProperty($ab, $property, $candidate); |
||
269 | if (!empty($rows)) { |
||
270 | return $rows; |
||
271 | } |
||
272 | } |
||
273 | } |
||
274 | |||
275 | return []; |
||
276 | } |
||
277 | |||
278 | /** |
||
279 | * Execute an exact resolve against the GAB for the provided property. |
||
280 | * |
||
281 | * @param resource $ab The addressbook |
||
282 | * @param int $property Property tag used for the lookup |
||
283 | * @param string $value Search value |
||
284 | * |
||
285 | * @return array |
||
286 | */ |
||
287 | protected function resolveNameByProperty($ab, $property, $value) { |
||
288 | if ($value === '') { |
||
289 | return []; |
||
290 | } |
||
291 | |||
292 | try { |
||
293 | return mapi_ab_resolvename($ab, [[$property => $value]], EMS_AB_ADDRESS_LOOKUP); |
||
294 | } |
||
295 | catch (MAPIException $e) { |
||
296 | if ($e->getCode() == MAPI_E_NOT_FOUND || $e->getCode() == MAPI_E_AMBIGUOUS_RECIP) { |
||
297 | return []; |
||
298 | } |
||
299 | |||
300 | throw $e; |
||
301 | } |
||
302 | } |
||
303 | |||
304 | /** |
||
305 | * Used to find multiple entries from the contact folders in the Addressbook when resolving |
||
306 | * returned an ambiguous result. It will find the Contact folders in the Addressbook and |
||
307 | * apply a restriction to extract the entries. |
||
308 | * |
||
309 | * @param resource $ab The addressbook |
||
310 | * @param string $query The search query, case is ignored |
||
311 | * @param bool $excludeGABGroups flag to exclude groups from resolving |
||
312 | */ |
||
313 | public function getAmbigiousContactResolveResults($ab, $query, $excludeGABGroups) { |
||
314 | /* We need to look for the Contact folders at the bottom of the following tree. |
||
315 | * |
||
316 | * IAddrBook |
||
317 | * - Root Container |
||
318 | * - HIERARCHY TABLE |
||
319 | * - Contacts Folders (Contact Container) |
||
320 | * - HIERARCHY TABLE (Contact Container Hierarchy) |
||
321 | * - Contact folder 1 |
||
322 | * - Contact folder 2 |
||
323 | */ |
||
324 | |||
325 | $rows = []; |
||
326 | $contactFolderRestriction = $this->getAmbigiousContactRestriction($query, $excludeGABGroups, PR_EMAIL_ADDRESS); |
||
327 | // Open the AB Root Container by not supplying an entryid |
||
328 | $abRootContainer = mapi_ab_openentry($ab); |
||
329 | |||
330 | // Get the 'Contact Folders' |
||
331 | $hierarchyTable = mapi_folder_gethierarchytable($abRootContainer, MAPI_DEFERRED_ERRORS); |
||
332 | $abHierarchyRows = mapi_table_queryallrows($hierarchyTable, [PR_AB_PROVIDER_ID, PR_ENTRYID]); |
||
333 | |||
334 | // Look for the 'Contacts Folders' |
||
335 | for ($i = 0,$len = count($abHierarchyRows); $i < $len; ++$i) { |
||
336 | // Check if the folder matches the Contact Provider GUID |
||
337 | if ($abHierarchyRows[$i][PR_AB_PROVIDER_ID] == MUIDZCSAB) { |
||
338 | $abContactContainerEntryid = $abHierarchyRows[$i][PR_ENTRYID]; |
||
339 | break; |
||
340 | } |
||
341 | } |
||
342 | |||
343 | // Next go into the 'Contacts Folders' and look in the hierarchy table for the Contact folders. |
||
344 | if ($abContactContainerEntryid) { |
||
345 | // Get the rows from hierarchy table of the 'Contacts Folders' |
||
346 | $abContactContainer = mapi_ab_openentry($ab, $abContactContainerEntryid); |
||
347 | $abContactContainerHierarchyTable = mapi_folder_gethierarchytable($abContactContainer, MAPI_DEFERRED_ERRORS); |
||
348 | $abContactContainerHierarchyRows = mapi_table_queryallrows($abContactContainerHierarchyTable, [PR_DISPLAY_NAME, PR_OBJECT_TYPE, PR_ENTRYID]); |
||
349 | |||
350 | // Loop through all the contact folders found under the 'Contacts Folders' hierarchy |
||
351 | for ($j = 0,$len = count($abContactContainerHierarchyRows); $j < $len; ++$j) { |
||
352 | // Open, get contents table, restrict, sort and then merge the result in the list of $rows |
||
353 | $abContactFolder = mapi_ab_openentry($ab, $abContactContainerHierarchyRows[$j][PR_ENTRYID]); |
||
354 | $abContactFolderTable = mapi_folder_getcontentstable($abContactFolder, MAPI_DEFERRED_ERRORS); |
||
355 | |||
356 | mapi_table_restrict($abContactFolderTable, $contactFolderRestriction, TBL_BATCH); |
||
357 | mapi_table_sort($abContactFolderTable, [PR_DISPLAY_NAME => TABLE_SORT_ASCEND], TBL_BATCH); |
||
358 | |||
359 | // Go go gadget, merge! |
||
360 | $rows = array_merge($rows, mapi_table_queryallrows($abContactFolderTable, [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])); |
||
361 | } |
||
362 | } |
||
363 | |||
364 | return $rows; |
||
365 | } |
||
366 | |||
367 | /** |
||
368 | * Setup the restriction used for resolving in the Contact folders or GAB. |
||
369 | * |
||
370 | * @param string $query The search query, case is ignored |
||
371 | * @param bool $excludeGABGroups flag to exclude groups from resolving |
||
372 | * @param int $content the PROPTAG to search in |
||
373 | */ |
||
374 | public function getAmbigiousContactRestriction($query, $excludeGABGroups, $content) { |
||
375 | // only return users from who the displayName or the username starts with $name |
||
376 | // TODO: use PR_ANR for this restriction instead of PR_DISPLAY_NAME and $content. |
||
377 | $resAnd = [ |
||
378 | [RES_OR, |
||
379 | [ |
||
380 | [RES_CONTENT, |
||
381 | [ |
||
382 | FUZZYLEVEL => FL_PREFIX | FL_IGNORECASE, |
||
383 | ULPROPTAG => PR_DISPLAY_NAME, |
||
384 | VALUE => $query, |
||
385 | ], |
||
386 | ], |
||
387 | [RES_CONTENT, |
||
388 | [ |
||
389 | FUZZYLEVEL => FL_PREFIX | FL_IGNORECASE, |
||
390 | ULPROPTAG => $content, |
||
391 | VALUE => $query, |
||
392 | ], |
||
393 | ], |
||
394 | ], // RES_OR |
||
395 | ], |
||
396 | ]; |
||
397 | |||
398 | // create restrictions based on excludeGABGroups flag |
||
399 | if ($excludeGABGroups) { |
||
400 | array_push($resAnd, [ |
||
401 | RES_PROPERTY, |
||
402 | [ |
||
403 | RELOP => RELOP_EQ, |
||
404 | ULPROPTAG => PR_OBJECT_TYPE, |
||
405 | VALUE => MAPI_MAILUSER, |
||
406 | ], |
||
407 | ]); |
||
408 | } |
||
409 | else { |
||
410 | array_push($resAnd, [RES_OR, |
||
411 | [ |
||
412 | [RES_PROPERTY, |
||
413 | [ |
||
414 | RELOP => RELOP_EQ, |
||
415 | ULPROPTAG => PR_OBJECT_TYPE, |
||
416 | VALUE => MAPI_MAILUSER, |
||
417 | ], |
||
418 | ], |
||
419 | [RES_PROPERTY, |
||
420 | [ |
||
421 | RELOP => RELOP_EQ, |
||
422 | ULPROPTAG => PR_OBJECT_TYPE, |
||
423 | VALUE => MAPI_DISTLIST, |
||
424 | ], |
||
425 | ], |
||
426 | ], |
||
427 | ]); |
||
428 | } |
||
429 | |||
430 | return [RES_AND, $resAnd]; |
||
431 | } |
||
432 | |||
433 | /** |
||
434 | * Function does customization of exception based on module data. |
||
435 | * like, here it will generate display message based on actionType |
||
436 | * for particular exception. |
||
437 | * |
||
438 | * @param object $e Exception object |
||
439 | * @param string $actionType the action type, sent by the client |
||
440 | * @param MAPIobject $store store object of message |
||
441 | * @param string $parententryid parent entryid of the message |
||
442 | * @param string $entryid entryid of the message |
||
443 | * @param array $action the action data, sent by the client |
||
444 | */ |
||
445 | #[Override] |
||
446 | public function handleException(&$e, $actionType = null, $store = null, $parententryid = null, $entryid = null, $action = null) { |
||
447 | if (is_null($e->displayMessage)) { |
||
448 | switch ($actionType) { |
||
449 | case 'checknames': |
||
450 | if ($e->getCode() == MAPI_E_NO_ACCESS) { |
||
451 | $e->setDisplayMessage(_('You have insufficient privileges to perform this action.')); |
||
452 | } |
||
453 | else { |
||
454 | $e->setDisplayMessage(_('Could not resolve user.')); |
||
455 | } |
||
456 | break; |
||
457 | } |
||
458 | } |
||
459 | |||
460 | parent::handleException($e, $actionType, $store, $parententryid, $entryid, $action); |
||
461 | } |
||
462 | |||
463 | /** |
||
464 | * This function searches the private contact folders for users and returns an array with data. |
||
465 | * Please note that the returning array must be UTF8. |
||
466 | * |
||
467 | * @param resource $ab The addressbook |
||
468 | * @param resource $ab_dir The addressbook container |
||
469 | * @param string $searchstr The search query, case is ignored |
||
470 | * @param array $rows Array of the found contacts |
||
471 | */ |
||
472 | public function searchContactsFolders($ab, $ab_dir, $searchstr, &$rows) { |
||
495 | } |
||
496 | } |
||
497 | } |
||
498 | } |
||
499 | } |
||
500 |
This check looks for function or method calls that always return null and whose return value is used.
The method
getObject()
can return nothing but null, so it makes no sense to use the return value.The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.