Passed
Push — master ( a78558...6fe2c8 )
by
unknown
03:13
created

Grommunio::openMessageStore()   D

Complexity

Conditions 19
Paths 51

Size

Total Lines 69
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 19
eloc 35
c 1
b 0
f 0
nc 51
nop 1
dl 0
loc 69
rs 4.5166

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
				$gsObj = new $jsonDec->gsSyncStateClass();
1698
				$gsObj->jsonDeserialize($jsonDec);
1699
				$gsObj->postUnserialize();
1700
			}
1701
		}
1702
1703
		return isset($gsObj) && is_object($gsObj) ? $gsObj : $state;
1704
	}
1705
1706
	/**
1707
	 * Writes ta state to for a key and counter.
1708
	 *
1709
	 * @param mixed  $state
1710
	 * @param string $devid   the device id
1711
	 * @param string $type    the state type
1712
	 * @param string $key     (opt)
1713
	 * @param int    $counter (opt)
1714
	 *
1715
	 * @throws StateInvalidException, UnavailableException
1716
	 *
1717
	 * @return bool
1718
	 */
1719
	public function SetState($state, $devid, $type, $key = false, $counter = false) {
1720
		return $this->setStateMessage($state, $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::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

1720
		return $this->setStateMessage($state, $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::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

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

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

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

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

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

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

1924
		$restriction = $this->getStateMessageRestriction($messageName, $counter, /** @scrutinizer ignore-type */ true);
Loading history...
1925
		$stateFolderContents = mapi_folder_getcontentstable($this->stateFolder, MAPI_ASSOCIATED);
1926
		if ($stateFolderContents) {
1927
			mapi_table_restrict($stateFolderContents, $restriction);
1928
			$rowCnt = mapi_table_getrowcount($stateFolderContents);
1929
			if ($rowCnt == 1) {
1930
				$stateFolderRows = mapi_table_queryrows($stateFolderContents, [PR_ENTRYID], 0, 1);
1931
1932
				return mapi_msgstore_openentry($this->store, $stateFolderRows[0][PR_ENTRYID]);
1933
			}
1934
1935
			if ($rowCnt > 1) {
1936
				SLog::Write(LOGLEVEL_WARN, sprintf("Grommunio->getStateMessage(): Cleaning up duplicated state messages '%s' (%d)", $messageName, $rowCnt));
1937
				$this->CleanStates($devid, $type, $key, $counter, true);
0 ignored issues
show
Bug introduced by
true of type true is incompatible with the type string expected by parameter $thisCounterOnly of Grommunio::CleanStates(). ( Ignorable by Annotation )

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

1937
				$this->CleanStates($devid, $type, $key, $counter, /** @scrutinizer ignore-type */ true);
Loading history...
1938
			}
1939
		}
1940
1941
		throw new StateNotFoundException(sprintf(
1942
			"Grommunio->getStateMessage(): Could not locate the state message '%s' (counter: %s)",
1943
			$messageName,
1944
			Utils::PrintAsString($counter)
1945
		));
1946
	}
1947
1948
	/**
1949
	 * Writes ta state to for a key and counter.
1950
	 *
1951
	 * @param mixed  $state
1952
	 * @param string $devid   the device id
1953
	 * @param string $type    the state type
1954
	 * @param string $key     (opt)
1955
	 * @param int    $counter (opt)
1956
	 *
1957
	 * @throws StateInvalidException, UnavailableException
1958
	 *
1959
	 * @return bool
1960
	 */
1961
	private function setStateMessage($state, $devid, $type, $key = false, $counter = false) {
1962
		if (!$this->stateFolder) {
1963
			throw new StateNotFoundException(sprintf("Grommunio->setStateMessage(): Could not locate the state folder for device '%s'", $devid));
1964
		}
1965
1966
		try {
1967
			$stateMessage = $this->getStateMessage($devid, $type, $key, $counter);
0 ignored issues
show
Bug introduced by
It seems like $counter can also be of type false; however, parameter $counter of Grommunio::getStateMessage() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

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

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

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

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

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

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

2457
		/** @scrutinizer ignore-call */ 
2458
  $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...
2458
		if ($searchFolderRoot === false) {
2459
			// error in finding search root folder
2460
			// or store doesn't support search folders
2461
			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...
2462
		}
2463
2464
		$searchFolder = $this->createSearchFolder($searchFolderRoot);
2465
2466
		if ($searchFolder !== false && mapi_last_hresult() == NOERROR) {
2467
			return $searchFolder;
2468
		}
2469
2470
		return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type mapiFolderObject.
Loading history...
2471
	}
2472
2473
	/**
2474
	 * Function will open FINDER_ROOT folder in root container
2475
	 * public folder's don't have FINDER_ROOT folder.
2476
	 *
2477
	 * @see getSearchFoldersRoot($store) function in the webaccess
2478
	 *
2479
	 * @return mapiFolderObject root folder for search folders
2480
	 */
2481
	private function getSearchFoldersRoot() {
2482
		// check if we can create search folders
2483
		$storeProps = mapi_getprops($this->store, [PR_STORE_SUPPORT_MASK, PR_FINDER_ENTRYID]);
0 ignored issues
show
Bug introduced by
The constant PR_FINDER_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_STORE_SUPPORT_MASK was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2484
		if (($storeProps[PR_STORE_SUPPORT_MASK] & STORE_SEARCH_OK) != STORE_SEARCH_OK) {
2485
			SLog::Write(LOGLEVEL_WARN, "Grommunio->getSearchFoldersRoot(): Store doesn't support search folders. Public store doesn't have FINDER_ROOT folder");
2486
2487
			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...
2488
		}
2489
2490
		// open search folders root
2491
		$searchRootFolder = mapi_msgstore_openentry($this->store, $storeProps[PR_FINDER_ENTRYID]);
2492
		if (mapi_last_hresult() != NOERROR) {
2493
			SLog::Write(LOGLEVEL_WARN, sprintf("Grommunio->getSearchFoldersRoot(): Unable to open search folder (0x%X)", mapi_last_hresult()));
2494
2495
			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...
2496
		}
2497
2498
		return $searchRootFolder;
2499
	}
2500
2501
	/**
2502
	 * Creates a search folder if it not exists or opens an existing one
2503
	 * and returns it.
2504
	 *
2505
	 * @param mapiFolderObject $searchFolderRoot
2506
	 *
2507
	 * @return mapiFolderObject
2508
	 */
2509
	private function createSearchFolder($searchFolderRoot) {
2510
		$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

2510
		$folderName = "grommunio-sync Search Folder " . /** @scrutinizer ignore-type */ @getmypid();
Loading history...
2511
		$searchFolders = mapi_folder_gethierarchytable($searchFolderRoot);
2512
		$restriction = [
2513
			RES_CONTENT,
2514
			[
2515
				FUZZYLEVEL => FL_PREFIX,
2516
				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...
2517
				VALUE => [PR_DISPLAY_NAME => $folderName],
2518
			],
2519
		];
2520
		// restrict the hierarchy to the grommunio-sync search folder only
2521
		mapi_table_restrict($searchFolders, $restriction);
2522
		if (mapi_table_getrowcount($searchFolders)) {
2523
			$searchFolder = mapi_table_queryrows($searchFolders, [PR_ENTRYID], 0, 1);
2524
2525
			return mapi_msgstore_openentry($this->store, $searchFolder[0][PR_ENTRYID]);
2526
		}
2527
2528
		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

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

2618
		$ab_entryid = /** @scrutinizer ignore-call */ mapi_ab_getdefaultdir($addrbook);
Loading history...
2619
		if ($ab_entryid) {
2620
			$ab_dir = mapi_ab_openentry($addrbook, $ab_entryid);
2621
		}
2622
		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...
2623
			$table = mapi_folder_getcontentstable($ab_dir);
2624
		}
2625
2626
		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...
2627
			SLog::Write(LOGLEVEL_WARN, sprintf("Grommunio->resolveRecipientGAL(): Unable to open addressbook:0x%X", mapi_last_hresult()));
2628
2629
			return false;
2630
		}
2631
2632
		$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

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

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

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

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

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