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

MAPISession::getUser()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 9
rs 10
c 0
b 0
f 0
1
<?php
2
3
require_once BASE_PATH . 'server/includes/core/class.properties.php';
4
5
/**
6
 * MAPI session handling.
7
 *
8
 * This class handles MAPI authentication and stores
9
 */
10
class MAPISession {
11
	/**
12
	 * @var resource This holds the MAPI Session
13
	 */
14
	private $session;
15
16
	/**
17
	 * @var resource This can hold the addressbook resource
18
	 */
19
	private $ab;
20
21
	/**
22
	 * @var array List with all the currently opened stores
23
	 */
24
	private $stores;
25
26
	/**
27
	 * @var string The entryid (binary) of the default store
28
	 */
29
	private $defaultstore;
30
31
	/**
32
	 * @var string The entryid (binary) of the public store
33
	 */
34
	private $publicStore;
35
36
	/**
37
	 * @var array Information about the current session (username/email/password/etc)
38
	 */
39
	private $session_info;
40
41
	/**
42
	 * @var array Mapping username -> entryid for other stores
43
	 */
44
	private $userstores;
45
46
	/**
47
	 * @var array Cache for userentryid -> archive properties
48
	 */
49
	private $archivePropsCache;
50
51
	/**
52
	 * @var int Makes sure retrieveUserData is called only once
53
	 */
54
	private $userDataRetrieved;
55
56
	public function __construct() {
57
		$this->stores = [];
58
		$this->defaultstore = 0;
59
		$this->publicStore = 0;
60
		$this->session = false;
0 ignored issues
show
Documentation Bug introduced by
It seems like false of type false is incompatible with the declared type resource of property $session.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
61
		$this->ab = false;
0 ignored issues
show
Documentation Bug introduced by
It seems like false of type false is incompatible with the declared type resource of property $ab.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
62
		$this->userstores = [];
63
		$this->archivePropsCache = [];
64
		$this->userDataRetrieved = false;
0 ignored issues
show
Documentation Bug introduced by
The property $userDataRetrieved was declared of type integer, but false is of type false. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
65
	}
66
67
	/**
68
	 * Logon to via php_mapi extension.
69
	 *
70
	 * Logs on to Gromox with the specified username and password. If the server is not specified,
71
	 * it will logon to the local server.
72
	 *
73
	 * @param string $username     the username of the user
74
	 * @param string $password     the password of the user
75
	 * @param string $server       the server address
76
	 * @param string $sslcert_file the optional ssl certificate file
77
	 * @param string $sslcert_pass the optional ssl certificate password
78
	 * @param string $flags        the optional logon flags
79
	 *
80
	 * @return int 0 on no error, otherwise a MAPI error code
81
	 */
82
	public function logon($username = '', $password = '', $server = DEFAULT_SERVER, $sslcert_file = '', $sslcert_pass = '', $flags = 0) {
83
		$result = NOERROR;
84
		$username = (string) $username;
85
		$password = (string) $password;
86
		$flags |= 1; // Always disable notifications
87
88
		try {
89
			$webapp_version = 'WebApp-' . trim(file_get_contents(BASE_PATH . 'version'));
90
			$browser_version = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
91
			$this->session = mapi_logon_zarafa(
92
				$username,
93
				$password,
94
				$server,
95
				$sslcert_file,
96
				$sslcert_pass,
97
				$flags,
98
				$webapp_version,
99
				$browser_version
100
			);
101
			if ($this->session !== false) {
102
				$this->session_info["username"] = $username;
103
			}
104
		}
105
		catch (MAPIException $e) {
106
			$result = $e->getCode();
107
		}
108
109
		return $result;
110
	}
111
112
	/**
113
	 * Logons to gromox using the access token.
114
	 *
115
	 * @param mixed $email the username/email of the user
116
	 * @param mixed $token the access token
117
	 *
118
	 * @return int 0 on no error, otherwise a MAPI error code
119
	 */
120
	public function logon_token($email = null, $token = null) {
121
		$result = NOERROR;
122
		$email = (string) $email;
123
		$token = (string) $token;
124
125
		try {
126
			$this->session = mapi_logon_token($token);
127
			if ($this->session !== false) {
128
				$this->session_info["username"] = $email;
129
			}
130
		}
131
		catch (MAPIException $e) {
132
			$result = $e->getCode();
133
		}
134
135
		return $result;
136
	}
137
138
	/**
139
	 * Get the user MAPI Object.
140
	 *
141
	 * @param string $userEntryid The user entryid which is going to open. default is false.
142
	 *
143
	 * @return object an user MAPI object
144
	 */
145
	public function getUser($userEntryid = false) {
146
		if ($userEntryid === false) {
147
			// get user entryid
148
			$store_props = mapi_getprops($this->getDefaultMessageStore(), [PR_USER_ENTRYID]);
149
			$userEntryid = $store_props[PR_USER_ENTRYID];
150
		}
151
152
		// open the user entry
153
		return mapi_ab_openentry($this->getAddressbook(true), $userEntryid);
154
	}
155
156
	/**
157
	 * Get logged-in user information.
158
	 *
159
	 * This function populates the 'session_info' property of this class with the following information:
160
	 * - userentryid: the MAPI entryid of the current user
161
	 * - fullname: the fullname of the current user
162
	 * - emailaddress: the email address of the current user
163
	 *
164
	 * The function only populates the information once, subsequent calls will return without error and without
165
	 * doing anything.
166
	 *
167
	 * @return array Array of information about the currently logged-on user
168
	 */
169
	public function retrieveUserData() {
170
		if ($this->userDataRetrieved) {
171
			return;
172
		}
173
174
		$result = NOERROR;
175
176
		try {
177
			$store_props = mapi_getprops($this->getDefaultMessageStore(), [PR_USER_ENTRYID]);
178
			$user = $this->getUser($store_props[PR_USER_ENTRYID]);
179
180
			// receive userdata
181
			$user_props = [ PR_ASSISTANT, PR_ASSISTANT_TELEPHONE_NUMBER, PR_BUSINESS2_TELEPHONE_NUMBER, PR_BUSINESS_TELEPHONE_NUMBER,
182
				PR_COMPANY_NAME, PR_COUNTRY, PR_DEPARTMENT_NAME, PR_DISPLAY_NAME,
183
				PR_EMAIL_ADDRESS, PR_EMS_AB_THUMBNAIL_PHOTO, PR_GIVEN_NAME, PR_HOME2_TELEPHONE_NUMBER,
184
				PR_STREET_ADDRESS, PR_HOME_TELEPHONE_NUMBER, PR_INITIALS, PR_LOCALITY,
185
				PR_MOBILE_TELEPHONE_NUMBER, PR_OFFICE_LOCATION, PR_PAGER_TELEPHONE_NUMBER, PR_POSTAL_CODE,
186
				PR_PRIMARY_FAX_NUMBER, PR_PRIMARY_TELEPHONE_NUMBER, PR_SEARCH_KEY, PR_SMTP_ADDRESS,
187
				PR_STATE_OR_PROVINCE, PR_SURNAME, PR_TITLE ];
188
189
			$user_props = mapi_getprops($user, $user_props);
190
191
			if (is_array($user_props) && isset($user_props[PR_DISPLAY_NAME], $user_props[PR_SMTP_ADDRESS])) {
192
				$this->session_info["userentryid"] = $store_props[PR_USER_ENTRYID];
193
				$this->session_info["fullname"] = $user_props[PR_DISPLAY_NAME];
194
				$this->session_info["smtpaddress"] = $user_props[PR_SMTP_ADDRESS];
195
				$this->session_info["emailaddress"] = $user_props[PR_EMAIL_ADDRESS];
196
				$this->session_info["searchkey"] = $user_props[PR_SEARCH_KEY];
197
				$this->session_info["userimage"] = isset($user_props[PR_EMS_AB_THUMBNAIL_PHOTO]) ? "data:image/jpeg;base64," . base64_encode($user_props[PR_EMS_AB_THUMBNAIL_PHOTO]) : "";
198
199
				$this->session_info["given_name"] = isset($user_props[PR_GIVEN_NAME]) ? $user_props[PR_GIVEN_NAME] : '';
200
				$this->session_info["initials"] = isset($user_props[PR_INITIALS]) ? $user_props[PR_INITIALS] : '';
201
				$this->session_info["surname"] = isset($user_props[PR_SURNAME]) ? $user_props[PR_SURNAME] : '';
202
				$this->session_info["street_address"] = isset($user_props[PR_STREET_ADDRESS]) ? $user_props[PR_STREET_ADDRESS] : '';
203
				$this->session_info["locality"] = isset($user_props[PR_LOCALITY]) ? $user_props[PR_LOCALITY] : '';
204
				$this->session_info["state_or_province"] = isset($user_props[PR_STATE_OR_PROVINCE]) ? $user_props[PR_STATE_OR_PROVINCE] : '';
205
				$this->session_info["postal_code"] = isset($user_props[PR_POSTAL_CODE]) ? $user_props[PR_POSTAL_CODE] : '';
206
				$this->session_info["country"] = isset($user_props[PR_COUNTRY]) ? $user_props[PR_COUNTRY] : '';
207
				$this->session_info["title"] = isset($user_props[PR_TITLE]) ? $user_props[PR_TITLE] : '';
208
				$this->session_info["company_name"] = isset($user_props[PR_COMPANY_NAME]) ? $user_props[PR_COMPANY_NAME] : '';
209
				$this->session_info["department_name"] = isset($user_props[PR_DEPARTMENT_NAME]) ? $user_props[PR_DEPARTMENT_NAME] : '';
210
				$this->session_info["office_location"] = isset($user_props[PR_OFFICE_LOCATION]) ? $user_props[PR_OFFICE_LOCATION] : '';
211
				$this->session_info["assistant"] = isset($user_props[PR_ASSISTANT]) ? $user_props[PR_ASSISTANT] : '';
212
				$this->session_info["assistant_telephone_number"] = isset($user_props[PR_ASSISTANT_TELEPHONE_NUMBER]) ? $user_props[PR_ASSISTANT_TELEPHONE_NUMBER] : '';
213
				$this->session_info["office_telephone_number"] = isset($user_props[PR_PRIMARY_TELEPHONE_NUMBER]) ? $user_props[PR_PRIMARY_TELEPHONE_NUMBER] : '';
214
				$this->session_info["business_telephone_number"] = isset($user_props[PR_BUSINESS_TELEPHONE_NUMBER]) ? $user_props[PR_BUSINESS_TELEPHONE_NUMBER] : '';
215
				$this->session_info["business2_telephone_number"] = isset($user_props[PR_BUSINESS2_TELEPHONE_NUMBER]) ? $user_props[PR_BUSINESS2_TELEPHONE_NUMBER] : '';
216
				$this->session_info["primary_fax_number"] = isset($user_props[PR_PRIMARY_FAX_NUMBER]) ? $user_props[PR_PRIMARY_FAX_NUMBER] : '';
217
				$this->session_info["home_telephone_number"] = isset($user_props[PR_HOME_TELEPHONE_NUMBER]) ? $user_props[PR_HOME_TELEPHONE_NUMBER] : '';
218
				$this->session_info["home2_telephone_number"] = isset($user_props[PR_HOME2_TELEPHONE_NUMBER]) ? $user_props[PR_HOME2_TELEPHONE_NUMBER] : '';
219
				$this->session_info["mobile_telephone_number"] = isset($user_props[PR_MOBILE_TELEPHONE_NUMBER]) ? $user_props[PR_MOBILE_TELEPHONE_NUMBER] : '';
220
				$this->session_info["pager_telephone_number"] = isset($user_props[PR_PAGER_TELEPHONE_NUMBER]) ? $user_props[PR_PAGER_TELEPHONE_NUMBER] : '';
221
			}
222
223
			$this->userDataRetrieved = true;
0 ignored issues
show
Documentation Bug introduced by
The property $userDataRetrieved was declared of type integer, but true is of type true. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
224
		}
225
		catch (MAPIException $e) {
226
			$result = $e->getCode();
227
		}
228
229
		return $result;
230
	}
231
232
	/**
233
	 * Get MAPI session object.
234
	 *
235
	 * @return mapisession Current MAPI session
236
	 */
237
	public function getSession() {
238
		return $this->session;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->session returns the type resource which is incompatible with the documented return type mapisession.
Loading history...
239
	}
240
241
	/**
242
	 * Set MAPI session object.
243
	 *
244
	 * @param mapisession The MAPI session
245
	 * @param mixed $session
246
	 */
247
	public function setSession($session) {
248
		$this->session = $session;
249
	}
250
251
	/**
252
	 * Get MAPI addressbook object.
253
	 *
254
	 * @param bool $providerless When set to true it will return an addressbook resource
255
	 *                           without any Contact Provider set on it, defaults to false
256
	 * @param bool $loadSharedContactsProvider when set to true it denotes that shared folders are
257
	 *                                         required to be configured to load the contacts from
258
	 *
259
	 * @return mixed An addressbook object to be used with mapi_ab_* or an error code
260
	 */
261
	public function getAddressbook($providerless = false, $loadSharedContactsProvider = false) {
262
		if ($providerless) {
263
			try {
264
				return mapi_openaddressbook($this->session);
265
			}
266
			catch (MAPIException $e) {
267
				return $e->getCode();
268
			}
269
		}
270
271
		$result = NOERROR;
272
273
		if ($this->ab === false) {
0 ignored issues
show
introduced by
The condition $this->ab === false is always false.
Loading history...
274
			$this->setupContactProviderAddressbook($loadSharedContactsProvider);
275
		}
276
277
		try {
278
			if ($this->ab === false) {
0 ignored issues
show
introduced by
The condition $this->ab === false is always false.
Loading history...
279
				$this->ab = mapi_openaddressbook($this->session);
280
			}
281
282
			if ($this->ab !== false) {
0 ignored issues
show
introduced by
The condition $this->ab !== false is always true.
Loading history...
283
				$result = $this->ab;
284
			}
285
		}
286
		catch (MAPIException $e) {
287
			$result = $e->getCode();
288
		}
289
290
		return $result;
291
	}
292
293
	/**
294
	 * Get logon status
295
	 * NOTE: This function only exists for backward compatibility with older plugins.
296
	 * 		 Currently the preferred way to check if a user is logged in, is using
297
	 * 		 the isAuthenticated() method of WebAppAuthentication.
298
	 *
299
	 * @return bool true on logged on, false on not logged on
300
	 */
301
	public function isLoggedOn() {
302
		trigger_error("isLoggedOn is deprecated, use WebAppAuthentication::isAuthenticated()", E_USER_NOTICE);
303
304
		return WebAppAuthentication::isAuthenticated();
305
	}
306
307
	/**
308
	 * Get current session id.
309
	 *
310
	 * @deprecated 2.2.0 This function only exists for backward compatibility with
311
	 * 		 older plugins that want to send the session id as a GET parameter with
312
	 * 		 requests that they make to grommunio.php. The script grommunio.php does not
313
	 * 		 expect this parameter anymore, but plugins that are not updated might
314
	 * 		 still call this function.
315
	 *
316
	 * @return string Always empty
317
	 */
318
	public function getSessionID() {
319
		return '';
320
	}
321
322
	/**
323
	 * Get current user entryid.
324
	 *
325
	 * @return string Current user's entryid
326
	 */
327
	public function getUserEntryID() {
328
		$this->retrieveUserData();
329
		return $this->session_info["userentryid"] ?? '';
330
	}
331
332
	/**
333
	 * Get current username.
334
	 *
335
	 * @return string Current user's username (equal to username passed in logon() )
336
	 */
337
	public function getUserName() {
338
		$encryptionStore = EncryptionStore::getInstance();
339
		return $encryptionStore->get('username') ? $encryptionStore->get('username') : '';
340
	}
341
342
	/**
343
	 * Get current user's full name.
344
	 *
345
	 * @return string User's full name
346
	 */
347
	public function getFullName() {
348
		$this->retrieveUserData();
349
		return array_key_exists("fullname", $this->session_info) ? $this->session_info["fullname"] : false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return array_key_exists(...nfo['fullname'] : false could also return false which is incompatible with the documented return type string. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
350
	}
351
352
	/**
353
	 * Get current user's smtp address.
354
	 *
355
	 * @return string User's smtp address
356
	 */
357
	public function getSMTPAddress() {
358
		$this->retrieveUserData();
359
		return array_key_exists("smtpaddress", $this->session_info) ? $this->session_info["smtpaddress"] : false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return array_key_exists(...['smtpaddress'] : false could also return false which is incompatible with the documented return type string. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
360
	}
361
362
	/**
363
	 * Get current user's email address.
364
	 *
365
	 * @return string User's email address
366
	 */
367
	public function getEmailAddress() {
368
		$this->retrieveUserData();
369
		return array_key_exists("emailaddress", $this->session_info) ? $this->session_info["emailaddress"] : false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return array_key_exists(...'emailaddress'] : false could also return false which is incompatible with the documented return type string. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
370
	}
371
372
	/**
373
	 * Get current user's image from the LDAP server.
374
	 *
375
	 * @return string A base64 encoded string (data url)
376
	 */
377
	public function getUserImage() {
378
		$this->retrieveUserData();
379
		return array_key_exists("userimage", $this->session_info) ? $this->session_info["userimage"] : false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return array_key_exists(...fo['userimage'] : false could also return false which is incompatible with the documented return type string. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
380
	}
381
382
	public function setUserImage($user_image) {
383
		if ($this->userDataRetrieved && is_array($this->session_info)) {
384
			$this->session_info["userimage"] = $user_image;
385
		}
386
	}
387
388
	public function getGivenName() {
389
		$this->retrieveUserData();
390
		return array_key_exists("given_name", $this->session_info) ? $this->session_info["given_name"] : false;
391
	}
392
393
	public function getInitials() {
394
		$this->retrieveUserData();
395
		return array_key_exists("initials", $this->session_info) ? $this->session_info["initials"] : false;
396
	}
397
398
	public function getSurname() {
399
		$this->retrieveUserData();
400
		return array_key_exists("surname", $this->session_info) ? $this->session_info["surname"] : false;
401
	}
402
403
	public function getStreetAddress() {
404
		$this->retrieveUserData();
405
		return array_key_exists("street_address", $this->session_info) ? $this->session_info["street_address"] : false;
406
	}
407
408
	public function getLocality() {
409
		$this->retrieveUserData();
410
		return array_key_exists("locality", $this->session_info) ? $this->session_info["locality"] : false;
411
	}
412
413
	public function getStateOrProvince() {
414
		$this->retrieveUserData();
415
		return array_key_exists("state_or_province", $this->session_info) ? $this->session_info["state_or_province"] : false;
416
	}
417
418
	public function getPostalCode() {
419
		$this->retrieveUserData();
420
		return array_key_exists("postal_code", $this->session_info) ? $this->session_info["postal_code"] : false;
421
	}
422
423
	public function getCountry() {
424
		$this->retrieveUserData();
425
		return array_key_exists("country", $this->session_info) ? $this->session_info["country"] : false;
426
	}
427
428
	public function getTitle() {
429
		$this->retrieveUserData();
430
		return array_key_exists("title", $this->session_info) ? $this->session_info["title"] : false;
431
	}
432
433
	public function getCompanyName() {
434
		$this->retrieveUserData();
435
		return array_key_exists("company_name", $this->session_info) ? $this->session_info["company_name"] : false;
436
	}
437
438
	public function getDepartmentName() {
439
		$this->retrieveUserData();
440
		return array_key_exists("department_name", $this->session_info) ? $this->session_info["department_name"] : false;
441
	}
442
443
	public function getOfficeLocation() {
444
		$this->retrieveUserData();
445
		return array_key_exists("office_location", $this->session_info) ? $this->session_info["office_location"] : false;
446
	}
447
448
	public function getAssistant() {
449
		$this->retrieveUserData();
450
		return array_key_exists("assistant", $this->session_info) ? $this->session_info["assistant"] : false;
451
	}
452
453
	public function getAssistantTelephoneNumber() {
454
		$this->retrieveUserData();
455
		return array_key_exists("assistant_telephone_number", $this->session_info) ? $this->session_info["assistant_telephone_number"] : false;
456
	}
457
458
	public function getOfficeTelephoneNumber() {
459
		$this->retrieveUserData();
460
		return array_key_exists("office_telephone_number", $this->session_info) ? $this->session_info["office_telephone_number"] : false;
461
	}
462
463
	public function getBusinessTelephoneNumber() {
464
		$this->retrieveUserData();
465
		return array_key_exists("business_telephone_number", $this->session_info) ? $this->session_info["business_telephone_number"] : false;
466
	}
467
468
	public function getBusiness2TelephoneNumber() {
469
		$this->retrieveUserData();
470
		return array_key_exists("business2_telephone_number", $this->session_info) ? $this->session_info["business2_telephone_number"] : false;
471
	}
472
473
	public function getPrimaryFaxNumber() {
474
		$this->retrieveUserData();
475
		return array_key_exists("primary_fax_number", $this->session_info) ? $this->session_info["primary_fax_number"] : false;
476
	}
477
478
	public function getHomeTelephoneNumber() {
479
		$this->retrieveUserData();
480
		return array_key_exists("home_telephone_number", $this->session_info) ? $this->session_info["home_telephone_number"] : false;
481
	}
482
483
	public function getHome2TelephoneNumber() {
484
		$this->retrieveUserData();
485
		return array_key_exists("home2_telephone_number", $this->session_info) ? $this->session_info["home2_telephone_number"] : false;
486
	}
487
488
	public function getMobileTelephoneNumber() {
489
		$this->retrieveUserData();
490
		return array_key_exists("mobile_telephone_number", $this->session_info) ? $this->session_info["mobile_telephone_number"] : false;
491
	}
492
493
	public function getPagerTelephoneNumber() {
494
		$this->retrieveUserData();
495
		return array_key_exists("pager_telephone_number", $this->session_info) ? $this->session_info["pager_telephone_number"] : false;
496
	}
497
498
	/**
499
	 * Get currently disabled features for the user.
500
	 *
501
	 * @return array an disabled features list
502
	 */
503
	public function getDisabledFeatures() {
504
		$userProps = mapi_getprops($this->getUser(), [PR_EC_DISABLED_FEATURES]);
505
		return isset($userProps[PR_EC_DISABLED_FEATURES]) ? $userProps[PR_EC_DISABLED_FEATURES] : [];
506
	}
507
508
	/**
509
	 * @return bool true if webapp is disabled feature else return false
510
	 */
511
	public function isWebappDisableAsFeature() {
512
		return array_search('webapp', $this->getDisabledFeatures()) !== false;
513
	}
514
515
	/**
516
	 * Magic method to get properties from the session_info. When a method of this class if called
517
	 * and there is no method of this name defined this function will be called
518
	 * It creates getter methods for the properties stored in $session_info using the following syntax:
519
	 * getSomeUserProperty() will look return a property called some_user_property if it exists and
520
	 * throw an exception otherwise.
521
	 *
522
	 * @param string $methodName The name of the method that was called
523
	 * @param array  $arguments  The arguments that were passed in the call
524
	 *
525
	 * @return string The requested property if it exists
526
	 *
527
	 * @throws Exception
528
	 */
529
	public function __call($methodName, $arguments) {
530
		if (!preg_match('/^get(.+)$/', $methodName, $matches)) {
531
			// We don't know this function, so let's throw an error
532
			throw new Exception('Method ' . $methodName . ' does not exist');
533
		}
534
		$this->retrieveUserData();
535
		$propertyName = strtolower(preg_replace('/([^A-Z])([A-Z])/', '$1_$2', $matches[1]));
536
		if (!array_key_exists($propertyName, $this->session_info)) {
537
			// We don't know this function, so let's throw an error
538
			throw new Exception('Method ' . $methodName . ' does not exist ' . $propertyName);
539
		}
540
541
		return $this->session_info[$propertyName];
542
	}
543
544
	/**
545
	 * Returns a hash with information about the user that is logged in.
546
	 *
547
	 * @return array
548
	 */
549
	public function getUserInfo() {
550
		return [
551
			'username' => $this->getUserName(),
552
			'fullname' => $this->getFullName(),
553
			'entryid' => bin2hex($this->getUserEntryid()),
554
			'email_address' => $this->getEmailAddress(),
555
			'smtp_address' => $this->getSMTPAddress(),
556
			'search_key' => bin2hex($this->getSearchKey()),
557
			'user_image' => $this->getUserImage(),
558
			'given_name' => $this->getGivenName(),
559
			'initials' => $this->getInitials(),
560
			'surname' => $this->getSurname(),
561
			'street_address' => $this->getStreetAddress(),
562
			'locality' => $this->getLocality(),
563
			'state_or_province' => $this->getStateOrProvince(),
564
			'postal_code' => $this->getPostalCode(),
565
			'country' => $this->getCountry(),
566
			'title' => $this->getTitle(),
567
			'company_name' => $this->getCompanyName(),
568
			'department_name' => $this->getDepartmentName(),
569
			'office_location' => $this->getOfficeLocation(),
570
			'assistant' => $this->getAssistant(),
571
			'assistant_telephone_number' => $this->getAssistantTelephoneNumber(),
572
			'office_telephone_number' => $this->getOfficeTelephoneNumber(),
573
			'business_telephone_number' => $this->getBusinessTelephoneNumber(),
574
			'business2_telephone_number' => $this->getBusiness2TelephoneNumber(),
575
			'primary_fax_number' => $this->getPrimaryFaxNumber(),
576
			'home_telephone_number' => $this->getHomeTelephoneNumber(),
577
			'home2_telephone_number' => $this->getHome2TelephoneNumber(),
578
			'mobile_telephone_number' => $this->getMobileTelephoneNumber(),
579
			'pager_telephone_number' => $this->getPagerTelephoneNumber(),
580
		];
581
	}
582
583
	/**
584
	 * Get current user's search key.
585
	 *
586
	 * @return string Current user's searchkey
587
	 */
588
	public function getSearchKey() {
589
		$this->retrieveUserData();
590
		return $this->session_info["searchkey"] ?? '';
591
	}
592
593
	/**
594
	 * Get the message stores from the message store table from your session. Standard stores
595
	 * like the default store and the public store are made them easily accessible through the
596
	 * defaultstore and publicStore properties.
597
	 */
598
	public function loadMessageStoresFromSession() {
599
		$storestables = mapi_getmsgstorestable($this->session);
600
		$rows = mapi_table_queryallrows($storestables, [PR_ENTRYID, PR_DEFAULT_STORE, PR_MDB_PROVIDER]);
601
		foreach ($rows as $row) {
602
			if (!$row[PR_ENTRYID]) {
603
				continue;
604
			}
605
606
			if (isset($row[PR_DEFAULT_STORE]) && $row[PR_DEFAULT_STORE] == true) {
607
				$this->defaultstore = $row[PR_ENTRYID];
608
			}
609
			elseif ($row[PR_MDB_PROVIDER] == ZARAFA_STORE_PUBLIC_GUID) {
610
				$this->publicStore = $row[PR_ENTRYID];
611
			}
612
			elseif ($row[PR_MDB_PROVIDER] == ZARAFA_STORE_DELEGATE_GUID) {
613
				$eidObj = $GLOBALS["entryid"]->createMsgStoreEntryIdObj($row[PR_ENTRYID]);
614
				if (isset($eidObj['MailboxDN'])) {
615
					$this->openMessageStore($row[PR_ENTRYID], strtolower($eidObj['MailboxDN']));
616
				}
617
			}
618
		}
619
	}
620
621
	/**
622
	 * Get the current user's default message store.
623
	 *
624
	 * The store is opened only once, subsequent calls will return the previous store object
625
	 *
626
	 * @param bool reopen force re-open
0 ignored issues
show
Bug introduced by
The type reopen 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...
627
	 * @param mixed $reopen
628
	 *
629
	 * @return mapistore User's default message store object
0 ignored issues
show
Bug introduced by
The type mapistore 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...
630
	 */
631
	public function getDefaultMessageStore($reopen = false) {
632
		// Return cached default store if we have one
633
		if (!$reopen && isset($this->defaultstore, $this->stores[$this->defaultstore])) {
634
			return $this->stores[$this->defaultstore];
635
		}
636
637
		$this->loadMessageStoresFromSession();
638
639
		return $this->openMessageStore($this->defaultstore, 'Default store');
640
	}
641
642
	/**
643
	 * The default messagestore entryid.
644
	 *
645
	 * @return string the entryid of the default messagestore
646
	 */
647
	public function getDefaultMessageStoreEntryId() {
648
		if (!isset($this->defaultstore)) {
649
			$this->loadMessageStoresFromSession();
650
		}
651
652
		return bin2hex($this->defaultstore);
653
	}
654
655
	/**
656
	 * Get single store and it's archive store as well if we are openig full store.
657
	 *
658
	 * @param object $store        the store of the user
659
	 * @param array  $storeOptions contains folder_type of which folder to open
660
	 *                             It is mapped to username, If folder_type is 'all' (i.e. Open Entire Inbox)
661
	 *                             then we will open full store and it's archived stores.
662
	 * @param string $username     The username
663
	 *
664
	 * @return array storeArray The array of stores containing user's store and archived stores
665
	 */
666
	public function getSingleMessageStores($store, $storeOptions, $username) {
667
		$storeArray = [$store];
668
		$archivedStores = [];
669
670
		// Get archived stores for user if there's any
671
		if (!empty($username)) {
672
			// Check whether we should open the whole store or just single folders
673
			if (is_array($storeOptions) && isset($storeOptions[$username], $storeOptions[$username]['all'])) {
674
				$archivedStores = $this->getArchivedStores($this->resolveStrictUserName($username));
675
			}
676
		}
677
678
		foreach ($archivedStores as $archivedStore) {
679
			$storeArray[] = $archivedStore;
680
		}
681
682
		return $storeArray;
683
	}
684
685
	/**
686
	 * Get the public message store.
687
	 *
688
	 * The store is opened only once, subsequent calls will return the previous store object
689
	 *
690
	 * @return mapistore Public message store object
691
	 */
692
	public function getPublicMessageStore() {
693
		// Return cached public store if we have one
694
		if (isset($this->publicStore, $this->stores[$this->publicStore])) {
695
			return $this->stores[$this->publicStore];
696
		}
697
698
		$this->loadMessageStoresFromSession();
699
700
		return $this->openMessageStore($this->publicStore, 'Public store');
701
	}
702
703
	/**
704
	 * Get all message stores currently open in the session.
705
	 *
706
	 * @return array Associative array with entryid -> mapistore of all open stores (private, public, delegate)
707
	 */
708
	public function getAllMessageStores() {
709
		$this->getDefaultMessageStore();
710
		$this->getPublicMessageStore();
711
		$this->getArchivedStores($this->getUserEntryID());
712
		// The cache now contains all the stores in our profile. Next, add the stores
713
		// for other users.
714
		$this->getOtherUserStore();
715
716
		// Just return all the stores in our cache, even if we have some error in mapi
717
		return $this->stores;
718
	}
719
720
	/**
721
	 * Open the message store with entryid $entryid.
722
	 *
723
	 * @param string $entryid string representation of the binary entryid of the store
724
	 * @param string $name    The name of the store. Will be logged when opening fails.
725
	 *
726
	 * @return mapistore The opened store on success, false otherwise
727
	 */
728
	public function openMessageStore($entryid, $name = '') {
729
		// Check the cache before opening
730
		foreach ($this->stores as $storeEntryId => $storeObj) {
731
			if ($GLOBALS["entryid"]->compareEntryIds(bin2hex($entryid), bin2hex($storeEntryId))) {
732
				return $storeObj;
733
			}
734
		}
735
736
		try {
737
			$store = mapi_openmsgstore($this->session, $entryid);
738
			$store_props = mapi_getprops($store, [PR_ENTRYID]);
739
			$entryid = $store_props[PR_ENTRYID];
740
741
			// Cache the store for later use
742
			$this->stores[$entryid] = $store;
743
			$this->userstores[$name] = $entryid;
744
		}
745
		catch (MAPIException $e) {
746
			error_log('Failed to open store. ' . $this->session_info["username"] .
747
					  ' requested ' . bin2hex($entryid) . ($name ? " ({$name})" : ''));
748
749
			return $e->getCode();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $e->getCode() also could return the type integer which is incompatible with the documented return type mapistore.
Loading history...
750
		}
751
		catch (Exception $e) {
752
			// mapi_openmsgstore seems to throw another exception than MAPIException
753
			// sometimes, so we add a safety net.
754
			error_log('Failed to open store. ' . $this->session_info["username"] .
755
					  ' requested ' . bin2hex($entryid) . ($name ? " ({$name})" : ''));
756
757
			return $e->getCode();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $e->getCode() also could return the type integer which is incompatible with the documented return type mapistore.
Loading history...
758
		}
759
760
		return $store;
761
	}
762
763
	/**
764
	 * Searches for the PR_EC_ARCHIVE_SERVERS property of the user of the passed entryid in the
765
	 * Addressbook. It will get all his archive store objects and add those to the $this->stores
766
	 * list. It will return an array with the list of archive stores where the key is the
767
	 * entryid of the store and the value the store resource.
768
	 *
769
	 * @param string $userEntryid Binary entryid of the user
770
	 *
771
	 * @return MAPIStore[] List of store resources with the key being the entryid of the store
772
	 */
773
	public function getArchivedStores($userEntryid) {
774
		if (!isset($this->archivePropsCache[$userEntryid])) {
775
			$this->archivePropsCache[$userEntryid] = $this->getArchiveProps($userEntryid);
776
		}
777
778
		$userData = $this->archivePropsCache[$userEntryid];
779
780
		$archiveStores = [];
781
		if (isset($userData[PR_EC_ARCHIVE_SERVERS]) && count($userData[PR_EC_ARCHIVE_SERVERS]) > 0) {
782
			// Get the store of the user, need this for the call to mapi_msgstore_getarchiveentryid()
783
			$userStoreEntryid = mapi_msgstore_createentryid($this->getDefaultMessageStore(), $userData[PR_ACCOUNT]);
784
			$userStore = mapi_openmsgstore($GLOBALS['mapisession']->getSession(), $userStoreEntryid);
785
786
			for ($i = 0; $i < count($userData[PR_EC_ARCHIVE_SERVERS]); ++$i) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
787
				try {
788
					// Check if the store exists. It can be that the store archiving has been enabled, but no
789
					// archived store has been created an none can be found in the PR_EC_ARCHIVE_SERVERS property.
790
					$archiveStoreEntryid = mapi_msgstore_getarchiveentryid($userStore, $userData[PR_ACCOUNT], $userData[PR_EC_ARCHIVE_SERVERS][$i]);
791
					$archiveStores[$archiveStoreEntryid] = mapi_openmsgstore($GLOBALS['mapisession']->getSession(), $archiveStoreEntryid);
792
					// Add the archive store to the list
793
					$this->stores[$archiveStoreEntryid] = $archiveStores[$archiveStoreEntryid];
794
				}
795
				catch (MAPIException $e) {
796
					$e->setHandled();
797
					if ($e->getCode() == MAPI_E_UNKNOWN_ENTRYID) {
798
						dump('Failed to load archive store as entryid is not valid' . $e->getDisplayMessage());
799
					}
800
					elseif ($e->getCode() == MAPI_E_NOT_FOUND) {
801
						// The corresponding store couldn't be found, print an error to the log.
802
						dump('Corresponding archive store couldn\'t be found' . $e->getDisplayMessage());
803
					}
804
					else {
805
						dump('Failed to load archive store' . $e->getDisplayMessage());
806
					}
807
				}
808
			}
809
		}
810
811
		return $archiveStores;
812
	}
813
814
	/**
815
	 * @param string $userEntryid binary entryid of the user
816
	 *
817
	 * @return array address Archive Properties of the user
818
	 */
819
	private function getArchiveProps($userEntryid) {
820
		$ab = $this->getAddressbook();
821
		$abitem = mapi_ab_openentry($ab, $userEntryid);
822
823
		return mapi_getprops($abitem, [PR_ACCOUNT, PR_EC_ARCHIVE_SERVERS]);
824
	}
825
826
	/**
827
	 * Get all the available shared stores.
828
	 *
829
	 * The store is opened only once, subsequent calls will return the previous store object
830
	 */
831
	public function getOtherUserStore() {
832
		$otherusers = $this->retrieveOtherUsersFromSettings();
833
		$otherUsersStores = [];
834
835
		foreach ($otherusers as $username => $folder) {
836
			if (isset($this->userstores[$username])) {
837
				continue;
838
			}
839
			$storeOk = true;
840
841
			if (is_array($folder) && !empty($folder)) {
842
				try {
843
					$user_entryid = mapi_msgstore_createentryid($this->getDefaultMessageStore(), $username);
844
845
					$sharedStore = $this->openMessageStore($user_entryid, $username);
846
					if ($sharedStore === false && $sharedStore === ecLoginPerm &&
847
						$sharedStore === MAPI_E_CALL_FAILED && $sharedStore === MAPI_E_NOT_FOUND) {
848
						$storeOk = false;
849
					}
850
851
					// Check if an entire store will be loaded, if so load the archive store as well
852
					if ($storeOk && isset($folder['all']) && $folder['all']['folder_type'] == 'all') {
853
						$this->getArchivedStores($this->resolveStrictUserName($username));
854
					}
855
				}
856
				catch (MAPIException $e) {
857
					if ($e->getCode() == MAPI_E_NOT_FOUND) {
858
						// The user or the corresponding store couldn't be found,
859
						// print an error to the log, and remove the user from the settings.
860
						dump('Failed to load store for user ' . $username . ', user was not found. Removing it from settings.');
861
						$GLOBALS["settings"]->delete("zarafa/v1/contexts/hierarchy/shared_stores/" . bin2hex($username), true);
862
					}
863
					else {
864
						// That is odd, something else went wrong. Lets not be hasty and preserve
865
						// the user in the settings, but do print something to the log to indicate
866
						// something happened...
867
						dump('Failed to load store for user ' . $username . '. ' . $e->getDisplayMessage());
868
					}
869
				}
870
				finally {
871
					if (!$storeOk && ($sharedStore == ecLoginPerm || $sharedStore == MAPI_E_NOT_FOUND)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $sharedStore does not seem to be defined for all execution paths leading up to this point.
Loading history...
872
						// The user or the corresponding store couldn't be opened
873
						// (e.g. the user was deleted or permissions revoked),
874
						// print an error to the log, and remove the user from the settings.
875
						dump(sprintf("The user %s failed to load store of the user %s. Removing it from settings.", $this->session_info["username"], $username));
876
						$GLOBALS["settings"]->delete("zarafa/v1/contexts/hierarchy/shared_stores/" . bin2hex($username), true);
877
					}
878
				}
879
			}
880
		}
881
882
		foreach ($this->userstores as $entryid) {
883
			$otherUsersStores[$entryid] = $this->stores[$entryid];
884
		}
885
886
		return $otherUsersStores;
887
	}
888
889
	/**
890
	 * Resolve the username strictly by opening that user's store and returning the
891
	 * PR_MAILBOX_OWNER_ENTRYID. This can be used for resolving an username without the risk of
892
	 * ambiguity since mapi_ab_resolve() does not strictly resolve on the username.
893
	 *
894
	 * @param string $username The username
895
	 *
896
	 * @return Binary|int Entryid of the user on success otherwise the hresult error code
0 ignored issues
show
Bug introduced by
The type Binary 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...
897
	 */
898
	public function resolveStrictUserName($username) {
899
		$storeEntryid = mapi_msgstore_createentryid($this->getDefaultMessageStore(), $username);
900
		$store = $this->openMessageStore($storeEntryid, $username);
901
		$storeProps = mapi_getprops($store, [PR_MAILBOX_OWNER_ENTRYID]);
902
903
		return $storeProps[PR_MAILBOX_OWNER_ENTRYID];
904
	}
905
906
	/**
907
	 * Get other users from settings.
908
	 *
909
	 * @return array Array of usernames of delegate stores
910
	 */
911
	public function retrieveOtherUsersFromSettings() {
912
		$other_users = $GLOBALS["settings"]->get("zarafa/v1/contexts/hierarchy/shared_stores", []);
913
		$result = [];
914
		foreach ($other_users as $username => $folders) {
915
			// No folders are being shared, the store has probably been closed by the user,
916
			// but the username is still lingering in the settings...
917
			if (!isset($folders) || empty($folders)) {
918
				continue;
919
			}
920
921
			$username = strtolower(hex2bin($username));
922
			if (!isset($result[$username])) {
923
				$result[$username] = [];
924
			}
925
926
			foreach ($folders as $folder) {
927
				if (is_array($folder)) {
928
					$result[$username][$folder["folder_type"]] = [];
929
					$result[$username][$folder["folder_type"]]["folder_type"] = $folder["folder_type"];
930
					$result[$username][$folder["folder_type"]]["show_subfolders"] = $folder["show_subfolders"];
931
				}
932
			}
933
		}
934
935
		return $result;
936
	}
937
938
	/**
939
	 * Add the store of another user to the list of other user stores.
940
	 *
941
	 * @param string $username The username whose store should be added to the list of other users' stores
942
	 *
943
	 * @return mapistore The store of the user or false on error;
944
	 */
945
	public function addUserStore($username) {
946
		$user_entryid = mapi_msgstore_createentryid($this->getDefaultMessageStore(), $username);
947
948
		if ($user_entryid) {
949
			// mapi_msgstore_createentryid and mapi_getprops(PR_ENTRYID) have different
950
			// values for shared stores, so save the one from mapi_getprops(PR_ENTRYID)
951
			// $this->userstores[$username] = $user_entryid;
952
953
			return $this->openMessageStore($user_entryid, $username);
954
		}
955
	}
956
957
	/**
958
	 * Remove the store of another user from the list of other user stores.
959
	 *
960
	 * @param string $username The username whose store should be deleted from the list of other users' stores
961
	 *
962
	 * @return string The entryid of the store which was removed
963
	 */
964
	public function removeUserStore($username) {
965
		// Remove the reference to the store if we had one
966
		if (isset($this->userstores[$username])) {
967
			$entryid = $this->userstores[$username];
968
			unset($this->userstores[$username], $this->stores[$entryid]);
969
970
			return $entryid;
971
		}
972
	}
973
974
	/**
975
	 * Get the store entryid of the specified user.
976
	 *
977
	 * The store must have been previously added via addUserStores.
978
	 *
979
	 * @param string $username The username whose store is being looked up
980
	 *
981
	 * @return string The entryid of the store of the user
982
	 */
983
	public function getStoreEntryIdOfUser($username) {
984
		return $this->userstores[$username];
985
	}
986
987
	/**
988
	 * Get the username of the user store.
989
	 *
990
	 * @param string $username the loginname of whom we want to full name
991
	 *
992
	 * @return string the display name of the user
993
	 */
994
	public function getDisplayNameofUser($username) {
995
		$user_entryid = $this->getStoreEntryIdOfUser($username);
996
		$store = $this->openMessageStore($user_entryid, $username);
997
		$props = mapi_getprops($store, [PR_DISPLAY_NAME]);
998
999
		return str_replace('Inbox - ', '', $props[PR_DISPLAY_NAME]);
1000
	}
1001
1002
	/**
1003
	 * Get the username of the owner of the specified store.
1004
	 *
1005
	 * The store must have been previously added via addUserStores.
1006
	 *
1007
	 * @param string $entryid EntryID of the store
1008
	 *
1009
	 * @return string Username of the specified store or false if it is not found
1010
	 */
1011
	public function getUserNameOfStore($entryid) {
1012
		foreach ($this->userstores as $username => $storeentryid) {
1013
			if ($GLOBALS["entryid"]->compareEntryIds(bin2hex($storeentryid), bin2hex($entryid))) {
1014
				return $username;
1015
			}
1016
		}
1017
1018
		return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type string.
Loading history...
1019
	}
1020
1021
	/**
1022
	 * Open a MAPI message using session object.
1023
	 * The function is used to open message when we dont' know
1024
	 * the specific store and we want to open message using entryid.
1025
	 *
1026
	 * @param string $entryid entryid of the message
1027
	 *
1028
	 * @return object MAPI Message
1029
	 */
1030
	public function openMessage($entryid) {
1031
		return mapi_openentry($this->session, $entryid);
1032
	}
1033
1034
	/**
1035
	 * Setup the contact provider for the addressbook. It asks getContactFoldersForABContactProvider
1036
	 * for the entryids and display names for the contact folders in the user's store.
1037
	 *
1038
	 * @param bool $loadSharedContactsProvider when set to true it denotes that shared folders are
1039
	 *                                         required to be configured to load the contacts from
1040
	 */
1041
	public function setupContactProviderAddressbook($loadSharedContactsProvider) {
1042
		$profsect = mapi_openprofilesection($GLOBALS['mapisession']->getSession(), pbGlobalProfileSectionGuid);
1043
		if ($profsect) {
1044
			// Get information about all contact folders from own store, shared stores and public store
1045
			$defaultStore = $this->getDefaultMessageStore();
1046
			$contactFolders = $this->getContactFoldersForABContactProvider($defaultStore);
1047
1048
			// include shared contact folders in addressbook if shared contact folders are enabled
1049
			if (ENABLE_SHARED_CONTACT_FOLDERS && $loadSharedContactsProvider) {
1050
				if (empty($this->userstores)) {
1051
					$this->getOtherUserStore();
1052
				}
1053
1054
				$sharedSetting = $GLOBALS["settings"]->get("zarafa/v1/contexts/hierarchy/shared_stores", []);
1055
				// Find available contact folders from all user stores, one by one.
1056
				foreach ($this->userstores as $username => $storeEntryID) {
1057
					$userContactFolders = [];
1058
					$sharedUserSetting = [];
1059
					$openedUserStore = $this->openMessageStore($storeEntryID, $username);
1060
1061
					// Get settings of respective shared folder of given user
1062
					if (array_key_exists(strtolower(bin2hex($username)), $sharedSetting)) {
1063
						$sharedUserSetting = $sharedSetting[strtolower(bin2hex($username))];
1064
					}
1065
1066
					// Only add opened shared folders into addressbook contacts provider.
1067
					// If entire inbox is opened then add each and every contact folders of that particular user.
1068
					if (isset($sharedUserSetting['all'])) {
1069
						$userContactFolders = $this->getContactFoldersForABContactProvider($openedUserStore);
1070
					}
1071
					elseif (isset($sharedUserSetting['contact'])) {
1072
						// Add respective default contact folder which is opened.
1073
						// Get entryid of default contact folder from root.
1074
						$root = mapi_msgstore_openentry($openedUserStore);
1075
						$rootProps = mapi_getprops($root, [PR_IPM_CONTACT_ENTRYID]);
1076
1077
						// Just add the default contact folder only.
1078
						$defaultContactFolder = [
1079
							PR_STORE_ENTRYID => $storeEntryID,
1080
							PR_ENTRYID => $rootProps[PR_IPM_CONTACT_ENTRYID],
1081
							PR_DISPLAY_NAME => _("Contacts"),
1082
						];
1083
						array_push($userContactFolders, $defaultContactFolder);
1084
1085
						// Go for sub folders only if configured in settings
1086
						if ($sharedUserSetting['contact']['show_subfolders'] == true) {
1087
							$subContactFolders = $this->getContactFolders($openedUserStore, $rootProps[PR_IPM_CONTACT_ENTRYID], true);
0 ignored issues
show
Bug introduced by
true of type true is incompatible with the type integer expected by parameter $depthSearch of MAPISession::getContactFolders(). ( Ignorable by Annotation )

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

1087
							$subContactFolders = $this->getContactFolders($openedUserStore, $rootProps[PR_IPM_CONTACT_ENTRYID], /** @scrutinizer ignore-type */ true);
Loading history...
1088
							if (is_array($subContactFolders)) {
1089
								$userContactFolders = array_merge($userContactFolders, $subContactFolders);
1090
							}
1091
						}
1092
					}
1093
1094
					// Postfix display name of every contact folder with respective owner name
1095
					// it is mandatory to keep display-name different
1096
					$userStoreProps = mapi_getprops($openedUserStore, [PR_MAILBOX_OWNER_NAME]);
1097
					for ($i = 0,$len = count($userContactFolders); $i < $len; ++$i) {
1098
						$userContactFolders[$i][PR_DISPLAY_NAME] = $userContactFolders[$i][PR_DISPLAY_NAME] . " - " . $userStoreProps[PR_MAILBOX_OWNER_NAME];
1099
					}
1100
1101
					$contactFolders = array_merge($contactFolders, $userContactFolders);
1102
				}
1103
			}
1104
1105
			// Include public contact folders in addressbook if public folders and public contacts folders are enabled
1106
			if (ENABLE_PUBLIC_CONTACT_FOLDERS && ENABLE_PUBLIC_FOLDERS) {
1107
				$publicStore = $this->getPublicMessageStore();
1108
				if ($publicStore !== false) {
0 ignored issues
show
introduced by
The condition $publicStore !== false is always true.
Loading history...
1109
					$contactFolders = array_merge($contactFolders, $this->getContactFoldersForABContactProvider($publicStore));
1110
				}
1111
			}
1112
			// TODO: The shared stores are not opened as there still is a bug that does not allow resolving from shared contact folders
1113
1114
			// These lists will be used to put set in the profile section
1115
			$contact_store_entryids = [];
1116
			$contact_folder_entryids = [];
1117
			$contact_folder_names = [];
1118
1119
			// Create the lists of store entryids, folder entryids and folder names to be added
1120
			// to the profile section
1121
			for ($i = 0, $len = count($contactFolders); $i < $len; ++$i) {
1122
				$contact_store_entryids[] = $contactFolders[$i][PR_STORE_ENTRYID];
1123
				$contact_folder_entryids[] = $contactFolders[$i][PR_ENTRYID];
1124
				$contact_folder_names[] = $contactFolders[$i][PR_DISPLAY_NAME];
1125
			}
1126
1127
			if (!empty($contact_store_entryids)) {
1128
				// add the defaults contacts folder in the addressbook hierarchy under 'Contacts Folders'
1129
				mapi_setprops($profsect, [PR_ZC_CONTACT_STORE_ENTRYIDS => $contact_store_entryids,
1130
					PR_ZC_CONTACT_FOLDER_ENTRYIDS => $contact_folder_entryids,
1131
					PR_ZC_CONTACT_FOLDER_NAMES => $contact_folder_names, ]);
1132
			}
1133
		}
1134
	}
1135
1136
	/**
1137
	 * Get the store entryid, folder entryid and display name of the contact folders in the
1138
	 * user's store. It returns an array prepared by getContactFolders.
1139
	 *
1140
	 * @param mapiStore $store The mapi store to look for folders in
0 ignored issues
show
Bug introduced by
The type mapiStore 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...
1141
	 *
1142
	 * @return array Contact folder information
1143
	 */
1144
	public function getContactFoldersForABContactProvider($store) {
1145
		$storeProps = mapi_getprops($store, [PR_ENTRYID, PR_MDB_PROVIDER, PR_IPM_SUBTREE_ENTRYID, PR_IPM_PUBLIC_FOLDERS_ENTRYID]);
1146
		$contactFolders = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $contactFolders is dead and can be removed.
Loading history...
1147
1148
		try {
1149
			// Only searches one level deep, otherwise deleted contact folders will also be included.
1150
			$contactFolders = $this->getContactFolders($store, $storeProps[PR_IPM_SUBTREE_ENTRYID], false);
0 ignored issues
show
Bug introduced by
false of type false is incompatible with the type integer expected by parameter $depthSearch of MAPISession::getContactFolders(). ( Ignorable by Annotation )

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

1150
			$contactFolders = $this->getContactFolders($store, $storeProps[PR_IPM_SUBTREE_ENTRYID], /** @scrutinizer ignore-type */ false);
Loading history...
1151
		}
1152
		catch (Exception $e) {
1153
			return $contactFolders;
1154
		}
1155
1156
		// Need to search all the contact-subfolders within first level contact folders.
1157
		$firstLevelHierarchyNodes = $contactFolders;
1158
		foreach ($firstLevelHierarchyNodes as $firstLevelNode) {
1159
			// To search for multiple levels CONVENIENT_DEPTH needs to be passed as well.
1160
			$contactFolders = array_merge($contactFolders, $this->getContactFolders($store, $firstLevelNode[PR_ENTRYID], true));
0 ignored issues
show
Bug introduced by
true of type true is incompatible with the type integer expected by parameter $depthSearch of MAPISession::getContactFolders(). ( Ignorable by Annotation )

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

1160
			$contactFolders = array_merge($contactFolders, $this->getContactFolders($store, $firstLevelNode[PR_ENTRYID], /** @scrutinizer ignore-type */ true));
Loading history...
1161
		}
1162
1163
		return $contactFolders;
1164
	}
1165
1166
	/**
1167
	 * Get the store entryid, folder entryid and display name of the contact folders from within given folder, in the
1168
	 * user's store. It provides an array where each item contains the information of a folder
1169
	 * formatted like this:
1170
	 * Array(
1171
	 *     PR_STORE_ENTRYID => '1234567890ABCDEF',
1172
	 *     PR_ENTRYID       => '1234567890ABCDEF',
1173
	 *     PR_DISPLAY_NAME  => 'Contact folder'
1174
	 * ).
1175
	 *
1176
	 * @param mapiStore $store         The mapi store of the user
1177
	 * @param string    $folderEntryid EntryID of the folder to look for contact folders in
1178
	 * @param int       $depthSearch   flag to search into all the folder levels
1179
	 *
1180
	 * @return array an array in which founded contact-folders will be pushed
1181
	 */
1182
	public function getContactFolders($store, $folderEntryid, $depthSearch) {
1183
		$restriction = [RES_CONTENT,
1184
			[
1185
				// Fuzzylevel PF_PREFIX also allows IPF.Contact.Custom folders to be included.
1186
				// Otherwise FL_FULLSTRING would only allow IPF.Contact folders.
1187
				FUZZYLEVEL => FL_PREFIX,
1188
				ULPROPTAG => PR_CONTAINER_CLASS,
1189
				VALUE => [
1190
					PR_CONTAINER_CLASS => "IPF.Contact",
1191
				],
1192
			],
1193
		];
1194
1195
		// Set necessary flag(s) to search considering all the sub folders or not
1196
		$depthFlag = MAPI_DEFERRED_ERRORS;
1197
		if ($depthSearch) {
1198
			$depthFlag |= CONVENIENT_DEPTH;
1199
		}
1200
1201
		$hierarchyFolder = mapi_msgstore_openentry($store, $folderEntryid);
1202
1203
		// Filter-out contact folders only
1204
		$contactFolderTable = mapi_folder_gethierarchytable($hierarchyFolder, $depthFlag);
1205
		mapi_table_restrict($contactFolderTable, $restriction, TBL_BATCH);
1206
1207
		return mapi_table_queryallrows($contactFolderTable, [PR_STORE_ENTRYID, PR_ENTRYID, PR_DISPLAY_NAME, PR_PARENT_ENTRYID, PR_DEPTH]);
1208
	}
1209
1210
	/**
1211
	 * Obtains server version from the PR_EC_SERVER_VERSION property.
1212
	 */
1213
	public function getServerVersion() {
1214
		$props = mapi_getprops($this->getDefaultMessageStore(), [PR_EC_SERVER_VERSION]);
1215
		if (propIsError(PR_EC_SERVER_VERSION, $props) === MAPI_E_NOT_FOUND) {
1216
			return '';
1217
		}
1218
1219
		return $props[PR_EC_SERVER_VERSION];
1220
	}
1221
1222
	/**
1223
	 * Checks if the entryid is of the public store.
1224
	 *
1225
	 * @param string $entryid
1226
	 *
1227
	 * @return bool true if public store entryid false otherwise
1228
	 */
1229
	public function isPublicStore($entryid) {
1230
		return $GLOBALS["entryid"]->compareEntryIds(bin2hex($this->publicStore), $entryid);
1231
	}
1232
}
1233