Passed
Push — master ( 6522b6...8c95d3 )
by
unknown
04:16 queued 01:06
created

DeviceManager::GetAdditionalUserSyncFolderStore()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 8
nc 4
nop 1
dl 0
loc 14
rs 10
c 0
b 0
f 0
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-2022 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 string $type
449
	 * @param int    $queuedmessages;
450
	 * @param mixed  $uuid
451
	 * @param mixed  $statecounter
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_WARN, 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
		// check for potential process loops like described in ZP-5
673
		return $this->loopdetection->ProcessLoopDetectionIsHierarchyResyncRequired();
674
	}
675
676
	/**
677
	 * Adds an Exceptions to the process tracking.
678
	 *
679
	 * @param Exception $exception
680
	 *
681
	 * @return bool
682
	 */
683
	public function AnnounceProcessException($exception) {
684
		return $this->loopdetection->ProcessLoopDetectionAddException($exception);
685
	}
686
687
	/**
688
	 * Adds a non-ok status for a folderid to the process tracking.
689
	 * On 'false' a hierarchy status is assumed.
690
	 *
691
	 * @param mixed $folderid
692
	 * @param mixed $status
693
	 *
694
	 * @return bool
695
	 */
696
	public function AnnounceProcessStatus($folderid, $status) {
697
		return $this->loopdetection->ProcessLoopDetectionAddStatus($folderid, $status);
698
	}
699
700
	/**
701
	 * Announces that the current process is a push connection to the process loop
702
	 * detection and to the Top collector.
703
	 *
704
	 * @return bool
705
	 */
706
	public function AnnounceProcessAsPush() {
707
		SLog::Write(LOGLEVEL_DEBUG, "Announce process as PUSH connection");
708
709
		return $this->loopdetection->ProcessLoopDetectionSetAsPush() && GSync::GetTopCollector()->SetAsPushConnection();
710
	}
711
712
	/**
713
	 * Checks if the given counter for a certain uuid+folderid was already exported or modified.
714
	 * This is called when a heartbeat request found changes to make sure that the same
715
	 * changes are not exported twice, as during the heartbeat there could have been a normal
716
	 * sync request.
717
	 *
718
	 * @param string $folderid folder id
719
	 * @param string $uuid     synkkey
720
	 * @param string $counter  synckey counter
721
	 *
722
	 * @return bool indicating if an uuid+counter were exported (with changes) before
723
	 */
724
	public function CheckHearbeatStateIntegrity($folderid, $uuid, $counter) {
725
		return $this->loopdetection->IsSyncStateObsolete($folderid, $uuid, $counter);
726
	}
727
728
	/**
729
	 * Marks a syncstate as obsolete for Heartbeat, as e.g. an import was started using it.
730
	 *
731
	 * @param string $folderid folder id
732
	 * @param string $uuid     synkkey
733
	 * @param string $counter  synckey counter
734
	 */
735
	public function SetHeartbeatStateIntegrity($folderid, $uuid, $counter) {
736
		return $this->loopdetection->SetSyncStateUsage($folderid, $uuid, $counter);
737
	}
738
739
	/**
740
	 * Checks the data integrity of the data in the hierarchy cache and the data of the content data (synchronized folders).
741
	 * If a folder is deleted, the sync states could still be on the server (and being loaded by PING) while
742
	 * the folder is not being synchronized anymore. See also https://jira.z-hub.io/browse/ZP-1077.
743
	 *
744
	 * @return bool
745
	 */
746
	public function CheckFolderData() {
747
		SLog::Write(LOGLEVEL_DEBUG, "DeviceManager->CheckFolderData() checking integrity of hierarchy cache with synchronized folders");
748
749
		$hc = $this->device->GetHierarchyCache();
750
		$notInCache = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $notInCache is dead and can be removed.
Loading history...
751
		foreach ($this->device->GetAllFolderIds() as $folderid) {
752
			$uuid = $this->device->GetFolderUUID($folderid);
753
			if ($uuid) {
754
				// has a UUID but is not in the cache?! This is deleted, remove the states.
755
				if (!$hc->GetFolder($folderid)) {
756
					SLog::Write(LOGLEVEL_WARN, sprintf("DeviceManager->CheckFolderData(): Folder '%s' has sync states but is not in the hierarchy cache. Removing states.", $folderid));
757
					StateManager::UnLinkState($this->device, $folderid);
758
				}
759
			}
760
		}
761
762
		return true;
763
	}
764
765
	/**
766
	 * Sets the current status of the folder.
767
	 *
768
	 * @param string $folderid   folder id
769
	 * @param int    $statusflag current status: DeviceManager::FLD_SYNC_INITIALIZED, DeviceManager::FLD_SYNC_INPROGRESS, DeviceManager::FLD_SYNC_COMPLETED
770
	 */
771
	public function SetFolderSyncStatus($folderid, $statusflag) {
772
		$currentStatus = $this->device->GetFolderSyncStatus($folderid);
773
774
		// status available or just initialized
775
		if (isset($currentStatus->{ASDevice::FOLDERSYNCSTATUS}) || $statusflag == self::FLD_SYNC_INITIALIZED) {
776
			// only update if there is a change
777
			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...
778
					$statusflag != self::FLD_SYNC_COMPLETED) {
779
				$this->device->SetFolderSyncStatus($folderid, $statusflag);
780
				SLog::Write(LOGLEVEL_DEBUG, sprintf("SetFolderSyncStatus(): set %s for %s", $statusflag, $folderid));
781
			}
782
			// if completed, remove the status
783
			elseif ($statusflag == self::FLD_SYNC_COMPLETED) {
784
				$this->device->SetFolderSyncStatus($folderid, false);
785
				SLog::Write(LOGLEVEL_DEBUG, sprintf("SetFolderSyncStatus(): completed for %s", $folderid));
786
			}
787
		}
788
789
		return true;
790
	}
791
792
	/**
793
	 * Indicates if a folder is synchronizing by the saved status.
794
	 *
795
	 * @param string $folderid folder id
796
	 *
797
	 * @return bool
798
	 */
799
	public function HasFolderSyncStatus($folderid) {
800
		$currentStatus = $this->device->GetFolderSyncStatus($folderid);
801
802
		// status available ?
803
		$hasStatus = isset($currentStatus->{ASDevice::FOLDERSYNCSTATUS});
804
		if ($hasStatus) {
805
			SLog::Write(LOGLEVEL_DEBUG, sprintf("HasFolderSyncStatus(): saved folder status for %s: %s", $folderid, $currentStatus->{ASDevice::FOLDERSYNCSTATUS}));
806
		}
807
808
		return $hasStatus;
809
	}
810
811
	/**
812
	 * Returns the indicator if the FolderSync was completed successfully  (all folders synchronized).
813
	 *
814
	 * @return bool
815
	 */
816
	public function GetFolderSyncComplete() {
817
		return $this->device->GetFolderSyncComplete();
818
	}
819
820
	/**
821
	 * Sets if the FolderSync was completed successfully (all folders synchronized).
822
	 *
823
	 * @param bool  $complete indicating if all folders were sent
824
	 * @param mixed $user
825
	 * @param mixed $devid
826
	 *
827
	 * @return bool
828
	 */
829
	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

829
	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

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

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