Passed
Push — master ( d5ffbe...9aa611 )
by
unknown
03:41 queued 13s
created

Grommunio::resolveRecipientGAL()   C

Complexity

Conditions 17
Paths 42

Size

Total Lines 73
Code Lines 46

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 17
eloc 46
c 2
b 0
f 0
nc 42
nop 3
dl 0
loc 73
rs 5.2166

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
/*
3
 * SPDX-License-Identifier: AGPL-3.0-only
4
 * SPDX-FileCopyrightText: Copyright 2007-2016 Zarafa Deutschland GmbH
5
 * SPDX-FileCopyrightText: Copyright 2020-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
	// gromox config parameter for PR_EC_ENABLED_FEATURES / PR_EC_DISABLED_FEATURES
42
	public const MOBILE_ENABLED = 'mobile';
43
44
	public const MAXAMBIGUOUSRECIPIENTS = 9999;
45
	public const FREEBUSYENUMBLOCKS = 50;
46
	public const MAXFREEBUSYSLOTS = 32767; // max length of 32k for the MergedFreeBusy element is allowed
47
	public const HALFHOURSECONDS = 1800;
48
49
	/**
50
	 * Constructor of the grommunio Backend.
51
	 */
52
	public function __construct() {
53
		$this->session = false;
54
		$this->store = false;
55
		$this->storeName = false;
56
		$this->storeCache = [];
57
		$this->changesSink = false;
58
		$this->changesSinkFolders = [];
59
		$this->changesSinkStores = [];
60
		$this->changesSinkHierarchyHash = false;
61
		$this->wastebasket = false;
62
		$this->session = false;
63
		$this->folderStatCache = [];
64
		$this->impersonateUser = false;
65
		$this->stateFolder = null;
66
67
		SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio using PHP-MAPI version: %s - PHP version: %s", phpversion("mapi"), phpversion()));
68
69
		# Interprocessdata
70
		$this->allocate = 0;
71
		$this->type = "grommunio-sync:userdevices";
72
		$this->userDeviceData = "grommunio-sync:statefoldercache";
73
		parent::__construct();
74
	}
75
76
	/**
77
	 * Indicates which StateMachine should be used.
78
	 *
79
	 * @return bool Grommunio uses own state machine
80
	 */
81
	public function GetStateMachine() {
82
		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...
83
	}
84
85
	/**
86
	 * Returns the Grommunio as it implements the ISearchProvider interface
87
	 * This could be overwritten by the global configuration.
88
	 *
89
	 * @return object Implementation of ISearchProvider
90
	 */
91
	public function GetSearchProvider() {
92
		return $this;
93
	}
94
95
	/**
96
	 * Indicates which AS version is supported by the backend.
97
	 *
98
	 * @return string AS version constant
99
	 */
100
	public function GetSupportedASVersion() {
101
		return GSync::ASV_161;
102
	}
103
104
	/**
105
	 * Authenticates the user with the configured grommunio server.
106
	 *
107
	 * @param string $domain
108
	 * @param mixed  $user
109
	 * @param mixed  $pass
110
	 *
111
	 * @return bool
112
	 *
113
	 * @throws AuthenticationRequiredException
114
	 */
115
	public function Logon($user, $domain, $pass) {
116
		SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->Logon(): Trying to authenticate user '%s'..", $user));
117
118
		$this->mainUser = strtolower($user);
119
		// TODO the impersonated user should be passed directly to IBackend->Logon()
120
		if (Request::GetImpersonatedUser()) {
121
			$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

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

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

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

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

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

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

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

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

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

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

367
			if ($this->storeName != $this->mainUser && !$this->hasSecretaryACLs(/** @scrutinizer ignore-type */ $this->store, $folderid)) {
Loading history...
368
				SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->GetImporter(): missing permissions on folderid: '%s'.", Utils::PrintAsString($folderid)));
369
370
				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...
371
			}
372
373
			return new ImportChangesICS($this->session, $this->store, hex2bin($folderid));
0 ignored issues
show
Bug introduced by
$this->store of type boolean is incompatible with the type mapistore expected by parameter $store of ImportChangesICS::__construct(). ( Ignorable by Annotation )

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

373
			return new ImportChangesICS($this->session, /** @scrutinizer ignore-type */ $this->store, hex2bin($folderid));
Loading history...
Bug introduced by
$this->session of type boolean is incompatible with the type mapisession expected by parameter $session of ImportChangesICS::__construct(). ( Ignorable by Annotation )

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

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

392
			if ($this->storeName != $this->mainUser && !$this->hasSecretaryACLs(/** @scrutinizer ignore-type */ $this->store, $folderid)) {
Loading history...
393
				SLog::Write(LOGLEVEL_DEBUG, sprintf("Grommunio->GetExporter(): missing permissions on folderid: '%s'.", Utils::PrintAsString($folderid)));
394
395
				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...
396
			}
397
398
			return new ExportChangesICS($this->session, $this->store, hex2bin($folderid));
0 ignored issues
show
Bug introduced by
$this->store of type boolean is incompatible with the type mapistore expected by parameter $store of ExportChangesICS::__construct(). ( Ignorable by Annotation )

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

398
			return new ExportChangesICS($this->session, /** @scrutinizer ignore-type */ $this->store, hex2bin($folderid));
Loading history...
Bug introduced by
$this->session of type boolean is incompatible with the type mapisession expected by parameter $session of ExportChangesICS::__construct(). ( Ignorable by Annotation )

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

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

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

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

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

642
		$mapiprovider = new MAPIProvider($this->session, /** @scrutinizer ignore-type */ $this->store);
Loading history...
Bug introduced by
$this->session of type boolean is incompatible with the type resource expected by parameter $session of MAPIProvider::__construct(). ( Ignorable by Annotation )

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1688
			$this->CleanStates($devid, $type, /** @scrutinizer ignore-type */ $key, $counter);
Loading history...
1689
			// also clean failsafe state for previous counter
1690
			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...
1691
				$this->CleanStates($devid, $type, IStateMachine::FAILSAFE, $counter);
1692
			}
1693
		}
1694
		$stateMessage = $this->getStateMessage($devid, $type, $key, $counter);
0 ignored issues
show
Bug introduced by
It seems like $counter can also be of type false; however, parameter $counter of Grommunio::getStateMessage() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

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

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

1694
		$stateMessage = $this->getStateMessage($devid, $type, /** @scrutinizer ignore-type */ $key, $counter);
Loading history...
1695
		$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...
1696
1697
		if ($state && $state[0] === '{') {
1698
			$jsonDec = json_decode($state);
1699
			if (isset($jsonDec->gsSyncStateClass)) {
1700
				$gsObj = new $jsonDec->gsSyncStateClass();
1701
				$gsObj->jsonDeserialize($jsonDec);
1702
				$gsObj->postUnserialize();
1703
			}
1704
		}
1705
1706
		return isset($gsObj) && is_object($gsObj) ? $gsObj : $state;
1707
	}
1708
1709
	/**
1710
	 * Writes ta state to for a key and counter.
1711
	 *
1712
	 * @param mixed  $state
1713
	 * @param string $devid   the device id
1714
	 * @param string $type    the state type
1715
	 * @param string $key     (opt)
1716
	 * @param int    $counter (opt)
1717
	 *
1718
	 * @return bool
1719
	 *
1720
	 * @throws StateInvalidException, UnavailableException
1721
	 */
1722
	public function SetState($state, $devid, $type, $key = false, $counter = false) {
1723
		return $this->setStateMessage($state, $devid, $type, $key, $counter);
0 ignored issues
show
Bug introduced by
It seems like $counter can also be of type false; however, parameter $counter of Grommunio::setStateMessage() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

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

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

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

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

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

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

1754
		$restriction = $this->getStateMessageRestriction($messageName, /** @scrutinizer ignore-type */ $counter, $thisCounterOnly);
Loading history...
1755
		$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...
1756
		if ($stateFolderContents) {
1757
			mapi_table_restrict($stateFolderContents, $restriction);
1758
			$rowCnt = mapi_table_getrowcount($stateFolderContents);
1759
			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

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

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

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

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

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

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

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

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

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

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

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

2559
		$folderName = "grommunio-sync Search Folder " . /** @scrutinizer ignore-type */ @getmypid();
Loading history...
2560
		$searchFolders = mapi_folder_gethierarchytable($searchFolderRoot);
2561
		$restriction = [
2562
			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...
2563
			[
2564
				FUZZYLEVEL => FL_PREFIX,
0 ignored issues
show
Bug introduced by
The constant FUZZYLEVEL was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
Bug introduced by
The constant FL_PREFIX was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2565
				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...
2566
				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...
2567
			],
2568
		];
2569
		// restrict the hierarchy to the grommunio-sync search folder only
2570
		mapi_table_restrict($searchFolders, $restriction);
2571
		if (mapi_table_getrowcount($searchFolders)) {
2572
			$searchFolder = mapi_table_queryrows($searchFolders, [PR_ENTRYID], 0, 1);
2573
2574
			return mapi_msgstore_openentry($this->store, $searchFolder[0][PR_ENTRYID]);
2575
		}
2576
2577
		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

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

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

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

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

3030
			$ab_entryid = /** @scrutinizer ignore-call */ mapi_ab_getdefaultdir($addrbook);
Loading history...
3031
			$ab_dir = mapi_ab_openentry($addrbook, $ab_entryid);
3032
3033
			return $ab_dir;
3034
		}
3035
		catch (MAPIException $e) {
3036
			SLog::Write(LOGLEVEL_ERROR, sprintf("Grommunio->getAddressbookDir(): Unable to open addressbook: %s", $e));
3037
		}
3038
3039
		return false;
3040
	}
3041
3042
	/**
3043
	 * Checks if the user is not disabled for grommunio-sync.
3044
	 *
3045
	 * @return bool
3046
	 *
3047
	 * @throws FatalException if user is disabled for grommunio-sync
3048
	 */
3049
	private function isGSyncEnabled() {
3050
		$addressbook = $this->getAddressbook();
3051
		// this check needs to be performed on the store of the main (authenticated) user
3052
		$store = $this->storeCache[$this->mainUser];
3053
		$userEntryid = mapi_getprops($store, [PR_MAILBOX_OWNER_ENTRYID]);
0 ignored issues
show
Bug introduced by
The constant PR_MAILBOX_OWNER_ENTRYID was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3054
		$mailuser = mapi_ab_openentry($addressbook, $userEntryid[PR_MAILBOX_OWNER_ENTRYID]);
3055
		$enabledFeatures = mapi_getprops($mailuser, [PR_EC_DISABLED_FEATURES]);
0 ignored issues
show
Bug introduced by
The constant PR_EC_DISABLED_FEATURES was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
3056
		if (isset($enabledFeatures[PR_EC_DISABLED_FEATURES]) && is_array($enabledFeatures[PR_EC_DISABLED_FEATURES])) {
3057
			$mobileDisabled = in_array(self::MOBILE_ENABLED, $enabledFeatures[PR_EC_DISABLED_FEATURES]);
3058
			$deviceId = Request::GetDeviceID();
3059
			// Checks for deviceId present in zarafaDisabledFeatures LDAP array attribute. Check is performed case insensitive.
3060
			$deviceIdDisabled = (($deviceId !== null) && in_array($deviceId, array_map('strtolower', $enabledFeatures[PR_EC_DISABLED_FEATURES]))) ? true : false;
3061
			if ($mobileDisabled) {
3062
				throw new FatalException("User is disabled for grommunio-sync.");
3063
			}
3064
			if ($deviceIdDisabled) {
3065
				throw new FatalException(sprintf("User has deviceId %s disabled for usage with grommunio-sync.", $deviceId));
3066
			}
3067
		}
3068
3069
		return true;
3070
	}
3071
}
3072