Grommunio::GetGALSearchResults()   F
last analyzed

Complexity

Conditions 22
Paths > 20000

Size

Total Lines 106
Code Lines 55

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 22
eloc 55
c 1
b 0
f 0
nc 65604
nop 3
dl 0
loc 106
rs 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

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

119
			$this->impersonateUser = strtolower(/** @scrutinizer ignore-type */ Request::GetImpersonatedUser());
Loading history...
120
		}
121
122
		// check if we are impersonating someone
123
		// $defaultUser will be used for $this->defaultStore
124
		if ($this->impersonateUser !== false) {
125
			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

125
			SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->Logon(): Impersonation active - authenticating: '%s' - impersonating '%s'", $this->mainUser, /** @scrutinizer ignore-type */ $this->impersonateUser));
Loading history...
126
			$defaultUser = $this->impersonateUser;
127
		}
128
		else {
129
			$defaultUser = $this->mainUser;
130
		}
131
132
		$deviceId = Request::GetDeviceID();
133
134
		try {
135
			$this->session = @mapi_logon_zarafa($this->mainUser, $pass, MAPI_SERVER, null, null, 0);
136
137
			if (mapi_last_hresult()) {
138
				SLog::Write(LOGLEVEL_ERROR, sprintf("Grommunio->Logon(): login failed with error code: 0x%X", mapi_last_hresult()));
139
				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...
140
					throw new ServiceUnavailableException("Error connecting to gromox-zcore (login)");
141
				}
142
			}
143
		}
144
		catch (MAPIException $ex) {
0 ignored issues
show
Bug introduced by
The type MAPIException 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...
145
			throw new AuthenticationRequiredException($ex->getDisplayMessage());
146
		}
147
148
		if (!$this->session) {
149
			SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->Logon(): logon failed for user '%s'", $this->mainUser));
150
			$this->defaultstore = false;
151
152
			return false;
153
		}
154
155
		// Get/open default store
156
		$this->defaultstore = $this->openMessageStore($this->mainUser);
157
158
		// To impersonate, we overwrite the defaultstore. We still need to open it before we can do that.
159
		if ($this->impersonateUser) {
160
			SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->Logon(): Impersonating user '%s'", $defaultUser));
161
			$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

161
			$this->defaultstore = $this->openMessageStore(/** @scrutinizer ignore-type */ $defaultUser);
Loading history...
162
		}
163
164
		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...
165
			throw new ServiceUnavailableException("Error connecting to gromox-zcore (open store)");
166
		}
167
168
		if ($this->defaultstore === false) {
169
			throw new AuthenticationRequiredException(sprintf("Grommunio->Logon(): User '%s' has no default store", $defaultUser));
170
		}
171
172
		$this->store = $this->defaultstore;
173
		$this->storeName = $defaultUser;
174
175
		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

175
		SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->Logon(): User '%s' is authenticated%s", $this->mainUser, $this->impersonateUser ? " impersonating '" . /** @scrutinizer ignore-type */ $this->impersonateUser . "'" : ''));
Loading history...
176
177
		$this->isGSyncEnabled();
178
179
		// check if this is a Zarafa 7 store with unicode support
180
		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

180
		MAPIUtils::IsUnicodeStore(/** @scrutinizer ignore-type */ $this->store);
Loading history...
181
182
		// open the state folder
183
		$this->getStateFolder($deviceId);
184
185
		return true;
186
	}
187
188
	/**
189
	 * Setup the backend to work on a specific store or checks ACLs there.
190
	 * If only the $store is submitted, all Import/Export/Fetch/Etc operations should be
191
	 * performed on this store (switch operations store).
192
	 * If the ACL check is enabled, this operation should just indicate the ACL status on
193
	 * the submitted store, without changing the store for operations.
194
	 * For the ACL status, the currently logged on user MUST have access rights on
195
	 *  - the entire store - admin access if no folderid is sent, or
196
	 *  - on a specific folderid in the store (secretary/full access rights).
197
	 *
198
	 * The ACLcheck MUST fail if a folder of the authenticated user is checked!
199
	 *
200
	 * @param string $store        target store, could contain a "domain\user" value
201
	 * @param bool   $checkACLonly if set to true, Setup() should just check ACLs
202
	 * @param string $folderid     if set, only ACLs on this folderid are relevant
203
	 *
204
	 * @return bool
205
	 */
206
	public function Setup($store, $checkACLonly = false, $folderid = false) {
207
		[$user, $domain] = Utils::SplitDomainUser($store);
208
209
		if (!isset($this->mainUser)) {
210
			return false;
211
		}
212
213
		$mainUser = $this->mainUser;
214
		// when impersonating we need to check against the impersonated user
215
		if ($this->impersonateUser) {
216
			$mainUser = $this->impersonateUser;
217
		}
218
219
		if ($user === false) {
0 ignored issues
show
introduced by
The condition $user === false is always false.
Loading history...
220
			$user = $mainUser;
221
		}
222
223
		// This is a special case. A user will get his entire folder structure by the foldersync by default.
224
		// The ACL check is executed when an additional folder is going to be sent to the mobile.
225
		// Configured that way the user could receive the same folderid twice, with two different names.
226
		if ($mainUser == $user && $checkACLonly && $folderid && !$this->impersonateUser) {
227
			SLog::Write(LOGLEVEL_DEBUG, "Grommunio->Setup(): Checking ACLs for folder of the users defaultstore. Fail is forced to avoid folder duplications on mobile.");
228
229
			return false;
230
		}
231
232
		// get the users store
233
		$userstore = $this->openMessageStore($user);
234
235
		// only proceed if a store was found, else return false
236
		if ($userstore) {
237
			// only check permissions
238
			if ($checkACLonly === true) {
239
				// check for admin rights
240
				if (!$folderid) {
241
					if ($user != $this->mainUser) {
242
						if ($this->impersonateUser) {
243
							$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...
244
							$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

244
							$rights = $this->HasSecretaryACLs(/** @scrutinizer ignore-type */ $userstore, '', $storeProps[PR_IPM_SUBTREE_ENTRYID]);
Loading history...
245
							SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->Setup(): Checking for secretary ACLs on root folder of impersonated store '%s': '%s'", $user, Utils::PrintAsString($rights)));
246
						}
247
						else {
248
							$zarafauserinfo = @nsp_getuserinfo($this->mainUser);
249
							$rights = (isset($zarafauserinfo['admin']) && $zarafauserinfo['admin']) ? true : false;
250
							SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->Setup(): Checking for admin ACLs on store '%s': '%s'", $user, Utils::PrintAsString($rights)));
251
						}
252
					}
253
					// the user has always full access to his own store
254
					else {
255
						$rights = true;
256
						SLog::Write(LOGLEVEL_DEBUG, "Grommunio->Setup(): the user has always full access to his own store");
257
					}
258
259
					return $rights;
260
				}
261
				// check permissions on this folder
262
263
				$rights = $this->HasSecretaryACLs($userstore, $folderid);
264
				SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->Setup(): Checking for secretary ACLs on '%s' of store '%s': '%s'", $folderid, $user, Utils::PrintAsString($rights)));
265
266
				return $rights;
267
			}
268
269
			// switch operations store
270
			// this should also be done if called with user = mainuser or user = false
271
			// which means to switch back to the default store
272
273
			// switch active store
274
			$this->store = $userstore;
275
			$this->storeName = $user;
276
277
			return true;
278
		}
279
280
		return false;
281
	}
282
283
	/**
284
	 * Logs off
285
	 * Free/Busy information is updated for modified calendars
286
	 * This is done after the synchronization process is completed.
287
	 *
288
	 * @return bool
289
	 */
290
	public function Logoff() {
291
		return true;
292
	}
293
294
	/**
295
	 * Returns an array of SyncFolder types with the entire folder hierarchy
296
	 * on the server (the array itself is flat, but refers to parents via the 'parent' property.
297
	 *
298
	 * provides AS 1.0 compatibility
299
	 *
300
	 * @return array SYNC_FOLDER
301
	 */
302
	public function GetHierarchy() {
303
		$folders = [];
304
		$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

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

304
		$mapiprovider = new MAPIProvider($this->session, /** @scrutinizer ignore-type */ $this->store);
Loading history...
305
		$storeProps = $mapiprovider->GetStoreProps();
306
307
		// for SYSTEM user open the public folders
308
		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

308
		if (strtoupper(/** @scrutinizer ignore-type */ $this->storeName) == "SYSTEM") {
Loading history...
309
			$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...
310
		}
311
		else {
312
			$rootfolder = mapi_msgstore_openentry($this->store);
313
		}
314
315
		$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...
316
317
		$hierarchy = mapi_folder_gethierarchytable($rootfolder, CONVENIENT_DEPTH);
0 ignored issues
show
Bug introduced by
The constant CONVENIENT_DEPTH was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
318
		$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_EXTENDED_FOLDER_FLAGS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
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_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_DISPLAY_NAME 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_PARENT_ENTRYID 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...
319
		SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->GetHierarchy(): fetched %d folders from MAPI", count($rows)));
320
321
		foreach ($rows as $row) {
322
			// do not display hidden and search folders
323
			if ((isset($row[PR_ATTR_HIDDEN]) && $row[PR_ATTR_HIDDEN]) ||
324
				(isset($row[PR_FOLDER_TYPE]) && $row[PR_FOLDER_TYPE] == FOLDER_SEARCH) ||
0 ignored issues
show
Bug introduced by
The constant FOLDER_SEARCH was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
325
				// for SYSTEM user $row[PR_PARENT_SOURCE_KEY] == $rootfolderprops[PR_SOURCE_KEY] is true, but we need those folders
326
				(isset($row[PR_PARENT_SOURCE_KEY]) && $row[PR_PARENT_SOURCE_KEY] == $rootfolderprops[PR_SOURCE_KEY] && strtoupper($this->storeName) != "SYSTEM")) {
327
				SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->GetHierarchy(): ignoring folder '%s' as it's a hidden/search/root folder", $row[PR_DISPLAY_NAME] ?? "unknown"));
328
329
				continue;
330
			}
331
			$folder = $mapiprovider->GetFolder($row);
332
			if ($folder) {
333
				$folders[] = $folder;
334
			}
335
			else {
336
				SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->GetHierarchy(): ignoring folder '%s' as MAPIProvider->GetFolder() did not return a SyncFolder object", $row[PR_DISPLAY_NAME] ?? "unknown"));
337
			}
338
		}
339
340
		SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->GetHierarchy(): processed %d folders, starting parent remap", count($folders)));
341
		// reloop the folders to make sure all parentids are mapped correctly
342
		$dm = GSync::GetDeviceManager();
343
		foreach ($folders as $folder) {
344
			if ($folder->parentid !== "0") {
345
				// SYSTEM user's parentid points to $rootfolderprops[PR_SOURCE_KEY], but they need to be on the top level
346
				$folder->parentid = (strtoupper($this->storeName) == "SYSTEM" && $folder->parentid == bin2hex((string) $rootfolderprops[PR_SOURCE_KEY])) ? '0' : $dm->GetFolderIdForBackendId($folder->parentid);
347
			}
348
		}
349
350
		return $folders;
351
	}
352
353
	/**
354
	 * Returns the importer to process changes from the mobile
355
	 * If no $folderid is given, hierarchy importer is expected.
356
	 *
357
	 * @param string $folderid (opt)
358
	 *
359
	 * @return object(ImportChanges)
360
	 */
361
	public function GetImporter($folderid = false) {
362
		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

362
		SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->GetImporter() folderid: '%s'", Utils::PrintAsString(/** @scrutinizer ignore-type */ $folderid)));
Loading history...
363
		if ($folderid !== false) {
364
			// check if the user of the current store has permissions to import to this folderid
365
			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

365
			if ($this->storeName != $this->mainUser && !$this->hasSecretaryACLs(/** @scrutinizer ignore-type */ $this->store, $folderid)) {
Loading history...
366
				SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->GetImporter(): missing permissions on folderid: '%s'.", Utils::PrintAsString($folderid)));
367
368
				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...
369
			}
370
371
			return new ImportChangesICS($this->session, $this->store, hex2bin($folderid));
0 ignored issues
show
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

371
			return new ImportChangesICS(/** @scrutinizer ignore-type */ $this->session, $this->store, hex2bin($folderid));
Loading history...
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

371
			return new ImportChangesICS($this->session, /** @scrutinizer ignore-type */ $this->store, hex2bin($folderid));
Loading history...
372
		}
373
374
		return new ImportChangesICS($this->session, $this->store);
375
	}
376
377
	/**
378
	 * Returns the exporter to send changes to the mobile
379
	 * If no $folderid is given, hierarchy exporter is expected.
380
	 *
381
	 * @param string $folderid (opt)
382
	 *
383
	 * @return object(ExportChanges)
384
	 *
385
	 * @throws StatusException
386
	 */
387
	public function GetExporter($folderid = false) {
388
		if ($folderid !== false) {
389
			// check if the user of the current store has permissions to export from this folderid
390
			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

390
			if ($this->storeName != $this->mainUser && !$this->hasSecretaryACLs(/** @scrutinizer ignore-type */ $this->store, $folderid)) {
Loading history...
391
				SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->GetExporter(): missing permissions on folderid: '%s'.", Utils::PrintAsString($folderid)));
392
393
				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...
394
			}
395
396
			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

396
			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

396
			return new ExportChangesICS(/** @scrutinizer ignore-type */ $this->session, $this->store, hex2bin($folderid));
Loading history...
397
		}
398
399
		return new ExportChangesICS($this->session, $this->store);
400
	}
401
402
	/**
403
	 * Sends an e-mail
404
	 * This messages needs to be saved into the 'sent items' folder.
405
	 *
406
	 * @param SyncSendMail $sm SyncSendMail object
407
	 *
408
	 * @return bool
409
	 *
410
	 * @throws StatusException
411
	 */
412
	public function SendMail($sm) {
413
		// Check if imtomapi function is available and use it to send the mime message.
414
		// It is available since ZCP 7.0.6
415
		if (!(function_exists('mapi_feature') && mapi_feature('INETMAPI_IMTOMAPI'))) {
416
			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);
417
418
			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...
419
		}
420
		$mimeLength = strlen((string) $sm->mime);
421
		SLog::Write(LOGLEVEL_DEBUG, sprintf(
422
			"Grommunio->SendMail(): RFC822: %d bytes  forward-id: '%s' reply-id: '%s' parent-id: '%s' SaveInSent: '%s' ReplaceMIME: '%s'",
423
			$mimeLength,
424
			Utils::PrintAsString($sm->forwardflag),
425
			Utils::PrintAsString($sm->replyflag),
426
			Utils::PrintAsString($sm->source->folderid ?? false),
0 ignored issues
show
Bug introduced by
It seems like $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

426
			Utils::PrintAsString(/** @scrutinizer ignore-type */ $sm->source->folderid ?? false),
Loading history...
427
			Utils::PrintAsString($sm->saveinsent),
428
			Utils::PrintAsString(isset($sm->replacemime))
429
		));
430
		if ($mimeLength == 0) {
431
			throw new StatusException("Grommunio->SendMail(): empty mail data", SYNC_COMMONSTATUS_MAILSUBMISSIONFAILED);
432
		}
433
434
		$sendMailProps = MAPIMapping::GetSendMailProperties();
435
		$sendMailProps = getPropIdsFromStrings($this->defaultstore, $sendMailProps);
0 ignored issues
show
Bug introduced by
The function getPropIdsFromStrings 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

435
		$sendMailProps = /** @scrutinizer ignore-call */ getPropIdsFromStrings($this->defaultstore, $sendMailProps);
Loading history...
436
437
		// Open the outbox and create the message there
438
		$storeprops = mapi_getprops($this->defaultstore, [$sendMailProps["outboxentryid"], $sendMailProps["ipmsentmailentryid"]]);
439
		if (isset($storeprops[$sendMailProps["outboxentryid"]])) {
440
			$outbox = mapi_msgstore_openentry($this->defaultstore, $storeprops[$sendMailProps["outboxentryid"]]);
441
		}
442
443
		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...
444
			throw new StatusException(sprintf("Grommunio->SendMail(): No Outbox found or unable to create message: 0x%X", mapi_last_hresult()), SYNC_COMMONSTATUS_SERVERERROR);
445
		}
446
447
		$mapimessage = mapi_folder_createmessage($outbox);
448
449
		// message properties to be set
450
		$mapiprops = [];
451
		// only save the outgoing in sent items folder if the mobile requests it
452
		$mapiprops[$sendMailProps["sentmailentryid"]] = $storeprops[$sendMailProps["ipmsentmailentryid"]];
453
454
		$ab = $this->getAddressbook();
455
		if (!$ab) {
0 ignored issues
show
introduced by
$ab is of type MAPIAddressbook, thus it always evaluated to true.
Loading history...
456
			throw new StatusException(sprintf("Grommunio->SendMail(): unable to open addressbook: 0x%X", mapi_last_hresult()), SYNC_COMMONSTATUS_SERVERERROR);
457
		}
458
		mapi_inetmapi_imtomapi($this->session, $this->defaultstore, $ab, $mapimessage, $sm->mime, []);
459
460
		// Set the appSeqNr so that tracking tab can be updated for meeting request updates
461
		$meetingRequestProps = MAPIMapping::GetMeetingRequestProperties();
462
		$meetingRequestProps = getPropIdsFromStrings($this->defaultstore, $meetingRequestProps);
463
		$props = mapi_getprops($mapimessage, [PR_MESSAGE_CLASS, $meetingRequestProps["goidtag"], $sendMailProps["internetcpid"], $sendMailProps["body"], $sendMailProps["html"]]);
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...
464
465
		// Convert sent message's body to UTF-8 if it was a HTML message.
466
		if (isset($props[$sendMailProps["internetcpid"]]) && $props[$sendMailProps["internetcpid"]] != INTERNET_CPID_UTF8 && MAPIUtils::GetNativeBodyType($props) == SYNC_BODYPREFERENCE_HTML) {
467
			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"]]));
468
			$mapiprops[$sendMailProps["internetcpid"]] = INTERNET_CPID_UTF8;
469
470
			$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...
471
			$bodyHtml = Utils::ConvertCodepageStringToUtf8($props[$sendMailProps["internetcpid"]], $bodyHtml);
472
			$mapiprops[$sendMailProps["html"]] = $bodyHtml;
473
474
			mapi_setprops($mapimessage, $mapiprops);
475
		}
476
		if (stripos((string) $props[PR_MESSAGE_CLASS], "IPM.Schedule.Meeting.Resp.") === 0) {
477
			// search for calendar items using goid
478
			$mr = new Meetingrequest($this->defaultstore, $mapimessage);
0 ignored issues
show
Bug introduced by
The type Meetingrequest 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...
479
			$appointments = $mr->findCalendarItems($props[$meetingRequestProps["goidtag"]]);
480
			if (is_array($appointments) && !empty($appointments)) {
481
				$app = mapi_msgstore_openentry($this->defaultstore, $appointments[0]);
482
				$appprops = mapi_getprops($app, [$meetingRequestProps["appSeqNr"]]);
483
				if (isset($appprops[$meetingRequestProps["appSeqNr"]]) && $appprops[$meetingRequestProps["appSeqNr"]]) {
484
					$mapiprops[$meetingRequestProps["appSeqNr"]] = $appprops[$meetingRequestProps["appSeqNr"]];
485
					SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->SendMail(): Set sequence number to:%d", $appprops[$meetingRequestProps["appSeqNr"]]));
486
				}
487
			}
488
		}
489
490
		// Delete the PR_SENT_REPRESENTING_* properties because some android devices
491
		// do not send neither From nor Sender header causing empty PR_SENT_REPRESENTING_NAME and
492
		// PR_SENT_REPRESENTING_EMAIL_ADDRESS properties and "broken" PR_SENT_REPRESENTING_ENTRYID
493
		// which results in spooler not being able to send the message.
494
		mapi_deleteprops(
495
			$mapimessage,
496
			[
497
				$sendMailProps["sentrepresentingname"],
498
				$sendMailProps["sentrepresentingemail"],
499
				$sendMailProps["representingentryid"],
500
				$sendMailProps["sentrepresentingaddt"],
501
				$sendMailProps["sentrepresentinsrchk"],
502
			]
503
		);
504
505
		if (isset($sm->source->itemid) && $sm->source->itemid) {
506
			// answering an email in a public/shared folder
507
			// 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)
508
			if (!$this->Setup(GSync::GetAdditionalSyncFolderStore($sm->source->folderid))) {
509
				throw new StatusException(sprintf("Grommunio->SendMail() could not Setup() the backend for folder id '%s'", $sm->source->folderid), SYNC_COMMONSTATUS_SERVERERROR);
510
			}
511
512
			$entryid = mapi_msgstore_entryidfromsourcekey($this->store, hex2bin($sm->source->folderid), hex2bin((string) $sm->source->itemid));
513
			if ($entryid) {
514
				$fwmessage = mapi_msgstore_openentry($this->store, $entryid);
515
			}
516
517
			if (isset($fwmessage) && $fwmessage) {
518
				// update icon and last_verb when forwarding or replying message
519
				// reply-all (verb 103) is not supported, as we cannot really detect this case
520
				if ($sm->forwardflag) {
521
					$updateProps = [
522
						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...
523
						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...
524
					];
525
				}
526
				elseif ($sm->replyflag) {
527
					$updateProps = [
528
						PR_ICON_INDEX => 261,
529
						PR_LAST_VERB_EXECUTED => 102,
530
					];
531
				}
532
				if (isset($updateProps)) {
533
					$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...
534
					mapi_setprops($fwmessage, $updateProps);
535
					mapi_savechanges($fwmessage);
536
				}
537
538
				// only attach the original message if the mobile does not send it itself
539
				if (!isset($sm->replacemime)) {
540
					// get message's body in order to append forward or reply text
541
					$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...
542
					if (!isset($bodyHtml)) {
543
						$bodyHtml = MAPIUtils::readPropStream($mapimessage, PR_HTML);
544
					}
545
					$cpid = mapi_getprops($fwmessage, [$sendMailProps["internetcpid"]]);
546
					if ($sm->forwardflag) {
547
						// attach the original attachments to the outgoing message
548
						$this->copyAttachments($mapimessage, $fwmessage);
549
					}
550
551
					if (strlen($body) > 0) {
552
						$fwbody = MAPIUtils::readPropStream($fwmessage, PR_BODY);
553
						// if only the old message's cpid is set, convert from old charset to utf-8
554
						if (isset($cpid[$sendMailProps["internetcpid"]]) && $cpid[$sendMailProps["internetcpid"]] != INTERNET_CPID_UTF8) {
555
							SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->SendMail(): convert plain forwarded message charset (only fw set) from '%s' to '65001'", $cpid[$sendMailProps["internetcpid"]]));
556
							$fwbody = Utils::ConvertCodepageStringToUtf8($cpid[$sendMailProps["internetcpid"]], $fwbody);
557
						}
558
559
						$mapiprops[$sendMailProps["body"]] = $body . "\r\n\r\n" . $fwbody;
560
					}
561
562
					if (strlen($bodyHtml) > 0) {
563
						$fwbodyHtml = MAPIUtils::readPropStream($fwmessage, PR_HTML);
564
						// if only new message's cpid is set, convert to UTF-8
565
						if (isset($cpid[$sendMailProps["internetcpid"]]) && $cpid[$sendMailProps["internetcpid"]] != INTERNET_CPID_UTF8) {
566
							SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->SendMail(): convert html forwarded message charset (only fw set) from '%s' to '65001'", $cpid[$sendMailProps["internetcpid"]]));
567
							$fwbodyHtml = Utils::ConvertCodepageStringToUtf8($cpid[$sendMailProps["internetcpid"]], $fwbodyHtml);
568
						}
569
570
						$mapiprops[$sendMailProps["html"]] = $bodyHtml . "<br><br>" . $fwbodyHtml;
571
					}
572
				}
573
			}
574
			else {
575
				// no fwmessage could be opened and we need it because we do not replace mime
576
				if (!isset($sm->replacemime) || $sm->replacemime == false) {
577
					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);
578
				}
579
			}
580
		}
581
582
		mapi_setprops($mapimessage, $mapiprops);
583
		mapi_savechanges($mapimessage);
584
		mapi_message_submitmessage($mapimessage);
585
		$hr = mapi_last_hresult();
586
587
		if ($hr) {
588
			$code = match ($hr) {
589
				MAPI_E_STORE_FULL => SYNC_COMMONSTATUS_MAILBOXQUOTAEXCEEDED,
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...
590
				default => SYNC_COMMONSTATUS_MAILSUBMISSIONFAILED,
591
			};
592
593
			throw new StatusException(sprintf("Grommunio->SendMail(): Error saving/submitting the message to the Outbox: 0x%X", $hr), $code);
594
		}
595
596
		SLog::Write(LOGLEVEL_DEBUG, "Grommunio->SendMail(): email submitted");
597
598
		return true;
599
	}
600
601
	/**
602
	 * Returns all available data of a single message.
603
	 *
604
	 * @param string            $folderid
605
	 * @param string            $id
606
	 * @param ContentParameters $contentparameters flag
607
	 *
608
	 * @return object(SyncObject)
609
	 *
610
	 * @throws StatusException
611
	 */
612
	public function Fetch($folderid, $id, $contentparameters) {
613
		// SEARCH fetches with folderid == false and PR_ENTRYID as ID
614
		if (!$folderid) {
615
			$entryid = hex2bin($id);
616
			$sk = $id;
617
		}
618
		else {
619
			// id might be in the new longid format, so we have to split it here
620
			[$fsk, $sk] = Utils::SplitMessageId($id);
621
			// get the entry id of the message
622
			$entryid = mapi_msgstore_entryidfromsourcekey($this->store, hex2bin($folderid), hex2bin((string) $sk));
623
		}
624
		if (!$entryid) {
625
			throw new StatusException(sprintf("Grommunio->Fetch('%s','%s'): Error getting entryid: 0x%X", $folderid, $sk, mapi_last_hresult()), SYNC_STATUS_OBJECTNOTFOUND);
626
		}
627
628
		// open the message
629
		$message = mapi_msgstore_openentry($this->store, $entryid);
630
		if (!$message) {
631
			throw new StatusException(sprintf("Grommunio->Fetch('%s','%s'): Error, unable to open message: 0x%X", $folderid, $sk, mapi_last_hresult()), SYNC_STATUS_OBJECTNOTFOUND);
632
		}
633
634
		// convert the mapi message into a SyncObject and return it
635
		$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

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

635
		$mapiprovider = new MAPIProvider($this->session, /** @scrutinizer ignore-type */ $this->store);
Loading history...
636
637
		// override truncation
638
		$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

638
		$contentparameters->/** @scrutinizer ignore-call */ 
639
                      SetTruncation(SYNC_TRUNCATION_ALL);
Loading history...
639
640
		// TODO check for body preferences
641
		return $mapiprovider->GetMessage($message, $contentparameters);
642
	}
643
644
	/**
645
	 * Returns the waste basket.
646
	 *
647
	 * @return string
648
	 */
649
	public function GetWasteBasket() {
650
		if ($this->wastebasket) {
651
			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...
652
		}
653
654
		$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...
655
		if (isset($storeprops[PR_IPM_WASTEBASKET_ENTRYID])) {
656
			$wastebasket = mapi_msgstore_openentry($this->defaultstore, $storeprops[PR_IPM_WASTEBASKET_ENTRYID]);
657
			$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...
658
			if (isset($wastebasketprops[PR_SOURCE_KEY])) {
659
				$this->wastebasket = bin2hex((string) $wastebasketprops[PR_SOURCE_KEY]);
660
				SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->GetWasteBasket(): Got waste basket with id '%s'", $this->wastebasket));
661
662
				return $this->wastebasket;
663
			}
664
		}
665
666
		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...
667
	}
668
669
	/**
670
	 * Returns the content of the named attachment as stream.
671
	 *
672
	 * @param string $attname
673
	 *
674
	 * @return SyncItemOperationsAttachment
675
	 *
676
	 * @throws StatusException
677
	 */
678
	public function GetAttachmentData($attname) {
679
		SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->GetAttachmentData('%s')", $attname));
680
681
		if (!strpos($attname, ":")) {
682
			throw new StatusException(sprintf("Grommunio->GetAttachmentData('%s'): Error, attachment requested for non-existing item", $attname), SYNC_ITEMOPERATIONSSTATUS_INVALIDATT);
683
		}
684
685
		[$id, $attachnum, $parentSourceKey, $exceptionBasedate] = explode(":", $attname);
686
		$this->Setup(GSync::GetAdditionalSyncFolderStore($parentSourceKey));
687
688
		$entryid = hex2bin($id);
689
		$message = mapi_msgstore_openentry($this->store, $entryid);
690
		if (!$message) {
691
			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);
692
		}
693
694
		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

694
		MAPIUtils::ParseSmime(/** @scrutinizer ignore-type */ $this->session, $this->defaultstore, $this->getAddressbook(), $message);
Loading history...
695
		$attach = mapi_message_openattach($message, $attachnum);
696
		if (!$attach) {
697
			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);
698
		}
699
700
		// attachment of a recurring appointment exception
701
		if (strlen($exceptionBasedate) > 1) {
702
			$recurrence = new Recurrence($this->store, $message);
0 ignored issues
show
Bug introduced by
The type Recurrence 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...
703
			$exceptionatt = $recurrence->getExceptionAttachment(hex2bin($exceptionBasedate));
704
			$exceptionobj = mapi_attach_openobj($exceptionatt, 0);
705
			$attach = mapi_message_openattach($exceptionobj, $attachnum);
706
		}
707
708
		// get necessary attachment props
709
		$attprops = mapi_getprops($attach, [PR_ATTACH_MIME_TAG, PR_ATTACH_METHOD]);
0 ignored issues
show
Bug introduced by
The constant PR_ATTACH_METHOD was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
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...
710
		$attachment = new SyncItemOperationsAttachment();
711
		// check if it's an embedded message and open it in such a case
712
		if (isset($attprops[PR_ATTACH_METHOD]) && $attprops[PR_ATTACH_METHOD] == ATTACH_EMBEDDED_MSG) {
0 ignored issues
show
Bug introduced by
The constant ATTACH_EMBEDDED_MSG was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
713
			$embMessage = mapi_attach_openobj($attach);
714
			$addrbook = $this->getAddressbook();
715
			$stream = mapi_inetmapi_imtoinet($this->session, $addrbook, $embMessage, ['use_tnef' => -1]);
716
			// set the default contenttype for this kind of messages
717
			$attachment->contenttype = "message/rfc822";
718
		}
719
		else {
720
			$stream = mapi_openproperty($attach, PR_ATTACH_DATA_BIN, IID_IStream, 0, 0);
0 ignored issues
show
Bug introduced by
The constant IID_IStream was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
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...
721
		}
722
723
		if (!$stream) {
724
			throw new StatusException(sprintf("Grommunio->GetAttachmentData('%s'): Error, unable to open attachment data stream: 0x%X", $attname, mapi_last_hresult()), SYNC_ITEMOPERATIONSSTATUS_INVALIDATT);
725
		}
726
727
		// put the mapi stream into a wrapper to get a standard stream
728
		$attachment->data = MAPIStreamWrapper::Open($stream);
729
		if (isset($attprops[PR_ATTACH_MIME_TAG])) {
730
			$attachment->contenttype = $attprops[PR_ATTACH_MIME_TAG];
731
		}
732
733
		// TODO default contenttype
734
		return $attachment;
735
	}
736
737
	/**
738
	 * Deletes all contents of the specified folder.
739
	 * This is generally used to empty the trash (wastebasked), but could also be used on any
740
	 * other folder.
741
	 *
742
	 * @param string $folderid
743
	 * @param bool   $includeSubfolders (opt) also delete sub folders, default true
744
	 *
745
	 * @return bool
746
	 *
747
	 * @throws StatusException
748
	 */
749
	public function EmptyFolder($folderid, $includeSubfolders = true) {
750
		$folderentryid = mapi_msgstore_entryidfromsourcekey($this->store, hex2bin($folderid));
751
		if (!$folderentryid) {
752
			throw new StatusException(sprintf("Grommunio->EmptyFolder('%s','%s'): Error, unable to open folder (no entry id)", $folderid, Utils::PrintAsString($includeSubfolders)), SYNC_ITEMOPERATIONSSTATUS_SERVERERROR);
753
		}
754
		$folder = mapi_msgstore_openentry($this->store, $folderentryid);
755
756
		if (!$folder) {
757
			throw new StatusException(sprintf("Grommunio->EmptyFolder('%s','%s'): Error, unable to open parent folder (open entry)", $folderid, Utils::PrintAsString($includeSubfolders)), SYNC_ITEMOPERATIONSSTATUS_SERVERERROR);
758
		}
759
760
		$flags = 0;
761
		if ($includeSubfolders) {
762
			$flags = DEL_ASSOCIATED;
0 ignored issues
show
Bug introduced by
The constant DEL_ASSOCIATED was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
763
		}
764
765
		SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->EmptyFolder('%s','%s'): emptying folder", $folderid, Utils::PrintAsString($includeSubfolders)));
766
767
		// empty folder!
768
		mapi_folder_emptyfolder($folder, $flags);
769
		if (mapi_last_hresult()) {
770
			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);
771
		}
772
773
		return true;
774
	}
775
776
	/**
777
	 * Processes a response to a meeting request.
778
	 * CalendarID is a reference and has to be set if a new calendar item is created.
779
	 *
780
	 * @param string $folderid id of the parent folder of $requestid
781
	 * @param array  $request
782
	 *
783
	 * @return string id of the created/updated calendar obj
784
	 *
785
	 * @throws StatusException
786
	 */
787
	public function MeetingResponse($folderid, $request) {
788
		$requestid = $calendarid = $request['requestid'];
789
		$response = $request['response'];
790
		// Use standard meeting response code to process meeting request
791
		[$fid, $requestid] = Utils::SplitMessageId($requestid);
792
		$reqentryid = mapi_msgstore_entryidfromsourcekey($this->store, hex2bin($folderid), hex2bin((string) $requestid));
793
		if (!$reqentryid) {
794
			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);
795
		}
796
797
		$mapimessage = mapi_msgstore_openentry($this->store, $reqentryid);
798
		if (!$mapimessage) {
799
			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);
800
		}
801
802
		$searchForResultCalendarItem = false;
803
		$folderClass = GSync::GetDeviceManager()->GetFolderClassFromCacheByID($fid);
804
		if ($folderClass == 'Email') {
805
			// The mobile requested this on a MR, when finishing we need to search for the resulting calendar item!
806
			$searchForResultCalendarItem = true;
807
		}
808
		// we are operating on the calendar item - try searching for the corresponding meeting request first
809
		else {
810
			$props = MAPIMapping::GetMeetingRequestProperties();
811
			$props = getPropIdsFromStrings($this->store, $props);
0 ignored issues
show
Bug introduced by
The function getPropIdsFromStrings 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

811
			$props = /** @scrutinizer ignore-call */ getPropIdsFromStrings($this->store, $props);
Loading history...
812
813
			$messageprops = mapi_getprops($mapimessage, [$props["goidtag"]]);
814
			$goid = $messageprops[$props["goidtag"]];
815
816
			$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

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

816
			$mapiprovider = new MAPIProvider(/** @scrutinizer ignore-type */ $this->session, $this->store);
Loading history...
817
			$inboxprops = $mapiprovider->GetInboxProps();
818
			$folder = mapi_msgstore_openentry($this->store, $inboxprops[PR_ENTRYID]);
819
820
			// Find the item by restricting all items to the correct ID
821
			$restrict = [RES_AND, [
0 ignored issues
show
Bug introduced by
The constant RES_AND was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
822
				[RES_PROPERTY,
0 ignored issues
show
Bug introduced by
The constant RES_PROPERTY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
823
					[
824
						RELOP => RELOP_EQ,
0 ignored issues
show
Bug introduced by
The constant RELOP was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant RELOP_EQ was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
825
						ULPROPTAG => $props["goidtag"],
0 ignored issues
show
Bug introduced by
The constant ULPROPTAG was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
826
						VALUE => $goid,
0 ignored issues
show
Bug introduced by
The constant VALUE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
827
					],
828
				],
829
			]];
830
831
			$inboxcontents = mapi_folder_getcontentstable($folder);
832
833
			$rows = mapi_table_queryallrows($inboxcontents, [PR_ENTRYID, PR_SOURCE_KEY], $restrict);
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...
834
835
			// AS 14.0 and older can only respond to a MR in the Inbox!
836
			if (empty($rows) && Request::GetProtocolVersion() <= 14.0) {
837
				throw new StatusException(sprintf("Grommunio->MeetingResponse('%s','%s', '%s'): Error, meeting request not found in the inbox. Can't proceed, aborting!", $requestid, $folderid, $response), SYNC_MEETRESPSTATUS_INVALIDMEETREQ);
838
			}
839
			if (!empty($rows)) {
840
				SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->MeetingResponse found meeting request in the inbox with ID: %s", bin2hex((string) $rows[0][PR_SOURCE_KEY])));
841
				$reqentryid = $rows[0][PR_ENTRYID];
842
				$mapimessage = mapi_msgstore_openentry($this->store, $reqentryid);
843
844
				// As we are using an MR from the inbox, when finishing we need to search for the resulting calendar item!
845
				$searchForResultCalendarItem = true;
846
			}
847
		}
848
849
		$meetingrequest = new Meetingrequest($this->store, $mapimessage, $this->session);
850
851
		if (Request::GetProtocolVersion() <= 14.0 && !$meetingrequest->isMeetingRequest() && !$meetingrequest->isMeetingRequestResponse() && !$meetingrequest->isMeetingCancellation()) {
852
			throw new StatusException(sprintf("Grommunio->MeetingResponse('%s','%s', '%s'): Error, attempt to respond to non-meeting request", $requestid, $folderid, $response), SYNC_MEETRESPSTATUS_INVALIDMEETREQ);
853
		}
854
855
		if ($meetingrequest->isLocalOrganiser()) {
856
			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);
857
		}
858
859
		// AS-16.1: did the attendee propose a new time ?
860
		if (!empty($request['proposedstarttime'])) {
861
			$request['proposedstarttime'] = Utils::ParseDate($request['proposedstarttime']);
862
		}
863
		else {
864
			$request['proposedstarttime'] = false;
865
		}
866
		if (!empty($request['proposedendtime'])) {
867
			$request['proposedendtime'] = Utils::ParseDate($request['proposedendtime']);
868
		}
869
		else {
870
			$request['proposedendtime'] = false;
871
		}
872
		if (!isset($request['body'])) {
873
			$request['body'] = false;
874
		}
875
876
		// from AS-14.0 we have to take care of sending out meeting request responses
877
		if (Request::GetProtocolVersion() >= 14.0) {
878
			$sendresponse = true;
879
		}
880
		else {
881
			// Old AS versions send MR updates by themselves - so our MR processing doesn't need to do this
882
			$sendresponse = false;
883
		}
884
885
		switch ($response) {
886
			case 1:     // accept
887
			default:
888
				$entryid = $meetingrequest->doAccept(false, $sendresponse, false, false, false, false, true); // last true is the $userAction
889
				break;
890
891
			case 2:        // tentative
892
				$entryid = $meetingrequest->doAccept(true, $sendresponse, false, $request['proposedstarttime'], $request['proposedendtime'], $request['body'], true); // last true is the $userAction
893
				break;
894
895
			case 3:        // decline
896
				$meetingrequest->doDecline($sendresponse);
897
				break;
898
		}
899
900
		// We have to return the ID of the new calendar item if it was created from an email
901
		if ($searchForResultCalendarItem) {
902
			$calendarid = "";
903
			$calFolderId = "";
904
			if (isset($entryid)) {
905
				$newitem = mapi_msgstore_openentry($this->store, $entryid);
906
				// new item might be in a delegator's store. ActiveSync does not support accepting them.
907
				if (!$newitem) {
908
					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);
909
				}
910
911
				$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...
912
				$calendarid = bin2hex((string) $newprops[PR_SOURCE_KEY]);
913
				$calFolderId = bin2hex((string) $newprops[PR_PARENT_SOURCE_KEY]);
914
			}
915
916
			// on recurring items, the MeetingRequest class responds with a wrong entryid
917
			if ($requestid == $calendarid) {
918
				SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->MeetingResponse('%s','%s', '%s'): returned calendar id is the same as the requestid - re-searching", $requestid, $folderid, $response));
919
920
				if (empty($props)) {
921
					$props = MAPIMapping::GetMeetingRequestProperties();
922
					$props = getPropIdsFromStrings($this->store, $props);
923
				}
924
925
				$messageprops = mapi_getprops($mapimessage, [$props["goidtag"]]);
926
				$goid = $messageprops[$props["goidtag"]];
927
				$items = $meetingrequest->findCalendarItems($goid);
928
929
				if (is_array($items)) {
930
					$newitem = mapi_msgstore_openentry($this->store, $items[0]);
931
					$newprops = mapi_getprops($newitem, [PR_SOURCE_KEY, PR_PARENT_SOURCE_KEY]);
932
					$calendarid = bin2hex((string) $newprops[PR_SOURCE_KEY]);
933
					$calFolderId = bin2hex((string) $newprops[PR_PARENT_SOURCE_KEY]);
934
					SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->MeetingResponse('%s','%s', '%s'): found other calendar id: %s", $requestid, $folderid, $response, $calendarid));
935
				}
936
937
				if ($requestid == $calendarid) {
938
					throw new StatusException(sprintf("Grommunio->MeetingResponse('%s','%s', '%s'): Error finding the accepted meeting response in the calendar", $requestid, $folderid, $response), SYNC_MEETRESPSTATUS_INVALIDMEETREQ);
939
				}
940
			}
941
942
			// delete meeting request from Inbox
943
			if (isset($folderClass) && $folderClass == 'Email') {
944
				$folderentryid = mapi_msgstore_entryidfromsourcekey($this->store, hex2bin($folderid));
945
				$folder = mapi_msgstore_openentry($this->store, $folderentryid);
946
				mapi_folder_deletemessages($folder, [$reqentryid], 0);
947
			}
948
949
			$prefix = '';
950
			// prepend the short folderid of the target calendar: if available and short ids are used
951
			if ($calFolderId) {
952
				$shortFolderId = GSync::GetDeviceManager()->GetFolderIdForBackendId($calFolderId);
953
				if ($calFolderId != $shortFolderId) {
954
					$prefix = $shortFolderId . ':';
955
				}
956
			}
957
			$calendarid = $prefix . $calendarid;
958
		}
959
960
		return $calendarid;
961
	}
962
963
	/**
964
	 * Indicates if the backend has a ChangesSink.
965
	 * A sink is an active notification mechanism which does not need polling.
966
	 * Since Zarafa 7.0.5 such a sink is available.
967
	 * The grommunio backend uses this method to initialize the sink with mapi.
968
	 *
969
	 * @return bool
970
	 */
971
	public function HasChangesSink() {
972
		$this->changesSink = @mapi_sink_create();
973
974
		if (!$this->changesSink || mapi_last_hresult()) {
975
			SLog::Write(LOGLEVEL_WARN, sprintf("Grommunio->HasChangesSink(): sink could not be created with  0x%X", mapi_last_hresult()));
976
977
			return false;
978
		}
979
980
		$this->changesSinkHierarchyHash = $this->getHierarchyHash();
981
		SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->HasChangesSink(): created - HierarchyHash: %s", $this->changesSinkHierarchyHash));
982
983
		// advise the main store and also to check if the connection supports it
984
		return $this->adviseStoreToSink($this->defaultstore);
985
	}
986
987
	/**
988
	 * The folder should be considered by the sink.
989
	 * Folders which were not initialized should not result in a notification
990
	 * of IBackend->ChangesSink().
991
	 *
992
	 * @param string $folderid
993
	 *
994
	 * @return bool false if entryid can not be found for that folder
995
	 */
996
	public function ChangesSinkInitialize($folderid) {
997
		SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->ChangesSinkInitialize(): folderid '%s'", $folderid));
998
999
		$entryid = mapi_msgstore_entryidfromsourcekey($this->store, hex2bin($folderid));
1000
		if (!$entryid) {
1001
			return false;
1002
		}
1003
1004
		// add entryid to the monitored folders
1005
		$this->changesSinkFolders[$entryid] = $folderid;
1006
1007
		// advise the current store to the sink
1008
		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

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

1219
		/** @scrutinizer ignore-call */ 
1220
  mapi_table_sort($table, [PR_DISPLAY_NAME => TABLE_SORT_ASCEND]);
Loading history...
1220
1221
		if (mapi_last_hresult()) {
1222
			throw new StatusException(sprintf("Grommunio->GetGALSearchResults(): could not apply restriction: 0x%08X", mapi_last_hresult()), SYNC_SEARCHSTATUS_STORE_TOOCOMPLEX);
1223
		}
1224
1225
		// range for the search results, default symbian range end is 50, wm 99,
1226
		// so we'll use that of nokia
1227
		$rangestart = 0;
1228
		$rangeend = 50;
1229
1230
		if ($searchrange != '0') {
1231
			$pos = strpos($searchrange, '-');
1232
			$rangestart = substr($searchrange, 0, $pos);
1233
			$rangeend = substr($searchrange, $pos + 1);
1234
		}
1235
		$items = [];
1236
1237
		$querycnt = mapi_table_getrowcount($table);
1238
		// do not return more results as requested in range
1239
		$querylimit = (($rangeend + 1) < $querycnt) ? ($rangeend + 1) : $querycnt;
1240
1241
		if ($querycnt > 0) {
1242
			$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_GIVEN_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...
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_ACCOUNT 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_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_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_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_OFFICE_LOCATION 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_SMTP_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1243
		}
1244
1245
		for ($i = 0; $i < $querylimit; ++$i) {
1246
			if (!isset($abentries[$i][PR_SMTP_ADDRESS])) {
1247
				SLog::Write(LOGLEVEL_WARN, sprintf("Grommunio->GetGALSearchResults(): The GAL entry '%s' does not have an email address and will be ignored.", $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...
1248
1249
				continue;
1250
			}
1251
1252
			$items[$i][SYNC_GAL_DISPLAYNAME] = $abentries[$i][PR_DISPLAY_NAME];
1253
1254
			if (strlen(trim((string) $items[$i][SYNC_GAL_DISPLAYNAME])) == 0) {
1255
				$items[$i][SYNC_GAL_DISPLAYNAME] = $abentries[$i][PR_ACCOUNT];
1256
			}
1257
1258
			$items[$i][SYNC_GAL_ALIAS] = $abentries[$i][PR_ACCOUNT];
1259
			// it's not possible not get first and last name of an user
1260
			// from the gab and user functions, so we just set lastname
1261
			// to displayname and leave firstname unset
1262
			// this was changed in Zarafa 6.40, so we try to get first and
1263
			// last name and fall back to the old behaviour if these values are not set
1264
			if (isset($abentries[$i][PR_GIVEN_NAME])) {
1265
				$items[$i][SYNC_GAL_FIRSTNAME] = $abentries[$i][PR_GIVEN_NAME];
1266
			}
1267
			if (isset($abentries[$i][PR_SURNAME])) {
1268
				$items[$i][SYNC_GAL_LASTNAME] = $abentries[$i][PR_SURNAME];
1269
			}
1270
1271
			if (!isset($items[$i][SYNC_GAL_LASTNAME])) {
1272
				$items[$i][SYNC_GAL_LASTNAME] = $items[$i][SYNC_GAL_DISPLAYNAME];
1273
			}
1274
1275
			$items[$i][SYNC_GAL_EMAILADDRESS] = $abentries[$i][PR_SMTP_ADDRESS];
1276
			// check if an user has an office number or it might produce warnings in the log
1277
			if (isset($abentries[$i][PR_BUSINESS_TELEPHONE_NUMBER])) {
1278
				$items[$i][SYNC_GAL_PHONE] = $abentries[$i][PR_BUSINESS_TELEPHONE_NUMBER];
1279
			}
1280
			// check if an user has a mobile number or it might produce warnings in the log
1281
			if (isset($abentries[$i][PR_MOBILE_TELEPHONE_NUMBER])) {
1282
				$items[$i][SYNC_GAL_MOBILEPHONE] = $abentries[$i][PR_MOBILE_TELEPHONE_NUMBER];
1283
			}
1284
			// check if an user has a home number or it might produce warnings in the log
1285
			if (isset($abentries[$i][PR_HOME_TELEPHONE_NUMBER])) {
1286
				$items[$i][SYNC_GAL_HOMEPHONE] = $abentries[$i][PR_HOME_TELEPHONE_NUMBER];
1287
			}
1288
1289
			if (isset($abentries[$i][PR_COMPANY_NAME])) {
1290
				$items[$i][SYNC_GAL_COMPANY] = $abentries[$i][PR_COMPANY_NAME];
1291
			}
1292
1293
			if (isset($abentries[$i][PR_TITLE])) {
1294
				$items[$i][SYNC_GAL_TITLE] = $abentries[$i][PR_TITLE];
1295
			}
1296
1297
			if (isset($abentries[$i][PR_OFFICE_LOCATION])) {
1298
				$items[$i][SYNC_GAL_OFFICE] = $abentries[$i][PR_OFFICE_LOCATION];
1299
			}
1300
1301
			if ($searchpicture !== false && isset($abentries[$i][PR_EMS_AB_THUMBNAIL_PHOTO])) {
1302
				$items[$i][SYNC_GAL_PICTURE] = StringStreamWrapper::Open($abentries[$i][PR_EMS_AB_THUMBNAIL_PHOTO]);
1303
			}
1304
		}
1305
		$nrResults = count($items);
1306
		$items['range'] = ($nrResults > 0) ? $rangestart . '-' . ($nrResults - 1) : '0-0';
1307
		$items['searchtotal'] = $nrResults;
1308
1309
		return $items;
1310
	}
1311
1312
	/**
1313
	 * Searches for the emails on the server.
1314
	 *
1315
	 * @param ContentParameter $cpo
1316
	 *
1317
	 * @return array
1318
	 */
1319
	public function GetMailboxSearchResults($cpo) {
1320
		$items = [];
1321
		$flags = 0;
1322
		$searchFolder = $this->getSearchFolder();
1323
		$searchFolders = [];
1324
1325
		if ($cpo->GetFindSearchId()) {
1326
			SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->GetMailboxSearchResults(): Do FIND"));
1327
			$searchRange = explode('-', (string) $cpo->GetFindRange());
1328
1329
			$searchRestriction = $this->getFindRestriction($cpo);
1330
			$searchFolderId = $cpo->GetFindFolderId();
1331
			$range = $cpo->GetFindRange();
1332
1333
			// if subfolders are required, do a recursive search
1334
			if ($cpo->GetFindDeepTraversal()) {
1335
				$flags |= SEARCH_RECURSIVE;
0 ignored issues
show
Bug introduced by
The constant SEARCH_RECURSIVE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1336
			}
1337
		}
1338
		else {
1339
			SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->GetMailboxSearchResults(): Do SEARCH"));
1340
			$searchRestriction = $this->getSearchRestriction($cpo);
1341
			$searchRange = explode('-', (string) $cpo->GetSearchRange());
1342
			$searchFolderId = $cpo->GetSearchFolderid();
1343
			$range = $cpo->GetSearchRange();
1344
1345
			// if subfolders are required, do a recursive search
1346
			if ($cpo->GetSearchDeepTraversal()) {
1347
				$flags |= SEARCH_RECURSIVE;
1348
			}
1349
		}
1350
1351
		// search only in required folders
1352
		if (!empty($searchFolderId)) {
1353
			$searchFolderEntryId = mapi_msgstore_entryidfromsourcekey($this->store, hex2bin((string) $searchFolderId));
1354
			$searchFolders[] = $searchFolderEntryId;
1355
		}
1356
		// if no folder was required then search in the entire store
1357
		else {
1358
			$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...
1359
			$searchFolders[] = $tmp[PR_IPM_SUBTREE_ENTRYID];
1360
		}
1361
		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

1361
		/** @scrutinizer ignore-call */ 
1362
  mapi_folder_setsearchcriteria($searchFolder, $searchRestriction, $searchFolders, $flags);
Loading history...
1362
1363
		$table = mapi_folder_getcontentstable($searchFolder);
1364
		$searchStart = time();
1365
		// do the search and wait for all the results available
1366
		while (time() - $searchStart < SEARCH_WAIT) {
1367
			$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

1367
			$searchcriteria = /** @scrutinizer ignore-call */ mapi_folder_getsearchcriteria($searchFolder);
Loading history...
1368
			if (($searchcriteria["searchstate"] & SEARCH_REBUILD) == 0) {
0 ignored issues
show
Bug introduced by
The constant SEARCH_REBUILD was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1369
				break;
1370
			} // Search is done
1371
			sleep(1);
1372
		}
1373
1374
		// if the search range is set limit the result to it, otherwise return all found messages
1375
		$rows = (is_array($searchRange) && isset($searchRange[0], $searchRange[1])) ?
1376
			mapi_table_queryrows($table, [PR_ENTRYID, PR_SOURCE_KEY], $searchRange[0], $searchRange[1] - $searchRange[0] + 1) :
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...
1377
			mapi_table_queryrows($table, [PR_ENTRYID, PR_SOURCE_KEY], 0, SEARCH_MAXRESULTS);
1378
1379
		$cnt = count($rows);
1380
		$items['searchtotal'] = $cnt;
1381
		$items["range"] = $range;
1382
		for ($i = 0; $i < $cnt; ++$i) {
1383
			$items[$i]['class'] = 'Email';
1384
			$items[$i]['longid'] = bin2hex((string) $rows[$i][PR_ENTRYID]);
1385
			$items[$i]['serverid'] = bin2hex((string) $rows[$i][PR_SOURCE_KEY]);
1386
		}
1387
1388
		return $items;
1389
	}
1390
1391
	/**
1392
	 * Terminates a search for a given PID.
1393
	 *
1394
	 * @param int $pid
1395
	 *
1396
	 * @return bool
1397
	 */
1398
	public function TerminateSearch($pid) {
1399
		SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->TerminateSearch(): terminating search for pid %d", $pid));
1400
		if (!isset($this->store) || $this->store === false) {
1401
			SLog::Write(LOGLEVEL_WARN, sprintf("Grommunio->TerminateSearch(): The store is not available. It is not possible to remove search folder with pid %d", $pid));
1402
1403
			return false;
1404
		}
1405
1406
		$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...
1407
		if (isset($storeProps[PR_STORE_SUPPORT_MASK]) && (($storeProps[PR_STORE_SUPPORT_MASK] & STORE_SEARCH_OK) != STORE_SEARCH_OK)) {
0 ignored issues
show
Bug introduced by
The constant STORE_SEARCH_OK was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1408
			SLog::Write(LOGLEVEL_WARN, "Grommunio->TerminateSearch(): Store doesn't support search folders. Public store doesn't have FINDER_ROOT folder");
1409
1410
			return false;
1411
		}
1412
1413
		if (!isset($storeProps[PR_FINDER_ENTRYID])) {
1414
			SLog::Write(LOGLEVEL_WARN, "Grommunio->TerminateSearch(): Unable to open search folder - finder entryid not found");
1415
1416
			return false;
1417
		}
1418
		$finderfolder = mapi_msgstore_openentry($this->store, $storeProps[PR_FINDER_ENTRYID]);
1419
		if (mapi_last_hresult() != NOERROR) {
0 ignored issues
show
Bug introduced by
The constant NOERROR was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1420
			SLog::Write(LOGLEVEL_WARN, sprintf("Grommunio->TerminateSearch(): Unable to open search folder (0x%X)", mapi_last_hresult()));
1421
1422
			return false;
1423
		}
1424
1425
		// do first folder deletion with the requested PID
1426
		return $this->terminateSearchDeleteFolders($finderfolder, $pid, 0);
1427
	}
1428
1429
	/**
1430
	 * Deletes obsolete Search-Folder with given parameters.
1431
	 *
1432
	 * @param resource $finderfolder
1433
	 * @param int      $pid
1434
	 * @param int      $since
1435
	 */
1436
	private function terminateSearchDeleteFolders($finderfolder, $pid, $since) {
1437
		$hierarchytable = mapi_folder_gethierarchytable($finderfolder);
1438
1439
		$restriction = [
1440
			RES_CONTENT,
0 ignored issues
show
Bug introduced by
The constant RES_CONTENT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1441
			[
1442
				FUZZYLEVEL => FL_PREFIX,
0 ignored issues
show
Bug introduced by
The constant FUZZYLEVEL was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant FL_PREFIX was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1443
				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...
Bug introduced by
The constant ULPROPTAG was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1444
				VALUE => [PR_DISPLAY_NAME => "grommunio-sync Search Folder " . $pid],
0 ignored issues
show
Bug introduced by
The constant VALUE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1445
			],
1446
		];
1447
1448
		if ($since > 0) {
1449
			$restriction = [
1450
				RES_AND,
0 ignored issues
show
Bug introduced by
The constant RES_AND was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1451
				$restriction,
1452
				[RES_PROPERTY,
0 ignored issues
show
Bug introduced by
The constant RES_PROPERTY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1453
					[
1454
						RELOP => RELOP_LE,
0 ignored issues
show
Bug introduced by
The constant RELOP_LE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant RELOP was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1455
						ULPROPTAG => 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...
1456
						VALUE => [PR_LAST_MODIFICATION_TIME => $since],
1457
					],
1458
				],
1459
			];
1460
		}
1461
		mapi_table_restrict($hierarchytable, $restriction, TBL_BATCH);
0 ignored issues
show
Bug introduced by
The constant TBL_BATCH was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1462
1463
		$folders = mapi_table_queryallrows($hierarchytable, [PR_ENTRYID, PR_DISPLAY_NAME, PR_LAST_MODIFICATION_TIME]);
1464
1465
		$last = 0;
1466
		foreach ($folders as $folder) {
1467
			if ($folder[PR_LAST_MODIFICATION_TIME] && $last < $folder[PR_LAST_MODIFICATION_TIME]) {
1468
				$last = $folder[PR_LAST_MODIFICATION_TIME];
1469
			}
1470
			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

1470
			/** @scrutinizer ignore-call */ 
1471
   mapi_folder_deletefolder($finderfolder, $folder[PR_ENTRYID]);
Loading history...
1471
		}
1472
1473
		// call recursivly once to delete older search folders than the one we had an PID to search for
1474
		if ($pid !== "" && $last > 0) {
1475
			$this->terminateSearchDeleteFolders($finderfolder, "", $last);
0 ignored issues
show
Bug introduced by
'' of type string is incompatible with the type integer expected by parameter $pid of Grommunio::terminateSearchDeleteFolders(). ( Ignorable by Annotation )

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

1475
			$this->terminateSearchDeleteFolders($finderfolder, /** @scrutinizer ignore-type */ "", $last);
Loading history...
1476
		}
1477
1478
		return true;
1479
	}
1480
1481
	/**
1482
	 * Disconnects from the current search provider.
1483
	 *
1484
	 * @return bool
1485
	 */
1486
	public function Disconnect() {
1487
		return true;
1488
	}
1489
1490
	/**
1491
	 * Returns the MAPI store resource for a folderid
1492
	 * This is not part of IBackend but necessary for the ImportChangesICS->MoveMessage() operation if
1493
	 * the destination folder is not in the default store
1494
	 * Note: The current backend store might be changed as IBackend->Setup() is executed.
1495
	 *
1496
	 * @param string $store    target store, could contain a "domain\user" value - if empty default store is returned
1497
	 * @param string $folderid
1498
	 *
1499
	 * @return bool|resource
1500
	 */
1501
	public function GetMAPIStoreForFolderId($store, $folderid) {
1502
		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...
1503
			SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->GetMAPIStoreForFolderId('%s', '%s'): no store specified, returning default store", $store, $folderid));
1504
1505
			return $this->defaultstore;
1506
		}
1507
1508
		// setup the correct store
1509
		if ($this->Setup($store, false, $folderid)) {
1510
			return $this->store;
1511
		}
1512
1513
		SLog::Write(LOGLEVEL_WARN, sprintf("Grommunio->GetMAPIStoreForFolderId('%s', '%s'): store is not available", $store, $folderid));
1514
1515
		return false;
1516
	}
1517
1518
	/**
1519
	 * Returns the email address and the display name of the user. Used by autodiscover.
1520
	 *
1521
	 * @param string $username The username
1522
	 *
1523
	 * @return array
1524
	 */
1525
	public function GetUserDetails($username) {
1526
		SLog::Write(LOGLEVEL_WBXML, sprintf("Grommunio->GetUserDetails for '%s'.", $username));
1527
		$zarafauserinfo = @nsp_getuserinfo($username);
1528
		$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...
1529
		$userDetails['fullname'] = (isset($zarafauserinfo['fullname']) && $zarafauserinfo['fullname']) ? $zarafauserinfo['fullname'] : false;
1530
1531
		return $userDetails;
1532
	}
1533
1534
	/**
1535
	 * Returns the username of the currently active user.
1536
	 *
1537
	 * @return string
1538
	 */
1539
	public function GetCurrentUsername() {
1540
		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...
1541
	}
1542
1543
	/**
1544
	 * Returns the impersonated user name.
1545
	 *
1546
	 * @return string or false if no user is impersonated
1547
	 */
1548
	public function GetImpersonatedUser() {
1549
		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...
1550
	}
1551
1552
	/**
1553
	 * Returns the authenticated user name.
1554
	 *
1555
	 * @return string
1556
	 */
1557
	public function GetMainUser() {
1558
		return $this->mainUser;
1559
	}
1560
1561
	/**
1562
	 * Indicates if the Backend supports folder statistics.
1563
	 *
1564
	 * @return bool
1565
	 */
1566
	public function HasFolderStats() {
1567
		return true;
1568
	}
1569
1570
	/**
1571
	 * Returns a status indication of the folder.
1572
	 * If there are changes in the folder, the returned value must change.
1573
	 * The returned values are compared with '===' to determine if a folder needs synchronization or not.
1574
	 *
1575
	 * @param string $store    the store where the folder resides
1576
	 * @param string $folderid the folder id
1577
	 *
1578
	 * @return string
1579
	 */
1580
	public function GetFolderStat($store, $folderid) {
1581
		[$user, $domain] = Utils::SplitDomainUser($store);
1582
		if ($user === false) {
0 ignored issues
show
introduced by
The condition $user === false is always false.
Loading history...
1583
			$user = $this->mainUser;
1584
			if ($this->impersonateUser) {
1585
				$user = $this->impersonateUser;
1586
			}
1587
		}
1588
1589
		if (!isset($this->folderStatCache[$user])) {
1590
			$this->folderStatCache[$user] = [];
1591
		}
1592
1593
		// if there is nothing in the cache for a store, load the data for all folders of it
1594
		if (empty($this->folderStatCache[$user])) {
1595
			// get the store
1596
			$userstore = $this->openMessageStore($user);
1597
			$rootfolder = mapi_msgstore_openentry($userstore);
1598
			$hierarchy = mapi_folder_gethierarchytable($rootfolder, CONVENIENT_DEPTH);
0 ignored issues
show
Bug introduced by
The constant CONVENIENT_DEPTH was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1599
			$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_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_SOURCE_KEY 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...
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...
1600
1601
			if (count($rows) == 0) {
1602
				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));
1603
			}
1604
1605
			foreach ($rows as $folder) {
1606
				$commit_time = $folder[PR_LOCAL_COMMIT_TIME_MAX] ?? "0000000000";
1607
				$content_count = $folder[PR_CONTENT_COUNT] ?? -1;
1608
				$content_unread = $folder[PR_CONTENT_UNREAD] ?? -1;
1609
				$content_deleted = $folder[PR_DELETED_MSG_COUNT] ?? -1;
1610
1611
				$this->folderStatCache[$user][bin2hex((string) $folder[PR_SOURCE_KEY])] = $commit_time . "/" . $content_count . "/" . $content_unread . "/" . $content_deleted;
1612
			}
1613
			SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->GetFolderStat() fetched status information of %d folders for store '%s'", count($this->folderStatCache[$user]), $user));
1614
		}
1615
1616
		// 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.
1617
		return $this->folderStatCache[$user][$folderid] ?? gmdate("Y-m-d-H");
1618
	}
1619
1620
	/**
1621
	 * Get a list of all folders in the public store that have PR_SYNC_TO_MOBILE set.
1622
	 *
1623
	 * @return array
1624
	 */
1625
	public function GetPublicSyncEnabledFolders() {
1626
		$store = $this->openMessageStore("SYSTEM");
1627
		$pubStore = mapi_msgstore_openentry($store, null);
1628
		$hierarchyTable = mapi_folder_gethierarchytable($pubStore, CONVENIENT_DEPTH);
0 ignored issues
show
Bug introduced by
The constant CONVENIENT_DEPTH was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1629
1630
		$properties = getPropIdsFromStrings($store, ["synctomobile" => "PT_BOOLEAN:PSETID_GROMOX:synctomobile"]);
0 ignored issues
show
Bug introduced by
The function getPropIdsFromStrings 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

1630
		$properties = /** @scrutinizer ignore-call */ getPropIdsFromStrings($store, ["synctomobile" => "PT_BOOLEAN:PSETID_GROMOX:synctomobile"]);
Loading history...
1631
1632
		$restriction = [RES_AND, [
0 ignored issues
show
Bug introduced by
The constant RES_AND was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1633
			[RES_EXIST,
0 ignored issues
show
Bug introduced by
The constant RES_EXIST was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1634
				[ULPROPTAG => $properties['synctomobile']],
0 ignored issues
show
Bug introduced by
The constant ULPROPTAG was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1635
			],
1636
			[RES_PROPERTY,
0 ignored issues
show
Bug introduced by
The constant RES_PROPERTY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1637
				[
1638
					RELOP => RELOP_EQ,
0 ignored issues
show
Bug introduced by
The constant RELOP was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant RELOP_EQ was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1639
					ULPROPTAG => $properties['synctomobile'],
1640
					VALUE => [$properties['synctomobile'] => true],
0 ignored issues
show
Bug introduced by
The constant VALUE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1641
				],
1642
			],
1643
		]];
1644
		mapi_table_restrict($hierarchyTable, $restriction, TBL_BATCH);
0 ignored issues
show
Bug introduced by
The constant TBL_BATCH was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1645
		$rows = mapi_table_queryallrows($hierarchyTable, [PR_DISPLAY_NAME, PR_CONTAINER_CLASS, PR_SOURCE_KEY]);
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_CONTAINER_CLASS 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...
1646
		$f = [];
1647
		foreach ($rows as $row) {
1648
			$folderid = bin2hex((string) $row[PR_SOURCE_KEY]);
1649
			$f[$folderid] = [
1650
				'store' => 'SYSTEM',
1651
				'flags' => DeviceManager::FLD_FLAGS_NONE,
1652
				'folderid' => $folderid,
1653
				'parentid' => '0',
1654
				'name' => $row[PR_DISPLAY_NAME],
1655
				'type' => MAPIUtils::GetFolderTypeFromContainerClass($row[PR_CONTAINER_CLASS]),
1656
			];
1657
		}
1658
1659
		return $f;
1660
	}
1661
1662
	/*----------------------------------------------------------------------------------------------------------
1663
	 * Implementation of the IStateMachine interface
1664
	 */
1665
1666
	/**
1667
	 * Gets a hash value indicating the latest dataset of the named
1668
	 * state with a specified key and counter.
1669
	 * If the state is changed between two calls of this method
1670
	 * the returned hash should be different.
1671
	 *
1672
	 * @param string $devid   the device id
1673
	 * @param string $type    the state type
1674
	 * @param string $key     (opt)
1675
	 * @param string $counter (opt)
1676
	 *
1677
	 * @return string
1678
	 *
1679
	 * @throws StateNotFoundException
1680
	 */
1681
	public function GetStateHash($devid, $type, $key = false, $counter = false) {
1682
		try {
1683
			$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

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

1683
			$stateMessage = $this->getStateMessage($devid, $type, /** @scrutinizer ignore-type */ $key, $counter);
Loading history...
1684
			$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...
1685
			if (isset($stateMessageProps[PR_LAST_MODIFICATION_TIME])) {
1686
				return $stateMessageProps[PR_LAST_MODIFICATION_TIME];
1687
			}
1688
		}
1689
		catch (StateNotFoundException) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1690
		}
1691
1692
		return "0";
1693
	}
1694
1695
	/**
1696
	 * Gets a state for a specified key and counter.
1697
	 * This method should call IStateMachine->CleanStates()
1698
	 * to remove older states (same key, previous counters).
1699
	 *
1700
	 * @param string $devid       the device id
1701
	 * @param string $type        the state type
1702
	 * @param string $key         (opt)
1703
	 * @param string $counter     (opt)
1704
	 * @param string $cleanstates (opt)
1705
	 *
1706
	 * @return mixed
1707
	 *
1708
	 * @throws StateNotFoundException, StateInvalidException, UnavailableException
1709
	 */
1710
	public function GetState($devid, $type, $key = false, $counter = false, $cleanstates = true) {
1711
		if ($counter && $cleanstates) {
1712
			$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

1712
			$this->CleanStates($devid, $type, /** @scrutinizer ignore-type */ $key, $counter);
Loading history...
1713
			// also clean failsafe state for previous counter
1714
			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...
1715
				$this->CleanStates($devid, $type, IStateMachine::FAILSAFE, $counter);
1716
			}
1717
		}
1718
		$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

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

1718
		$stateMessage = $this->getStateMessage($devid, $type, $key, /** @scrutinizer ignore-type */ $counter);
Loading history...
1719
		$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...
1720
1721
		if ($state && $state[0] === '{') {
1722
			$jsonDec = json_decode($state);
1723
			if (isset($jsonDec->gsSyncStateClass)) {
1724
				$gsObj = new $jsonDec->gsSyncStateClass();
1725
				$gsObj->jsonDeserialize($jsonDec);
1726
				$gsObj->postUnserialize();
1727
			}
1728
		}
1729
1730
		return isset($gsObj) && is_object($gsObj) ? $gsObj : $state;
1731
	}
1732
1733
	/**
1734
	 * Writes ta state to for a key and counter.
1735
	 *
1736
	 * @param mixed  $state
1737
	 * @param string $devid   the device id
1738
	 * @param string $type    the state type
1739
	 * @param string $key     (opt)
1740
	 * @param int    $counter (opt)
1741
	 *
1742
	 * @return bool
1743
	 *
1744
	 * @throws StateInvalidException, UnavailableException
1745
	 */
1746
	public function SetState($state, $devid, $type, $key = false, $counter = false) {
1747
		return $this->setStateMessage($state, $devid, $type, $key, $counter);
0 ignored issues
show
Bug introduced by
It seems like $counter can also be of type false; however, parameter $counter of Grommunio::setStateMessage() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

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

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

1747
		return $this->setStateMessage($state, $devid, $type, /** @scrutinizer ignore-type */ $key, $counter);
Loading history...
1748
	}
1749
1750
	/**
1751
	 * Cleans up all older states.
1752
	 * If called with a $counter, all states previous state counter can be removed.
1753
	 * If additionally the $thisCounterOnly flag is true, only that specific counter will be removed.
1754
	 * If called without $counter, all keys (independently from the counter) can be removed.
1755
	 *
1756
	 * @param string $devid           the device id
1757
	 * @param string $type            the state type
1758
	 * @param string $key
1759
	 * @param string $counter         (opt)
1760
	 * @param string $thisCounterOnly (opt) if provided, the exact counter only will be removed
1761
	 *
1762
	 * @throws StateInvalidException
1763
	 */
1764
	public function CleanStates($devid, $type, $key, $counter = false, $thisCounterOnly = false) {
1765
		if (!$this->stateFolder) {
1766
			$this->getStateFolder($devid);
1767
			if (!$this->stateFolder) {
1768
				throw new StateNotFoundException(sprintf(
1769
					"Grommunio->getStateMessage(): Could not locate the state folder for device '%s'",
1770
					$devid
1771
				));
1772
			}
1773
		}
1774
		if ($type == IStateMachine::FAILSAFE && $counter && $counter > 1) {
1775
			--$counter;
1776
		}
1777
		$messageName = rtrim((($key !== false) ? $key . "-" : "") . (($type !== "") ? $type : ""), "-");
0 ignored issues
show
introduced by
The condition $key !== false is always true.
Loading history...
1778
		$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

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

1778
		$restriction = $this->getStateMessageRestriction($messageName, /** @scrutinizer ignore-type */ $counter, $thisCounterOnly);
Loading history...
1779
		$stateFolderContents = mapi_folder_getcontentstable($this->stateFolder, MAPI_ASSOCIATED);
0 ignored issues
show
Bug introduced by
The constant MAPI_ASSOCIATED was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1780
		if ($stateFolderContents) {
1781
			mapi_table_restrict($stateFolderContents, $restriction);
1782
			$rowCnt = mapi_table_getrowcount($stateFolderContents);
1783
			SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->CleanStates(): Found %d states to clean (%s) %s", $rowCnt, $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

1783
			SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->CleanStates(): Found %d states to clean (%s) %s", $rowCnt, $messageName, Utils::PrintAsString(/** @scrutinizer ignore-type */ $counter)));
Loading history...
1784
			if ($rowCnt > 0) {
1785
				$rows = mapi_table_queryallrows($stateFolderContents, [PR_ENTRYID]);
1786
				$entryids = [];
1787
				foreach ($rows as $row) {
1788
					$entryids[] = $row[PR_ENTRYID];
1789
				}
1790
				mapi_folder_deletemessages($this->stateFolder, $entryids, DELETE_HARD_DELETE);
0 ignored issues
show
Bug introduced by
The constant DELETE_HARD_DELETE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1791
			}
1792
		}
1793
	}
1794
1795
	/**
1796
	 * Links a user to a device.
1797
	 *
1798
	 * @param string $username
1799
	 * @param string $devid
1800
	 *
1801
	 * @return bool indicating if the user was added or not (existed already)
1802
	 */
1803
	public function LinkUserDevice($username, $devid) {
1804
		$device = [$devid => time()];
1805
		$this->setDeviceUserData($this->type, $device, $username, -1, $subkey = -1, $doCas = "merge");
1806
1807
		return false;
1808
	}
1809
1810
	/**
1811
	 * Unlinks a device from a user.
1812
	 *
1813
	 * @param string $username
1814
	 * @param string $devid
1815
	 *
1816
	 * @return bool
1817
	 */
1818
	public function UnLinkUserDevice($username, $devid) {
1819
		// TODO: Implement
1820
		return false;
1821
	}
1822
1823
	/**
1824
	 * Returns the current version of the state files
1825
	 * grommunio:  This is not relevant atm. IStateMachine::STATEVERSION_02 will match GSync::GetLatestStateVersion().
1826
	 *          If it might be required to update states in the future, this could be implemented on a store level,
1827
	 *          where states are then migrated "on-the-fly"
1828
	 *          or
1829
	 *          in a global settings where all states in all stores are migrated once.
1830
	 *
1831
	 * @return int
1832
	 */
1833
	public function GetStateVersion() {
1834
		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...
1835
	}
1836
1837
	/**
1838
	 * Sets the current version of the state files.
1839
	 *
1840
	 * @param int $version the new supported version
1841
	 *
1842
	 * @return bool
1843
	 */
1844
	public function SetStateVersion($version) {
1845
		return true;
1846
	}
1847
1848
	/**
1849
	 * Returns MAPIFolder object which contains the state information.
1850
	 * Creates this folder if it is not available yet.
1851
	 *
1852
	 * @param string $devid the device id
1853
	 *
1854
	 * @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...
1855
	 */
1856
	private function getStateFolder($devid) {
1857
		// Options request doesn't send device id
1858
		if (strlen($devid) == 0) {
1859
			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...
1860
		}
1861
		// Try to get the state folder id from redis
1862
		if (!$this->stateFolder) {
1863
			$folderentryid = $this->getDeviceUserData($this->userDeviceData, $devid, $this->mainUser, "statefolder");
1864
			if ($folderentryid) {
1865
				$this->stateFolder = mapi_msgstore_openentry($this->defaultstore, hex2bin((string) $folderentryid));
1866
			}
1867
		}
1868
1869
		// fallback code
1870
		if (!$this->stateFolder && $this->defaultstore) {
1871
			SLog::Write(LOGLEVEL_INFO, sprintf("Grommunio->getStateFolder(): state folder not set. Use fallback"));
1872
			$rootfolder = mapi_msgstore_openentry($this->defaultstore);
1873
			$hierarchy = mapi_folder_gethierarchytable($rootfolder, CONVENIENT_DEPTH);
0 ignored issues
show
Bug introduced by
The constant CONVENIENT_DEPTH was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1874
			$restriction = $this->getStateFolderRestriction($devid);
1875
			// restrict the hierarchy to the grommunio-sync search folder only
1876
			mapi_table_restrict($hierarchy, $restriction);
1877
			$rowCnt = mapi_table_getrowcount($hierarchy);
1878
			SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->getStateFolder(): found %d device state folders", $rowCnt));
1879
			if ($rowCnt == 1) {
1880
				$hierarchyRows = mapi_table_queryrows($hierarchy, [PR_ENTRYID], 0, 1);
1881
				$this->stateFolder = mapi_msgstore_openentry($this->defaultstore, $hierarchyRows[0][PR_ENTRYID]);
1882
				SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->getStateFolder(): %s", bin2hex((string) $hierarchyRows[0][PR_ENTRYID])));
1883
				// put found id in redis
1884
				if ($devid) {
1885
					$this->setDeviceUserData($this->userDeviceData, bin2hex((string) $hierarchyRows[0][PR_ENTRYID]), $devid, $this->mainUser, "statefolder");
1886
				}
1887
			}
1888
			elseif ($rowCnt == 0) {
1889
				// legacy code: create the hidden state folder and the device subfolder
1890
				// this should happen when the user configures the device (autodiscover or first sync if no autodiscover)
1891
1892
				$hierarchy = mapi_folder_gethierarchytable($rootfolder, CONVENIENT_DEPTH);
1893
				$restriction = $this->getStateFolderRestriction(STORE_STATE_FOLDER);
1894
				mapi_table_restrict($hierarchy, $restriction);
1895
				$rowCnt = mapi_table_getrowcount($hierarchy);
1896
				SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->getStateFolder(): found %d store state folders", $rowCnt));
1897
				if ($rowCnt == 1) {
1898
					$hierarchyRows = mapi_table_queryrows($hierarchy, [PR_ENTRYID], 0, 1);
1899
					$stateFolder = mapi_msgstore_openentry($this->defaultstore, $hierarchyRows[0][PR_ENTRYID]);
1900
				}
1901
				elseif ($rowCnt == 0) {
1902
					$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

1902
					$stateFolder = /** @scrutinizer ignore-call */ mapi_folder_createfolder($rootfolder, STORE_STATE_FOLDER, "");
Loading history...
1903
					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...
1904
				}
1905
1906
				// TODO: handle this
1907
1908
				if (isset($stateFolder) && $stateFolder) {
1909
					$devStateFolder = mapi_folder_createfolder($stateFolder, $devid, "");
1910
					$devStateFolderProps = mapi_getprops($devStateFolder);
1911
					$this->stateFolder = mapi_msgstore_openentry($this->defaultstore, $devStateFolderProps[PR_ENTRYID]);
1912
					mapi_setprops($this->stateFolder, [PR_ATTR_HIDDEN => true]);
1913
					// we don't cache the entryid in redis, because this will happen on the next request anyway
1914
				}
1915
1916
				// TODO: unable to create state folder - throw exception
1917
			}
1918
1919
			// This case is rather unlikely that there would be several
1920
			// hidden folders having PR_DISPLAY_NAME the same as device id.
1921
1922
			// TODO: get the hierarchy table again, get entry id of STORE_STATE_FOLDER
1923
			// and compare it to the parent id of those folders.
1924
		}
1925
1926
		return $this->stateFolder;
1927
	}
1928
1929
	/**
1930
	 * Returns the associated MAPIMessage which contains the state information.
1931
	 *
1932
	 * @param string $devid            the device id
1933
	 * @param string $type             the state type
1934
	 * @param string $key              (opt)
1935
	 * @param string $counter          state counter
1936
	 * @param mixed  $logStateNotFound
1937
	 *
1938
	 * @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...
1939
	 *
1940
	 * @throws StateNotFoundException
1941
	 */
1942
	private function getStateMessage($devid, $type, $key, $counter, $logStateNotFound = true) {
1943
		if (!$this->stateFolder) {
1944
			$this->getStateFolder(Request::GetDeviceID());
1945
			if (!$this->stateFolder) {
1946
				throw new StateNotFoundException(sprintf(
1947
					"Grommunio->getStateMessage(): Could not locate the state folder for device '%s'",
1948
					$devid
1949
				));
1950
			}
1951
		}
1952
		$messageName = rtrim((($key !== false) ? $key . "-" : "") . (($type !== "") ? $type : ""), "-");
0 ignored issues
show
introduced by
The condition $key !== false is always true.
Loading history...
1953
		$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

1953
		$restriction = $this->getStateMessageRestriction($messageName, $counter, /** @scrutinizer ignore-type */ true);
Loading history...
1954
		$stateFolderContents = mapi_folder_getcontentstable($this->stateFolder, MAPI_ASSOCIATED);
0 ignored issues
show
Bug introduced by
The constant MAPI_ASSOCIATED was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1955
		if ($stateFolderContents) {
1956
			mapi_table_restrict($stateFolderContents, $restriction);
1957
			$rowCnt = mapi_table_getrowcount($stateFolderContents);
1958
			if ($rowCnt == 1) {
1959
				$stateFolderRows = mapi_table_queryrows($stateFolderContents, [PR_ENTRYID], 0, 1);
1960
1961
				return mapi_msgstore_openentry($this->defaultstore, $stateFolderRows[0][PR_ENTRYID]);
1962
			}
1963
1964
			if ($rowCnt > 1) {
1965
				SLog::Write(LOGLEVEL_WARN, sprintf("Grommunio->getStateMessage(): Cleaning up duplicated state messages '%s' (%d)", $messageName, $rowCnt));
1966
				$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

1966
				$this->CleanStates($devid, $type, $key, $counter, /** @scrutinizer ignore-type */ true);
Loading history...
1967
			}
1968
		}
1969
1970
		throw new StateNotFoundException(
1971
			sprintf(
1972
				"Grommunio->getStateMessage(): Could not locate the state message '%s' (counter: %s)",
1973
				$messageName,
1974
				Utils::PrintAsString($counter)
1975
			),
1976
			0,
1977
			null,
1978
			$logStateNotFound ? false : LOGLEVEL_WBXMLSTACK
1979
		);
1980
	}
1981
1982
	/**
1983
	 * Writes ta state to for a key and counter.
1984
	 *
1985
	 * @param mixed  $state
1986
	 * @param string $devid   the device id
1987
	 * @param string $type    the state type
1988
	 * @param string $key     (opt)
1989
	 * @param int    $counter (opt)
1990
	 *
1991
	 * @return bool
1992
	 *
1993
	 * @throws StateInvalidException, UnavailableException
1994
	 */
1995
	private function setStateMessage($state, $devid, $type, $key = false, $counter = false) {
1996
		if (!$this->stateFolder) {
1997
			throw new StateNotFoundException(sprintf("Grommunio->setStateMessage(): Could not locate the state folder for device '%s'", $devid));
1998
		}
1999
2000
		try {
2001
			$stateMessage = $this->getStateMessage($devid, $type, $key, $counter, false);
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

2001
			$stateMessage = $this->getStateMessage($devid, $type, $key, /** @scrutinizer ignore-type */ $counter, false);
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

2001
			$stateMessage = $this->getStateMessage($devid, $type, /** @scrutinizer ignore-type */ $key, $counter, false);
Loading history...
2002
		}
2003
		catch (StateNotFoundException) {
2004
			// if message is not available, try to create a new one
2005
			$stateMessage = mapi_folder_createmessage($this->stateFolder, MAPI_ASSOCIATED);
0 ignored issues
show
Bug introduced by
The constant MAPI_ASSOCIATED was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2006
			if (mapi_last_hresult()) {
2007
				SLog::Write(LOGLEVEL_ERROR, sprintf("Grommunio->setStateMessage(): Could not create new state message, mapi_folder_createmessage: 0x%08X", mapi_last_hresult()));
2008
2009
				return false;
2010
			}
2011
2012
			$messageName = rtrim((($key !== false) ? $key . "-" : "") . (($type !== "") ? $type : ""), "-");
2013
			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

2013
			SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->setStateMessage(): creating new state message '%s' (counter: %s)", $messageName, Utils::PrintAsString(/** @scrutinizer ignore-type */ $counter)));
Loading history...
2014
			mapi_setprops($stateMessage, [PR_DISPLAY_NAME => $messageName, PR_MESSAGE_CLASS => 'IPM.Note.GrommunioState']);
0 ignored issues
show
Bug introduced by
The constant PR_MESSAGE_CLASS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2015
		}
2016
		if ($stateMessage) {
2017
			$jsonEncodedState = is_object($state) || is_array($state) ? json_encode($state, JSON_INVALID_UTF8_IGNORE | JSON_UNESCAPED_UNICODE) : $state;
2018
2019
			$encodedState = base64_encode((string) $jsonEncodedState);
2020
			$encodedStateLength = strlen($encodedState);
2021
			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...
2022
			$stream = mapi_openproperty($stateMessage, PR_BODY, IID_IStream, STGM_DIRECT, MAPI_CREATE | MAPI_MODIFY);
0 ignored issues
show
Bug introduced by
The constant MAPI_MODIFY 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 MAPI_CREATE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant STGM_DIRECT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant IID_IStream was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2023
			mapi_stream_setsize($stream, $encodedStateLength);
2024
			mapi_stream_write($stream, $encodedState);
2025
			mapi_stream_commit($stream);
2026
			mapi_savechanges($stateMessage);
2027
2028
			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...
2029
		}
2030
2031
		return false;
2032
	}
2033
2034
	/**
2035
	 * Returns the restriction for the state folder name.
2036
	 *
2037
	 * @param string $folderName the state folder name
2038
	 *
2039
	 * @return array
2040
	 */
2041
	private function getStateFolderRestriction($folderName) {
2042
		return [RES_AND, [
0 ignored issues
show
Bug introduced by
The constant RES_AND was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2043
			[RES_PROPERTY,
0 ignored issues
show
Bug introduced by
The constant RES_PROPERTY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2044
				[RELOP => RELOP_EQ,
0 ignored issues
show
Bug introduced by
The constant RELOP was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant RELOP_EQ was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2045
					ULPROPTAG => PR_DISPLAY_NAME,
0 ignored issues
show
Bug introduced by
The constant ULPROPTAG 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...
2046
					VALUE => $folderName,
0 ignored issues
show
Bug introduced by
The constant VALUE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2047
				],
2048
			],
2049
			[RES_PROPERTY,
2050
				[RELOP => RELOP_EQ,
2051
					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...
2052
					VALUE => true,
2053
				],
2054
			],
2055
		]];
2056
	}
2057
2058
	/**
2059
	 * Returns the restriction for the associated message in the state folder.
2060
	 *
2061
	 * @param string $messageName     the message name
2062
	 * @param string $counter         counter
2063
	 * @param string $thisCounterOnly (opt) if provided, restrict to the exact counter
2064
	 *
2065
	 * @return array
2066
	 */
2067
	private function getStateMessageRestriction($messageName, $counter, $thisCounterOnly = false) {
2068
		return [RES_AND, [
0 ignored issues
show
Bug introduced by
The constant RES_AND was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2069
			[RES_PROPERTY,
0 ignored issues
show
Bug introduced by
The constant RES_PROPERTY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2070
				[RELOP => RELOP_EQ,
0 ignored issues
show
Bug introduced by
The constant RELOP was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant RELOP_EQ was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2071
					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...
Bug introduced by
The constant ULPROPTAG was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2072
					VALUE => $messageName,
0 ignored issues
show
Bug introduced by
The constant VALUE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2073
				],
2074
			],
2075
			[RES_PROPERTY,
2076
				[RELOP => RELOP_EQ,
2077
					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...
2078
					VALUE => 'IPM.Note.GrommunioState',
2079
				],
2080
			],
2081
			[RES_PROPERTY,
2082
				[RELOP => $thisCounterOnly ? RELOP_EQ : RELOP_LT,
0 ignored issues
show
Bug introduced by
The constant RELOP_LT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2083
					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...
2084
					VALUE => $counter,
2085
				],
2086
			],
2087
		]];
2088
	}
2089
2090
	/*----------------------------------------------------------------------------------------------------------
2091
	 * Private methods
2092
	 */
2093
2094
	/**
2095
	 * Checks if all stores check in the changes sink are still available.
2096
	 *
2097
	 * @return bool
2098
	 */
2099
	private function checkAdvisedSinkStores() {
2100
		foreach ($this->changesSinkStores as $store) {
2101
			$error_state = false;
2102
			$stateFolderCount = 0;
2103
2104
			try {
2105
				$stateFolderContents = @mapi_folder_getcontentstable($this->stateFolder, MAPI_ASSOCIATED);
0 ignored issues
show
Bug introduced by
The constant MAPI_ASSOCIATED was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2106
				if ($stateFolderContents) {
2107
					$stateFolderCount = mapi_table_getrowcount($stateFolderContents);
2108
				}
2109
				else {
2110
					$error_state = true;
2111
				}
2112
			}
2113
			catch (Exception|TypeError) {
2114
				$error_state = true;
2115
			}
2116
			if ($error_state || (isset($stateFolderContents) && $stateFolderContents === false) || $stateFolderCount == 0 || mapi_last_hresult()) {
2117
				SLog::Write(LOGLEVEL_INFO, sprintf("Grommunio->checkAdvisedSinkStores(): could not access advised store store '%s' - failed with code 0x%08X", $store, mapi_last_hresult()));
2118
2119
				return false;
2120
			}
2121
		}
2122
2123
		return true;
2124
	}
2125
2126
	/**
2127
	 * Returns a hash representing changes in the hierarchy of the main user.
2128
	 * It changes if a folder is added, renamed or deleted.
2129
	 *
2130
	 * @return string
2131
	 */
2132
	private function getHierarchyHash() {
2133
		$storeProps = mapi_getprops($this->defaultstore, [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...
2134
		$rootfolder = mapi_msgstore_openentry($this->defaultstore, $storeProps[PR_IPM_SUBTREE_ENTRYID]);
2135
		$hierarchy = mapi_folder_gethierarchytable($rootfolder, CONVENIENT_DEPTH);
0 ignored issues
show
Bug introduced by
The constant CONVENIENT_DEPTH was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2136
2137
		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...
2138
	}
2139
2140
	/**
2141
	 * Advises a store to the changes sink.
2142
	 *
2143
	 * @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...
2144
	 *
2145
	 * @return bool
2146
	 */
2147
	private function adviseStoreToSink($store) {
2148
		// check if we already advised the store
2149
		if (!in_array($store, $this->changesSinkStores)) {
2150
			mapi_msgstore_advise($store, null, fnevNewMail | fnevObjectModified | fnevObjectCreated | fnevObjectMoved | fnevObjectDeleted, $this->changesSink);
0 ignored issues
show
Bug introduced by
The constant fnevObjectDeleted was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
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

2150
			/** @scrutinizer ignore-call */ 
2151
   mapi_msgstore_advise($store, null, fnevNewMail | fnevObjectModified | fnevObjectCreated | fnevObjectMoved | fnevObjectDeleted, $this->changesSink);
Loading history...
Bug introduced by
The constant fnevObjectMoved was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant fnevObjectCreated was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant fnevObjectModified was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant fnevNewMail was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2151
2152
			if (mapi_last_hresult()) {
2153
				SLog::Write(LOGLEVEL_WARN, sprintf("Grommunio->adviseStoreToSink(): failed to advised store '%s' with code 0x%08X. Polling will be performed.", $store, mapi_last_hresult()));
2154
2155
				return false;
2156
			}
2157
2158
			SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->adviseStoreToSink(): advised store '%s'", $store));
2159
			$this->changesSinkStores[] = $store;
2160
		}
2161
2162
		return true;
2163
	}
2164
2165
	/**
2166
	 * Open the store marked with PR_DEFAULT_STORE = TRUE
2167
	 * if $return_public is set, the public store is opened.
2168
	 *
2169
	 * @param string $user User which store should be opened
2170
	 *
2171
	 * @return bool
2172
	 */
2173
	private function openMessageStore($user) {
2174
		// During PING requests the operations store has to be switched constantly
2175
		// the cache prevents the same store opened several times
2176
		if (isset($this->storeCache[$user])) {
2177
			return $this->storeCache[$user];
2178
		}
2179
2180
		$entryid = false;
2181
		$return_public = false;
2182
2183
		if (strtoupper($user) == 'SYSTEM') {
2184
			$return_public = true;
2185
		}
2186
2187
		// loop through the storestable if authenticated user of public folder
2188
		if ($user == $this->mainUser || $return_public === true) {
2189
			// Find the default store
2190
			$storestables = mapi_getmsgstorestable($this->session);
2191
			$result = mapi_last_hresult();
2192
2193
			if ($result == NOERROR) {
0 ignored issues
show
Bug introduced by
The constant NOERROR was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2194
				$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...
2195
				$result = mapi_last_hresult();
2196
				if ($result != NOERROR || !is_array($rows)) {
2197
					SLog::Write(LOGLEVEL_WARN, sprintf("Grommunio->openMessageStore('%s'): Could not get storestables information 0x%08X", $user, $result));
2198
2199
					return false;
2200
				}
2201
2202
				foreach ($rows as $row) {
2203
					if (!$return_public && isset($row[PR_DEFAULT_STORE]) && $row[PR_DEFAULT_STORE] == true) {
2204
						$entryid = $row[PR_ENTRYID];
2205
2206
						break;
2207
					}
2208
					if ($return_public && isset($row[PR_MDB_PROVIDER]) && $row[PR_MDB_PROVIDER] == ZARAFA_STORE_PUBLIC_GUID) {
0 ignored issues
show
Bug introduced by
The constant ZARAFA_STORE_PUBLIC_GUID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2209
						$entryid = $row[PR_ENTRYID];
2210
2211
						break;
2212
					}
2213
				}
2214
			}
2215
		}
2216
		else {
2217
			$entryid = @mapi_msgstore_createentryid($this->defaultstore, $user);
2218
		}
2219
2220
		if ($entryid) {
2221
			$store = @mapi_openmsgstore($this->session, $entryid);
2222
2223
			if (!$store) {
2224
				SLog::Write(LOGLEVEL_WARN, sprintf("Grommunio->openMessageStore('%s'): Could not open store", $user));
2225
2226
				return false;
2227
			}
2228
2229
			// add this store to the cache
2230
			if (!isset($this->storeCache[$user])) {
2231
				$this->storeCache[$user] = $store;
2232
			}
2233
2234
			// g-sync135: always use SMTP address (issue with altnames)
2235
			if (!$return_public) {
2236
				$addressbook = $this->getAddressbook();
2237
				$storeProps = 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...
2238
				$mailuser = mapi_ab_openentry($addressbook, $storeProps[PR_MAILBOX_OWNER_ENTRYID]);
2239
				$smtpProps = mapi_getprops($mailuser, [PR_SMTP_ADDRESS]);
0 ignored issues
show
Bug introduced by
The constant PR_SMTP_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2240
				if (isset($smtpProps[PR_SMTP_ADDRESS])) {
2241
					Request::SetUserIdentifier($smtpProps[PR_SMTP_ADDRESS]);
2242
				}
2243
			}
2244
2245
			SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->openMessageStore('%s'): Found '%s' store: '%s'", $user, ($return_public) ? 'PUBLIC' : 'DEFAULT', $store));
2246
2247
			return $store;
2248
		}
2249
2250
		SLog::Write(LOGLEVEL_WARN, sprintf("Grommunio->openMessageStore('%s'): No store found for this user", $user));
2251
2252
		return false;
2253
	}
2254
2255
	/**
2256
	 * Checks if the logged in user has secretary permissions on a folder.
2257
	 *
2258
	 * @param resource $store
2259
	 * @param string   $folderid
2260
	 * @param mixed    $entryid
2261
	 *
2262
	 * @return bool
2263
	 */
2264
	public function HasSecretaryACLs($store, $folderid, $entryid = false) {
2265
		if (!$entryid) {
2266
			$entryid = mapi_msgstore_entryidfromsourcekey($store, hex2bin($folderid));
2267
			if (!$entryid) {
2268
				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

2268
				SLog::Write(LOGLEVEL_WARN, sprintf("Grommunio->HasSecretaryACLs(): error, no entryid resolved for %s on store %s", $folderid, /** @scrutinizer ignore-type */ $store));
Loading history...
2269
2270
				return false;
2271
			}
2272
		}
2273
2274
		$folder = mapi_msgstore_openentry($store, $entryid);
2275
		if (!$folder) {
2276
			SLog::Write(LOGLEVEL_WARN, sprintf("Grommunio->HasSecretaryACLs(): error, could not open folder with entryid %s on store %s", bin2hex((string) $entryid), $store));
2277
2278
			return false;
2279
		}
2280
2281
		$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...
2282
		if (isset($props[PR_RIGHTS]) &&
2283
			($props[PR_RIGHTS] & ecRightsReadAny) &&
0 ignored issues
show
Bug introduced by
The constant ecRightsReadAny was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2284
			($props[PR_RIGHTS] & ecRightsCreate) &&
0 ignored issues
show
Bug introduced by
The constant ecRightsCreate was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2285
			($props[PR_RIGHTS] & ecRightsEditOwned) &&
0 ignored issues
show
Bug introduced by
The constant ecRightsEditOwned was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2286
			($props[PR_RIGHTS] & ecRightsDeleteOwned) &&
0 ignored issues
show
Bug introduced by
The constant ecRightsDeleteOwned was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2287
			($props[PR_RIGHTS] & ecRightsEditAny) &&
0 ignored issues
show
Bug introduced by
The constant ecRightsEditAny was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2288
			($props[PR_RIGHTS] & ecRightsDeleteAny) &&
0 ignored issues
show
Bug introduced by
The constant ecRightsDeleteAny was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2289
			($props[PR_RIGHTS] & ecRightsFolderVisible)) {
0 ignored issues
show
Bug introduced by
The constant ecRightsFolderVisible was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2290
			return true;
2291
		}
2292
2293
		return false;
2294
	}
2295
2296
	/**
2297
	 * The meta function for out of office settings.
2298
	 *
2299
	 * @param SyncObject $oof
2300
	 */
2301
	private function settingsOOF(&$oof) {
2302
		// if oof state is set it must be set of oof and get otherwise
2303
		if (isset($oof->oofstate)) {
2304
			$this->settingsOofSet($oof);
2305
		}
2306
		else {
2307
			$this->settingsOofGet($oof);
2308
		}
2309
	}
2310
2311
	/**
2312
	 * Gets the out of office settings.
2313
	 *
2314
	 * @param SyncObject $oof
2315
	 */
2316
	private function settingsOofGet(&$oof) {
2317
		$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_SUBJECT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_EC_OUTOFOFFICE_UNTIL was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_EC_OUTOFOFFICE_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...
2318
		$oof->oofstate = SYNC_SETTINGSOOF_DISABLED;
2319
		$oof->Status = SYNC_SETTINGSSTATUS_SUCCESS;
2320
		if ($oofprops != false) {
2321
			$oof->oofstate = isset($oofprops[PR_EC_OUTOFOFFICE]) ? ($oofprops[PR_EC_OUTOFOFFICE] ? SYNC_SETTINGSOOF_GLOBAL : SYNC_SETTINGSOOF_DISABLED) : SYNC_SETTINGSOOF_DISABLED;
2322
			// TODO external and external unknown
2323
			$oofmessage = new SyncOOFMessage();
2324
			$oofmessage->appliesToInternal = "";
2325
			$oofmessage->enabled = $oof->oofstate;
2326
			$oofmessage->replymessage = $oofprops[PR_EC_OUTOFOFFICE_MSG] ?? "";
2327
			$oofmessage->bodytype = $oof->bodytype;
2328
			unset($oofmessage->appliesToExternal, $oofmessage->appliesToExternalUnknown);
2329
			$oof->oofmessage[] = $oofmessage;
2330
2331
			// check whether time based out of office is set
2332
			if ($oof->oofstate == SYNC_SETTINGSOOF_GLOBAL && isset($oofprops[PR_EC_OUTOFOFFICE_FROM], $oofprops[PR_EC_OUTOFOFFICE_UNTIL])) {
2333
				$now = time();
2334
				if ($now > $oofprops[PR_EC_OUTOFOFFICE_FROM] && $now > $oofprops[PR_EC_OUTOFOFFICE_UNTIL]) {
2335
					// Out of office is set but the date is in the past. Set the state to disabled.
2336
					$oof->oofstate = SYNC_SETTINGSOOF_DISABLED;
2337
					@mapi_setprops($this->defaultstore, [PR_EC_OUTOFOFFICE => false]);
2338
					@mapi_deleteprops($this->defaultstore, [PR_EC_OUTOFOFFICE_FROM, PR_EC_OUTOFOFFICE_UNTIL]);
2339
					SLog::Write(LOGLEVEL_INFO, "Grommunio->settingsOofGet(): Out of office is set but the from and until are in the past. Disabling out of office.");
2340
				}
2341
				elseif ($oofprops[PR_EC_OUTOFOFFICE_FROM] < $oofprops[PR_EC_OUTOFOFFICE_UNTIL]) {
2342
					$oof->oofstate = SYNC_SETTINGSOOF_TIMEBASED;
2343
					$oof->starttime = $oofprops[PR_EC_OUTOFOFFICE_FROM];
2344
					$oof->endtime = $oofprops[PR_EC_OUTOFOFFICE_UNTIL];
2345
				}
2346
				else {
2347
					SLog::Write(LOGLEVEL_WARN, sprintf(
2348
						"Grommunio->settingsOofGet(): Time based out of office set but end time ('%s') is before startime ('%s').",
2349
						date("Y-m-d H:i:s", $oofprops[PR_EC_OUTOFOFFICE_FROM]),
2350
						date("Y-m-d H:i:s", $oofprops[PR_EC_OUTOFOFFICE_UNTIL])
2351
					));
2352
					$oof->Status = SYNC_SETTINGSSTATUS_PROTOCOLLERROR;
2353
				}
2354
			}
2355
			elseif ($oof->oofstate == SYNC_SETTINGSOOF_GLOBAL && (isset($oofprops[PR_EC_OUTOFOFFICE_FROM]) || isset($oofprops[PR_EC_OUTOFOFFICE_UNTIL]))) {
2356
				SLog::Write(LOGLEVEL_WARN, sprintf(
2357
					"Grommunio->settingsOofGet(): Time based out of office set but either start time ('%s') or end time ('%s') is missing.",
2358
					isset($oofprops[PR_EC_OUTOFOFFICE_FROM]) ? date("Y-m-d H:i:s", $oofprops[PR_EC_OUTOFOFFICE_FROM]) : 'empty',
2359
					isset($oofprops[PR_EC_OUTOFOFFICE_UNTIL]) ? date("Y-m-d H:i:s", $oofprops[PR_EC_OUTOFOFFICE_UNTIL]) : 'empty'
2360
				));
2361
				$oof->Status = SYNC_SETTINGSSTATUS_PROTOCOLLERROR;
2362
			}
2363
		}
2364
		else {
2365
			SLog::Write(LOGLEVEL_WARN, "Grommunio->Unable to get out of office information");
2366
		}
2367
2368
		// unset body type for oof in order not to stream it
2369
		unset($oof->bodytype);
2370
	}
2371
2372
	/**
2373
	 * Sets the out of office settings.
2374
	 *
2375
	 * @param SyncObject $oof
2376
	 */
2377
	private function settingsOofSet(&$oof) {
2378
		$oof->Status = SYNC_SETTINGSSTATUS_SUCCESS;
2379
		$props = [];
2380
		if ($oof->oofstate == SYNC_SETTINGSOOF_GLOBAL || $oof->oofstate == SYNC_SETTINGSOOF_TIMEBASED) {
2381
			$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...
2382
			foreach ($oof->oofmessage as $oofmessage) {
2383
				if (isset($oofmessage->appliesToInternal)) {
2384
					$props[PR_EC_OUTOFOFFICE_MSG] = $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...
2385
					$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...
2386
				}
2387
			}
2388
			if ($oof->oofstate == SYNC_SETTINGSOOF_TIMEBASED) {
2389
				if (isset($oof->starttime, $oof->endtime)) {
2390
					$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...
2391
					$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...
2392
				}
2393
				elseif (isset($oof->starttime) || isset($oof->endtime)) {
2394
					$oof->Status = SYNC_SETTINGSSTATUS_PROTOCOLLERROR;
2395
				}
2396
			}
2397
			else {
2398
				$deleteProps = [PR_EC_OUTOFOFFICE_FROM, PR_EC_OUTOFOFFICE_UNTIL];
2399
			}
2400
		}
2401
		elseif ($oof->oofstate == SYNC_SETTINGSOOF_DISABLED) {
2402
			$props[PR_EC_OUTOFOFFICE] = false;
2403
			$deleteProps = [PR_EC_OUTOFOFFICE_FROM, PR_EC_OUTOFOFFICE_UNTIL];
2404
		}
2405
2406
		if (!empty($props)) {
2407
			@mapi_setprops($this->defaultstore, $props);
2408
			$result = mapi_last_hresult();
2409
			if ($result != NOERROR) {
0 ignored issues
show
Bug introduced by
The constant NOERROR was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2410
				SLog::Write(LOGLEVEL_ERROR, sprintf("Grommunio->settingsOofSet(): Setting oof information failed (%X)", $result));
2411
2412
				return false;
2413
			}
2414
		}
2415
2416
		if (!empty($deleteProps)) {
2417
			@mapi_deleteprops($this->defaultstore, $deleteProps);
2418
		}
2419
2420
		return true;
2421
	}
2422
2423
	/**
2424
	 * Gets the user's email address from server.
2425
	 *
2426
	 * @param SyncObject $userinformation
2427
	 */
2428
	private function settingsUserInformation(&$userinformation) {
2429
		if (!isset($this->defaultstore) || !isset($this->mainUser)) {
2430
			SLog::Write(LOGLEVEL_ERROR, "Grommunio->settingsUserInformation(): The store or user are not available for getting user information");
2431
2432
			return false;
2433
		}
2434
		$user = nsp_getuserinfo(Request::GetUserIdentifier());
2435
		if ($user != false) {
2436
			$userinformation->Status = SYNC_SETTINGSSTATUS_USERINFO_SUCCESS;
2437
			if (Request::GetProtocolVersion() >= 14.1) {
2438
				$account = new SyncAccount();
2439
				$emailaddresses = new SyncEmailAddresses();
2440
				$emailaddresses->smtpaddress[] = $user["primary_email"];
2441
				$emailaddresses->primarysmtpaddress = $user["primary_email"];
2442
				$account->emailaddresses = $emailaddresses;
2443
				$userinformation->accounts[] = $account;
2444
			}
2445
			else {
2446
				$userinformation->emailaddresses[] = $user["primary_email"];
2447
			}
2448
2449
			return true;
2450
		}
2451
		SLog::Write(LOGLEVEL_ERROR, sprintf("Grommunio->settingsUserInformation(): Getting user information failed: nsp_getuserinfo(%X)", mapi_last_hresult()));
2452
2453
		return false;
2454
	}
2455
2456
	/**
2457
	 * Gets the rights management templates from the server.
2458
	 *
2459
	 * @param SyncObject $rmTemplates
2460
	 */
2461
	private function settingsRightsManagementTemplates(&$rmTemplates) {
2462
		/* Currently there is no information rights management feature in
2463
		 * the grommunio backend, so just return the status and empty
2464
		 * SyncRightsManagementTemplates tag.
2465
		 * Once it's available, it would be something like:
2466
2467
		$rmTemplate = new SyncRightsManagementTemplate();
2468
		$rmTemplate->id = "some-template-id-eg-guid";
2469
		$rmTemplate->name = "Template name";
2470
		$rmTemplate->description = "What does the template do. E.g. it disables forward and reply.";
2471
		$rmTemplates->rmtemplates[] = $rmTemplate;
2472
		 */
2473
		$rmTemplates->Status = SYNC_COMMONSTATUS_IRMFEATUREDISABLED;
2474
		$rmTemplates->rmtemplates = [];
2475
	}
2476
2477
	/**
2478
	 * Sets the importance and priority of a message from a RFC822 message headers.
2479
	 *
2480
	 * @param int   $xPriority
2481
	 * @param array $mapiprops
2482
	 * @param mixed $sendMailProps
2483
	 */
2484
	private function getImportanceAndPriority($xPriority, &$mapiprops, $sendMailProps) {
2485
		switch ($xPriority) {
2486
			case 1:
2487
			case 2:
2488
				$priority = PRIO_URGENT;
0 ignored issues
show
Bug introduced by
The constant PRIO_URGENT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2489
				$importance = IMPORTANCE_HIGH;
0 ignored issues
show
Bug introduced by
The constant IMPORTANCE_HIGH was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2490
				break;
2491
2492
			case 4:
2493
			case 5:
2494
				$priority = PRIO_NONURGENT;
0 ignored issues
show
Bug introduced by
The constant PRIO_NONURGENT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2495
				$importance = IMPORTANCE_LOW;
0 ignored issues
show
Bug introduced by
The constant IMPORTANCE_LOW was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2496
				break;
2497
2498
			case 3:
2499
			default:
2500
				$priority = PRIO_NORMAL;
0 ignored issues
show
Bug introduced by
The constant PRIO_NORMAL was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2501
				$importance = IMPORTANCE_NORMAL;
0 ignored issues
show
Bug introduced by
The constant IMPORTANCE_NORMAL was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2502
				break;
2503
		}
2504
		$mapiprops[$sendMailProps["importance"]] = $importance;
2505
		$mapiprops[$sendMailProps["priority"]] = $priority;
2506
	}
2507
2508
	/**
2509
	 * Copies attachments from one message to another.
2510
	 *
2511
	 * @param MAPIMessage $toMessage
2512
	 * @param MAPIMessage $fromMessage
2513
	 */
2514
	private function copyAttachments(&$toMessage, $fromMessage) {
2515
		$attachtable = mapi_message_getattachmenttable($fromMessage);
2516
		$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...
2517
2518
		foreach ($rows as $row) {
2519
			if (isset($row[PR_ATTACH_NUM])) {
2520
				$attach = mapi_message_openattach($fromMessage, $row[PR_ATTACH_NUM]);
2521
				$newattach = mapi_message_createattach($toMessage);
2522
				mapi_copyto($attach, [], [], $newattach, 0);
2523
				mapi_savechanges($newattach);
2524
			}
2525
		}
2526
	}
2527
2528
	/**
2529
	 * Function will create a search folder in FINDER_ROOT folder
2530
	 * if folder exists then it will open it.
2531
	 *
2532
	 * @see createSearchFolder($store, $openIfExists = true) function in the webaccess
2533
	 *
2534
	 * @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...
2535
	 */
2536
	private function getSearchFolder() {
2537
		// create new or open existing search folder
2538
		$searchFolderRoot = $this->getSearchFoldersRoot();
2539
		if ($searchFolderRoot === false) {
0 ignored issues
show
introduced by
The condition $searchFolderRoot === false is always false.
Loading history...
2540
			// error in finding search root folder
2541
			// or store doesn't support search folders
2542
			return false;
2543
		}
2544
2545
		$searchFolder = $this->createSearchFolder($searchFolderRoot);
2546
2547
		if ($searchFolder !== false && mapi_last_hresult() == NOERROR) {
0 ignored issues
show
Bug introduced by
The constant NOERROR was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2548
			return $searchFolder;
2549
		}
2550
2551
		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...
2552
	}
2553
2554
	/**
2555
	 * Function will open FINDER_ROOT folder in root container
2556
	 * public folder's don't have FINDER_ROOT folder.
2557
	 *
2558
	 * @see getSearchFoldersRoot($store) function in the webaccess
2559
	 *
2560
	 * @return mapiFolderObject root folder for search folders
2561
	 */
2562
	private function getSearchFoldersRoot() {
2563
		// check if we can create search folders
2564
		$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...
2565
		if (($storeProps[PR_STORE_SUPPORT_MASK] & STORE_SEARCH_OK) != STORE_SEARCH_OK) {
0 ignored issues
show
Bug introduced by
The constant STORE_SEARCH_OK was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2566
			SLog::Write(LOGLEVEL_WARN, "Grommunio->getSearchFoldersRoot(): Store doesn't support search folders. Public store doesn't have FINDER_ROOT folder");
2567
2568
			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...
2569
		}
2570
2571
		// open search folders root
2572
		$searchRootFolder = mapi_msgstore_openentry($this->store, $storeProps[PR_FINDER_ENTRYID]);
2573
		if (mapi_last_hresult() != NOERROR) {
0 ignored issues
show
Bug introduced by
The constant NOERROR was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2574
			SLog::Write(LOGLEVEL_WARN, sprintf("Grommunio->getSearchFoldersRoot(): Unable to open search folder (0x%X)", mapi_last_hresult()));
2575
2576
			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...
2577
		}
2578
2579
		return $searchRootFolder;
2580
	}
2581
2582
	/**
2583
	 * Creates a search folder if it not exists or opens an existing one
2584
	 * and returns it.
2585
	 *
2586
	 * @param mapiFolderObject $searchFolderRoot
2587
	 *
2588
	 * @return mapiFolderObject
2589
	 */
2590
	private function createSearchFolder($searchFolderRoot) {
2591
		$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

2591
		$folderName = "grommunio-sync Search Folder " . /** @scrutinizer ignore-type */ @getmypid();
Loading history...
2592
		$searchFolders = mapi_folder_gethierarchytable($searchFolderRoot);
2593
		$restriction = [
2594
			RES_CONTENT,
0 ignored issues
show
Bug introduced by
The constant RES_CONTENT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2595
			[
2596
				FUZZYLEVEL => FL_PREFIX,
0 ignored issues
show
Bug introduced by
The constant FL_PREFIX was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant FUZZYLEVEL was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2597
				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...
Bug introduced by
The constant ULPROPTAG was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2598
				VALUE => [PR_DISPLAY_NAME => $folderName],
0 ignored issues
show
Bug introduced by
The constant VALUE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2599
			],
2600
		];
2601
		// restrict the hierarchy to the grommunio-sync search folder only
2602
		mapi_table_restrict($searchFolders, $restriction);
2603
		if (mapi_table_getrowcount($searchFolders)) {
2604
			$searchFolder = mapi_table_queryrows($searchFolders, [PR_ENTRYID], 0, 1);
2605
2606
			return mapi_msgstore_openentry($this->store, $searchFolder[0][PR_ENTRYID]);
2607
		}
2608
2609
		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

2609
		return /** @scrutinizer ignore-call */ mapi_folder_createfolder($searchFolderRoot, $folderName, null, 0, FOLDER_SEARCH);
Loading history...
Bug introduced by
The constant FOLDER_SEARCH was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2610
	}
2611
2612
	/**
2613
	 * Creates a search restriction.
2614
	 *
2615
	 * @param ContentParameter $cpo
2616
	 *
2617
	 * @return array
2618
	 */
2619
	private function getSearchRestriction($cpo) {
2620
		$searchText = $cpo->GetSearchFreeText();
2621
2622
		$searchGreater = strtotime((string) $cpo->GetSearchValueGreater());
2623
		$searchLess = strtotime((string) $cpo->GetSearchValueLess());
2624
2625
		// split the search on whitespache and look for every word
2626
		$searchText = preg_split("/\\W+/u", (string) $searchText);
2627
		$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_SENT_REPRESENTING_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_SUBJECT 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_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_NAME 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_DISPLAY_CC was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_SENDER_EMAIL_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2628
		$resAnd = [];
2629
		foreach ($searchText as $term) {
2630
			$resOr = [];
2631
2632
			foreach ($searchProps as $property) {
2633
				array_push(
2634
					$resOr,
2635
					[RES_CONTENT,
0 ignored issues
show
Bug introduced by
The constant RES_CONTENT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2636
						[
2637
							FUZZYLEVEL => FL_SUBSTRING | FL_IGNORECASE,
0 ignored issues
show
Bug introduced by
The constant FL_SUBSTRING was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant FL_IGNORECASE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant FUZZYLEVEL was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2638
							ULPROPTAG => $property,
0 ignored issues
show
Bug introduced by
The constant ULPROPTAG was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2639
							VALUE => $term,
0 ignored issues
show
Bug introduced by
The constant VALUE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2640
						],
2641
					]
2642
				);
2643
			}
2644
			array_push($resAnd, [RES_OR, $resOr]);
0 ignored issues
show
Bug introduced by
The constant RES_OR was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2645
		}
2646
2647
		// add time range restrictions
2648
		if ($searchGreater) {
2649
			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...
Bug introduced by
The constant RES_PROPERTY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant RELOP was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant RELOP_GE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2650
		}
2651
		if ($searchLess) {
2652
			array_push($resAnd, [RES_PROPERTY, [RELOP => RELOP_LE, ULPROPTAG => PR_MESSAGE_DELIVERY_TIME, VALUE => [PR_MESSAGE_DELIVERY_TIME => $searchLess]]]);
0 ignored issues
show
Bug introduced by
The constant RELOP_LE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2653
		}
2654
2655
		return [RES_AND, $resAnd];
0 ignored issues
show
Bug introduced by
The constant RES_AND was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2656
	}
2657
2658
	/**
2659
	 * Creates a FIND restriction.
2660
	 *
2661
	 * @param ContentParameter $cpo
2662
	 *
2663
	 * @return array
2664
	 */
2665
	private function getFindRestriction($cpo) {
2666
		$findText = $cpo->GetFindFreeText();
2667
2668
		$findFor = "";
2669
		if (!(stripos((string) $findText, ":") && (stripos((string) $findText, "OR") || stripos((string) $findText, "AND")))) {
2670
			$findFor = $findText;
2671
		}
2672
		else {
2673
			// just extract a list of words we search for ignoring the fields to be searched in
2674
			// this list of words is then passed to getSearchRestriction()
2675
			$words = [];
2676
			foreach (explode(" OR ", (string) $findText) as $search) {
2677
				if (stripos($search, ':')) {
2678
					$value = explode(":", $search)[1];
2679
				}
2680
				else {
2681
					$value = $search;
2682
				}
2683
				$words[str_replace('"', '', $value)] = true;
2684
			}
2685
			$findFor = implode(" ", array_keys($words));
2686
		}
2687
		SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->getFindRestriction(): extracted words: %s", $findFor));
2688
		$cpo->SetSearchFreeText($findFor);
2689
2690
		return $this->getSearchRestriction($cpo);
2691
	}
2692
2693
	/**
2694
	 * Resolve recipient based on his email address.
2695
	 *
2696
	 * @param string $to
2697
	 * @param int    $maxAmbiguousRecipients
2698
	 * @param bool   $expandDistlist
2699
	 *
2700
	 * @return array|bool
2701
	 */
2702
	private function resolveRecipient($to, $maxAmbiguousRecipients, $expandDistlist = true) {
2703
		$recipient = $this->resolveRecipientGAL($to, $maxAmbiguousRecipients, $expandDistlist);
2704
2705
		if ($recipient !== false) {
0 ignored issues
show
introduced by
The condition $recipient !== false is always true.
Loading history...
2706
			return $recipient;
2707
		}
2708
2709
		$recipient = $this->resolveRecipientContact($to, $maxAmbiguousRecipients);
2710
2711
		if ($recipient !== false) {
2712
			return $recipient;
2713
		}
2714
2715
		return false;
2716
	}
2717
2718
	/**
2719
	 * Resolves recipient from the GAL and gets his certificates.
2720
	 *
2721
	 * @param string $to
2722
	 * @param int    $maxAmbiguousRecipients
2723
	 * @param bool   $expandDistlist
2724
	 *
2725
	 * @return array|bool
2726
	 */
2727
	private function resolveRecipientGAL($to, $maxAmbiguousRecipients, $expandDistlist = true) {
2728
		SLog::Write(LOGLEVEL_WBXML, sprintf("Grommunio->resolveRecipientGAL(): Resolving recipient '%s' in GAL", $to));
2729
		$table = null;
2730
		$addrbook = $this->getAddressbook();
2731
		$ab_dir = $this->getAddressbookDir();
2732
		if ($ab_dir) {
2733
			$table = mapi_folder_getcontentstable($ab_dir);
2734
		}
2735
		if (!$table) {
2736
			SLog::Write(LOGLEVEL_WARN, sprintf("Grommunio->resolveRecipientGAL(): Unable to open addressbook:0x%X", mapi_last_hresult()));
2737
2738
			return false;
2739
		}
2740
2741
		$restriction = MAPIUtils::GetSearchRestriction($to);
2742
		mapi_table_restrict($table, $restriction);
2743
2744
		$querycnt = mapi_table_getrowcount($table);
2745
		if ($querycnt > 0) {
2746
			$recipientGal = [];
2747
			$rowsToQuery = $maxAmbiguousRecipients;
2748
			// some devices request 0 ambiguous recipients
2749
			if ($querycnt == 1 && $maxAmbiguousRecipients == 0) {
2750
				$rowsToQuery = 1;
2751
			}
2752
			elseif ($querycnt > 1 && $maxAmbiguousRecipients == 0) {
2753
				SLog::Write(LOGLEVEL_INFO, sprintf("Grommunio->resolveRecipientGAL(): GAL search found %d recipients but the device hasn't requested ambiguous recipients", $querycnt));
2754
2755
				return $recipientGal;
2756
			}
2757
			elseif ($querycnt > 1 && $maxAmbiguousRecipients == 1) {
2758
				$rowsToQuery = $querycnt;
2759
			}
2760
			// get the certificate every time because caching the certificate is less expensive than opening addressbook entry again
2761
			$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_EMS_AB_X509_CERT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_OBJECT_TYPE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_SMTP_ADDRESS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2762
			for ($i = 0, $nrEntries = count($abentries); $i < $nrEntries; ++$i) {
2763
				if (strcasecmp((string) $abentries[$i][PR_SMTP_ADDRESS], $to) !== 0 && $maxAmbiguousRecipients == 1) {
2764
					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]));
2765
2766
					continue;
2767
				}
2768
				if ($abentries[$i][PR_OBJECT_TYPE] == MAPI_DISTLIST) {
0 ignored issues
show
Bug introduced by
The constant MAPI_DISTLIST was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2769
					// check whether to expand dist list
2770
					if ($expandDistlist) {
2771
						SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->resolveRecipientGAL(): '%s' is a dist list. Expand it to members.", $to));
2772
						$distList = mapi_ab_openentry($addrbook, $abentries[$i][PR_ENTRYID]);
2773
						$distListContent = mapi_folder_getcontentstable($distList);
2774
						$distListMembers = mapi_table_queryallrows($distListContent, [PR_ENTRYID, PR_DISPLAY_NAME, PR_EMS_AB_X509_CERT]);
2775
						for ($j = 0, $nrDistListMembers = mapi_table_getrowcount($distListContent); $j < $nrDistListMembers; ++$j) {
2776
							SLog::Write(LOGLEVEL_WBXML, sprintf("Grommunio->resolveRecipientGAL(): distlist's '%s' member: '%s'", $to, $distListMembers[$j][PR_DISPLAY_NAME]));
2777
							$recipientGal[] = $this->createResolveRecipient(SYNC_RESOLVERECIPIENTS_TYPE_GAL, $to, $distListMembers[$j], $nrDistListMembers);
2778
						}
2779
					}
2780
					else {
2781
						SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->resolveRecipientGAL(): '%s' is a dist list, but return it as is.", $to));
2782
						$recipientGal[] = $this->createResolveRecipient(SYNC_RESOLVERECIPIENTS_TYPE_GAL, $abentries[$i][PR_SMTP_ADDRESS], $abentries[$i]);
2783
					}
2784
				}
2785
				elseif ($abentries[$i][PR_OBJECT_TYPE] == MAPI_MAILUSER) {
0 ignored issues
show
Bug introduced by
The constant MAPI_MAILUSER was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2786
					$recipientGal[] = $this->createResolveRecipient(SYNC_RESOLVERECIPIENTS_TYPE_GAL, $abentries[$i][PR_SMTP_ADDRESS], $abentries[$i]);
2787
				}
2788
			}
2789
2790
			SLog::Write(LOGLEVEL_WBXML, "Grommunio->resolveRecipientGAL(): Found a recipient in GAL");
2791
2792
			return $recipientGal;
2793
		}
2794
2795
		SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->resolveRecipientGAL(): No recipient found for: '%s' in GAL", $to));
2796
2797
		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...
2798
2799
		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...
2800
	}
2801
2802
	/**
2803
	 * Resolves recipient from the contact list and gets his certificates.
2804
	 *
2805
	 * @param string $to
2806
	 * @param int    $maxAmbiguousRecipients
2807
	 *
2808
	 * @return array|bool
2809
	 */
2810
	private function resolveRecipientContact($to, $maxAmbiguousRecipients) {
2811
		SLog::Write(LOGLEVEL_WBXML, sprintf("Grommunio->resolveRecipientContact(): Resolving recipient '%s' in user's contacts", $to));
2812
		// go through all contact folders of the user and
2813
		// check if there's a contact with the given email address
2814
		$root = mapi_msgstore_openentry($this->defaultstore);
2815
		if (!$root) {
2816
			SLog::Write(LOGLEVEL_ERROR, sprintf("Grommunio->resolveRecipientContact(): Unable to open default store: 0x%X", mapi_last_hresult()));
2817
		}
2818
		$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...
2819
		$contacts = $this->getContactsFromFolder($this->defaultstore, $rootprops[PR_IPM_CONTACT_ENTRYID], $to);
2820
		$recipients = [];
2821
2822
		if ($contacts !== false) {
2823
			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

2823
			SLog::Write(LOGLEVEL_WBXML, sprintf("Grommunio->resolveRecipientContact(): Found %d contacts in main contacts folder.", count(/** @scrutinizer ignore-type */ $contacts)));
Loading history...
2824
			// create resolve recipient object
2825
			foreach ($contacts as $contact) {
2826
				$recipients[] = $this->createResolveRecipient(SYNC_RESOLVERECIPIENTS_TYPE_CONTACT, $to, $contact);
2827
			}
2828
		}
2829
2830
		$contactfolder = mapi_msgstore_openentry($this->defaultstore, $rootprops[PR_IPM_CONTACT_ENTRYID]);
2831
		$subfolders = MAPIUtils::GetSubfoldersForType($contactfolder, "IPF.Contact");
2832
		if ($subfolders !== false) {
2833
			foreach ($subfolders as $folder) {
2834
				$contacts = $this->getContactsFromFolder($this->defaultstore, $folder[PR_ENTRYID], $to);
2835
				if ($contacts !== false) {
2836
					SLog::Write(LOGLEVEL_WBXML, sprintf("Grommunio->resolveRecipientContact(): Found %d contacts in contacts' subfolder.", count($contacts)));
2837
					foreach ($contacts as $contact) {
2838
						$recipients[] = $this->createResolveRecipient(SYNC_RESOLVERECIPIENTS_TYPE_CONTACT, $to, $contact);
2839
					}
2840
				}
2841
			}
2842
		}
2843
2844
		// search contacts in public folders
2845
		$storestables = mapi_getmsgstorestable($this->session);
2846
		$result = mapi_last_hresult();
2847
2848
		if ($result == NOERROR) {
0 ignored issues
show
Bug introduced by
The constant NOERROR was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2849
			$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...
2850
			foreach ($rows as $row) {
2851
				if (isset($row[PR_MDB_PROVIDER]) && $row[PR_MDB_PROVIDER] == ZARAFA_STORE_PUBLIC_GUID) {
0 ignored issues
show
Bug introduced by
The constant ZARAFA_STORE_PUBLIC_GUID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2852
					// TODO refactor public store
2853
					$publicstore = mapi_openmsgstore($this->session, $row[PR_ENTRYID]);
2854
					$publicfolder = mapi_msgstore_openentry($publicstore);
2855
2856
					$subfolders = MAPIUtils::GetSubfoldersForType($publicfolder, "IPF.Contact");
2857
					if ($subfolders !== false) {
2858
						foreach ($subfolders as $folder) {
2859
							$contacts = $this->getContactsFromFolder($publicstore, $folder[PR_ENTRYID], $to);
2860
							if ($contacts !== false) {
2861
								SLog::Write(LOGLEVEL_WBXML, sprintf("Grommunio->resolveRecipientContact(): Found %d contacts in public contacts folder.", count($contacts)));
2862
								foreach ($contacts as $contact) {
2863
									$recipients[] = $this->createResolveRecipient(SYNC_RESOLVERECIPIENTS_TYPE_CONTACT, $to, $contact);
2864
								}
2865
							}
2866
						}
2867
					}
2868
2869
					break;
2870
				}
2871
			}
2872
		}
2873
		else {
2874
			SLog::Write(LOGLEVEL_WARN, sprintf("Grommunio->resolveRecipientContact(): Unable to open public store: 0x%X", $result));
2875
		}
2876
2877
		if (empty($recipients)) {
2878
			$contactProperties = [];
2879
			$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...
2880
			$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...
2881
2882
			$recipients[] = $this->createResolveRecipient(SYNC_RESOLVERECIPIENTS_TYPE_CONTACT, $to, $contactProperties);
2883
		}
2884
2885
		return $recipients;
2886
	}
2887
2888
	/**
2889
	 * Creates SyncResolveRecipientsCertificates object for ResolveRecipients.
2890
	 *
2891
	 * @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...
2892
	 * @param int    $recipientCount
2893
	 *
2894
	 * @return SyncResolveRecipientsCertificates
2895
	 */
2896
	private function getCertificates($certificates, $recipientCount = 0) {
2897
		$cert = new SyncResolveRecipientsCertificates();
2898
		if ($certificates === false) {
0 ignored issues
show
introduced by
The condition $certificates === false is always false.
Loading history...
2899
			$cert->status = SYNC_RESOLVERECIPSSTATUS_CERTIFICATES_NOVALIDCERT;
2900
2901
			return $cert;
2902
		}
2903
		$cert->status = SYNC_RESOLVERECIPSSTATUS_SUCCESS;
2904
		$cert->certificatecount = count($certificates);
2905
		$cert->recipientcount = $recipientCount;
2906
		$cert->certificate = [];
2907
		foreach ($certificates as $certificate) {
2908
			$cert->certificate[] = base64_encode((string) $certificate);
2909
		}
2910
2911
		return $cert;
2912
	}
2913
2914
	/**
2915
	 * Creates SyncResolveRecipient object for ResolveRecipientsResponse.
2916
	 *
2917
	 * @param int    $type
2918
	 * @param string $email
2919
	 * @param array  $recipientProperties
2920
	 * @param int    $recipientCount
2921
	 *
2922
	 * @return SyncResolveRecipient
2923
	 */
2924
	private function createResolveRecipient($type, $email, $recipientProperties, $recipientCount = 0) {
2925
		$recipient = new SyncResolveRecipient();
2926
		$recipient->type = $type;
2927
		$recipient->displayname = $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...
2928
		$recipient->emailaddress = $email;
2929
2930
		if ($type == SYNC_RESOLVERECIPIENTS_TYPE_GAL) {
2931
			$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...
2932
		}
2933
		elseif ($type == SYNC_RESOLVERECIPIENTS_TYPE_CONTACT) {
2934
			$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...
2935
		}
2936
		else {
2937
			$certificateProp = null;
2938
		}
2939
2940
		if (isset($recipientProperties[$certificateProp]) && is_array($recipientProperties[$certificateProp]) && !empty($recipientProperties[$certificateProp])) {
2941
			$certificates = $this->getCertificates($recipientProperties[$certificateProp], $recipientCount);
2942
		}
2943
		else {
2944
			$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

2944
			$certificates = $this->getCertificates(/** @scrutinizer ignore-type */ false);
Loading history...
2945
			SLog::Write(LOGLEVEL_INFO, sprintf("Grommunio->createResolveRecipient(): No certificate found for '%s' (requested email address: '%s')", $recipientProperties[PR_DISPLAY_NAME], $email));
2946
		}
2947
		$recipient->certificates = $certificates;
2948
2949
		if (isset($recipientProperties[PR_ENTRYID])) {
2950
			$recipient->id = $recipientProperties[PR_ENTRYID];
2951
		}
2952
2953
		return $recipient;
2954
	}
2955
2956
	/**
2957
	 * Gets the availability of a user for the given time window.
2958
	 *
2959
	 * @param string                       $to
2960
	 * @param SyncResolveRecipient         $resolveRecipient
2961
	 * @param SyncResolveRecipientsOptions $resolveRecipientsOptions
2962
	 *
2963
	 * @return SyncResolveRecipientsAvailability
2964
	 */
2965
	private function getAvailability($to, $resolveRecipient, $resolveRecipientsOptions) {
2966
		$availability = new SyncResolveRecipientsAvailability();
2967
		$availability->status = SYNC_RESOLVERECIPSSTATUS_AVAILABILITY_SUCCESS;
2968
2969
		if (!isset($resolveRecipient->id)) {
2970
			// TODO this shouldn't happen but try to get the recipient in such a case
2971
		}
2972
2973
		$start = strtotime((string) $resolveRecipientsOptions->availability->starttime);
2974
		$end = strtotime((string) $resolveRecipientsOptions->availability->endtime);
2975
		// Each digit in the MergedFreeBusy indicates the free/busy status for the user for every 30 minute interval.
2976
		$timeslots = intval(ceil(($end - $start) / self::HALFHOURSECONDS));
2977
2978
		if ($timeslots > self::MAXFREEBUSYSLOTS) {
2979
			throw new StatusException("Grommunio->getAvailability(): the requested free busy range is too large.", SYNC_RESOLVERECIPSSTATUS_PROTOCOLERROR);
2980
		}
2981
2982
		$mergedFreeBusy = str_pad(fbNoData, $timeslots, fbNoData);
0 ignored issues
show
Bug introduced by
The constant fbNoData was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2983
2984
		$fbdata = mapi_getuserfreebusy($this->session, $resolveRecipient->id, $start, $end);
0 ignored issues
show
Bug introduced by
The function mapi_getuserfreebusy 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

2984
		$fbdata = /** @scrutinizer ignore-call */ mapi_getuserfreebusy($this->session, $resolveRecipient->id, $start, $end);
Loading history...
2985
2986
		if (!empty($fbdata['fbevents'])) {
2987
			$mergedFreeBusy = str_pad(fbFree, $timeslots, fbFree);
0 ignored issues
show
Bug introduced by
The constant fbFree was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2988
			foreach ($fbdata['fbevents'] as $event) {
2989
				// calculate which timeslot of mergedFreeBusy should be replaced.
2990
				$startSlot = intval(floor(($event['start'] - $start) / self::HALFHOURSECONDS));
2991
				$endSlot = intval(floor(($event['end'] - $start) / self::HALFHOURSECONDS));
2992
				// if event started at a multiple of half an hour from requested freebusy time and
2993
				// its duration is also a multiple of half an hour
2994
				// then it's necessary to reduce endSlot by one
2995
				if ((($event['start'] - $start) % self::HALFHOURSECONDS == 0) && (($event['end'] - $event['start']) % self::HALFHOURSECONDS == 0)) {
2996
					--$endSlot;
2997
				}
2998
				for ($i = $startSlot; $i <= $endSlot && $i < $timeslots; ++$i) {
2999
					// only set the new slot's free busy status if it's higher than the current one (fbFree < fbTentative < fbBusy < fbOutOfOffice)
3000
					if ($event['busystatus'] > $mergedFreeBusy[$i]) {
3001
						$mergedFreeBusy[$i] = $event['busystatus'];
3002
					}
3003
				}
3004
			}
3005
		}
3006
		$availability->mergedfreebusy = $mergedFreeBusy;
3007
3008
		return $availability;
3009
	}
3010
3011
	/**
3012
	 * Returns contacts matching given email address from a folder.
3013
	 *
3014
	 * @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...
3015
	 * @param binary    $folderEntryid
3016
	 * @param string    $email
3017
	 *
3018
	 * @return array|bool
3019
	 */
3020
	private function getContactsFromFolder($store, $folderEntryid, $email) {
3021
		$folder = mapi_msgstore_openentry($store, $folderEntryid);
3022
		$folderContent = mapi_folder_getcontentstable($folder);
3023
		mapi_table_restrict($folderContent, MAPIUtils::GetEmailAddressRestriction($store, $email));
3024
		// TODO max limit
3025
		if (mapi_table_getrowcount($folderContent) > 0) {
3026
			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...
3027
		}
3028
3029
		return false;
3030
	}
3031
3032
	/**
3033
	 * Get MAPI addressbook object.
3034
	 *
3035
	 * @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...
3036
	 */
3037
	private function getAddressbook() {
3038
		if (isset($this->addressbook) && $this->addressbook) {
3039
			return $this->addressbook;
3040
		}
3041
		$this->addressbook = mapi_openaddressbook($this->session);
3042
		$result = mapi_last_hresult();
3043
		if ($result && $this->addressbook === false) {
3044
			SLog::Write(LOGLEVEL_ERROR, sprintf("Grommunio->getAddressbook error opening addressbook 0x%X", $result));
3045
3046
			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...
3047
		}
3048
3049
		return $this->addressbook;
3050
	}
3051
3052
	/**
3053
	 * Returns the adressbook dir entry.
3054
	 *
3055
	 * @return mixed addressbook dir entry or false on error
3056
	 */
3057
	private function getAddressbookDir() {
3058
		try {
3059
			$addrbook = $this->getAddressbook();
3060
			$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

3060
			$ab_entryid = /** @scrutinizer ignore-call */ mapi_ab_getdefaultdir($addrbook);
Loading history...
3061
3062
			return mapi_ab_openentry($addrbook, $ab_entryid);
3063
		}
3064
		catch (MAPIException $e) {
3065
			SLog::Write(LOGLEVEL_ERROR, sprintf("Grommunio->getAddressbookDir(): Unable to open addressbook: %s", $e));
3066
		}
3067
3068
		return false;
3069
	}
3070
3071
	/**
3072
	 * Checks if the user is not disabled for grommunio-sync.
3073
	 *
3074
	 * @return bool
3075
	 *
3076
	 * @throws FatalException if user is disabled for grommunio-sync
3077
	 */
3078
	private function isGSyncEnabled() {
3079
		// this check needs to be performed on the store of the main (authenticated) user
3080
		$storeProps = mapi_getprops($this->storeCache[$this->mainUser], [PR_EC_ENABLED_FEATURES_L]);
0 ignored issues
show
Bug introduced by
The constant PR_EC_ENABLED_FEATURES_L was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3081
		if (!($storeProps[PR_EC_ENABLED_FEATURES_L] & UP_EAS)) {
0 ignored issues
show
Bug introduced by
The constant UP_EAS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3082
			throw new FatalException("User is disabled for grommunio-sync.");
3083
		}
3084
3085
		return true;
3086
	}
3087
}
3088