Grommunio::SetStateVersion()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 2
rs 10
c 0
b 0
f 0
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($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
		list($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", isset($row[PR_DISPLAY_NAME]) ? $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", isset($row[PR_DISPLAY_NAME]) ? $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($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($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(isset($sm->source->folderid) ? $sm->source->folderid : false),
0 ignored issues
show
Bug introduced by
It seems like IssetNode ? $sm->source->folderid : false can also be of type false; however, parameter $var of Utils::PrintAsString() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

426
			Utils::PrintAsString(/** @scrutinizer ignore-type */ isset($sm->source->folderid) ? $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($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($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
			switch ($hr) {
589
				case MAPI_E_STORE_FULL:
0 ignored issues
show
Bug introduced by
The constant MAPI_E_STORE_FULL was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
590
					$code = SYNC_COMMONSTATUS_MAILBOXQUOTAEXCEEDED;
591
					break;
592
593
				default:
594
					$code = SYNC_COMMONSTATUS_MAILSUBMISSIONFAILED;
595
					break;
596
			}
597
598
			throw new StatusException(sprintf("Grommunio->SendMail(): Error saving/submitting the message to the Outbox: 0x%X", $hr), $code);
599
		}
600
601
		SLog::Write(LOGLEVEL_DEBUG, "Grommunio->SendMail(): email submitted");
602
603
		return true;
604
	}
605
606
	/**
607
	 * Returns all available data of a single message.
608
	 *
609
	 * @param string            $folderid
610
	 * @param string            $id
611
	 * @param ContentParameters $contentparameters flag
612
	 *
613
	 * @return object(SyncObject)
614
	 *
615
	 * @throws StatusException
616
	 */
617
	public function Fetch($folderid, $id, $contentparameters) {
618
		// SEARCH fetches with folderid == false and PR_ENTRYID as ID
619
		if (!$folderid) {
620
			$entryid = hex2bin($id);
621
			$sk = $id;
622
		}
623
		else {
624
			// id might be in the new longid format, so we have to split it here
625
			list($fsk, $sk) = Utils::SplitMessageId($id);
626
			// get the entry id of the message
627
			$entryid = mapi_msgstore_entryidfromsourcekey($this->store, hex2bin($folderid), hex2bin($sk));
628
		}
629
		if (!$entryid) {
630
			throw new StatusException(sprintf("Grommunio->Fetch('%s','%s'): Error getting entryid: 0x%X", $folderid, $sk, mapi_last_hresult()), SYNC_STATUS_OBJECTNOTFOUND);
631
		}
632
633
		// open the message
634
		$message = mapi_msgstore_openentry($this->store, $entryid);
635
		if (!$message) {
636
			throw new StatusException(sprintf("Grommunio->Fetch('%s','%s'): Error, unable to open message: 0x%X", $folderid, $sk, mapi_last_hresult()), SYNC_STATUS_OBJECTNOTFOUND);
637
		}
638
639
		// convert the mapi message into a SyncObject and return it
640
		$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

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

640
		$mapiprovider = new MAPIProvider(/** @scrutinizer ignore-type */ $this->session, $this->store);
Loading history...
641
642
		// override truncation
643
		$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

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

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

816
			$props = /** @scrutinizer ignore-call */ getPropIdsFromStrings($this->store, $props);
Loading history...
817
818
			$messageprops = mapi_getprops($mapimessage, [$props["goidtag"]]);
819
			$goid = $messageprops[$props["goidtag"]];
820
821
			$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

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

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

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

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

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

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

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

1475
			/** @scrutinizer ignore-call */ 
1476
   mapi_folder_deletefolder($finderfolder, $folder[PR_ENTRYID]);
Loading history...
1476
		}
1477
1478
		// call recursivly once to delete older search folders than the one we had an PID to search for
1479
		if ($pid !== "" && $last > 0) {
1480
			$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

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

1639
		$properties = /** @scrutinizer ignore-call */ getPropIdsFromStrings($store, ["synctomobile" => "PT_BOOLEAN:PSETID_GROMOX:synctomobile"]);
Loading history...
1640
1641
		$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...
1642
			[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...
1643
				[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...
1644
			],
1645
			[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...
1646
				[
1647
					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...
1648
					ULPROPTAG => $properties['synctomobile'],
1649
					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...
1650
				],
1651
			],
1652
		]];
1653
		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...
1654
		$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...
1655
		$f = [];
1656
		foreach ($rows as $row) {
1657
			$folderid = bin2hex($row[PR_SOURCE_KEY]);
1658
			$f[$folderid] = [
1659
				'store' => 'SYSTEM',
1660
				'flags' => DeviceManager::FLD_FLAGS_NONE,
1661
				'folderid' => $folderid,
1662
				'parentid' => '0',
1663
				'name' => $row[PR_DISPLAY_NAME],
1664
				'type' => MAPIUtils::GetFolderTypeFromContainerClass($row[PR_CONTAINER_CLASS]),
1665
			];
1666
		}
1667
1668
		return $f;
1669
	}
1670
1671
	/*----------------------------------------------------------------------------------------------------------
1672
	 * Implementation of the IStateMachine interface
1673
	 */
1674
1675
	/**
1676
	 * Gets a hash value indicating the latest dataset of the named
1677
	 * state with a specified key and counter.
1678
	 * If the state is changed between two calls of this method
1679
	 * the returned hash should be different.
1680
	 *
1681
	 * @param string $devid   the device id
1682
	 * @param string $type    the state type
1683
	 * @param string $key     (opt)
1684
	 * @param string $counter (opt)
1685
	 *
1686
	 * @return string
1687
	 *
1688
	 * @throws StateNotFoundException
1689
	 */
1690
	public function GetStateHash($devid, $type, $key = false, $counter = false) {
1691
		try {
1692
			$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

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

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

1721
			$this->CleanStates($devid, $type, /** @scrutinizer ignore-type */ $key, $counter);
Loading history...
1722
			// also clean failsafe state for previous counter
1723
			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...
1724
				$this->CleanStates($devid, $type, IStateMachine::FAILSAFE, $counter);
1725
			}
1726
		}
1727
		$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

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

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

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

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

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

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

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

1787
		$restriction = $this->getStateMessageRestriction($messageName, /** @scrutinizer ignore-type */ $counter, $thisCounterOnly);
Loading history...
1788
		$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...
1789
		if ($stateFolderContents) {
1790
			mapi_table_restrict($stateFolderContents, $restriction);
1791
			$rowCnt = mapi_table_getrowcount($stateFolderContents);
1792
			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

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

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

1962
		$restriction = $this->getStateMessageRestriction($messageName, $counter, /** @scrutinizer ignore-type */ true);
Loading history...
1963
		$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...
1964
		if ($stateFolderContents) {
1965
			mapi_table_restrict($stateFolderContents, $restriction);
1966
			$rowCnt = mapi_table_getrowcount($stateFolderContents);
1967
			if ($rowCnt == 1) {
1968
				$stateFolderRows = mapi_table_queryrows($stateFolderContents, [PR_ENTRYID], 0, 1);
1969
1970
				return mapi_msgstore_openentry($this->defaultstore, $stateFolderRows[0][PR_ENTRYID]);
1971
			}
1972
1973
			if ($rowCnt > 1) {
1974
				SLog::Write(LOGLEVEL_WARN, sprintf("Grommunio->getStateMessage(): Cleaning up duplicated state messages '%s' (%d)", $messageName, $rowCnt));
1975
				$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

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

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

2010
			$stateMessage = $this->getStateMessage($devid, $type, $key, /** @scrutinizer ignore-type */ $counter, false);
Loading history...
2011
		}
2012
		catch (StateNotFoundException $e) {
2013
			// if message is not available, try to create a new one
2014
			$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...
2015
			if (mapi_last_hresult()) {
2016
				SLog::Write(LOGLEVEL_ERROR, sprintf("Grommunio->setStateMessage(): Could not create new state message, mapi_folder_createmessage: 0x%08X", mapi_last_hresult()));
2017
2018
				return false;
2019
			}
2020
2021
			$messageName = rtrim((($key !== false) ? $key . "-" : "") . (($type !== "") ? $type : ""), "-");
2022
			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

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

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

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

2603
		$folderName = "grommunio-sync Search Folder " . /** @scrutinizer ignore-type */ @getmypid();
Loading history...
2604
		$searchFolders = mapi_folder_gethierarchytable($searchFolderRoot);
2605
		$restriction = [
2606
			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...
2607
			[
2608
				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...
2609
				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...
2610
				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...
2611
			],
2612
		];
2613
		// restrict the hierarchy to the grommunio-sync search folder only
2614
		mapi_table_restrict($searchFolders, $restriction);
2615
		if (mapi_table_getrowcount($searchFolders)) {
2616
			$searchFolder = mapi_table_queryrows($searchFolders, [PR_ENTRYID], 0, 1);
2617
2618
			return mapi_msgstore_openentry($this->store, $searchFolder[0][PR_ENTRYID]);
2619
		}
2620
2621
		return mapi_folder_createfolder($searchFolderRoot, $folderName, null, 0, 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...
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

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

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

2956
			$certificates = $this->getCertificates(/** @scrutinizer ignore-type */ false);
Loading history...
2957
			SLog::Write(LOGLEVEL_INFO, sprintf("Grommunio->createResolveRecipient(): No certificate found for '%s' (requested email address: '%s')", $recipientProperties[PR_DISPLAY_NAME], $email));
2958
		}
2959
		$recipient->certificates = $certificates;
2960
2961
		if (isset($recipientProperties[PR_ENTRYID])) {
2962
			$recipient->id = $recipientProperties[PR_ENTRYID];
2963
		}
2964
2965
		return $recipient;
2966
	}
2967
2968
	/**
2969
	 * Gets the availability of a user for the given time window.
2970
	 *
2971
	 * @param string                       $to
2972
	 * @param SyncResolveRecipient         $resolveRecipient
2973
	 * @param SyncResolveRecipientsOptions $resolveRecipientsOptions
2974
	 *
2975
	 * @return SyncResolveRecipientsAvailability
2976
	 */
2977
	private function getAvailability($to, $resolveRecipient, $resolveRecipientsOptions) {
2978
		$availability = new SyncResolveRecipientsAvailability();
2979
		$availability->status = SYNC_RESOLVERECIPSSTATUS_AVAILABILITY_SUCCESS;
2980
2981
		if (!isset($resolveRecipient->id)) {
2982
			// TODO this shouldn't happen but try to get the recipient in such a case
2983
		}
2984
2985
		$start = strtotime($resolveRecipientsOptions->availability->starttime);
2986
		$end = strtotime($resolveRecipientsOptions->availability->endtime);
2987
		// Each digit in the MergedFreeBusy indicates the free/busy status for the user for every 30 minute interval.
2988
		$timeslots = intval(ceil(($end - $start) / self::HALFHOURSECONDS));
2989
2990
		if ($timeslots > self::MAXFREEBUSYSLOTS) {
2991
			throw new StatusException("Grommunio->getAvailability(): the requested free busy range is too large.", SYNC_RESOLVERECIPSSTATUS_PROTOCOLERROR);
2992
		}
2993
2994
		$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...
2995
2996
		$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

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

3072
			$ab_entryid = /** @scrutinizer ignore-call */ mapi_ab_getdefaultdir($addrbook);
Loading history...
3073
3074
			return mapi_ab_openentry($addrbook, $ab_entryid);
3075
		}
3076
		catch (MAPIException $e) {
3077
			SLog::Write(LOGLEVEL_ERROR, sprintf("Grommunio->getAddressbookDir(): Unable to open addressbook: %s", $e));
3078
		}
3079
3080
		return false;
3081
	}
3082
3083
	/**
3084
	 * Checks if the user is not disabled for grommunio-sync.
3085
	 *
3086
	 * @return bool
3087
	 *
3088
	 * @throws FatalException if user is disabled for grommunio-sync
3089
	 */
3090
	private function isGSyncEnabled() {
3091
		// this check needs to be performed on the store of the main (authenticated) user
3092
		$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...
3093
		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...
3094
			throw new FatalException("User is disabled for grommunio-sync.");
3095
		}
3096
3097
		return true;
3098
	}
3099
}
3100