Passed
Push — master ( e95939...eccc14 )
by
unknown
34:59 queued 15s
created

Grommunio::isGSyncEnabled()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

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

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

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

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

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

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

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

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

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

370
			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

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

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

395
			return new ExportChangesICS(/** @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 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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1603
		$properties = /** @scrutinizer ignore-call */ getPropIdsFromStrings($store, ["synctomobile" => "PT_BOOLEAN:PSETID_GROMOX:synctomobile"]);
Loading history...
1604
1605
		$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...
1606
			[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...
1607
				[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...
1608
			],
1609
			[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...
1610
				[
1611
					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...
1612
					ULPROPTAG => $properties['synctomobile'],
1613
					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...
1614
				],
1615
			],
1616
		]];
1617
		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...
1618
		$rows = mapi_table_queryallrows($hierarchyTable, [PR_DISPLAY_NAME, PR_CONTAINER_CLASS, 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...
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...
1619
		$f = [];
1620
		foreach ($rows as $row) {
1621
			$folderid = bin2hex($row[PR_SOURCE_KEY]);
1622
			$f[$folderid] = [
1623
				'store' => 'SYSTEM',
1624
				'flags' => DeviceManager::FLD_FLAGS_NONE,
1625
				'folderid' => $folderid,
1626
				'parentid' => '0',
1627
				'name' => $row[PR_DISPLAY_NAME],
1628
				'type' => MAPIUtils::GetFolderTypeFromContainerClass($row[PR_CONTAINER_CLASS]),
1629
			];
1630
		}
1631
1632
		return $f;
1633
	}
1634
1635
	/*----------------------------------------------------------------------------------------------------------
1636
	 * Implementation of the IStateMachine interface
1637
	 */
1638
1639
	/**
1640
	 * Gets a hash value indicating the latest dataset of the named
1641
	 * state with a specified key and counter.
1642
	 * If the state is changed between two calls of this method
1643
	 * the returned hash should be different.
1644
	 *
1645
	 * @param string $devid   the device id
1646
	 * @param string $type    the state type
1647
	 * @param string $key     (opt)
1648
	 * @param string $counter (opt)
1649
	 *
1650
	 * @return string
1651
	 *
1652
	 * @throws StateNotFoundException
1653
	 */
1654
	public function GetStateHash($devid, $type, $key = false, $counter = false) {
1655
		try {
1656
			$stateMessage = $this->getStateMessage($devid, $type, $key, $counter);
0 ignored issues
show
Bug introduced by
It seems like $key can also be of type false; however, parameter $key of Grommunio::getStateMessage() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

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

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

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

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

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

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

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

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

1691
		$stateMessage = $this->getStateMessage($devid, $type, $key, /** @scrutinizer ignore-type */ $counter);
Loading history...
1692
		$state = base64_decode(MAPIUtils::readPropStream($stateMessage, PR_BODY));
0 ignored issues
show
Bug introduced by
The constant PR_BODY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1693
1694
		if ($state && $state[0] === '{') {
1695
			$jsonDec = json_decode($state);
1696
			if (isset($jsonDec->gsSyncStateClass)) {
1697
				$gsObj = new $jsonDec->gsSyncStateClass();
1698
				$gsObj->jsonDeserialize($jsonDec);
1699
				$gsObj->postUnserialize();
1700
			}
1701
		}
1702
1703
		return isset($gsObj) && is_object($gsObj) ? $gsObj : $state;
1704
	}
1705
1706
	/**
1707
	 * Writes ta state to for a key and counter.
1708
	 *
1709
	 * @param mixed  $state
1710
	 * @param string $devid   the device id
1711
	 * @param string $type    the state type
1712
	 * @param string $key     (opt)
1713
	 * @param int    $counter (opt)
1714
	 *
1715
	 * @return bool
1716
	 *
1717
	 * @throws StateInvalidException, UnavailableException
1718
	 */
1719
	public function SetState($state, $devid, $type, $key = false, $counter = false) {
1720
		return $this->setStateMessage($state, $devid, $type, $key, $counter);
0 ignored issues
show
Bug introduced by
It seems like $key can also be of type false; however, parameter $key of Grommunio::setStateMessage() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

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

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

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

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

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

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

1751
		$restriction = $this->getStateMessageRestriction($messageName, $counter, /** @scrutinizer ignore-type */ $thisCounterOnly);
Loading history...
1752
		$stateFolderContents = mapi_folder_getcontentstable($this->stateFolder, MAPI_ASSOCIATED);
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...
1753
		if ($stateFolderContents) {
1754
			mapi_table_restrict($stateFolderContents, $restriction);
1755
			$rowCnt = mapi_table_getrowcount($stateFolderContents);
1756
			SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->CleanStates(): Found %d states to clean (%s) %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

1756
			SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->CleanStates(): Found %d states to clean (%s) %s", $rowCnt, $messageName, Utils::PrintAsString(/** @scrutinizer ignore-type */ $counter)));
Loading history...
1757
			if ($rowCnt > 0) {
1758
				$rows = mapi_table_queryallrows($stateFolderContents, [PR_ENTRYID]);
1759
				$entryids = [];
1760
				foreach ($rows as $row) {
1761
					$entryids[] = $row[PR_ENTRYID];
1762
				}
1763
				mapi_folder_deletemessages($this->stateFolder, $entryids, DELETE_HARD_DELETE);
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...
1764
			}
1765
		}
1766
	}
1767
1768
	/**
1769
	 * Links a user to a device.
1770
	 *
1771
	 * @param string $username
1772
	 * @param string $devid
1773
	 *
1774
	 * @return bool indicating if the user was added or not (existed already)
1775
	 */
1776
	public function LinkUserDevice($username, $devid) {
1777
		$device = [$devid => time()];
1778
		$this->setDeviceUserData($this->type, $device, $username, -1, $subkey = -1, $doCas = "merge");
1779
1780
		return false;
1781
	}
1782
1783
	/**
1784
	 * Unlinks a device from a user.
1785
	 *
1786
	 * @param string $username
1787
	 * @param string $devid
1788
	 *
1789
	 * @return bool
1790
	 */
1791
	public function UnLinkUserDevice($username, $devid) {
1792
		// TODO: Implement
1793
		return false;
1794
	}
1795
1796
	/**
1797
	 * Returns the current version of the state files
1798
	 * grommunio:  This is not relevant atm. IStateMachine::STATEVERSION_02 will match GSync::GetLatestStateVersion().
1799
	 *          If it might be required to update states in the future, this could be implemented on a store level,
1800
	 *          where states are then migrated "on-the-fly"
1801
	 *          or
1802
	 *          in a global settings where all states in all stores are migrated once.
1803
	 *
1804
	 * @return int
1805
	 */
1806
	public function GetStateVersion() {
1807
		return IStateMachine::STATEVERSION_02;
0 ignored issues
show
Bug Best Practice introduced by
The expression return IStateMachine::STATEVERSION_02 returns the type string which is incompatible with the documented return type integer.
Loading history...
1808
	}
1809
1810
	/**
1811
	 * Sets the current version of the state files.
1812
	 *
1813
	 * @param int $version the new supported version
1814
	 *
1815
	 * @return bool
1816
	 */
1817
	public function SetStateVersion($version) {
1818
		return true;
1819
	}
1820
1821
	/**
1822
	 * Returns MAPIFolder object which contains the state information.
1823
	 * Creates this folder if it is not available yet.
1824
	 *
1825
	 * @param string $devid the device id
1826
	 *
1827
	 * @return MAPIFolder
0 ignored issues
show
Bug introduced by
The type MAPIFolder was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
1828
	 */
1829
	private function getStateFolder($devid) {
1830
		// Options request doesn't send device id
1831
		if (strlen($devid) == 0) {
1832
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type MAPIFolder.
Loading history...
1833
		}
1834
		// Try to get the state folder id from redis
1835
		if (!$this->stateFolder) {
1836
			$folderentryid = $this->getDeviceUserData($this->userDeviceData, $devid, $this->mainUser, "statefolder");
1837
			if ($folderentryid) {
1838
				$this->stateFolder = mapi_msgstore_openentry($this->defaultstore, hex2bin($folderentryid));
1839
			}
1840
		}
1841
1842
		// fallback code
1843
		if (!$this->stateFolder && $this->defaultstore) {
1844
			SLog::Write(LOGLEVEL_INFO, sprintf("Grommunio->getStateFolder(): state folder not set. Use fallback"));
1845
			$rootfolder = mapi_msgstore_openentry($this->defaultstore);
1846
			$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...
1847
			$restriction = $this->getStateFolderRestriction($devid);
1848
			// restrict the hierarchy to the grommunio-sync search folder only
1849
			mapi_table_restrict($hierarchy, $restriction);
1850
			$rowCnt = mapi_table_getrowcount($hierarchy);
1851
			SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->getStateFolder(): found %d device state folders", $rowCnt));
1852
			if ($rowCnt == 1) {
1853
				$hierarchyRows = mapi_table_queryrows($hierarchy, [PR_ENTRYID], 0, 1);
1854
				$this->stateFolder = mapi_msgstore_openentry($this->defaultstore, $hierarchyRows[0][PR_ENTRYID]);
1855
				SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->getStateFolder(): %s", bin2hex($hierarchyRows[0][PR_ENTRYID])));
1856
				// put found id in redis
1857
				if ($devid) {
1858
					$this->setDeviceUserData($this->userDeviceData, bin2hex($hierarchyRows[0][PR_ENTRYID]), $devid, $this->mainUser, "statefolder");
1859
				}
1860
			}
1861
			elseif ($rowCnt == 0) {
1862
				// legacy code: create the hidden state folder and the device subfolder
1863
				// this should happen when the user configures the device (autodiscover or first sync if no autodiscover)
1864
1865
				$hierarchy = mapi_folder_gethierarchytable($rootfolder, CONVENIENT_DEPTH);
1866
				$restriction = $this->getStateFolderRestriction(STORE_STATE_FOLDER);
1867
				mapi_table_restrict($hierarchy, $restriction);
1868
				$rowCnt = mapi_table_getrowcount($hierarchy);
1869
				SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->getStateFolder(): found %d store state folders", $rowCnt));
1870
				if ($rowCnt == 1) {
1871
					$hierarchyRows = mapi_table_queryrows($hierarchy, [PR_ENTRYID], 0, 1);
1872
					$stateFolder = mapi_msgstore_openentry($this->defaultstore, $hierarchyRows[0][PR_ENTRYID]);
1873
				}
1874
				elseif ($rowCnt == 0) {
1875
					$stateFolder = mapi_folder_createfolder($rootfolder, STORE_STATE_FOLDER, "");
0 ignored issues
show
Bug introduced by
The function mapi_folder_createfolder was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1875
					$stateFolder = /** @scrutinizer ignore-call */ mapi_folder_createfolder($rootfolder, STORE_STATE_FOLDER, "");
Loading history...
1876
					mapi_setprops($stateFolder, [PR_ATTR_HIDDEN => true]);
0 ignored issues
show
Bug introduced by
The constant PR_ATTR_HIDDEN was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1877
				}
1878
1879
				// TODO: handle this
1880
1881
				if (isset($stateFolder) && $stateFolder) {
1882
					$devStateFolder = mapi_folder_createfolder($stateFolder, $devid, "");
1883
					$devStateFolderProps = mapi_getprops($devStateFolder);
1884
					$this->stateFolder = mapi_msgstore_openentry($this->defaultstore, $devStateFolderProps[PR_ENTRYID]);
1885
					mapi_setprops($this->stateFolder, [PR_ATTR_HIDDEN => true]);
1886
					// we don't cache the entryid in redis, because this will happen on the next request anyway
1887
				}
1888
1889
				// TODO: unable to create state folder - throw exception
1890
			}
1891
1892
			// This case is rather unlikely that there would be several
1893
			// hidden folders having PR_DISPLAY_NAME the same as device id.
1894
1895
			// TODO: get the hierarchy table again, get entry id of STORE_STATE_FOLDER
1896
			// and compare it to the parent id of those folders.
1897
		}
1898
1899
		return $this->stateFolder;
1900
	}
1901
1902
	/**
1903
	 * Returns the associated MAPIMessage which contains the state information.
1904
	 *
1905
	 * @param string $devid            the device id
1906
	 * @param string $type             the state type
1907
	 * @param string $key              (opt)
1908
	 * @param string $counter          state counter
1909
	 * @param mixed  $logStateNotFound
1910
	 *
1911
	 * @return MAPIMessage
0 ignored issues
show
Bug introduced by
The type MAPIMessage was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
1912
	 *
1913
	 * @throws StateNotFoundException
1914
	 */
1915
	private function getStateMessage($devid, $type, $key, $counter, $logStateNotFound = true) {
1916
		if (!$this->stateFolder) {
1917
			$this->getStateFolder(Request::GetDeviceID());
1918
			if (!$this->stateFolder) {
1919
				throw new StateNotFoundException(sprintf(
1920
					"Grommunio->getStateMessage(): Could not locate the state folder for device '%s'",
1921
					$devid
1922
				));
1923
			}
1924
		}
1925
		$messageName = rtrim((($key !== false) ? $key . "-" : "") . (($type !== "") ? $type : ""), "-");
0 ignored issues
show
introduced by
The condition $key !== false is always true.
Loading history...
1926
		$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

1926
		$restriction = $this->getStateMessageRestriction($messageName, $counter, /** @scrutinizer ignore-type */ true);
Loading history...
1927
		$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...
1928
		if ($stateFolderContents) {
1929
			mapi_table_restrict($stateFolderContents, $restriction);
1930
			$rowCnt = mapi_table_getrowcount($stateFolderContents);
1931
			if ($rowCnt == 1) {
1932
				$stateFolderRows = mapi_table_queryrows($stateFolderContents, [PR_ENTRYID], 0, 1);
1933
1934
				return mapi_msgstore_openentry($this->defaultstore, $stateFolderRows[0][PR_ENTRYID]);
1935
			}
1936
1937
			if ($rowCnt > 1) {
1938
				SLog::Write(LOGLEVEL_WARN, sprintf("Grommunio->getStateMessage(): Cleaning up duplicated state messages '%s' (%d)", $messageName, $rowCnt));
1939
				$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

1939
				$this->CleanStates($devid, $type, $key, $counter, /** @scrutinizer ignore-type */ true);
Loading history...
1940
			}
1941
		}
1942
1943
		throw new StateNotFoundException(
1944
			sprintf(
1945
				"Grommunio->getStateMessage(): Could not locate the state message '%s' (counter: %s)",
1946
				$messageName,
1947
				Utils::PrintAsString($counter)
1948
			),
1949
			0,
1950
			null,
1951
			$logStateNotFound ? false : LOGLEVEL_WBXMLSTACK
1952
		);
1953
	}
1954
1955
	/**
1956
	 * Writes ta state to for a key and counter.
1957
	 *
1958
	 * @param mixed  $state
1959
	 * @param string $devid   the device id
1960
	 * @param string $type    the state type
1961
	 * @param string $key     (opt)
1962
	 * @param int    $counter (opt)
1963
	 *
1964
	 * @return bool
1965
	 *
1966
	 * @throws StateInvalidException, UnavailableException
1967
	 */
1968
	private function setStateMessage($state, $devid, $type, $key = false, $counter = false) {
1969
		if (!$this->stateFolder) {
1970
			throw new StateNotFoundException(sprintf("Grommunio->setStateMessage(): Could not locate the state folder for device '%s'", $devid));
1971
		}
1972
1973
		try {
1974
			$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

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

1974
			$stateMessage = $this->getStateMessage($devid, $type, $key, /** @scrutinizer ignore-type */ $counter, false);
Loading history...
1975
		}
1976
		catch (StateNotFoundException $e) {
1977
			// if message is not available, try to create a new one
1978
			$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...
1979
			if (mapi_last_hresult()) {
1980
				SLog::Write(LOGLEVEL_ERROR, sprintf("Grommunio->setStateMessage(): Could not create new state message, mapi_folder_createmessage: 0x%08X", mapi_last_hresult()));
1981
1982
				return false;
1983
			}
1984
1985
			$messageName = rtrim((($key !== false) ? $key . "-" : "") . (($type !== "") ? $type : ""), "-");
1986
			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

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

2126
			/** @scrutinizer ignore-call */ 
2127
   mapi_msgstore_advise($store, null, fnevNewMail | fnevObjectModified | fnevObjectCreated | fnevObjectMoved | fnevObjectDeleted, $this->changesSink);
Loading history...
Bug introduced by
The constant fnevObjectModified was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant fnevObjectCreated was not found. Maybe you did not declare it correctly or list all dependencies?
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 fnevObjectDeleted was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2127
2128
			if (mapi_last_hresult()) {
2129
				SLog::Write(LOGLEVEL_WARN, sprintf("Grommunio->adviseStoreToSink(): failed to advised store '%s' with code 0x%08X. Polling will be performed.", $store, mapi_last_hresult()));
2130
2131
				return false;
2132
			}
2133
2134
			SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->adviseStoreToSink(): advised store '%s'", $store));
2135
			$this->changesSinkStores[] = $store;
2136
		}
2137
2138
		return true;
2139
	}
2140
2141
	/**
2142
	 * Open the store marked with PR_DEFAULT_STORE = TRUE
2143
	 * if $return_public is set, the public store is opened.
2144
	 *
2145
	 * @param string $user User which store should be opened
2146
	 *
2147
	 * @return bool
2148
	 */
2149
	private function openMessageStore($user) {
2150
		// During PING requests the operations store has to be switched constantly
2151
		// the cache prevents the same store opened several times
2152
		if (isset($this->storeCache[$user])) {
2153
			return $this->storeCache[$user];
2154
		}
2155
2156
		$entryid = false;
2157
		$return_public = false;
2158
2159
		if (strtoupper($user) == 'SYSTEM') {
2160
			$return_public = true;
2161
		}
2162
2163
		// loop through the storestable if authenticated user of public folder
2164
		if ($user == $this->mainUser || $return_public === true) {
2165
			// Find the default store
2166
			$storestables = mapi_getmsgstorestable($this->session);
2167
			$result = mapi_last_hresult();
2168
2169
			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...
2170
				$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...
2171
				$result = mapi_last_hresult();
2172
				if ($result != NOERROR || !is_array($rows)) {
2173
					SLog::Write(LOGLEVEL_WARN, sprintf("Grommunio->openMessageStore('%s'): Could not get storestables information 0x%08X", $user, $result));
2174
2175
					return false;
2176
				}
2177
2178
				foreach ($rows as $row) {
2179
					if (!$return_public && isset($row[PR_DEFAULT_STORE]) && $row[PR_DEFAULT_STORE] == true) {
2180
						$entryid = $row[PR_ENTRYID];
2181
2182
						break;
2183
					}
2184
					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...
2185
						$entryid = $row[PR_ENTRYID];
2186
2187
						break;
2188
					}
2189
				}
2190
			}
2191
		}
2192
		else {
2193
			$entryid = @mapi_msgstore_createentryid($this->defaultstore, $user);
2194
		}
2195
2196
		if ($entryid) {
2197
			$store = @mapi_openmsgstore($this->session, $entryid);
2198
2199
			if (!$store) {
2200
				SLog::Write(LOGLEVEL_WARN, sprintf("Grommunio->openMessageStore('%s'): Could not open store", $user));
2201
2202
				return false;
2203
			}
2204
2205
			// add this store to the cache
2206
			if (!isset($this->storeCache[$user])) {
2207
				$this->storeCache[$user] = $store;
2208
			}
2209
2210
			SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->openMessageStore('%s'): Found '%s' store: '%s'", $user, ($return_public) ? 'PUBLIC' : 'DEFAULT', $store));
2211
2212
			return $store;
2213
		}
2214
2215
		SLog::Write(LOGLEVEL_WARN, sprintf("Grommunio->openMessageStore('%s'): No store found for this user", $user));
2216
2217
		return false;
2218
	}
2219
2220
	/**
2221
	 * Checks if the logged in user has secretary permissions on a folder.
2222
	 *
2223
	 * @param resource $store
2224
	 * @param string   $folderid
2225
	 * @param mixed    $entryid
2226
	 *
2227
	 * @return bool
2228
	 */
2229
	public function HasSecretaryACLs($store, $folderid, $entryid = false) {
2230
		if (!$entryid) {
2231
			$entryid = mapi_msgstore_entryidfromsourcekey($store, hex2bin($folderid));
2232
			if (!$entryid) {
2233
				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

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

2556
		$folderName = "grommunio-sync Search Folder " . /** @scrutinizer ignore-type */ @getmypid();
Loading history...
2557
		$searchFolders = mapi_folder_gethierarchytable($searchFolderRoot);
2558
		$restriction = [
2559
			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...
2560
			[
2561
				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...
2562
				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...
2563
				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...
2564
			],
2565
		];
2566
		// restrict the hierarchy to the grommunio-sync search folder only
2567
		mapi_table_restrict($searchFolders, $restriction);
2568
		if (mapi_table_getrowcount($searchFolders)) {
2569
			$searchFolder = mapi_table_queryrows($searchFolders, [PR_ENTRYID], 0, 1);
2570
2571
			return mapi_msgstore_openentry($this->store, $searchFolder[0][PR_ENTRYID]);
2572
		}
2573
2574
		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

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

2788
			SLog::Write(LOGLEVEL_WBXML, sprintf("Grommunio->resolveRecipientContact(): Found %d contacts in main contacts folder.", count(/** @scrutinizer ignore-type */ $contacts)));
Loading history...
2789
			// create resolve recipient object
2790
			foreach ($contacts as $contact) {
2791
				$recipients[] = $this->createResolveRecipient(SYNC_RESOLVERECIPIENTS_TYPE_CONTACT, $to, $contact);
2792
			}
2793
		}
2794
2795
		$contactfolder = mapi_msgstore_openentry($this->defaultstore, $rootprops[PR_IPM_CONTACT_ENTRYID]);
2796
		$subfolders = MAPIUtils::GetSubfoldersForType($contactfolder, "IPF.Contact");
2797
		if ($subfolders !== false) {
2798
			foreach ($subfolders as $folder) {
2799
				$contacts = $this->getContactsFromFolder($this->defaultstore, $folder[PR_ENTRYID], $to);
2800
				if ($contacts !== false) {
2801
					SLog::Write(LOGLEVEL_WBXML, sprintf("Grommunio->resolveRecipientContact(): Found %d contacts in contacts' subfolder.", count($contacts)));
2802
					foreach ($contacts as $contact) {
2803
						$recipients[] = $this->createResolveRecipient(SYNC_RESOLVERECIPIENTS_TYPE_CONTACT, $to, $contact);
2804
					}
2805
				}
2806
			}
2807
		}
2808
2809
		// search contacts in public folders
2810
		$storestables = mapi_getmsgstorestable($this->session);
2811
		$result = mapi_last_hresult();
2812
2813
		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...
2814
			$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...
2815
			foreach ($rows as $row) {
2816
				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...
2817
					// TODO refactor public store
2818
					$publicstore = mapi_openmsgstore($this->session, $row[PR_ENTRYID]);
2819
					$publicfolder = mapi_msgstore_openentry($publicstore);
2820
2821
					$subfolders = MAPIUtils::GetSubfoldersForType($publicfolder, "IPF.Contact");
2822
					if ($subfolders !== false) {
2823
						foreach ($subfolders as $folder) {
2824
							$contacts = $this->getContactsFromFolder($publicstore, $folder[PR_ENTRYID], $to);
2825
							if ($contacts !== false) {
2826
								SLog::Write(LOGLEVEL_WBXML, sprintf("Grommunio->resolveRecipientContact(): Found %d contacts in public contacts folder.", count($contacts)));
2827
								foreach ($contacts as $contact) {
2828
									$recipients[] = $this->createResolveRecipient(SYNC_RESOLVERECIPIENTS_TYPE_CONTACT, $to, $contact);
2829
								}
2830
							}
2831
						}
2832
					}
2833
2834
					break;
2835
				}
2836
			}
2837
		}
2838
		else {
2839
			SLog::Write(LOGLEVEL_WARN, sprintf("Grommunio->resolveRecipientContact(): Unable to open public store: 0x%X", $result));
2840
		}
2841
2842
		if (empty($recipients)) {
2843
			$contactProperties = [];
2844
			$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...
2845
			$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...
2846
2847
			$recipients[] = $this->createResolveRecipient(SYNC_RESOLVERECIPIENTS_TYPE_CONTACT, $to, $contactProperties);
2848
		}
2849
2850
		return $recipients;
2851
	}
2852
2853
	/**
2854
	 * Creates SyncResolveRecipientsCertificates object for ResolveRecipients.
2855
	 *
2856
	 * @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...
2857
	 * @param int    $recipientCount
2858
	 *
2859
	 * @return SyncResolveRecipientsCertificates
2860
	 */
2861
	private function getCertificates($certificates, $recipientCount = 0) {
2862
		$cert = new SyncResolveRecipientsCertificates();
2863
		if ($certificates === false) {
0 ignored issues
show
introduced by
The condition $certificates === false is always false.
Loading history...
2864
			$cert->status = SYNC_RESOLVERECIPSSTATUS_CERTIFICATES_NOVALIDCERT;
2865
2866
			return $cert;
2867
		}
2868
		$cert->status = SYNC_RESOLVERECIPSSTATUS_SUCCESS;
2869
		$cert->certificatecount = count($certificates);
2870
		$cert->recipientcount = $recipientCount;
2871
		$cert->certificate = [];
2872
		foreach ($certificates as $certificate) {
2873
			$cert->certificate[] = base64_encode($certificate);
2874
		}
2875
2876
		return $cert;
2877
	}
2878
2879
	/**
2880
	 * Creates SyncResolveRecipient object for ResolveRecipientsResponse.
2881
	 *
2882
	 * @param int    $type
2883
	 * @param string $email
2884
	 * @param array  $recipientProperties
2885
	 * @param int    $recipientCount
2886
	 *
2887
	 * @return SyncResolveRecipient
2888
	 */
2889
	private function createResolveRecipient($type, $email, $recipientProperties, $recipientCount = 0) {
2890
		$recipient = new SyncResolveRecipient();
2891
		$recipient->type = $type;
2892
		$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...
2893
		$recipient->emailaddress = $email;
2894
2895
		if ($type == SYNC_RESOLVERECIPIENTS_TYPE_GAL) {
2896
			$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...
2897
		}
2898
		elseif ($type == SYNC_RESOLVERECIPIENTS_TYPE_CONTACT) {
2899
			$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...
2900
		}
2901
		else {
2902
			$certificateProp = null;
2903
		}
2904
2905
		if (isset($recipientProperties[$certificateProp]) && is_array($recipientProperties[$certificateProp]) && !empty($recipientProperties[$certificateProp])) {
2906
			$certificates = $this->getCertificates($recipientProperties[$certificateProp], $recipientCount);
2907
		}
2908
		else {
2909
			$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

2909
			$certificates = $this->getCertificates(/** @scrutinizer ignore-type */ false);
Loading history...
2910
			SLog::Write(LOGLEVEL_INFO, sprintf("Grommunio->createResolveRecipient(): No certificate found for '%s' (requested email address: '%s')", $recipientProperties[PR_DISPLAY_NAME], $email));
2911
		}
2912
		$recipient->certificates = $certificates;
2913
2914
		if (isset($recipientProperties[PR_ENTRYID])) {
2915
			$recipient->id = $recipientProperties[PR_ENTRYID];
2916
		}
2917
2918
		return $recipient;
2919
	}
2920
2921
	/**
2922
	 * Gets the availability of a user for the given time window.
2923
	 *
2924
	 * @param string                       $to
2925
	 * @param SyncResolveRecipient         $resolveRecipient
2926
	 * @param SyncResolveRecipientsOptions $resolveRecipientsOptions
2927
	 *
2928
	 * @return SyncResolveRecipientsAvailability
2929
	 */
2930
	private function getAvailability($to, $resolveRecipient, $resolveRecipientsOptions) {
2931
		$availability = new SyncResolveRecipientsAvailability();
2932
		$availability->status = SYNC_RESOLVERECIPSSTATUS_AVAILABILITY_SUCCESS;
2933
2934
		if (!isset($resolveRecipient->id)) {
2935
			// TODO this shouldn't happen but try to get the recipient in such a case
2936
		}
2937
2938
		$start = strtotime($resolveRecipientsOptions->availability->starttime);
2939
		$end = strtotime($resolveRecipientsOptions->availability->endtime);
2940
		// Each digit in the MergedFreeBusy indicates the free/busy status for the user for every 30 minute interval.
2941
		$timeslots = intval(ceil(($end - $start) / self::HALFHOURSECONDS));
2942
2943
		if ($timeslots > self::MAXFREEBUSYSLOTS) {
2944
			throw new StatusException("Grommunio->getAvailability(): the requested free busy range is too large.", SYNC_RESOLVERECIPSSTATUS_PROTOCOLERROR);
2945
		}
2946
2947
		$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...
2948
2949
		$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

2949
		$fbdata = /** @scrutinizer ignore-call */ mapi_getuserfreebusy($this->session, $resolveRecipient->id, $start, $end);
Loading history...
2950
2951
		if (!empty($fbdata['fbevents'])) {
2952
			$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...
2953
			foreach ($fbdata['fbevents'] as $event) {
2954
				// calculate which timeslot of mergedFreeBusy should be replaced.
2955
				$startSlot = intval(floor(($event['start'] - $start) / self::HALFHOURSECONDS));
2956
				$endSlot = intval(floor(($event['end'] - $start) / self::HALFHOURSECONDS));
2957
				// if event started at a multiple of half an hour from requested freebusy time and
2958
				// its duration is also a multiple of half an hour
2959
				// then it's necessary to reduce endSlot by one
2960
				if ((($event['start'] - $start) % self::HALFHOURSECONDS == 0) && (($event['end'] - $event['start']) % self::HALFHOURSECONDS == 0)) {
2961
					--$endSlot;
2962
				}
2963
				for ($i = $startSlot; $i <= $endSlot && $i < $timeslots; ++$i) {
2964
					// only set the new slot's free busy status if it's higher than the current one (fbFree < fbTentative < fbBusy < fbOutOfOffice)
2965
					if ($event['busystatus'] > $mergedFreeBusy[$i]) {
2966
						$mergedFreeBusy[$i] = $event['busystatus'];
2967
					}
2968
				}
2969
			}
2970
		}
2971
		$availability->mergedfreebusy = $mergedFreeBusy;
2972
2973
		return $availability;
2974
	}
2975
2976
	/**
2977
	 * Returns contacts matching given email address from a folder.
2978
	 *
2979
	 * @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...
2980
	 * @param binary    $folderEntryid
2981
	 * @param string    $email
2982
	 *
2983
	 * @return array|bool
2984
	 */
2985
	private function getContactsFromFolder($store, $folderEntryid, $email) {
2986
		$folder = mapi_msgstore_openentry($store, $folderEntryid);
2987
		$folderContent = mapi_folder_getcontentstable($folder);
2988
		mapi_table_restrict($folderContent, MAPIUtils::GetEmailAddressRestriction($store, $email));
2989
		// TODO max limit
2990
		if (mapi_table_getrowcount($folderContent) > 0) {
2991
			return mapi_table_queryallrows($folderContent, [PR_DISPLAY_NAME, PR_USER_X509_CERTIFICATE, PR_ENTRYID]);
0 ignored issues
show
Bug introduced by
The constant PR_DISPLAY_NAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant PR_USER_X509_CERTIFICATE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2992
		}
2993
2994
		return false;
2995
	}
2996
2997
	/**
2998
	 * Get MAPI addressbook object.
2999
	 *
3000
	 * @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...
3001
	 */
3002
	private function getAddressbook() {
3003
		if (isset($this->addressbook) && $this->addressbook) {
3004
			return $this->addressbook;
3005
		}
3006
		$this->addressbook = mapi_openaddressbook($this->session);
3007
		$result = mapi_last_hresult();
3008
		if ($result && $this->addressbook === false) {
3009
			SLog::Write(LOGLEVEL_ERROR, sprintf("Grommunio->getAddressbook error opening addressbook 0x%X", $result));
3010
3011
			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...
3012
		}
3013
3014
		return $this->addressbook;
3015
	}
3016
3017
	/**
3018
	 * Returns the adressbook dir entry
3019
	 *
3020
	 * @access private
3021
	 *
3022
	 * @return mixed addressbook dir entry or false on error
3023
	 */
3024
	private function getAddressbookDir() {
3025
		try {
3026
			$addrbook = $this->getAddressbook();
3027
			$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

3027
			$ab_entryid = /** @scrutinizer ignore-call */ mapi_ab_getdefaultdir($addrbook);
Loading history...
3028
			$ab_dir = mapi_ab_openentry($addrbook, $ab_entryid);
3029
3030
			return $ab_dir;
3031
		}
3032
		catch (MAPIException $e) {
3033
			SLog::Write(LOGLEVEL_ERROR, sprintf("Grommunio->getAddressbookDir(): Unable to open addressbook: %s", $e));
3034
		}
3035
3036
		return false;
3037
	}
3038
3039
	/**
3040
	 * Checks if the user is not disabled for grommunio-sync.
3041
	 *
3042
	 * @return bool
3043
	 *
3044
	 * @throws FatalException if user is disabled for grommunio-sync
3045
	 */
3046
	private function isGSyncEnabled() {
3047
		// this check needs to be performed on the store of the main (authenticated) user
3048
		$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...
3049
		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...
3050
			throw new FatalException("User is disabled for grommunio-sync.");
3051
		}
3052
3053
		return true;
3054
	}
3055
}
3056