AddressbookListModule::GABUsers()   C
last analyzed

Complexity

Conditions 4
Paths 662

Size

Total Lines 316
Code Lines 210

Duplication

Lines 0
Ratio 0 %

Importance

Changes 13
Bugs 3 Features 0
Metric Value
cc 4
eloc 210
c 13
b 3
f 0
nc 662
nop 2
dl 0
loc 316
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) {
0 ignored issues
show
Bug introduced by
The type MAPIException was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
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
						if (!isset($item['smtp_address']) && $item['address_type'] === 'SMTP') {
174
							$item['smtp_address'] = $item['email_address'];
175
						}
176
					}
177
					elseif ($isSharedFolder && $sharedStore) {
178
						// do not display private items
179
						if (isset($user_data[$this->properties['private']]) && $user_data[$this->properties['private']] === true) {
180
							continue;
181
						}
182
						// do not display items without an email address
183
						// a shared contact is either a single contact item or a distribution list
184
						$isContact = strcasecmp((string) $user_data[$this->properties['message_class']], 'IPM.Contact') === 0;
185
						if ($isContact &&
186
							empty($user_data[$this->properties["email_address_1"]]) &&
187
							empty($user_data[$this->properties["email_address_2"]]) &&
188
							empty($user_data[$this->properties["email_address_3"]])) {
189
							continue;
190
						}
191
						$item['display_type_ex'] = $isContact ? DT_MAILUSER : DT_MAILUSER | DT_PRIVATE_DISTLIST;
192
						$item['display_type'] = $isContact ? DT_MAILUSER : DT_DISTLIST;
193
						$item['fileas'] = $item['display_name'];
194
						$item['surname'] = $user_data[PR_SURNAME] ?? '';
195
						$item['given_name'] = $user_data[$this->properties['given_name']] ?? '';
196
						$item['object_type'] = $user_data[PR_ICON_INDEX] == 512 ? MAPI_MAILUSER : MAPI_DISTLIST;
197
						$item['store_entryid'] = $action["sharedFolder"]["store_entryid"];
198
						$item['is_shared'] = true;
199
						if (!empty($user_data[$this->properties["email_address_type_1"]])) {
200
							$abprovidertype |= 1;
201
						}
202
						if (!empty($user_data[$this->properties["email_address_type_2"]])) {
203
							$abprovidertype |= 2;
204
						}
205
						if (!empty($user_data[$this->properties["email_address_type_3"]])) {
206
							$abprovidertype |= 4;
207
						}
208
209
						switch ($abprovidertype) {
210
							case 1:
211
							case 3:
212
							case 5:
213
							case 7:
214
								$item['entryid'] .= '01';
215
								$item['address_type'] = $user_data[$this->properties["email_address_type_1"]];
216
								$item['email_address'] = $user_data[$this->properties["email_address_1"]];
217
								break;
218
219
							case 2:
220
							case 6:
221
								$item['entryid'] .= '02';
222
								$item['address_type'] = $user_data[$this->properties["email_address_type_2"]];
223
								$item['email_address'] = $user_data[$this->properties["email_address_2"]];
224
								break;
225
226
							case 4:
227
								$item['entryid'] .= '03';
228
								$item['address_type'] = $user_data[$this->properties["email_address_type_3"]];
229
								$item['email_address'] = $user_data[$this->properties["email_address_3"]];
230
								break;
231
						}
232
					}
233
					else {
234
						// If display_type_ex is not set we can overwrite it with display_type
235
						$item['display_type_ex'] = $user_data[PR_DISPLAY_TYPE_EX] ?? $user_data[PR_DISPLAY_TYPE];
236
						$item['fileas'] = $item['display_name'];
237
						$item['mobile_telephone_number'] = $user_data[PR_MOBILE_TELEPHONE_NUMBER] ?? '';
238
						$item['home_telephone_number'] = $user_data[PR_HOME_TELEPHONE_NUMBER] ?? '';
239
						$item['pager_telephone_number'] = $user_data[PR_PAGER_TELEPHONE_NUMBER] ?? '';
240
						$item['surname'] = $user_data[PR_SURNAME] ?? '';
241
						$item['given_name'] = $user_data[$this->properties['given_name']] ?? '';
242
243
						switch ($user_data[PR_DISPLAY_TYPE]) {
244
							case DT_ORGANIZATION:
245
								$item['email_address'] = $user_data[$this->properties['account']];
246
								$item['address_type'] = 'EX';
247
								// The account property is used to fill in the fileas column
248
								$item['fileas'] = $user_data[$this->properties['account']];
249
								break;
250
251
							case DT_DISTLIST:
252
								// The account property is used to fill in the fileas column, private dislist does not have that
253
								$item['fileas'] = $user_data[$this->properties['account']];
254
255
								// no break
256
							case DT_PRIVATE_DISTLIST:
257
								$item['email_address'] = $user_data[$this->properties['email_address']] ?? $user_data[$this->properties['account']];
258
								// FIXME: shouldn't be needed, but atm this gives us an undefined offset error which makes the unittests fail.
259
								if ($item['email_address'] !== 'Everyone' && isset($user_data[$this->properties['smtp_address']])) {
260
									$item['smtp_address'] = $user_data[$this->properties['smtp_address']];
261
								}
262
								$item['address_type'] = 'EX';
263
								break;
264
265
							case DT_MAILUSER:
266
								// The account property is used to fill in the fileas column, remote mailuser does not have that
267
								$item['fileas'] = $user_data[$this->properties['account']];
268
269
								// no break
270
							case DT_REMOTE_MAILUSER:
271
							default:
272
								$item['email_address'] = $user_data[$this->properties['email_address']];
273
								$item['smtp_address'] = $user_data[$this->properties['smtp_address']];
274
275
								$item['address_type'] = $user_data[$this->properties['address_type']] ?? 'SMTP';
276
								$item['department_name'] = $user_data[$this->properties['department_name']] ?? '';
277
								$item['office_telephone_number'] = $user_data[$this->properties['office_telephone_number']] ?? '';
278
								$item['office_location'] = $user_data[$this->properties['office_location']] ?? '';
279
								$item['primary_fax_number'] = $user_data[$this->properties['primary_fax_number']] ?? '';
280
								break;
281
						}
282
					}
283
284
					// Create a nice full_name prop ("Lastname, Firstname Middlename")
285
					if (isset($user_data[$this->properties['surname']])) {
286
						$item['full_name'] = $user_data[$this->properties['surname']];
287
					}
288
					else {
289
						$item['full_name'] = '';
290
					}
291
					if ((isset($user_data[$this->properties['given_name']]) || isset($user_data[$this->properties['middle_name']])) && !empty($item['full_name'])) {
292
						$item['full_name'] .= ', ';
293
					}
294
					if (isset($user_data[$this->properties['given_name']])) {
295
						$item['full_name'] .= $user_data[$this->properties['given_name']];
296
					}
297
					if (isset($user_data[$this->properties['middle_name']])) {
298
						$item['full_name'] .= ' ' . $user_data[$this->properties['middle_name']];
299
					}
300
					if (empty($item['full_name'])) {
301
						$item['full_name'] = $item['display_name'];
302
					}
303
304
					if (!empty($user_data[$this->properties['search_key']])) {
305
						$item['search_key'] = bin2hex((string) $user_data[$this->properties['search_key']]);
306
					}
307
					else {
308
						// contacts folders are not returning search keys, this should be fixed in Gromox
309
						// meanwhile this is a workaround, check ZCP-10814
310
						// if search key is not passed then we will generate it
311
						$email_address = '';
312
						if (!empty($item['smtp_address'])) {
313
							$email_address = $item['smtp_address'];
314
						}
315
						elseif (!empty($item['email_address'])) {
316
							$email_address = $item['email_address'];
317
						}
318
319
						if (!empty($email_address)) {
320
							$item['search_key'] = bin2hex(strtoupper($item['address_type'] . ':' . $email_address)) . '00';
321
						}
322
					}
323
324
					array_push($items, ['props' => $item]);
325
					if ($isSharedFolder && $sharedStore) {
326
						switch ($abprovidertype) {
327
							case 3:
328
								$item['address_type'] = $user_data[$this->properties["email_address_type_2"]];
329
								$item['email_address'] = $user_data[$this->properties["email_address_2"]];
330
								$item['search_key'] = bin2hex(strtoupper($item['address_type'] . ':' . $item['email_address'])) . '00';
331
								$item['entryid'] = $entryid . '02';
332
								array_push($items, ['props' => $item]);
333
								break;
334
335
							case 5:
336
							case 6:
337
								$item['address_type'] = $user_data[$this->properties["email_address_type_3"]];
338
								$item['email_address'] = $user_data[$this->properties["email_address_3"]];
339
								$item['search_key'] = bin2hex(strtoupper($item['address_type'] . ':' . $item['email_address'])) . '00';
340
								$item['entryid'] = $entryid . '03';
341
								array_push($items, ['props' => $item]);
342
								break;
343
344
							case 7:
345
								$item['address_type'] = $user_data[$this->properties["email_address_type_2"]];
346
								$item['email_address'] = $user_data[$this->properties["email_address_2"]];
347
								$item['search_key'] = bin2hex(strtoupper($item['address_type'] . ':' . $item['email_address'])) . '00';
348
								$item['entryid'] = $entryid . '02';
349
								array_push($items, ['props' => $item]);
350
								$item['address_type'] = $user_data[$this->properties["email_address_type_3"]];
351
								$item['email_address'] = $user_data[$this->properties["email_address_3"]];
352
								$item['search_key'] = bin2hex(strtoupper($item['address_type'] . ':' . $item['email_address'])) . '00';
353
								$item['entryid'] = $entryid . '03';
354
								array_push($items, ['props' => $item]);
355
								break;
356
						}
357
					}
358
				}
359
360
				function sorter($direction, $key) {
361
					return fn ($a, $b) => $direction == 'ASC' ?
362
							strcasecmp($a['props'][$key] ?? '', $b['props'][$key] ?? '') :
363
							strcasecmp($b['props'][$key] ?? '', $a['props'][$key] ?? '');
364
				}
365
				usort($items, sorter($sortingDir, $sortingField));
366
367
				// todo: fix paging stuff
368
				$data['page']['start'] = 0;
369
				$data['page']['rowcount'] = $rowCount;
370
				$data['page']['totalrowcount'] = $data['page']['rowcount'];
371
				$data = array_merge($data, ['item' => $items]);
372
			}
373
		}
374
		else {
375
			// Provide clue that full GAB is disabled.
376
			$data = array_merge($data, ['disable_full_gab' => !ENABLE_FULL_GAB]);
377
		}
378
379
		$this->addActionData('list', $data);
380
		$GLOBALS['bus']->addData($this->getResponseData());
381
382
		return true;
383
	}
384
385
	/**
386
	 *	Function will create a restriction based on parameters passed for hiding users.
387
	 *
388
	 * @param array $hide_users list of users that should not be shown
389
	 *
390
	 * @return null|array restriction for hiding provided users
391
	 */
392
	public function createUsersRestriction($hide_users) {
393
		$usersRestriction = null;
394
395
		// When $hide_users is true, then we globally disable
396
		// all users regardless of their subtype. Otherwise if
397
		// $hide_users is set then we start looking which subtype
398
		// is being filtered out.
399
		if ($hide_users === true) {
0 ignored issues
show
introduced by
The condition $hide_users === true is always false.
Loading history...
400
			$usersRestriction = [
401
				RES_AND,
402
				[
403
					[
404
						RES_PROPERTY,
405
						[
406
							RELOP => RELOP_NE,
407
							ULPROPTAG => PR_DISPLAY_TYPE,
408
							VALUE => [
409
								PR_DISPLAY_TYPE => DT_MAILUSER,
410
							],
411
						],
412
					],
413
					[
414
						RES_PROPERTY,
415
						[
416
							RELOP => RELOP_NE,
417
							ULPROPTAG => PR_DISPLAY_TYPE,
418
							VALUE => [
419
								PR_DISPLAY_TYPE => DT_REMOTE_MAILUSER,
420
							],
421
						],
422
					],
423
				],
424
			];
425
		}
426
		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...
427
			$tempRestrictions = [];
428
429
			// wrap parameters in an array
430
			if (!is_array($hide_users)) {
0 ignored issues
show
introduced by
The condition is_array($hide_users) is always true.
Loading history...
431
				$hide_users = [$hide_users];
432
			}
433
434
			if (in_array('non_security', $hide_users)) {
435
				array_push(
436
					$tempRestrictions,
437
					[
438
						RES_BITMASK,
439
						[
440
							ULTYPE => BMR_EQZ,
441
							ULPROPTAG => PR_DISPLAY_TYPE_EX,
442
							ULMASK => DTE_FLAG_ACL_CAPABLE,
443
						],
444
					]
445
				);
446
			}
447
448
			if (in_array('room', $hide_users)) {
449
				array_push(
450
					$tempRestrictions,
451
					[
452
						RES_PROPERTY,
453
						[
454
							RELOP => RELOP_EQ,
455
							ULPROPTAG => PR_DISPLAY_TYPE_EX,
456
							VALUE => [
457
								PR_DISPLAY_TYPE_EX => DT_ROOM,
458
							],
459
						],
460
					]
461
				);
462
			}
463
464
			if (in_array('equipment', $hide_users)) {
465
				array_push(
466
					$tempRestrictions,
467
					[
468
						RES_PROPERTY,
469
						[
470
							RELOP => RELOP_EQ,
471
							ULPROPTAG => PR_DISPLAY_TYPE_EX,
472
							VALUE => [
473
								PR_DISPLAY_TYPE_EX => DT_EQUIPMENT,
474
							],
475
						],
476
					]
477
				);
478
			}
479
480
			if (in_array('active', $hide_users)) {
481
				array_push(
482
					$tempRestrictions,
483
					[
484
						RES_PROPERTY,
485
						[
486
							RELOP => RELOP_EQ,
487
							ULPROPTAG => PR_DISPLAY_TYPE_EX,
488
							VALUE => [
489
								PR_DISPLAY_TYPE_EX => DTE_FLAG_ACL_CAPABLE,
490
							],
491
						],
492
					]
493
				);
494
			}
495
496
			if (in_array('non_active', $hide_users)) {
497
				array_push(
498
					$tempRestrictions,
499
					[
500
						RES_PROPERTY,
501
						[
502
							RELOP => RELOP_EQ,
503
							ULPROPTAG => PR_DISPLAY_TYPE_EX,
504
							VALUE => [
505
								PR_DISPLAY_TYPE_EX => DT_MAILUSER,
506
							],
507
						],
508
					]
509
				);
510
			}
511
512
			if (in_array('contact', $hide_users)) {
513
				array_push(
514
					$tempRestrictions,
515
					[
516
						RES_PROPERTY,
517
						[
518
							RELOP => RELOP_EQ,
519
							ULPROPTAG => PR_DISPLAY_TYPE_EX,
520
							VALUE => [
521
								PR_DISPLAY_TYPE_EX => DT_REMOTE_MAILUSER,
522
							],
523
						],
524
					]
525
				);
526
			}
527
528
			if (in_array('system', $hide_users)) {
529
				array_push(
530
					$tempRestrictions,
531
					[
532
						RES_CONTENT,
533
						[
534
							FUZZYLEVEL => FL_FULLSTRING | FL_IGNORECASE,
535
							ULPROPTAG => PR_ACCOUNT,
536
							VALUE => [
537
								PR_ACCOUNT => 'SYSTEM',
538
							],
539
						],
540
					]
541
				);
542
			}
543
544
			if (!empty($tempRestrictions)) {
545
				$usersRestriction = [
546
					RES_NOT,
547
					[
548
						[
549
							RES_AND,
550
							[
551
								[
552
									RES_OR,
553
									[
554
										[
555
											RES_PROPERTY,
556
											[
557
												RELOP => RELOP_EQ,
558
												ULPROPTAG => PR_DISPLAY_TYPE,
559
												VALUE => [
560
													PR_DISPLAY_TYPE => DT_MAILUSER,
561
												],
562
											],
563
										],
564
										[
565
											RES_PROPERTY,
566
											[
567
												RELOP => RELOP_EQ,
568
												ULPROPTAG => PR_DISPLAY_TYPE,
569
												VALUE => [
570
													PR_DISPLAY_TYPE => DT_REMOTE_MAILUSER,
571
												],
572
											],
573
										],
574
									],
575
								],
576
								[
577
									RES_OR,
578
									$tempRestrictions, // all user restrictions
579
								],
580
							],
581
						],
582
					],
583
				];
584
			}
585
		}
586
587
		return $usersRestriction;
588
	}
589
590
	/**
591
	 *	Function will create a restriction based on parameters passed for hiding groups.
592
	 *
593
	 * @param array $hide_groups list of groups that should not be shown
594
	 *
595
	 * @return null|array restriction for hiding provided users
596
	 */
597
	public function createGroupsRestriction($hide_groups) {
598
		$groupsRestriction = null;
599
600
		// When $hide_groups is true, then we globally disable
601
		// all groups regardless of their subtype. Otherwise if
602
		// $hide_groups is set then we start looking which subtype
603
		// is being filtered out.
604
		if ($hide_groups === true) {
0 ignored issues
show
introduced by
The condition $hide_groups === true is always false.
Loading history...
605
			$groupsRestriction = [
606
				RES_AND,
607
				[
608
					[
609
						RES_PROPERTY,
610
						[
611
							RELOP => RELOP_NE,
612
							ULPROPTAG => PR_DISPLAY_TYPE,
613
							VALUE => [
614
								PR_DISPLAY_TYPE => DT_DISTLIST,
615
							],
616
						],
617
					],
618
					[
619
						RES_PROPERTY,
620
						[
621
							RELOP => RELOP_NE,
622
							ULPROPTAG => PR_DISPLAY_TYPE,
623
							VALUE => [
624
								PR_DISPLAY_TYPE => DT_PRIVATE_DISTLIST,
625
							],
626
						],
627
					],
628
				],
629
			];
630
		}
631
		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...
632
			$tempRestrictions = [];
633
634
			// wrap parameters in an array
635
			if (!is_array($hide_groups)) {
0 ignored issues
show
introduced by
The condition is_array($hide_groups) is always true.
Loading history...
636
				$hide_groups = [$hide_groups];
637
			}
638
639
			if (in_array('non_security', $hide_groups)) {
640
				array_push(
641
					$tempRestrictions,
642
					[
643
						RES_BITMASK,
644
						[
645
							ULTYPE => BMR_EQZ,
646
							ULPROPTAG => PR_DISPLAY_TYPE_EX,
647
							ULMASK => DTE_FLAG_ACL_CAPABLE,
648
						],
649
					]
650
				);
651
			}
652
653
			if (in_array('normal', $hide_groups)) {
654
				array_push(
655
					$tempRestrictions,
656
					[
657
						RES_PROPERTY,
658
						[
659
							RELOP => RELOP_EQ,
660
							ULPROPTAG => PR_DISPLAY_TYPE_EX,
661
							VALUE => [
662
								PR_DISPLAY_TYPE_EX => DT_DISTLIST,
663
							],
664
						],
665
					]
666
				);
667
			}
668
669
			if (in_array('security', $hide_groups)) {
670
				array_push(
671
					$tempRestrictions,
672
					[
673
						RES_PROPERTY,
674
						[
675
							RELOP => RELOP_EQ,
676
							ULPROPTAG => PR_DISPLAY_TYPE_EX,
677
							VALUE => [
678
								PR_DISPLAY_TYPE_EX => (DT_SEC_DISTLIST | DTE_FLAG_ACL_CAPABLE),
679
							],
680
						],
681
					]
682
				);
683
			}
684
685
			if (in_array('dynamic', $hide_groups)) {
686
				array_push(
687
					$tempRestrictions,
688
					[
689
						RES_PROPERTY,
690
						[
691
							RELOP => RELOP_EQ,
692
							ULPROPTAG => PR_DISPLAY_TYPE_EX,
693
							VALUE => [
694
								PR_DISPLAY_TYPE_EX => DT_AGENT,
695
							],
696
						],
697
					]
698
				);
699
			}
700
701
			if (in_array('distribution_list', $hide_groups)) {
702
				array_push(
703
					$tempRestrictions,
704
					[
705
						RES_PROPERTY,
706
						[
707
							RELOP => RELOP_EQ,
708
							ULPROPTAG => PR_DISPLAY_TYPE,
709
							VALUE => [
710
								PR_DISPLAY_TYPE => DT_PRIVATE_DISTLIST,
711
							],
712
						],
713
					]
714
				);
715
			}
716
717
			if (in_array('everyone', $hide_groups)) {
718
				array_push(
719
					$tempRestrictions,
720
					[
721
						RES_CONTENT,
722
						[
723
							FUZZYLEVEL => FL_FULLSTRING | FL_IGNORECASE,
724
							ULPROPTAG => PR_ACCOUNT,
725
							VALUE => [
726
								PR_ACCOUNT => 'Everyone',
727
							],
728
						],
729
					]
730
				);
731
			}
732
733
			if (!empty($tempRestrictions)) {
734
				$groupsRestriction = [
735
					RES_NOT,
736
					[
737
						[
738
							RES_AND,
739
							[
740
								[
741
									RES_OR,
742
									[
743
										[
744
											RES_PROPERTY,
745
											[
746
												RELOP => RELOP_EQ,
747
												ULPROPTAG => PR_DISPLAY_TYPE,
748
												VALUE => [
749
													PR_DISPLAY_TYPE => DT_DISTLIST,
750
												],
751
											],
752
										],
753
										[
754
											RES_PROPERTY,
755
											[
756
												RELOP => RELOP_EQ,
757
												ULPROPTAG => PR_DISPLAY_TYPE,
758
												VALUE => [
759
													PR_DISPLAY_TYPE => DT_PRIVATE_DISTLIST,
760
												],
761
											],
762
										],
763
									],
764
								],
765
								[
766
									RES_OR,
767
									$tempRestrictions,     // all group restrictions
768
								],
769
							],
770
						],
771
					],
772
				];
773
			}
774
		}
775
776
		return $groupsRestriction;
777
	}
778
779
	/**
780
	 *	Function will create a restriction to get company information.
781
	 *
782
	 * @param bool $hide_companies true/false
783
	 *
784
	 * @return array|bool restriction for getting company info
785
	 */
786
	public function createCompanyRestriction($hide_companies) {
787
		$companyRestriction = false;
788
789
		if ($hide_companies) {
790
			$companyRestriction = [
791
				RES_PROPERTY,
792
				[
793
					RELOP => RELOP_NE,
794
					ULPROPTAG => PR_DISPLAY_TYPE,
795
					VALUE => [
796
						PR_DISPLAY_TYPE => DT_ORGANIZATION,
797
					],
798
				],
799
			];
800
		}
801
802
		return $companyRestriction;
803
	}
804
805
	public function getHierarchy($action) {
806
		// Check if hide_contacts is set in the restriction
807
		$hideContacts = $action['restriction']['hide_contacts'] ?? false;
808
809
		$folders = $this->getAddressbookHierarchy($hideContacts);
810
		$data = ['item' => $folders];
811
		$this->addActionData('list', $data);
812
		$GLOBALS['bus']->addData($this->getResponseData());
813
814
		return true;
815
	}
816
817
	/**
818
	 * Get addressbook hierarchy.
819
	 *
820
	 * This function returns the entire hierarchy of the addressbook, with global addressbooks, and contacts
821
	 * folders.
822
	 *
823
	 * The output array contains an associative array for each found contact folder. Each entry contains
824
	 * "display_name" => Name of the folder, "entryid" => entryid of the folder, "parent_entryid" => parent entryid
825
	 * "storeid" => store entryid, "type" => gab | contacts
826
	 *
827
	 * @param bool $hideContacts
828
	 *
829
	 * @return array Array of associative arrays with addressbook container information
830
	 */
831
	public function getAddressbookHierarchy($hideContacts = false) {
832
		$folders = [];
833
		$this->getAbContactFolders($hideContacts, $folders);
834
		// add shared contact folders if they are enabled
835
		if (ENABLE_SHARED_CONTACT_FOLDERS) {
836
			$this->getSharedContactFolders($folders);
837
		}
838
		if (ENABLE_PUBLIC_CONTACT_FOLDERS && ENABLE_PUBLIC_FOLDERS) {
839
			$this->getPublicContactFolders($folders);
840
		}
841
842
		return $folders;
843
	}
844
845
	/**
846
	 * Gets the contacts' folders from the global addressbook.
847
	 *
848
	 * @param bool  $hideContacts
849
	 * @param array $folders      list of folders
850
	 */
851
	public function getAbContactFolders($hideContacts, &$folders) {
852
		$ab = $GLOBALS["mapisession"]->getAddressbook(false, true);
853
		$dir = mapi_ab_openentry($ab);
854
		$table = mapi_folder_gethierarchytable($dir, MAPI_DEFERRED_ERRORS | CONVENIENT_DEPTH);
855
856
		if ($hideContacts) {
857
			// Restrict on the addressbook provider GUID if the contact folders need to be hidden
858
			$restriction = [RES_PROPERTY,
859
				[
860
					RELOP => RELOP_EQ,
861
					ULPROPTAG => PR_AB_PROVIDER_ID,
862
					VALUE => [
863
						PR_AB_PROVIDER_ID => MUIDECSAB,
864
					],
865
				],
866
			];
867
			mapi_table_restrict($table, $restriction);
868
		}
869
870
		$items = mapi_table_queryallrows($table, [PR_DISPLAY_NAME, PR_ENTRYID, PR_PARENT_ENTRYID, PR_DEPTH, PR_AB_PROVIDER_ID]);
871
		$parent = false;
872
		foreach ($items as $item) {
873
			if ($item[PR_DEPTH] == 0) {
874
				$parent = $item[PR_ENTRYID];
875
			}
876
			$item[PR_PARENT_ENTRYID] = $parent;
877
878
			$this->addFolder($folders, [
879
				"display_name" => $item[PR_DISPLAY_NAME] ?? '',
880
				"entryid" => bin2hex((string) $item[PR_ENTRYID]),
881
				"parent_entryid" => bin2hex((string) $item[PR_PARENT_ENTRYID]),
882
				"depth" => $item[PR_DEPTH],
883
				"type" => $item[PR_AB_PROVIDER_ID] == MUIDECSAB ? "gab" : 'contacts',
884
				"object_type" => MAPI_ABCONT,
885
			]);
886
		}
887
	}
888
889
	/**
890
	 * Gets the shared contacts folders.
891
	 *
892
	 * @param array $folders list of folders
893
	 */
894
	public function getSharedContactFolders(&$folders) {
895
		$otherstores = $GLOBALS['mapisession']->getOtherUserStore();
896
		$mainUserEntryId = $GLOBALS['mapisession']->getUserEntryID();
897
		$shareUserSettings = $GLOBALS["mapisession"]->retrieveOtherUsersFromSettings();
898
		foreach ($otherstores as $sharedEntryId => $sharedStore) {
899
			$sharedStoreProps = mapi_getprops($sharedStore, [PR_MAILBOX_OWNER_NAME, PR_MDB_PROVIDER]);
900
			$sharedUserSetting = [];
901
			if ($sharedStoreProps[PR_MDB_PROVIDER] == ZARAFA_STORE_DELEGATE_GUID) {
902
				$eidObj = $GLOBALS["entryid"]->createMsgStoreEntryIdObj($sharedEntryId);
903
				if (array_key_exists(strtolower((string) $eidObj['MailboxDN']), $shareUserSettings)) {
904
					$sharedUserSetting = $shareUserSettings[strtolower((string) $eidObj['MailboxDN'])];
905
				}
906
				$sharedContactFolders = $GLOBALS["mapisession"]->getContactFoldersForABContactProvider($sharedStore);
907
				for ($i = 0, $len = count($sharedContactFolders); $i < $len; ++$i) {
908
					$folder = mapi_msgstore_openentry($sharedStore, $sharedContactFolders[$i][PR_ENTRYID]);
909
					$folderProps = mapi_getprops($folder, [PR_CONTAINER_CLASS]);
910
					if (isset($folderProps[PR_CONTAINER_CLASS]) && $folderProps[PR_CONTAINER_CLASS] == 'IPF.Contact') {
911
						$grants = mapi_zarafa_getpermissionrules($folder, ACCESS_TYPE_GRANT);
912
						// Do not show contacts' folders in the AB list view for which
913
						// the user has permissions, but hasn't added them to the folder hierarchy.
914
						if (!empty($sharedUserSetting) &&
915
							!isset($sharedUserSetting['all']) &&
916
							!isset($sharedUserSetting['contact']) &&
917
							in_array($mainUserEntryId, array_column($grants, 'userid'))) {
918
							continue;
919
						}
920
						if (isset($sharedUserSetting['all']) ||
921
							isset($sharedUserSetting['contact']) ||
922
							in_array($mainUserEntryId, array_column($grants, 'userid'))) {
923
							$this->addFolder($folders, [
924
								// Postfix display name of every contact folder with respective owner name
925
								// it is mandatory to keep display-name different
926
								"display_name" => $sharedContactFolders[$i][PR_DISPLAY_NAME] . " - " . $sharedStoreProps[PR_MAILBOX_OWNER_NAME],
927
								"entryid" => bin2hex((string) $sharedContactFolders[$i][PR_ENTRYID]),
928
								"parent_entryid" => bin2hex((string) $sharedContactFolders[$i][PR_PARENT_ENTRYID]),
929
								"depth" => $sharedContactFolders[$i][PR_DEPTH],
930
								"type" => 'sharedcontacts',
931
								"object_type" => MAPI_ABCONT,
932
							]);
933
						}
934
					}
935
				}
936
			}
937
		}
938
	}
939
940
	/**
941
	 * Gets the public contacts folders.
942
	 *
943
	 * @param array $folders list of folders
944
	 */
945
	public function getPublicContactFolders(&$folders) {
946
		$publicStore = $GLOBALS['mapisession']->getPublicMessageStore();
947
		$publicStoreProps = mapi_getprops($publicStore, [PR_DISPLAY_NAME]);
948
		$publicContactFolders = $GLOBALS['mapisession']->getContactFoldersForABContactProvider($publicStore);
949
		$knownParents = [];
950
		for ($i = 0, $len = count($publicContactFolders); $i < $len; ++$i) {
951
			$knownParents[] = $publicContactFolders[$i][PR_ENTRYID];
952
			$this->addFolder($folders, [
953
				// Postfix display name of every contact folder with respective owner name
954
				// it is mandatory to keep display-name different
955
				"display_name" => $publicContactFolders[$i][PR_DISPLAY_NAME] . ' - ' . $publicStoreProps[PR_DISPLAY_NAME],
956
				"entryid" => bin2hex((string) $publicContactFolders[$i][PR_ENTRYID]),
957
				"parent_entryid" => bin2hex((string) $publicContactFolders[$i][PR_PARENT_ENTRYID]),
958
				// only indent folders which have a parent folder already in the list
959
				"depth" => $publicContactFolders[$i][PR_DEPTH] > 1 && in_array($publicContactFolders[$i][PR_PARENT_ENTRYID], $knownParents) ?
960
					$publicContactFolders[$i][PR_DEPTH] : 1,
961
				"type" => 'sharedcontacts',
962
				"object_type" => MAPI_ABCONT,
963
			]);
964
		}
965
	}
966
967
	/**
968
	 * Adds folder information to the folder list.
969
	 *
970
	 * @param array $folders list of folders
971
	 * @param array $data    folder information
972
	 */
973
	public function addFolder(&$folders, $data) {
974
		$folders[] = ["props" => $data];
975
	}
976
977
	/**
978
	 * Returns the field which will be used to sort the ab items.
979
	 *
980
	 * @param array  $action
981
	 * @param array  $map
982
	 * @param string $sortingDir
983
	 *
984
	 * @return string
985
	 */
986
	public function getSortingField(&$action, $map, $sortingDir) {
987
		// Rewrite the sort info when sorting on full name as this is a combination of multiple fields
988
		$sortingField = 'full_name';
989
		if (isset($action["sort"]) && is_array($action["sort"]) && count($action["sort"]) === 1 && isset($action["sort"][0]["field"])) {
990
			$sortingField = $action["sort"][0]["field"];
991
			if ($action["sort"][0]["field"] === 'full_name') {
992
				$action["sort"] = [
993
					[
994
						"field" => "surname",
995
						"direction" => $sortingDir,
996
					],
997
					[
998
						"field" => "given_name",
999
						"direction" => $sortingDir,
1000
					],
1001
					[
1002
						"field" => "middle_name",
1003
						"direction" => $sortingDir,
1004
					],
1005
					[
1006
						"field" => "display_name",
1007
						"direction" => $sortingDir,
1008
					],
1009
				];
1010
			}
1011
			elseif ($action["sort"][0]["field"] === 'icon_index') {
1012
				$sortingField = 'display_type_ex';
1013
				$action["sort"] = [
1014
					[
1015
						"field" => 'display_type_ex',
1016
						"direction" => $sortingDir,
1017
					],
1018
					[
1019
						"field" => 'display_name',
1020
						"direction" => $sortingDir,
1021
					],
1022
				];
1023
			}
1024
1025
			// Parse incoming sort order
1026
			$this->parseSortOrder($action, $map, true);
1027
		}
1028
1029
		return $sortingField;
1030
	}
1031
1032
	/**
1033
	 * Returns the restriction for the ab items.
1034
	 *
1035
	 * @param string $searchstring
1036
	 * @param bool   $hide_users
1037
	 * @param bool   $hide_groups
1038
	 * @param bool   $hide_companies
1039
	 *
1040
	 * @return array
1041
	 */
1042
	public function getRestriction($searchstring, $hide_users, $hide_groups, $hide_companies) {
1043
		$restriction = [];
1044
		$userGroupRestriction = $this->getUserGroupCompanyRestriction($hide_users, $hide_groups, $hide_companies);
1045
		$searchRestriction = $this->getSearchRestriction($searchstring);
1046
1047
		if (!empty($searchRestriction) && !empty($userGroupRestriction)) {
1048
			$restriction = [
1049
				RES_AND,
1050
				[
1051
					// restriction for search/alphabet bar
1052
					$searchRestriction,
1053
					// restriction for hiding users/groups
1054
					$userGroupRestriction,
1055
				], ];
1056
		}
1057
		elseif (!empty($searchRestriction)) {
1058
			// restriction for search/alphabet bar
1059
			$restriction = $searchRestriction;
1060
		}
1061
		else {
1062
			// restriction for hiding users/groups
1063
			$restriction = $userGroupRestriction;
1064
		}
1065
1066
		return $restriction;
1067
	}
1068
1069
	/**
1070
	 * Returns the search/alphabet bar restriction for the ab items.
1071
	 *
1072
	 * @param string $searchstring
1073
	 *
1074
	 * @return array
1075
	 */
1076
	public function getSearchRestriction($searchstring) {
1077
		$searchRestriction = [];
1078
1079
		if (!empty($searchstring)) {
1080
			// create restriction for search
1081
			// only return users from who the displayName or the username starts with $searchstring
1082
			// TODO: use PR_ANR for this restriction instead of PR_DISPLAY_NAME and PR_ACCOUNT
1083
			$searchRestriction = [RES_OR,
1084
				[
1085
					// Display name of user from GAB and contacts.
1086
					[
1087
						RES_CONTENT,
1088
						[FUZZYLEVEL => FL_SUBSTRING | FL_IGNORECASE,
1089
							ULPROPTAG => PR_DISPLAY_NAME,
1090
							VALUE => $searchstring,
1091
						],
1092
					],
1093
					// fileas value of user from GAB.
1094
					[
1095
						RES_CONTENT,
1096
						[FUZZYLEVEL => FL_SUBSTRING | FL_IGNORECASE,
1097
							ULPROPTAG => PR_ACCOUNT,
1098
							VALUE => $searchstring,
1099
						],
1100
					],
1101
					// smtp_address of user from GAB.
1102
					[
1103
						RES_CONTENT,
1104
						[FUZZYLEVEL => FL_SUBSTRING | FL_IGNORECASE,
1105
							ULPROPTAG => PR_SMTP_ADDRESS,
1106
							VALUE => $searchstring,
1107
						],
1108
					],
1109
					// email_address of user from GAB and contacts.
1110
					[
1111
						RES_CONTENT,
1112
						[FUZZYLEVEL => FL_SUBSTRING | FL_IGNORECASE,
1113
							ULPROPTAG => PR_EMAIL_ADDRESS,
1114
							VALUE => $searchstring,
1115
						],
1116
					],
1117
					// department of user from GAB.
1118
					[
1119
						RES_CONTENT,
1120
						[FUZZYLEVEL => FL_SUBSTRING | FL_IGNORECASE,
1121
							ULPROPTAG => PR_DEPARTMENT_NAME,
1122
							VALUE => $searchstring,
1123
						],
1124
					],
1125
					// fileas of user from Contacts.
1126
					[
1127
						RES_CONTENT,
1128
						[FUZZYLEVEL => FL_SUBSTRING | FL_IGNORECASE,
1129
							ULPROPTAG => PR_ORIGINAL_DISPLAY_NAME,
1130
							VALUE => $searchstring,
1131
						],
1132
					],
1133
				],
1134
			];
1135
		}
1136
1137
		return $searchRestriction;
1138
	}
1139
1140
	/**
1141
	 * Returns the hiding users/groups restriction for the ab items.
1142
	 *
1143
	 * @param bool $hide_users
1144
	 * @param bool $hide_groups
1145
	 * @param bool $hide_companies
1146
	 *
1147
	 * @return array
1148
	 */
1149
	public function getUserGroupCompanyRestriction($hide_users, $hide_groups, $hide_companies) {
1150
		$userGroupRestriction = [];
1151
1152
		if ($hide_users || $hide_groups || $hide_companies) {
1153
			$userRestrictions = [];
1154
			if ($hide_users) {
1155
				$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

1155
				$tmp = $this->createUsersRestriction(/** @scrutinizer ignore-type */ $hide_users);
Loading history...
1156
				if ($tmp) {
0 ignored issues
show
introduced by
$tmp is a non-empty array, thus is always true.
Loading history...
1157
					$userRestrictions[] = $tmp;
1158
				}
1159
			}
1160
			if ($hide_groups) {
1161
				$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

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