Passed
Push — master ( 2b92c8...a7c928 )
by
unknown
05:24
created

AddressbookListModule::GABUsers()   C

Complexity

Conditions 4
Paths 662

Size

Total Lines 313
Code Lines 208

Duplication

Lines 0
Ratio 0 %

Importance

Changes 12
Bugs 3 Features 0
Metric Value
cc 4
eloc 208
c 12
b 3
f 0
nc 662
nop 2
dl 0
loc 313
rs 5.0422

1 Method

Rating   Name   Duplication   Size   Complexity  
A sorter() 0 4 2

How to fix   Long Method   

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
3
/**
4
 * Addressbook Module.
5
 */
6
class AddressbookListModule extends ListModule {
7
	/**
8
	 * Constructor.
9
	 *
10
	 * @param int   $id   unique id
11
	 * @param array $data list of all actions
12
	 */
13
	public function __construct($id, $data) {
14
		$this->properties = $GLOBALS['properties']->getAddressBookListProperties();
15
16
		parent::__construct($id, $data);
17
	}
18
19
	/**
20
	 * Creates the notifiers for this module,
21
	 * and register them to the Bus.
22
	 */
23
	public function createNotifiers() {
24
		$GLOBALS["bus"]->registerNotifier('addressbooknotifier', ADDRESSBOOK_ENTRYID);
25
	}
26
27
	/**
28
	 * Executes all the actions in the $data variable.
29
	 */
30
	#[Override]
31
	public function execute() {
32
		foreach ($this->data as $actionType => $action) {
33
			if (isset($actionType)) {
34
				try {
35
					$store = $this->getActionStore($action);
36
					$parententryid = $this->getActionParentEntryID($action);
37
					$entryid = $this->getActionEntryID($action);
38
39
					if (isset($action['subActionType']) && $action['subActionType'] != '') {
40
						$subActionType = $action['subActionType'];
41
					}
42
43
					match ($actionType) {
44
						'list' => match ($subActionType) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $subActionType does not seem to be defined for all execution paths leading up to this point.
Loading history...
45
							'hierarchy' => $this->getHierarchy($action),
46
							'globaladdressbook' => $this->GABUsers($action, $subActionType),
47
							default => $this->handleUnknownActionType($actionType),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->handleUnknownActionType($actionType) targeting Module::handleUnknownActionType() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

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.

Loading history...
48
						},
49
						default => $this->handleUnknownActionType($actionType),
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->handleUnknownActionType($actionType) targeting Module::handleUnknownActionType() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

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.

Loading history...
50
					};
51
				}
52
				catch (MAPIException $e) {
53
					$this->processException($e, $actionType, $store, $parententryid, $entryid, $action);
0 ignored issues
show
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...
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...
54
				}
55
			}
56
		}
57
	}
58
59
	/**
60
	 * Function which retrieves the list of system users in Zarafa.
61
	 *
62
	 * @param array  $action     the action data, sent by the client
63
	 * @param string $actionType the action type, sent by the client
64
	 *
65
	 * @return bool true on success or false on failure
66
	 */
67
	public function GABUsers($action, $actionType) {
68
		$searchstring = $action['restriction']['searchstring'] ?? '';
69
		$hide_users = $action['restriction']['hide_users'] ?? false;
70
		$hide_groups = $action['restriction']['hide_groups'] ?? false;
71
		$hide_companies = $action['restriction']['hide_companies'] ?? false;
72
		$items = [];
73
		$data = [];
74
		$this->sort = [];
75
		$map = [];
76
		$map['fileas'] = $this->properties['account'];
77
		$sortingDir = $action["sort"][0]["direction"] ?? 'ASC';
78
		$sortingField = $this->getSortingField($action, $map, $sortingDir);
79
		$folderType = $action['folderType'];
80
		$sharedStore = null;
81
		$isSharedFolder = $folderType === 'sharedcontacts' && isset($action["sharedFolder"]["store_entryid"]);
82
83
		if (($folderType !== 'gab' || ENABLE_FULL_GAB) || !empty($searchstring)) {
84
			$table = null;
85
			$ab = $GLOBALS['mapisession']->getAddressbook(false, true);
86
			$entryid = !empty($action['entryid']) ? hex2bin((string) $action['entryid']) : mapi_ab_getdefaultdir($ab);
87
88
			try {
89
				$dir = mapi_ab_openentry($ab, $entryid);
90
91
				/**
92
				 * @TODO: 'All Address Lists' on IABContainer gives MAPI_E_INVALID_PARAMETER,
93
				 * as it contains subfolders only. When #7344 is fixed, MAPI will return error here,
94
				 * handle it here and return false.
95
				 */
96
				$table = mapi_folder_getcontentstable($dir, MAPI_DEFERRED_ERRORS);
97
			}
98
			catch (MAPIException) {
99
				// for the shared and public contact folders open the store
100
				// and get the contents of the folder
101
				if ($isSharedFolder) {
102
					$sharedStore = $GLOBALS["mapisession"]->openMessageStore(
103
						hex2bin((string) $action["sharedFolder"]["store_entryid"])
104
					);
105
					$sharedContactsFolder = mapi_msgstore_openentry($sharedStore, $entryid);
106
					$table = mapi_folder_getcontentstable($sharedContactsFolder, MAPI_DEFERRED_ERRORS);
107
					$this->properties = $GLOBALS['properties']->getContactProperties();
108
				}
109
			}
110
111
			if ($table) {
112
				$restriction = $this->getRestriction($searchstring, $hide_users, $hide_groups, $hide_companies);
113
				// Only add restriction when it is used
114
				if (!empty($restriction)) {
115
					mapi_table_restrict($table, $restriction, TBL_BATCH);
116
				}
117
				// Only sort when asked for
118
				if (!empty($this->sort)) {
119
					mapi_table_sort($table, $this->sort, TBL_BATCH);
120
				}
121
122
				$rowCount = mapi_table_getrowcount($table);
123
124
				// Try to find hidden users if enabled and searching
125
				$hiddenUserRows = [];
126
				if (defined('ENABLE_RESOLVE_HIDDEN_USERS') && ENABLE_RESOLVE_HIDDEN_USERS && !empty($searchstring) && $folderType === 'gab') {
127
					$hiddenUserRows = $this->resolveHiddenGABUsers($ab, $searchstring, $hide_users, $hide_groups);
128
				}
129
130
				if (is_int(MAX_GAB_RESULTS) && MAX_GAB_RESULTS > 0 && $rowCount > MAX_GAB_RESULTS) {
131
					// Create a response that contains an error message that there are too much results
132
					$data['error'] = ['code' => 'listexceederror', 'max_gab_users' => MAX_GAB_RESULTS];
133
					$rows = mapi_table_queryrows($table, $this->properties, 0, MAX_GAB_RESULTS);
134
					$rowCount = MAX_GAB_RESULTS;
135
				}
136
				else {
137
					$rows = mapi_table_queryallrows($table, $this->properties);
138
				}
139
140
				// Merge hidden user results
141
				if (!empty($hiddenUserRows)) {
142
					$rows = array_merge($rows, $hiddenUserRows);
143
					$rowCount += count($hiddenUserRows);
144
				}
145
146
				for ($i = 0, $len = $rowCount; $i < $len; ++$i) {
147
					// Use array_shift to so we won't double memory usage!
148
					$user_data = array_shift($rows);
149
					$abprovidertype = 0;
150
					$item = [];
151
					$entryid = bin2hex((string) $user_data[$this->properties['entryid']]);
152
					$item['entryid'] = $entryid;
153
					$item['display_name'] = $user_data[$this->properties['display_name']] ?? "";
154
					$item['object_type'] = $user_data[$this->properties['object_type']] ?? "";
155
					$item['display_type'] = $user_data[PR_DISPLAY_TYPE] ?? "";
156
					$item['title'] = $user_data[PR_TITLE] ?? "";
157
					$item['company_name'] = $user_data[PR_COMPANY_NAME] ?? "";
158
159
					// Test whether the GUID in the entryid is from the Contact Provider
160
					if ($GLOBALS['entryid']->hasContactProviderGUID(bin2hex((string) $user_data[$this->properties['entryid']]))) {
161
						// Use the original_display_name property to fill in the fileas column
162
						$item['fileas'] = $user_data[$this->properties['original_display_name']] ?? $item['display_name'];
163
						$item['address_type'] = $user_data[$this->properties['address_type']] ?? 'SMTP';
164
						// necessary to display the proper icon
165
						if ($item['address_type'] === 'MAPIPDL') {
166
							$item['display_type_ex'] = DTE_FLAG_ACL_CAPABLE | DT_MAILUSER | DT_DISTLIST;
167
						}
168
169
						$item['email_address'] = match ($user_data[PR_DISPLAY_TYPE]) {
170
							DT_PRIVATE_DISTLIST => '',
171
							default => $user_data[$this->properties['email_address']],
172
						};
173
					}
174
					elseif ($isSharedFolder && $sharedStore) {
175
						// do not display private items
176
						if (isset($user_data[$this->properties['private']]) && $user_data[$this->properties['private']] === true) {
177
							continue;
178
						}
179
						// do not display items without an email address
180
						// a shared contact is either a single contact item or a distribution list
181
						$isContact = strcasecmp((string) $user_data[$this->properties['message_class']], 'IPM.Contact') === 0;
182
						if ($isContact &&
183
							empty($user_data[$this->properties["email_address_1"]]) &&
184
							empty($user_data[$this->properties["email_address_2"]]) &&
185
							empty($user_data[$this->properties["email_address_3"]])) {
186
							continue;
187
						}
188
						$item['display_type_ex'] = $isContact ? DT_MAILUSER : DT_MAILUSER | DT_PRIVATE_DISTLIST;
189
						$item['display_type'] = $isContact ? DT_MAILUSER : DT_DISTLIST;
190
						$item['fileas'] = $item['display_name'];
191
						$item['surname'] = $user_data[PR_SURNAME] ?? '';
192
						$item['given_name'] = $user_data[$this->properties['given_name']] ?? '';
193
						$item['object_type'] = $user_data[PR_ICON_INDEX] == 512 ? MAPI_MAILUSER : MAPI_DISTLIST;
194
						$item['store_entryid'] = $action["sharedFolder"]["store_entryid"];
195
						$item['is_shared'] = true;
196
						if (!empty($user_data[$this->properties["email_address_type_1"]])) {
197
							$abprovidertype |= 1;
198
						}
199
						if (!empty($user_data[$this->properties["email_address_type_2"]])) {
200
							$abprovidertype |= 2;
201
						}
202
						if (!empty($user_data[$this->properties["email_address_type_3"]])) {
203
							$abprovidertype |= 4;
204
						}
205
206
						switch ($abprovidertype) {
207
							case 1:
208
							case 3:
209
							case 5:
210
							case 7:
211
								$item['entryid'] .= '01';
212
								$item['address_type'] = $user_data[$this->properties["email_address_type_1"]];
213
								$item['email_address'] = $user_data[$this->properties["email_address_1"]];
214
								break;
215
216
							case 2:
217
							case 6:
218
								$item['entryid'] .= '02';
219
								$item['address_type'] = $user_data[$this->properties["email_address_type_2"]];
220
								$item['email_address'] = $user_data[$this->properties["email_address_2"]];
221
								break;
222
223
							case 4:
224
								$item['entryid'] .= '03';
225
								$item['address_type'] = $user_data[$this->properties["email_address_type_3"]];
226
								$item['email_address'] = $user_data[$this->properties["email_address_3"]];
227
								break;
228
						}
229
					}
230
					else {
231
						// If display_type_ex is not set we can overwrite it with display_type
232
						$item['display_type_ex'] = $user_data[PR_DISPLAY_TYPE_EX] ?? $user_data[PR_DISPLAY_TYPE];
233
						$item['fileas'] = $item['display_name'];
234
						$item['mobile_telephone_number'] = $user_data[PR_MOBILE_TELEPHONE_NUMBER] ?? '';
235
						$item['home_telephone_number'] = $user_data[PR_HOME_TELEPHONE_NUMBER] ?? '';
236
						$item['pager_telephone_number'] = $user_data[PR_PAGER_TELEPHONE_NUMBER] ?? '';
237
						$item['surname'] = $user_data[PR_SURNAME] ?? '';
238
						$item['given_name'] = $user_data[$this->properties['given_name']] ?? '';
239
240
						switch ($user_data[PR_DISPLAY_TYPE]) {
241
							case DT_ORGANIZATION:
242
								$item['email_address'] = $user_data[$this->properties['account']];
243
								$item['address_type'] = 'EX';
244
								// The account property is used to fill in the fileas column
245
								$item['fileas'] = $user_data[$this->properties['account']];
246
								break;
247
248
							case DT_DISTLIST:
249
								// The account property is used to fill in the fileas column, private dislist does not have that
250
								$item['fileas'] = $user_data[$this->properties['account']];
251
252
								// no break
253
							case DT_PRIVATE_DISTLIST:
254
								$item['email_address'] = $user_data[$this->properties['email_address']] ?? $user_data[$this->properties['account']];
255
								// FIXME: shouldn't be needed, but atm this gives us an undefined offset error which makes the unittests fail.
256
								if ($item['email_address'] !== 'Everyone' && isset($user_data[$this->properties['smtp_address']])) {
257
									$item['smtp_address'] = $user_data[$this->properties['smtp_address']];
258
								}
259
								$item['address_type'] = 'EX';
260
								break;
261
262
							case DT_MAILUSER:
263
								// The account property is used to fill in the fileas column, remote mailuser does not have that
264
								$item['fileas'] = $user_data[$this->properties['account']];
265
266
								// no break
267
							case DT_REMOTE_MAILUSER:
268
							default:
269
								$item['email_address'] = $user_data[$this->properties['email_address']];
270
								$item['smtp_address'] = $user_data[$this->properties['smtp_address']];
271
272
								$item['address_type'] = $user_data[$this->properties['address_type']] ?? 'SMTP';
273
								$item['department_name'] = $user_data[$this->properties['department_name']] ?? '';
274
								$item['office_telephone_number'] = $user_data[$this->properties['office_telephone_number']] ?? '';
275
								$item['office_location'] = $user_data[$this->properties['office_location']] ?? '';
276
								$item['primary_fax_number'] = $user_data[$this->properties['primary_fax_number']] ?? '';
277
								break;
278
						}
279
					}
280
281
					// Create a nice full_name prop ("Lastname, Firstname Middlename")
282
					if (isset($user_data[$this->properties['surname']])) {
283
						$item['full_name'] = $user_data[$this->properties['surname']];
284
					}
285
					else {
286
						$item['full_name'] = '';
287
					}
288
					if ((isset($user_data[$this->properties['given_name']]) || isset($user_data[$this->properties['middle_name']])) && !empty($item['full_name'])) {
289
						$item['full_name'] .= ', ';
290
					}
291
					if (isset($user_data[$this->properties['given_name']])) {
292
						$item['full_name'] .= $user_data[$this->properties['given_name']];
293
					}
294
					if (isset($user_data[$this->properties['middle_name']])) {
295
						$item['full_name'] .= ' ' . $user_data[$this->properties['middle_name']];
296
					}
297
					if (empty($item['full_name'])) {
298
						$item['full_name'] = $item['display_name'];
299
					}
300
301
					if (!empty($user_data[$this->properties['search_key']])) {
302
						$item['search_key'] = bin2hex((string) $user_data[$this->properties['search_key']]);
303
					}
304
					else {
305
						// contacts folders are not returning search keys, this should be fixed in Gromox
306
						// meanwhile this is a workaround, check ZCP-10814
307
						// if search key is not passed then we will generate it
308
						$email_address = '';
309
						if (!empty($item['smtp_address'])) {
310
							$email_address = $item['smtp_address'];
311
						}
312
						elseif (!empty($item['email_address'])) {
313
							$email_address = $item['email_address'];
314
						}
315
316
						if (!empty($email_address)) {
317
							$item['search_key'] = bin2hex(strtoupper($item['address_type'] . ':' . $email_address)) . '00';
318
						}
319
					}
320
321
					array_push($items, ['props' => $item]);
322
					if ($isSharedFolder && $sharedStore) {
323
						switch ($abprovidertype) {
324
							case 3:
325
								$item['address_type'] = $user_data[$this->properties["email_address_type_2"]];
326
								$item['email_address'] = $user_data[$this->properties["email_address_2"]];
327
								$item['search_key'] = bin2hex(strtoupper($item['address_type'] . ':' . $item['email_address'])) . '00';
328
								$item['entryid'] = $entryid . '02';
329
								array_push($items, ['props' => $item]);
330
								break;
331
332
							case 5:
333
							case 6:
334
								$item['address_type'] = $user_data[$this->properties["email_address_type_3"]];
335
								$item['email_address'] = $user_data[$this->properties["email_address_3"]];
336
								$item['search_key'] = bin2hex(strtoupper($item['address_type'] . ':' . $item['email_address'])) . '00';
337
								$item['entryid'] = $entryid . '03';
338
								array_push($items, ['props' => $item]);
339
								break;
340
341
							case 7:
342
								$item['address_type'] = $user_data[$this->properties["email_address_type_2"]];
343
								$item['email_address'] = $user_data[$this->properties["email_address_2"]];
344
								$item['search_key'] = bin2hex(strtoupper($item['address_type'] . ':' . $item['email_address'])) . '00';
345
								$item['entryid'] = $entryid . '02';
346
								array_push($items, ['props' => $item]);
347
								$item['address_type'] = $user_data[$this->properties["email_address_type_3"]];
348
								$item['email_address'] = $user_data[$this->properties["email_address_3"]];
349
								$item['search_key'] = bin2hex(strtoupper($item['address_type'] . ':' . $item['email_address'])) . '00';
350
								$item['entryid'] = $entryid . '03';
351
								array_push($items, ['props' => $item]);
352
								break;
353
						}
354
					}
355
				}
356
357
				function sorter($direction, $key) {
358
					return fn ($a, $b) => $direction == 'ASC' ?
359
							strcasecmp($a['props'][$key] ?? '', $b['props'][$key] ?? '') :
360
							strcasecmp($b['props'][$key] ?? '', $a['props'][$key] ?? '');
361
				}
362
				usort($items, sorter($sortingDir, $sortingField));
363
364
				// todo: fix paging stuff
365
				$data['page']['start'] = 0;
366
				$data['page']['rowcount'] = $rowCount;
367
				$data['page']['totalrowcount'] = $data['page']['rowcount'];
368
				$data = array_merge($data, ['item' => $items]);
369
			}
370
		}
371
		else {
372
			// Provide clue that full GAB is disabled.
373
			$data = array_merge($data, ['disable_full_gab' => !ENABLE_FULL_GAB]);
374
		}
375
376
		$this->addActionData('list', $data);
377
		$GLOBALS['bus']->addData($this->getResponseData());
378
379
		return true;
380
	}
381
382
	/**
383
	 *	Function will create a restriction based on parameters passed for hiding users.
384
	 *
385
	 * @param array $hide_users list of users that should not be shown
386
	 *
387
	 * @return null|array restriction for hiding provided users
388
	 */
389
	public function createUsersRestriction($hide_users) {
390
		$usersRestriction = null;
391
392
		// When $hide_users is true, then we globally disable
393
		// all users regardless of their subtype. Otherwise if
394
		// $hide_users is set then we start looking which subtype
395
		// is being filtered out.
396
		if ($hide_users === true) {
0 ignored issues
show
introduced by
The condition $hide_users === true is always false.
Loading history...
397
			$usersRestriction = [
398
				RES_AND,
399
				[
400
					[
401
						RES_PROPERTY,
402
						[
403
							RELOP => RELOP_NE,
404
							ULPROPTAG => PR_DISPLAY_TYPE,
405
							VALUE => [
406
								PR_DISPLAY_TYPE => DT_MAILUSER,
407
							],
408
						],
409
					],
410
					[
411
						RES_PROPERTY,
412
						[
413
							RELOP => RELOP_NE,
414
							ULPROPTAG => PR_DISPLAY_TYPE,
415
							VALUE => [
416
								PR_DISPLAY_TYPE => DT_REMOTE_MAILUSER,
417
							],
418
						],
419
					],
420
				],
421
			];
422
		}
423
		elseif ($hide_users) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $hide_users of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
424
			$tempRestrictions = [];
425
426
			// wrap parameters in an array
427
			if (!is_array($hide_users)) {
0 ignored issues
show
introduced by
The condition is_array($hide_users) is always true.
Loading history...
428
				$hide_users = [$hide_users];
429
			}
430
431
			if (in_array('non_security', $hide_users)) {
432
				array_push(
433
					$tempRestrictions,
434
					[
435
						RES_BITMASK,
436
						[
437
							ULTYPE => BMR_EQZ,
438
							ULPROPTAG => PR_DISPLAY_TYPE_EX,
439
							ULMASK => DTE_FLAG_ACL_CAPABLE,
440
						],
441
					]
442
				);
443
			}
444
445
			if (in_array('room', $hide_users)) {
446
				array_push(
447
					$tempRestrictions,
448
					[
449
						RES_PROPERTY,
450
						[
451
							RELOP => RELOP_EQ,
452
							ULPROPTAG => PR_DISPLAY_TYPE_EX,
453
							VALUE => [
454
								PR_DISPLAY_TYPE_EX => DT_ROOM,
455
							],
456
						],
457
					]
458
				);
459
			}
460
461
			if (in_array('equipment', $hide_users)) {
462
				array_push(
463
					$tempRestrictions,
464
					[
465
						RES_PROPERTY,
466
						[
467
							RELOP => RELOP_EQ,
468
							ULPROPTAG => PR_DISPLAY_TYPE_EX,
469
							VALUE => [
470
								PR_DISPLAY_TYPE_EX => DT_EQUIPMENT,
471
							],
472
						],
473
					]
474
				);
475
			}
476
477
			if (in_array('active', $hide_users)) {
478
				array_push(
479
					$tempRestrictions,
480
					[
481
						RES_PROPERTY,
482
						[
483
							RELOP => RELOP_EQ,
484
							ULPROPTAG => PR_DISPLAY_TYPE_EX,
485
							VALUE => [
486
								PR_DISPLAY_TYPE_EX => DTE_FLAG_ACL_CAPABLE,
487
							],
488
						],
489
					]
490
				);
491
			}
492
493
			if (in_array('non_active', $hide_users)) {
494
				array_push(
495
					$tempRestrictions,
496
					[
497
						RES_PROPERTY,
498
						[
499
							RELOP => RELOP_EQ,
500
							ULPROPTAG => PR_DISPLAY_TYPE_EX,
501
							VALUE => [
502
								PR_DISPLAY_TYPE_EX => DT_MAILUSER,
503
							],
504
						],
505
					]
506
				);
507
			}
508
509
			if (in_array('contact', $hide_users)) {
510
				array_push(
511
					$tempRestrictions,
512
					[
513
						RES_PROPERTY,
514
						[
515
							RELOP => RELOP_EQ,
516
							ULPROPTAG => PR_DISPLAY_TYPE_EX,
517
							VALUE => [
518
								PR_DISPLAY_TYPE_EX => DT_REMOTE_MAILUSER,
519
							],
520
						],
521
					]
522
				);
523
			}
524
525
			if (in_array('system', $hide_users)) {
526
				array_push(
527
					$tempRestrictions,
528
					[
529
						RES_CONTENT,
530
						[
531
							FUZZYLEVEL => FL_FULLSTRING | FL_IGNORECASE,
532
							ULPROPTAG => PR_ACCOUNT,
533
							VALUE => [
534
								PR_ACCOUNT => 'SYSTEM',
535
							],
536
						],
537
					]
538
				);
539
			}
540
541
			if (!empty($tempRestrictions)) {
542
				$usersRestriction = [
543
					RES_NOT,
544
					[
545
						[
546
							RES_AND,
547
							[
548
								[
549
									RES_OR,
550
									[
551
										[
552
											RES_PROPERTY,
553
											[
554
												RELOP => RELOP_EQ,
555
												ULPROPTAG => PR_DISPLAY_TYPE,
556
												VALUE => [
557
													PR_DISPLAY_TYPE => DT_MAILUSER,
558
												],
559
											],
560
										],
561
										[
562
											RES_PROPERTY,
563
											[
564
												RELOP => RELOP_EQ,
565
												ULPROPTAG => PR_DISPLAY_TYPE,
566
												VALUE => [
567
													PR_DISPLAY_TYPE => DT_REMOTE_MAILUSER,
568
												],
569
											],
570
										],
571
									],
572
								],
573
								[
574
									RES_OR,
575
									$tempRestrictions, // all user restrictions
576
								],
577
							],
578
						],
579
					],
580
				];
581
			}
582
		}
583
584
		return $usersRestriction;
585
	}
586
587
	/**
588
	 *	Function will create a restriction based on parameters passed for hiding groups.
589
	 *
590
	 * @param array $hide_groups list of groups that should not be shown
591
	 *
592
	 * @return null|array restriction for hiding provided users
593
	 */
594
	public function createGroupsRestriction($hide_groups) {
595
		$groupsRestriction = null;
596
597
		// When $hide_groups is true, then we globally disable
598
		// all groups regardless of their subtype. Otherwise if
599
		// $hide_groups is set then we start looking which subtype
600
		// is being filtered out.
601
		if ($hide_groups === true) {
0 ignored issues
show
introduced by
The condition $hide_groups === true is always false.
Loading history...
602
			$groupsRestriction = [
603
				RES_AND,
604
				[
605
					[
606
						RES_PROPERTY,
607
						[
608
							RELOP => RELOP_NE,
609
							ULPROPTAG => PR_DISPLAY_TYPE,
610
							VALUE => [
611
								PR_DISPLAY_TYPE => DT_DISTLIST,
612
							],
613
						],
614
					],
615
					[
616
						RES_PROPERTY,
617
						[
618
							RELOP => RELOP_NE,
619
							ULPROPTAG => PR_DISPLAY_TYPE,
620
							VALUE => [
621
								PR_DISPLAY_TYPE => DT_PRIVATE_DISTLIST,
622
							],
623
						],
624
					],
625
				],
626
			];
627
		}
628
		elseif ($hide_groups) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $hide_groups of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
629
			$tempRestrictions = [];
630
631
			// wrap parameters in an array
632
			if (!is_array($hide_groups)) {
0 ignored issues
show
introduced by
The condition is_array($hide_groups) is always true.
Loading history...
633
				$hide_groups = [$hide_groups];
634
			}
635
636
			if (in_array('non_security', $hide_groups)) {
637
				array_push(
638
					$tempRestrictions,
639
					[
640
						RES_BITMASK,
641
						[
642
							ULTYPE => BMR_EQZ,
643
							ULPROPTAG => PR_DISPLAY_TYPE_EX,
644
							ULMASK => DTE_FLAG_ACL_CAPABLE,
645
						],
646
					]
647
				);
648
			}
649
650
			if (in_array('normal', $hide_groups)) {
651
				array_push(
652
					$tempRestrictions,
653
					[
654
						RES_PROPERTY,
655
						[
656
							RELOP => RELOP_EQ,
657
							ULPROPTAG => PR_DISPLAY_TYPE_EX,
658
							VALUE => [
659
								PR_DISPLAY_TYPE_EX => DT_DISTLIST,
660
							],
661
						],
662
					]
663
				);
664
			}
665
666
			if (in_array('security', $hide_groups)) {
667
				array_push(
668
					$tempRestrictions,
669
					[
670
						RES_PROPERTY,
671
						[
672
							RELOP => RELOP_EQ,
673
							ULPROPTAG => PR_DISPLAY_TYPE_EX,
674
							VALUE => [
675
								PR_DISPLAY_TYPE_EX => (DT_SEC_DISTLIST | DTE_FLAG_ACL_CAPABLE),
676
							],
677
						],
678
					]
679
				);
680
			}
681
682
			if (in_array('dynamic', $hide_groups)) {
683
				array_push(
684
					$tempRestrictions,
685
					[
686
						RES_PROPERTY,
687
						[
688
							RELOP => RELOP_EQ,
689
							ULPROPTAG => PR_DISPLAY_TYPE_EX,
690
							VALUE => [
691
								PR_DISPLAY_TYPE_EX => DT_AGENT,
692
							],
693
						],
694
					]
695
				);
696
			}
697
698
			if (in_array('distribution_list', $hide_groups)) {
699
				array_push(
700
					$tempRestrictions,
701
					[
702
						RES_PROPERTY,
703
						[
704
							RELOP => RELOP_EQ,
705
							ULPROPTAG => PR_DISPLAY_TYPE,
706
							VALUE => [
707
								PR_DISPLAY_TYPE => DT_PRIVATE_DISTLIST,
708
							],
709
						],
710
					]
711
				);
712
			}
713
714
			if (in_array('everyone', $hide_groups)) {
715
				array_push(
716
					$tempRestrictions,
717
					[
718
						RES_CONTENT,
719
						[
720
							FUZZYLEVEL => FL_FULLSTRING | FL_IGNORECASE,
721
							ULPROPTAG => PR_ACCOUNT,
722
							VALUE => [
723
								PR_ACCOUNT => 'Everyone',
724
							],
725
						],
726
					]
727
				);
728
			}
729
730
			if (!empty($tempRestrictions)) {
731
				$groupsRestriction = [
732
					RES_NOT,
733
					[
734
						[
735
							RES_AND,
736
							[
737
								[
738
									RES_OR,
739
									[
740
										[
741
											RES_PROPERTY,
742
											[
743
												RELOP => RELOP_EQ,
744
												ULPROPTAG => PR_DISPLAY_TYPE,
745
												VALUE => [
746
													PR_DISPLAY_TYPE => DT_DISTLIST,
747
												],
748
											],
749
										],
750
										[
751
											RES_PROPERTY,
752
											[
753
												RELOP => RELOP_EQ,
754
												ULPROPTAG => PR_DISPLAY_TYPE,
755
												VALUE => [
756
													PR_DISPLAY_TYPE => DT_PRIVATE_DISTLIST,
757
												],
758
											],
759
										],
760
									],
761
								],
762
								[
763
									RES_OR,
764
									$tempRestrictions,     // all group restrictions
765
								],
766
							],
767
						],
768
					],
769
				];
770
			}
771
		}
772
773
		return $groupsRestriction;
774
	}
775
776
	/**
777
	 *	Function will create a restriction to get company information.
778
	 *
779
	 * @param bool $hide_companies true/false
780
	 *
781
	 * @return array|bool restriction for getting company info
782
	 */
783
	public function createCompanyRestriction($hide_companies) {
784
		$companyRestriction = false;
785
786
		if ($hide_companies) {
787
			$companyRestriction = [
788
				RES_PROPERTY,
789
				[
790
					RELOP => RELOP_NE,
791
					ULPROPTAG => PR_DISPLAY_TYPE,
792
					VALUE => [
793
						PR_DISPLAY_TYPE => DT_ORGANIZATION,
794
					],
795
				],
796
			];
797
		}
798
799
		return $companyRestriction;
800
	}
801
802
	public function getHierarchy($action) {
803
		// Check if hide_contacts is set in the restriction
804
		$hideContacts = $action['restriction']['hide_contacts'] ?? false;
805
806
		$folders = $this->getAddressbookHierarchy($hideContacts);
807
		$data = ['item' => $folders];
808
		$this->addActionData('list', $data);
809
		$GLOBALS['bus']->addData($this->getResponseData());
810
811
		return true;
812
	}
813
814
	/**
815
	 * Get addressbook hierarchy.
816
	 *
817
	 * This function returns the entire hierarchy of the addressbook, with global addressbooks, and contacts
818
	 * folders.
819
	 *
820
	 * The output array contains an associative array for each found contact folder. Each entry contains
821
	 * "display_name" => Name of the folder, "entryid" => entryid of the folder, "parent_entryid" => parent entryid
822
	 * "storeid" => store entryid, "type" => gab | contacts
823
	 *
824
	 * @param bool $hideContacts
825
	 *
826
	 * @return array Array of associative arrays with addressbook container information
827
	 */
828
	public function getAddressbookHierarchy($hideContacts = false) {
829
		$folders = [];
830
		$this->getAbContactFolders($hideContacts, $folders);
831
		// add shared contact folders if they are enabled
832
		if (ENABLE_SHARED_CONTACT_FOLDERS) {
833
			$this->getSharedContactFolders($folders);
834
		}
835
		if (ENABLE_PUBLIC_CONTACT_FOLDERS && ENABLE_PUBLIC_FOLDERS) {
836
			$this->getPublicContactFolders($folders);
837
		}
838
839
		return $folders;
840
	}
841
842
	/**
843
	 * Gets the contacts' folders from the global addressbook.
844
	 *
845
	 * @param bool  $hideContacts
846
	 * @param array $folders      list of folders
847
	 */
848
	public function getAbContactFolders($hideContacts, &$folders) {
849
		$ab = $GLOBALS["mapisession"]->getAddressbook(false, true);
850
		$dir = mapi_ab_openentry($ab);
851
		$table = mapi_folder_gethierarchytable($dir, MAPI_DEFERRED_ERRORS | CONVENIENT_DEPTH);
852
853
		if ($hideContacts) {
854
			// Restrict on the addressbook provider GUID if the contact folders need to be hidden
855
			$restriction = [RES_PROPERTY,
856
				[
857
					RELOP => RELOP_EQ,
858
					ULPROPTAG => PR_AB_PROVIDER_ID,
859
					VALUE => [
860
						PR_AB_PROVIDER_ID => MUIDECSAB,
861
					],
862
				],
863
			];
864
			mapi_table_restrict($table, $restriction);
865
		}
866
867
		$items = mapi_table_queryallrows($table, [PR_DISPLAY_NAME, PR_ENTRYID, PR_PARENT_ENTRYID, PR_DEPTH, PR_AB_PROVIDER_ID]);
868
		$parent = false;
869
		foreach ($items as $item) {
870
			if ($item[PR_DEPTH] == 0) {
871
				$parent = $item[PR_ENTRYID];
872
			}
873
			$item[PR_PARENT_ENTRYID] = $parent;
874
875
			$this->addFolder($folders, [
876
				"display_name" => $item[PR_DISPLAY_NAME] ?? '',
877
				"entryid" => bin2hex((string) $item[PR_ENTRYID]),
878
				"parent_entryid" => bin2hex((string) $item[PR_PARENT_ENTRYID]),
879
				"depth" => $item[PR_DEPTH],
880
				"type" => $item[PR_AB_PROVIDER_ID] == MUIDECSAB ? "gab" : 'contacts',
881
				"object_type" => MAPI_ABCONT,
882
			]);
883
		}
884
	}
885
886
	/**
887
	 * Gets the shared contacts folders.
888
	 *
889
	 * @param array $folders list of folders
890
	 */
891
	public function getSharedContactFolders(&$folders) {
892
		$otherstores = $GLOBALS['mapisession']->getOtherUserStore();
893
		$mainUserEntryId = $GLOBALS['mapisession']->getUserEntryID();
894
		$shareUserSettings = $GLOBALS["mapisession"]->retrieveOtherUsersFromSettings();
895
		foreach ($otherstores as $sharedEntryId => $sharedStore) {
896
			$sharedStoreProps = mapi_getprops($sharedStore, [PR_MAILBOX_OWNER_NAME, PR_MDB_PROVIDER]);
897
			$sharedUserSetting = [];
898
			if ($sharedStoreProps[PR_MDB_PROVIDER] == ZARAFA_STORE_DELEGATE_GUID) {
899
				$eidObj = $GLOBALS["entryid"]->createMsgStoreEntryIdObj($sharedEntryId);
900
				if (array_key_exists(strtolower((string) $eidObj['MailboxDN']), $shareUserSettings)) {
901
					$sharedUserSetting = $shareUserSettings[strtolower((string) $eidObj['MailboxDN'])];
902
				}
903
				$sharedContactFolders = $GLOBALS["mapisession"]->getContactFoldersForABContactProvider($sharedStore);
904
				for ($i = 0, $len = count($sharedContactFolders); $i < $len; ++$i) {
905
					$folder = mapi_msgstore_openentry($sharedStore, $sharedContactFolders[$i][PR_ENTRYID]);
906
					$folderProps = mapi_getprops($folder, [PR_CONTAINER_CLASS]);
907
					if (isset($folderProps[PR_CONTAINER_CLASS]) && $folderProps[PR_CONTAINER_CLASS] == 'IPF.Contact') {
908
						$grants = mapi_zarafa_getpermissionrules($folder, ACCESS_TYPE_GRANT);
909
						// Do not show contacts' folders in the AB list view for which
910
						// the user has permissions, but hasn't added them to the folder hierarchy.
911
						if (!empty($sharedUserSetting) &&
912
							!isset($sharedUserSetting['all']) &&
913
							!isset($sharedUserSetting['contact']) &&
914
							in_array($mainUserEntryId, array_column($grants, 'userid'))) {
915
							continue;
916
						}
917
						if (isset($sharedUserSetting['all']) ||
918
							isset($sharedUserSetting['contact']) ||
919
							in_array($mainUserEntryId, array_column($grants, 'userid'))) {
920
							$this->addFolder($folders, [
921
								// Postfix display name of every contact folder with respective owner name
922
								// it is mandatory to keep display-name different
923
								"display_name" => $sharedContactFolders[$i][PR_DISPLAY_NAME] . " - " . $sharedStoreProps[PR_MAILBOX_OWNER_NAME],
924
								"entryid" => bin2hex((string) $sharedContactFolders[$i][PR_ENTRYID]),
925
								"parent_entryid" => bin2hex((string) $sharedContactFolders[$i][PR_PARENT_ENTRYID]),
926
								"depth" => $sharedContactFolders[$i][PR_DEPTH],
927
								"type" => 'sharedcontacts',
928
								"object_type" => MAPI_ABCONT,
929
							]);
930
						}
931
					}
932
				}
933
			}
934
		}
935
	}
936
937
	/**
938
	 * Gets the public contacts folders.
939
	 *
940
	 * @param array $folders list of folders
941
	 */
942
	public function getPublicContactFolders(&$folders) {
943
		$publicStore = $GLOBALS['mapisession']->getPublicMessageStore();
944
		$publicStoreProps = mapi_getprops($publicStore, [PR_DISPLAY_NAME]);
945
		$publicContactFolders = $GLOBALS['mapisession']->getContactFoldersForABContactProvider($publicStore);
946
		$knownParents = [];
947
		for ($i = 0, $len = count($publicContactFolders); $i < $len; ++$i) {
948
			$knownParents[] = $publicContactFolders[$i][PR_ENTRYID];
949
			$this->addFolder($folders, [
950
				// Postfix display name of every contact folder with respective owner name
951
				// it is mandatory to keep display-name different
952
				"display_name" => $publicContactFolders[$i][PR_DISPLAY_NAME] . ' - ' . $publicStoreProps[PR_DISPLAY_NAME],
953
				"entryid" => bin2hex((string) $publicContactFolders[$i][PR_ENTRYID]),
954
				"parent_entryid" => bin2hex((string) $publicContactFolders[$i][PR_PARENT_ENTRYID]),
955
				// only indent folders which have a parent folder already in the list
956
				"depth" => $publicContactFolders[$i][PR_DEPTH] > 1 && in_array($publicContactFolders[$i][PR_PARENT_ENTRYID], $knownParents) ?
957
					$publicContactFolders[$i][PR_DEPTH] : 1,
958
				"type" => 'sharedcontacts',
959
				"object_type" => MAPI_ABCONT,
960
			]);
961
		}
962
	}
963
964
	/**
965
	 * Adds folder information to the folder list.
966
	 *
967
	 * @param array $folders list of folders
968
	 * @param array $data    folder information
969
	 */
970
	public function addFolder(&$folders, $data) {
971
		$folders[] = ["props" => $data];
972
	}
973
974
	/**
975
	 * Returns the field which will be used to sort the ab items.
976
	 *
977
	 * @param array  $action
978
	 * @param array  $map
979
	 * @param string $sortingDir
980
	 *
981
	 * @return string
982
	 */
983
	public function getSortingField(&$action, $map, $sortingDir) {
984
		// Rewrite the sort info when sorting on full name as this is a combination of multiple fields
985
		$sortingField = 'full_name';
986
		if (isset($action["sort"]) && is_array($action["sort"]) && count($action["sort"]) === 1 && isset($action["sort"][0]["field"])) {
987
			$sortingField = $action["sort"][0]["field"];
988
			if ($action["sort"][0]["field"] === 'full_name') {
989
				$action["sort"] = [
990
					[
991
						"field" => "surname",
992
						"direction" => $sortingDir,
993
					],
994
					[
995
						"field" => "given_name",
996
						"direction" => $sortingDir,
997
					],
998
					[
999
						"field" => "middle_name",
1000
						"direction" => $sortingDir,
1001
					],
1002
					[
1003
						"field" => "display_name",
1004
						"direction" => $sortingDir,
1005
					],
1006
				];
1007
			}
1008
			elseif ($action["sort"][0]["field"] === 'icon_index') {
1009
				$sortingField = 'display_type_ex';
1010
				$action["sort"] = [
1011
					[
1012
						"field" => 'display_type_ex',
1013
						"direction" => $sortingDir,
1014
					],
1015
					[
1016
						"field" => 'display_name',
1017
						"direction" => $sortingDir,
1018
					],
1019
				];
1020
			}
1021
1022
			// Parse incoming sort order
1023
			$this->parseSortOrder($action, $map, true);
1024
		}
1025
1026
		return $sortingField;
1027
	}
1028
1029
	/**
1030
	 * Returns the restriction for the ab items.
1031
	 *
1032
	 * @param string $searchstring
1033
	 * @param bool   $hide_users
1034
	 * @param bool   $hide_groups
1035
	 * @param bool   $hide_companies
1036
	 *
1037
	 * @return array
1038
	 */
1039
	public function getRestriction($searchstring, $hide_users, $hide_groups, $hide_companies) {
1040
		$restriction = [];
1041
		$userGroupRestriction = $this->getUserGroupCompanyRestriction($hide_users, $hide_groups, $hide_companies);
1042
		$searchRestriction = $this->getSearchRestriction($searchstring);
1043
1044
		if (!empty($searchRestriction) && !empty($userGroupRestriction)) {
1045
			$restriction = [
1046
				RES_AND,
1047
				[
1048
					// restriction for search/alphabet bar
1049
					$searchRestriction,
1050
					// restriction for hiding users/groups
1051
					$userGroupRestriction,
1052
				], ];
1053
		}
1054
		elseif (!empty($searchRestriction)) {
1055
			// restriction for search/alphabet bar
1056
			$restriction = $searchRestriction;
1057
		}
1058
		else {
1059
			// restriction for hiding users/groups
1060
			$restriction = $userGroupRestriction;
1061
		}
1062
1063
		return $restriction;
1064
	}
1065
1066
	/**
1067
	 * Returns the search/alphabet bar restriction for the ab items.
1068
	 *
1069
	 * @param string $searchstring
1070
	 *
1071
	 * @return array
1072
	 */
1073
	public function getSearchRestriction($searchstring) {
1074
		$searchRestriction = [];
1075
1076
		if (!empty($searchstring)) {
1077
			// create restriction for search
1078
			// only return users from who the displayName or the username starts with $searchstring
1079
			// TODO: use PR_ANR for this restriction instead of PR_DISPLAY_NAME and PR_ACCOUNT
1080
			$searchRestriction = [RES_OR,
1081
				[
1082
					// Display name of user from GAB and contacts.
1083
					[
1084
						RES_CONTENT,
1085
						[FUZZYLEVEL => FL_SUBSTRING | FL_IGNORECASE,
1086
							ULPROPTAG => PR_DISPLAY_NAME,
1087
							VALUE => $searchstring,
1088
						],
1089
					],
1090
					// fileas value of user from GAB.
1091
					[
1092
						RES_CONTENT,
1093
						[FUZZYLEVEL => FL_SUBSTRING | FL_IGNORECASE,
1094
							ULPROPTAG => PR_ACCOUNT,
1095
							VALUE => $searchstring,
1096
						],
1097
					],
1098
					// smtp_address of user from GAB.
1099
					[
1100
						RES_CONTENT,
1101
						[FUZZYLEVEL => FL_SUBSTRING | FL_IGNORECASE,
1102
							ULPROPTAG => PR_SMTP_ADDRESS,
1103
							VALUE => $searchstring,
1104
						],
1105
					],
1106
					// email_address of user from GAB and contacts.
1107
					[
1108
						RES_CONTENT,
1109
						[FUZZYLEVEL => FL_SUBSTRING | FL_IGNORECASE,
1110
							ULPROPTAG => PR_EMAIL_ADDRESS,
1111
							VALUE => $searchstring,
1112
						],
1113
					],
1114
					// department of user from GAB.
1115
					[
1116
						RES_CONTENT,
1117
						[FUZZYLEVEL => FL_SUBSTRING | FL_IGNORECASE,
1118
							ULPROPTAG => PR_DEPARTMENT_NAME,
1119
							VALUE => $searchstring,
1120
						],
1121
					],
1122
					// fileas of user from Contacts.
1123
					[
1124
						RES_CONTENT,
1125
						[FUZZYLEVEL => FL_SUBSTRING | FL_IGNORECASE,
1126
							ULPROPTAG => PR_ORIGINAL_DISPLAY_NAME,
1127
							VALUE => $searchstring,
1128
						],
1129
					],
1130
				],
1131
			];
1132
		}
1133
1134
		return $searchRestriction;
1135
	}
1136
1137
	/**
1138
	 * Returns the hiding users/groups restriction for the ab items.
1139
	 *
1140
	 * @param bool $hide_users
1141
	 * @param bool $hide_groups
1142
	 * @param bool $hide_companies
1143
	 *
1144
	 * @return array
1145
	 */
1146
	public function getUserGroupCompanyRestriction($hide_users, $hide_groups, $hide_companies) {
1147
		$userGroupRestriction = [];
1148
1149
		if ($hide_users || $hide_groups || $hide_companies) {
1150
			$userRestrictions = [];
1151
			if ($hide_users) {
1152
				$tmp = $this->createUsersRestriction($hide_users);
0 ignored issues
show
Bug introduced by
$hide_users of type true is incompatible with the type array expected by parameter $hide_users of AddressbookListModule::createUsersRestriction(). ( Ignorable by Annotation )

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

1152
				$tmp = $this->createUsersRestriction(/** @scrutinizer ignore-type */ $hide_users);
Loading history...
1153
				if ($tmp) {
0 ignored issues
show
introduced by
$tmp is a non-empty array, thus is always true.
Loading history...
1154
					$userRestrictions[] = $tmp;
1155
				}
1156
			}
1157
			if ($hide_groups) {
1158
				$tmp = $this->createGroupsRestriction($hide_groups);
0 ignored issues
show
Bug introduced by
$hide_groups of type true is incompatible with the type array expected by parameter $hide_groups of AddressbookListModule::createGroupsRestriction(). ( Ignorable by Annotation )

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

1158
				$tmp = $this->createGroupsRestriction(/** @scrutinizer ignore-type */ $hide_groups);
Loading history...
1159
				if ($tmp) {
0 ignored issues
show
introduced by
$tmp is a non-empty array, thus is always true.
Loading history...
1160
					$userRestrictions[] = $tmp;
1161
				}
1162
			}
1163
			if ($hide_companies) {
1164
				$tmp = $this->createCompanyRestriction($hide_companies);
1165
				if ($tmp) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $tmp of type array<integer,array<mixed,array|mixed>|mixed> is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
introduced by
$tmp is a non-empty array, thus is always true.
Loading history...
1166
					$userRestrictions[] = $tmp;
1167
				}
1168
			}
1169
			$userGroupRestriction = [RES_AND, $userRestrictions];
1170
		}
1171
1172
		return $userGroupRestriction;
1173
	}
1174
1175
	/**
1176
	 * Try to resolve hidden GAB users by exact match when ENABLE_RESOLVE_HIDDEN_USERS is enabled.
1177
	 * This allows finding users that are hidden from the GAB when the search string exactly matches
1178
	 * their display name, email address, or account name.
1179
	 *
1180
	 * @param resource $ab          The addressbook resource
1181
	 * @param string   $searchstr   The search string to match exactly
1182
	 * @param bool     $hide_users  Whether to exclude users from results
1183
	 * @param bool     $hide_groups Whether to exclude groups from results
1184
	 *
1185
	 * @return array Array of user rows in the same format as table query results
1186
	 */
1187
	protected function resolveHiddenGABUsers($ab, $searchstr, $hide_users, $hide_groups) {
1188
		if (!defined('ENABLE_RESOLVE_HIDDEN_USERS') || !ENABLE_RESOLVE_HIDDEN_USERS) {
1189
			return [];
1190
		}
1191
1192
		$searchstr = trim($searchstr);
1193
		if (empty($searchstr)) {
1194
			return [];
1195
		}
1196
1197
		// Try to find exact matches by different properties
1198
		$properties = [PR_DISPLAY_NAME, PR_ACCOUNT];
1199
		if (str_contains($searchstr, '@')) {
1200
			$properties[] = PR_EMAIL_ADDRESS;
1201
			$properties[] = PR_SMTP_ADDRESS;
1202
		}
1203
1204
		foreach ($properties as $property) {
1205
			try {
1206
				$rows = mapi_ab_resolvename($ab, [[$property => $searchstr]], EMS_AB_ADDRESS_LOOKUP);
1207
				if (!empty($rows)) {
1208
					// Filter based on hide_users and hide_groups flags
1209
					$filteredRows = [];
1210
					foreach ($rows as $row) {
1211
						$objectType = $row[PR_OBJECT_TYPE] ?? MAPI_MAILUSER;
1212
						if ($hide_users && $objectType === MAPI_MAILUSER) {
1213
							continue;
1214
						}
1215
						if ($hide_groups && $objectType === MAPI_DISTLIST) {
1216
							continue;
1217
						}
1218
						$filteredRows[] = $row;
1219
					}
1220
					return $filteredRows;
1221
				}
1222
			}
1223
			catch (MAPIException $e) {
1224
				// NOT_FOUND or AMBIGUOUS - try next property
1225
				if ($e->getCode() != MAPI_E_NOT_FOUND && $e->getCode() != MAPI_E_AMBIGUOUS_RECIP) {
1226
					error_log("RESOLVE_HIDDEN_GAB: Error resolving by property " . $property . ": " . $e->getMessage());
1227
				}
1228
			}
1229
		}
1230
1231
		return [];
1232
	}
1233
}
1234