Passed
Push — master ( 4ee882...b0ee79 )
by
unknown
03:16
created

Grommunio::openMessageStore()   C

Complexity

Conditions 17
Paths 49

Size

Total Lines 63
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 17
eloc 31
nc 49
nop 1
dl 0
loc 63
rs 5.2166
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
	 * Returns information about the user's store:
1583
	 * number of folders, store size, full name, email address.
1584
	 *
1585
	 * @return UserStoreInfo
1586
	 */
1587
	public function GetUserStoreInfo() {
1588
		$userStoreInfo = new UserStoreInfo();
1589
1590
		$rootfolder = mapi_msgstore_openentry($this->store);
1591
		$hierarchy = mapi_folder_gethierarchytable($rootfolder, CONVENIENT_DEPTH);
1592
		// Do not take hidden and system folders into account
1593
		// TODO make this restriction generic and use for hierarchy?
1594
		$restrict = [
1595
			RES_AND,
1596
			[
1597
				[
1598
					RES_PROPERTY,
1599
					[
1600
						RELOP => RELOP_NE,
1601
						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...
1602
						VALUE => true, ],
1603
				],
1604
				[
1605
					RES_PROPERTY,
1606
					[
1607
						RELOP => RELOP_EQ,
1608
						ULPROPTAG => PR_FOLDER_TYPE,
0 ignored issues
show
Bug introduced by
The constant PR_FOLDER_TYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1609
						VALUE => FOLDER_GENERIC, ],
1610
				],
1611
				[
1612
					RES_EXIST,
1613
					[ULPROPTAG => PR_CONTAINER_CLASS],
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...
1614
				],
1615
			], ];
1616
		mapi_table_restrict($hierarchy, $restrict);
1617
		$foldercount = mapi_table_getrowcount($hierarchy);
1618
1619
		$storeProps = mapi_getprops($this->store, [PR_MESSAGE_SIZE_EXTENDED]);
0 ignored issues
show
Bug introduced by
The constant PR_MESSAGE_SIZE_EXTENDED was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1620
		$storesize = isset($storeProps[PR_MESSAGE_SIZE_EXTENDED]) ? $storeProps[PR_MESSAGE_SIZE_EXTENDED] : 0;
1621
1622
		$userDetails = $this->GetUserDetails($this->impersonateUser ?: $this->mainUser);
0 ignored issues
show
Bug introduced by
It seems like $this->impersonateUser ?: $this->mainUser can also be of type true; however, parameter $username of Grommunio::GetUserDetails() 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

1622
		$userDetails = $this->GetUserDetails(/** @scrutinizer ignore-type */ $this->impersonateUser ?: $this->mainUser);
Loading history...
1623
		$userStoreInfo->SetData($foldercount, $storesize, $userDetails['fullname'], $userDetails['emailaddress']);
1624
		SLog::Write(LOGLEVEL_DEBUG, sprintf(
1625
			"Grommunio->GetUserStoreInfo(): user %s (%s) store size is %d bytes and contains %d folders",
1626
			Utils::PrintAsString($userDetails['fullname']),
1627
			Utils::PrintAsString($userDetails['emailaddress']),
1628
			$storesize,
1629
			$foldercount
1630
		));
1631
1632
		return $userStoreInfo;
1633
	}
1634
1635
	/*----------------------------------------------------------------------------------------------------------
1636
	 * Implementation of the IStateMachine interface
1637
	 */
1638
1639
	/**
1640
	 * Gets a hash value indicating the latest dataset of the named
1641
	 * state with a specified key and counter.
1642
	 * If the state is changed between two calls of this method
1643
	 * the returned hash should be different.
1644
	 *
1645
	 * @param string $devid   the device id
1646
	 * @param string $type    the state type
1647
	 * @param string $key     (opt)
1648
	 * @param string $counter (opt)
1649
	 *
1650
	 * @throws StateNotFoundException
1651
	 *
1652
	 * @return string
1653
	 */
1654
	public function GetStateHash($devid, $type, $key = false, $counter = false) {
1655
		try {
1656
			$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

1656
			$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

1656
			$stateMessage = $this->getStateMessage($devid, $type, $key, /** @scrutinizer ignore-type */ $counter);
Loading history...
1657
			$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...
1658
			if (isset($stateMessageProps[PR_LAST_MODIFICATION_TIME])) {
1659
				return $stateMessageProps[PR_LAST_MODIFICATION_TIME];
1660
			}
1661
		}
1662
		catch (StateNotFoundException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1663
		}
1664
1665
		return "0";
1666
	}
1667
1668
	/**
1669
	 * Gets a state for a specified key and counter.
1670
	 * This method should call IStateMachine->CleanStates()
1671
	 * to remove older states (same key, previous counters).
1672
	 *
1673
	 * @param string $devid       the device id
1674
	 * @param string $type        the state type
1675
	 * @param string $key         (opt)
1676
	 * @param string $counter     (opt)
1677
	 * @param string $cleanstates (opt)
1678
	 *
1679
	 * @throws StateNotFoundException, StateInvalidException, UnavailableException
1680
	 *
1681
	 * @return mixed
1682
	 */
1683
	public function GetState($devid, $type, $key = false, $counter = false, $cleanstates = true) {
1684
		if ($counter && $cleanstates) {
1685
			$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

1685
			$this->CleanStates($devid, $type, /** @scrutinizer ignore-type */ $key, $counter);
Loading history...
1686
			// also clean Failsave state for previous counter
1687
			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...
1688
				$this->CleanStates($devid, $type, IStateMachine::FAILSAVE, $counter);
1689
			}
1690
		}
1691
		$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

1691
		$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

1691
		$stateMessage = $this->getStateMessage($devid, $type, $key, /** @scrutinizer ignore-type */ $counter);
Loading history...
1692
		$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...
1693
1694
		if ($state && $state[0] === '{') {
1695
			$jsonDec = json_decode($state);
1696
			if (isset($jsonDec->gsSyncStateClass)) {
1697
				SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->GetState(): top class '%s'", $jsonDec->gsSyncStateClass));
1698
				$gsObj = new $jsonDec->gsSyncStateClass();
1699
				$gsObj->jsonDeserialize($jsonDec);
1700
				$gsObj->postUnserialize();
1701
			}
1702
		}
1703
1704
		return isset($gsObj) && is_object($gsObj) ? $gsObj : $state;
1705
	}
1706
1707
	/**
1708
	 * Writes ta state to for a key and counter.
1709
	 *
1710
	 * @param mixed  $state
1711
	 * @param string $devid   the device id
1712
	 * @param string $type    the state type
1713
	 * @param string $key     (opt)
1714
	 * @param int    $counter (opt)
1715
	 *
1716
	 * @throws StateInvalidException, UnavailableException
1717
	 *
1718
	 * @return bool
1719
	 */
1720
	public function SetState($state, $devid, $type, $key = false, $counter = false) {
1721
		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

1721
		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

1721
		return $this->setStateMessage($state, $devid, $type, /** @scrutinizer ignore-type */ $key, $counter);
Loading history...
1722
	}
1723
1724
	/**
1725
	 * Cleans up all older states.
1726
	 * If called with a $counter, all states previous state counter can be removed.
1727
	 * If additionally the $thisCounterOnly flag is true, only that specific counter will be removed.
1728
	 * If called without $counter, all keys (independently from the counter) can be removed.
1729
	 *
1730
	 * @param string $devid           the device id
1731
	 * @param string $type            the state type
1732
	 * @param string $key
1733
	 * @param string $counter         (opt)
1734
	 * @param string $thisCounterOnly (opt) if provided, the exact counter only will be removed
1735
	 *
1736
	 * @throws StateInvalidException
1737
	 *
1738
	 * @return
1739
	 */
1740
	public function CleanStates($devid, $type, $key, $counter = false, $thisCounterOnly = false) {
1741
		if (!$this->stateFolder) {
1742
			$this->getStateFolder($devid);
1743
			if (!$this->stateFolder) {
1744
				throw new StateNotFoundException(sprintf(
1745
					"Grommunio->getStateMessage(): Could not locate the state folder for device '%s'",
1746
					$devid
1747
				));
1748
			}
1749
		}
1750
		$messageName = rtrim((($key !== false) ? $key . "-" : "") . (($type !== "") ? $type : ""), "-");
0 ignored issues
show
introduced by
The condition $key !== false is always true.
Loading history...
1751
		$restriction = $this->getStateMessageRestriction($messageName, $counter, $thisCounterOnly);
0 ignored issues
show
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

1751
		$restriction = $this->getStateMessageRestriction($messageName, /** @scrutinizer ignore-type */ $counter, $thisCounterOnly);
Loading history...
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

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

1875
					$stateFolder = /** @scrutinizer ignore-call */ mapi_folder_createfolder($rootfolder, STORE_STATE_FOLDER, "");
Loading history...
1876
					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...
1877
				}
1878
1879
				// TODO: handle this
1880
1881
				if (isset($stateFolder) && $stateFolder) {
1882
					$devStateFolder = mapi_folder_createfolder($stateFolder, $devid, "");
1883
					$devStateFolderProps = mapi_getprops($devStateFolder);
1884
					$this->stateFolder = mapi_msgstore_openentry($this->store, $devStateFolderProps[PR_ENTRYID]);
1885
					mapi_setprops($this->stateFolder, [PR_ATTR_HIDDEN => true]);
1886
					// we don't cache the entryid in redis, because this will happen on the next request anyway
1887
				}
1888
1889
				// TODO: unable to create state folder - throw exception
1890
			}
1891
1892
			// This case is rather unlikely that there would be several
1893
				// hidden folders having PR_DISPLAY_NAME the same as device id.
1894
1895
				// TODO: get the hierarchy table again, get entry id of STORE_STATE_FOLDER
1896
				// and compare it to the parent id of those folders.
1897
		}
1898
1899
		return $this->stateFolder;
1900
	}
1901
1902
	/**
1903
	 * Returns the associated MAPIMessage which contains the state information.
1904
	 *
1905
	 * @param string $devid   the device id
1906
	 * @param string $type    the state type
1907
	 * @param string $key     (opt)
1908
	 * @param string $counter state counter
1909
	 *
1910
	 * @throws StateNotFoundException
1911
	 *
1912
	 * @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...
1913
	 */
1914
	private function getStateMessage($devid, $type, $key, $counter) {
1915
		if (!$this->stateFolder) {
1916
			$this->getStateFolder(Request::GetDeviceID());
1917
			if (!$this->stateFolder) {
1918
				throw new StateNotFoundException(sprintf(
1919
					"Grommunio->getStateMessage(): Could not locate the state folder for device '%s'",
1920
					$devid
1921
				));
1922
			}
1923
		}
1924
		$messageName = rtrim((($key !== false) ? $key . "-" : "") . (($type !== "") ? $type : ""), "-");
0 ignored issues
show
introduced by
The condition $key !== false is always true.
Loading history...
1925
		$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

1925
		$restriction = $this->getStateMessageRestriction($messageName, $counter, /** @scrutinizer ignore-type */ true);
Loading history...
1926
		$stateFolderContents = mapi_folder_getcontentstable($this->stateFolder, MAPI_ASSOCIATED);
1927
		if ($stateFolderContents) {
1928
			mapi_table_restrict($stateFolderContents, $restriction);
1929
			$rowCnt = mapi_table_getrowcount($stateFolderContents);
1930
			if ($rowCnt == 1) {
1931
				$stateFolderRows = mapi_table_queryrows($stateFolderContents, [PR_ENTRYID], 0, 1);
1932
1933
				return mapi_msgstore_openentry($this->store, $stateFolderRows[0][PR_ENTRYID]);
1934
			}
1935
		}
1936
1937
		throw new StateNotFoundException(sprintf(
1938
			"Grommunio->getStateMessage(): Could not locate the state message '%s' (counter: %s)",
1939
			$messageName,
1940
			Utils::PrintAsString($counter)
1941
		));
1942
	}
1943
1944
	/**
1945
	 * Writes ta state to for a key and counter.
1946
	 *
1947
	 * @param mixed  $state
1948
	 * @param string $devid   the device id
1949
	 * @param string $type    the state type
1950
	 * @param string $key     (opt)
1951
	 * @param int    $counter (opt)
1952
	 *
1953
	 * @throws StateInvalidException, UnavailableException
1954
	 *
1955
	 * @return bool
1956
	 */
1957
	private function setStateMessage($state, $devid, $type, $key = false, $counter = false) {
1958
		if (!$this->stateFolder) {
1959
			throw new StateNotFoundException(sprintf("Grommunio->setStateMessage(): Could not locate the state folder for device '%s'", $devid));
1960
		}
1961
1962
		try {
1963
			$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

1963
			$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

1963
			$stateMessage = $this->getStateMessage($devid, $type, $key, /** @scrutinizer ignore-type */ $counter);
Loading history...
1964
		}
1965
		catch (StateNotFoundException $e) {
1966
			// if message is not available, try to create a new one
1967
			$stateMessage = mapi_folder_createmessage($this->stateFolder, MAPI_ASSOCIATED);
1968
			SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->setStateMessage(): mapi_folder_createmessage 0x%08X", mapi_last_hresult()));
1969
1970
			$messageName = rtrim((($key !== false) ? $key . "-" : "") . (($type !== "") ? $type : ""), "-");
1971
			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

1971
			SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->setStateMessage(): creating new state message '%s' (counter: %s)", $messageName, Utils::PrintAsString(/** @scrutinizer ignore-type */ $counter)));
Loading history...
1972
			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...
1973
		}
1974
		if (isset($stateMessage)) {
1975
			$jsonEncodedState = is_object($state) || is_array($state) ? json_encode($state, JSON_INVALID_UTF8_IGNORE | JSON_UNESCAPED_UNICODE) : $state;
1976
1977
			$encodedState = base64_encode($jsonEncodedState);
1978
			$encodedStateLength = strlen($encodedState);
1979
			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...
1980
			$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...
1981
			mapi_stream_setsize($stream, $encodedStateLength);
1982
			mapi_stream_write($stream, $encodedState);
1983
			mapi_stream_commit($stream);
1984
			mapi_savechanges($stateMessage);
1985
1986
			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...
1987
		}
1988
1989
		return false;
1990
	}
1991
1992
	/**
1993
	 * Returns the restriction for the state folder name.
1994
	 *
1995
	 * @param string $folderName the state folder name
1996
	 *
1997
	 * @return array
1998
	 */
1999
	private function getStateFolderRestriction($folderName) {
2000
		return [RES_AND, [
2001
			[RES_PROPERTY,
2002
				[RELOP => RELOP_EQ,
2003
					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...
2004
					VALUE => $folderName,
2005
				],
2006
			],
2007
			[RES_PROPERTY,
2008
				[RELOP => RELOP_EQ,
2009
					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...
2010
					VALUE => true,
2011
				],
2012
			],
2013
		]];
2014
	}
2015
2016
	/**
2017
	 * Returns the restriction for the associated message in the state folder.
2018
	 *
2019
	 * @param string $messageName     the message name
2020
	 * @param string $counter         counter
2021
	 * @param string $thisCounterOnly (opt) if provided, restrict to the exact counter
2022
	 *
2023
	 * @return array
2024
	 */
2025
	private function getStateMessageRestriction($messageName, $counter, $thisCounterOnly = false) {
2026
		return [RES_AND, [
2027
			[RES_PROPERTY,
2028
				[RELOP => RELOP_EQ,
2029
					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...
2030
					VALUE => $messageName,
2031
				],
2032
			],
2033
			[RES_PROPERTY,
2034
				[RELOP => RELOP_EQ,
2035
					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...
2036
					VALUE => 'IPM.Note.GrommunioState',
2037
				],
2038
			],
2039
			[RES_PROPERTY,
2040
				[RELOP => $thisCounterOnly ? RELOP_EQ : RELOP_LT,
2041
					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...
2042
					VALUE => $counter,
2043
				],
2044
			],
2045
		]];
2046
	}
2047
2048
	/*----------------------------------------------------------------------------------------------------------
2049
	 * Private methods
2050
	 */
2051
2052
	/**
2053
	 * Returns a hash representing changes in the hierarchy of the main user.
2054
	 * It changes if a folder is added, renamed or deleted.
2055
	 *
2056
	 * @return string
2057
	 */
2058
	private function getHierarchyHash() {
2059
		$rootfolder = mapi_msgstore_openentry($this->defaultstore);
2060
		$hierarchy = mapi_folder_gethierarchytable($rootfolder, CONVENIENT_DEPTH);
2061
2062
		return md5(serialize(mapi_table_queryallrows($hierarchy, [PR_DISPLAY_NAME, PR_PARENT_ENTRYID])));
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...
Bug introduced by
The constant PR_PARENT_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2063
	}
2064
2065
	/**
2066
	 * Advises a store to the changes sink.
2067
	 *
2068
	 * @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...
2069
	 *
2070
	 * @return bool
2071
	 */
2072
	private function adviseStoreToSink($store) {
2073
		// check if we already advised the store
2074
		if (!in_array($store, $this->changesSinkStores)) {
2075
			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

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

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

2447
		/** @scrutinizer ignore-call */ 
2448
  $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...
2448
		if ($searchFolderRoot === false) {
2449
			// error in finding search root folder
2450
			// or store doesn't support search folders
2451
			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...
2452
		}
2453
2454
		$searchFolder = $this->createSearchFolder($searchFolderRoot);
2455
2456
		if ($searchFolder !== false && mapi_last_hresult() == NOERROR) {
2457
			return $searchFolder;
2458
		}
2459
2460
		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...
2461
	}
2462
2463
	/**
2464
	 * Function will open FINDER_ROOT folder in root container
2465
	 * public folder's don't have FINDER_ROOT folder.
2466
	 *
2467
	 * @see getSearchFoldersRoot($store) function in the webaccess
2468
	 *
2469
	 * @return mapiFolderObject root folder for search folders
2470
	 */
2471
	private function getSearchFoldersRoot() {
2472
		// check if we can create search folders
2473
		$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...
2474
		if (($storeProps[PR_STORE_SUPPORT_MASK] & STORE_SEARCH_OK) != STORE_SEARCH_OK) {
2475
			SLog::Write(LOGLEVEL_WARN, "Grommunio->getSearchFoldersRoot(): Store doesn't support search folders. Public store doesn't have FINDER_ROOT folder");
2476
2477
			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...
2478
		}
2479
2480
		// open search folders root
2481
		$searchRootFolder = mapi_msgstore_openentry($this->store, $storeProps[PR_FINDER_ENTRYID]);
2482
		if (mapi_last_hresult() != NOERROR) {
2483
			SLog::Write(LOGLEVEL_WARN, sprintf("Grommunio->getSearchFoldersRoot(): Unable to open search folder (0x%X)", mapi_last_hresult()));
2484
2485
			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...
2486
		}
2487
2488
		return $searchRootFolder;
2489
	}
2490
2491
	/**
2492
	 * Creates a search folder if it not exists or opens an existing one
2493
	 * and returns it.
2494
	 *
2495
	 * @param mapiFolderObject $searchFolderRoot
2496
	 *
2497
	 * @return mapiFolderObject
2498
	 */
2499
	private function createSearchFolder($searchFolderRoot) {
2500
		$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

2500
		$folderName = "grommunio-sync Search Folder " . /** @scrutinizer ignore-type */ @getmypid();
Loading history...
2501
		$searchFolders = mapi_folder_gethierarchytable($searchFolderRoot);
2502
		$restriction = [
2503
			RES_CONTENT,
2504
			[
2505
				FUZZYLEVEL => FL_PREFIX,
2506
				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...
2507
				VALUE => [PR_DISPLAY_NAME => $folderName],
2508
			],
2509
		];
2510
		// restrict the hierarchy to the grommunio-sync search folder only
2511
		mapi_table_restrict($searchFolders, $restriction);
2512
		if (mapi_table_getrowcount($searchFolders)) {
2513
			$searchFolder = mapi_table_queryrows($searchFolders, [PR_ENTRYID], 0, 1);
2514
2515
			return mapi_msgstore_openentry($this->store, $searchFolder[0][PR_ENTRYID]);
2516
		}
2517
2518
		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

2518
		return /** @scrutinizer ignore-call */ mapi_folder_createfolder($searchFolderRoot, $folderName, null, 0, FOLDER_SEARCH);
Loading history...
2519
	}
2520
2521
	/**
2522
	 * Creates a search restriction.
2523
	 *
2524
	 * @param ContentParameter $cpo
2525
	 *
2526
	 * @return array
2527
	 */
2528
	private function getSearchRestriction($cpo) {
2529
		$searchText = $cpo->GetSearchFreeText();
2530
2531
		$searchGreater = strtotime($cpo->GetSearchValueGreater());
2532
		$searchLess = strtotime($cpo->GetSearchValueLess());
2533
2534
		if (version_compare(phpversion(), '5.3.4') < 0) {
2535
			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()));
2536
		}
2537
		// split the search on whitespache and look for every word
2538
		$searchText = preg_split("/\\W+/u", $searchText);
2539
		$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_SUBJECT 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_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_DISPLAY_CC was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
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...
2540
		$resAnd = [];
2541
		foreach ($searchText as $term) {
2542
			$resOr = [];
2543
2544
			foreach ($searchProps as $property) {
2545
				array_push(
2546
					$resOr,
2547
					[RES_CONTENT,
2548
						[
2549
							FUZZYLEVEL => FL_SUBSTRING | FL_IGNORECASE,
2550
							ULPROPTAG => $property,
2551
							VALUE => u2w($term),
2552
						],
2553
					]
2554
				);
2555
			}
2556
			array_push($resAnd, [RES_OR, $resOr]);
2557
		}
2558
2559
		// add time range restrictions
2560
		if ($searchGreater) {
2561
			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...
2562
		}
2563
		if ($searchLess) {
2564
			array_push($resAnd, [RES_PROPERTY, [RELOP => RELOP_LE, ULPROPTAG => PR_MESSAGE_DELIVERY_TIME, VALUE => [PR_MESSAGE_DELIVERY_TIME => $searchLess]]]);
2565
		}
2566
2567
		return [RES_AND, $resAnd];
2568
	}
2569
2570
	/**
2571
	 * Resolve recipient based on his email address.
2572
	 *
2573
	 * @param string $to
2574
	 * @param int    $maxAmbiguousRecipients
2575
	 * @param bool   $expandDistlist
2576
	 *
2577
	 * @return bool|SyncResolveRecipient
2578
	 */
2579
	private function resolveRecipient($to, $maxAmbiguousRecipients, $expandDistlist = true) {
2580
		$recipient = $this->resolveRecipientGAL($to, $maxAmbiguousRecipients, $expandDistlist);
2581
2582
		if ($recipient !== false) {
0 ignored issues
show
introduced by
The condition $recipient !== false is always true.
Loading history...
2583
			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...
2584
		}
2585
2586
		$recipient = $this->resolveRecipientContact($to, $maxAmbiguousRecipients);
2587
2588
		if ($recipient !== false) {
2589
			return $recipient;
2590
		}
2591
2592
		return false;
2593
	}
2594
2595
	/**
2596
	 * Resolves recipient from the GAL and gets his certificates.
2597
	 *
2598
	 * @param string $to
2599
	 * @param int    $maxAmbiguousRecipients
2600
	 * @param bool   $expandDistlist
2601
	 *
2602
	 * @return array|bool
2603
	 */
2604
	private function resolveRecipientGAL($to, $maxAmbiguousRecipients, $expandDistlist = true) {
2605
		SLog::Write(LOGLEVEL_WBXML, sprintf("Grommunio->resolveRecipientGAL(): Resolving recipient '%s' in GAL", $to));
2606
		$addrbook = $this->getAddressbook();
2607
		// FIXME: create a function to get the adressbook contentstable
2608
		$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

2608
		$ab_entryid = /** @scrutinizer ignore-call */ mapi_ab_getdefaultdir($addrbook);
Loading history...
2609
		if ($ab_entryid) {
2610
			$ab_dir = mapi_ab_openentry($addrbook, $ab_entryid);
2611
		}
2612
		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...
2613
			$table = mapi_folder_getcontentstable($ab_dir);
2614
		}
2615
2616
		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...
2617
			SLog::Write(LOGLEVEL_WARN, sprintf("Grommunio->resolveRecipientGAL(): Unable to open addressbook:0x%X", mapi_last_hresult()));
2618
2619
			return false;
2620
		}
2621
2622
		$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

2622
		$restriction = MAPIUtils::GetSearchRestriction(/** @scrutinizer ignore-type */ u2w($to));
Loading history...
2623
		mapi_table_restrict($table, $restriction);
2624
2625
		$querycnt = mapi_table_getrowcount($table);
2626
		if ($querycnt > 0) {
2627
			$recipientGal = [];
2628
			$rowsToQuery = $maxAmbiguousRecipients;
2629
			// some devices request 0 ambiguous recipients
2630
			if ($querycnt == 1 && $maxAmbiguousRecipients == 0) {
2631
				$rowsToQuery = 1;
2632
			}
2633
			elseif ($querycnt > 1 && $maxAmbiguousRecipients == 0) {
2634
				SLog::Write(LOGLEVEL_INFO, sprintf("Grommunio->resolveRecipientGAL(): GAL search found %d recipients but the device hasn't requested ambiguous recipients", $querycnt));
2635
2636
				return $recipientGal;
2637
			}
2638
			elseif ($querycnt > 1 && $maxAmbiguousRecipients == 1) {
2639
				$rowsToQuery = $querycnt;
2640
			}
2641
			// get the certificate every time because caching the certificate is less expensive than opening addressbook entry again
2642
			$abentries = mapi_table_queryrows($table, [PR_ENTRYID, PR_DISPLAY_NAME, PR_EMS_AB_TAGGED_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_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_TAGGED_X509_CERT 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...
2643
			for ($i = 0, $nrEntries = count($abentries); $i < $nrEntries; ++$i) {
2644
				if (strcasecmp($abentries[$i][PR_SMTP_ADDRESS], $to) !== 0 && $maxAmbiguousRecipients == 1) {
2645
					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]));
2646
2647
					continue;
2648
				}
2649
				if ($abentries[$i][PR_OBJECT_TYPE] == MAPI_DISTLIST) {
2650
					// check whether to expand dist list
2651
					if ($expandDistlist) {
2652
						SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->resolveRecipientGAL(): '%s' is a dist list. Expand it to members.", $to));
2653
						$distList = mapi_ab_openentry($addrbook, $abentries[$i][PR_ENTRYID]);
2654
						$distListContent = mapi_folder_getcontentstable($distList);
2655
						$distListMembers = mapi_table_queryallrows($distListContent, [PR_ENTRYID, PR_DISPLAY_NAME, PR_EMS_AB_TAGGED_X509_CERT]);
2656
						for ($j = 0, $nrDistListMembers = mapi_table_getrowcount($distListContent); $j < $nrDistListMembers; ++$j) {
2657
							SLog::Write(LOGLEVEL_WBXML, sprintf("Grommunio->resolveRecipientGAL(): distlist's '%s' member: '%s'", $to, $distListMembers[$j][PR_DISPLAY_NAME]));
2658
							$recipientGal[] = $this->createResolveRecipient(SYNC_RESOLVERECIPIENTS_TYPE_GAL, $to, $distListMembers[$j], $nrDistListMembers);
2659
						}
2660
					}
2661
					else {
2662
						SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->resolveRecipientGAL(): '%s' is a dist list, but return it as is.", $to));
2663
						$recipientGal[] = $this->createResolveRecipient(SYNC_RESOLVERECIPIENTS_TYPE_GAL, $abentries[$i][PR_SMTP_ADDRESS], $abentries[$i]);
2664
					}
2665
				}
2666
				elseif ($abentries[$i][PR_OBJECT_TYPE] == MAPI_MAILUSER) {
2667
					$recipientGal[] = $this->createResolveRecipient(SYNC_RESOLVERECIPIENTS_TYPE_GAL, $abentries[$i][PR_SMTP_ADDRESS], $abentries[$i]);
2668
				}
2669
			}
2670
2671
			SLog::Write(LOGLEVEL_WBXML, "Grommunio->resolveRecipientGAL(): Found a recipient in GAL");
2672
2673
			return $recipientGal;
2674
		}
2675
2676
		SLog::Write(LOGLEVEL_WARN, sprintf("Grommunio->resolveRecipientGAL(): No recipient found for: '%s' in GAL", $to));
2677
2678
		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...
2679
2680
		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...
2681
	}
2682
2683
	/**
2684
	 * Resolves recipient from the contact list and gets his certificates.
2685
	 *
2686
	 * @param string $to
2687
	 * @param int    $maxAmbiguousRecipients
2688
	 *
2689
	 * @return array|bool
2690
	 */
2691
	private function resolveRecipientContact($to, $maxAmbiguousRecipients) {
2692
		SLog::Write(LOGLEVEL_WBXML, sprintf("Grommunio->resolveRecipientContact(): Resolving recipient '%s' in user's contacts", $to));
2693
		// go through all contact folders of the user and
2694
		// check if there's a contact with the given email address
2695
		$root = mapi_msgstore_openentry($this->defaultstore);
2696
		if (!$root) {
2697
			SLog::Write(LOGLEVEL_ERROR, sprintf("Grommunio->resolveRecipientContact(): Unable to open default store: 0x%X", mapi_last_hresult()));
2698
		}
2699
		$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...
2700
		$contacts = $this->getContactsFromFolder($this->defaultstore, $rootprops[PR_IPM_CONTACT_ENTRYID], $to);
2701
		$recipients = [];
2702
2703
		if ($contacts !== false) {
2704
			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

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

2825
			$certificates = $this->getCertificates(/** @scrutinizer ignore-type */ false);
Loading history...
2826
			SLog::Write(LOGLEVEL_INFO, sprintf("Grommunio->createResolveRecipient(): No certificate found for '%s' (requested email address: '%s')", $recipientProperties[PR_DISPLAY_NAME], $email));
2827
		}
2828
		$recipient->certificates = $certificates;
2829
2830
		if (isset($recipientProperties[PR_ENTRYID])) {
2831
			$recipient->id = $recipientProperties[PR_ENTRYID];
2832
		}
2833
2834
		return $recipient;
2835
	}
2836
2837
	/**
2838
	 * Gets the availability of a user for the given time window.
2839
	 *
2840
	 * @param string                       $to
2841
	 * @param SyncResolveRecipient         $resolveRecipient
2842
	 * @param SyncResolveRecipientsOptions $resolveRecipientsOptions
2843
	 *
2844
	 * @return SyncResolveRecipientsAvailability
2845
	 */
2846
	private function getAvailability($to, $resolveRecipient, $resolveRecipientsOptions) {
2847
		$availability = new SyncResolveRecipientsAvailability();
2848
		$availability->status = SYNC_RESOLVERECIPSSTATUS_AVAILABILITY_SUCCESS;
2849
2850
		if (!isset($resolveRecipient->id)) {
2851
			// TODO this shouldn't happen but try to get the recipient in such a case
2852
		}
2853
2854
		$start = strtotime($resolveRecipientsOptions->availability->starttime);
2855
		$end = strtotime($resolveRecipientsOptions->availability->endtime);
2856
		// Each digit in the MergedFreeBusy indicates the free/busy status for the user for every 30 minute interval.
2857
		$timeslots = intval(ceil(($end - $start) / self::HALFHOURSECONDS));
2858
2859
		if ($timeslots > self::MAXFREEBUSYSLOTS) {
2860
			throw new StatusException("Grommunio->getAvailability(): the requested free busy range is too large.", SYNC_RESOLVERECIPSSTATUS_PROTOCOLERROR);
2861
		}
2862
2863
		$mergedFreeBusy = str_pad(fbNoData, $timeslots, fbNoData);
2864
2865
		$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

2865
		$retval = /** @scrutinizer ignore-call */ mapi_getuseravailability($this->session, $resolveRecipient->id, $start, $end);
Loading history...
2866
		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

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