Passed
Push — master ( 6522b6...8c95d3 )
by
unknown
04:16 queued 01:06
created

Grommunio::GetPublicSyncEnabledFolders()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 30
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 23
nc 3
nop 0
dl 0
loc 30
rs 9.552
c 0
b 0
f 0
1
<?php
2
/*
3
 * SPDX-License-Identifier: AGPL-3.0-only
4
 * SPDX-FileCopyrightText: Copyright 2007-2016 Zarafa Deutschland GmbH
5
 * SPDX-FileCopyrightText: Copyright 2020-2022 grommunio GmbH
6
 *
7
 * This is a backend for grommunio. It is an implementation of IBackend and also
8
 * implements ISearchProvider to search in the grommunio system. The backend
9
 * implements IStateMachine as well to save the devices' information in the
10
 * user's store and extends InterProcessData to access Redis.
11
 */
12
13
// include PHP-MAPI classes
14
include_once 'mapi/mapi.util.php';
15
include_once 'mapi/mapidefs.php';
16
include_once 'mapi/mapitags.php';
17
include_once 'mapi/mapiguid.php';
18
19
// setlocale to UTF-8 in order to support properties containing Unicode characters
20
setlocale(LC_CTYPE, "en_US.UTF-8");
21
22
class Grommunio extends InterProcessData implements IBackend, ISearchProvider, IStateMachine {
23
	private $mainUser;
24
	private $session;
25
	private $defaultstore;
26
	private $store;
27
	private $storeName;
28
	private $storeCache;
29
	private $notifications;
30
	private $changesSink;
31
	private $changesSinkFolders;
32
	private $changesSinkHierarchyHash;
33
	private $changesSinkStores;
34
	private $wastebasket;
35
	private $addressbook;
36
	private $folderStatCache;
37
	private $impersonateUser;
38
	private $stateFolder;
39
	private $userDeviceData;
40
41
	// KC config parameter for PR_EC_ENABLED_FEATURES / PR_EC_DISABLED_FEATURES
42
	public const MOBILE_ENABLED = 'mobile';
43
44
	public const MAXAMBIGUOUSRECIPIENTS = 9999;
45
	public const FREEBUSYENUMBLOCKS = 50;
46
	public const MAXFREEBUSYSLOTS = 32767; // max length of 32k for the MergedFreeBusy element is allowed
47
	public const HALFHOURSECONDS = 1800;
48
49
	/**
50
	 * Constructor of the grommunio Backend.
51
	 */
52
	public function __construct() {
53
		$this->session = false;
54
		$this->store = false;
55
		$this->storeName = false;
56
		$this->storeCache = [];
57
		$this->notifications = false;
58
		$this->changesSink = false;
59
		$this->changesSinkFolders = [];
60
		$this->changesSinkStores = [];
61
		$this->changesSinkHierarchyHash = false;
62
		$this->wastebasket = false;
63
		$this->session = false;
64
		$this->folderStatCache = [];
65
		$this->impersonateUser = false;
66
		$this->stateFolder = null;
67
68
		SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio using PHP-MAPI version: %s - PHP version: %s", phpversion("mapi"), phpversion()));
69
70
		# Interprocessdata
71
		$this->allocate = 0;
72
		$this->type = "grommunio-sync:userdevices";
73
		$this->userDeviceData = "grommunio-sync:statefoldercache";
74
		parent::__construct();
75
	}
76
77
	/**
78
	 * Indicates which StateMachine should be used.
79
	 *
80
	 * @return bool Grommunio uses own state machine
81
	 */
82
	public function GetStateMachine() {
83
		return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type Grommunio which is incompatible with the documented return type boolean.
Loading history...
84
	}
85
86
	/**
87
	 * Returns the Grommunio as it implements the ISearchProvider interface
88
	 * This could be overwritten by the global configuration.
89
	 *
90
	 * @return object Implementation of ISearchProvider
91
	 */
92
	public function GetSearchProvider() {
93
		return $this;
94
	}
95
96
	/**
97
	 * Indicates which AS version is supported by the backend.
98
	 *
99
	 * @return string AS version constant
100
	 */
101
	public function GetSupportedASVersion() {
102
		return GSync::ASV_141;
103
	}
104
105
	/**
106
	 * Authenticates the user with the configured grommunio server.
107
	 *
108
	 * @param string $username
109
	 * @param string $domain
110
	 * @param string $password
111
	 * @param mixed  $user
112
	 * @param mixed  $pass
113
	 *
114
	 * @throws AuthenticationRequiredException
115
	 *
116
	 * @return bool
117
	 */
118
	public function Logon($user, $domain, $pass) {
119
		SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->Logon(): Trying to authenticate user '%s'..", $user));
120
121
		$this->mainUser = strtolower($user);
122
		// TODO the impersonated user should be passed directly to IBackend->Logon() - ZP-1351
123
		if (Request::GetImpersonatedUser()) {
124
			$this->impersonateUser = strtolower(Request::GetImpersonatedUser());
0 ignored issues
show
Bug introduced by
It seems like Request::GetImpersonatedUser() can also be of type boolean; however, parameter $string of strtolower() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

124
			$this->impersonateUser = strtolower(/** @scrutinizer ignore-type */ Request::GetImpersonatedUser());
Loading history...
125
		}
126
127
		// check if we are impersonating someone
128
		// $defaultUser will be used for $this->defaultStore
129
		if ($this->impersonateUser !== false) {
130
			SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->Logon(): Impersonation active - authenticating: '%s' - impersonating '%s'", $this->mainUser, $this->impersonateUser));
0 ignored issues
show
Bug introduced by
$this->impersonateUser of type true is incompatible with the type double|integer|string expected by parameter $values of sprintf(). ( Ignorable by Annotation )

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

130
			SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->Logon(): Impersonation active - authenticating: '%s' - impersonating '%s'", $this->mainUser, /** @scrutinizer ignore-type */ $this->impersonateUser));
Loading history...
131
			$defaultUser = $this->impersonateUser;
132
		}
133
		else {
134
			$defaultUser = $this->mainUser;
135
		}
136
137
		$deviceId = Request::GetDeviceID();
138
139
		try {
140
			// check if notifications are available in php-mapi
141
			if (function_exists('mapi_feature') && mapi_feature('LOGONFLAGS')) {
142
				// send grommunio-sync version and user agent to ZCP - ZP-589
143
				if (Utils::CheckMapiExtVersion('7.2.0')) {
144
					$gsync_version = 'Grommunio-Sync_' . @constant('GROMMUNIOSYNC_VERSION');
145
					$user_agent = ($deviceId) ? GSync::GetDeviceManager()->GetUserAgent() : "unknown";
146
					$this->session = @mapi_logon_zarafa($this->mainUser, $pass, MAPI_SERVER, null, null, 0, $gsync_version, $user_agent);
147
				}
148
				else {
149
					$this->session = @mapi_logon_zarafa($this->mainUser, $pass, MAPI_SERVER, null, null, 0);
150
				}
151
				$this->notifications = true;
152
			}
153
			// old fashioned session
154
			else {
155
				$this->session = @mapi_logon_zarafa($this->mainUser, $pass, MAPI_SERVER);
156
				$this->notifications = false;
157
			}
158
159
			if (mapi_last_hresult()) {
160
				SLog::Write(LOGLEVEL_ERROR, sprintf("Grommunio->Logon(): login failed with error code: 0x%X", mapi_last_hresult()));
161
				if (mapi_last_hresult() == MAPI_E_NETWORK_ERROR) {
0 ignored issues
show
Bug introduced by
The constant MAPI_E_NETWORK_ERROR was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
162
					throw new ServiceUnavailableException("Error connecting to KC (login)");
163
				}
164
			}
165
		}
166
		catch (MAPIException $ex) {
167
			throw new AuthenticationRequiredException($ex->getDisplayMessage());
168
		}
169
170
		if (!$this->session) {
171
			SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->Logon(): logon failed for user '%s'", $this->mainUser));
172
			$this->defaultstore = false;
173
174
			return false;
175
		}
176
177
		// Get/open default store
178
		$this->defaultstore = $this->openMessageStore($this->mainUser);
179
180
		// To impersonate, we overwrite the defaultstore. We still need to open it before we can do that.
181
		if ($this->impersonateUser) {
182
			SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->Logon(): Impersonating user '%s'", $defaultUser));
183
			$this->defaultstore = $this->openMessageStore($defaultUser);
0 ignored issues
show
Bug introduced by
It seems like $defaultUser can also be of type true; however, parameter $user of Grommunio::openMessageStore() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

183
			$this->defaultstore = $this->openMessageStore(/** @scrutinizer ignore-type */ $defaultUser);
Loading history...
184
		}
185
186
		if (mapi_last_hresult() == MAPI_E_FAILONEPROVIDER) {
0 ignored issues
show
Bug introduced by
The constant MAPI_E_FAILONEPROVIDER was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
187
			throw new ServiceUnavailableException("Error connecting to KC (open store)");
188
		}
189
190
		if ($this->defaultstore === false) {
191
			throw new AuthenticationRequiredException(sprintf("Grommunio->Logon(): User '%s' has no default store", $defaultUser));
192
		}
193
194
		$this->store = $this->defaultstore;
195
		$this->storeName = $defaultUser;
196
197
		SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->Logon(): User '%s' is authenticated%s", $this->mainUser, ($this->impersonateUser ? " impersonating '" . $this->impersonateUser . "'" : '')));
0 ignored issues
show
Bug introduced by
Are you sure $this->impersonateUser of type true can be used in concatenation? ( Ignorable by Annotation )

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

197
		SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->Logon(): User '%s' is authenticated%s", $this->mainUser, ($this->impersonateUser ? " impersonating '" . /** @scrutinizer ignore-type */ $this->impersonateUser . "'" : '')));
Loading history...
198
199
		$this->isGSyncEnabled();
200
201
		// check if this is a Zarafa 7 store with unicode support
202
		MAPIUtils::IsUnicodeStore($this->store);
0 ignored issues
show
Bug introduced by
$this->store of type true is incompatible with the type MAPIStore expected by parameter $store of MAPIUtils::IsUnicodeStore(). ( Ignorable by Annotation )

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

202
		MAPIUtils::IsUnicodeStore(/** @scrutinizer ignore-type */ $this->store);
Loading history...
203
204
		// open the state folder
205
		$this->getStateFolder($deviceId);
206
207
		return true;
208
	}
209
210
	/**
211
	 * Setup the backend to work on a specific store or checks ACLs there.
212
	 * If only the $store is submitted, all Import/Export/Fetch/Etc operations should be
213
	 * performed on this store (switch operations store).
214
	 * If the ACL check is enabled, this operation should just indicate the ACL status on
215
	 * the submitted store, without changing the store for operations.
216
	 * For the ACL status, the currently logged on user MUST have access rights on
217
	 *  - the entire store - admin access if no folderid is sent, or
218
	 *  - on a specific folderid in the store (secretary/full access rights).
219
	 *
220
	 * The ACLcheck MUST fail if a folder of the authenticated user is checked!
221
	 *
222
	 * @param string $store        target store, could contain a "domain\user" value
223
	 * @param bool   $checkACLonly if set to true, Setup() should just check ACLs
224
	 * @param string $folderid     if set, only ACLs on this folderid are relevant
225
	 *
226
	 * @return bool
227
	 */
228
	public function Setup($store, $checkACLonly = false, $folderid = false) {
229
		list($user, $domain) = Utils::SplitDomainUser($store);
230
231
		if (!isset($this->mainUser)) {
232
			return false;
233
		}
234
235
		$mainUser = $this->mainUser;
236
		// when impersonating we need to check against the impersonated user
237
		if ($this->impersonateUser) {
238
			$mainUser = $this->impersonateUser;
239
		}
240
241
		if ($user === false) {
0 ignored issues
show
introduced by
The condition $user === false is always false.
Loading history...
242
			$user = $mainUser;
243
		}
244
245
		// This is a special case. A user will get his entire folder structure by the foldersync by default.
246
		// The ACL check is executed when an additional folder is going to be sent to the mobile.
247
		// Configured that way the user could receive the same folderid twice, with two different names.
248
		if ($mainUser == $user && $checkACLonly && $folderid && !$this->impersonateUser) {
249
			SLog::Write(LOGLEVEL_DEBUG, "Grommunio->Setup(): Checking ACLs for folder of the users defaultstore. Fail is forced to avoid folder duplications on mobile.");
250
251
			return false;
252
		}
253
254
		// get the users store
255
		$userstore = $this->openMessageStore($user);
256
257
		// only proceed if a store was found, else return false
258
		if ($userstore) {
259
			// only check permissions
260
			if ($checkACLonly === true) {
261
				// check for admin rights
262
				if (!$folderid) {
263
					if ($user != $this->mainUser) {
264
						if ($this->impersonateUser) {
265
							$storeProps = mapi_getprops($userstore, [PR_IPM_SUBTREE_ENTRYID]);
0 ignored issues
show
Bug introduced by
The constant PR_IPM_SUBTREE_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
266
							$rights = $this->HasSecretaryACLs($userstore, '', $storeProps[PR_IPM_SUBTREE_ENTRYID]);
0 ignored issues
show
Bug introduced by
$userstore of type true is incompatible with the type resource expected by parameter $store of Grommunio::HasSecretaryACLs(). ( Ignorable by Annotation )

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

266
							$rights = $this->HasSecretaryACLs(/** @scrutinizer ignore-type */ $userstore, '', $storeProps[PR_IPM_SUBTREE_ENTRYID]);
Loading history...
267
							SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->Setup(): Checking for secretary ACLs on root folder of impersonated store '%s': '%s'", $user, Utils::PrintAsString($rights)));
268
						}
269
						else {
270
							$zarafauserinfo = @nsp_getuserinfo($this->mainUser);
271
							$rights = (isset($zarafauserinfo['admin']) && $zarafauserinfo['admin']) ? true : false;
272
							SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->Setup(): Checking for admin ACLs on store '%s': '%s'", $user, Utils::PrintAsString($rights)));
273
						}
274
					}
275
					// the user has always full access to his own store
276
					else {
277
						$rights = true;
278
						SLog::Write(LOGLEVEL_DEBUG, "Grommunio->Setup(): the user has always full access to his own store");
279
					}
280
281
					return $rights;
282
				}
283
				// check permissions on this folder
284
285
				$rights = $this->HasSecretaryACLs($userstore, $folderid);
286
				SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->Setup(): Checking for secretary ACLs on '%s' of store '%s': '%s'", $folderid, $user, Utils::PrintAsString($rights)));
287
288
				return $rights;
289
			}
290
291
			// switch operations store
292
			// this should also be done if called with user = mainuser or user = false
293
			// which means to switch back to the default store
294
295
			// switch active store
296
			$this->store = $userstore;
297
			$this->storeName = $user;
298
299
			return true;
300
		}
301
302
		return false;
303
	}
304
305
	/**
306
	 * Logs off
307
	 * Free/Busy information is updated for modified calendars
308
	 * This is done after the synchronization process is completed.
309
	 *
310
	 * @return bool
311
	 */
312
	public function Logoff() {
313
		return true;
314
	}
315
316
	/**
317
	 * Returns an array of SyncFolder types with the entire folder hierarchy
318
	 * on the server (the array itself is flat, but refers to parents via the 'parent' property.
319
	 *
320
	 * provides AS 1.0 compatibility
321
	 *
322
	 * @return array SYNC_FOLDER
323
	 */
324
	public function GetHierarchy() {
325
		$folders = [];
326
		$mapiprovider = new MAPIProvider($this->session, $this->store);
0 ignored issues
show
Bug introduced by
$this->session of type boolean is incompatible with the type resource expected by parameter $session of MAPIProvider::__construct(). ( Ignorable by Annotation )

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

326
		$mapiprovider = new MAPIProvider(/** @scrutinizer ignore-type */ $this->session, $this->store);
Loading history...
Bug introduced by
$this->store of type boolean is incompatible with the type resource expected by parameter $store of MAPIProvider::__construct(). ( Ignorable by Annotation )

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

326
		$mapiprovider = new MAPIProvider($this->session, /** @scrutinizer ignore-type */ $this->store);
Loading history...
327
		$storeProps = $mapiprovider->GetStoreProps();
328
329
		// for SYSTEM user open the public folders
330
		if (strtoupper($this->storeName) == "SYSTEM") {
0 ignored issues
show
Bug introduced by
$this->storeName of type boolean is incompatible with the type string expected by parameter $string of strtoupper(). ( Ignorable by Annotation )

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

330
		if (strtoupper(/** @scrutinizer ignore-type */ $this->storeName) == "SYSTEM") {
Loading history...
331
			$rootfolder = mapi_msgstore_openentry($this->store, $storeProps[PR_IPM_PUBLIC_FOLDERS_ENTRYID]);
0 ignored issues
show
Bug introduced by
The constant PR_IPM_PUBLIC_FOLDERS_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
332
		}
333
		else {
334
			$rootfolder = mapi_msgstore_openentry($this->store);
335
		}
336
337
		$rootfolderprops = mapi_getprops($rootfolder, [PR_SOURCE_KEY]);
0 ignored issues
show
Bug introduced by
The constant PR_SOURCE_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
338
339
		$hierarchy = mapi_folder_gethierarchytable($rootfolder, CONVENIENT_DEPTH);
340
		$rows = mapi_table_queryallrows($hierarchy, [PR_DISPLAY_NAME, PR_PARENT_ENTRYID, PR_ENTRYID, PR_SOURCE_KEY, PR_PARENT_SOURCE_KEY, PR_CONTAINER_CLASS, PR_ATTR_HIDDEN, PR_EXTENDED_FOLDER_FLAGS, PR_FOLDER_TYPE]);
0 ignored issues
show
Bug introduced by
The constant PR_CONTAINER_CLASS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ATTR_HIDDEN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_PARENT_SOURCE_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_PARENT_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_FOLDER_TYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_EXTENDED_FOLDER_FLAGS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
341
		SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->GetHierarchy(): fetched %d folders from MAPI", count($rows)));
342
343
		foreach ($rows as $row) {
344
			// do not display hidden and search folders
345
			if ((isset($row[PR_ATTR_HIDDEN]) && $row[PR_ATTR_HIDDEN]) ||
346
				(isset($row[PR_FOLDER_TYPE]) && $row[PR_FOLDER_TYPE] == FOLDER_SEARCH) ||
347
				// for SYSTEM user $row[PR_PARENT_SOURCE_KEY] == $rootfolderprops[PR_SOURCE_KEY] is true, but we need those folders
348
				(isset($row[PR_PARENT_SOURCE_KEY]) && $row[PR_PARENT_SOURCE_KEY] == $rootfolderprops[PR_SOURCE_KEY] && strtoupper($this->storeName) != "SYSTEM")) {
349
				SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->GetHierarchy(): ignoring folder '%s' as it's a hidden/search/root folder", (isset($row[PR_DISPLAY_NAME]) ? $row[PR_DISPLAY_NAME] : "unknown")));
350
351
				continue;
352
			}
353
			$folder = $mapiprovider->GetFolder($row);
354
			if ($folder) {
355
				$folders[] = $folder;
356
			}
357
			else {
358
				SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->GetHierarchy(): ignoring folder '%s' as MAPIProvider->GetFolder() did not return a SyncFolder object", (isset($row[PR_DISPLAY_NAME]) ? $row[PR_DISPLAY_NAME] : "unknown")));
359
			}
360
		}
361
362
		SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->GetHierarchy(): processed %d folders, starting parent remap", count($folders)));
363
		// reloop the folders to make sure all parentids are mapped correctly
364
		$dm = GSync::GetDeviceManager();
365
		foreach ($folders as $folder) {
366
			if ($folder->parentid !== "0") {
367
				// SYSTEM user's parentid points to $rootfolderprops[PR_SOURCE_KEY], but they need to be on the top level
368
				$folder->parentid = (strtoupper($this->storeName) == "SYSTEM" && $folder->parentid == bin2hex($rootfolderprops[PR_SOURCE_KEY])) ? '0' : $dm->GetFolderIdForBackendId($folder->parentid);
369
			}
370
		}
371
372
		return $folders;
373
	}
374
375
	/**
376
	 * Returns the importer to process changes from the mobile
377
	 * If no $folderid is given, hierarchy importer is expected.
378
	 *
379
	 * @param string $folderid (opt)
380
	 *
381
	 * @return object(ImportChanges)
382
	 */
383
	public function GetImporter($folderid = false) {
384
		SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->GetImporter() folderid: '%s'", Utils::PrintAsString($folderid)));
0 ignored issues
show
Bug introduced by
It seems like $folderid can also be of type false; however, parameter $var of Utils::PrintAsString() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

384
		SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->GetImporter() folderid: '%s'", Utils::PrintAsString(/** @scrutinizer ignore-type */ $folderid)));
Loading history...
385
		if ($folderid !== false) {
386
			// check if the user of the current store has permissions to import to this folderid
387
			if ($this->storeName != $this->mainUser && !$this->hasSecretaryACLs($this->store, $folderid)) {
0 ignored issues
show
Bug introduced by
$this->store of type boolean is incompatible with the type resource expected by parameter $store of Grommunio::HasSecretaryACLs(). ( Ignorable by Annotation )

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

387
			if ($this->storeName != $this->mainUser && !$this->hasSecretaryACLs(/** @scrutinizer ignore-type */ $this->store, $folderid)) {
Loading history...
388
				SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->GetImporter(): missing permissions on folderid: '%s'.", Utils::PrintAsString($folderid)));
389
390
				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 object.
Loading history...
391
			}
392
393
			return new ImportChangesICS($this->session, $this->store, hex2bin($folderid));
0 ignored issues
show
Bug introduced by
$this->store of type boolean is incompatible with the type mapistore expected by parameter $store of ImportChangesICS::__construct(). ( Ignorable by Annotation )

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

393
			return new ImportChangesICS($this->session, /** @scrutinizer ignore-type */ $this->store, hex2bin($folderid));
Loading history...
Bug introduced by
$this->session of type boolean is incompatible with the type mapisession expected by parameter $session of ImportChangesICS::__construct(). ( Ignorable by Annotation )

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

393
			return new ImportChangesICS(/** @scrutinizer ignore-type */ $this->session, $this->store, hex2bin($folderid));
Loading history...
394
		}
395
396
		return new ImportChangesICS($this->session, $this->store);
397
	}
398
399
	/**
400
	 * Returns the exporter to send changes to the mobile
401
	 * If no $folderid is given, hierarchy exporter is expected.
402
	 *
403
	 * @param string $folderid (opt)
404
	 *
405
	 * @throws StatusException
406
	 *
407
	 * @return object(ExportChanges)
408
	 */
409
	public function GetExporter($folderid = false) {
410
		if ($folderid !== false) {
411
			// check if the user of the current store has permissions to export from this folderid
412
			if ($this->storeName != $this->mainUser && !$this->hasSecretaryACLs($this->store, $folderid)) {
0 ignored issues
show
Bug introduced by
$this->store of type boolean is incompatible with the type resource expected by parameter $store of Grommunio::HasSecretaryACLs(). ( Ignorable by Annotation )

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

412
			if ($this->storeName != $this->mainUser && !$this->hasSecretaryACLs(/** @scrutinizer ignore-type */ $this->store, $folderid)) {
Loading history...
413
				SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->GetExporter(): missing permissions on folderid: '%s'.", Utils::PrintAsString($folderid)));
414
415
				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 object.
Loading history...
416
			}
417
418
			return new ExportChangesICS($this->session, $this->store, hex2bin($folderid));
0 ignored issues
show
Bug introduced by
$this->store of type boolean is incompatible with the type mapistore expected by parameter $store of ExportChangesICS::__construct(). ( Ignorable by Annotation )

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

418
			return new ExportChangesICS($this->session, /** @scrutinizer ignore-type */ $this->store, hex2bin($folderid));
Loading history...
Bug introduced by
$this->session of type boolean is incompatible with the type mapisession expected by parameter $session of ExportChangesICS::__construct(). ( Ignorable by Annotation )

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

418
			return new ExportChangesICS(/** @scrutinizer ignore-type */ $this->session, $this->store, hex2bin($folderid));
Loading history...
419
		}
420
421
		return new ExportChangesICS($this->session, $this->store);
422
	}
423
424
	/**
425
	 * Sends an e-mail
426
	 * This messages needs to be saved into the 'sent items' folder.
427
	 *
428
	 * @param SyncSendMail $sm SyncSendMail object
429
	 *
430
	 * @throws StatusException
431
	 *
432
	 * @return bool
433
	 */
434
	public function SendMail($sm) {
435
		// Check if imtomapi function is available and use it to send the mime message.
436
		// It is available since ZCP 7.0.6
437
		// @see http://jira.zarafa.com/browse/ZCP-9508
438
		if (!(function_exists('mapi_feature') && mapi_feature('INETMAPI_IMTOMAPI'))) {
439
			throw new StatusException("Grommunio->SendMail(): ZCP/KC version is too old, INETMAPI_IMTOMAPI is not available. Install at least ZCP version 7.0.6 or later.", SYNC_COMMONSTATUS_MAILSUBMISSIONFAILED, null, LOGLEVEL_FATAL);
440
441
			return false;
0 ignored issues
show
Unused Code introduced by
return false is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
442
		}
443
		$mimeLength = strlen($sm->mime);
444
		SLog::Write(LOGLEVEL_DEBUG, sprintf(
445
			"Grommunio->SendMail(): RFC822: %d bytes  forward-id: '%s' reply-id: '%s' parent-id: '%s' SaveInSent: '%s' ReplaceMIME: '%s'",
446
			$mimeLength,
447
			Utils::PrintAsString($sm->forwardflag),
448
			Utils::PrintAsString($sm->replyflag),
449
			Utils::PrintAsString((isset($sm->source->folderid) ? $sm->source->folderid : false)),
0 ignored issues
show
Bug introduced by
It seems like IssetNode ? $sm->source->folderid : false can also be of type false; however, parameter $var of Utils::PrintAsString() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

449
			Utils::PrintAsString(/** @scrutinizer ignore-type */ (isset($sm->source->folderid) ? $sm->source->folderid : false)),
Loading history...
450
			Utils::PrintAsString(($sm->saveinsent)),
451
			Utils::PrintAsString(isset($sm->replacemime))
452
		));
453
		if ($mimeLength == 0) {
454
			throw new StatusException("Grommunio->SendMail(): empty mail data", SYNC_COMMONSTATUS_MAILSUBMISSIONFAILED);
455
		}
456
457
		$sendMailProps = MAPIMapping::GetSendMailProperties();
458
		$sendMailProps = getPropIdsFromStrings($this->defaultstore, $sendMailProps);
459
460
		// Open the outbox and create the message there
461
		$storeprops = mapi_getprops($this->defaultstore, [$sendMailProps["outboxentryid"], $sendMailProps["ipmsentmailentryid"]]);
462
		if (isset($storeprops[$sendMailProps["outboxentryid"]])) {
463
			$outbox = mapi_msgstore_openentry($this->defaultstore, $storeprops[$sendMailProps["outboxentryid"]]);
464
		}
465
466
		if (!$outbox) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $outbox does not seem to be defined for all execution paths leading up to this point.
Loading history...
467
			throw new StatusException(sprintf("Grommunio->SendMail(): No Outbox found or unable to create message: 0x%X", mapi_last_hresult()), SYNC_COMMONSTATUS_SERVERERROR);
468
		}
469
470
		$mapimessage = mapi_folder_createmessage($outbox);
471
472
		// message properties to be set
473
		$mapiprops = [];
474
		// only save the outgoing in sent items folder if the mobile requests it
475
		$mapiprops[$sendMailProps["sentmailentryid"]] = $storeprops[$sendMailProps["ipmsentmailentryid"]];
476
477
		SLog::Write(LOGLEVEL_DEBUG, "Grommunio->SendMail(): Use the mapi_inetmapi_imtomapi function");
478
		$ab = mapi_openaddressbook($this->session);
479
		mapi_inetmapi_imtomapi($this->session, $this->defaultstore, $ab, $mapimessage, $sm->mime, []);
480
481
		// Set the appSeqNr so that tracking tab can be updated for meeting request updates
482
		// @see http://jira.zarafa.com/browse/ZP-68
483
		$meetingRequestProps = MAPIMapping::GetMeetingRequestProperties();
484
		$meetingRequestProps = getPropIdsFromStrings($this->defaultstore, $meetingRequestProps);
485
		$props = mapi_getprops($mapimessage, [PR_MESSAGE_CLASS, $meetingRequestProps["goidtag"], $sendMailProps["internetcpid"], $sendMailProps["body"], $sendMailProps["html"], $sendMailProps["rtf"], $sendMailProps["rtfinsync"]]);
0 ignored issues
show
Bug introduced by
The constant PR_MESSAGE_CLASS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
486
487
		// Convert sent message's body to UTF-8 if it was a HTML message.
488
		// @see http://jira.zarafa.com/browse/ZP-505 and http://jira.zarafa.com/browse/ZP-555
489
		if (isset($props[$sendMailProps["internetcpid"]]) && $props[$sendMailProps["internetcpid"]] != INTERNET_CPID_UTF8 && MAPIUtils::GetNativeBodyType($props) == SYNC_BODYPREFERENCE_HTML) {
490
			SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->SendMail(): Sent email cpid is not unicode (%d). Set it to unicode and convert email html body.", $props[$sendMailProps["internetcpid"]]));
491
			$mapiprops[$sendMailProps["internetcpid"]] = INTERNET_CPID_UTF8;
492
493
			$bodyHtml = MAPIUtils::readPropStream($mapimessage, PR_HTML);
0 ignored issues
show
Bug introduced by
The constant PR_HTML was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
494
			$bodyHtml = Utils::ConvertCodepageStringToUtf8($props[$sendMailProps["internetcpid"]], $bodyHtml);
495
			$mapiprops[$sendMailProps["html"]] = $bodyHtml;
496
497
			mapi_setprops($mapimessage, $mapiprops);
498
		}
499
		if (stripos($props[PR_MESSAGE_CLASS], "IPM.Schedule.Meeting.Resp.") === 0) {
500
			// search for calendar items using goid
501
			$mr = new Meetingrequest($this->defaultstore, $mapimessage);
502
			$appointments = $mr->findCalendarItems($props[$meetingRequestProps["goidtag"]]);
503
			if (is_array($appointments) && !empty($appointments)) {
504
				$app = mapi_msgstore_openentry($this->defaultstore, $appointments[0]);
505
				$appprops = mapi_getprops($app, [$meetingRequestProps["appSeqNr"]]);
506
				if (isset($appprops[$meetingRequestProps["appSeqNr"]]) && $appprops[$meetingRequestProps["appSeqNr"]]) {
507
					$mapiprops[$meetingRequestProps["appSeqNr"]] = $appprops[$meetingRequestProps["appSeqNr"]];
508
					SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->SendMail(): Set sequence number to:%d", $appprops[$meetingRequestProps["appSeqNr"]]));
509
				}
510
			}
511
		}
512
513
		// Delete the PR_SENT_REPRESENTING_* properties because some android devices
514
		// do not send neither From nor Sender header causing empty PR_SENT_REPRESENTING_NAME and
515
		// PR_SENT_REPRESENTING_EMAIL_ADDRESS properties and "broken" PR_SENT_REPRESENTING_ENTRYID
516
		// which results in spooler not being able to send the message.
517
		// @see http://jira.zarafa.com/browse/ZP-85
518
		mapi_deleteprops(
519
			$mapimessage,
520
			[
521
				$sendMailProps["sentrepresentingname"],
522
				$sendMailProps["sentrepresentingemail"],
523
				$sendMailProps["representingentryid"],
524
				$sendMailProps["sentrepresentingaddt"],
525
				$sendMailProps["sentrepresentinsrchk"],
526
			]
527
		);
528
529
		if (isset($sm->source->itemid) && $sm->source->itemid) {
530
			// answering an email in a public/shared folder
531
			// TODO as the store is setup, we should actually user $this->store instead of $this->defaultstore - nevertheless we need to make sure this store is able to send mail (has an outbox)
532
			if (!$this->Setup(GSync::GetAdditionalSyncFolderStore($sm->source->folderid))) {
533
				throw new StatusException(sprintf("Grommunio->SendMail() could not Setup() the backend for folder id '%s'", $sm->source->folderid), SYNC_COMMONSTATUS_SERVERERROR);
534
			}
535
536
			$entryid = mapi_msgstore_entryidfromsourcekey($this->store, hex2bin($sm->source->folderid), hex2bin($sm->source->itemid));
537
			if ($entryid) {
538
				$fwmessage = mapi_msgstore_openentry($this->store, $entryid);
539
			}
540
541
			if (isset($fwmessage) && $fwmessage) {
542
				// update icon and last_verb when forwarding or replying message
543
				// reply-all (verb 103) is not supported, as we cannot really detect this case
544
				if ($sm->forwardflag) {
545
					$updateProps = [
546
						PR_ICON_INDEX => 262,
0 ignored issues
show
Bug introduced by
The constant PR_ICON_INDEX was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
547
						PR_LAST_VERB_EXECUTED => 104,
0 ignored issues
show
Bug introduced by
The constant PR_LAST_VERB_EXECUTED was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
548
					];
549
				}
550
				elseif ($sm->replyflag) {
551
					$updateProps = [
552
						PR_ICON_INDEX => 261,
553
						PR_LAST_VERB_EXECUTED => 102,
554
					];
555
				}
556
				if (isset($updateProps)) {
557
					$updateProps[PR_LAST_VERB_EXECUTION_TIME] = time();
0 ignored issues
show
Bug introduced by
The constant PR_LAST_VERB_EXECUTION_TIME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
558
					mapi_setprops($fwmessage, $updateProps);
559
					mapi_savechanges($fwmessage);
560
				}
561
562
				// only attach the original message if the mobile does not send it itself
563
				if (!isset($sm->replacemime)) {
564
					// get message's body in order to append forward or reply text
565
					if (!isset($body)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $body seems to never exist and therefore isset should always be false.
Loading history...
566
						$body = MAPIUtils::readPropStream($mapimessage, PR_BODY);
0 ignored issues
show
Bug introduced by
The constant PR_BODY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
567
					}
568
					if (!isset($bodyHtml)) {
569
						$bodyHtml = MAPIUtils::readPropStream($mapimessage, PR_HTML);
570
					}
571
					$cpid = mapi_getprops($fwmessage, [$sendMailProps["internetcpid"]]);
572
					if ($sm->forwardflag) {
573
						// attach the original attachments to the outgoing message
574
						$this->copyAttachments($mapimessage, $fwmessage);
575
					}
576
577
					// regarding the conversion @see ZP-470
578
					if (strlen($body) > 0) {
579
						$fwbody = MAPIUtils::readPropStream($fwmessage, PR_BODY);
580
						// if only the old message's cpid is set, convert from old charset to utf-8
581
						if (isset($cpid[$sendMailProps["internetcpid"]]) && $cpid[$sendMailProps["internetcpid"]] != INTERNET_CPID_UTF8) {
582
							SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->SendMail(): convert plain forwarded message charset (only fw set) from '%s' to '65001'", $cpid[$sendMailProps["internetcpid"]]));
583
							$fwbody = Utils::ConvertCodepageStringToUtf8($cpid[$sendMailProps["internetcpid"]], $fwbody);
584
						}
585
						// otherwise to the general conversion
586
						else {
587
							SLog::Write(LOGLEVEL_DEBUG, "Grommunio->SendMail(): no charset conversion done for plain forwarded message");
588
							$fwbody = w2u($fwbody);
589
						}
590
591
						$mapiprops[$sendMailProps["body"]] = $body . "\r\n\r\n" . $fwbody;
0 ignored issues
show
Bug introduced by
Are you sure $fwbody of type false|string can be used in concatenation? ( Ignorable by Annotation )

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

591
						$mapiprops[$sendMailProps["body"]] = $body . "\r\n\r\n" . /** @scrutinizer ignore-type */ $fwbody;
Loading history...
592
					}
593
594
					if (strlen($bodyHtml) > 0) {
595
						$fwbodyHtml = MAPIUtils::readPropStream($fwmessage, PR_HTML);
596
						// if only new message's cpid is set, convert to UTF-8
597
						if (isset($cpid[$sendMailProps["internetcpid"]]) && $cpid[$sendMailProps["internetcpid"]] != INTERNET_CPID_UTF8) {
598
							SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->SendMail(): convert html forwarded message charset (only fw set) from '%s' to '65001'", $cpid[$sendMailProps["internetcpid"]]));
599
							$fwbodyHtml = Utils::ConvertCodepageStringToUtf8($cpid[$sendMailProps["internetcpid"]], $fwbodyHtml);
600
						}
601
						// otherwise to the general conversion
602
						else {
603
							SLog::Write(LOGLEVEL_DEBUG, "Grommunio->SendMail(): no charset conversion done for html forwarded message");
604
							$fwbodyHtml = w2u($fwbodyHtml);
605
						}
606
607
						$mapiprops[$sendMailProps["html"]] = $bodyHtml . "<br><br>" . $fwbodyHtml;
0 ignored issues
show
Bug introduced by
Are you sure $fwbodyHtml of type false|string can be used in concatenation? ( Ignorable by Annotation )

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

607
						$mapiprops[$sendMailProps["html"]] = $bodyHtml . "<br><br>" . /** @scrutinizer ignore-type */ $fwbodyHtml;
Loading history...
608
					}
609
				}
610
			}
611
			else {
612
				// no fwmessage could be opened and we need it because we do not replace mime
613
				if (!isset($sm->replacemime) || $sm->replacemime == false) {
614
					throw new StatusException(sprintf("Grommunio->SendMail(): Could not open message id '%s' in folder id '%s' to be replied/forwarded: 0x%X", $sm->source->itemid, $sm->source->folderid, mapi_last_hresult()), SYNC_COMMONSTATUS_ITEMNOTFOUND);
615
				}
616
			}
617
		}
618
619
		mapi_setprops($mapimessage, $mapiprops);
620
		mapi_savechanges($mapimessage);
621
		mapi_message_submitmessage($mapimessage);
622
		$hr = mapi_last_hresult();
623
624
		if ($hr) {
625
			switch ($hr) {
626
				case MAPI_E_STORE_FULL:
0 ignored issues
show
Bug introduced by
The constant MAPI_E_STORE_FULL was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
627
					$code = SYNC_COMMONSTATUS_MAILBOXQUOTAEXCEEDED;
628
					break;
629
630
				default:
631
					$code = SYNC_COMMONSTATUS_MAILSUBMISSIONFAILED;
632
					break;
633
			}
634
635
			throw new StatusException(sprintf("Grommunio->SendMail(): Error saving/submitting the message to the Outbox: 0x%X", $hr), $code);
636
		}
637
638
		SLog::Write(LOGLEVEL_DEBUG, "Grommunio->SendMail(): email submitted");
639
640
		return true;
641
	}
642
643
	/**
644
	 * Returns all available data of a single message.
645
	 *
646
	 * @param string            $folderid
647
	 * @param string            $id
648
	 * @param ContentParameters $contentparameters flag
649
	 *
650
	 * @throws StatusException
651
	 *
652
	 * @return object(SyncObject)
653
	 */
654
	public function Fetch($folderid, $id, $contentparameters) {
655
		// SEARCH fetches with folderid == false and PR_ENTRYID as ID
656
		if (!$folderid) {
657
			$entryid = hex2bin($id);
658
			$sk = $id;
659
		}
660
		else {
661
			// id might be in the new longid format, so we have to split it here
662
			list($fsk, $sk) = Utils::SplitMessageId($id);
663
			// get the entry id of the message
664
			$entryid = mapi_msgstore_entryidfromsourcekey($this->store, hex2bin($folderid), hex2bin($sk));
665
		}
666
		if (!$entryid) {
667
			throw new StatusException(sprintf("Grommunio->Fetch('%s','%s'): Error getting entryid: 0x%X", $folderid, $sk, mapi_last_hresult()), SYNC_STATUS_OBJECTNOTFOUND);
668
		}
669
670
		// open the message
671
		$message = mapi_msgstore_openentry($this->store, $entryid);
672
		if (!$message) {
673
			throw new StatusException(sprintf("Grommunio->Fetch('%s','%s'): Error, unable to open message: 0x%X", $folderid, $sk, mapi_last_hresult()), SYNC_STATUS_OBJECTNOTFOUND);
674
		}
675
676
		// convert the mapi message into a SyncObject and return it
677
		$mapiprovider = new MAPIProvider($this->session, $this->store);
0 ignored issues
show
Bug introduced by
$this->session of type boolean is incompatible with the type resource expected by parameter $session of MAPIProvider::__construct(). ( Ignorable by Annotation )

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

677
		$mapiprovider = new MAPIProvider(/** @scrutinizer ignore-type */ $this->session, $this->store);
Loading history...
Bug introduced by
$this->store of type boolean is incompatible with the type resource expected by parameter $store of MAPIProvider::__construct(). ( Ignorable by Annotation )

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

677
		$mapiprovider = new MAPIProvider($this->session, /** @scrutinizer ignore-type */ $this->store);
Loading history...
678
679
		// override truncation
680
		$contentparameters->SetTruncation(SYNC_TRUNCATION_ALL);
0 ignored issues
show
Bug introduced by
The method SetTruncation() does not exist on ContentParameters. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

680
		$contentparameters->/** @scrutinizer ignore-call */ 
681
                      SetTruncation(SYNC_TRUNCATION_ALL);
Loading history...
681
		// TODO check for body preferences
682
		return $mapiprovider->GetMessage($message, $contentparameters);
683
	}
684
685
	/**
686
	 * Returns the waste basket.
687
	 *
688
	 * @return string
689
	 */
690
	public function GetWasteBasket() {
691
		if ($this->wastebasket) {
692
			return $this->wastebasket;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->wastebasket returns the type true which is incompatible with the documented return type string.
Loading history...
693
		}
694
695
		$storeprops = mapi_getprops($this->defaultstore, [PR_IPM_WASTEBASKET_ENTRYID]);
0 ignored issues
show
Bug introduced by
The constant PR_IPM_WASTEBASKET_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
696
		if (isset($storeprops[PR_IPM_WASTEBASKET_ENTRYID])) {
697
			$wastebasket = mapi_msgstore_openentry($this->defaultstore, $storeprops[PR_IPM_WASTEBASKET_ENTRYID]);
698
			$wastebasketprops = mapi_getprops($wastebasket, [PR_SOURCE_KEY]);
0 ignored issues
show
Bug introduced by
The constant PR_SOURCE_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
699
			if (isset($wastebasketprops[PR_SOURCE_KEY])) {
700
				$this->wastebasket = bin2hex($wastebasketprops[PR_SOURCE_KEY]);
701
				SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->GetWasteBasket(): Got waste basket with id '%s'", $this->wastebasket));
702
703
				return $this->wastebasket;
704
			}
705
		}
706
707
		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...
708
	}
709
710
	/**
711
	 * Returns the content of the named attachment as stream.
712
	 *
713
	 * @param string $attname
714
	 *
715
	 * @throws StatusException
716
	 *
717
	 * @return SyncItemOperationsAttachment
718
	 */
719
	public function GetAttachmentData($attname) {
720
		SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->GetAttachmentData('%s')", $attname));
721
722
		if (!strpos($attname, ":")) {
723
			throw new StatusException(sprintf("Grommunio->GetAttachmentData('%s'): Error, attachment requested for non-existing item", $attname), SYNC_ITEMOPERATIONSSTATUS_INVALIDATT);
724
		}
725
726
		list($id, $attachnum, $parentEntryid) = explode(":", $attname);
727
		if (isset($parentEntryid)) {
728
			$this->Setup(GSync::GetAdditionalSyncFolderStore($parentEntryid));
729
		}
730
731
		$entryid = hex2bin($id);
732
		$message = mapi_msgstore_openentry($this->store, $entryid);
733
		if (!$message) {
734
			throw new StatusException(sprintf("Grommunio->GetAttachmentData('%s'): Error, unable to open item for attachment data for id '%s' with: 0x%X", $attname, $id, mapi_last_hresult()), SYNC_ITEMOPERATIONSSTATUS_INVALIDATT);
735
		}
736
737
		MAPIUtils::ParseSmime($this->session, $this->defaultstore, $this->getAddressbook(), $message);
0 ignored issues
show
Bug introduced by
$this->session of type boolean is incompatible with the type MAPISession expected by parameter $session of MAPIUtils::ParseSmime(). ( Ignorable by Annotation )

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

737
		MAPIUtils::ParseSmime(/** @scrutinizer ignore-type */ $this->session, $this->defaultstore, $this->getAddressbook(), $message);
Loading history...
738
		$attach = mapi_message_openattach($message, $attachnum);
739
		if (!$attach) {
740
			throw new StatusException(sprintf("Grommunio->GetAttachmentData('%s'): Error, unable to open attachment number '%s' with: 0x%X", $attname, $attachnum, mapi_last_hresult()), SYNC_ITEMOPERATIONSSTATUS_INVALIDATT);
741
		}
742
743
		// get necessary attachment props
744
		$attprops = mapi_getprops($attach, [PR_ATTACH_MIME_TAG, PR_ATTACH_METHOD]);
0 ignored issues
show
Bug introduced by
The constant PR_ATTACH_MIME_TAG was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ATTACH_METHOD was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
745
		$attachment = new SyncItemOperationsAttachment();
746
		// check if it's an embedded message and open it in such a case
747
		if (isset($attprops[PR_ATTACH_METHOD]) && $attprops[PR_ATTACH_METHOD] == ATTACH_EMBEDDED_MSG) {
748
			$embMessage = mapi_attach_openobj($attach);
749
			$addrbook = $this->getAddressbook();
750
			$stream = mapi_inetmapi_imtoinet($this->session, $addrbook, $embMessage, ['use_tnef' => -1]);
751
			// set the default contenttype for this kind of messages
752
			$attachment->contenttype = "message/rfc822";
753
		}
754
		else {
755
			$stream = mapi_openproperty($attach, PR_ATTACH_DATA_BIN, IID_IStream, 0, 0);
0 ignored issues
show
Bug introduced by
The constant PR_ATTACH_DATA_BIN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
756
		}
757
758
		if (!$stream) {
759
			throw new StatusException(sprintf("Grommunio->GetAttachmentData('%s'): Error, unable to open attachment data stream: 0x%X", $attname, mapi_last_hresult()), SYNC_ITEMOPERATIONSSTATUS_INVALIDATT);
760
		}
761
762
		// put the mapi stream into a wrapper to get a standard stream
763
		$attachment->data = MAPIStreamWrapper::Open($stream);
764
		if (isset($attprops[PR_ATTACH_MIME_TAG])) {
765
			$attachment->contenttype = $attprops[PR_ATTACH_MIME_TAG];
766
		}
767
		// TODO default contenttype
768
		return $attachment;
769
	}
770
771
	/**
772
	 * Deletes all contents of the specified folder.
773
	 * This is generally used to empty the trash (wastebasked), but could also be used on any
774
	 * other folder.
775
	 *
776
	 * @param string $folderid
777
	 * @param bool   $includeSubfolders (opt) also delete sub folders, default true
778
	 *
779
	 * @throws StatusException
780
	 *
781
	 * @return bool
782
	 */
783
	public function EmptyFolder($folderid, $includeSubfolders = true) {
784
		$folderentryid = mapi_msgstore_entryidfromsourcekey($this->store, hex2bin($folderid));
785
		if (!$folderentryid) {
786
			throw new StatusException(sprintf("Grommunio->EmptyFolder('%s','%s'): Error, unable to open folder (no entry id)", $folderid, Utils::PrintAsString($includeSubfolders)), SYNC_ITEMOPERATIONSSTATUS_SERVERERROR);
787
		}
788
		$folder = mapi_msgstore_openentry($this->store, $folderentryid);
789
790
		if (!$folder) {
791
			throw new StatusException(sprintf("Grommunio->EmptyFolder('%s','%s'): Error, unable to open parent folder (open entry)", $folderid, Utils::PrintAsString($includeSubfolders)), SYNC_ITEMOPERATIONSSTATUS_SERVERERROR);
792
		}
793
794
		$flags = 0;
795
		if ($includeSubfolders) {
796
			$flags = DEL_ASSOCIATED;
797
		}
798
799
		SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->EmptyFolder('%s','%s'): emptying folder", $folderid, Utils::PrintAsString($includeSubfolders)));
800
801
		// empty folder!
802
		mapi_folder_emptyfolder($folder, $flags);
803
		if (mapi_last_hresult()) {
804
			throw new StatusException(sprintf("Grommunio->EmptyFolder('%s','%s'): Error, mapi_folder_emptyfolder() failed: 0x%X", $folderid, Utils::PrintAsString($includeSubfolders), mapi_last_hresult()), SYNC_ITEMOPERATIONSSTATUS_SERVERERROR);
805
		}
806
807
		return true;
808
	}
809
810
	/**
811
	 * Processes a response to a meeting request.
812
	 * CalendarID is a reference and has to be set if a new calendar item is created.
813
	 *
814
	 * @param string $requestid id of the object containing the request
815
	 * @param string $folderid  id of the parent folder of $requestid
816
	 * @param string $response
817
	 *
818
	 * @throws StatusException
819
	 *
820
	 * @return string id of the created/updated calendar obj
821
	 */
822
	public function MeetingResponse($requestid, $folderid, $response) {
823
		// Use standard meeting response code to process meeting request
824
		list($fid, $requestid) = Utils::SplitMessageId($requestid);
825
		$reqentryid = mapi_msgstore_entryidfromsourcekey($this->store, hex2bin($folderid), hex2bin($requestid));
826
		if (!$reqentryid) {
827
			throw new StatusException(sprintf("Grommunio->MeetingResponse('%s', '%s', '%s'): Error, unable to entryid of the message 0x%X", $requestid, $folderid, $response, mapi_last_hresult()), SYNC_MEETRESPSTATUS_INVALIDMEETREQ);
828
		}
829
830
		$mapimessage = mapi_msgstore_openentry($this->store, $reqentryid);
831
		if (!$mapimessage) {
832
			throw new StatusException(sprintf("Grommunio->MeetingResponse('%s','%s', '%s'): Error, unable to open request message for response 0x%X", $requestid, $folderid, $response, mapi_last_hresult()), SYNC_MEETRESPSTATUS_INVALIDMEETREQ);
833
		}
834
835
		// ios sends calendar item in MeetingResponse
836
		// @see https://jira.z-hub.io/browse/ZP-1524
837
		$folderClass = GSync::GetDeviceManager()->GetFolderClassFromCacheByID($fid);
838
		// find the corresponding meeting request
839
		if ($folderClass != 'Email') {
840
			$props = MAPIMapping::GetMeetingRequestProperties();
841
			$props = getPropIdsFromStrings($this->store, $props);
842
843
			$messageprops = mapi_getprops($mapimessage, [$props["goidtag"]]);
844
			$goid = $messageprops[$props["goidtag"]];
845
846
			$mapiprovider = new MAPIProvider($this->session, $this->store);
0 ignored issues
show
Bug introduced by
$this->store of type boolean is incompatible with the type resource expected by parameter $store of MAPIProvider::__construct(). ( Ignorable by Annotation )

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

846
			$mapiprovider = new MAPIProvider($this->session, /** @scrutinizer ignore-type */ $this->store);
Loading history...
Bug introduced by
$this->session of type boolean is incompatible with the type resource expected by parameter $session of MAPIProvider::__construct(). ( Ignorable by Annotation )

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

846
			$mapiprovider = new MAPIProvider(/** @scrutinizer ignore-type */ $this->session, $this->store);
Loading history...
847
			$inboxprops = $mapiprovider->GetInboxProps();
848
			$folder = mapi_msgstore_openentry($this->store, $inboxprops[PR_ENTRYID]);
849
850
			// Find the item by restricting all items to the correct ID
851
			$restrict = [RES_AND, [
852
				[RES_PROPERTY,
853
					[
854
						RELOP => RELOP_EQ,
855
						ULPROPTAG => $props["goidtag"],
856
						VALUE => $goid,
857
					],
858
				],
859
			]];
860
861
			$inboxcontents = mapi_folder_getcontentstable($folder);
862
863
			$rows = mapi_table_queryallrows($inboxcontents, [PR_ENTRYID], $restrict);
864
			if (empty($rows)) {
865
				throw new StatusException(sprintf("Grommunio->MeetingResponse('%s','%s', '%s'): Error, meeting request not found in the inbox", $requestid, $folderid, $response), SYNC_MEETRESPSTATUS_INVALIDMEETREQ);
866
			}
867
			SLog::Write(LOGLEVEL_DEBUG, "Grommunio->MeetingResponse found meeting request in the inbox");
868
			$mapimessage = mapi_msgstore_openentry($this->store, $rows[0][PR_ENTRYID]);
869
			$reqentryid = $rows[0][PR_ENTRYID];
870
		}
871
872
		$meetingrequest = new Meetingrequest($this->store, $mapimessage, $this->session);
873
874
		if (!$meetingrequest->isMeetingRequest() && !$meetingrequest->isMeetingRequestResponse() && !$meetingrequest->isMeetingCancellation()) {
875
			throw new StatusException(sprintf("Grommunio->MeetingResponse('%s','%s', '%s'): Error, attempt to respond to non-meeting request", $requestid, $folderid, $response), SYNC_MEETRESPSTATUS_INVALIDMEETREQ);
876
		}
877
878
		if ($meetingrequest->isLocalOrganiser()) {
879
			throw new StatusException(sprintf("Grommunio->MeetingResponse('%s','%s', '%s'): Error, attempt to response to meeting request that we organized", $requestid, $folderid, $response), SYNC_MEETRESPSTATUS_INVALIDMEETREQ);
880
		}
881
882
		// Process the meeting response. We don't have to send the actual meeting response
883
		// e-mail, because the device will send it itself. This seems not to be the case
884
		// anymore for the ios devices since at least version 12.4. grommunio-sync will send the
885
		// accepted email in such a case.
886
		// @see https://jira.z-hub.io/browse/ZP-1524
887
		$sendresponse = false;
888
		$deviceType = strtolower(Request::GetDeviceType());
0 ignored issues
show
Bug introduced by
It seems like Request::GetDeviceType() can also be of type boolean; however, parameter $string of strtolower() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

888
		$deviceType = strtolower(/** @scrutinizer ignore-type */ Request::GetDeviceType());
Loading history...
889
		if ($deviceType == 'iphone' || $deviceType == 'ipad' || $deviceType == 'ipod') {
890
			$matches = [];
891
			if (preg_match("/^Apple-.*?\\/(\\d{4})\\./", Request::GetUserAgent(), $matches) && isset($matches[1]) && $matches[1] >= 1607 && $matches[1] <= 1707) {
0 ignored issues
show
Bug introduced by
It seems like Request::GetUserAgent() can also be of type boolean; however, parameter $subject of preg_match() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

891
			if (preg_match("/^Apple-.*?\\/(\\d{4})\\./", /** @scrutinizer ignore-type */ Request::GetUserAgent(), $matches) && isset($matches[1]) && $matches[1] >= 1607 && $matches[1] <= 1707) {
Loading history...
892
				SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->MeetingResponse: iOS device %s->%s", Request::GetDeviceType(), Request::GetUserAgent()));
893
				$sendresponse = true;
894
			}
895
		}
896
897
		switch ($response) {
898
			case 1:     // accept
899
			default:
900
				$entryid = $meetingrequest->doAccept(false, $sendresponse, false, false, false, false, true); // last true is the $userAction
901
				break;
902
903
			case 2:        // tentative
904
				$entryid = $meetingrequest->doAccept(true, $sendresponse, false, false, false, false, true); // last true is the $userAction
905
				break;
906
907
			case 3:        // decline
908
				$meetingrequest->doDecline(false);
909
				break;
910
		}
911
912
		// F/B will be updated on logoff
913
914
		// We have to return the ID of the new calendar item, so do that here
915
		$calendarid = "";
916
		$calFolderId = "";
917
		if (isset($entryid)) {
918
			$newitem = mapi_msgstore_openentry($this->store, $entryid);
919
			// new item might be in a delegator's store. ActiveSync does not support accepting them.
920
			if (!$newitem) {
921
				throw new StatusException(sprintf("Grommunio->MeetingResponse('%s','%s', '%s'): Object with entryid '%s' was not found in user's store (0x%X). It might be in a delegator's store.", $requestid, $folderid, $response, bin2hex($entryid), mapi_last_hresult()), SYNC_MEETRESPSTATUS_SERVERERROR, null, LOGLEVEL_WARN);
922
			}
923
924
			$newprops = mapi_getprops($newitem, [PR_SOURCE_KEY, PR_PARENT_SOURCE_KEY]);
0 ignored issues
show
Bug introduced by
The constant PR_PARENT_SOURCE_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_SOURCE_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
925
			$calendarid = bin2hex($newprops[PR_SOURCE_KEY]);
926
			$calFolderId = bin2hex($newprops[PR_PARENT_SOURCE_KEY]);
927
		}
928
929
		// on recurring items, the MeetingRequest class responds with a wrong entryid
930
		if ($requestid == $calendarid) {
931
			SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->MeetingResponse('%s','%s', '%s'): returned calendar id is the same as the requestid - re-searching", $requestid, $folderid, $response));
932
933
			if (empty($props)) {
934
				$props = MAPIMapping::GetMeetingRequestProperties();
935
				$props = getPropIdsFromStrings($this->store, $props);
936
937
				$messageprops = mapi_getprops($mapimessage, [$props["goidtag"]]);
938
				$goid = $messageprops[$props["goidtag"]];
939
			}
940
941
			$items = $meetingrequest->findCalendarItems($goid);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $goid does not seem to be defined for all execution paths leading up to this point.
Loading history...
942
943
			if (is_array($items)) {
944
				$newitem = mapi_msgstore_openentry($this->store, $items[0]);
945
				$newprops = mapi_getprops($newitem, [PR_SOURCE_KEY, PR_PARENT_SOURCE_KEY]);
946
				$calendarid = bin2hex($newprops[PR_SOURCE_KEY]);
947
				$calFolderId = bin2hex($newprops[PR_PARENT_SOURCE_KEY]);
948
				SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->MeetingResponse('%s','%s', '%s'): found other calendar entryid", $requestid, $folderid, $response));
949
			}
950
951
			if ($requestid == $calendarid) {
952
				throw new StatusException(sprintf("Grommunio->MeetingResponse('%s','%s', '%s'): Error finding the accepted meeting response in the calendar", $requestid, $folderid, $response), SYNC_MEETRESPSTATUS_INVALIDMEETREQ);
953
			}
954
		}
955
956
		// delete meeting request from Inbox
957
		if ($folderClass == 'Email') {
958
			$folderentryid = mapi_msgstore_entryidfromsourcekey($this->store, hex2bin($folderid));
959
			$folder = mapi_msgstore_openentry($this->store, $folderentryid);
960
		}
961
		mapi_folder_deletemessages($folder, [$reqentryid], 0);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $folder does not seem to be defined for all execution paths leading up to this point.
Loading history...
962
963
		$prefix = '';
964
		// prepend the short folderid of the target calendar: if available and short ids are used
965
		if ($calFolderId) {
966
			$shortFolderId = GSync::GetDeviceManager()->GetFolderIdForBackendId($calFolderId);
967
			if ($calFolderId != $shortFolderId) {
968
				$prefix = $shortFolderId . ':';
969
			}
970
		}
971
972
		return $prefix . $calendarid;
973
	}
974
975
	/**
976
	 * Indicates if the backend has a ChangesSink.
977
	 * A sink is an active notification mechanism which does not need polling.
978
	 * Since Zarafa 7.0.5 such a sink is available.
979
	 * The grommunio backend uses this method to initialize the sink with mapi.
980
	 *
981
	 * @return bool
982
	 */
983
	public function HasChangesSink() {
984
		if (!$this->notifications) {
985
			SLog::Write(LOGLEVEL_DEBUG, "Grommunio->HasChangesSink(): sink is not available");
986
987
			return false;
988
		}
989
990
		$this->changesSink = @mapi_sink_create();
991
992
		if (!$this->changesSink || mapi_last_hresult()) {
993
			SLog::Write(LOGLEVEL_WARN, sprintf("Grommunio->HasChangesSink(): sink could not be created with  0x%X", mapi_last_hresult()));
994
995
			return false;
996
		}
997
998
		$this->changesSinkHierarchyHash = $this->getHierarchyHash();
999
		SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->HasChangesSink(): created - HierarchyHash: %s", $this->changesSinkHierarchyHash));
1000
1001
		// advise the main store and also to check if the connection supports it
1002
		return $this->adviseStoreToSink($this->defaultstore);
1003
	}
1004
1005
	/**
1006
	 * The folder should be considered by the sink.
1007
	 * Folders which were not initialized should not result in a notification
1008
	 * of IBackend->ChangesSink().
1009
	 *
1010
	 * @param string $folderid
1011
	 *
1012
	 * @return bool false if entryid can not be found for that folder
1013
	 */
1014
	public function ChangesSinkInitialize($folderid) {
1015
		SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->ChangesSinkInitialize(): folderid '%s'", $folderid));
1016
1017
		$entryid = mapi_msgstore_entryidfromsourcekey($this->store, hex2bin($folderid));
1018
		if (!$entryid) {
1019
			return false;
1020
		}
1021
1022
		// add entryid to the monitored folders
1023
		$this->changesSinkFolders[$entryid] = $folderid;
1024
1025
		// advise the current store to the sink
1026
		return $this->adviseStoreToSink($this->store);
0 ignored issues
show
Bug introduced by
$this->store of type boolean is incompatible with the type mapistore expected by parameter $store of Grommunio::adviseStoreToSink(). ( Ignorable by Annotation )

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

1026
		return $this->adviseStoreToSink(/** @scrutinizer ignore-type */ $this->store);
Loading history...
1027
	}
1028
1029
	/**
1030
	 * The actual ChangesSink.
1031
	 * For max. the $timeout value this method should block and if no changes
1032
	 * are available return an empty array.
1033
	 * If changes are available a list of folderids is expected.
1034
	 *
1035
	 * @param int $timeout max. amount of seconds to block
1036
	 *
1037
	 * @return array
1038
	 */
1039
	public function ChangesSink($timeout = 30) {
1040
		// clear the folder stats cache
1041
		unset($this->folderStatCache);
1042
1043
		$notifications = [];
1044
		$hierarchyNotifications = [];
1045
		$sinkresult = @mapi_sink_timedwait($this->changesSink, $timeout * 1000);
1046
1047
		if (!is_array($sinkresult)) {
1048
			throw new StatusException("Grommunio->ChangesSink(): Sink returned invalid notification, aborting", SyncCollections::OBSOLETE_CONNECTION);
1049
		}
1050
1051
		// reverse array so that the changes on folders are before changes on messages and
1052
		// it's possible to filter such notifications
1053
		$sinkresult = array_reverse($sinkresult, true);
1054
		foreach ($sinkresult as $sinknotif) {
1055
			if (isset($sinknotif['objtype'])) {
1056
				// add a notification on a folder
1057
				if ($sinknotif['objtype'] == MAPI_FOLDER) {
1058
					$hierarchyNotifications[$sinknotif['entryid']] = IBackend::HIERARCHYNOTIFICATION;
1059
				}
1060
				// change on a message, remove hierarchy notification
1061
				if (isset($sinknotif['parentid']) && $sinknotif['objtype'] == MAPI_MESSAGE && isset($notifications[$sinknotif['parentid']])) {
1062
					unset($hierarchyNotifications[$sinknotif['parentid']]);
1063
				}
1064
			}
1065
1066
			// check if something in the monitored folders changed
1067
			// 'objtype' is not set when mail is received, so we don't check for it
1068
			if (isset($sinknotif['parentid']) && array_key_exists($sinknotif['parentid'], $this->changesSinkFolders)) {
1069
				$notifications[] = $this->changesSinkFolders[$sinknotif['parentid']];
1070
			}
1071
			// deletes and moves
1072
			if (isset($sinknotif['oldparentid']) && array_key_exists($sinknotif['oldparentid'], $this->changesSinkFolders)) {
1073
				$notifications[] = $this->changesSinkFolders[$sinknotif['oldparentid']];
1074
			}
1075
		}
1076
1077
		// validate hierarchy notifications by comparing the hierarchy hashes (too many false positives otherwise)
1078
		if (!empty($hierarchyNotifications)) {
1079
			$hash = $this->getHierarchyHash();
1080
			if ($hash !== $this->changesSinkHierarchyHash) {
1081
				SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->ChangesSink() Hierarchy notification, pending validation. New hierarchyHash: %s", $hash));
1082
				$notifications[] = IBackend::HIERARCHYNOTIFICATION;
1083
				$this->changesSinkHierarchyHash = $hash;
1084
			}
1085
		}
1086
1087
		return $notifications;
1088
	}
1089
1090
	/**
1091
	 * Applies settings to and gets information from the device.
1092
	 *
1093
	 * @param SyncObject $settings (SyncOOF, SyncUserInformation, SyncRightsManagementTemplates possible)
1094
	 *
1095
	 * @return SyncObject $settings
1096
	 */
1097
	public function Settings($settings) {
1098
		if ($settings instanceof SyncOOF) {
1099
			$this->settingsOOF($settings);
1100
		}
1101
1102
		if ($settings instanceof SyncUserInformation) {
1103
			$this->settingsUserInformation($settings);
1104
		}
1105
1106
		if ($settings instanceof SyncRightsManagementTemplates) {
1107
			$this->settingsRightsManagementTemplates($settings);
1108
		}
1109
1110
		return $settings;
1111
	}
1112
1113
	/**
1114
	 * Resolves recipients.
1115
	 *
1116
	 * @param SyncObject $resolveRecipients
1117
	 *
1118
	 * @return SyncObject $resolveRecipients
1119
	 */
1120
	public function ResolveRecipients($resolveRecipients) {
1121
		if ($resolveRecipients instanceof SyncResolveRecipients) {
1122
			$resolveRecipients->status = SYNC_RESOLVERECIPSSTATUS_SUCCESS;
1123
			$resolveRecipients->response = [];
1124
			$resolveRecipientsOptions = new SyncResolveRecipientsOptions();
1125
			$maxAmbiguousRecipients = self::MAXAMBIGUOUSRECIPIENTS;
1126
1127
			if (isset($resolveRecipients->options)) {
1128
				$resolveRecipientsOptions = $resolveRecipients->options;
1129
				// only limit ambiguous recipients if the client requests it.
1130
1131
				if (isset($resolveRecipientsOptions->maxambiguousrecipients) &&
1132
						$resolveRecipientsOptions->maxambiguousrecipients >= 0 &&
1133
						$resolveRecipientsOptions->maxambiguousrecipients <= self::MAXAMBIGUOUSRECIPIENTS) {
1134
					$maxAmbiguousRecipients = $resolveRecipientsOptions->maxambiguousrecipients;
1135
					SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->ResolveRecipients(): The client requested %d max ambiguous recipients to resolve.", $maxAmbiguousRecipients));
1136
				}
1137
			}
1138
1139
			foreach ($resolveRecipients->to as $i => $to) {
1140
				$response = new SyncResolveRecipientsResponse();
1141
				$response->to = $to;
1142
				$response->status = SYNC_RESOLVERECIPSSTATUS_SUCCESS;
1143
1144
				// do not expand distlists here
1145
				$recipient = $this->resolveRecipient($to, $maxAmbiguousRecipients, false);
1146
				if (is_array($recipient) && !empty($recipient)) {
1147
					$response->recipientcount = 0;
1148
					foreach ($recipient as $entry) {
1149
						if ($entry instanceof SyncResolveRecipient) {
1150
							// certificates are already set. Unset them if they weren't required.
1151
							if (!isset($resolveRecipientsOptions->certificateretrieval)) {
1152
								unset($entry->certificates);
1153
							}
1154
							if (isset($resolveRecipientsOptions->availability)) {
1155
								if (!isset($resolveRecipientsOptions->starttime)) {
1156
									// TODO error, the request must include a valid StartTime element value
1157
								}
1158
								$entry->availability = $this->getAvailability($to, $entry, $resolveRecipientsOptions);
1159
							}
1160
							if (isset($resolveRecipientsOptions->picture)) {
1161
								// TODO implement picture retrieval of the recipient
1162
							}
1163
							++$response->recipientcount;
1164
							$response->recipient[] = $entry;
1165
						}
1166
						elseif (is_int($recipient)) {
1167
							$response->status = $recipient;
1168
						}
1169
					}
1170
				}
1171
1172
				$resolveRecipients->response[$i] = $response;
1173
			}
1174
1175
			return $resolveRecipients;
1176
		}
1177
1178
		SLog::Write(LOGLEVEL_WARN, "Grommunio->ResolveRecipients(): Not a valid SyncResolveRecipients object.");
1179
		// return a SyncResolveRecipients object so that sync doesn't fail
1180
		$r = new SyncResolveRecipients();
1181
		$r->status = SYNC_RESOLVERECIPSSTATUS_PROTOCOLERROR;
1182
1183
		return $r;
1184
	}
1185
1186
	/*----------------------------------------------------------------------------------------------------------
1187
	 * Implementation of the ISearchProvider interface
1188
	 */
1189
1190
	/**
1191
	 * Indicates if a search type is supported by this SearchProvider
1192
	 * Currently only the type ISearchProvider::SEARCH_GAL (Global Address List) is implemented.
1193
	 *
1194
	 * @param string $searchtype
1195
	 *
1196
	 * @return bool
1197
	 */
1198
	public function SupportsType($searchtype) {
1199
		return ($searchtype == ISearchProvider::SEARCH_GAL) || ($searchtype == ISearchProvider::SEARCH_MAILBOX);
1200
	}
1201
1202
	/**
1203
	 * Searches the GAB of Grommunio
1204
	 * Can be overwritten globally by configuring a SearchBackend.
1205
	 *
1206
	 * @param string                       $searchquery   string to be searched for
1207
	 * @param string                       $searchrange   specified searchrange
1208
	 * @param SyncResolveRecipientsPicture $searchpicture limitations for picture
1209
	 *
1210
	 * @throws StatusException
1211
	 *
1212
	 * @return array search results
1213
	 */
1214
	public function GetGALSearchResults($searchquery, $searchrange, $searchpicture) {
1215
		// only return users whose displayName or the username starts with $name
1216
		// TODO: use PR_ANR for this restriction instead of PR_DISPLAY_NAME and PR_ACCOUNT
1217
		$addrbook = $this->getAddressbook();
1218
		// FIXME: create a function to get the adressbook contentstable
1219
		if ($addrbook) {
0 ignored issues
show
introduced by
$addrbook is of type MAPIAddressbook, thus it always evaluated to true.
Loading history...
1220
			$ab_entryid = mapi_ab_getdefaultdir($addrbook);
0 ignored issues
show
Bug introduced by
The function mapi_ab_getdefaultdir was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1220
			$ab_entryid = /** @scrutinizer ignore-call */ mapi_ab_getdefaultdir($addrbook);
Loading history...
1221
		}
1222
		if ($ab_entryid) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $ab_entryid does not seem to be defined for all execution paths leading up to this point.
Loading history...
1223
			$ab_dir = mapi_ab_openentry($addrbook, $ab_entryid);
1224
		}
1225
		if ($ab_dir) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $ab_dir does not seem to be defined for all execution paths leading up to this point.
Loading history...
1226
			$table = mapi_folder_getcontentstable($ab_dir);
1227
		}
1228
1229
		if (!$table) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $table does not seem to be defined for all execution paths leading up to this point.
Loading history...
1230
			throw new StatusException(sprintf("Grommunio->GetGALSearchResults(): could not open addressbook: 0x%X", mapi_last_hresult()), SYNC_SEARCHSTATUS_STORE_CONNECTIONFAILED);
1231
		}
1232
1233
		$restriction = MAPIUtils::GetSearchRestriction(u2w($searchquery));
0 ignored issues
show
Bug introduced by
It seems like u2w($searchquery) can also be of type false; however, parameter $query of MAPIUtils::GetSearchRestriction() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

1233
		$restriction = MAPIUtils::GetSearchRestriction(/** @scrutinizer ignore-type */ u2w($searchquery));
Loading history...
1234
		mapi_table_restrict($table, $restriction);
1235
		mapi_table_sort($table, [PR_DISPLAY_NAME => TABLE_SORT_ASCEND]);
0 ignored issues
show
Bug introduced by
The function mapi_table_sort was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1235
		/** @scrutinizer ignore-call */ 
1236
  mapi_table_sort($table, [PR_DISPLAY_NAME => TABLE_SORT_ASCEND]);
Loading history...
Bug introduced by
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1236
1237
		if (mapi_last_hresult()) {
1238
			throw new StatusException(sprintf("Grommunio->GetGALSearchResults(): could not apply restriction: 0x%X", mapi_last_hresult()), SYNC_SEARCHSTATUS_STORE_TOOCOMPLEX);
1239
		}
1240
1241
		// range for the search results, default symbian range end is 50, wm 99,
1242
		// so we'll use that of nokia
1243
		$rangestart = 0;
1244
		$rangeend = 50;
1245
1246
		if ($searchrange != '0') {
1247
			$pos = strpos($searchrange, '-');
1248
			$rangestart = substr($searchrange, 0, $pos);
1249
			$rangeend = substr($searchrange, ($pos + 1));
1250
		}
1251
		$items = [];
1252
1253
		$querycnt = mapi_table_getrowcount($table);
1254
		// do not return more results as requested in range
1255
		$querylimit = (($rangeend + 1) < $querycnt) ? ($rangeend + 1) : $querycnt;
1256
1257
		if ($querycnt > 0) {
1258
			$abentries = mapi_table_queryrows($table, [PR_ENTRYID, PR_ACCOUNT, PR_DISPLAY_NAME, PR_SMTP_ADDRESS, PR_BUSINESS_TELEPHONE_NUMBER, PR_GIVEN_NAME, PR_SURNAME, PR_MOBILE_TELEPHONE_NUMBER, PR_HOME_TELEPHONE_NUMBER, PR_TITLE, PR_COMPANY_NAME, PR_OFFICE_LOCATION, PR_EMS_AB_THUMBNAIL_PHOTO], $rangestart, $querylimit);
0 ignored issues
show
Bug introduced by
The constant PR_HOME_TELEPHONE_NUMBER was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_OFFICE_LOCATION was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_TITLE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_GIVEN_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_EMS_AB_THUMBNAIL_PHOTO was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_SMTP_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_BUSINESS_TELEPHONE_NUMBER was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_ACCOUNT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_SURNAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_COMPANY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_MOBILE_TELEPHONE_NUMBER was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1259
		}
1260
1261
		for ($i = 0; $i < $querylimit; ++$i) {
1262
			if (!isset($abentries[$i][PR_SMTP_ADDRESS])) {
1263
				SLog::Write(LOGLEVEL_WARN, sprintf("Grommunio->GetGALSearchResults(): The GAL entry '%s' does not have an email address and will be ignored.", w2u($abentries[$i][PR_DISPLAY_NAME])));
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $abentries does not seem to be defined for all execution paths leading up to this point.
Loading history...
Bug introduced by
It seems like w2u($abentries[$i][PR_DISPLAY_NAME]) can also be of type false; however, parameter $values of sprintf() does only seem to accept double|integer|string, maybe add an additional type check? ( Ignorable by Annotation )

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

1263
				SLog::Write(LOGLEVEL_WARN, sprintf("Grommunio->GetGALSearchResults(): The GAL entry '%s' does not have an email address and will be ignored.", /** @scrutinizer ignore-type */ w2u($abentries[$i][PR_DISPLAY_NAME])));
Loading history...
1264
1265
				continue;
1266
			}
1267
1268
			$items[$i][SYNC_GAL_DISPLAYNAME] = w2u($abentries[$i][PR_DISPLAY_NAME]);
1269
1270
			if (strlen(trim($items[$i][SYNC_GAL_DISPLAYNAME])) == 0) {
1271
				$items[$i][SYNC_GAL_DISPLAYNAME] = w2u($abentries[$i][PR_ACCOUNT]);
1272
			}
1273
1274
			$items[$i][SYNC_GAL_ALIAS] = w2u($abentries[$i][PR_ACCOUNT]);
1275
			// it's not possible not get first and last name of an user
1276
			// from the gab and user functions, so we just set lastname
1277
			// to displayname and leave firstname unset
1278
			// this was changed in Zarafa 6.40, so we try to get first and
1279
			// last name and fall back to the old behaviour if these values are not set
1280
			if (isset($abentries[$i][PR_GIVEN_NAME])) {
1281
				$items[$i][SYNC_GAL_FIRSTNAME] = w2u($abentries[$i][PR_GIVEN_NAME]);
1282
			}
1283
			if (isset($abentries[$i][PR_SURNAME])) {
1284
				$items[$i][SYNC_GAL_LASTNAME] = w2u($abentries[$i][PR_SURNAME]);
1285
			}
1286
1287
			if (!isset($items[$i][SYNC_GAL_LASTNAME])) {
1288
				$items[$i][SYNC_GAL_LASTNAME] = $items[$i][SYNC_GAL_DISPLAYNAME];
1289
			}
1290
1291
			$items[$i][SYNC_GAL_EMAILADDRESS] = w2u($abentries[$i][PR_SMTP_ADDRESS]);
1292
			// check if an user has an office number or it might produce warnings in the log
1293
			if (isset($abentries[$i][PR_BUSINESS_TELEPHONE_NUMBER])) {
1294
				$items[$i][SYNC_GAL_PHONE] = w2u($abentries[$i][PR_BUSINESS_TELEPHONE_NUMBER]);
1295
			}
1296
			// check if an user has a mobile number or it might produce warnings in the log
1297
			if (isset($abentries[$i][PR_MOBILE_TELEPHONE_NUMBER])) {
1298
				$items[$i][SYNC_GAL_MOBILEPHONE] = w2u($abentries[$i][PR_MOBILE_TELEPHONE_NUMBER]);
1299
			}
1300
			// check if an user has a home number or it might produce warnings in the log
1301
			if (isset($abentries[$i][PR_HOME_TELEPHONE_NUMBER])) {
1302
				$items[$i][SYNC_GAL_HOMEPHONE] = w2u($abentries[$i][PR_HOME_TELEPHONE_NUMBER]);
1303
			}
1304
1305
			if (isset($abentries[$i][PR_COMPANY_NAME])) {
1306
				$items[$i][SYNC_GAL_COMPANY] = w2u($abentries[$i][PR_COMPANY_NAME]);
1307
			}
1308
1309
			if (isset($abentries[$i][PR_TITLE])) {
1310
				$items[$i][SYNC_GAL_TITLE] = w2u($abentries[$i][PR_TITLE]);
1311
			}
1312
1313
			if (isset($abentries[$i][PR_OFFICE_LOCATION])) {
1314
				$items[$i][SYNC_GAL_OFFICE] = w2u($abentries[$i][PR_OFFICE_LOCATION]);
1315
			}
1316
1317
			if ($searchpicture !== false && isset($abentries[$i][PR_EMS_AB_THUMBNAIL_PHOTO])) {
1318
				$items[$i][SYNC_GAL_PICTURE] = StringStreamWrapper::Open($abentries[$i][PR_EMS_AB_THUMBNAIL_PHOTO]);
1319
			}
1320
		}
1321
		$nrResults = count($items);
1322
		$items['range'] = ($nrResults > 0) ? $rangestart . '-' . ($nrResults - 1) : '0-0';
1323
		$items['searchtotal'] = $nrResults;
1324
1325
		return $items;
1326
	}
1327
1328
	/**
1329
	 * Searches for the emails on the server.
1330
	 *
1331
	 * @param ContentParameter $cpo
1332
	 *
1333
	 * @return array
1334
	 */
1335
	public function GetMailboxSearchResults($cpo) {
1336
		$searchFolder = $this->getSearchFolder();
1337
		$searchRestriction = $this->getSearchRestriction($cpo);
1338
		$searchRange = explode('-', $cpo->GetSearchRange());
1339
		$searchFolderId = $cpo->GetSearchFolderid();
1340
		$searchFolders = [];
1341
		// search only in required folders
1342
		if (!empty($searchFolderId)) {
1343
			$searchFolderEntryId = mapi_msgstore_entryidfromsourcekey($this->store, hex2bin($searchFolderId));
1344
			$searchFolders[] = $searchFolderEntryId;
1345
		}
1346
		// if no folder was required then search in the entire store
1347
		else {
1348
			$tmp = mapi_getprops($this->store, [PR_ENTRYID, PR_DISPLAY_NAME, PR_IPM_SUBTREE_ENTRYID]);
0 ignored issues
show
Bug introduced by
The constant PR_IPM_SUBTREE_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1349
			$searchFolders[] = $tmp[PR_IPM_SUBTREE_ENTRYID];
1350
		}
1351
		$items = [];
1352
		$flags = 0;
1353
		// if subfolders are required, do a recursive search
1354
		if ($cpo->GetSearchDeepTraversal()) {
1355
			$flags |= SEARCH_RECURSIVE;
1356
		}
1357
1358
		mapi_folder_setsearchcriteria($searchFolder, $searchRestriction, $searchFolders, $flags);
0 ignored issues
show
Bug introduced by
The function mapi_folder_setsearchcriteria was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1358
		/** @scrutinizer ignore-call */ 
1359
  mapi_folder_setsearchcriteria($searchFolder, $searchRestriction, $searchFolders, $flags);
Loading history...
1359
1360
		$table = mapi_folder_getcontentstable($searchFolder);
1361
		$searchStart = time();
1362
		// do the search and wait for all the results available
1363
		while (time() - $searchStart < SEARCH_WAIT) {
1364
			$searchcriteria = mapi_folder_getsearchcriteria($searchFolder);
0 ignored issues
show
Bug introduced by
The function mapi_folder_getsearchcriteria was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1364
			$searchcriteria = /** @scrutinizer ignore-call */ mapi_folder_getsearchcriteria($searchFolder);
Loading history...
1365
			if (($searchcriteria["searchstate"] & SEARCH_REBUILD) == 0) {
1366
				break;
1367
			} // Search is done
1368
			sleep(1);
1369
		}
1370
1371
		// if the search range is set limit the result to it, otherwise return all found messages
1372
		$rows = (is_array($searchRange) && isset($searchRange[0], $searchRange[1])) ?
1373
			mapi_table_queryrows($table, [PR_ENTRYID], $searchRange[0], $searchRange[1] - $searchRange[0] + 1) :
1374
			mapi_table_queryrows($table, [PR_ENTRYID], 0, SEARCH_MAXRESULTS);
1375
1376
		$cnt = count($rows);
1377
		$items['searchtotal'] = $cnt;
1378
		$items["range"] = $cpo->GetSearchRange();
1379
		for ($i = 0; $i < $cnt; ++$i) {
1380
			$items[$i]['class'] = 'Email';
1381
			$items[$i]['longid'] = bin2hex($rows[$i][PR_ENTRYID]);
1382
			// $items[$i]['folderid'] = bin2hex($rows[$i][PR_PARENT_SOURCE_KEY]);
1383
		}
1384
1385
		return $items;
1386
	}
1387
1388
	/**
1389
	 * Terminates a search for a given PID.
1390
	 *
1391
	 * @param int $pid
1392
	 *
1393
	 * @return bool
1394
	 */
1395
	public function TerminateSearch($pid) {
1396
		SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->TerminateSearch(): terminating search for pid %d", $pid));
1397
		if (!isset($this->store) || $this->store === false) {
1398
			SLog::Write(LOGLEVEL_WARN, sprintf("Grommunio->TerminateSearch(): The store is not available. It is not possible to remove search folder with pid %d", $pid));
1399
1400
			return false;
1401
		}
1402
1403
		$storeProps = mapi_getprops($this->store, [PR_STORE_SUPPORT_MASK, PR_FINDER_ENTRYID]);
0 ignored issues
show
Bug introduced by
The constant PR_STORE_SUPPORT_MASK was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_FINDER_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1404
		if (($storeProps[PR_STORE_SUPPORT_MASK] & STORE_SEARCH_OK) != STORE_SEARCH_OK) {
1405
			SLog::Write(LOGLEVEL_WARN, "Grommunio->TerminateSearch(): Store doesn't support search folders. Public store doesn't have FINDER_ROOT folder");
1406
1407
			return false;
1408
		}
1409
1410
		$finderfolder = mapi_msgstore_openentry($this->store, $storeProps[PR_FINDER_ENTRYID]);
1411
		if (mapi_last_hresult() != NOERROR) {
1412
			SLog::Write(LOGLEVEL_WARN, sprintf("Grommunio->TerminateSearch(): Unable to open search folder (0x%X)", mapi_last_hresult()));
1413
1414
			return false;
1415
		}
1416
1417
		$hierarchytable = mapi_folder_gethierarchytable($finderfolder);
1418
		mapi_table_restrict(
1419
			$hierarchytable,
1420
			[RES_CONTENT,
1421
				[
1422
					FUZZYLEVEL => FL_PREFIX,
1423
					ULPROPTAG => PR_DISPLAY_NAME,
0 ignored issues
show
Bug introduced by
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1424
					VALUE => [PR_DISPLAY_NAME => "grommunio-sync Search Folder " . $pid],
1425
				],
1426
			],
1427
			TBL_BATCH
1428
		);
1429
1430
		$folders = mapi_table_queryallrows($hierarchytable, [PR_ENTRYID, PR_DISPLAY_NAME, PR_LAST_MODIFICATION_TIME]);
0 ignored issues
show
Bug introduced by
The constant PR_LAST_MODIFICATION_TIME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1431
		foreach ($folders as $folder) {
1432
			mapi_folder_deletefolder($finderfolder, $folder[PR_ENTRYID]);
0 ignored issues
show
Bug introduced by
The function mapi_folder_deletefolder was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1432
			/** @scrutinizer ignore-call */ 
1433
   mapi_folder_deletefolder($finderfolder, $folder[PR_ENTRYID]);
Loading history...
1433
		}
1434
1435
		return true;
1436
	}
1437
1438
	/**
1439
	 * Disconnects from the current search provider.
1440
	 *
1441
	 * @return bool
1442
	 */
1443
	public function Disconnect() {
1444
		return true;
1445
	}
1446
1447
	/**
1448
	 * Returns the MAPI store resource for a folderid
1449
	 * This is not part of IBackend but necessary for the ImportChangesICS->MoveMessage() operation if
1450
	 * the destination folder is not in the default store
1451
	 * Note: The current backend store might be changed as IBackend->Setup() is executed.
1452
	 *
1453
	 * @param string $store    target store, could contain a "domain\user" value - if empty default store is returned
1454
	 * @param string $folderid
1455
	 *
1456
	 * @return bool|resource
1457
	 */
1458
	public function GetMAPIStoreForFolderId($store, $folderid) {
1459
		if ($store == false) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $store of type string to the boolean false. If you are specifically checking for an empty string, consider using the more explicit === '' instead.
Loading history...
1460
			SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->GetMAPIStoreForFolderId('%s', '%s'): no store specified, returning default store", $store, $folderid));
1461
1462
			return $this->defaultstore;
1463
		}
1464
1465
		// setup the correct store
1466
		if ($this->Setup($store, false, $folderid)) {
1467
			return $this->store;
1468
		}
1469
1470
		SLog::Write(LOGLEVEL_WARN, sprintf("Grommunio->GetMAPIStoreForFolderId('%s', '%s'): store is not available", $store, $folderid));
1471
1472
		return false;
1473
	}
1474
1475
	/**
1476
	 * Returns the email address and the display name of the user. Used by autodiscover.
1477
	 *
1478
	 * @param string $username The username
1479
	 *
1480
	 * @return array
1481
	 */
1482
	public function GetUserDetails($username) {
1483
		SLog::Write(LOGLEVEL_WBXML, sprintf("Grommunio->GetUserDetails for '%s'.", $username));
1484
		$zarafauserinfo = @nsp_getuserinfo($username);
1485
		$userDetails['emailaddress'] = (isset($zarafauserinfo['primary_email']) && $zarafauserinfo['primary_email']) ? $zarafauserinfo['primary_email'] : false;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$userDetails was never initialized. Although not strictly required by PHP, it is generally a good practice to add $userDetails = array(); before regardless.
Loading history...
1486
		$userDetails['fullname'] = (isset($zarafauserinfo['fullname']) && $zarafauserinfo['fullname']) ? $zarafauserinfo['fullname'] : false;
1487
1488
		return $userDetails;
1489
	}
1490
1491
	/**
1492
	 * Returns the username of the currently active user.
1493
	 *
1494
	 * @return string
1495
	 */
1496
	public function GetCurrentUsername() {
1497
		return $this->storeName;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->storeName returns the type boolean which is incompatible with the documented return type string.
Loading history...
1498
	}
1499
1500
	/**
1501
	 * Returns the impersonated user name.
1502
	 *
1503
	 * @return string or false if no user is impersonated
1504
	 */
1505
	public function GetImpersonatedUser() {
1506
		return $this->impersonateUser;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->impersonateUser returns the type boolean which is incompatible with the documented return type string.
Loading history...
1507
	}
1508
1509
	/**
1510
	 * Returns the authenticated user name.
1511
	 *
1512
	 * @return string
1513
	 */
1514
	public function GetMainUser() {
1515
		return $this->mainUser;
1516
	}
1517
1518
	/**
1519
	 * Indicates if the Backend supports folder statistics.
1520
	 *
1521
	 * @return bool
1522
	 */
1523
	public function HasFolderStats() {
1524
		return true;
1525
	}
1526
1527
	/**
1528
	 * Returns a status indication of the folder.
1529
	 * If there are changes in the folder, the returned value must change.
1530
	 * The returned values are compared with '===' to determine if a folder needs synchronization or not.
1531
	 *
1532
	 * @param string $store    the store where the folder resides
1533
	 * @param string $folderid the folder id
1534
	 *
1535
	 * @return string
1536
	 */
1537
	public function GetFolderStat($store, $folderid) {
1538
		list($user, $domain) = Utils::SplitDomainUser($store);
1539
		if ($user === false) {
0 ignored issues
show
introduced by
The condition $user === false is always false.
Loading history...
1540
			$user = $this->mainUser;
1541
			if ($this->impersonateUser) {
1542
				$user = $this->impersonateUser;
1543
			}
1544
		}
1545
1546
		if (!isset($this->folderStatCache[$user])) {
1547
			$this->folderStatCache[$user] = [];
1548
		}
1549
1550
		// if there is nothing in the cache for a store, load the data for all folders of it
1551
		if (empty($this->folderStatCache[$user])) {
1552
			// get the store
1553
			$userstore = $this->openMessageStore($user);
1554
			$rootfolder = mapi_msgstore_openentry($userstore);
1555
			$hierarchy = mapi_folder_gethierarchytable($rootfolder, CONVENIENT_DEPTH);
1556
			$rows = mapi_table_queryallrows($hierarchy, [PR_SOURCE_KEY, PR_LOCAL_COMMIT_TIME_MAX, PR_CONTENT_COUNT, PR_CONTENT_UNREAD, PR_DELETED_MSG_COUNT]);
0 ignored issues
show
Bug introduced by
The constant PR_CONTENT_COUNT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_SOURCE_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_LOCAL_COMMIT_TIME_MAX was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_DELETED_MSG_COUNT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_CONTENT_UNREAD was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1557
1558
			if (count($rows) == 0) {
1559
				SLog::Write(LOGLEVEL_INFO, sprintf("Grommunio->GetFolderStat(): could not access folder statistics for user '%s'. Probably missing 'read' permissions on the root folder! Folders of this store will be synchronized ONCE per hour only!", $user));
1560
			}
1561
1562
			foreach ($rows as $folder) {
1563
				$commit_time = isset($folder[PR_LOCAL_COMMIT_TIME_MAX]) ? $folder[PR_LOCAL_COMMIT_TIME_MAX] : "0000000000";
1564
				$content_count = isset($folder[PR_CONTENT_COUNT]) ? $folder[PR_CONTENT_COUNT] : -1;
1565
				$content_unread = isset($folder[PR_CONTENT_UNREAD]) ? $folder[PR_CONTENT_UNREAD] : -1;
1566
				$content_deleted = isset($folder[PR_DELETED_MSG_COUNT]) ? $folder[PR_DELETED_MSG_COUNT] : -1;
1567
1568
				$this->folderStatCache[$user][bin2hex($folder[PR_SOURCE_KEY])] = $commit_time . "/" . $content_count . "/" . $content_unread . "/" . $content_deleted;
1569
			}
1570
			SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->GetFolderStat() fetched status information of %d folders for store '%s'", count($this->folderStatCache[$user]), $user));
1571
		}
1572
1573
		if (isset($this->folderStatCache[$user][$folderid])) {
1574
			return $this->folderStatCache[$user][$folderid];
1575
		}
1576
1577
		// a timestamp that changes once per hour is returned in case there is no data found for this folder. It will be synchronized only once per hour.
1578
		return gmdate("Y-m-d-H");
1579
	}
1580
1581
	/**
1582
	 * Get a list of all folders in the public store that have PR_SYNC_TO_MOBILE set.
1583
	 *
1584
	 * @return array
1585
	 */
1586
	public function GetPublicSyncEnabledFolders() {
1587
		$f = [];
1588
		if (!defined("PR_SYNC_TO_MOBILE")) {
1589
			return $f;
1590
		}
1591
		$store = $this->openMessageStore("SYSTEM");
1592
		$pubStore = mapi_msgstore_openentry($store, null);
1593
		$hierarchyTable = mapi_folder_gethierarchytable($pubStore, CONVENIENT_DEPTH);
1594
		$restriction = [ RES_PROPERTY,
1595
			[
1596
				RELOP => RELOP_EQ,
1597
				ULPROPTAG => PR_SYNC_TO_MOBILE,
0 ignored issues
show
Bug introduced by
The constant PR_SYNC_TO_MOBILE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1598
				VALUE => [ PR_SYNC_TO_MOBILE => True],
1599
			]
1600
		];
1601
		mapi_table_restrict($hierarchyTable, $restriction, TBL_BATCH);
1602
		$rows = mapi_table_queryallrows($hierarchyTable, [ PR_DISPLAY_NAME, PR_CONTAINER_CLASS, PR_SOURCE_KEY ]);
0 ignored issues
show
Bug introduced by
The constant PR_CONTAINER_CLASS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_SOURCE_KEY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1603
		$f = [];
1604
		foreach ($rows as $row) {
1605
			$folderid = bin2hex($row[PR_SOURCE_KEY]);
1606
			$f[$folderid] = [
1607
				'store' => 'SYSTEM',
1608
				'flags' => DeviceManager::FLD_FLAGS_NONE,
1609
				'folderid' => $folderid,
1610
				'parentid' => '0',
1611
				'name' => $row[PR_DISPLAY_NAME],
1612
				'type' => MAPIUtils::GetFolderTypeFromContainerClass($row[PR_CONTAINER_CLASS]),
1613
			];
1614
		}
1615
		return $f;
1616
	}
1617
1618
	/*----------------------------------------------------------------------------------------------------------
1619
	 * Implementation of the IStateMachine interface
1620
	 */
1621
1622
	/**
1623
	 * Gets a hash value indicating the latest dataset of the named
1624
	 * state with a specified key and counter.
1625
	 * If the state is changed between two calls of this method
1626
	 * the returned hash should be different.
1627
	 *
1628
	 * @param string $devid   the device id
1629
	 * @param string $type    the state type
1630
	 * @param string $key     (opt)
1631
	 * @param string $counter (opt)
1632
	 *
1633
	 * @throws StateNotFoundException
1634
	 *
1635
	 * @return string
1636
	 */
1637
	public function GetStateHash($devid, $type, $key = false, $counter = false) {
1638
		try {
1639
			$stateMessage = $this->getStateMessage($devid, $type, $key, $counter);
0 ignored issues
show
Bug introduced by
It seems like $key can also be of type false; however, parameter $key of Grommunio::getStateMessage() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

1639
			$stateMessage = $this->getStateMessage($devid, $type, /** @scrutinizer ignore-type */ $key, $counter);
Loading history...
Bug introduced by
It seems like $counter can also be of type false; however, parameter $counter of Grommunio::getStateMessage() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

1639
			$stateMessage = $this->getStateMessage($devid, $type, $key, /** @scrutinizer ignore-type */ $counter);
Loading history...
1640
			$stateMessageProps = mapi_getprops($stateMessage, [PR_LAST_MODIFICATION_TIME]);
0 ignored issues
show
Bug introduced by
The constant PR_LAST_MODIFICATION_TIME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1641
			if (isset($stateMessageProps[PR_LAST_MODIFICATION_TIME])) {
1642
				return $stateMessageProps[PR_LAST_MODIFICATION_TIME];
1643
			}
1644
		}
1645
		catch (StateNotFoundException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1646
		}
1647
1648
		return "0";
1649
	}
1650
1651
	/**
1652
	 * Gets a state for a specified key and counter.
1653
	 * This method should call IStateMachine->CleanStates()
1654
	 * to remove older states (same key, previous counters).
1655
	 *
1656
	 * @param string $devid       the device id
1657
	 * @param string $type        the state type
1658
	 * @param string $key         (opt)
1659
	 * @param string $counter     (opt)
1660
	 * @param string $cleanstates (opt)
1661
	 *
1662
	 * @throws StateNotFoundException, StateInvalidException, UnavailableException
1663
	 *
1664
	 * @return mixed
1665
	 */
1666
	public function GetState($devid, $type, $key = false, $counter = false, $cleanstates = true) {
1667
		if ($counter && $cleanstates) {
1668
			$this->CleanStates($devid, $type, $key, $counter);
0 ignored issues
show
Bug introduced by
It seems like $key can also be of type false; however, parameter $key of Grommunio::CleanStates() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

1668
			$this->CleanStates($devid, $type, /** @scrutinizer ignore-type */ $key, $counter);
Loading history...
1669
			// also clean Failsave state for previous counter
1670
			if ($key == false) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $key of type false|string against false; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
1671
				$this->CleanStates($devid, $type, IStateMachine::FAILSAVE, $counter);
1672
			}
1673
		}
1674
		$stateMessage = $this->getStateMessage($devid, $type, $key, $counter);
0 ignored issues
show
Bug introduced by
It seems like $counter can also be of type false; however, parameter $counter of Grommunio::getStateMessage() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

1674
		$stateMessage = $this->getStateMessage($devid, $type, $key, /** @scrutinizer ignore-type */ $counter);
Loading history...
Bug introduced by
It seems like $key can also be of type false; however, parameter $key of Grommunio::getStateMessage() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

1674
		$stateMessage = $this->getStateMessage($devid, $type, /** @scrutinizer ignore-type */ $key, $counter);
Loading history...
1675
		$state = base64_decode(MAPIUtils::readPropStream($stateMessage, PR_BODY));
0 ignored issues
show
Bug introduced by
The constant PR_BODY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1676
1677
		if ($state && $state[0] === '{') {
1678
			$jsonDec = json_decode($state);
1679
			if (isset($jsonDec->gsSyncStateClass)) {
1680
				$gsObj = new $jsonDec->gsSyncStateClass();
1681
				$gsObj->jsonDeserialize($jsonDec);
1682
				$gsObj->postUnserialize();
1683
			}
1684
		}
1685
1686
		return isset($gsObj) && is_object($gsObj) ? $gsObj : $state;
1687
	}
1688
1689
	/**
1690
	 * Writes ta state to for a key and counter.
1691
	 *
1692
	 * @param mixed  $state
1693
	 * @param string $devid   the device id
1694
	 * @param string $type    the state type
1695
	 * @param string $key     (opt)
1696
	 * @param int    $counter (opt)
1697
	 *
1698
	 * @throws StateInvalidException, UnavailableException
1699
	 *
1700
	 * @return bool
1701
	 */
1702
	public function SetState($state, $devid, $type, $key = false, $counter = false) {
1703
		return $this->setStateMessage($state, $devid, $type, $key, $counter);
0 ignored issues
show
Bug introduced by
It seems like $counter can also be of type false; however, parameter $counter of Grommunio::setStateMessage() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

1703
		return $this->setStateMessage($state, $devid, $type, $key, /** @scrutinizer ignore-type */ $counter);
Loading history...
Bug introduced by
It seems like $key can also be of type false; however, parameter $key of Grommunio::setStateMessage() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

1703
		return $this->setStateMessage($state, $devid, $type, /** @scrutinizer ignore-type */ $key, $counter);
Loading history...
1704
	}
1705
1706
	/**
1707
	 * Cleans up all older states.
1708
	 * If called with a $counter, all states previous state counter can be removed.
1709
	 * If additionally the $thisCounterOnly flag is true, only that specific counter will be removed.
1710
	 * If called without $counter, all keys (independently from the counter) can be removed.
1711
	 *
1712
	 * @param string $devid           the device id
1713
	 * @param string $type            the state type
1714
	 * @param string $key
1715
	 * @param string $counter         (opt)
1716
	 * @param string $thisCounterOnly (opt) if provided, the exact counter only will be removed
1717
	 *
1718
	 * @throws StateInvalidException
1719
	 *
1720
	 * @return
1721
	 */
1722
	public function CleanStates($devid, $type, $key, $counter = false, $thisCounterOnly = false) {
1723
		if (!$this->stateFolder) {
1724
			$this->getStateFolder($devid);
1725
			if (!$this->stateFolder) {
1726
				throw new StateNotFoundException(sprintf(
1727
					"Grommunio->getStateMessage(): Could not locate the state folder for device '%s'",
1728
					$devid
1729
				));
1730
			}
1731
		}
1732
		$messageName = rtrim((($key !== false) ? $key . "-" : "") . (($type !== "") ? $type : ""), "-");
0 ignored issues
show
introduced by
The condition $key !== false is always true.
Loading history...
1733
		$restriction = $this->getStateMessageRestriction($messageName, $counter, $thisCounterOnly);
0 ignored issues
show
Bug introduced by
It seems like $thisCounterOnly can also be of type false; however, parameter $thisCounterOnly of Grommunio::getStateMessageRestriction() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

1733
		$restriction = $this->getStateMessageRestriction($messageName, $counter, /** @scrutinizer ignore-type */ $thisCounterOnly);
Loading history...
Bug introduced by
It seems like $counter can also be of type false; however, parameter $counter of Grommunio::getStateMessageRestriction() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

1733
		$restriction = $this->getStateMessageRestriction($messageName, /** @scrutinizer ignore-type */ $counter, $thisCounterOnly);
Loading history...
1734
		$stateFolderContents = mapi_folder_getcontentstable($this->stateFolder, MAPI_ASSOCIATED);
1735
		if ($stateFolderContents) {
1736
			mapi_table_restrict($stateFolderContents, $restriction);
1737
			$rowCnt = mapi_table_getrowcount($stateFolderContents);
1738
			SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->CleanStates(): Found %d states to clean (%s)", $rowCnt, $messageName));
1739
			if ($rowCnt > 0) {
1740
				$rows = mapi_table_queryallrows($stateFolderContents, [PR_ENTRYID]);
1741
				$entryids = [];
1742
				foreach ($rows as $row) {
1743
					$entryids[] = $row[PR_ENTRYID];
1744
				}
1745
				mapi_folder_deletemessages($this->stateFolder, $entryids, DELETE_HARD_DELETE);
1746
			}
1747
		}
1748
	}
1749
1750
	/**
1751
	 * Links a user to a device.
1752
	 *
1753
	 * @param string $username
1754
	 * @param string $devid
1755
	 *
1756
	 * @return bool indicating if the user was added or not (existed already)
1757
	 */
1758
	public function LinkUserDevice($username, $devid) {
1759
		$device = [$devid => time()];
1760
		$this->setDeviceUserData($this->type, $device, $username, -1, $subkey = -1, $doCas = "merge");
1761
1762
		return false;
1763
	}
1764
1765
	/**
1766
	 * Unlinks a device from a user.
1767
	 *
1768
	 * @param string $username
1769
	 * @param string $devid
1770
	 *
1771
	 * @return bool
1772
	 */
1773
	public function UnLinkUserDevice($username, $devid) {
1774
		// TODO: Implement
1775
		return false;
1776
	}
1777
1778
	/**
1779
	 * Returns the current version of the state files
1780
	 * grommunio:  This is not relevant atm. IStateMachine::STATEVERSION_02 will match GSync::GetLatestStateVersion().
1781
	 *          If it might be required to update states in the future, this could be implemented on a store level,
1782
	 *          where states are then migrated "on-the-fly"
1783
	 *          or
1784
	 *          in a global settings where all states in all stores are migrated once.
1785
	 *
1786
	 * @return int
1787
	 */
1788
	public function GetStateVersion() {
1789
		return IStateMachine::STATEVERSION_02;
0 ignored issues
show
Bug Best Practice introduced by
The expression return IStateMachine::STATEVERSION_02 returns the type string which is incompatible with the documented return type integer.
Loading history...
1790
	}
1791
1792
	/**
1793
	 * Sets the current version of the state files.
1794
	 *
1795
	 * @param int $version the new supported version
1796
	 *
1797
	 * @return bool
1798
	 */
1799
	public function SetStateVersion($version) {
1800
		return true;
1801
	}
1802
1803
	/**
1804
	 * Returns MAPIFolder object which contains the state information.
1805
	 * Creates this folder if it is not available yet.
1806
	 *
1807
	 * @param string $devid the device id
1808
	 *
1809
	 * @return MAPIFolder
0 ignored issues
show
Bug introduced by
The type MAPIFolder 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...
1810
	 */
1811
	private function getStateFolder($devid) {
1812
		// Options request doesn't send device id
1813
		if (strlen($devid) == 0) {
1814
			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 MAPIFolder.
Loading history...
1815
		}
1816
		// Try to get the state folder id from redis
1817
		if (!$this->stateFolder) {
1818
			$folderentryid = $this->getDeviceUserData($this->userDeviceData, $devid, $this->mainUser, "statefolder");
1819
			if ($folderentryid) {
1820
				$this->stateFolder = mapi_msgstore_openentry($this->defaultstore, hex2bin($folderentryid));
1821
			}
1822
		}
1823
1824
		// fallback code
1825
		if (!$this->stateFolder && $this->defaultstore) {
1826
			SLog::Write(LOGLEVEL_INFO, sprintf("Grommunio->getStateFolder(): state folder not set. Use fallback"));
1827
			$rootfolder = mapi_msgstore_openentry($this->defaultstore);
1828
			$hierarchy = mapi_folder_gethierarchytable($rootfolder, CONVENIENT_DEPTH);
1829
			$restriction = $this->getStateFolderRestriction($devid);
1830
			// restrict the hierarchy to the grommunio-sync search folder only
1831
			mapi_table_restrict($hierarchy, $restriction);
1832
			$rowCnt = mapi_table_getrowcount($hierarchy);
1833
			SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->getStateFolder(): found %d device state folders", $rowCnt));
1834
			if ($rowCnt == 1) {
1835
				$hierarchyRows = mapi_table_queryrows($hierarchy, [PR_ENTRYID], 0, 1);
1836
				$this->stateFolder = mapi_msgstore_openentry($this->defaultstore, $hierarchyRows[0][PR_ENTRYID]);
1837
				SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->getStateFolder(): %s", bin2hex($hierarchyRows[0][PR_ENTRYID])));
1838
				// put found id in redis
1839
				if ($devid) {
1840
					$this->setDeviceUserData($this->userDeviceData, bin2hex($hierarchyRows[0][PR_ENTRYID]), $devid, $this->mainUser, "statefolder");
1841
				}
1842
			}
1843
			elseif ($rowCnt == 0) {
1844
				// legacy code: create the hidden state folder and the device subfolder
1845
				// this should happen when the user configures the device (autodiscover or first sync if no autodiscover)
1846
1847
				$hierarchy = mapi_folder_gethierarchytable($rootfolder, CONVENIENT_DEPTH);
1848
				$restriction = $this->getStateFolderRestriction(STORE_STATE_FOLDER);
1849
				mapi_table_restrict($hierarchy, $restriction);
1850
				$rowCnt = mapi_table_getrowcount($hierarchy);
1851
				SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->getStateFolder(): found %d store state folders", $rowCnt));
1852
				if ($rowCnt == 1) {
1853
					$hierarchyRows = mapi_table_queryrows($hierarchy, [PR_ENTRYID], 0, 1);
1854
					$stateFolder = mapi_msgstore_openentry($this->defaultstore, $hierarchyRows[0][PR_ENTRYID]);
1855
				}
1856
				elseif ($rowCnt == 0) {
1857
					$stateFolder = mapi_folder_createfolder($rootfolder, STORE_STATE_FOLDER, "");
0 ignored issues
show
Bug introduced by
The function mapi_folder_createfolder was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1857
					$stateFolder = /** @scrutinizer ignore-call */ mapi_folder_createfolder($rootfolder, STORE_STATE_FOLDER, "");
Loading history...
1858
					mapi_setprops($stateFolder, [PR_ATTR_HIDDEN => true]);
0 ignored issues
show
Bug introduced by
The constant PR_ATTR_HIDDEN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1859
				}
1860
1861
				// TODO: handle this
1862
1863
				if (isset($stateFolder) && $stateFolder) {
1864
					$devStateFolder = mapi_folder_createfolder($stateFolder, $devid, "");
1865
					$devStateFolderProps = mapi_getprops($devStateFolder);
1866
					$this->stateFolder = mapi_msgstore_openentry($this->defaultstore, $devStateFolderProps[PR_ENTRYID]);
1867
					mapi_setprops($this->stateFolder, [PR_ATTR_HIDDEN => true]);
1868
					// we don't cache the entryid in redis, because this will happen on the next request anyway
1869
				}
1870
1871
				// TODO: unable to create state folder - throw exception
1872
			}
1873
1874
			// This case is rather unlikely that there would be several
1875
				// hidden folders having PR_DISPLAY_NAME the same as device id.
1876
1877
				// TODO: get the hierarchy table again, get entry id of STORE_STATE_FOLDER
1878
				// and compare it to the parent id of those folders.
1879
		}
1880
1881
		return $this->stateFolder;
1882
	}
1883
1884
	/**
1885
	 * Returns the associated MAPIMessage which contains the state information.
1886
	 *
1887
	 * @param string $devid   the device id
1888
	 * @param string $type    the state type
1889
	 * @param string $key     (opt)
1890
	 * @param string $counter state counter
1891
	 *
1892
	 * @throws StateNotFoundException
1893
	 *
1894
	 * @return MAPIMessage
0 ignored issues
show
Bug introduced by
The type MAPIMessage 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...
1895
	 */
1896
	private function getStateMessage($devid, $type, $key, $counter) {
1897
		if (!$this->stateFolder) {
1898
			$this->getStateFolder(Request::GetDeviceID());
1899
			if (!$this->stateFolder) {
1900
				throw new StateNotFoundException(sprintf(
1901
					"Grommunio->getStateMessage(): Could not locate the state folder for device '%s'",
1902
					$devid
1903
				));
1904
			}
1905
		}
1906
		$messageName = rtrim((($key !== false) ? $key . "-" : "") . (($type !== "") ? $type : ""), "-");
0 ignored issues
show
introduced by
The condition $key !== false is always true.
Loading history...
1907
		$restriction = $this->getStateMessageRestriction($messageName, $counter, true);
0 ignored issues
show
Bug introduced by
true of type true is incompatible with the type string expected by parameter $thisCounterOnly of Grommunio::getStateMessageRestriction(). ( Ignorable by Annotation )

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

1907
		$restriction = $this->getStateMessageRestriction($messageName, $counter, /** @scrutinizer ignore-type */ true);
Loading history...
1908
		$stateFolderContents = mapi_folder_getcontentstable($this->stateFolder, MAPI_ASSOCIATED);
1909
		if ($stateFolderContents) {
1910
			mapi_table_restrict($stateFolderContents, $restriction);
1911
			$rowCnt = mapi_table_getrowcount($stateFolderContents);
1912
			if ($rowCnt == 1) {
1913
				$stateFolderRows = mapi_table_queryrows($stateFolderContents, [PR_ENTRYID], 0, 1);
1914
1915
				return mapi_msgstore_openentry($this->defaultstore, $stateFolderRows[0][PR_ENTRYID]);
1916
			}
1917
1918
			if ($rowCnt > 1) {
1919
				SLog::Write(LOGLEVEL_WARN, sprintf("Grommunio->getStateMessage(): Cleaning up duplicated state messages '%s' (%d)", $messageName, $rowCnt));
1920
				$this->CleanStates($devid, $type, $key, $counter, true);
0 ignored issues
show
Bug introduced by
true of type true is incompatible with the type string expected by parameter $thisCounterOnly of Grommunio::CleanStates(). ( Ignorable by Annotation )

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

1920
				$this->CleanStates($devid, $type, $key, $counter, /** @scrutinizer ignore-type */ true);
Loading history...
1921
			}
1922
		}
1923
1924
		throw new StateNotFoundException(sprintf(
1925
			"Grommunio->getStateMessage(): Could not locate the state message '%s' (counter: %s)",
1926
			$messageName,
1927
			Utils::PrintAsString($counter)
1928
		));
1929
	}
1930
1931
	/**
1932
	 * Writes ta state to for a key and counter.
1933
	 *
1934
	 * @param mixed  $state
1935
	 * @param string $devid   the device id
1936
	 * @param string $type    the state type
1937
	 * @param string $key     (opt)
1938
	 * @param int    $counter (opt)
1939
	 *
1940
	 * @throws StateInvalidException, UnavailableException
1941
	 *
1942
	 * @return bool
1943
	 */
1944
	private function setStateMessage($state, $devid, $type, $key = false, $counter = false) {
1945
		if (!$this->stateFolder) {
1946
			throw new StateNotFoundException(sprintf("Grommunio->setStateMessage(): Could not locate the state folder for device '%s'", $devid));
1947
		}
1948
1949
		try {
1950
			$stateMessage = $this->getStateMessage($devid, $type, $key, $counter);
0 ignored issues
show
Bug introduced by
It seems like $counter can also be of type false; however, parameter $counter of Grommunio::getStateMessage() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

1950
			$stateMessage = $this->getStateMessage($devid, $type, $key, /** @scrutinizer ignore-type */ $counter);
Loading history...
Bug introduced by
It seems like $key can also be of type false; however, parameter $key of Grommunio::getStateMessage() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

1950
			$stateMessage = $this->getStateMessage($devid, $type, /** @scrutinizer ignore-type */ $key, $counter);
Loading history...
1951
		}
1952
		catch (StateNotFoundException $e) {
1953
			// if message is not available, try to create a new one
1954
			$stateMessage = mapi_folder_createmessage($this->stateFolder, MAPI_ASSOCIATED);
1955
			SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->setStateMessage(): mapi_folder_createmessage 0x%08X", mapi_last_hresult()));
1956
1957
			$messageName = rtrim((($key !== false) ? $key . "-" : "") . (($type !== "") ? $type : ""), "-");
1958
			SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->setStateMessage(): creating new state message '%s' (counter: %s)", $messageName, Utils::PrintAsString($counter)));
0 ignored issues
show
Bug introduced by
It seems like $counter can also be of type false; however, parameter $var of Utils::PrintAsString() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

1958
			SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->setStateMessage(): creating new state message '%s' (counter: %s)", $messageName, Utils::PrintAsString(/** @scrutinizer ignore-type */ $counter)));
Loading history...
1959
			mapi_setprops($stateMessage, [PR_DISPLAY_NAME => $messageName, PR_MESSAGE_CLASS => 'IPM.Note.GrommunioState']);
0 ignored issues
show
Bug introduced by
The constant PR_MESSAGE_CLASS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1960
		}
1961
		if (isset($stateMessage)) {
1962
			$jsonEncodedState = is_object($state) || is_array($state) ? json_encode($state, JSON_INVALID_UTF8_IGNORE | JSON_UNESCAPED_UNICODE) : $state;
1963
1964
			$encodedState = base64_encode($jsonEncodedState);
1965
			$encodedStateLength = strlen($encodedState);
1966
			mapi_setprops($stateMessage, [PR_LAST_VERB_EXECUTED => is_int($counter) ? $counter : 0]);
0 ignored issues
show
Bug introduced by
The constant PR_LAST_VERB_EXECUTED was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1967
			$stream = mapi_openproperty($stateMessage, PR_BODY, IID_IStream, STGM_DIRECT, MAPI_CREATE | MAPI_MODIFY);
0 ignored issues
show
Bug introduced by
The constant PR_BODY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1968
			mapi_stream_setsize($stream, $encodedStateLength);
1969
			mapi_stream_write($stream, $encodedState);
1970
			mapi_stream_commit($stream);
1971
			mapi_savechanges($stateMessage);
1972
1973
			return $encodedStateLength;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $encodedStateLength returns the type integer which is incompatible with the documented return type boolean.
Loading history...
1974
		}
1975
1976
		return false;
1977
	}
1978
1979
	/**
1980
	 * Returns the restriction for the state folder name.
1981
	 *
1982
	 * @param string $folderName the state folder name
1983
	 *
1984
	 * @return array
1985
	 */
1986
	private function getStateFolderRestriction($folderName) {
1987
		return [RES_AND, [
1988
			[RES_PROPERTY,
1989
				[RELOP => RELOP_EQ,
1990
					ULPROPTAG => PR_DISPLAY_NAME,
0 ignored issues
show
Bug introduced by
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1991
					VALUE => $folderName,
1992
				],
1993
			],
1994
			[RES_PROPERTY,
1995
				[RELOP => RELOP_EQ,
1996
					ULPROPTAG => PR_ATTR_HIDDEN,
0 ignored issues
show
Bug introduced by
The constant PR_ATTR_HIDDEN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1997
					VALUE => true,
1998
				],
1999
			],
2000
		]];
2001
	}
2002
2003
	/**
2004
	 * Returns the restriction for the associated message in the state folder.
2005
	 *
2006
	 * @param string $messageName     the message name
2007
	 * @param string $counter         counter
2008
	 * @param string $thisCounterOnly (opt) if provided, restrict to the exact counter
2009
	 *
2010
	 * @return array
2011
	 */
2012
	private function getStateMessageRestriction($messageName, $counter, $thisCounterOnly = false) {
2013
		return [RES_AND, [
2014
			[RES_PROPERTY,
2015
				[RELOP => RELOP_EQ,
2016
					ULPROPTAG => PR_DISPLAY_NAME,
0 ignored issues
show
Bug introduced by
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2017
					VALUE => $messageName,
2018
				],
2019
			],
2020
			[RES_PROPERTY,
2021
				[RELOP => RELOP_EQ,
2022
					ULPROPTAG => PR_MESSAGE_CLASS,
0 ignored issues
show
Bug introduced by
The constant PR_MESSAGE_CLASS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2023
					VALUE => 'IPM.Note.GrommunioState',
2024
				],
2025
			],
2026
			[RES_PROPERTY,
2027
				[RELOP => $thisCounterOnly ? RELOP_EQ : RELOP_LT,
2028
					ULPROPTAG => PR_LAST_VERB_EXECUTED,
0 ignored issues
show
Bug introduced by
The constant PR_LAST_VERB_EXECUTED was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2029
					VALUE => $counter,
2030
				],
2031
			],
2032
		]];
2033
	}
2034
2035
	/*----------------------------------------------------------------------------------------------------------
2036
	 * Private methods
2037
	 */
2038
2039
	/**
2040
	 * Returns a hash representing changes in the hierarchy of the main user.
2041
	 * It changes if a folder is added, renamed or deleted.
2042
	 *
2043
	 * @return string
2044
	 */
2045
	private function getHierarchyHash() {
2046
		$rootfolder = mapi_msgstore_openentry($this->defaultstore);
2047
		$hierarchy = mapi_folder_gethierarchytable($rootfolder, CONVENIENT_DEPTH);
2048
2049
		return md5(serialize(mapi_table_queryallrows($hierarchy, [PR_DISPLAY_NAME, PR_PARENT_ENTRYID])));
0 ignored issues
show
Bug introduced by
The constant PR_PARENT_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2050
	}
2051
2052
	/**
2053
	 * Advises a store to the changes sink.
2054
	 *
2055
	 * @param mapistore $store store to be advised
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...
2056
	 *
2057
	 * @return bool
2058
	 */
2059
	private function adviseStoreToSink($store) {
2060
		// check if we already advised the store
2061
		if (!in_array($store, $this->changesSinkStores)) {
2062
			mapi_msgstore_advise($store, null, fnevNewMail |fnevObjectModified | fnevObjectCreated | fnevObjectMoved | fnevObjectDeleted, $this->changesSink);
0 ignored issues
show
Bug introduced by
The function mapi_msgstore_advise was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2062
			/** @scrutinizer ignore-call */ 
2063
   mapi_msgstore_advise($store, null, fnevNewMail |fnevObjectModified | fnevObjectCreated | fnevObjectMoved | fnevObjectDeleted, $this->changesSink);
Loading history...
2063
2064
			if (mapi_last_hresult()) {
2065
				SLog::Write(LOGLEVEL_WARN, sprintf("Grommunio->adviseStoreToSink(): failed to advised store '%s' with code 0x%X. Polling will be performed.", $store, mapi_last_hresult()));
2066
2067
				return false;
2068
			}
2069
2070
			SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->adviseStoreToSink(): advised store '%s'", $store));
2071
			$this->changesSinkStores[] = $store;
2072
		}
2073
2074
		return true;
2075
	}
2076
2077
	/**
2078
	 * Open the store marked with PR_DEFAULT_STORE = TRUE
2079
	 * if $return_public is set, the public store is opened.
2080
	 *
2081
	 * @param string $user User which store should be opened
2082
	 *
2083
	 * @return bool
2084
	 */
2085
	private function openMessageStore($user) {
2086
		// During PING requests the operations store has to be switched constantly
2087
		// the cache prevents the same store opened several times
2088
		if (isset($this->storeCache[$user])) {
2089
			return $this->storeCache[$user];
2090
		}
2091
2092
		$entryid = false;
2093
		$return_public = false;
2094
2095
		if (strtoupper($user) == 'SYSTEM') {
2096
			$return_public = true;
2097
		}
2098
2099
		// loop through the storestable if authenticated user of public folder
2100
		if ($user == $this->mainUser || $return_public === true) {
2101
			// Find the default store
2102
			$storestables = mapi_getmsgstorestable($this->session);
2103
			$result = mapi_last_hresult();
2104
2105
			if ($result == NOERROR) {
2106
				$rows = mapi_table_queryallrows($storestables, [PR_ENTRYID, PR_DEFAULT_STORE, PR_MDB_PROVIDER]);
0 ignored issues
show
Bug introduced by
The constant PR_DEFAULT_STORE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_MDB_PROVIDER was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2107
				$result = mapi_last_hresult();
2108
				if ($result != NOERROR || !is_array($rows)) {
2109
					SLog::Write(LOGLEVEL_WARN, sprintf("Grommunio->openMessageStore('%s'): Could not get storestables information 0x%08X", $user, $result));
2110
	
2111
					return false;
2112
				}
2113
2114
				foreach ($rows as $row) {
2115
					if (!$return_public && isset($row[PR_DEFAULT_STORE]) && $row[PR_DEFAULT_STORE] == true) {
2116
						$entryid = $row[PR_ENTRYID];
2117
2118
						break;
2119
					}
2120
					if ($return_public && isset($row[PR_MDB_PROVIDER]) && $row[PR_MDB_PROVIDER] == ZARAFA_STORE_PUBLIC_GUID) {
2121
						$entryid = $row[PR_ENTRYID];
2122
2123
						break;
2124
					}
2125
				}
2126
			}
2127
		}
2128
		else {
2129
			$entryid = @mapi_msgstore_createentryid($this->defaultstore, $user);
2130
		}
2131
2132
		if ($entryid) {
2133
			$store = @mapi_openmsgstore($this->session, $entryid);
2134
2135
			if (!$store) {
2136
				SLog::Write(LOGLEVEL_WARN, sprintf("Grommunio->openMessageStore('%s'): Could not open store", $user));
2137
2138
				return false;
2139
			}
2140
2141
			// add this store to the cache
2142
			if (!isset($this->storeCache[$user])) {
2143
				$this->storeCache[$user] = $store;
2144
			}
2145
2146
			SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->openMessageStore('%s'): Found '%s' store: '%s'", $user, (($return_public) ? 'PUBLIC' : 'DEFAULT'), $store));
2147
2148
			return $store;
2149
		}
2150
2151
		SLog::Write(LOGLEVEL_WARN, sprintf("Grommunio->openMessageStore('%s'): No store found for this user", $user));
2152
2153
		return false;
2154
	}
2155
2156
	/**
2157
	 * Checks if the logged in user has secretary permissions on a folder.
2158
	 *
2159
	 * @param resource $store
2160
	 * @param string   $folderid
2161
	 * @param mixed    $entryid
2162
	 *
2163
	 * @return bool
2164
	 */
2165
	public function HasSecretaryACLs($store, $folderid, $entryid = false) {
2166
		if (!$entryid) {
2167
			$entryid = mapi_msgstore_entryidfromsourcekey($store, hex2bin($folderid));
2168
			if (!$entryid) {
2169
				SLog::Write(LOGLEVEL_WARN, sprintf("Grommunio->HasSecretaryACLs(): error, no entryid resolved for %s on store %s", $folderid, $store));
0 ignored issues
show
Bug introduced by
$store of type resource is incompatible with the type double|integer|string expected by parameter $values of sprintf(). ( Ignorable by Annotation )

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

2169
				SLog::Write(LOGLEVEL_WARN, sprintf("Grommunio->HasSecretaryACLs(): error, no entryid resolved for %s on store %s", $folderid, /** @scrutinizer ignore-type */ $store));
Loading history...
2170
2171
				return false;
2172
			}
2173
		}
2174
2175
		$folder = mapi_msgstore_openentry($store, $entryid);
2176
		if (!$folder) {
2177
			SLog::Write(LOGLEVEL_WARN, sprintf("Grommunio->HasSecretaryACLs(): error, could not open folder with entryid %s on store %s", bin2hex($entryid), $store));
2178
2179
			return false;
2180
		}
2181
2182
		$props = mapi_getprops($folder, [PR_RIGHTS]);
0 ignored issues
show
Bug introduced by
The constant PR_RIGHTS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2183
		if (isset($props[PR_RIGHTS]) &&
2184
			($props[PR_RIGHTS] & ecRightsReadAny) &&
2185
			($props[PR_RIGHTS] & ecRightsCreate) &&
2186
			($props[PR_RIGHTS] & ecRightsEditOwned) &&
2187
			($props[PR_RIGHTS] & ecRightsDeleteOwned) &&
2188
			($props[PR_RIGHTS] & ecRightsEditAny) &&
2189
			($props[PR_RIGHTS] & ecRightsDeleteAny) &&
2190
			($props[PR_RIGHTS] & ecRightsFolderVisible)) {
2191
			return true;
2192
		}
2193
2194
		return false;
2195
	}
2196
2197
	/**
2198
	 * The meta function for out of office settings.
2199
	 *
2200
	 * @param SyncObject $oof
2201
	 */
2202
	private function settingsOOF(&$oof) {
2203
		// if oof state is set it must be set of oof and get otherwise
2204
		if (isset($oof->oofstate)) {
2205
			$this->settingsOofSet($oof);
2206
		}
2207
		else {
2208
			$this->settingsOofGet($oof);
2209
		}
2210
	}
2211
2212
	/**
2213
	 * Gets the out of office settings.
2214
	 *
2215
	 * @param SyncObject $oof
2216
	 */
2217
	private function settingsOofGet(&$oof) {
2218
		$oofprops = mapi_getprops($this->defaultstore, [PR_EC_OUTOFOFFICE, PR_EC_OUTOFOFFICE_MSG, PR_EC_OUTOFOFFICE_SUBJECT, PR_EC_OUTOFOFFICE_FROM, PR_EC_OUTOFOFFICE_UNTIL]);
0 ignored issues
show
Bug introduced by
The constant PR_EC_OUTOFOFFICE_FROM was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_EC_OUTOFOFFICE_SUBJECT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_EC_OUTOFOFFICE_UNTIL was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_EC_OUTOFOFFICE_MSG was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_EC_OUTOFOFFICE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2219
		$oof->oofstate = SYNC_SETTINGSOOF_DISABLED;
2220
		$oof->Status = SYNC_SETTINGSSTATUS_SUCCESS;
2221
		if ($oofprops != false) {
2222
			$oof->oofstate = isset($oofprops[PR_EC_OUTOFOFFICE]) ? ($oofprops[PR_EC_OUTOFOFFICE] ? SYNC_SETTINGSOOF_GLOBAL : SYNC_SETTINGSOOF_DISABLED) : SYNC_SETTINGSOOF_DISABLED;
2223
			// TODO external and external unknown
2224
			$oofmessage = new SyncOOFMessage();
2225
			$oofmessage->appliesToInternal = "";
2226
			$oofmessage->enabled = $oof->oofstate;
2227
			$oofmessage->replymessage = (isset($oofprops[PR_EC_OUTOFOFFICE_MSG])) ? w2u($oofprops[PR_EC_OUTOFOFFICE_MSG]) : "";
2228
			$oofmessage->bodytype = $oof->bodytype;
2229
			unset($oofmessage->appliesToExternal, $oofmessage->appliesToExternalUnknown);
2230
			$oof->oofmessage[] = $oofmessage;
2231
2232
			// check whether time based out of office is set
2233
			if ($oof->oofstate == SYNC_SETTINGSOOF_GLOBAL && isset($oofprops[PR_EC_OUTOFOFFICE_FROM], $oofprops[PR_EC_OUTOFOFFICE_UNTIL])) {
2234
				$now = time();
2235
				if ($now > $oofprops[PR_EC_OUTOFOFFICE_FROM] && $now > $oofprops[PR_EC_OUTOFOFFICE_UNTIL]) {
2236
					// Out of office is set but the date is in the past. Set the state to disabled.
2237
					// @see https://jira.z-hub.io/browse/ZP-1188 for details
2238
					$oof->oofstate = SYNC_SETTINGSOOF_DISABLED;
2239
					@mapi_setprops($this->defaultstore, [PR_EC_OUTOFOFFICE => false]);
2240
					@mapi_deleteprops($this->defaultstore, [PR_EC_OUTOFOFFICE_FROM, PR_EC_OUTOFOFFICE_UNTIL]);
2241
					SLog::Write(LOGLEVEL_INFO, "Grommunio->settingsOofGet(): Out of office is set but the from and until are in the past. Disabling out of office.");
2242
				}
2243
				elseif ($oofprops[PR_EC_OUTOFOFFICE_FROM] < $oofprops[PR_EC_OUTOFOFFICE_UNTIL]) {
2244
					$oof->oofstate = SYNC_SETTINGSOOF_TIMEBASED;
2245
					$oof->starttime = $oofprops[PR_EC_OUTOFOFFICE_FROM];
2246
					$oof->endtime = $oofprops[PR_EC_OUTOFOFFICE_UNTIL];
2247
				}
2248
				else {
2249
					SLog::Write(LOGLEVEL_WARN, sprintf(
2250
						"Grommunio->settingsOofGet(): Time based out of office set but end time ('%s') is before startime ('%s').",
2251
						date("Y-m-d H:i:s", $oofprops[PR_EC_OUTOFOFFICE_FROM]),
2252
						date("Y-m-d H:i:s", $oofprops[PR_EC_OUTOFOFFICE_UNTIL])
2253
					));
2254
					$oof->Status = SYNC_SETTINGSSTATUS_PROTOCOLLERROR;
2255
				}
2256
			}
2257
			elseif ($oof->oofstate == SYNC_SETTINGSOOF_GLOBAL && (isset($oofprops[PR_EC_OUTOFOFFICE_FROM]) || isset($oofprops[PR_EC_OUTOFOFFICE_UNTIL]))) {
2258
				SLog::Write(LOGLEVEL_WARN, sprintf(
2259
					"Grommunio->settingsOofGet(): Time based out of office set but either start time ('%s') or end time ('%s') is missing.",
2260
					(isset($oofprops[PR_EC_OUTOFOFFICE_FROM]) ? date("Y-m-d H:i:s", $oofprops[PR_EC_OUTOFOFFICE_FROM]) : 'empty'),
2261
					(isset($oofprops[PR_EC_OUTOFOFFICE_UNTIL]) ? date("Y-m-d H:i:s", $oofprops[PR_EC_OUTOFOFFICE_UNTIL]) : 'empty')
2262
				));
2263
				$oof->Status = SYNC_SETTINGSSTATUS_PROTOCOLLERROR;
2264
			}
2265
		}
2266
		else {
2267
			SLog::Write(LOGLEVEL_WARN, "Grommunio->Unable to get out of office information");
2268
		}
2269
2270
		// unset body type for oof in order not to stream it
2271
		unset($oof->bodytype);
2272
	}
2273
2274
	/**
2275
	 * Sets the out of office settings.
2276
	 *
2277
	 * @param SyncObject $oof
2278
	 */
2279
	private function settingsOofSet(&$oof) {
2280
		$oof->Status = SYNC_SETTINGSSTATUS_SUCCESS;
2281
		$props = [];
2282
		if ($oof->oofstate == SYNC_SETTINGSOOF_GLOBAL || $oof->oofstate == SYNC_SETTINGSOOF_TIMEBASED) {
2283
			$props[PR_EC_OUTOFOFFICE] = true;
0 ignored issues
show
Bug introduced by
The constant PR_EC_OUTOFOFFICE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2284
			foreach ($oof->oofmessage as $oofmessage) {
2285
				if (isset($oofmessage->appliesToInternal)) {
2286
					$props[PR_EC_OUTOFOFFICE_MSG] = isset($oofmessage->replymessage) ? u2w($oofmessage->replymessage) : "";
0 ignored issues
show
Bug introduced by
The constant PR_EC_OUTOFOFFICE_MSG was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2287
					$props[PR_EC_OUTOFOFFICE_SUBJECT] = "Out of office";
0 ignored issues
show
Bug introduced by
The constant PR_EC_OUTOFOFFICE_SUBJECT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2288
				}
2289
			}
2290
			if ($oof->oofstate == SYNC_SETTINGSOOF_TIMEBASED) {
2291
				if (isset($oof->starttime, $oof->endtime)) {
2292
					$props[PR_EC_OUTOFOFFICE_FROM] = $oof->starttime;
0 ignored issues
show
Bug introduced by
The constant PR_EC_OUTOFOFFICE_FROM was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2293
					$props[PR_EC_OUTOFOFFICE_UNTIL] = $oof->endtime;
0 ignored issues
show
Bug introduced by
The constant PR_EC_OUTOFOFFICE_UNTIL was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2294
				}
2295
				elseif (isset($oof->starttime) || isset($oof->endtime)) {
2296
					$oof->Status = SYNC_SETTINGSSTATUS_PROTOCOLLERROR;
2297
				}
2298
			}
2299
			else {
2300
				$deleteProps = [PR_EC_OUTOFOFFICE_FROM, PR_EC_OUTOFOFFICE_UNTIL];
2301
			}
2302
		}
2303
		elseif ($oof->oofstate == SYNC_SETTINGSOOF_DISABLED) {
2304
			$props[PR_EC_OUTOFOFFICE] = false;
2305
			$deleteProps = [PR_EC_OUTOFOFFICE_FROM, PR_EC_OUTOFOFFICE_UNTIL];
2306
		}
2307
2308
		if (!empty($props)) {
2309
			@mapi_setprops($this->defaultstore, $props);
2310
			$result = mapi_last_hresult();
2311
			if ($result != NOERROR) {
2312
				SLog::Write(LOGLEVEL_ERROR, sprintf("Grommunio->settingsOofSet(): Setting oof information failed (%X)", $result));
2313
2314
				return false;
2315
			}
2316
		}
2317
2318
		if (!empty($deleteProps)) {
2319
			@mapi_deleteprops($this->defaultstore, $deleteProps);
2320
		}
2321
2322
		return true;
2323
	}
2324
2325
	/**
2326
	 * Gets the user's email address from server.
2327
	 *
2328
	 * @param SyncObject $userinformation
2329
	 */
2330
	private function settingsUserInformation(&$userinformation) {
2331
		if (!isset($this->defaultstore) || !isset($this->mainUser)) {
2332
			SLog::Write(LOGLEVEL_ERROR, "Grommunio->settingsUserInformation(): The store or user are not available for getting user information");
2333
2334
			return false;
2335
		}
2336
		$user = nsp_getuserinfo($this->mainUser);
2337
		if ($user != false) {
2338
			$userinformation->Status = SYNC_SETTINGSSTATUS_USERINFO_SUCCESS;
2339
			if (Request::GetProtocolVersion() >= 14.1) {
2340
				$account = new SyncAccount();
2341
				$emailaddresses = new SyncEmailAddresses();
2342
				$emailaddresses->smtpaddress[] = $user["primary_email"];
2343
				$emailaddresses->primarysmtpaddress = $user["primary_email"];
2344
				$account->emailaddresses = $emailaddresses;
2345
				$userinformation->accounts[] = $account;
2346
			}
2347
			else {
2348
				$userinformation->emailaddresses[] = $user["primary_email"];
2349
			}
2350
2351
			return true;
2352
		}
2353
		SLog::Write(LOGLEVEL_ERROR, sprintf("Grommunio->settingsUserInformation(): Getting user information failed: nsp_getuserinfo(%X)", mapi_last_hresult()));
2354
2355
		return false;
2356
	}
2357
2358
	/**
2359
	 * Gets the rights management templates from the server.
2360
	 *
2361
	 * @param SyncObject $rmTemplates
2362
	 */
2363
	private function settingsRightsManagementTemplates(&$rmTemplates) {
2364
		/* Currently there is no information rights management feature in
2365
		 * the grommunio backend, so just return the status and empty
2366
		 * SyncRightsManagementTemplates tag.
2367
		 * Once it's available, it would be something like:
2368
2369
		$rmTemplate = new SyncRightsManagementTemplate();
2370
		$rmTemplate->id = "some-template-id-eg-guid";
2371
		$rmTemplate->name = "Template name";
2372
		$rmTemplate->description = "What does the template do. E.g. it disables forward and reply.";
2373
		$rmTemplates->rmtemplates[] = $rmTemplate;
2374
		 */
2375
		$rmTemplates->Status = SYNC_COMMONSTATUS_IRMFEATUREDISABLED;
2376
		$rmTemplates->rmtemplates = [];
2377
	}
2378
2379
	/**
2380
	 * Sets the importance and priority of a message from a RFC822 message headers.
2381
	 *
2382
	 * @param int   $xPriority
2383
	 * @param array $mapiprops
2384
	 * @param mixed $sendMailProps
2385
	 */
2386
	private function getImportanceAndPriority($xPriority, &$mapiprops, $sendMailProps) {
2387
		switch ($xPriority) {
2388
			case 1:
2389
			case 2:
2390
				$priority = PRIO_URGENT;
2391
				$importance = IMPORTANCE_HIGH;
2392
				break;
2393
2394
			case 4:
2395
			case 5:
2396
				$priority = PRIO_NONURGENT;
2397
				$importance = IMPORTANCE_LOW;
2398
				break;
2399
2400
			case 3:
2401
			default:
2402
				$priority = PRIO_NORMAL;
2403
				$importance = IMPORTANCE_NORMAL;
2404
				break;
2405
		}
2406
		$mapiprops[$sendMailProps["importance"]] = $importance;
2407
		$mapiprops[$sendMailProps["priority"]] = $priority;
2408
	}
2409
2410
	/**
2411
	 * Copies attachments from one message to another.
2412
	 *
2413
	 * @param MAPIMessage $toMessage
2414
	 * @param MAPIMessage $fromMessage
2415
	 */
2416
	private function copyAttachments(&$toMessage, $fromMessage) {
2417
		$attachtable = mapi_message_getattachmenttable($fromMessage);
2418
		$rows = mapi_table_queryallrows($attachtable, [PR_ATTACH_NUM]);
0 ignored issues
show
Bug introduced by
The constant PR_ATTACH_NUM was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2419
2420
		foreach ($rows as $row) {
2421
			if (isset($row[PR_ATTACH_NUM])) {
2422
				$attach = mapi_message_openattach($fromMessage, $row[PR_ATTACH_NUM]);
2423
				$newattach = mapi_message_createattach($toMessage);
2424
				mapi_copyto($attach, [], [], $newattach, 0);
2425
				mapi_savechanges($newattach);
2426
			}
2427
		}
2428
	}
2429
2430
	/**
2431
	 * Function will create a search folder in FINDER_ROOT folder
2432
	 * if folder exists then it will open it.
2433
	 *
2434
	 * @see createSearchFolder($store, $openIfExists = true) function in the webaccess
2435
	 *
2436
	 * @return mapiFolderObject $folder created search folder
0 ignored issues
show
Bug introduced by
The type mapiFolderObject 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...
2437
	 */
2438
	private function getSearchFolder() {
2439
		// create new or open existing search folder
2440
		$searchFolderRoot = $this->getSearchFoldersRoot($this->store);
0 ignored issues
show
Unused Code introduced by
The call to Grommunio::getSearchFoldersRoot() has too many arguments starting with $this->store. ( Ignorable by Annotation )

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

2440
		/** @scrutinizer ignore-call */ 
2441
  $searchFolderRoot = $this->getSearchFoldersRoot($this->store);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
2441
		if ($searchFolderRoot === false) {
2442
			// error in finding search root folder
2443
			// or store doesn't support search folders
2444
			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 mapiFolderObject.
Loading history...
2445
		}
2446
2447
		$searchFolder = $this->createSearchFolder($searchFolderRoot);
2448
2449
		if ($searchFolder !== false && mapi_last_hresult() == NOERROR) {
2450
			return $searchFolder;
2451
		}
2452
2453
		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 mapiFolderObject.
Loading history...
2454
	}
2455
2456
	/**
2457
	 * Function will open FINDER_ROOT folder in root container
2458
	 * public folder's don't have FINDER_ROOT folder.
2459
	 *
2460
	 * @see getSearchFoldersRoot($store) function in the webaccess
2461
	 *
2462
	 * @return mapiFolderObject root folder for search folders
2463
	 */
2464
	private function getSearchFoldersRoot() {
2465
		// check if we can create search folders
2466
		$storeProps = mapi_getprops($this->store, [PR_STORE_SUPPORT_MASK, PR_FINDER_ENTRYID]);
0 ignored issues
show
Bug introduced by
The constant PR_FINDER_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_STORE_SUPPORT_MASK was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2467
		if (($storeProps[PR_STORE_SUPPORT_MASK] & STORE_SEARCH_OK) != STORE_SEARCH_OK) {
2468
			SLog::Write(LOGLEVEL_WARN, "Grommunio->getSearchFoldersRoot(): Store doesn't support search folders. Public store doesn't have FINDER_ROOT folder");
2469
2470
			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 mapiFolderObject.
Loading history...
2471
		}
2472
2473
		// open search folders root
2474
		$searchRootFolder = mapi_msgstore_openentry($this->store, $storeProps[PR_FINDER_ENTRYID]);
2475
		if (mapi_last_hresult() != NOERROR) {
2476
			SLog::Write(LOGLEVEL_WARN, sprintf("Grommunio->getSearchFoldersRoot(): Unable to open search folder (0x%X)", mapi_last_hresult()));
2477
2478
			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 mapiFolderObject.
Loading history...
2479
		}
2480
2481
		return $searchRootFolder;
2482
	}
2483
2484
	/**
2485
	 * Creates a search folder if it not exists or opens an existing one
2486
	 * and returns it.
2487
	 *
2488
	 * @param mapiFolderObject $searchFolderRoot
2489
	 *
2490
	 * @return mapiFolderObject
2491
	 */
2492
	private function createSearchFolder($searchFolderRoot) {
2493
		$folderName = "grommunio-sync Search Folder " . @getmypid();
0 ignored issues
show
Bug introduced by
Are you sure @getmypid() of type false|integer can be used in concatenation? ( Ignorable by Annotation )

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

2493
		$folderName = "grommunio-sync Search Folder " . /** @scrutinizer ignore-type */ @getmypid();
Loading history...
2494
		$searchFolders = mapi_folder_gethierarchytable($searchFolderRoot);
2495
		$restriction = [
2496
			RES_CONTENT,
2497
			[
2498
				FUZZYLEVEL => FL_PREFIX,
2499
				ULPROPTAG => PR_DISPLAY_NAME,
0 ignored issues
show
Bug introduced by
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2500
				VALUE => [PR_DISPLAY_NAME => $folderName],
2501
			],
2502
		];
2503
		// restrict the hierarchy to the grommunio-sync search folder only
2504
		mapi_table_restrict($searchFolders, $restriction);
2505
		if (mapi_table_getrowcount($searchFolders)) {
2506
			$searchFolder = mapi_table_queryrows($searchFolders, [PR_ENTRYID], 0, 1);
2507
2508
			return mapi_msgstore_openentry($this->store, $searchFolder[0][PR_ENTRYID]);
2509
		}
2510
2511
		return mapi_folder_createfolder($searchFolderRoot, $folderName, null, 0, FOLDER_SEARCH);
0 ignored issues
show
Bug introduced by
The function mapi_folder_createfolder was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2511
		return /** @scrutinizer ignore-call */ mapi_folder_createfolder($searchFolderRoot, $folderName, null, 0, FOLDER_SEARCH);
Loading history...
2512
	}
2513
2514
	/**
2515
	 * Creates a search restriction.
2516
	 *
2517
	 * @param ContentParameter $cpo
2518
	 *
2519
	 * @return array
2520
	 */
2521
	private function getSearchRestriction($cpo) {
2522
		$searchText = $cpo->GetSearchFreeText();
2523
2524
		$searchGreater = strtotime($cpo->GetSearchValueGreater());
2525
		$searchLess = strtotime($cpo->GetSearchValueLess());
2526
2527
		if (version_compare(phpversion(), '5.3.4') < 0) {
2528
			SLog::Write(LOGLEVEL_WARN, sprintf("Grommunio->getSearchRestriction(): Your system's PHP version (%s) might not correctly process unicode strings. Search containing such characters might not return correct results. It is recommended to update to at least PHP 5.3.4. See ZP-541 for more information.", phpversion()));
2529
		}
2530
		// split the search on whitespache and look for every word
2531
		$searchText = preg_split("/\\W+/u", $searchText);
2532
		$searchProps = [PR_BODY, PR_SUBJECT, PR_DISPLAY_TO, PR_DISPLAY_CC, PR_SENDER_NAME, PR_SENDER_EMAIL_ADDRESS, PR_SENT_REPRESENTING_NAME, PR_SENT_REPRESENTING_EMAIL_ADDRESS];
0 ignored issues
show
Bug introduced by
The constant PR_SENDER_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_SUBJECT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_DISPLAY_TO was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_BODY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_SENDER_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_SENT_REPRESENTING_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_SENT_REPRESENTING_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_DISPLAY_CC was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2533
		$resAnd = [];
2534
		foreach ($searchText as $term) {
2535
			$resOr = [];
2536
2537
			foreach ($searchProps as $property) {
2538
				array_push(
2539
					$resOr,
2540
					[RES_CONTENT,
2541
						[
2542
							FUZZYLEVEL => FL_SUBSTRING | FL_IGNORECASE,
2543
							ULPROPTAG => $property,
2544
							VALUE => u2w($term),
2545
						],
2546
					]
2547
				);
2548
			}
2549
			array_push($resAnd, [RES_OR, $resOr]);
2550
		}
2551
2552
		// add time range restrictions
2553
		if ($searchGreater) {
2554
			array_push($resAnd, [RES_PROPERTY, [RELOP => RELOP_GE, ULPROPTAG => PR_MESSAGE_DELIVERY_TIME, VALUE => [PR_MESSAGE_DELIVERY_TIME => $searchGreater]]]); // RES_AND;
0 ignored issues
show
Bug introduced by
The constant PR_MESSAGE_DELIVERY_TIME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2555
		}
2556
		if ($searchLess) {
2557
			array_push($resAnd, [RES_PROPERTY, [RELOP => RELOP_LE, ULPROPTAG => PR_MESSAGE_DELIVERY_TIME, VALUE => [PR_MESSAGE_DELIVERY_TIME => $searchLess]]]);
2558
		}
2559
2560
		return [RES_AND, $resAnd];
2561
	}
2562
2563
	/**
2564
	 * Resolve recipient based on his email address.
2565
	 *
2566
	 * @param string $to
2567
	 * @param int    $maxAmbiguousRecipients
2568
	 * @param bool   $expandDistlist
2569
	 *
2570
	 * @return bool|SyncResolveRecipient
2571
	 */
2572
	private function resolveRecipient($to, $maxAmbiguousRecipients, $expandDistlist = true) {
2573
		$recipient = $this->resolveRecipientGAL($to, $maxAmbiguousRecipients, $expandDistlist);
2574
2575
		if ($recipient !== false) {
0 ignored issues
show
introduced by
The condition $recipient !== false is always true.
Loading history...
2576
			return $recipient;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $recipient returns the type array which is incompatible with the documented return type SyncResolveRecipient|boolean.
Loading history...
2577
		}
2578
2579
		$recipient = $this->resolveRecipientContact($to, $maxAmbiguousRecipients);
2580
2581
		if ($recipient !== false) {
2582
			return $recipient;
2583
		}
2584
2585
		return false;
2586
	}
2587
2588
	/**
2589
	 * Resolves recipient from the GAL and gets his certificates.
2590
	 *
2591
	 * @param string $to
2592
	 * @param int    $maxAmbiguousRecipients
2593
	 * @param bool   $expandDistlist
2594
	 *
2595
	 * @return array|bool
2596
	 */
2597
	private function resolveRecipientGAL($to, $maxAmbiguousRecipients, $expandDistlist = true) {
2598
		SLog::Write(LOGLEVEL_WBXML, sprintf("Grommunio->resolveRecipientGAL(): Resolving recipient '%s' in GAL", $to));
2599
		$addrbook = $this->getAddressbook();
2600
		// FIXME: create a function to get the adressbook contentstable
2601
		$ab_entryid = mapi_ab_getdefaultdir($addrbook);
0 ignored issues
show
Bug introduced by
The function mapi_ab_getdefaultdir was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2601
		$ab_entryid = /** @scrutinizer ignore-call */ mapi_ab_getdefaultdir($addrbook);
Loading history...
2602
		if ($ab_entryid) {
2603
			$ab_dir = mapi_ab_openentry($addrbook, $ab_entryid);
2604
		}
2605
		if ($ab_dir) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $ab_dir does not seem to be defined for all execution paths leading up to this point.
Loading history...
2606
			$table = mapi_folder_getcontentstable($ab_dir);
2607
		}
2608
2609
		if (!$table) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $table does not seem to be defined for all execution paths leading up to this point.
Loading history...
2610
			SLog::Write(LOGLEVEL_WARN, sprintf("Grommunio->resolveRecipientGAL(): Unable to open addressbook:0x%X", mapi_last_hresult()));
2611
2612
			return false;
2613
		}
2614
2615
		$restriction = MAPIUtils::GetSearchRestriction(u2w($to));
0 ignored issues
show
Bug introduced by
It seems like u2w($to) can also be of type false; however, parameter $query of MAPIUtils::GetSearchRestriction() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

2615
		$restriction = MAPIUtils::GetSearchRestriction(/** @scrutinizer ignore-type */ u2w($to));
Loading history...
2616
		mapi_table_restrict($table, $restriction);
2617
2618
		$querycnt = mapi_table_getrowcount($table);
2619
		if ($querycnt > 0) {
2620
			$recipientGal = [];
2621
			$rowsToQuery = $maxAmbiguousRecipients;
2622
			// some devices request 0 ambiguous recipients
2623
			if ($querycnt == 1 && $maxAmbiguousRecipients == 0) {
2624
				$rowsToQuery = 1;
2625
			}
2626
			elseif ($querycnt > 1 && $maxAmbiguousRecipients == 0) {
2627
				SLog::Write(LOGLEVEL_INFO, sprintf("Grommunio->resolveRecipientGAL(): GAL search found %d recipients but the device hasn't requested ambiguous recipients", $querycnt));
2628
2629
				return $recipientGal;
2630
			}
2631
			elseif ($querycnt > 1 && $maxAmbiguousRecipients == 1) {
2632
				$rowsToQuery = $querycnt;
2633
			}
2634
			// get the certificate every time because caching the certificate is less expensive than opening addressbook entry again
2635
			$abentries = mapi_table_queryrows($table, [PR_ENTRYID, PR_DISPLAY_NAME, PR_EMS_AB_X509_CERT, PR_OBJECT_TYPE, PR_SMTP_ADDRESS], 0, $rowsToQuery);
0 ignored issues
show
Bug introduced by
The constant PR_OBJECT_TYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_SMTP_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_EMS_AB_X509_CERT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2636
			for ($i = 0, $nrEntries = count($abentries); $i < $nrEntries; ++$i) {
2637
				if (strcasecmp($abentries[$i][PR_SMTP_ADDRESS], $to) !== 0 && $maxAmbiguousRecipients == 1) {
2638
					SLog::Write(LOGLEVEL_INFO, sprintf("Grommunio->resolveRecipientGAL(): maxAmbiguousRecipients is 1 and found non-matching user (to '%s' found: '%s')", $to, $abentries[$i][PR_SMTP_ADDRESS]));
2639
2640
					continue;
2641
				}
2642
				if ($abentries[$i][PR_OBJECT_TYPE] == MAPI_DISTLIST) {
2643
					// check whether to expand dist list
2644
					if ($expandDistlist) {
2645
						SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->resolveRecipientGAL(): '%s' is a dist list. Expand it to members.", $to));
2646
						$distList = mapi_ab_openentry($addrbook, $abentries[$i][PR_ENTRYID]);
2647
						$distListContent = mapi_folder_getcontentstable($distList);
2648
						$distListMembers = mapi_table_queryallrows($distListContent, [PR_ENTRYID, PR_DISPLAY_NAME, PR_EMS_AB_X509_CERT]);
2649
						for ($j = 0, $nrDistListMembers = mapi_table_getrowcount($distListContent); $j < $nrDistListMembers; ++$j) {
2650
							SLog::Write(LOGLEVEL_WBXML, sprintf("Grommunio->resolveRecipientGAL(): distlist's '%s' member: '%s'", $to, $distListMembers[$j][PR_DISPLAY_NAME]));
2651
							$recipientGal[] = $this->createResolveRecipient(SYNC_RESOLVERECIPIENTS_TYPE_GAL, $to, $distListMembers[$j], $nrDistListMembers);
2652
						}
2653
					}
2654
					else {
2655
						SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->resolveRecipientGAL(): '%s' is a dist list, but return it as is.", $to));
2656
						$recipientGal[] = $this->createResolveRecipient(SYNC_RESOLVERECIPIENTS_TYPE_GAL, $abentries[$i][PR_SMTP_ADDRESS], $abentries[$i]);
2657
					}
2658
				}
2659
				elseif ($abentries[$i][PR_OBJECT_TYPE] == MAPI_MAILUSER) {
2660
					$recipientGal[] = $this->createResolveRecipient(SYNC_RESOLVERECIPIENTS_TYPE_GAL, $abentries[$i][PR_SMTP_ADDRESS], $abentries[$i]);
2661
				}
2662
			}
2663
2664
			SLog::Write(LOGLEVEL_WBXML, "Grommunio->resolveRecipientGAL(): Found a recipient in GAL");
2665
2666
			return $recipientGal;
2667
		}
2668
2669
		SLog::Write(LOGLEVEL_WARN, sprintf("Grommunio->resolveRecipientGAL(): No recipient found for: '%s' in GAL", $to));
2670
2671
		return SYNC_RESOLVERECIPSSTATUS_RESPONSE_UNRESOLVEDRECIP;
0 ignored issues
show
Bug Best Practice introduced by
The expression return SYNC_RESOLVERECIP...ESPONSE_UNRESOLVEDRECIP returns the type integer which is incompatible with the documented return type array|boolean.
Loading history...
2672
2673
		return false;
0 ignored issues
show
Unused Code introduced by
return false is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
2674
	}
2675
2676
	/**
2677
	 * Resolves recipient from the contact list and gets his certificates.
2678
	 *
2679
	 * @param string $to
2680
	 * @param int    $maxAmbiguousRecipients
2681
	 *
2682
	 * @return array|bool
2683
	 */
2684
	private function resolveRecipientContact($to, $maxAmbiguousRecipients) {
2685
		SLog::Write(LOGLEVEL_WBXML, sprintf("Grommunio->resolveRecipientContact(): Resolving recipient '%s' in user's contacts", $to));
2686
		// go through all contact folders of the user and
2687
		// check if there's a contact with the given email address
2688
		$root = mapi_msgstore_openentry($this->defaultstore);
2689
		if (!$root) {
2690
			SLog::Write(LOGLEVEL_ERROR, sprintf("Grommunio->resolveRecipientContact(): Unable to open default store: 0x%X", mapi_last_hresult()));
2691
		}
2692
		$rootprops = mapi_getprops($root, [PR_IPM_CONTACT_ENTRYID]);
0 ignored issues
show
Bug introduced by
The constant PR_IPM_CONTACT_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2693
		$contacts = $this->getContactsFromFolder($this->defaultstore, $rootprops[PR_IPM_CONTACT_ENTRYID], $to);
2694
		$recipients = [];
2695
2696
		if ($contacts !== false) {
2697
			SLog::Write(LOGLEVEL_WBXML, sprintf("Grommunio->resolveRecipientContact(): Found %d contacts in main contacts folder.", count($contacts)));
0 ignored issues
show
Bug introduced by
It seems like $contacts can also be of type true; however, parameter $value of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

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

2697
			SLog::Write(LOGLEVEL_WBXML, sprintf("Grommunio->resolveRecipientContact(): Found %d contacts in main contacts folder.", count(/** @scrutinizer ignore-type */ $contacts)));
Loading history...
2698
			// create resolve recipient object
2699
			foreach ($contacts as $contact) {
2700
				$recipients[] = $this->createResolveRecipient(SYNC_RESOLVERECIPIENTS_TYPE_CONTACT, $to, $contact);
2701
			}
2702
		}
2703
2704
		$contactfolder = mapi_msgstore_openentry($this->defaultstore, $rootprops[PR_IPM_CONTACT_ENTRYID]);
2705
		$subfolders = MAPIUtils::GetSubfoldersForType($contactfolder, "IPF.Contact");
2706
		if ($subfolders !== false) {
2707
			foreach ($subfolders as $folder) {
2708
				$contacts = $this->getContactsFromFolder($this->defaultstore, $folder[PR_ENTRYID], $to);
2709
				if ($contacts !== false) {
2710
					SLog::Write(LOGLEVEL_WBXML, sprintf("Grommunio->resolveRecipientContact(): Found %d contacts in contacts' subfolder.", count($contacts)));
2711
					foreach ($contacts as $contact) {
2712
						$recipients[] = $this->createResolveRecipient(SYNC_RESOLVERECIPIENTS_TYPE_CONTACT, $to, $contact);
2713
					}
2714
				}
2715
			}
2716
		}
2717
2718
		// search contacts in public folders
2719
		$storestables = mapi_getmsgstorestable($this->session);
2720
		$result = mapi_last_hresult();
2721
2722
		if ($result == NOERROR) {
2723
			$rows = mapi_table_queryallrows($storestables, [PR_ENTRYID, PR_DEFAULT_STORE, PR_MDB_PROVIDER]);
0 ignored issues
show
Bug introduced by
The constant PR_MDB_PROVIDER was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_DEFAULT_STORE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2724
			foreach ($rows as $row) {
2725
				if (isset($row[PR_MDB_PROVIDER]) && $row[PR_MDB_PROVIDER] == ZARAFA_STORE_PUBLIC_GUID) {
2726
					// TODO refactor public store
2727
					$publicstore = mapi_openmsgstore($this->session, $row[PR_ENTRYID]);
2728
					$publicfolder = mapi_msgstore_openentry($publicstore);
2729
2730
					$subfolders = MAPIUtils::GetSubfoldersForType($publicfolder, "IPF.Contact");
2731
					if ($subfolders !== false) {
2732
						foreach ($subfolders as $folder) {
2733
							$contacts = $this->getContactsFromFolder($publicstore, $folder[PR_ENTRYID], $to);
2734
							if ($contacts !== false) {
2735
								SLog::Write(LOGLEVEL_WBXML, sprintf("Grommunio->resolveRecipientContact(): Found %d contacts in public contacts folder.", count($contacts)));
2736
								foreach ($contacts as $contact) {
2737
									$recipients[] = $this->createResolveRecipient(SYNC_RESOLVERECIPIENTS_TYPE_CONTACT, $to, $contact);
2738
								}
2739
							}
2740
						}
2741
					}
2742
2743
					break;
2744
				}
2745
			}
2746
		}
2747
		else {
2748
			SLog::Write(LOGLEVEL_WARN, sprintf("Grommunio->resolveRecipientContact(): Unable to open public store: 0x%X", $result));
2749
		}
2750
2751
		if (empty($recipients)) {
2752
			$contactProperties = [];
2753
			$contactProperties[PR_DISPLAY_NAME] = $to;
0 ignored issues
show
Bug introduced by
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2754
			$contactProperties[PR_USER_X509_CERTIFICATE] = false;
0 ignored issues
show
Bug introduced by
The constant PR_USER_X509_CERTIFICATE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2755
2756
			$recipients[] = $this->createResolveRecipient(SYNC_RESOLVERECIPIENTS_TYPE_CONTACT, $to, $contactProperties);
2757
		}
2758
2759
		return $recipients;
2760
	}
2761
2762
	/**
2763
	 * Creates SyncResolveRecipientsCertificates object for ResolveRecipients.
2764
	 *
2765
	 * @param binary $certificates
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...
2766
	 * @param int    $recipientCount
2767
	 *
2768
	 * @return SyncResolveRecipientsCertificates
2769
	 */
2770
	private function getCertificates($certificates, $recipientCount = 0) {
2771
		$cert = new SyncResolveRecipientsCertificates();
2772
		if ($certificates === false) {
0 ignored issues
show
introduced by
The condition $certificates === false is always false.
Loading history...
2773
			$cert->status = SYNC_RESOLVERECIPSSTATUS_CERTIFICATES_NOVALIDCERT;
2774
2775
			return $cert;
2776
		}
2777
		$cert->status = SYNC_RESOLVERECIPSSTATUS_SUCCESS;
2778
		$cert->certificatecount = count($certificates);
2779
		$cert->recipientcount = $recipientCount;
2780
		$cert->certificate = [];
2781
		foreach ($certificates as $certificate) {
2782
			$cert->certificate[] = base64_encode($certificate);
2783
		}
2784
2785
		return $cert;
2786
	}
2787
2788
	/**
2789
	 * Creates SyncResolveRecipient object for ResolveRecipientsResponse.
2790
	 *
2791
	 * @param int    $type
2792
	 * @param string $email
2793
	 * @param array  $recipientProperties
2794
	 * @param int    $recipientCount
2795
	 *
2796
	 * @return SyncResolveRecipient
2797
	 */
2798
	private function createResolveRecipient($type, $email, $recipientProperties, $recipientCount = 0) {
2799
		$recipient = new SyncResolveRecipient();
2800
		$recipient->type = $type;
2801
		$recipient->displayname = u2w($recipientProperties[PR_DISPLAY_NAME]);
0 ignored issues
show
Bug introduced by
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2802
		$recipient->emailaddress = $email;
2803
2804
		if ($type == SYNC_RESOLVERECIPIENTS_TYPE_GAL) {
2805
			$certificateProp = PR_EMS_AB_X509_CERT;
0 ignored issues
show
Bug introduced by
The constant PR_EMS_AB_X509_CERT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2806
		}
2807
		elseif ($type == SYNC_RESOLVERECIPIENTS_TYPE_CONTACT) {
2808
			$certificateProp = PR_USER_X509_CERTIFICATE;
0 ignored issues
show
Bug introduced by
The constant PR_USER_X509_CERTIFICATE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2809
		}
2810
		else {
2811
			$certificateProp = null;
2812
		}
2813
2814
		if (isset($recipientProperties[$certificateProp]) && is_array($recipientProperties[$certificateProp]) && !empty($recipientProperties[$certificateProp])) {
2815
			$certificates = $this->getCertificates($recipientProperties[$certificateProp], $recipientCount);
2816
		}
2817
		else {
2818
			$certificates = $this->getCertificates(false);
0 ignored issues
show
Bug introduced by
false of type false is incompatible with the type binary expected by parameter $certificates of Grommunio::getCertificates(). ( Ignorable by Annotation )

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

2818
			$certificates = $this->getCertificates(/** @scrutinizer ignore-type */ false);
Loading history...
2819
			SLog::Write(LOGLEVEL_INFO, sprintf("Grommunio->createResolveRecipient(): No certificate found for '%s' (requested email address: '%s')", $recipientProperties[PR_DISPLAY_NAME], $email));
2820
		}
2821
		$recipient->certificates = $certificates;
2822
2823
		if (isset($recipientProperties[PR_ENTRYID])) {
2824
			$recipient->id = $recipientProperties[PR_ENTRYID];
2825
		}
2826
2827
		return $recipient;
2828
	}
2829
2830
	/**
2831
	 * Gets the availability of a user for the given time window.
2832
	 *
2833
	 * @param string                       $to
2834
	 * @param SyncResolveRecipient         $resolveRecipient
2835
	 * @param SyncResolveRecipientsOptions $resolveRecipientsOptions
2836
	 *
2837
	 * @return SyncResolveRecipientsAvailability
2838
	 */
2839
	private function getAvailability($to, $resolveRecipient, $resolveRecipientsOptions) {
2840
		$availability = new SyncResolveRecipientsAvailability();
2841
		$availability->status = SYNC_RESOLVERECIPSSTATUS_AVAILABILITY_SUCCESS;
2842
2843
		if (!isset($resolveRecipient->id)) {
2844
			// TODO this shouldn't happen but try to get the recipient in such a case
2845
		}
2846
2847
		$start = strtotime($resolveRecipientsOptions->availability->starttime);
2848
		$end = strtotime($resolveRecipientsOptions->availability->endtime);
2849
		// Each digit in the MergedFreeBusy indicates the free/busy status for the user for every 30 minute interval.
2850
		$timeslots = intval(ceil(($end - $start) / self::HALFHOURSECONDS));
2851
2852
		if ($timeslots > self::MAXFREEBUSYSLOTS) {
2853
			throw new StatusException("Grommunio->getAvailability(): the requested free busy range is too large.", SYNC_RESOLVERECIPSSTATUS_PROTOCOLERROR);
2854
		}
2855
2856
		$mergedFreeBusy = str_pad(fbNoData, $timeslots, fbNoData);
2857
2858
		$retval = mapi_getuseravailability($this->session, $resolveRecipient->id, $start, $end);
0 ignored issues
show
Bug introduced by
The function mapi_getuseravailability was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2858
		$retval = /** @scrutinizer ignore-call */ mapi_getuseravailability($this->session, $resolveRecipient->id, $start, $end);
Loading history...
2859
		SLog::Write(LOGLEVEL_INFO, sprintf("Grommunio->getAvailability(): free busy '%s'", print_r($retval, 1)));
0 ignored issues
show
Bug introduced by
It seems like print_r($retval, 1) can also be of type true; however, parameter $values of sprintf() does only seem to accept double|integer|string, maybe add an additional type check? ( Ignorable by Annotation )

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

2859
		SLog::Write(LOGLEVEL_INFO, sprintf("Grommunio->getAvailability(): free busy '%s'", /** @scrutinizer ignore-type */ print_r($retval, 1)));
Loading history...
2860
2861
		if (!empty($retval)) {
2862
			$freebusy = json_decode($retval, true);
2863
			// freebusy is available, assume that the user is free
2864
			$mergedFreeBusy = str_pad(fbFree, $timeslots, fbFree);
2865
			foreach ($freebusy['events'] as $event) {
2866
				// calculate which timeslot of mergedFreeBusy should be replaced.
2867
				$startSlot = intval(floor(($event['StartTime'] - $start) / self::HALFHOURSECONDS));
2868
				$endSlot = intval(floor(($event['EndTime'] - $start) / self::HALFHOURSECONDS));
2869
				// if event started at a multiple of half an hour from requested freebusy time and
2870
				// its duration is also a multiple of half an hour
2871
				// then it's necessary to reduce endSlot by one
2872
				if ((($event['StartTime'] - $start) % self::HALFHOURSECONDS == 0) && (($event['EndTime'] - $event['StartTime']) % self::HALFHOURSECONDS == 0)) {
2873
					--$endSlot;
2874
				}
2875
				$fbType = Utils::GetFbStatusFromType($event['BusyType']);
2876
				for ($i = $startSlot; $i <= $endSlot && $i < $timeslots; ++$i) {
2877
					// only set the new slot's free busy status if it's higher than the current one
2878
					if ($fbType > $mergedFreeBusy[$i]) {
2879
						$mergedFreeBusy[$i] = $fbType;
2880
					}
2881
				}
2882
			}
2883
		}
2884
		$availability->mergedfreebusy = $mergedFreeBusy;
2885
2886
		return $availability;
2887
	}
2888
2889
	/**
2890
	 * Returns contacts matching given email address from a folder.
2891
	 *
2892
	 * @param MAPIStore $store
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...
2893
	 * @param binary    $folderEntryid
2894
	 * @param string    $email
2895
	 *
2896
	 * @return array|bool
2897
	 */
2898
	private function getContactsFromFolder($store, $folderEntryid, $email) {
2899
		$folder = mapi_msgstore_openentry($store, $folderEntryid);
2900
		$folderContent = mapi_folder_getcontentstable($folder);
2901
		mapi_table_restrict($folderContent, MAPIUtils::GetEmailAddressRestriction($store, $email));
2902
		// TODO max limit
2903
		if (mapi_table_getrowcount($folderContent) > 0) {
2904
			return mapi_table_queryallrows($folderContent, [PR_DISPLAY_NAME, PR_USER_X509_CERTIFICATE, PR_ENTRYID]);
0 ignored issues
show
Bug introduced by
The constant PR_USER_X509_CERTIFICATE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2905
		}
2906
2907
		return false;
2908
	}
2909
2910
	/**
2911
	 * Get MAPI addressbook object.
2912
	 *
2913
	 * @return MAPIAddressbook object to be used with mapi_ab_* or false on failure
0 ignored issues
show
Bug introduced by
The type MAPIAddressbook 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...
2914
	 */
2915
	private function getAddressbook() {
2916
		if (isset($this->addressbook) && $this->addressbook) {
2917
			return $this->addressbook;
2918
		}
2919
		$this->addressbook = mapi_openaddressbook($this->session);
2920
		$result = mapi_last_hresult();
2921
		if ($result && $this->addressbook === false) {
2922
			SLog::Write(LOGLEVEL_ERROR, sprintf("Grommunio->getAddressbook error opening addressbook 0x%X", $result));
2923
2924
			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 MAPIAddressbook.
Loading history...
2925
		}
2926
2927
		return $this->addressbook;
2928
	}
2929
2930
	/**
2931
	 * Checks if the user is not disabled for grommunio-sync.
2932
	 *
2933
	 * @throws FatalException if user is disabled for grommunio-sync
2934
	 *
2935
	 * @return bool
2936
	 */
2937
	private function isGSyncEnabled() {
2938
		$addressbook = $this->getAddressbook();
2939
		// this check needs to be performed on the store of the main (authenticated) user
2940
		$store = $this->storeCache[$this->mainUser];
2941
		$userEntryid = mapi_getprops($store, [PR_MAILBOX_OWNER_ENTRYID]);
0 ignored issues
show
Bug introduced by
The constant PR_MAILBOX_OWNER_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2942
		$mailuser = mapi_ab_openentry($addressbook, $userEntryid[PR_MAILBOX_OWNER_ENTRYID]);
2943
		$enabledFeatures = mapi_getprops($mailuser, [PR_EC_DISABLED_FEATURES]);
2944
		if (isset($enabledFeatures[PR_EC_DISABLED_FEATURES]) && is_array($enabledFeatures[PR_EC_DISABLED_FEATURES])) {
2945
			$mobileDisabled = in_array(self::MOBILE_ENABLED, $enabledFeatures[PR_EC_DISABLED_FEATURES]);
2946
			$deviceId = Request::GetDeviceID();
2947
			// Checks for deviceId present in zarafaDisabledFeatures LDAP array attribute. Check is performed case insensitive.
2948
			$deviceIdDisabled = (($deviceId !== null) && in_array($deviceId, array_map('strtolower', $enabledFeatures[PR_EC_DISABLED_FEATURES]))) ? true : false;
2949
			if ($mobileDisabled) {
2950
				throw new FatalException("User is disabled for grommunio-sync.");
2951
			}
2952
			if ($deviceIdDisabled) {
2953
				throw new FatalException(sprintf("User has deviceId %s disabled for usage with grommunio-sync.", $deviceId));
2954
			}
2955
		}
2956
2957
		return true;
2958
	}
2959
}
2960