DeviceManager   F
last analyzed

Complexity

Total Complexity 149

Size/Duplication

Total Lines 1111
Duplicated Lines 0 %

Importance

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

49 Methods

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

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

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

828
	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...
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

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

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