AddressbookListModule   F
last analyzed

Complexity

Total Complexity 89

Size/Duplication

Total Lines 1155
Duplicated Lines 0 %

Importance

Changes 16
Bugs 4 Features 0
Metric Value
eloc 568
c 16
b 4
f 0
dl 0
loc 1155
rs 2
wmc 89

18 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A createNotifiers() 0 2 1
A addFolder() 0 2 1
A createCompanyRestriction() 0 17 2
B GABUsers() 0 301 4
A getAbContactFolders() 0 34 5
A getRestriction() 0 25 4
F createUsersRestriction() 0 196 12
A getPublicContactFolders() 0 18 4
A getHierarchy() 0 10 1
B getUserGroupCompanyRestriction() 0 27 10
C getSharedContactFolders() 0 38 14
D createGroupsRestriction() 0 180 11
A execute() 0 24 6
A getAddressbookHierarchy() 0 12 4
A sorter() 0 4 2
A getSearchRestriction() 0 62 2
B getSortingField() 0 44 7

How to fix   Complexity   

Complex Class

Complex classes like AddressbookListModule often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AddressbookListModule, and based on these observations, apply Extract Interface, too.

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

1140
				$tmp = $this->createUsersRestriction(/** @scrutinizer ignore-type */ $hide_users);
Loading history...
1141
				if ($tmp) {
0 ignored issues
show
introduced by
$tmp is a non-empty array, thus is always true.
Loading history...
1142
					$userRestrictions[] = $tmp;
1143
				}
1144
			}
1145
			if ($hide_groups) {
1146
				$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

1146
				$tmp = $this->createGroupsRestriction(/** @scrutinizer ignore-type */ $hide_groups);
Loading history...
1147
				if ($tmp) {
0 ignored issues
show
introduced by
$tmp is a non-empty array, thus is always true.
Loading history...
1148
					$userRestrictions[] = $tmp;
1149
				}
1150
			}
1151
			if ($hide_companies) {
1152
				$tmp = $this->createCompanyRestriction($hide_companies);
1153
				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...
1154
					$userRestrictions[] = $tmp;
1155
				}
1156
			}
1157
			$userGroupRestriction = [RES_AND, $userRestrictions];
1158
		}
1159
1160
		return $userGroupRestriction;
1161
	}
1162
}
1163