DeviceManager   F
last analyzed

Complexity

Total Complexity 149

Size/Duplication

Total Lines 1112
Duplicated Lines 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
eloc 354
dl 0
loc 1112
rs 2
c 5
b 0
f 0
wmc 149

49 Methods

Rating   Name   Duplication   Size   Complexity  
A GetStateManager() 0 2 1
A SetSupportedFields() 0 2 1
A GetSupportedFields() 0 2 1
B GetWindowSize() 0 28 8
A SetWindowSize() 0 4 1
C GetFilterType() 0 52 15
A ForceFullResync() 0 12 2
A ForceFolderResync() 0 7 1
A getAdditionalFoldersHash() 0 2 1
A IsHierarchySyncRequired() 0 25 5
A InitializeFolderCache() 0 4 1
A setLatestFolder() 0 10 2
A CheckHearbeatStateIntegrity() 0 2 1
A SentData() 0 6 1
A GetFolderClassFromCacheByID() 0 13 3
A GetFolderIdForBackendId() 0 6 2
A AnnounceProcessAsPush() 0 4 2
A AnnounceProcessException() 0 2 1
A GetHierarchyChangesWrapper() 0 2 1
A __construct() 0 32 2
A announceAcceptedMessage() 0 8 2
A RemoveBrokenMessage() 0 9 2
A SetFolderSyncComplete() 0 4 1
A ClearLoopDetectionData() 0 7 3
A SetHeartbeatStateIntegrity() 0 2 1
C loadDeviceData() 0 49 12
A SetDevice() 0 4 1
A DoNotStreamMessage() 0 31 5
A GetDevid() 0 2 1
A HasFolderSyncStatus() 0 10 2
A GetFolderTypeFromCacheById() 0 2 1
B SetFolderSyncStatus() 0 19 8
A checkBrokenMessages() 0 7 2
A DoAutomaticASDeviceSaving() 0 3 1
A GetUserAgent() 0 2 1
B AnnounceIgnoredMessage() 0 36 6
A GetFolderSyncComplete() 0 2 1
A GetBackendIdForFolderId() 0 10 2
C Save() 0 51 12
A AnnounceASVersion() 0 6 1
A GetAdditionalUserSyncFolderStore() 0 14 4
A GetAdditionalUserSyncFolders() 0 23 5
B GetFolderIdFromCacheByClass() 0 26 9
A getLatestFolder() 0 2 1
A SaveDeviceInformation() 0 16 5
A BuildSyncFolderObject() 0 13 1
A AnnounceProcessStatus() 0 2 1
A IsHierarchyFullResyncRequired() 0 10 2
A CheckFolderData() 0 17 4

How to fix   Complexity   

Complex Class

Complex classes like DeviceManager often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DeviceManager, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/*
4
 * SPDX-License-Identifier: AGPL-3.0-only
5
 * SPDX-FileCopyrightText: Copyright 2007-2016 Zarafa Deutschland GmbH
6
 * SPDX-FileCopyrightText: Copyright 2020-2024 grommunio GmbH
7
 *
8
 * Manages device relevant data, loop detection and device states.
9
 * The DeviceManager uses a IStateMachine implementation with
10
 * IStateMachine::DEVICEDATA to save device relevant data.
11
 *
12
 * In order to update device information in redis, DeviceManager
13
 * implements InterProcessData.
14
 */
15
16
class DeviceManager extends InterProcessData {
17
	// broken message indicators
18
	public const MSG_BROKEN_UNKNOWN = 1;
19
	public const MSG_BROKEN_CAUSINGLOOP = 2;
20
	public const MSG_BROKEN_SEMANTICERR = 4;
21
22
	public const FLD_SYNC_INITIALIZED = 1;
23
	public const FLD_SYNC_INPROGRESS = 2;
24
	public const FLD_SYNC_COMPLETED = 4;
25
26
	// new types need to be added to Request::HEX_EXTENDED2 filter
27
	public const FLD_ORIGIN_USER = "U";
28
	public const FLD_ORIGIN_CONFIG = "C";
29
	public const FLD_ORIGIN_SHARED = "S";
30
	public const FLD_ORIGIN_GAB = "G";
31
	public const FLD_ORIGIN_IMPERSONATED = "I";
32
33
	public const FLD_FLAGS_NONE = 0;
34
	public const FLD_FLAGS_SENDASOWNER = 1;
35
	public const FLD_FLAGS_TRACKSHARENAME = 2;
36
	public const FLD_FLAGS_CALENDARREMINDERS = 4;
37
38
	private $device;
39
	private $deviceHash;
40
	private $saveDevice;
41
	private $statemachine;
42
	private $stateManager;
43
	private $incomingData = 0;
44
	private $outgoingData = 0;
45
46
	private $windowSize;
47
	private $latestFolder;
48
49
	private $loopdetection;
50
	private $hierarchySyncRequired;
51
	private $additionalFoldersHash;
52
53
	/**
54
	 * Constructor.
55
	 */
56
	public function __construct() {
57
		$this->statemachine = GSync::GetStateMachine();
58
		$this->deviceHash = false;
59
		$this->saveDevice = true;
60
		$this->windowSize = [];
61
		$this->latestFolder = false;
62
		$this->hierarchySyncRequired = false;
63
64
		// initialize InterProcess parameters
65
		$this->allocate = 0;
66
		$this->type = "grommunio-sync:devicesuser";
67
		parent::__construct();
68
		parent::initializeParams();
69
		$this->stateManager = new StateManager();
70
71
		// only continue if deviceid is set
72
		if (self::$devid) {
73
			$this->device = new ASDevice();
74
			$this->device->Initialize(self::$devid, Request::GetDeviceType(), Request::GetGETUser(), Request::GetUserAgent());
75
			$this->loadDeviceData();
76
77
			GSync::GetTopCollector()->SetUserAgent($this->device->GetDeviceUserAgent());
78
		}
79
		else {
80
			throw new FatalNotImplementedException("Can not proceed without a device id.");
81
		}
82
83
		$this->loopdetection = new LoopDetection();
84
		$this->loopdetection->ProcessLoopDetectionInit();
85
		$this->loopdetection->ProcessLoopDetectionPreviousConnectionFailed();
86
87
		$this->additionalFoldersHash = $this->getAdditionalFoldersHash();
88
	}
89
90
	/**
91
	 * Load another different device.
92
	 *
93
	 * @param ASDevice $asDevice
94
	 */
95
	public function SetDevice($asDevice) {
96
		// TODO: this is broken and callers should be removed/updated. ASDevice is now always overwritten.
97
		$this->device = $asDevice;
98
		$this->loadDeviceData();
99
		// $this->stateManager->SetDevice($this->device);
100
	}
101
102
	/**
103
	 * Returns the StateManager for the current device.
104
	 *
105
	 * @return StateManager
106
	 */
107
	public function GetStateManager() {
108
		return $this->stateManager;
109
	}
110
111
	/*----------------------------------------------------------------------------------------------------------
112
	 * Device operations
113
	 */
114
115
	/**
116
	 * Announces amount of transmitted data to the DeviceManager.
117
	 *
118
	 * @param int $datacounter
119
	 *
120
	 * @return bool
121
	 */
122
	public function SentData($datacounter) {
123
		// TODO save this somewhere
124
		$this->incomingData = Request::GetContentLength();
125
		$this->outgoingData = $datacounter;
126
127
		return true;
128
	}
129
130
	/**
131
	 * Called at the end of the request
132
	 * Statistics about received/sent data is saved here.
133
	 *
134
	 * @return bool
135
	 */
136
	public function Save() {
137
		// TODO save other stuff
138
139
		// check if previousily ignored messages were synchronized for the current folder
140
		// on multifolder operations of AS14 this is done by setLatestFolder()
141
		if ($this->latestFolder !== false) {
142
			$this->checkBrokenMessages($this->latestFolder);
0 ignored issues
show
Bug introduced by
$this->latestFolder of type true is incompatible with the type string expected by parameter $folderid of DeviceManager::checkBrokenMessages(). ( Ignorable by Annotation )

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

142
			$this->checkBrokenMessages(/** @scrutinizer ignore-type */ $this->latestFolder);
Loading history...
143
		}
144
145
		// update the user agent and AS version on the device
146
		$this->device->SetUserAgent(Request::GetUserAgent());
147
		$this->device->SetASVersion(Request::GetProtocolVersion());
148
149
		// data to be saved
150
		if ($this->device->IsDataChanged() && Request::IsValidDeviceID() && $this->saveDevice) {
151
			SLog::Write(LOGLEVEL_DEBUG, "DeviceManager->Save(): Device data changed");
152
153
			try {
154
				// check if this is the first time the device data is saved and it is authenticated. If so, link the user to the device id
155
				if ($this->device->IsNewDevice() && RequestProcessor::isUserAuthenticated()) {
156
					SLog::Write(LOGLEVEL_INFO, sprintf("Linking device ID '%s' to user '%s'", self::$devid, $this->device->GetDeviceUser()));
157
					$this->statemachine->LinkUserDevice($this->device->GetDeviceUser(), self::$devid);
158
				}
159
160
				if (RequestProcessor::isUserAuthenticated() || $this->device->GetForceSave()) {
161
					$this->device->lastupdatetime = time();
162
					$this->device->StripData();
163
					$this->statemachine->SetState($this->device, self::$devid, IStateMachine::DEVICEDATA);
164
165
					// update deviceuser stat in redis as well
166
					$this->setDeviceUserData($this->type, [self::$user => $this->device], self::$devid, -1, $doCas = "merge");
167
					SLog::Write(LOGLEVEL_DEBUG, "DeviceManager->Save(): Device data saved");
168
				}
169
			}
170
			catch (StateNotFoundException $snfex) {
171
				SLog::Write(LOGLEVEL_ERROR, "DeviceManager->Save(): Exception: " . $snfex->getMessage());
172
			}
173
		}
174
175
		// remove old search data
176
		$oldpid = $this->loopdetection->ProcessLoopDetectionGetOutdatedSearchPID();
177
		if ($oldpid) {
178
			GSync::GetBackend()->GetSearchProvider()->TerminateSearch($oldpid);
179
		}
180
181
		// we terminated this process
182
		if ($this->loopdetection) {
183
			$this->loopdetection->ProcessLoopDetectionTerminate();
184
		}
185
186
		return true;
187
	}
188
189
	/**
190
	 * Sets if the AS Device should automatically be saved when terminating the request.
191
	 *
192
	 * @param bool $doSave
193
	 */
194
	public function DoAutomaticASDeviceSaving($doSave) {
195
		SLog::Write(LOGLEVEL_DEBUG, "DeviceManager->DoAutomaticASDeviceSaving(): save automatically: " . Utils::PrintAsString($doSave));
196
		$this->saveDevice = $doSave;
197
	}
198
199
	/**
200
	 * Newer mobiles send extensive device information with the Settings command
201
	 * These information are saved in the ASDevice.
202
	 *
203
	 * @param SyncDeviceInformation $deviceinformation
204
	 *
205
	 * @return bool
206
	 */
207
	public function SaveDeviceInformation($deviceinformation) {
208
		SLog::Write(LOGLEVEL_DEBUG, "Saving submitted device information");
209
210
		// set the user agent
211
		if (isset($deviceinformation->useragent)) {
212
			$this->device->SetUserAgent($deviceinformation->useragent);
213
		}
214
215
		// save other information
216
		foreach (["model", "imei", "friendlyname", "os", "oslanguage", "phonenumber", "mobileoperator", "enableoutboundsms"] as $info) {
217
			if (isset($deviceinformation->{$info}) && $deviceinformation->{$info} != "") {
218
				$this->device->__set("device" . $info, $deviceinformation->{$info});
219
			}
220
		}
221
222
		return true;
223
	}
224
225
	/*----------------------------------------------------------------------------------------------------------
226
	 * LEGACY AS 1.0 and WRAPPER operations
227
	 */
228
229
	/**
230
	 * Returns a wrapped Importer & Exporter to use the
231
	 * HierarchyChache.
232
	 *
233
	 * @see ChangesMemoryWrapper
234
	 *
235
	 * @return object HierarchyCache
236
	 */
237
	public function GetHierarchyChangesWrapper() {
238
		return $this->device->GetHierarchyCache();
239
	}
240
241
	/**
242
	 * Initializes the HierarchyCache for legacy syncs
243
	 * this is for AS 1.0 compatibility:
244
	 *      save folder information synched with GetHierarchy().
245
	 *
246
	 * @param string $folders Array with folder information
247
	 *
248
	 * @return bool
249
	 */
250
	public function InitializeFolderCache($folders) {
251
		$this->stateManager->SetDevice($this->device);
252
253
		return $this->stateManager->InitializeFolderCache($folders);
254
	}
255
256
	/**
257
	 * Returns the ActiveSync folder type for a FolderID.
258
	 *
259
	 * @param string $folderid
260
	 *
261
	 * @return bool|int boolean if no type is found
262
	 */
263
	public function GetFolderTypeFromCacheById($folderid) {
264
		return $this->device->GetFolderType($folderid);
265
	}
266
267
	/**
268
	 * Returns a FolderID of default classes
269
	 * this is for AS 1.0 compatibility:
270
	 *      this information was made available during GetHierarchy().
271
	 *
272
	 * @param string $class The class requested
273
	 *
274
	 * @return string
275
	 *
276
	 * @throws NoHierarchyCacheAvailableException
277
	 */
278
	public function GetFolderIdFromCacheByClass($class) {
279
		$folderidforClass = false;
280
		// look at the default foldertype for this class
281
		$type = GSync::getDefaultFolderTypeFromFolderClass($class);
282
283
		if ($type && $type > SYNC_FOLDER_TYPE_OTHER && $type < SYNC_FOLDER_TYPE_USER_MAIL) {
284
			$folderids = $this->device->GetAllFolderIds();
285
			foreach ($folderids as $folderid) {
286
				if ($type == $this->device->GetFolderType($folderid)) {
287
					$folderidforClass = $folderid;
288
289
					break;
290
				}
291
			}
292
293
			// Old Palm Treos always do initial sync for calendar and contacts, even if they are not made available by the backend.
294
			// We need to fake these folderids, allowing a fake sync/ping, even if they are not supported by the backend
295
			// if the folderid would be available, they would already be returned in the above statement
296
			if ($folderidforClass == false && ($type == SYNC_FOLDER_TYPE_APPOINTMENT || $type == SYNC_FOLDER_TYPE_CONTACT)) {
297
				$folderidforClass = SYNC_FOLDER_TYPE_DUMMY;
298
			}
299
		}
300
301
		SLog::Write(LOGLEVEL_DEBUG, sprintf("DeviceManager->GetFolderIdFromCacheByClass('%s'): '%s' => '%s'", $class, $type, $folderidforClass));
302
303
		return $folderidforClass;
304
	}
305
306
	/**
307
	 * Returns a FolderClass for a FolderID which is known to the mobile.
308
	 *
309
	 * @param string $folderid
310
	 *
311
	 * @return int
312
	 *
313
	 * @throws NoHierarchyCacheAvailableException, NotImplementedException
314
	 */
315
	public function GetFolderClassFromCacheByID($folderid) {
316
		// TODO check if the parent folder exists and is also being synchronized
317
		$typeFromCache = $this->device->GetFolderType($folderid);
318
		if ($typeFromCache === false) {
319
			throw new NoHierarchyCacheAvailableException(sprintf("Folderid '%s' is not fully synchronized on the device", $folderid));
320
		}
321
322
		$class = GSync::GetFolderClassFromFolderType($typeFromCache);
323
		if ($class === false) {
324
			throw new NotImplementedException(sprintf("Folderid '%s' is saved to be of type '%d' but this type is not implemented", $folderid, $typeFromCache));
325
		}
326
327
		return $class;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $class returns the type string which is incompatible with the documented return type integer.
Loading history...
328
	}
329
330
	/**
331
	 * Returns the additional folders as SyncFolder objects.
332
	 *
333
	 * @return array of SyncFolder with backendids as keys
334
	 */
335
	public function GetAdditionalUserSyncFolders() {
336
		$folders = [];
337
338
		// In impersonated stores, no additional folders will be synchronized
339
		if (Request::GetImpersonatedUser()) {
340
			return $folders;
341
		}
342
		$shares = $this->device->GetAdditionalFolders() + SharedFolders::GetSharedFolders();
343
		foreach ($shares as $df) {
344
			if (!isset($df['flags'])) {
345
				$df['flags'] = 0;
346
				SLog::Write(LOGLEVEL_WARN, sprintf("DeviceManager->GetAdditionalUserSyncFolders(): Additional folder '%s' has no flags.", $df['name']));
347
			}
348
			if (!isset($df['parentid'])) {
349
				$df['parentid'] = '0';
350
				SLog::Write(LOGLEVEL_WARN, sprintf("DeviceManager->GetAdditionalUserSyncFolders(): Additional folder '%s' has no parentid.", $df['name']));
351
			}
352
353
			$folder = $this->BuildSyncFolderObject($df['store'], $df['folderid'], $df['parentid'], $df['name'], $df['type'], $df['flags'], DeviceManager::FLD_ORIGIN_SHARED);
354
			$folders[$folder->BackendId] = $folder;
355
		}
356
357
		return $folders;
358
	}
359
360
	/**
361
	 * Get the store of an additional folder.
362
	 *
363
	 * @param string $folderid
364
	 *
365
	 * @return bool|string
366
	 */
367
	public function GetAdditionalUserSyncFolderStore($folderid) {
368
		$sfolders = SharedFolders::GetSharedFolders();
369
		if (isset($sfolders[$folderid])) {
370
			$f = $sfolders[$folderid];
371
		}
372
		else {
373
			$f = $this->device->GetAdditionalFolder($folderid);
374
		}
375
376
		if ($f && isset($f['store'])) {
377
			return $f['store'];
378
		}
379
380
		return false;
381
	}
382
383
	/**
384
	 * Checks if the message should be streamed to a mobile
385
	 * Should always be called before a message is sent to the mobile
386
	 * Returns true if there is something wrong and the content could break the
387
	 * synchronization.
388
	 *
389
	 * @param string     $id       message id
390
	 * @param SyncObject &$message the method could edit the message to change the flags
391
	 *
392
	 * @return bool returns true if the message should NOT be send!
393
	 */
394
	public function DoNotStreamMessage($id, &$message) {
395
		$folderid = $this->getLatestFolder();
396
397
		if (isset($message->parentid)) {
398
			$folder = $message->parentid;
0 ignored issues
show
Unused Code introduced by
The assignment to $folder is dead and can be removed.
Loading history...
399
		}
400
401
		// message was identified to be causing a loop
402
		if ($this->loopdetection->IgnoreNextMessage(true, $id, $folderid)) {
403
			$this->AnnounceIgnoredMessage($folderid, $id, $message, self::MSG_BROKEN_CAUSINGLOOP);
404
405
			return true;
406
		}
407
408
		// message is semantically incorrect
409
		if (!$message->Check(true)) {
410
			$this->AnnounceIgnoredMessage($folderid, $id, $message, self::MSG_BROKEN_SEMANTICERR);
411
412
			return true;
413
		}
414
415
		// check if this message is broken
416
		if ($this->device->HasIgnoredMessage($folderid, $id)) {
417
			// reset the flags so the message is always streamed with <Add>
418
			$message->flags = false;
419
420
			// track the broken message in the loop detection
421
			$this->loopdetection->SetBrokenMessage($folderid, $id);
422
		}
423
424
		return false;
425
	}
426
427
	/**
428
	 * Removes device information about a broken message as it is been removed from the mobile.
429
	 *
430
	 * @param string $id message id
431
	 *
432
	 * @return bool
433
	 */
434
	public function RemoveBrokenMessage($id) {
435
		$folderid = $this->getLatestFolder();
436
		if ($this->device->RemoveIgnoredMessage($folderid, $id)) {
437
			SLog::Write(LOGLEVEL_INFO, sprintf("DeviceManager->RemoveBrokenMessage('%s', '%s'): cleared data about previously ignored message", $folderid, $id));
438
439
			return true;
440
		}
441
442
		return false;
443
	}
444
445
	/**
446
	 * Amount of items to me synchronized.
447
	 *
448
	 * @param string $folderid
449
	 * @param mixed  $uuid
450
	 * @param mixed  $statecounter
451
	 * @param mixed  $queuedmessages
452
	 *
453
	 * @return int
454
	 */
455
	public function GetWindowSize($folderid, $uuid, $statecounter, $queuedmessages) {
456
		if (isset($this->windowSize[$folderid])) {
457
			$items = $this->windowSize[$folderid];
458
		}
459
		else {
460
			$items = WINDOW_SIZE_MAX;
461
		} // 512 by default
462
463
		$this->setLatestFolder($folderid);
464
465
		// detect if this is a loop condition
466
		$loop = $this->loopdetection->Detect($folderid, $uuid, $statecounter, $items, $queuedmessages);
467
		if ($loop !== false) {
468
			if ($loop === true) {
469
				$items = ($items == 0) ? 0 : 1 + ($this->loopdetection->IgnoreNextMessage(false) ? 1 : 0);
470
			}
471
			else {
472
				// we got a new suggested window size
473
				$items = $loop;
474
				SLog::Write(LOGLEVEL_DEBUG, sprintf("Mobile loop pre stage detected! Forcing smaller window size of %d before entering loop detection mode", $items));
475
			}
476
		}
477
478
		if ($items >= 0 && $items <= 2) {
479
			SLog::Write(LOGLEVEL_INFO, sprintf("Mobile loop detected! Messages sent to the mobile will be restricted to %d items in order to identify the conflict", $items));
480
		}
481
482
		return $items;
483
	}
484
485
	/**
486
	 * Sets the amount of items the device is requesting.
487
	 *
488
	 * @param string $folderid
489
	 * @param int    $maxItems
490
	 *
491
	 * @return bool
492
	 */
493
	public function SetWindowSize($folderid, $maxItems) {
494
		$this->windowSize[$folderid] = $maxItems;
495
496
		return true;
497
	}
498
499
	/**
500
	 * Sets the supported fields transmitted by the device for a certain folder.
501
	 *
502
	 * @param string $folderid
503
	 * @param array  $fieldlist supported fields
504
	 *
505
	 * @return bool
506
	 */
507
	public function SetSupportedFields($folderid, $fieldlist) {
508
		return $this->device->SetSupportedFields($folderid, $fieldlist);
509
	}
510
511
	/**
512
	 * Gets the supported fields transmitted previously by the device
513
	 * for a certain folder.
514
	 *
515
	 * @param string $folderid
516
	 *
517
	 * @return array@boolean
0 ignored issues
show
Documentation Bug introduced by
The doc comment array@boolean at position 0 could not be parsed: Unknown type name 'array@boolean' at position 0 in array@boolean.
Loading history...
518
	 */
519
	public function GetSupportedFields($folderid) {
520
		return $this->device->GetSupportedFields($folderid);
521
	}
522
523
	/**
524
	 * Returns the maximum filter type for a folder.
525
	 * This might be limited globally, per device or per folder.
526
	 *
527
	 * @param string $folderid
528
	 * @param mixed  $backendFolderId
529
	 *
530
	 * @return int
531
	 */
532
	public function GetFilterType($folderid, $backendFolderId) {
533
		global $specialSyncFilter;
534
		// either globally configured SYNC_FILTERTIME_MAX or ALL (no limit)
535
		$maxAllowed = (defined('SYNC_FILTERTIME_MAX') && SYNC_FILTERTIME_MAX > SYNC_FILTERTYPE_ALL) ? SYNC_FILTERTIME_MAX : SYNC_FILTERTYPE_ALL;
536
537
		// TODO we could/should check for a specific value for the folder, if it's available
538
		$maxDevice = $this->device->GetSyncFilterType();
539
540
		// ALL has a value of 0, all limitations have higher integer values, see SYNC_FILTERTYPE_ALL definition
541
		if ($maxDevice !== false && $maxDevice > SYNC_FILTERTYPE_ALL && ($maxAllowed == SYNC_FILTERTYPE_ALL || $maxDevice < $maxAllowed)) {
542
			$maxAllowed = $maxDevice;
543
		}
544
545
		if (is_array($specialSyncFilter)) {
546
			$store = GSync::GetAdditionalSyncFolderStore($backendFolderId);
547
			// the store is only available when this is a shared folder (but might also be statically configured)
548
			if ($store) {
549
				$origin = Utils::GetFolderOriginFromId($folderid);
550
				// do not limit when the owner or impersonated user is syncing!
551
				if ($origin == DeviceManager::FLD_ORIGIN_USER || $origin == DeviceManager::FLD_ORIGIN_IMPERSONATED) {
552
					SLog::Write(LOGLEVEL_DEBUG, "Not checking for specific sync limit as this is the owner/impersonated user.");
553
				}
554
				else {
555
					$spKey = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $spKey is dead and can be removed.
Loading history...
556
					$spFilter = false;
557
					// 1. step: check if there is a general limitation for the store
558
					if (array_key_exists($store, $specialSyncFilter)) {
559
						$spFilter = $specialSyncFilter[$store];
560
						SLog::Write(LOGLEVEL_DEBUG, sprintf("Limit sync due to configured limitation on the store: '%s': %s", $store, $spFilter));
561
					}
562
563
					// 2. step: check if there is a limitation for the hashed ID (for shared/configured stores)
564
					$spKey = $store . '/' . $folderid;
565
					if (array_key_exists($spKey, $specialSyncFilter)) {
566
						$spFilter = $specialSyncFilter[$spKey];
567
						SLog::Write(LOGLEVEL_DEBUG, sprintf("Limit sync due to configured limitation on the folder: '%s': %s", $spKey, $spFilter));
568
					}
569
570
					// 3. step: check if there is a limitation for the backendId
571
					$spKey = $store . '/' . $backendFolderId;
572
					if (array_key_exists($spKey, $specialSyncFilter)) {
573
						$spFilter = $specialSyncFilter[$spKey];
574
						SLog::Write(LOGLEVEL_DEBUG, sprintf("Limit sync due to configured limitation on the folder: '%s': %s", $spKey, $spFilter));
575
					}
576
					if ($spFilter) {
577
						$maxAllowed = $spFilter;
578
					}
579
				}
580
			}
581
		}
582
583
		return $maxAllowed;
584
	}
585
586
	/**
587
	 * Removes all linked states of a specific folder.
588
	 * During next request the folder is resynchronized.
589
	 *
590
	 * @param string $folderid
591
	 *
592
	 * @return bool
593
	 */
594
	public function ForceFolderResync($folderid) {
595
		SLog::Write(LOGLEVEL_INFO, sprintf("DeviceManager->ForceFolderResync('%s'): folder resync", $folderid));
596
597
		// delete folder states
598
		StateManager::UnLinkState($this->device, $folderid);
599
600
		return true;
601
	}
602
603
	/**
604
	 * Removes all linked states from a device.
605
	 * During next requests a full resync is triggered.
606
	 *
607
	 * @return bool
608
	 */
609
	public function ForceFullResync() {
610
		SLog::Write(LOGLEVEL_INFO, "Full device resync requested");
611
612
		// delete all other uuids
613
		foreach ($this->device->GetAllFolderIds() as $folderid) {
614
			$uuid = StateManager::UnLinkState($this->device, $folderid);
0 ignored issues
show
Unused Code introduced by
The assignment to $uuid is dead and can be removed.
Loading history...
615
		}
616
617
		// delete hierarchy states
618
		StateManager::UnLinkState($this->device, false);
0 ignored issues
show
Bug introduced by
false of type false is incompatible with the type string expected by parameter $folderid of StateManager::UnLinkState(). ( Ignorable by Annotation )

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

618
		StateManager::UnLinkState($this->device, /** @scrutinizer ignore-type */ false);
Loading history...
619
620
		return true;
621
	}
622
623
	/**
624
	 * Indicates if the hierarchy should be resynchronized based on the general folder state and
625
	 * if additional folders changed.
626
	 *
627
	 * @return bool
628
	 */
629
	public function IsHierarchySyncRequired() {
630
		$this->loadDeviceData();
631
632
		if ($this->loopdetection->ProcessLoopDetectionIsHierarchySyncAdvised()) {
633
			return true;
634
		}
635
636
		// if the hash of the additional folders changed, we have to sync the hierarchy
637
		if ($this->additionalFoldersHash != $this->getAdditionalFoldersHash()) {
638
			$this->hierarchySyncRequired = true;
639
		}
640
641
		// check if a hierarchy sync might be necessary
642
		$huuid = $this->device->GetFolderUUID(false);
643
		if ($huuid === false) {
644
			$this->hierarchySyncRequired = true;
645
		}
646
		// check if the hierarchy state is really available
647
		else {
648
			if ($this->statemachine->GetStateHash(self::$devid, IStateMachine::FOLDERDATA, $huuid) == "0") {
649
				$this->hierarchySyncRequired = true;
650
			}
651
		}
652
653
		return $this->hierarchySyncRequired;
654
	}
655
656
	private function getAdditionalFoldersHash() {
657
		return md5(serialize($this->device->GetAdditionalFolders() + array_values(SharedFolders::GetSharedFolders())));
658
	}
659
660
	/**
661
	 * Indicates if a full hierarchy resync should be triggered due to loops.
662
	 *
663
	 * @return bool
664
	 */
665
	public function IsHierarchyFullResyncRequired() {
666
		// do not check for loop detection, if the foldersync is not yet complete
667
		if ($this->GetFolderSyncComplete() === false) {
668
			SLog::Write(LOGLEVEL_INFO, "DeviceManager->IsHierarchyFullResyncRequired(): aborted, as exporting of folders has not yet completed");
669
670
			return false;
671
		}
672
673
		// check for potential process loops
674
		return $this->loopdetection->ProcessLoopDetectionIsHierarchyResyncRequired();
675
	}
676
677
	/**
678
	 * Adds an Exceptions to the process tracking.
679
	 *
680
	 * @param Exception $exception
681
	 *
682
	 * @return bool
683
	 */
684
	public function AnnounceProcessException($exception) {
685
		return $this->loopdetection->ProcessLoopDetectionAddException($exception);
686
	}
687
688
	/**
689
	 * Adds a non-ok status for a folderid to the process tracking.
690
	 * On 'false' a hierarchy status is assumed.
691
	 *
692
	 * @param mixed $folderid
693
	 * @param mixed $status
694
	 *
695
	 * @return bool
696
	 */
697
	public function AnnounceProcessStatus($folderid, $status) {
698
		return $this->loopdetection->ProcessLoopDetectionAddStatus($folderid, $status);
699
	}
700
701
	/**
702
	 * Announces that the current process is a push connection to the process loop
703
	 * detection and to the Top collector.
704
	 *
705
	 * @return bool
706
	 */
707
	public function AnnounceProcessAsPush() {
708
		SLog::Write(LOGLEVEL_DEBUG, "Announce process as PUSH connection");
709
710
		return $this->loopdetection->ProcessLoopDetectionSetAsPush() && GSync::GetTopCollector()->SetAsPushConnection();
711
	}
712
713
	/**
714
	 * Checks if the given counter for a certain uuid+folderid was already exported or modified.
715
	 * This is called when a heartbeat request found changes to make sure that the same
716
	 * changes are not exported twice, as during the heartbeat there could have been a normal
717
	 * sync request.
718
	 *
719
	 * @param string $folderid folder id
720
	 * @param string $uuid     synkkey
721
	 * @param string $counter  synckey counter
722
	 *
723
	 * @return bool indicating if an uuid+counter were exported (with changes) before
724
	 */
725
	public function CheckHearbeatStateIntegrity($folderid, $uuid, $counter) {
726
		return $this->loopdetection->IsSyncStateObsolete($folderid, $uuid, $counter);
727
	}
728
729
	/**
730
	 * Marks a syncstate as obsolete for Heartbeat, as e.g. an import was started using it.
731
	 *
732
	 * @param string $folderid folder id
733
	 * @param string $uuid     synkkey
734
	 * @param string $counter  synckey counter
735
	 */
736
	public function SetHeartbeatStateIntegrity($folderid, $uuid, $counter) {
737
		return $this->loopdetection->SetSyncStateUsage($folderid, $uuid, $counter);
738
	}
739
740
	/**
741
	 * Checks the data integrity of the data in the hierarchy cache and the data of the content data (synchronized folders).
742
	 * If a folder is deleted, the sync states could still be on the server (and being loaded by PING) while
743
	 * the folder is not being synchronized anymore.
744
	 *
745
	 * @return bool
746
	 */
747
	public function CheckFolderData() {
748
		SLog::Write(LOGLEVEL_DEBUG, "DeviceManager->CheckFolderData() checking integrity of hierarchy cache with synchronized folders");
749
750
		$hc = $this->device->GetHierarchyCache();
751
		$notInCache = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $notInCache is dead and can be removed.
Loading history...
752
		foreach ($this->device->GetAllFolderIds() as $folderid) {
753
			$uuid = $this->device->GetFolderUUID($folderid);
754
			if ($uuid) {
755
				// has a UUID but is not in the cache?! This is deleted, remove the states.
756
				if (!$hc->GetFolder($folderid)) {
757
					SLog::Write(LOGLEVEL_WARN, sprintf("DeviceManager->CheckFolderData(): Folder '%s' has sync states but is not in the hierarchy cache. Removing states.", $folderid));
758
					StateManager::UnLinkState($this->device, $folderid);
759
				}
760
			}
761
		}
762
763
		return true;
764
	}
765
766
	/**
767
	 * Sets the current status of the folder.
768
	 *
769
	 * @param string $folderid   folder id
770
	 * @param int    $statusflag current status: DeviceManager::FLD_SYNC_INITIALIZED, DeviceManager::FLD_SYNC_INPROGRESS, DeviceManager::FLD_SYNC_COMPLETED
771
	 */
772
	public function SetFolderSyncStatus($folderid, $statusflag) {
773
		$currentStatus = $this->device->GetFolderSyncStatus($folderid);
774
775
		// status available or just initialized
776
		if (isset($currentStatus->{ASDevice::FOLDERSYNCSTATUS}) || $statusflag == self::FLD_SYNC_INITIALIZED) {
777
			// only update if there is a change
778
			if ((!$currentStatus || (isset($currentStatus->{ASDevice::FOLDERSYNCSTATUS}) && $statusflag !== $currentStatus->{ASDevice::FOLDERSYNCSTATUS})) &&
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: (! $currentStatus || Iss...elf::FLD_SYNC_COMPLETED, Probably Intended Meaning: ! $currentStatus || (Iss...lf::FLD_SYNC_COMPLETED)
Loading history...
779
					$statusflag != self::FLD_SYNC_COMPLETED) {
780
				$this->device->SetFolderSyncStatus($folderid, $statusflag);
781
				SLog::Write(LOGLEVEL_DEBUG, sprintf("SetFolderSyncStatus(): set %s for %s", $statusflag, $folderid));
782
			}
783
			// if completed, remove the status
784
			elseif ($statusflag == self::FLD_SYNC_COMPLETED) {
785
				$this->device->SetFolderSyncStatus($folderid, false);
786
				SLog::Write(LOGLEVEL_DEBUG, sprintf("SetFolderSyncStatus(): completed for %s", $folderid));
787
			}
788
		}
789
790
		return true;
791
	}
792
793
	/**
794
	 * Indicates if a folder is synchronizing by the saved status.
795
	 *
796
	 * @param string $folderid folder id
797
	 *
798
	 * @return bool
799
	 */
800
	public function HasFolderSyncStatus($folderid) {
801
		$currentStatus = $this->device->GetFolderSyncStatus($folderid);
802
803
		// status available ?
804
		$hasStatus = isset($currentStatus->{ASDevice::FOLDERSYNCSTATUS});
805
		if ($hasStatus) {
806
			SLog::Write(LOGLEVEL_DEBUG, sprintf("HasFolderSyncStatus(): saved folder status for %s: %s", $folderid, $currentStatus->{ASDevice::FOLDERSYNCSTATUS}));
807
		}
808
809
		return $hasStatus;
810
	}
811
812
	/**
813
	 * Returns the indicator if the FolderSync was completed successfully  (all folders synchronized).
814
	 *
815
	 * @return bool
816
	 */
817
	public function GetFolderSyncComplete() {
818
		return $this->device->GetFolderSyncComplete();
819
	}
820
821
	/**
822
	 * Sets if the FolderSync was completed successfully (all folders synchronized).
823
	 *
824
	 * @param bool  $complete indicating if all folders were sent
825
	 * @param mixed $user
826
	 * @param mixed $devid
827
	 *
828
	 * @return bool
829
	 */
830
	public function SetFolderSyncComplete($complete, $user = false, $devid = false) {
0 ignored issues
show
Unused Code introduced by
The parameter $devid is not used and could be removed. ( Ignorable by Annotation )

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

830
	public function SetFolderSyncComplete($complete, $user = false, /** @scrutinizer ignore-unused */ $devid = false) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $user is not used and could be removed. ( Ignorable by Annotation )

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

830
	public function SetFolderSyncComplete($complete, /** @scrutinizer ignore-unused */ $user = false, $devid = false) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
831
		$this->device->SetFolderSyncComplete($complete);
832
833
		return true;
834
	}
835
836
	/**
837
	 * Removes the Loop detection data for a user & device.
838
	 *
839
	 * @param string $user
840
	 * @param string $devid
841
	 *
842
	 * @return bool
843
	 */
844
	public function ClearLoopDetectionData($user, $devid) {
845
		if ($user == false || $devid == false) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $devid of type string to the boolean false. If you are specifically checking for an empty string, consider using the more explicit === '' instead.
Loading history...
Bug Best Practice introduced by
It seems like you are loosely comparing $user of type string to the boolean false. If you are specifically checking for an empty string, consider using the more explicit === '' instead.
Loading history...
846
			return false;
847
		}
848
		SLog::Write(LOGLEVEL_DEBUG, sprintf("DeviceManager->ClearLoopDetectionData(): clearing data for user '%s' and device '%s'", $user, $devid));
849
850
		return $this->loopdetection->ClearData($user, $devid);
851
	}
852
853
	/**
854
	 * Indicates if the device needs an AS version update.
855
	 *
856
	 * @return bool
857
	 */
858
	public function AnnounceASVersion() {
859
		$latest = GSync::GetSupportedASVersion();
860
		$announced = $this->device->GetAnnouncedASversion();
861
		$this->device->SetAnnouncedASversion($latest);
862
863
		return $announced != $latest;
864
	}
865
866
	/**
867
	 * Returns the User Agent. This data is consolidated with data from Request::GetUserAgent()
868
	 * and the data saved in the ASDevice.
869
	 *
870
	 * @return string
871
	 */
872
	public function GetUserAgent() {
873
		return $this->device->GetDeviceUserAgent();
874
	}
875
876
	/**
877
	 * Returns the backend folder id from the AS folderid known to the mobile.
878
	 * If the id is not known, it's returned as is.
879
	 *
880
	 * @param mixed $folderid
881
	 *
882
	 * @return bool|int returns false if the type is not set
883
	 */
884
	public function GetBackendIdForFolderId($folderid) {
885
		$backendId = $this->device->GetFolderBackendId($folderid);
886
		if (!$backendId) {
887
			SLog::Write(LOGLEVEL_DEBUG, sprintf("DeviceManager->GetBackendIdForFolderId(): no backend-folderid available for '%s', returning as is.", $folderid));
888
889
			return $folderid;
890
		}
891
		SLog::Write(LOGLEVEL_DEBUG, sprintf("DeviceManager->GetBackendIdForFolderId(): folderid %s => %s", $folderid, $backendId));
892
893
		return $backendId;
894
	}
895
896
	/**
897
	 * Gets the AS folderid for a backendFolderId.
898
	 * If there is no known AS folderId a new one is being created.
899
	 *
900
	 * @param string $backendid          Backend folder id
901
	 * @param bool   $generateNewIdIfNew generates a new AS folderid for the case the backend folder is not known yet, default: false
902
	 * @param string $folderOrigin       Folder type is one of   'U' (user)
903
	 *                                   'C' (configured)
904
	 *                                   'S' (shared)
905
	 *                                   'G' (global address book)
906
	 *                                   'I' (impersonated)
907
	 * @param string $folderName         Folder name of the backend folder
908
	 *
909
	 * @return bool|string returns false if there is folderid known for this backendid and $generateNewIdIfNew is not set or false
910
	 */
911
	public function GetFolderIdForBackendId($backendid, $generateNewIdIfNew = false, $folderOrigin = self::FLD_ORIGIN_USER, $folderName = null) {
912
		if (!in_array($folderOrigin, [DeviceManager::FLD_ORIGIN_CONFIG, DeviceManager::FLD_ORIGIN_GAB, DeviceManager::FLD_ORIGIN_SHARED, DeviceManager::FLD_ORIGIN_USER, DeviceManager::FLD_ORIGIN_IMPERSONATED])) {
913
			SLog::Write(LOGLEVEL_WARN, sprintf("ASDevice->GetFolderIdForBackendId(): folder type '%s' is unknown in DeviceManager", $folderOrigin));
914
		}
915
916
		return $this->device->GetFolderIdForBackendId($backendid, $generateNewIdIfNew, $folderOrigin, $folderName);
917
	}
918
919
	/*----------------------------------------------------------------------------------------------------------
920
	 * private DeviceManager methods
921
	 */
922
923
	/**
924
	 * Loads devicedata from the StateMachine and loads it into the device.
925
	 *
926
	 * @return bool
927
	 */
928
	private function loadDeviceData() {
929
		if (!Request::IsValidDeviceID()) {
930
			return false;
931
		}
932
933
		try {
934
			$deviceHash = $this->statemachine->GetStateHash(self::$devid, IStateMachine::DEVICEDATA);
935
			if ($deviceHash != $this->deviceHash) {
936
				if ($this->deviceHash) {
937
					SLog::Write(LOGLEVEL_DEBUG, "DeviceManager->loadDeviceData(): Device data was changed, reloading");
938
				}
939
				$device = $this->statemachine->GetState(self::$devid, IStateMachine::DEVICEDATA);
940
				// TODO: case should be removed when removing ASDevice backwards compatibility
941
				// fallback for old grosync like devicedata
942
				if (($device instanceof StateObject) && isset($device->devices) && is_array($device->devices)) {
0 ignored issues
show
Bug Best Practice introduced by
The property devices does not exist on StateObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
943
					SLog::Write(LOGLEVEL_INFO, "Found old style device, converting...");
944
					[$_deviceuser, $_domain] = Utils::SplitDomainUser(Request::GetGETUser());
945
					if (!isset($device->data->devices[$_deviceuser])) {
0 ignored issues
show
Bug Best Practice introduced by
The property $data is declared protected in StateObject. Since you implement __get, consider adding a @property or @property-read.
Loading history...
946
						SLog::Write(LOGLEVEL_INFO, "Using old style device for this request and updating when concluding");
947
						$device = $device->devices[$_deviceuser];
948
						$device->lastupdatetime = time();
949
					}
950
					else {
951
						SLog::Write(LOGLEVEL_WARN, sprintf("Could not find '%s' in device state. Dropping previous device state!", $_deviceuser));
952
					}
953
				}
954
				if (method_exists($device, 'LoadedDevice')) {
955
					$this->device = $device;
956
					$this->device->LoadedDevice();
957
					$this->deviceHash = $deviceHash;
958
				}
959
				else {
960
					SLog::Write(LOGLEVEL_WARN, "Loaded device is not a device object. Dropping new loaded state and keeping initialized object!");
961
				}
962
			}
963
		}
964
		catch (StateNotFoundException) {
965
			$this->hierarchySyncRequired = true;
966
		}
967
		catch (UnavailableException) {
968
			// This is temporary and can be ignored e.g. in PING.
969
			// If the hash was not available before we treat it like a StateNotFoundException.
970
			if ($this->deviceHash === false) {
971
				$this->hierarchySyncRequired = true;
972
			}
973
		}
974
		$this->stateManager->SetDevice($this->device);
975
976
		return true;
977
	}
978
979
	/**
980
	 * Called when a SyncObject is not being streamed to the mobile.
981
	 * The user can be informed so he knows about this issue.
982
	 *
983
	 * @param string     $folderid id of the parent folder (may be false if unknown)
984
	 * @param string     $id       message id
985
	 * @param SyncObject $message  the broken message
986
	 * @param string     $reason   (self::MSG_BROKEN_UNKNOWN, self::MSG_BROKEN_CAUSINGLOOP, self::MSG_BROKEN_SEMANTICERR)
987
	 *
988
	 * @return bool
989
	 */
990
	public function AnnounceIgnoredMessage($folderid, $id, SyncObject $message, $reason = self::MSG_BROKEN_UNKNOWN) {
991
		if ($folderid === false) {
0 ignored issues
show
introduced by
The condition $folderid === false is always false.
Loading history...
992
			$folderid = $this->getLatestFolder();
993
		}
994
995
		$class = $message::class;
996
997
		$brokenMessage = new StateObject();
998
		$brokenMessage->id = $id;
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist on StateObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
999
		$brokenMessage->folderid = $folderid;
0 ignored issues
show
Bug Best Practice introduced by
The property folderid does not exist on StateObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
1000
		$brokenMessage->ASClass = $class;
0 ignored issues
show
Bug Best Practice introduced by
The property ASClass does not exist on StateObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
1001
		$brokenMessage->folderid = $folderid;
1002
		$brokenMessage->reasonCode = $reason;
0 ignored issues
show
Bug Best Practice introduced by
The property reasonCode does not exist on StateObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
1003
		$brokenMessage->reasonString = 'unknown cause';
0 ignored issues
show
Bug Best Practice introduced by
The property reasonString does not exist on StateObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
1004
		$brokenMessage->timestamp = time();
0 ignored issues
show
Bug Best Practice introduced by
The property timestamp does not exist on StateObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
1005
		$info = "";
1006
		if (isset($message->subject)) {
1007
			$info .= sprintf("Subject: '%s'", $message->subject);
1008
		}
1009
		if (isset($message->fileas)) {
1010
			$info .= sprintf("FileAs: '%s'", $message->fileas);
1011
		}
1012
		if (isset($message->from)) {
1013
			$info .= sprintf(" - From: '%s'", $message->from);
1014
		}
1015
		if (isset($message->starttime)) {
1016
			$info .= sprintf(" - On: '%s'", date("Y-m-d H:i", $message->starttime));
1017
		}
1018
		$brokenMessage->info = $info;
0 ignored issues
show
Bug Best Practice introduced by
The property info does not exist on StateObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
1019
		$brokenMessage->reasonString = SLog::GetLastMessage(LOGLEVEL_WARN);
1020
1021
		$this->device->AddIgnoredMessage($brokenMessage);
1022
1023
		SLog::Write(LOGLEVEL_ERROR, sprintf("Ignored broken message (%s). Reason: '%s' Folderid: '%s' message id '%s'", $class, $reason, $folderid, $id));
1024
1025
		return true;
1026
	}
1027
1028
	/**
1029
	 * Called when a SyncObject was streamed to the mobile.
1030
	 * If the message could not be sent before this data is obsolete.
1031
	 *
1032
	 * @param string $folderid id of the parent folder
1033
	 * @param string $id       message id
1034
	 *
1035
	 * @return bool returns true if the message was ignored before
1036
	 */
1037
	private function announceAcceptedMessage($folderid, $id) {
1038
		if ($this->device->RemoveIgnoredMessage($folderid, $id)) {
1039
			SLog::Write(LOGLEVEL_INFO, sprintf("DeviceManager->announceAcceptedMessage('%s', '%s'): cleared previously ignored message as message is successfully streamed", $folderid, $id));
1040
1041
			return true;
1042
		}
1043
1044
		return false;
1045
	}
1046
1047
	/**
1048
	 * Checks if there were broken messages streamed to the mobile.
1049
	 * If the sync completes/continues without further errors they are marked as accepted.
1050
	 *
1051
	 * @param string $folderid folderid which is to be checked
1052
	 *
1053
	 * @return bool
1054
	 */
1055
	private function checkBrokenMessages($folderid) {
1056
		// check for correctly synchronized messages of the folder
1057
		foreach ($this->loopdetection->GetSyncedButBeforeIgnoredMessages($folderid) as $okID) {
1058
			$this->announceAcceptedMessage($folderid, $okID);
1059
		}
1060
1061
		return true;
1062
	}
1063
1064
	/**
1065
	 * Setter for the latest folder id
1066
	 * on multi-folder operations of AS 14 this is used to set the new current folder id.
1067
	 *
1068
	 * @param string $folderid the current folder
1069
	 *
1070
	 * @return bool
1071
	 */
1072
	private function setLatestFolder($folderid) {
1073
		// this is a multi folder operation
1074
		// check on ignoredmessages before discaring the folderid
1075
		if ($this->latestFolder !== false) {
1076
			$this->checkBrokenMessages($this->latestFolder);
0 ignored issues
show
Bug introduced by
$this->latestFolder of type true is incompatible with the type string expected by parameter $folderid of DeviceManager::checkBrokenMessages(). ( Ignorable by Annotation )

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

1076
			$this->checkBrokenMessages(/** @scrutinizer ignore-type */ $this->latestFolder);
Loading history...
1077
		}
1078
1079
		$this->latestFolder = $folderid;
1080
1081
		return true;
1082
	}
1083
1084
	/**
1085
	 * Getter for the latest folder id.
1086
	 *
1087
	 * @return string $folderid       the current folder
1088
	 */
1089
	private function getLatestFolder() {
1090
		return $this->latestFolder;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->latestFolder returns the type boolean which is incompatible with the documented return type string.
Loading history...
1091
	}
1092
1093
	/**
1094
	 * Generates and SyncFolder object and returns it.
1095
	 *
1096
	 * @param string $store
1097
	 * @param string $folderid
1098
	 * @param string $name
1099
	 * @param int    $type
1100
	 * @param int    $flags
1101
	 * @param string $folderOrigin
1102
	 * @param mixed  $parentid
1103
	 *
1104
	 * @returns SyncFolder
1105
	 */
1106
	public function BuildSyncFolderObject($store, $folderid, $parentid, $name, $type, $flags, $folderOrigin) {
1107
		$folder = new SyncFolder();
1108
		$folder->BackendId = $folderid;
1109
		$folder->serverid = $this->GetFolderIdForBackendId($folder->BackendId, true, $folderOrigin, $name);
1110
		$folder->parentid = $this->GetFolderIdForBackendId($parentid);
1111
		$folder->displayname = $name;
1112
		$folder->type = $type;
1113
		// save store as custom property which is not streamed directly to the device
1114
		$folder->NoBackendFolder = true;
1115
		$folder->Store = $store;
1116
		$folder->Flags = $flags;
1117
1118
		return $folder;
1119
	}
1120
1121
	/**
1122
	 * Returns the device id.
1123
	 *
1124
	 * @return string
1125
	 */
1126
	public function GetDevid() {
1127
		return self::$devid;
1128
	}
1129
}
1130