Passed
Push — master ( 4d7880...348e1a )
by
unknown
06:02 queued 27s
created

AddressbookListModule::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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