Passed
Push — master ( d205fb...56c548 )
by
unknown
31:30 queued 16:35
created

MAPISession::loadMessageStoresFromSession()   B

Complexity

Conditions 8
Paths 7

Size

Total Lines 18
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 13
nc 7
nop 0
dl 0
loc 18
rs 8.4444
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
	 * Checks whether the user is enabled for grommunio-web.
510
	 *
511
	 * @return bool
512
	 */
513
	public function isGwebEnabled() {
514
		$store_props = mapi_getprops($this->getDefaultMessageStore(), [PR_EC_ENABLED_FEATURES_L]);
515
		return $store_props[PR_EC_ENABLED_FEATURES_L] & UP_WEB;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $store_props[PR_E...ED_FEATURES_L] & UP_WEB returns the type integer which is incompatible with the documented return type boolean.
Loading history...
516
	}
517
518
	/**
519
	 * @return bool true if webapp is disabled feature else return false
520
	 */
521
	public function isWebappDisableAsFeature() {
522
		return !$this->isGwebEnabled() || array_search('webapp', $this->getDisabledFeatures()) !== false;
523
	}
524
525
	/**
526
	 * Magic method to get properties from the session_info. When a method of this class if called
527
	 * and there is no method of this name defined this function will be called
528
	 * It creates getter methods for the properties stored in $session_info using the following syntax:
529
	 * getSomeUserProperty() will look return a property called some_user_property if it exists and
530
	 * throw an exception otherwise.
531
	 *
532
	 * @param string $methodName The name of the method that was called
533
	 * @param array  $arguments  The arguments that were passed in the call
534
	 *
535
	 * @return string The requested property if it exists
536
	 *
537
	 * @throws Exception
538
	 */
539
	public function __call($methodName, $arguments) {
540
		if (!preg_match('/^get(.+)$/', $methodName, $matches)) {
541
			// We don't know this function, so let's throw an error
542
			throw new Exception('Method ' . $methodName . ' does not exist');
543
		}
544
		$this->retrieveUserData();
545
		$propertyName = strtolower(preg_replace('/([^A-Z])([A-Z])/', '$1_$2', $matches[1]));
546
		if (!array_key_exists($propertyName, $this->session_info)) {
547
			// We don't know this function, so let's throw an error
548
			throw new Exception('Method ' . $methodName . ' does not exist ' . $propertyName);
549
		}
550
551
		return $this->session_info[$propertyName];
552
	}
553
554
	/**
555
	 * Returns a hash with information about the user that is logged in.
556
	 *
557
	 * @return array
558
	 */
559
	public function getUserInfo() {
560
		return [
561
			'username' => $this->getUserName(),
562
			'fullname' => $this->getFullName(),
563
			'entryid' => bin2hex($this->getUserEntryid()),
564
			'email_address' => $this->getEmailAddress(),
565
			'smtp_address' => $this->getSMTPAddress(),
566
			'search_key' => bin2hex($this->getSearchKey()),
567
			'user_image' => $this->getUserImage(),
568
			'given_name' => $this->getGivenName(),
569
			'initials' => $this->getInitials(),
570
			'surname' => $this->getSurname(),
571
			'street_address' => $this->getStreetAddress(),
572
			'locality' => $this->getLocality(),
573
			'state_or_province' => $this->getStateOrProvince(),
574
			'postal_code' => $this->getPostalCode(),
575
			'country' => $this->getCountry(),
576
			'title' => $this->getTitle(),
577
			'company_name' => $this->getCompanyName(),
578
			'department_name' => $this->getDepartmentName(),
579
			'office_location' => $this->getOfficeLocation(),
580
			'assistant' => $this->getAssistant(),
581
			'assistant_telephone_number' => $this->getAssistantTelephoneNumber(),
582
			'office_telephone_number' => $this->getOfficeTelephoneNumber(),
583
			'business_telephone_number' => $this->getBusinessTelephoneNumber(),
584
			'business2_telephone_number' => $this->getBusiness2TelephoneNumber(),
585
			'primary_fax_number' => $this->getPrimaryFaxNumber(),
586
			'home_telephone_number' => $this->getHomeTelephoneNumber(),
587
			'home2_telephone_number' => $this->getHome2TelephoneNumber(),
588
			'mobile_telephone_number' => $this->getMobileTelephoneNumber(),
589
			'pager_telephone_number' => $this->getPagerTelephoneNumber(),
590
		];
591
	}
592
593
	/**
594
	 * Get current user's search key.
595
	 *
596
	 * @return string Current user's searchkey
597
	 */
598
	public function getSearchKey() {
599
		$this->retrieveUserData();
600
		return $this->session_info["searchkey"] ?? '';
601
	}
602
603
	/**
604
	 * Get the message stores from the message store table from your session. Standard stores
605
	 * like the default store and the public store are made them easily accessible through the
606
	 * defaultstore and publicStore properties.
607
	 */
608
	public function loadMessageStoresFromSession() {
609
		$storestables = mapi_getmsgstorestable($this->session);
610
		$rows = mapi_table_queryallrows($storestables, [PR_ENTRYID, PR_DEFAULT_STORE, PR_MDB_PROVIDER]);
611
		foreach ($rows as $row) {
612
			if (!$row[PR_ENTRYID]) {
613
				continue;
614
			}
615
616
			if (isset($row[PR_DEFAULT_STORE]) && $row[PR_DEFAULT_STORE] == true) {
617
				$this->defaultstore = $row[PR_ENTRYID];
618
			}
619
			elseif ($row[PR_MDB_PROVIDER] == ZARAFA_STORE_PUBLIC_GUID) {
620
				$this->publicStore = $row[PR_ENTRYID];
621
			}
622
			elseif ($row[PR_MDB_PROVIDER] == ZARAFA_STORE_DELEGATE_GUID) {
623
				$eidObj = $GLOBALS["entryid"]->createMsgStoreEntryIdObj($row[PR_ENTRYID]);
624
				if (isset($eidObj['MailboxDN'])) {
625
					$this->openMessageStore($row[PR_ENTRYID], strtolower($eidObj['MailboxDN']));
626
				}
627
			}
628
		}
629
	}
630
631
	/**
632
	 * Get the current user's default message store.
633
	 *
634
	 * The store is opened only once, subsequent calls will return the previous store object
635
	 *
636
	 * @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...
637
	 * @param mixed $reopen
638
	 *
639
	 * @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...
640
	 */
641
	public function getDefaultMessageStore($reopen = false) {
642
		// Return cached default store if we have one
643
		if (!$reopen && isset($this->defaultstore, $this->stores[$this->defaultstore])) {
644
			return $this->stores[$this->defaultstore];
645
		}
646
647
		$this->loadMessageStoresFromSession();
648
649
		return $this->openMessageStore($this->defaultstore, 'Default store');
650
	}
651
652
	/**
653
	 * The default messagestore entryid.
654
	 *
655
	 * @return string the entryid of the default messagestore
656
	 */
657
	public function getDefaultMessageStoreEntryId() {
658
		if (!isset($this->defaultstore)) {
659
			$this->loadMessageStoresFromSession();
660
		}
661
662
		return bin2hex($this->defaultstore);
663
	}
664
665
	/**
666
	 * Get single store and it's archive store as well if we are openig full store.
667
	 *
668
	 * @param object $store        the store of the user
669
	 * @param array  $storeOptions contains folder_type of which folder to open
670
	 *                             It is mapped to username, If folder_type is 'all' (i.e. Open Entire Inbox)
671
	 *                             then we will open full store and it's archived stores.
672
	 * @param string $username     The username
673
	 *
674
	 * @return array storeArray The array of stores containing user's store and archived stores
675
	 */
676
	public function getSingleMessageStores($store, $storeOptions, $username) {
677
		$storeArray = [$store];
678
		$archivedStores = [];
679
680
		// Get archived stores for user if there's any
681
		if (!empty($username)) {
682
			// Check whether we should open the whole store or just single folders
683
			if (is_array($storeOptions) && isset($storeOptions[$username], $storeOptions[$username]['all'])) {
684
				$archivedStores = $this->getArchivedStores($this->resolveStrictUserName($username));
685
			}
686
		}
687
688
		foreach ($archivedStores as $archivedStore) {
689
			$storeArray[] = $archivedStore;
690
		}
691
692
		return $storeArray;
693
	}
694
695
	/**
696
	 * Get the public message store.
697
	 *
698
	 * The store is opened only once, subsequent calls will return the previous store object
699
	 *
700
	 * @return mapistore Public message store object
701
	 */
702
	public function getPublicMessageStore() {
703
		// Return cached public store if we have one
704
		if (isset($this->publicStore, $this->stores[$this->publicStore])) {
705
			return $this->stores[$this->publicStore];
706
		}
707
708
		$this->loadMessageStoresFromSession();
709
710
		return $this->openMessageStore($this->publicStore, 'Public store');
711
	}
712
713
	/**
714
	 * Get all message stores currently open in the session.
715
	 *
716
	 * @return array Associative array with entryid -> mapistore of all open stores (private, public, delegate)
717
	 */
718
	public function getAllMessageStores() {
719
		$this->getDefaultMessageStore();
720
		$this->getPublicMessageStore();
721
		$this->getArchivedStores($this->getUserEntryID());
722
		// The cache now contains all the stores in our profile. Next, add the stores
723
		// for other users.
724
		$this->getOtherUserStore();
725
726
		// Just return all the stores in our cache, even if we have some error in mapi
727
		return $this->stores;
728
	}
729
730
	/**
731
	 * Open the message store with entryid $entryid.
732
	 *
733
	 * @param string $entryid string representation of the binary entryid of the store
734
	 * @param string $name    The name of the store. Will be logged when opening fails.
735
	 *
736
	 * @return mapistore The opened store on success, false otherwise
737
	 */
738
	public function openMessageStore($entryid, $name = '') {
739
		// Check the cache before opening
740
		foreach ($this->stores as $storeEntryId => $storeObj) {
741
			if ($GLOBALS["entryid"]->compareEntryIds(bin2hex($entryid), bin2hex($storeEntryId))) {
742
				return $storeObj;
743
			}
744
		}
745
746
		try {
747
			$store = mapi_openmsgstore($this->session, $entryid);
748
			$store_props = mapi_getprops($store, [PR_ENTRYID]);
749
			$entryid = $store_props[PR_ENTRYID];
750
751
			// Cache the store for later use
752
			$this->stores[$entryid] = $store;
753
			$this->userstores[$name] = $entryid;
754
		}
755
		catch (MAPIException $e) {
756
			error_log('Failed to open store. ' . $this->session_info["username"] .
757
					  ' requested ' . bin2hex($entryid) . ($name ? " ({$name})" : ''));
758
759
			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...
760
		}
761
		catch (Exception $e) {
762
			// mapi_openmsgstore seems to throw another exception than MAPIException
763
			// sometimes, so we add a safety net.
764
			error_log('Failed to open store. ' . $this->session_info["username"] .
765
					  ' requested ' . bin2hex($entryid) . ($name ? " ({$name})" : ''));
766
767
			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...
768
		}
769
770
		return $store;
771
	}
772
773
	/**
774
	 * Searches for the PR_EC_ARCHIVE_SERVERS property of the user of the passed entryid in the
775
	 * Addressbook. It will get all his archive store objects and add those to the $this->stores
776
	 * list. It will return an array with the list of archive stores where the key is the
777
	 * entryid of the store and the value the store resource.
778
	 *
779
	 * @param string $userEntryid Binary entryid of the user
780
	 *
781
	 * @return MAPIStore[] List of store resources with the key being the entryid of the store
782
	 */
783
	public function getArchivedStores($userEntryid) {
784
		if (!isset($this->archivePropsCache[$userEntryid])) {
785
			$this->archivePropsCache[$userEntryid] = $this->getArchiveProps($userEntryid);
786
		}
787
788
		$userData = $this->archivePropsCache[$userEntryid];
789
790
		$archiveStores = [];
791
		if (isset($userData[PR_EC_ARCHIVE_SERVERS]) && count($userData[PR_EC_ARCHIVE_SERVERS]) > 0) {
792
			// Get the store of the user, need this for the call to mapi_msgstore_getarchiveentryid()
793
			$userStoreEntryid = mapi_msgstore_createentryid($this->getDefaultMessageStore(), $userData[PR_ACCOUNT]);
794
			$userStore = mapi_openmsgstore($GLOBALS['mapisession']->getSession(), $userStoreEntryid);
795
796
			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...
797
				try {
798
					// Check if the store exists. It can be that the store archiving has been enabled, but no
799
					// archived store has been created an none can be found in the PR_EC_ARCHIVE_SERVERS property.
800
					$archiveStoreEntryid = mapi_msgstore_getarchiveentryid($userStore, $userData[PR_ACCOUNT], $userData[PR_EC_ARCHIVE_SERVERS][$i]);
801
					$archiveStores[$archiveStoreEntryid] = mapi_openmsgstore($GLOBALS['mapisession']->getSession(), $archiveStoreEntryid);
802
					// Add the archive store to the list
803
					$this->stores[$archiveStoreEntryid] = $archiveStores[$archiveStoreEntryid];
804
				}
805
				catch (MAPIException $e) {
806
					$e->setHandled();
807
					if ($e->getCode() == MAPI_E_UNKNOWN_ENTRYID) {
808
						dump('Failed to load archive store as entryid is not valid' . $e->getDisplayMessage());
809
					}
810
					elseif ($e->getCode() == MAPI_E_NOT_FOUND) {
811
						// The corresponding store couldn't be found, print an error to the log.
812
						dump('Corresponding archive store couldn\'t be found' . $e->getDisplayMessage());
813
					}
814
					else {
815
						dump('Failed to load archive store' . $e->getDisplayMessage());
816
					}
817
				}
818
			}
819
		}
820
821
		return $archiveStores;
822
	}
823
824
	/**
825
	 * @param string $userEntryid binary entryid of the user
826
	 *
827
	 * @return array address Archive Properties of the user
828
	 */
829
	private function getArchiveProps($userEntryid) {
830
		$ab = $this->getAddressbook();
831
		$abitem = mapi_ab_openentry($ab, $userEntryid);
832
833
		return mapi_getprops($abitem, [PR_ACCOUNT, PR_EC_ARCHIVE_SERVERS]);
834
	}
835
836
	/**
837
	 * Get all the available shared stores.
838
	 *
839
	 * The store is opened only once, subsequent calls will return the previous store object
840
	 */
841
	public function getOtherUserStore() {
842
		$otherusers = $this->retrieveOtherUsersFromSettings();
843
		$otherUsersStores = [];
844
845
		foreach ($otherusers as $username => $folder) {
846
			if (isset($this->userstores[$username])) {
847
				continue;
848
			}
849
			$storeOk = true;
850
851
			if (is_array($folder) && !empty($folder)) {
852
				try {
853
					$user_entryid = mapi_msgstore_createentryid($this->getDefaultMessageStore(), $username);
854
855
					$sharedStore = $this->openMessageStore($user_entryid, $username);
856
					if ($sharedStore === false && $sharedStore === ecLoginPerm &&
857
						$sharedStore === MAPI_E_CALL_FAILED && $sharedStore === MAPI_E_NOT_FOUND) {
858
						$storeOk = false;
859
					}
860
861
					// Check if an entire store will be loaded, if so load the archive store as well
862
					if ($storeOk && isset($folder['all']) && $folder['all']['folder_type'] == 'all') {
863
						$this->getArchivedStores($this->resolveStrictUserName($username));
864
					}
865
				}
866
				catch (MAPIException $e) {
867
					if ($e->getCode() == MAPI_E_NOT_FOUND) {
868
						// The user or the corresponding store couldn't be found,
869
						// print an error to the log, and remove the user from the settings.
870
						dump('Failed to load store for user ' . $username . ', user was not found. Removing it from settings.');
871
						$GLOBALS["settings"]->delete("zarafa/v1/contexts/hierarchy/shared_stores/" . bin2hex($username), true);
872
					}
873
					else {
874
						// That is odd, something else went wrong. Lets not be hasty and preserve
875
						// the user in the settings, but do print something to the log to indicate
876
						// something happened...
877
						dump('Failed to load store for user ' . $username . '. ' . $e->getDisplayMessage());
878
					}
879
				}
880
				finally {
881
					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...
882
						// The user or the corresponding store couldn't be opened
883
						// (e.g. the user was deleted or permissions revoked),
884
						// print an error to the log, and remove the user from the settings.
885
						dump(sprintf("The user %s failed to load store of the user %s. Removing it from settings.", $this->session_info["username"], $username));
886
						$GLOBALS["settings"]->delete("zarafa/v1/contexts/hierarchy/shared_stores/" . bin2hex($username), true);
887
					}
888
				}
889
			}
890
		}
891
892
		foreach ($this->userstores as $entryid) {
893
			$otherUsersStores[$entryid] = $this->stores[$entryid];
894
		}
895
896
		return $otherUsersStores;
897
	}
898
899
	/**
900
	 * Resolve the username strictly by opening that user's store and returning the
901
	 * PR_MAILBOX_OWNER_ENTRYID. This can be used for resolving an username without the risk of
902
	 * ambiguity since mapi_ab_resolve() does not strictly resolve on the username.
903
	 *
904
	 * @param string $username The username
905
	 *
906
	 * @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...
907
	 */
908
	public function resolveStrictUserName($username) {
909
		$storeEntryid = mapi_msgstore_createentryid($this->getDefaultMessageStore(), $username);
910
		$store = $this->openMessageStore($storeEntryid, $username);
911
		$storeProps = mapi_getprops($store, [PR_MAILBOX_OWNER_ENTRYID]);
912
913
		return $storeProps[PR_MAILBOX_OWNER_ENTRYID];
914
	}
915
916
	/**
917
	 * Get other users from settings.
918
	 *
919
	 * @return array Array of usernames of delegate stores
920
	 */
921
	public function retrieveOtherUsersFromSettings() {
922
		$other_users = $GLOBALS["settings"]->get("zarafa/v1/contexts/hierarchy/shared_stores", []);
923
		$result = [];
924
		foreach ($other_users as $username => $folders) {
925
			// No folders are being shared, the store has probably been closed by the user,
926
			// but the username is still lingering in the settings...
927
			if (!isset($folders) || empty($folders)) {
928
				continue;
929
			}
930
931
			$username = strtolower(hex2bin($username));
932
			if (!isset($result[$username])) {
933
				$result[$username] = [];
934
			}
935
936
			foreach ($folders as $folder) {
937
				if (is_array($folder)) {
938
					$result[$username][$folder["folder_type"]] = [];
939
					$result[$username][$folder["folder_type"]]["folder_type"] = $folder["folder_type"];
940
					$result[$username][$folder["folder_type"]]["show_subfolders"] = $folder["show_subfolders"];
941
				}
942
			}
943
		}
944
945
		return $result;
946
	}
947
948
	/**
949
	 * Add the store of another user to the list of other user stores.
950
	 *
951
	 * @param string $username The username whose store should be added to the list of other users' stores
952
	 *
953
	 * @return mapistore The store of the user or false on error;
954
	 */
955
	public function addUserStore($username) {
956
		$user_entryid = mapi_msgstore_createentryid($this->getDefaultMessageStore(), $username);
957
958
		if ($user_entryid) {
959
			// mapi_msgstore_createentryid and mapi_getprops(PR_ENTRYID) have different
960
			// values for shared stores, so save the one from mapi_getprops(PR_ENTRYID)
961
			// $this->userstores[$username] = $user_entryid;
962
963
			return $this->openMessageStore($user_entryid, $username);
964
		}
965
	}
966
967
	/**
968
	 * Remove the store of another user from the list of other user stores.
969
	 *
970
	 * @param string $username The username whose store should be deleted from the list of other users' stores
971
	 *
972
	 * @return string The entryid of the store which was removed
973
	 */
974
	public function removeUserStore($username) {
975
		// Remove the reference to the store if we had one
976
		if (isset($this->userstores[$username])) {
977
			$entryid = $this->userstores[$username];
978
			unset($this->userstores[$username], $this->stores[$entryid]);
979
980
			return $entryid;
981
		}
982
	}
983
984
	/**
985
	 * Get the store entryid of the specified user.
986
	 *
987
	 * The store must have been previously added via addUserStores.
988
	 *
989
	 * @param string $username The username whose store is being looked up
990
	 *
991
	 * @return string The entryid of the store of the user
992
	 */
993
	public function getStoreEntryIdOfUser($username) {
994
		return $this->userstores[$username];
995
	}
996
997
	/**
998
	 * Get the username of the user store.
999
	 *
1000
	 * @param string $username the loginname of whom we want to full name
1001
	 *
1002
	 * @return string the display name of the user
1003
	 */
1004
	public function getDisplayNameofUser($username) {
1005
		$user_entryid = $this->getStoreEntryIdOfUser($username);
1006
		$store = $this->openMessageStore($user_entryid, $username);
1007
		$props = mapi_getprops($store, [PR_DISPLAY_NAME]);
1008
1009
		return str_replace('Inbox - ', '', $props[PR_DISPLAY_NAME]);
1010
	}
1011
1012
	/**
1013
	 * Get the username of the owner of the specified store.
1014
	 *
1015
	 * The store must have been previously added via addUserStores.
1016
	 *
1017
	 * @param string $entryid EntryID of the store
1018
	 *
1019
	 * @return string Username of the specified store or false if it is not found
1020
	 */
1021
	public function getUserNameOfStore($entryid) {
1022
		foreach ($this->userstores as $username => $storeentryid) {
1023
			if ($GLOBALS["entryid"]->compareEntryIds(bin2hex($storeentryid), bin2hex($entryid))) {
1024
				return $username;
1025
			}
1026
		}
1027
1028
		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...
1029
	}
1030
1031
	/**
1032
	 * Open a MAPI message using session object.
1033
	 * The function is used to open message when we dont' know
1034
	 * the specific store and we want to open message using entryid.
1035
	 *
1036
	 * @param string $entryid entryid of the message
1037
	 *
1038
	 * @return object MAPI Message
1039
	 */
1040
	public function openMessage($entryid) {
1041
		return mapi_openentry($this->session, $entryid);
1042
	}
1043
1044
	/**
1045
	 * Setup the contact provider for the addressbook. It asks getContactFoldersForABContactProvider
1046
	 * for the entryids and display names for the contact folders in the user's store.
1047
	 *
1048
	 * @param bool $loadSharedContactsProvider when set to true it denotes that shared folders are
1049
	 *                                         required to be configured to load the contacts from
1050
	 */
1051
	public function setupContactProviderAddressbook($loadSharedContactsProvider) {
1052
		$profsect = mapi_openprofilesection($GLOBALS['mapisession']->getSession(), pbGlobalProfileSectionGuid);
1053
		if ($profsect) {
1054
			// Get information about all contact folders from own store, shared stores and public store
1055
			$defaultStore = $this->getDefaultMessageStore();
1056
			$contactFolders = $this->getContactFoldersForABContactProvider($defaultStore);
1057
1058
			// include shared contact folders in addressbook if shared contact folders are enabled
1059
			if (ENABLE_SHARED_CONTACT_FOLDERS && $loadSharedContactsProvider) {
1060
				if (empty($this->userstores)) {
1061
					$this->getOtherUserStore();
1062
				}
1063
1064
				$sharedSetting = $GLOBALS["settings"]->get("zarafa/v1/contexts/hierarchy/shared_stores", []);
1065
				// Find available contact folders from all user stores, one by one.
1066
				foreach ($this->userstores as $username => $storeEntryID) {
1067
					$userContactFolders = [];
1068
					$sharedUserSetting = [];
1069
					$openedUserStore = $this->openMessageStore($storeEntryID, $username);
1070
1071
					// Get settings of respective shared folder of given user
1072
					if (array_key_exists(strtolower(bin2hex($username)), $sharedSetting)) {
1073
						$sharedUserSetting = $sharedSetting[strtolower(bin2hex($username))];
1074
					}
1075
1076
					// Only add opened shared folders into addressbook contacts provider.
1077
					// If entire inbox is opened then add each and every contact folders of that particular user.
1078
					if (isset($sharedUserSetting['all'])) {
1079
						$userContactFolders = $this->getContactFoldersForABContactProvider($openedUserStore);
1080
					}
1081
					elseif (isset($sharedUserSetting['contact'])) {
1082
						// Add respective default contact folder which is opened.
1083
						// Get entryid of default contact folder from root.
1084
						$root = mapi_msgstore_openentry($openedUserStore);
1085
						$rootProps = mapi_getprops($root, [PR_IPM_CONTACT_ENTRYID]);
1086
1087
						// Just add the default contact folder only.
1088
						$defaultContactFolder = [
1089
							PR_STORE_ENTRYID => $storeEntryID,
1090
							PR_ENTRYID => $rootProps[PR_IPM_CONTACT_ENTRYID],
1091
							PR_DISPLAY_NAME => _("Contacts"),
1092
						];
1093
						array_push($userContactFolders, $defaultContactFolder);
1094
1095
						// Go for sub folders only if configured in settings
1096
						if ($sharedUserSetting['contact']['show_subfolders'] == true) {
1097
							$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

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

1160
			$contactFolders = $this->getContactFolders($store, $storeProps[PR_IPM_SUBTREE_ENTRYID], /** @scrutinizer ignore-type */ false);
Loading history...
1161
		}
1162
		catch (Exception $e) {
1163
			return $contactFolders;
1164
		}
1165
1166
		// Need to search all the contact-subfolders within first level contact folders.
1167
		$firstLevelHierarchyNodes = $contactFolders;
1168
		foreach ($firstLevelHierarchyNodes as $firstLevelNode) {
1169
			// To search for multiple levels CONVENIENT_DEPTH needs to be passed as well.
1170
			$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

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