Sync::importMessage()   F
last analyzed

Complexity

Conditions 29
Paths 296

Size

Total Lines 145
Code Lines 78

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 29
eloc 78
c 1
b 0
f 0
nc 296
nop 8
dl 0
loc 145
rs 2.1333

How to fix   Long Method    Complexity    Many Parameters   

Long Method

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

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

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/*
4
 * SPDX-License-Identifier: AGPL-3.0-only
5
 * SPDX-FileCopyrightText: Copyright 2007-2016 Zarafa Deutschland GmbH
6
 * SPDX-FileCopyrightText: Copyright 2020-2024 grommunio GmbH
7
 *
8
 * Provides the SYNC command
9
 */
10
11
class Sync extends RequestProcessor {
12
	// Ignored SMS identifier
13
	public const GSYNCIGNORESMS = "ZPISMS";
14
	private $importer;
15
	private $globallyExportedItems;
16
	private $singleFolder;
17
	private $multiFolderInfo;
18
	private $startTagsSent = false;
19
	private $startFolderTagSent = false;
20
21
	/**
22
	 * Handles the Sync command
23
	 * Performs the synchronization of messages.
24
	 *
25
	 * @param int $commandCode
26
	 *
27
	 * @return bool
28
	 */
29
	public function Handle($commandCode) {
30
		// Contains all requested folders (containers)
31
		$sc = new SyncCollections();
32
		$status = SYNC_STATUS_SUCCESS;
33
		$wbxmlproblem = false;
34
		$emptysync = false;
35
		$this->singleFolder = true;
36
		$this->multiFolderInfo = [];
37
		$this->globallyExportedItems = 0;
38
39
		// check if the hierarchySync was fully completed
40
		if (USE_PARTIAL_FOLDERSYNC) {
41
			if (self::$deviceManager->GetFolderSyncComplete() === false) {
42
				SLog::Write(LOGLEVEL_INFO, "Request->HandleSync(): Sync request aborted, as exporting of folders has not yet completed");
43
				self::$topCollector->AnnounceInformation("Aborted due incomplete folder sync", true);
44
				$status = SYNC_STATUS_FOLDERHIERARCHYCHANGED;
45
			}
46
			else {
47
				SLog::Write(LOGLEVEL_INFO, "Request->HandleSync(): FolderSync marked as complete");
48
			}
49
		}
50
51
		// Start Synchronize
52
		if (self::$decoder->getElementStartTag(SYNC_SYNCHRONIZE)) {
53
			// AS 1.0 sends version information in WBXML
54
			if (self::$decoder->getElementStartTag(SYNC_VERSION)) {
55
				$sync_version = self::$decoder->getElementContent();
56
				SLog::Write(LOGLEVEL_DEBUG, sprintf("WBXML sync version: '%s'", $sync_version));
57
				if (!self::$decoder->getElementEndTag()) {
58
					return false;
59
				}
60
			}
61
62
			// Syncing specified folders
63
			// Android still sends heartbeat sync even if all syncfolders are disabled.
64
			// Check if Folders tag is empty (<Folders/>) and only sync if there are
65
			// some folders in the request.
66
			$startTag = self::$decoder->getElementStartTag(SYNC_FOLDERS);
67
			if (isset($startTag[EN_FLAGS]) && $startTag[EN_FLAGS]) {
68
				while (self::$decoder->getElementStartTag(SYNC_FOLDER)) {
69
					$actiondata = [];
70
					$actiondata["requested"] = true;
71
					$actiondata["clientids"] = [];
72
					$actiondata["modifyids"] = [];
73
					$actiondata["removeids"] = [];
74
					$actiondata["fetchids"] = [];
75
					$actiondata["statusids"] = [];
76
77
					// read class, synckey and folderid without SyncParameters Object for now
78
					$class = $synckey = $folderid = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $synckey is dead and can be removed.
Loading history...
79
80
					// if there are already collections in SyncCollections, this is min. the second folder
81
					if ($sc->HasCollections()) {
82
						$this->singleFolder = false;
83
					}
84
85
					// for AS versions < 2.5
86
					if (self::$decoder->getElementStartTag(SYNC_FOLDERTYPE)) {
87
						$class = self::$decoder->getElementContent();
88
						SLog::Write(LOGLEVEL_DEBUG, sprintf("Sync folder: '%s'", $class));
89
90
						if (!self::$decoder->getElementEndTag()) {
91
							return false;
92
						}
93
					}
94
95
					// SyncKey
96
					if (self::$decoder->getElementStartTag(SYNC_SYNCKEY)) {
97
						$synckey = "0";
98
						if (($synckey = self::$decoder->getElementContent()) !== false) {
99
							if (!self::$decoder->getElementEndTag()) {
100
								return false;
101
							}
102
						}
103
					}
104
					else {
105
						return false;
106
					}
107
108
					// FolderId
109
					if (self::$decoder->getElementStartTag(SYNC_FOLDERID)) {
110
						$folderid = self::$decoder->getElementContent();
111
112
						if (!self::$decoder->getElementEndTag()) {
113
							return false;
114
						}
115
					}
116
117
					// compatibility mode AS 1.0 - get folderid which was sent during GetHierarchy()
118
					if (!$folderid && $class) {
119
						$folderid = self::$deviceManager->GetFolderIdFromCacheByClass($class);
120
					}
121
122
					// folderid HAS TO BE known by now, so we retrieve the correct SyncParameters object for an update
123
					try {
124
						$spa = self::$deviceManager->GetStateManager()->GetSynchedFolderState($folderid);
125
126
						// TODO remove resync of folders
127
						// this forces a resync of all states
128
						if (!$spa instanceof SyncParameters) {
129
							throw new StateInvalidException("Saved state are not of type SyncParameters");
130
						}
131
132
						// new/resync requested
133
						if ($synckey == "0") {
134
							$spa->RemoveSyncKey();
135
							$spa->DelFolderStat();
0 ignored issues
show
Bug introduced by
The method DelFolderStat() does not exist on SyncParameters. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

135
							$spa->/** @scrutinizer ignore-call */ 
136
             DelFolderStat();
Loading history...
136
							$spa->SetMoveState(false);
0 ignored issues
show
Bug introduced by
The method SetMoveState() does not exist on SyncParameters. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

136
							$spa->/** @scrutinizer ignore-call */ 
137
             SetMoveState(false);
Loading history...
137
						}
138
						elseif ($synckey !== false) {
139
							if ($synckey !== $spa->GetSyncKey() && $synckey !== $spa->GetNewSyncKey()) {
140
								SLog::Write(LOGLEVEL_DEBUG, "HandleSync(): Synckey does not match latest saved for this folder or there is a move state, removing folderstat to force Exporter setup");
141
								$spa->DelFolderStat();
142
							}
143
							$spa->SetSyncKey($synckey);
144
						}
145
					}
146
					catch (StateInvalidException) {
147
						$spa = new SyncParameters();
148
						$status = SYNC_STATUS_INVALIDSYNCKEY;
149
						self::$topCollector->AnnounceInformation("State invalid - Resync folder", $this->singleFolder);
150
						self::$deviceManager->ForceFolderResync($folderid);
151
						$this->saveMultiFolderInfo("exception", "StateInvalidException");
152
					}
153
154
					// update folderid.. this might be a new object
155
					$spa->SetFolderId($folderid);
0 ignored issues
show
Bug introduced by
The method SetFolderId() does not exist on SyncParameters. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

155
					$spa->/** @scrutinizer ignore-call */ 
156
           SetFolderId($folderid);
Loading history...
156
					$spa->SetBackendFolderId(self::$deviceManager->GetBackendIdForFolderId($folderid));
0 ignored issues
show
Bug introduced by
The method SetBackendFolderId() does not exist on SyncParameters. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

156
					$spa->/** @scrutinizer ignore-call */ 
157
           SetBackendFolderId(self::$deviceManager->GetBackendIdForFolderId($folderid));
Loading history...
157
158
					if ($class !== false) {
159
						$spa->SetContentClass($class);
0 ignored issues
show
Bug introduced by
The method SetContentClass() does not exist on SyncParameters. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

159
						$spa->/** @scrutinizer ignore-call */ 
160
            SetContentClass($class);
Loading history...
160
					}
161
162
					// Get class for as versions >= 12.0
163
					if (!$spa->HasContentClass()) {
0 ignored issues
show
Bug introduced by
The method HasContentClass() does not exist on SyncParameters. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

163
					if (!$spa->/** @scrutinizer ignore-call */ HasContentClass()) {
Loading history...
164
						try {
165
							$spa->SetContentClass(self::$deviceManager->GetFolderClassFromCacheByID($spa->GetFolderId()));
0 ignored issues
show
Bug introduced by
The method GetFolderId() does not exist on SyncParameters. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

165
							$spa->SetContentClass(self::$deviceManager->GetFolderClassFromCacheByID($spa->/** @scrutinizer ignore-call */ GetFolderId()));
Loading history...
166
							SLog::Write(LOGLEVEL_DEBUG, sprintf("GetFolderClassFromCacheByID from Device Manager: '%s' for id:'%s'", $spa->GetContentClass(), $spa->GetFolderId()));
0 ignored issues
show
Bug introduced by
The method GetContentClass() does not exist on SyncParameters. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

166
							SLog::Write(LOGLEVEL_DEBUG, sprintf("GetFolderClassFromCacheByID from Device Manager: '%s' for id:'%s'", $spa->/** @scrutinizer ignore-call */ GetContentClass(), $spa->GetFolderId()));
Loading history...
167
						}
168
						catch (NoHierarchyCacheAvailableException) {
169
							$status = SYNC_STATUS_FOLDERHIERARCHYCHANGED;
170
							self::$deviceManager->ForceFullResync();
171
						}
172
					}
173
174
					// done basic SPA initialization/loading -> add to SyncCollection
175
					$sc->AddCollection($spa);
176
					$sc->AddParameter($spa, "requested", true);
177
178
					if ($spa->HasContentClass()) {
179
						self::$topCollector->AnnounceInformation(sprintf("%s request", $spa->GetContentClass()), $this->singleFolder);
180
					}
181
					else {
182
						SLog::Write(LOGLEVEL_WARN, "Not possible to determine class of request. Request did not contain class and apparently there is an issue with the HierarchyCache.");
183
					}
184
185
					// SUPPORTED properties
186
					if (($se = self::$decoder->getElementStartTag(SYNC_SUPPORTED)) !== false) {
187
						// LG phones send an empty supported tag, so only read the contents if available here
188
						// if <Supported/> is received, it's as no supported fields would have been sent at all.
189
						// unsure if this is the correct approach, or if in this case some default list should be used
190
						if ($se[EN_FLAGS] & EN_FLAGS_CONTENT) {
191
							$supfields = [];
192
							WBXMLDecoder::ResetInWhile("syncSupported");
193
							while (WBXMLDecoder::InWhile("syncSupported")) {
194
								$el = self::$decoder->getElement();
195
196
								if ($el[EN_TYPE] == EN_TYPE_ENDTAG) {
197
									break;
198
								}
199
200
								$supfields[] = $el[EN_TAG];
201
							}
202
							self::$deviceManager->SetSupportedFields($spa->GetFolderId(), $supfields);
203
						}
204
					}
205
206
					// Deletes as moves can be an empty tag as well as have value
207
					if (self::$decoder->getElementStartTag(SYNC_DELETESASMOVES)) {
208
						$spa->SetDeletesAsMoves(true);
0 ignored issues
show
Bug introduced by
The method SetDeletesAsMoves() does not exist on SyncParameters. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

208
						$spa->/** @scrutinizer ignore-call */ 
209
            SetDeletesAsMoves(true);
Loading history...
209
						if (($dam = self::$decoder->getElementContent()) !== false) {
210
							$spa->SetDeletesAsMoves((bool) $dam);
211
							if (!self::$decoder->getElementEndTag()) {
212
								return false;
213
							}
214
						}
215
					}
216
217
					// Get changes can be an empty tag as well as have value
218
					// code block partly contributed by dw2412
219
					if ($starttag = self::$decoder->getElementStartTag(SYNC_GETCHANGES)) {
220
						$sc->AddParameter($spa, "getchanges", true);
221
						if (($gc = self::$decoder->getElementContent()) !== false) {
222
							$sc->AddParameter($spa, "getchanges", $gc);
223
						}
224
						// read the endtag if SYNC_GETCHANGES wasn't an empty tag
225
						if ($starttag[EN_FLAGS] & EN_FLAGS_CONTENT) {
226
							if (!self::$decoder->getElementEndTag()) {
227
								return false;
228
							}
229
						}
230
					}
231
232
					if (self::$decoder->getElementStartTag(SYNC_WINDOWSIZE)) {
233
						$ws = self::$decoder->getElementContent();
234
						// normalize windowsize
235
						if ($ws == 0 || $ws > WINDOW_SIZE_MAX) {
236
							$ws = WINDOW_SIZE_MAX;
237
						}
238
239
						$spa->SetWindowSize($ws);
0 ignored issues
show
Bug introduced by
The method SetWindowSize() does not exist on SyncParameters. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

239
						$spa->/** @scrutinizer ignore-call */ 
240
            SetWindowSize($ws);
Loading history...
240
241
						// also announce the currently requested window size to the DeviceManager
242
						self::$deviceManager->SetWindowSize($spa->GetFolderId(), $spa->GetWindowSize());
0 ignored issues
show
Bug introduced by
The method GetWindowSize() does not exist on SyncParameters. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

242
						self::$deviceManager->SetWindowSize($spa->GetFolderId(), $spa->/** @scrutinizer ignore-call */ GetWindowSize());
Loading history...
243
244
						if (!self::$decoder->getElementEndTag()) {
245
							return false;
246
						}
247
					}
248
249
					// conversation mode requested
250
					if (self::$decoder->getElementStartTag(SYNC_CONVERSATIONMODE)) {
251
						$spa->SetConversationMode(true);
0 ignored issues
show
Bug introduced by
The method SetConversationMode() does not exist on SyncParameters. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

251
						$spa->/** @scrutinizer ignore-call */ 
252
            SetConversationMode(true);
Loading history...
252
						if (($conversationmode = self::$decoder->getElementContent()) !== false) {
253
							$spa->SetConversationMode((bool) $conversationmode);
254
							if (!self::$decoder->getElementEndTag()) {
255
								return false;
256
							}
257
						}
258
					}
259
260
					// Do not truncate by default
261
					$spa->SetTruncation(SYNC_TRUNCATION_ALL);
0 ignored issues
show
Bug introduced by
The method SetTruncation() does not exist on SyncParameters. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

261
					$spa->/** @scrutinizer ignore-call */ 
262
           SetTruncation(SYNC_TRUNCATION_ALL);
Loading history...
262
263
					// use default conflict handling if not specified by the mobile
264
					$spa->SetConflict(SYNC_CONFLICT_DEFAULT);
0 ignored issues
show
Bug introduced by
The method SetConflict() does not exist on SyncParameters. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

264
					$spa->/** @scrutinizer ignore-call */ 
265
           SetConflict(SYNC_CONFLICT_DEFAULT);
Loading history...
265
266
					// save the current filtertype because it might have been changed on the mobile
267
					$currentFilterType = $spa->GetFilterType();
0 ignored issues
show
Bug introduced by
The method GetFilterType() does not exist on SyncParameters. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

267
					/** @scrutinizer ignore-call */ 
268
     $currentFilterType = $spa->GetFilterType();
Loading history...
268
269
					while (self::$decoder->getElementStartTag(SYNC_OPTIONS)) {
270
						$firstOption = true;
271
						WBXMLDecoder::ResetInWhile("syncOptions");
272
						while (WBXMLDecoder::InWhile("syncOptions")) {
273
							// foldertype definition
274
							if (self::$decoder->getElementStartTag(SYNC_FOLDERTYPE)) {
275
								$foldertype = self::$decoder->getElementContent();
276
								SLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): specified options block with foldertype '%s'", $foldertype));
277
278
								// switch the foldertype for the next options
279
								$spa->UseCPO($foldertype);
280
281
								// save the current filtertype because it might have been changed on the mobile
282
								$currentFilterType = $spa->GetFilterType();
283
284
								// set to synchronize all changes. The mobile could overwrite this value
285
								$spa->SetFilterType(SYNC_FILTERTYPE_ALL);
0 ignored issues
show
Bug introduced by
The method SetFilterType() does not exist on SyncParameters. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

285
								$spa->/** @scrutinizer ignore-call */ 
286
              SetFilterType(SYNC_FILTERTYPE_ALL);
Loading history...
286
287
								if (!self::$decoder->getElementEndTag()) {
288
									return false;
289
								}
290
							}
291
							// if no foldertype is defined, use default cpo
292
							elseif ($firstOption) {
293
								$spa->UseCPO();
294
								// save the current filtertype because it might have been changed on the mobile
295
								$currentFilterType = $spa->GetFilterType();
296
								// set to synchronize all changes. The mobile could overwrite this value
297
								$spa->SetFilterType(SYNC_FILTERTYPE_ALL);
298
							}
299
							$firstOption = false;
300
301
							if (self::$decoder->getElementStartTag(SYNC_FILTERTYPE)) {
302
								$spa->SetFilterType(self::$decoder->getElementContent());
303
								if (!self::$decoder->getElementEndTag()) {
304
									return false;
305
								}
306
							}
307
							if (self::$decoder->getElementStartTag(SYNC_TRUNCATION)) {
308
								$spa->SetTruncation(self::$decoder->getElementContent());
309
								if (!self::$decoder->getElementEndTag()) {
310
									return false;
311
								}
312
							}
313
							if (self::$decoder->getElementStartTag(SYNC_RTFTRUNCATION)) {
314
								$spa->SetRTFTruncation(self::$decoder->getElementContent());
0 ignored issues
show
Bug introduced by
The method SetRTFTruncation() does not exist on SyncParameters. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

314
								$spa->/** @scrutinizer ignore-call */ 
315
              SetRTFTruncation(self::$decoder->getElementContent());
Loading history...
315
								if (!self::$decoder->getElementEndTag()) {
316
									return false;
317
								}
318
							}
319
320
							if (self::$decoder->getElementStartTag(SYNC_MIMESUPPORT)) {
321
								$spa->SetMimeSupport(self::$decoder->getElementContent());
0 ignored issues
show
Bug introduced by
The method SetMimeSupport() does not exist on SyncParameters. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

321
								$spa->/** @scrutinizer ignore-call */ 
322
              SetMimeSupport(self::$decoder->getElementContent());
Loading history...
322
								if (!self::$decoder->getElementEndTag()) {
323
									return false;
324
								}
325
							}
326
327
							if (self::$decoder->getElementStartTag(SYNC_MIMETRUNCATION)) {
328
								$spa->SetMimeTruncation(self::$decoder->getElementContent());
0 ignored issues
show
Bug introduced by
The method SetMimeTruncation() does not exist on SyncParameters. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

328
								$spa->/** @scrutinizer ignore-call */ 
329
              SetMimeTruncation(self::$decoder->getElementContent());
Loading history...
329
								if (!self::$decoder->getElementEndTag()) {
330
									return false;
331
								}
332
							}
333
334
							if (self::$decoder->getElementStartTag(SYNC_CONFLICT)) {
335
								$spa->SetConflict(self::$decoder->getElementContent());
336
								if (!self::$decoder->getElementEndTag()) {
337
									return false;
338
								}
339
							}
340
341
							while (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_BODYPREFERENCE)) {
342
								if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_TYPE)) {
343
									$bptype = self::$decoder->getElementContent();
344
									$spa->BodyPreference($bptype);
0 ignored issues
show
Bug introduced by
The method BodyPreference() does not exist on SyncParameters. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

344
									$spa->/** @scrutinizer ignore-call */ 
345
               BodyPreference($bptype);
Loading history...
345
									if (!self::$decoder->getElementEndTag()) {
346
										return false;
347
									}
348
								}
349
350
								if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_TRUNCATIONSIZE)) {
351
									$spa->BodyPreference($bptype)->SetTruncationSize(self::$decoder->getElementContent());
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $bptype does not seem to be defined for all execution paths leading up to this point.
Loading history...
352
									if (!self::$decoder->getElementEndTag()) {
353
										return false;
354
									}
355
								}
356
357
								if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_ALLORNONE)) {
358
									$spa->BodyPreference($bptype)->SetAllOrNone(self::$decoder->getElementContent());
359
									if (!self::$decoder->getElementEndTag()) {
360
										return false;
361
									}
362
								}
363
364
								if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_PREVIEW)) {
365
									$spa->BodyPreference($bptype)->SetPreview(self::$decoder->getElementContent());
366
									if (!self::$decoder->getElementEndTag()) {
367
										return false;
368
									}
369
								}
370
371
								if (!self::$decoder->getElementEndTag()) {
372
									return false;
373
								}
374
							}
375
376
							if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_BODYPARTPREFERENCE)) {
377
								if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_TYPE)) {
378
									$bpptype = self::$decoder->getElementContent();
379
									$spa->BodyPartPreference($bpptype);
0 ignored issues
show
Bug introduced by
The method BodyPartPreference() does not exist on SyncParameters. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

379
									$spa->/** @scrutinizer ignore-call */ 
380
               BodyPartPreference($bpptype);
Loading history...
380
									if (!self::$decoder->getElementEndTag()) {
381
										return false;
382
									}
383
								}
384
385
								if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_TRUNCATIONSIZE)) {
386
									$spa->BodyPartPreference($bpptype)->SetTruncationSize(self::$decoder->getElementContent());
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $bpptype does not seem to be defined for all execution paths leading up to this point.
Loading history...
387
									if (!self::$decoder->getElementEndTag()) {
388
										return false;
389
									}
390
								}
391
392
								if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_ALLORNONE)) {
393
									$spa->BodyPartPreference($bpptype)->SetAllOrNone(self::$decoder->getElementContent());
394
									if (!self::$decoder->getElementEndTag()) {
395
										return false;
396
									}
397
								}
398
399
								if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_PREVIEW)) {
400
									$spa->BodyPartPreference($bpptype)->SetPreview(self::$decoder->getElementContent());
401
									if (!self::$decoder->getElementEndTag()) {
402
										return false;
403
									}
404
								}
405
406
								if (!self::$decoder->getElementEndTag()) {
407
									return false;
408
								}
409
							}
410
411
							if (self::$decoder->getElementStartTag(SYNC_RIGHTSMANAGEMENT_SUPPORT)) {
412
								$spa->SetRmSupport(self::$decoder->getElementContent());
0 ignored issues
show
Bug introduced by
The method SetRmSupport() does not exist on SyncParameters. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

412
								$spa->/** @scrutinizer ignore-call */ 
413
              SetRmSupport(self::$decoder->getElementContent());
Loading history...
413
								if (!self::$decoder->getElementEndTag()) {
414
									return false;
415
								}
416
							}
417
418
							$e = self::$decoder->peek();
419
							if ($e[EN_TYPE] == EN_TYPE_ENDTAG) {
420
								self::$decoder->getElementEndTag();
421
422
								break;
423
							}
424
						}
425
					}
426
427
					// limit items to be synchronized to the mobiles if configured
428
					$maxAllowed = self::$deviceManager->GetFilterType($spa->GetFolderId(), $spa->GetBackendFolderId());
429
					if ($maxAllowed > SYNC_FILTERTYPE_ALL &&
430
						(!$spa->HasFilterType() || $spa->GetFilterType() == SYNC_FILTERTYPE_ALL || $spa->GetFilterType() > $maxAllowed)) {
0 ignored issues
show
Bug introduced by
The method HasFilterType() does not exist on SyncParameters. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

430
						(!$spa->/** @scrutinizer ignore-call */ HasFilterType() || $spa->GetFilterType() == SYNC_FILTERTYPE_ALL || $spa->GetFilterType() > $maxAllowed)) {
Loading history...
431
						SLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): FilterType applied globally or specifically, using value: %s", $maxAllowed));
432
						$spa->SetFilterType($maxAllowed);
433
					}
434
435
					if ($currentFilterType != $spa->GetFilterType()) {
436
						SLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): FilterType has changed (old: '%s', new: '%s'), removing folderstat to force Exporter setup", $currentFilterType, $spa->GetFilterType()));
437
						$spa->DelFolderStat();
438
					}
439
440
					// Check if the hierarchycache is available. If not, trigger a HierarchySync
441
					if (self::$deviceManager->IsHierarchySyncRequired()) {
442
						$status = SYNC_STATUS_FOLDERHIERARCHYCHANGED;
443
						SLog::Write(LOGLEVEL_DEBUG, "HierarchyCache is also not available. Triggering HierarchySync to device");
444
					}
445
446
					// AS16: Check if this is a DRAFTS folder - if so, disable FilterType
447
					if (Request::GetProtocolVersion() >= 16.0 && self::$deviceManager->GetFolderTypeFromCacheById($spa->GetFolderId()) == SYNC_FOLDER_TYPE_DRAFTS) {
448
						$spa->SetFilterType(SYNC_FILTERTYPE_DISABLE);
449
						SLog::Write(LOGLEVEL_DEBUG, "HandleSync(): FilterType has been disabled as this is a DRAFTS folder.");
450
					}
451
452
					if (($el = self::$decoder->getElementStartTag(SYNC_PERFORM)) && ($el[EN_FLAGS] & EN_FLAGS_CONTENT)) {
453
						// We can not proceed here as the content class is unknown
454
						if ($status != SYNC_STATUS_SUCCESS) {
455
							SLog::Write(LOGLEVEL_WARN, "Ignoring all incoming actions as global status indicates problem.");
456
							$wbxmlproblem = true;
457
458
							break;
459
						}
460
461
						$performaction = true;
462
463
						// unset the importer
464
						$this->importer = false;
465
466
						$nchanges = 0;
467
						WBXMLDecoder::ResetInWhile("syncActions");
468
						while (WBXMLDecoder::InWhile("syncActions")) {
469
							// ADD, MODIFY, REMOVE or FETCH
470
							$element = self::$decoder->getElement();
471
472
							if ($element[EN_TYPE] != EN_TYPE_STARTTAG) {
473
								self::$decoder->ungetElement($element);
474
475
								break;
476
							}
477
478
							if ($status == SYNC_STATUS_SUCCESS) {
479
								++$nchanges;
480
							}
481
482
							// Foldertype sent when syncing SMS
483
							if (self::$decoder->getElementStartTag(SYNC_FOLDERTYPE)) {
484
								$foldertype = self::$decoder->getElementContent();
485
								SLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): incoming data with foldertype '%s'", $foldertype));
486
487
								if (!self::$decoder->getElementEndTag()) {
488
									return false;
489
								}
490
							}
491
							else {
492
								$foldertype = false;
493
							}
494
495
							$serverid = false;
496
							if (self::$decoder->getElementStartTag(SYNC_SERVERENTRYID)) {
497
								if (($serverid = self::$decoder->getElementContent()) !== false) {
498
									if (!self::$decoder->getElementEndTag()) { // end serverid
499
										return false;
500
									}
501
								}
502
							}
503
							// get the instanceId if available
504
							$instanceid = false;
505
							if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_INSTANCEID)) {
506
								if (($instanceid = self::$decoder->getElementContent()) !== false) {
507
									if (!self::$decoder->getElementEndTag()) { // end instanceid
508
										return false;
509
									}
510
								}
511
							}
512
513
							if (self::$decoder->getElementStartTag(SYNC_CLIENTENTRYID)) {
514
								$clientid = self::$decoder->getElementContent();
515
516
								if (!self::$decoder->getElementEndTag()) { // end clientid
517
									return false;
518
								}
519
							}
520
							else {
521
								$clientid = false;
522
							}
523
524
							// Get the SyncMessage if sent
525
							if (($el = self::$decoder->getElementStartTag(SYNC_DATA)) && ($el[EN_FLAGS] & EN_FLAGS_CONTENT)) {
526
								$message = GSync::getSyncObjectFromFolderClass($spa->GetContentClass());
527
								$message->Decode(self::$decoder);
528
529
								// set Ghosted fields
530
								$message->emptySupported(self::$deviceManager->GetSupportedFields($spa->GetFolderId()));
531
532
								if (!self::$decoder->getElementEndTag()) { // end applicationdata
533
									return false;
534
								}
535
							}
536
							else {
537
								$message = false;
538
							}
539
540
							// InstanceID sent: do action to a recurrency exception
541
							if ($instanceid) {
542
								// for delete actions we don't have an ASObject
543
								if (!$message) {
544
									$message = GSync::getSyncObjectFromFolderClass($spa->GetContentClass());
545
									$message->Decode(self::$decoder);
546
								}
547
								$message->instanceid = $instanceid;
0 ignored issues
show
Bug introduced by
The property instanceid does not exist on string.
Loading history...
548
								if ($element[EN_TAG] == SYNC_REMOVE) {
549
									$message->instanceiddelete = true;
0 ignored issues
show
Bug introduced by
The property instanceiddelete does not exist on string.
Loading history...
550
									$element[EN_TAG] = SYNC_MODIFY;
551
								}
552
							}
553
554
							switch ($element[EN_TAG]) {
555
								case SYNC_FETCH:
556
									array_push($actiondata["fetchids"], $serverid);
557
									break;
558
559
								default:
560
									// get the importer
561
									if ($this->importer === false) {
562
										$status = $this->getImporter($sc, $spa, $actiondata);
563
									}
564
565
									if ($status == SYNC_STATUS_SUCCESS) {
566
										$this->importMessage($spa, $actiondata, $element[EN_TAG], $message, $clientid, $serverid, $foldertype, $nchanges);
0 ignored issues
show
Bug introduced by
$message of type false|string is incompatible with the type SyncObject expected by parameter $message of Sync::importMessage(). ( Ignorable by Annotation )

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

566
										$this->importMessage($spa, $actiondata, $element[EN_TAG], /** @scrutinizer ignore-type */ $message, $clientid, $serverid, $foldertype, $nchanges);
Loading history...
567
									}
568
									else {
569
										SLog::Write(LOGLEVEL_WARN, "Ignored incoming change, global status indicates problem.");
570
									}
571
									break;
572
							}
573
574
							if ($actiondata["fetchids"]) {
575
								self::$topCollector->AnnounceInformation(sprintf("Fetching %d", $nchanges));
576
							}
577
							else {
578
								self::$topCollector->AnnounceInformation(sprintf("Incoming %d", $nchanges));
579
							}
580
581
							if (!self::$decoder->getElementEndTag()) { // end add/change/delete/move
582
								return false;
583
							}
584
						}
585
586
						if ($status == SYNC_STATUS_SUCCESS && $this->importer !== false) {
587
							SLog::Write(LOGLEVEL_INFO, sprintf("Processed '%d' incoming changes", $nchanges));
588
							if (!$actiondata["fetchids"]) {
589
								self::$topCollector->AnnounceInformation(sprintf("%d incoming", $nchanges), $this->singleFolder);
590
								$this->saveMultiFolderInfo("incoming", $nchanges);
591
							}
592
593
							try {
594
								// Save the updated state, which is used for the exporter later
595
								$sc->AddParameter($spa, "state", $this->importer->GetState());
596
							}
597
							catch (StatusException $stex) {
598
								$status = $stex->getCode();
599
							}
600
601
							// Check if changes are requested - if not and there are no changes to be exported anyway, update folderstat!
602
							if (!$sc->GetParameter($spa, "getchanges") && !$sc->CountChange($spa->GetFolderId())) {
603
								SLog::Write(LOGLEVEL_DEBUG, "Incoming changes, no export requested: update folderstat");
604
								$newFolderStatAfterImport = self::$backend->GetFolderStat(GSync::GetAdditionalSyncFolderStore($spa->GetBackendFolderId()), $spa->GetBackendFolderId());
605
								$this->setFolderStat($spa, $newFolderStatAfterImport);
606
							}
607
						}
608
609
						if (!self::$decoder->getElementEndTag()) { // end PERFORM
610
							return false;
611
						}
612
					}
613
614
					// save the failsafe state
615
					if (!empty($actiondata["statusids"])) {
616
						unset($actiondata["failstate"]);
617
						$actiondata["failedsyncstate"] = $sc->GetParameter($spa, "state");
618
						self::$deviceManager->GetStateManager()->SetSyncFailState($actiondata);
619
					}
620
621
					// save actiondata
622
					$sc->AddParameter($spa, "actiondata", $actiondata);
623
624
					if (!self::$decoder->getElementEndTag()) { // end collection
625
						return false;
626
					}
627
628
					// AS14 does not send GetChanges anymore. We should do it if there were no incoming changes
629
					if (!isset($performaction) && !$sc->GetParameter($spa, "getchanges") && $spa->HasSyncKey()) {
630
						$sc->AddParameter($spa, "getchanges", true);
631
					}
632
				} // END FOLDER
633
634
				if (!$wbxmlproblem && !self::$decoder->getElementEndTag()) { // end collections
635
					return false;
636
				}
637
			} // end FOLDERS
638
639
			if (self::$decoder->getElementStartTag(SYNC_HEARTBEATINTERVAL)) {
640
				$hbinterval = self::$decoder->getElementContent();
641
				if (!self::$decoder->getElementEndTag()) { // SYNC_HEARTBEATINTERVAL
642
					return false;
643
				}
644
			}
645
646
			if (self::$decoder->getElementStartTag(SYNC_WAIT)) {
647
				$wait = self::$decoder->getElementContent();
648
				if (!self::$decoder->getElementEndTag()) { // SYNC_WAIT
649
					return false;
650
				}
651
652
				// internally the heartbeat interval and the wait time are the same
653
				// heartbeat is in seconds, wait in minutes
654
				$hbinterval = $wait * 60;
655
			}
656
657
			if (self::$decoder->getElementStartTag(SYNC_WINDOWSIZE)) {
658
				$sc->SetGlobalWindowSize(self::$decoder->getElementContent());
659
				SLog::Write(LOGLEVEL_DEBUG, "Sync(): Global WindowSize requested: " . $sc->GetGlobalWindowSize());
660
				if (!self::$decoder->getElementEndTag()) { // SYNC_WINDOWSIZE
661
					return false;
662
				}
663
			}
664
665
			if (self::$decoder->getElementStartTag(SYNC_PARTIAL)) {
666
				$partial = true;
667
			}
668
			else {
669
				$partial = false;
670
			}
671
672
			if (!$wbxmlproblem && !self::$decoder->getElementEndTag()) { // end sync
673
				return false;
674
			}
675
		}
676
		// we did not receive a SYNCHRONIZE block - assume empty sync
677
		else {
678
			$emptysync = true;
679
		}
680
		// END SYNCHRONIZE
681
682
		// check heartbeat/wait time
683
		if (isset($hbinterval)) {
684
			if ($hbinterval < 60 || $hbinterval > 3540) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $hbinterval does not seem to be defined for all execution paths leading up to this point.
Loading history...
685
				$status = SYNC_STATUS_INVALIDWAITORHBVALUE;
686
				SLog::Write(LOGLEVEL_WARN, sprintf("HandleSync(): Invalid heartbeat or wait value '%s'", $hbinterval));
687
			}
688
		}
689
690
		// Partial & Empty Syncs need saved data to proceed with synchronization
691
		if ($status == SYNC_STATUS_SUCCESS && ($emptysync === true || $partial === true)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $partial does not seem to be defined for all execution paths leading up to this point.
Loading history...
692
			SLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Partial or Empty sync requested. Retrieving data of synchronized folders."));
693
694
			// Load all collections - do not overwrite existing (received!), load states, check permissions and only load confirmed states!
695
			try {
696
				$sc->LoadAllCollections(false, true, true, true, true);
697
			}
698
			catch (StateInvalidException) {
699
				$status = SYNC_STATUS_INVALIDSYNCKEY;
700
				self::$topCollector->AnnounceInformation("StateNotFoundException", $this->singleFolder);
701
				$this->saveMultiFolderInfo("exception", "StateNotFoundException");
702
			}
703
			catch (StatusException) {
704
				$status = SYNC_STATUS_FOLDERHIERARCHYCHANGED;
705
				self::$topCollector->AnnounceInformation(sprintf("StatusException code: %d", $status), $this->singleFolder);
706
				$this->saveMultiFolderInfo("exception", "StatusException");
707
			}
708
709
			// update a few values
710
			foreach ($sc as $folderid => $spa) {
711
				// manually set getchanges parameter for this collection if it is synchronized
712
				if ($spa->HasSyncKey()) {
713
					$actiondata = $sc->GetParameter($spa, "actiondata");
714
					// request changes if no other actions are executed
715
					if (empty($actiondata["modifyids"]) && empty($actiondata["clientids"]) && empty($actiondata["removeids"])) {
716
						$sc->AddParameter($spa, "getchanges", true);
717
					}
718
719
					// announce WindowSize to DeviceManager
720
					self::$deviceManager->SetWindowSize($folderid, $spa->GetWindowSize());
721
				}
722
			}
723
			if (!$sc->HasCollections()) {
724
				$status = SYNC_STATUS_SYNCREQUESTINCOMPLETE;
725
			}
726
		}
727
		elseif (isset($hbinterval)) {
728
			// load the hierarchy data - there are no permissions to verify so we just set it to false
729
			if (!$sc->LoadCollection(false, true, false)) {
0 ignored issues
show
Bug introduced by
false of type false is incompatible with the type string expected by parameter $folderid of SyncCollections::LoadCollection(). ( Ignorable by Annotation )

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

729
			if (!$sc->LoadCollection(/** @scrutinizer ignore-type */ false, true, false)) {
Loading history...
730
				$status = SYNC_STATUS_FOLDERHIERARCHYCHANGED;
731
				self::$topCollector->AnnounceInformation(sprintf("StatusException code: %d", $status), $this->singleFolder);
732
				$this->saveMultiFolderInfo("exception", "StatusException");
733
			}
734
		}
735
736
		// HEARTBEAT
737
		if ($status == SYNC_STATUS_SUCCESS && isset($hbinterval)) {
738
			$interval = (defined('PING_INTERVAL') && PING_INTERVAL > 0) ? PING_INTERVAL : 30;
739
			$sc->SetLifetime($hbinterval);
740
741
			// states are lazy loaded - we have to make sure that they are there!
742
			$loadstatus = SYNC_STATUS_SUCCESS;
743
			foreach ($sc as $folderid => $spa) {
744
				// some androids do heartbeat on the OUTBOX folder, with weird results
745
				// we do not load the state so we will never get relevant changes on the OUTBOX folder
746
				if (self::$deviceManager->GetFolderTypeFromCacheById($folderid) == SYNC_FOLDER_TYPE_OUTBOX) {
747
					SLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Heartbeat on Outbox folder not allowed"));
748
749
					continue;
750
				}
751
752
				$fad = [];
753
				// if loading the states fails, we do not enter heartbeat, but we keep $status on SYNC_STATUS_SUCCESS
754
				// so when the changes are exported the correct folder gets an SYNC_STATUS_INVALIDSYNCKEY
755
				if ($loadstatus == SYNC_STATUS_SUCCESS) {
756
					$loadstatus = $this->loadStates($sc, $spa, $fad);
757
				}
758
			}
759
760
			if ($loadstatus == SYNC_STATUS_SUCCESS) {
761
				$foundchanges = false;
762
763
				try {
764
					// always check for changes
765
					SLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Entering Heartbeat mode"));
766
					$foundchanges = $sc->CheckForChanges($sc->GetLifetime(), $interval);
767
				}
768
				catch (StatusException $stex) {
769
					if ($stex->getCode() == SyncCollections::OBSOLETE_CONNECTION) {
770
						$status = SYNC_COMMONSTATUS_SYNCSTATEVERSIONINVALID;
771
					}
772
					else {
773
						$status = SYNC_STATUS_FOLDERHIERARCHYCHANGED;
774
						self::$topCollector->AnnounceInformation(sprintf("StatusException code: %d", $status), $this->singleFolder);
775
						$this->saveMultiFolderInfo("exception", "StatusException");
776
					}
777
				}
778
779
				// update the waittime waited
780
				self::$waitTime = $sc->GetWaitedSeconds();
781
782
				// in case there are no changes and no other request has synchronized while we waited, we can reply with an empty response
783
				if (!$foundchanges && $status == SYNC_STATUS_SUCCESS) {
784
					// if there were changes to the SPA or CPOs we need to save this before we terminate
785
					// only save if the state was not modified by some other request, if so, return state invalid status
786
					foreach ($sc as $folderid => $spa) {
787
						if (self::$deviceManager->CheckHearbeatStateIntegrity($spa->GetFolderId(), $spa->GetUuid(), $spa->GetUuidCounter())) {
788
							$status = SYNC_COMMONSTATUS_SYNCSTATEVERSIONINVALID;
789
						}
790
						else {
791
							$sc->SaveCollection($spa);
792
						}
793
					}
794
795
					if ($status == SYNC_STATUS_SUCCESS) {
796
						SLog::Write(LOGLEVEL_DEBUG, "No changes found and no other process changed states. Replying with empty response and closing connection.");
797
						self::$specialHeaders = [];
798
						self::$specialHeaders[] = "Connection: close";
799
800
						return true;
801
					}
802
				}
803
804
				if ($foundchanges) {
805
					foreach ($sc->GetChangedFolderIds() as $folderid => $changecount) {
806
						// check if there were other sync requests for a folder during the heartbeat
807
						$spa = $sc->GetCollection($folderid);
808
						if ($changecount > 0 && $sc->WaitedForChanges() && self::$deviceManager->CheckHearbeatStateIntegrity($spa->GetFolderId(), $spa->GetUuid(), $spa->GetUuidCounter())) {
0 ignored issues
show
Bug introduced by
The method GetUuidCounter() does not exist on SyncParameters. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

808
						if ($changecount > 0 && $sc->WaitedForChanges() && self::$deviceManager->CheckHearbeatStateIntegrity($spa->GetFolderId(), $spa->GetUuid(), $spa->/** @scrutinizer ignore-call */ GetUuidCounter())) {
Loading history...
Bug introduced by
The method GetUuid() does not exist on SyncParameters. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

808
						if ($changecount > 0 && $sc->WaitedForChanges() && self::$deviceManager->CheckHearbeatStateIntegrity($spa->GetFolderId(), $spa->/** @scrutinizer ignore-call */ GetUuid(), $spa->GetUuidCounter())) {
Loading history...
809
							SLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): heartbeat: found %d changes in '%s' which was already synchronized. Heartbeat aborted!", $changecount, $folderid));
810
							$status = SYNC_COMMONSTATUS_SYNCSTATEVERSIONINVALID;
811
						}
812
						else {
813
							SLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): heartbeat: found %d changes in '%s'", $changecount, $folderid));
814
						}
815
					}
816
				}
817
			}
818
		}
819
820
		// Start the output
821
		SLog::Write(LOGLEVEL_DEBUG, "HandleSync(): Start Output");
822
823
		// global status
824
		// SYNC_COMMONSTATUS_* start with values from 101
825
		if ($status != SYNC_COMMONSTATUS_SUCCESS && ($status == SYNC_STATUS_FOLDERHIERARCHYCHANGED || $status > 100)) {
826
			self::$deviceManager->AnnounceProcessStatus($folderid, $status);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $folderid does not seem to be defined for all execution paths leading up to this point.
Loading history...
827
			$this->sendStartTags();
828
			self::$encoder->startTag(SYNC_STATUS);
829
			self::$encoder->content($status);
830
			self::$encoder->endTag();
831
			self::$encoder->endTag(); // SYNC_SYNCHRONIZE
832
833
			return true;
834
		}
835
836
		// Loop through requested folders
837
		foreach ($sc as $folderid => $spa) {
838
			// get actiondata
839
			$actiondata = $sc->GetParameter($spa, "actiondata");
840
841
			if ($status == SYNC_STATUS_SUCCESS && (!$spa->GetContentClass() || !$spa->GetFolderId())) {
842
				SLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): no content class or folderid found for collection."));
843
844
				continue;
845
			}
846
847
			if (!$sc->GetParameter($spa, "requested")) {
848
				SLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): partial sync for folder class '%s' with id '%s'", $spa->GetContentClass(), $spa->GetFolderId()));
849
				// reload state and initialize StateMachine correctly
850
				$sc->AddParameter($spa, "state", null);
851
				$status = $this->loadStates($sc, $spa, $actiondata);
852
			}
853
854
			// initialize exporter to get changecount
855
			$changecount = false;
856
			$exporter = false;
857
			$streamimporter = false;
858
			$newFolderStat = false;
859
			$setupExporter = true;
860
861
			// TODO we could check against $sc->GetChangedFolderIds() on heartbeat so we do not need to configure all exporter again
862
			if ($status == SYNC_STATUS_SUCCESS && ($sc->GetParameter($spa, "getchanges") || !$spa->HasSyncKey())) {
863
				// no need to run the exporter if the globalwindowsize is already full - if collection already has a synckey
864
				if ($sc->GetGlobalWindowSize() == $this->globallyExportedItems && $spa->HasSyncKey()) {
865
					SLog::Write(LOGLEVEL_DEBUG, sprintf("Sync(): no exporter setup for '%s' as GlobalWindowSize is full.", $spa->GetFolderId()));
866
					$setupExporter = false;
867
				}
868
				// if the maximum request timeout is reached, stop processing other collections
869
				if (Request::IsRequestTimeoutReached()) {
870
					SLog::Write(LOGLEVEL_DEBUG, sprintf("Sync(): no exporter setup for '%s' as request timeout reached, omitting output for collection.", $spa->GetFolderId()));
871
					$setupExporter = false;
872
				}
873
874
				// if max memory allocation is reached, stop processing other collections
875
				if (Request::IsRequestMemoryLimitReached()) {
876
					SLog::Write(LOGLEVEL_DEBUG, sprintf("Sync(): no exporter setup for '%s' as max memory allocatation reached, omitting output for collection.", $spa->GetFolderId()));
877
					$setupExporter = false;
878
				}
879
880
				// force exporter run if there is a saved status
881
				if ($setupExporter && self::$deviceManager->HasFolderSyncStatus($spa->GetFolderId())) {
882
					SLog::Write(LOGLEVEL_DEBUG, sprintf("Sync(): forcing exporter setup for '%s' as a sync status is saved - ignoring backend folder stats", $spa->GetFolderId()));
883
				}
884
				// compare the folder statistics if the backend supports this
885
				elseif ($setupExporter && self::$backend->HasFolderStats()) {
886
					// check if the folder stats changed -> if not, don't setup the exporter, there are no changes!
887
					$newFolderStat = self::$backend->GetFolderStat(GSync::GetAdditionalSyncFolderStore($spa->GetBackendFolderId()), $spa->GetBackendFolderId());
888
					if ($newFolderStat !== false && !$spa->IsExporterRunRequired($newFolderStat, true)) {
889
						$changecount = 0;
890
						$setupExporter = false;
891
					}
892
				}
893
894
				// Do a full Exporter setup if we can't avoid it
895
				if ($setupExporter) {
896
					// make sure the states are loaded
897
					$status = $this->loadStates($sc, $spa, $actiondata);
898
899
					if ($status == SYNC_STATUS_SUCCESS) {
900
						try {
901
							// if this is an additional folder the backend has to be setup correctly
902
							if (!self::$backend->Setup(GSync::GetAdditionalSyncFolderStore($spa->GetBackendFolderId()))) {
903
								throw new StatusException(sprintf("HandleSync() could not Setup() the backend for folder id %s/%s", $spa->GetFolderId(), $spa->GetBackendFolderId()), SYNC_STATUS_FOLDERHIERARCHYCHANGED);
904
							}
905
906
							// Use the state from the importer, as changes may have already happened
907
							$exporter = self::$backend->GetExporter($spa->GetBackendFolderId());
908
909
							if ($exporter === false) {
910
								throw new StatusException(sprintf("HandleSync() could not get an exporter for folder id %s/%s", $spa->GetFolderId(), $spa->GetBackendFolderId()), SYNC_STATUS_FOLDERHIERARCHYCHANGED);
911
							}
912
						}
913
						catch (StatusException $stex) {
914
							$status = $stex->getCode();
915
						}
916
917
						try {
918
							// Stream the messages directly to the PDA
919
							$streamimporter = new ImportChangesStream(self::$encoder, GSync::getSyncObjectFromFolderClass($spa->GetContentClass()));
0 ignored issues
show
Bug introduced by
GSync::getSyncObjectFrom...spa->GetContentClass()) of type string is incompatible with the type SyncObject expected by parameter $objclass of ImportChangesStream::__construct(). ( Ignorable by Annotation )

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

919
							$streamimporter = new ImportChangesStream(self::$encoder, /** @scrutinizer ignore-type */ GSync::getSyncObjectFromFolderClass($spa->GetContentClass()));
Loading history...
920
921
							if ($exporter !== false) {
922
								$exporter->Config($sc->GetParameter($spa, "state"));
923
								$exporter->ConfigContentParameters($spa->GetCPO());
924
								$exporter->InitializeExporter($streamimporter);
925
926
								$changecount = $exporter->GetChangeCount();
927
							}
928
						}
929
						catch (StatusException $stex) {
930
							if ($stex->getCode() === SYNC_FSSTATUS_CODEUNKNOWN && $spa->HasSyncKey()) {
931
								$status = SYNC_STATUS_INVALIDSYNCKEY;
932
							}
933
							else {
934
								$status = $stex->getCode();
935
							}
936
						}
937
938
						if (!$spa->HasSyncKey()) {
939
							self::$topCollector->AnnounceInformation(sprintf("Exporter registered. %d objects queued.", $changecount), $this->singleFolder);
940
							$this->saveMultiFolderInfo("queued", $changecount);
941
							// update folder status as initialized
942
							$spa->SetFolderSyncTotal($changecount);
943
							$spa->SetFolderSyncRemaining($changecount);
944
							if ($changecount > 0) {
945
								self::$deviceManager->SetFolderSyncStatus($folderid, DeviceManager::FLD_SYNC_INITIALIZED);
946
							}
947
						}
948
						elseif ($status != SYNC_STATUS_SUCCESS) {
949
							self::$topCollector->AnnounceInformation(sprintf("StatusException code: %d", $status), $this->singleFolder);
950
							$this->saveMultiFolderInfo("exception", "StatusException");
951
						}
952
						self::$deviceManager->AnnounceProcessStatus($spa->GetFolderId(), $status);
953
					}
954
				}
955
			}
956
957
			// Get a new sync key to output to the client if any changes have been send by the mobile or a new synckey is to be sent
958
			if (!empty($actiondata["modifyids"]) ||
959
				!empty($actiondata["clientids"]) ||
960
				!empty($actiondata["removeids"]) ||
961
				(!$spa->HasSyncKey() && $status == SYNC_STATUS_SUCCESS)) {
962
				$spa->SetNewSyncKey(self::$deviceManager->GetStateManager()->GetNewSyncKey($spa->GetSyncKey()));
963
			}
964
			// get a new synckey only if we did not reach the global limit yet
965
			else {
966
				// when reaching the global limit for changes of all collections, stop processing other collections
967
				if ($sc->GetGlobalWindowSize() <= $this->globallyExportedItems) {
968
					SLog::Write(LOGLEVEL_DEBUG, "Global WindowSize for amount of exported changes reached, omitting output for collection.");
969
970
					continue;
971
				}
972
973
				// get a new synckey if there are changes are we did not reach the limit yet
974
				if ($changecount > 0) {
975
					$spa->SetNewSyncKey(self::$deviceManager->GetStateManager()->GetNewSyncKey($spa->GetSyncKey()));
976
				}
977
			}
978
979
			// Fir AS 14.0+ omit output for folder, if there were no incoming or outgoing changes and no Fetch
980
			if (Request::GetProtocolVersion() >= 14.0 && !$spa->HasNewSyncKey() && $changecount == 0 && empty($actiondata["fetchids"]) && $status == SYNC_STATUS_SUCCESS &&
981
					!$spa->HasConfirmationChanged() && ($newFolderStat === false || !$spa->IsExporterRunRequired($newFolderStat))) {
982
				SLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync: No changes found for %s folder id '%s'. Omitting output.", $spa->GetContentClass(), $spa->GetFolderId()));
983
984
				continue;
985
			}
986
987
			// if there are no other responses sent, we should end with a global status
988
			if ($status == SYNC_STATUS_FOLDERHIERARCHYCHANGED && $this->startTagsSent === false) {
989
				$this->sendStartTags();
990
				self::$encoder->startTag(SYNC_STATUS);
991
				self::$encoder->content($status);
992
				self::$encoder->endTag();
993
				self::$encoder->endTag(); // SYNC_SYNCHRONIZE
994
995
				return true;
996
			}
997
998
			// there is something to send here, sync folder to output
999
			$this->syncFolder($sc, $spa, $exporter, $changecount, $streamimporter, $status, $newFolderStat);
0 ignored issues
show
Bug introduced by
It seems like $streamimporter can also be of type false; however, parameter $streamimporter of Sync::syncFolder() does only seem to accept ImportChangesStream, maybe add an additional type check? ( Ignorable by Annotation )

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

999
			$this->syncFolder($sc, $spa, $exporter, $changecount, /** @scrutinizer ignore-type */ $streamimporter, $status, $newFolderStat);
Loading history...
Bug introduced by
It seems like $status can also be of type status; however, parameter $status of Sync::syncFolder() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

999
			$this->syncFolder($sc, $spa, $exporter, $changecount, $streamimporter, /** @scrutinizer ignore-type */ $status, $newFolderStat);
Loading history...
1000
1001
			// reset status for the next folder
1002
			$status = SYNC_STATUS_SUCCESS;
1003
		} // END foreach collection
1004
1005
		// SYNC_FOLDERS - only if the starttag was sent
1006
		if ($this->startFolderTagSent) {
1007
			self::$encoder->endTag();
1008
		}
1009
1010
		// Check if there was any response - in case of an empty sync request, we shouldn't send an empty answer
1011
		if (!$this->startTagsSent && $emptysync === true) {
1012
			$this->sendStartTags();
1013
			self::$encoder->startTag(SYNC_STATUS);
1014
			self::$encoder->content(SYNC_STATUS_SYNCREQUESTINCOMPLETE);
1015
			self::$encoder->endTag();
1016
		}
1017
1018
		// SYNC_SYNCHRONIZE - only if the starttag was sent
1019
		if ($this->startTagsSent) {
1020
			self::$encoder->endTag();
1021
		}
1022
1023
		// final top announcement for a multi-folder sync
1024
		if ($sc->GetCollectionCount() > 1) {
1025
			self::$topCollector->AnnounceInformation($this->getMultiFolderInfoLine($sc->GetCollectionCount()), true);
1026
			SLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync: Processed %d folders", $sc->GetCollectionCount()));
1027
		}
1028
1029
		// update the waittime waited
1030
		self::$waitTime = $sc->GetWaitedSeconds();
1031
1032
		return true;
1033
	}
1034
1035
	/**
1036
	 * Sends the SYNC_SYNCHRONIZE once per request.
1037
	 */
1038
	private function sendStartTags() {
1039
		if ($this->startTagsSent === false) {
1040
			self::$encoder->startWBXML();
1041
			self::$encoder->startTag(SYNC_SYNCHRONIZE);
1042
			$this->startTagsSent = true;
1043
		}
1044
	}
1045
1046
	/**
1047
	 * Sends the SYNC_FOLDERS once per request.
1048
	 */
1049
	private function sendFolderStartTag() {
1050
		$this->sendStartTags();
1051
		if ($this->startFolderTagSent === false) {
1052
			self::$encoder->startTag(SYNC_FOLDERS);
1053
			$this->startFolderTagSent = true;
1054
		}
1055
	}
1056
1057
	/**
1058
	 * Synchronizes a folder to the output stream. Changes for this folders are expected.
1059
	 *
1060
	 * @param SyncCollections     $sc
1061
	 * @param SyncParameters      $spa
1062
	 * @param IExportChanges      $exporter       Fully configured exporter for this folder
1063
	 * @param int                 $changecount    Amount of changes expected
1064
	 * @param ImportChangesStream $streamimporter Output stream
1065
	 * @param int                 $status         current status of the folder processing
1066
	 * @param string              $newFolderStat  the new folder stat to be set if everything was exported
1067
	 *
1068
	 * @return int sync status code
1069
	 *
1070
	 * @throws StatusException
1071
	 */
1072
	private function syncFolder($sc, $spa, $exporter, $changecount, $streamimporter, $status, $newFolderStat) {
1073
		$actiondata = $sc->GetParameter($spa, "actiondata");
1074
1075
		// send the WBXML start tags (if not happened already)
1076
		$this->sendFolderStartTag();
1077
		self::$encoder->startTag(SYNC_FOLDER);
1078
1079
		if ($spa->HasContentClass()) {
1080
			SLog::Write(LOGLEVEL_DEBUG, sprintf("Folder type: %s", $spa->GetContentClass()));
1081
			// AS 12.0 devices require content class
1082
			if (Request::GetProtocolVersion() < 12.1) {
1083
				self::$encoder->startTag(SYNC_FOLDERTYPE);
1084
				self::$encoder->content($spa->GetContentClass());
1085
				self::$encoder->endTag();
1086
			}
1087
		}
1088
1089
		self::$encoder->startTag(SYNC_SYNCKEY);
1090
		if ($status == SYNC_STATUS_SUCCESS && $spa->HasNewSyncKey()) {
1091
			self::$encoder->content($spa->GetNewSyncKey());
1092
		}
1093
		else {
1094
			self::$encoder->content($spa->GetSyncKey());
1095
		}
1096
		self::$encoder->endTag();
1097
1098
		self::$encoder->startTag(SYNC_FOLDERID);
1099
		self::$encoder->content($spa->GetFolderId());
1100
		self::$encoder->endTag();
1101
1102
		self::$encoder->startTag(SYNC_STATUS);
1103
		self::$encoder->content($status);
1104
		self::$encoder->endTag();
1105
1106
		// announce failing status to the process loop detection
1107
		if ($status !== SYNC_STATUS_SUCCESS) {
1108
			self::$deviceManager->AnnounceProcessStatus($spa->GetFolderId(), $status);
1109
		}
1110
1111
		// Output IDs and status for incoming items & requests
1112
		if ($status == SYNC_STATUS_SUCCESS && (
1113
			!empty($actiondata["clientids"]) ||
1114
				!empty($actiondata["modifyids"]) ||
1115
				!empty($actiondata["removeids"]) ||
1116
				!empty($actiondata["fetchids"])
1117
		)) {
1118
			self::$encoder->startTag(SYNC_REPLIES);
1119
			// output result of all new incoming items
1120
			foreach ($actiondata["clientids"] as $clientid => $response) {
1121
				self::$encoder->startTag(SYNC_ADD);
1122
				self::$encoder->startTag(SYNC_CLIENTENTRYID);
1123
				self::$encoder->content($clientid);
1124
				self::$encoder->endTag();
1125
				if (!empty($response->serverid)) {
1126
					self::$encoder->startTag(SYNC_SERVERENTRYID);
1127
					self::$encoder->content($response->serverid);
1128
					self::$encoder->endTag();
1129
				}
1130
				self::$encoder->startTag(SYNC_STATUS);
1131
				self::$encoder->content($actiondata["statusids"][$clientid] ?? SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR);
1132
				self::$encoder->endTag();
1133
				if (!empty($response->hasResponse)) {
1134
					self::$encoder->startTag(SYNC_DATA);
1135
					$response->Encode(self::$encoder);
1136
					self::$encoder->endTag();
1137
				}
1138
				self::$encoder->endTag();
1139
			}
1140
1141
			// loop through modify operations which were not a success, send status
1142
			foreach ($actiondata["modifyids"] as $serverid => $response) {
1143
				if (isset($actiondata["statusids"][$serverid]) && ($actiondata["statusids"][$serverid] !== SYNC_STATUS_SUCCESS || !empty($response->hasResponse))) {
1144
					self::$encoder->startTag(SYNC_MODIFY);
1145
					self::$encoder->startTag(SYNC_SERVERENTRYID);
1146
					self::$encoder->content($serverid);
1147
					self::$encoder->endTag();
1148
					self::$encoder->startTag(SYNC_STATUS);
1149
					self::$encoder->content($actiondata["statusids"][$serverid]);
1150
					self::$encoder->endTag();
1151
					if (!empty($response->hasResponse)) {
1152
						self::$encoder->startTag(SYNC_DATA);
1153
						$response->Encode(self::$encoder);
1154
						self::$encoder->endTag();
1155
					}
1156
					self::$encoder->endTag();
1157
				}
1158
			}
1159
1160
			// loop through remove operations which were not a success, send status
1161
			foreach ($actiondata["removeids"] as $serverid) {
1162
				if (isset($actiondata["statusids"][$serverid]) && $actiondata["statusids"][$serverid] !== SYNC_STATUS_SUCCESS) {
1163
					self::$encoder->startTag(SYNC_REMOVE);
1164
					self::$encoder->startTag(SYNC_SERVERENTRYID);
1165
					self::$encoder->content($serverid);
1166
					self::$encoder->endTag();
1167
					self::$encoder->startTag(SYNC_STATUS);
1168
					self::$encoder->content($actiondata["statusids"][$serverid]);
1169
					self::$encoder->endTag();
1170
					self::$encoder->endTag();
1171
				}
1172
			}
1173
1174
			if (!empty($actiondata["fetchids"])) {
1175
				self::$topCollector->AnnounceInformation(sprintf("Fetching %d objects ", count($actiondata["fetchids"])), $this->singleFolder);
1176
				$this->saveMultiFolderInfo("fetching", count($actiondata["fetchids"]));
1177
			}
1178
1179
			foreach ($actiondata["fetchids"] as $id) {
1180
				$data = false;
1181
1182
				try {
1183
					$fetchstatus = SYNC_STATUS_SUCCESS;
1184
1185
					// if this is an additional folder the backend has to be setup correctly
1186
					if (!self::$backend->Setup(GSync::GetAdditionalSyncFolderStore($spa->GetBackendFolderId()))) {
1187
						throw new StatusException(sprintf("HandleSync(): could not Setup() the backend to fetch in folder id %s/%s", $spa->GetFolderId(), $spa->GetBackendFolderId()), SYNC_STATUS_OBJECTNOTFOUND);
1188
					}
1189
1190
					$data = self::$backend->Fetch($spa->GetBackendFolderId(), $id, $spa->GetCPO());
1191
1192
					// check if the message is broken
1193
					if (GSync::GetDeviceManager(false) && GSync::GetDeviceManager()->DoNotStreamMessage($id, $data)) {
1194
						SLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): message not to be streamed as requested by DeviceManager, id = %s", $id));
1195
						$fetchstatus = SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR;
1196
					}
1197
				}
1198
				catch (StatusException $stex) {
1199
					$fetchstatus = $stex->getCode();
1200
				}
1201
1202
				self::$encoder->startTag(SYNC_FETCH);
1203
				self::$encoder->startTag(SYNC_SERVERENTRYID);
1204
				self::$encoder->content($id);
1205
				self::$encoder->endTag();
1206
1207
				self::$encoder->startTag(SYNC_STATUS);
1208
				self::$encoder->content($fetchstatus);
1209
				self::$encoder->endTag();
1210
1211
				if ($data !== false && $status == SYNC_STATUS_SUCCESS) {
1212
					self::$encoder->startTag(SYNC_DATA);
1213
					$data->Encode(self::$encoder);
1214
					self::$encoder->endTag();
1215
				}
1216
				else {
1217
					SLog::Write(LOGLEVEL_WARN, sprintf("Unable to Fetch '%s'", $id));
1218
				}
1219
				self::$encoder->endTag();
1220
			}
1221
			self::$encoder->endTag();
1222
		}
1223
1224
		if ($sc->GetParameter($spa, "getchanges") && $spa->HasFolderId() && $spa->HasContentClass() && $spa->HasSyncKey()) {
0 ignored issues
show
Bug introduced by
The method HasFolderId() does not exist on SyncParameters. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

1224
		if ($sc->GetParameter($spa, "getchanges") && $spa->/** @scrutinizer ignore-call */ HasFolderId() && $spa->HasContentClass() && $spa->HasSyncKey()) {
Loading history...
1225
			$moreAvailableSent = false;
1226
			$windowSize = self::$deviceManager->GetWindowSize($spa->GetFolderId(), $spa->GetUuid(), $spa->GetUuidCounter(), $changecount);
1227
1228
			// limit windowSize to the max available limit of the global window size left
1229
			$globallyAvailable = $sc->GetGlobalWindowSize() - $this->globallyExportedItems;
1230
			if ($changecount > $globallyAvailable && $windowSize > $globallyAvailable) {
1231
				SLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Limit window size to %d as the global window size limit will be reached", $globallyAvailable));
1232
				$windowSize = $globallyAvailable;
1233
			}
1234
			// send <MoreAvailable/> if there are more changes than fit in the folder windowsize
1235
			// or there is a move state (another sync should be done afterwards)
1236
			if ($changecount > $windowSize) {
1237
				self::$encoder->startTag(SYNC_MOREAVAILABLE, false, true);
1238
				$moreAvailableSent = true;
1239
				$spa->DelFolderStat();
1240
			}
1241
		}
1242
1243
		// Stream outgoing changes
1244
		if ($status == SYNC_STATUS_SUCCESS && $sc->GetParameter($spa, "getchanges") == true && $windowSize > 0 && (bool) $exporter) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $windowSize does not seem to be defined for all execution paths leading up to this point.
Loading history...
1245
			self::$topCollector->AnnounceInformation(sprintf("Streaming data of %d objects", ($changecount > $windowSize) ? $windowSize : $changecount));
1246
1247
			// Output message changes per folder
1248
			self::$encoder->startTag(SYNC_PERFORM);
1249
1250
			$n = 0;
1251
			WBXMLDecoder::ResetInWhile("syncSynchronize");
1252
			while (WBXMLDecoder::InWhile("syncSynchronize")) {
1253
				try {
1254
					$progress = $exporter->Synchronize();
1255
					if (!is_array($progress)) {
1256
						break;
1257
					}
1258
					++$n;
1259
					if ($n % 10 == 0) {
1260
						self::$topCollector->AnnounceInformation(sprintf("Streamed data of %d objects out of %d", $n, ($changecount > $windowSize) ? $windowSize : $changecount));
1261
					}
1262
				}
1263
				catch (SyncObjectBrokenException $mbe) {
1264
					$brokenSO = $mbe->GetSyncObject();
1265
					if (!$brokenSO) {
1266
						SLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): Caught SyncObjectBrokenException but broken SyncObject not available. This should be fixed in the backend."));
1267
					}
1268
					else {
1269
						if (!isset($brokenSO->id)) {
1270
							$brokenSO->id = "Unknown ID";
1271
							SLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): Caught SyncObjectBrokenException but no ID of object set. This should be fixed in the backend."));
1272
						}
1273
						self::$deviceManager->AnnounceIgnoredMessage($spa->GetFolderId(), $brokenSO->id, $brokenSO);
1274
					}
1275
				}
1276
				// something really bad happened while exporting changes
1277
				catch (StatusException $stex) {
1278
					$status = $stex->getCode();
1279
					// during export we found out that the states should be thrown away
1280
					if ($status == SYNC_STATUS_INVALIDSYNCKEY) {
1281
						self::$deviceManager->ForceFolderResync($spa->GetFolderId());
1282
1283
						break;
1284
					}
1285
				}
1286
1287
				if ($n >= $windowSize || Request::IsRequestTimeoutReached() || Request::IsRequestMemoryLimitReached()) {
1288
					SLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Exported maxItems of messages: %d / %d", $n, $changecount));
1289
1290
					break;
1291
				}
1292
			}
1293
1294
			// $progress is not an array when exporting the last message
1295
			// so we get the number to display from the streamimporter if it's available
1296
			if ((bool) $streamimporter) {
1297
				$n = $streamimporter->GetImportedMessages();
1298
			}
1299
1300
			self::$encoder->endTag();
1301
1302
			// log the request timeout
1303
			if (Request::IsRequestTimeoutReached() || Request::IsRequestMemoryLimitReached()) {
1304
				SLog::Write(LOGLEVEL_DEBUG, "HandleSync(): Stopping export as limits of request timeout or available memory are almost reached!");
1305
				// Send a <MoreAvailable/> tag if we reached the request timeout or max memory, there are more changes and a moreavailable was not already send
1306
				if (!$moreAvailableSent && ($n > $windowSize)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $moreAvailableSent does not seem to be defined for all execution paths leading up to this point.
Loading history...
1307
					self::$encoder->startTag(SYNC_MOREAVAILABLE, false, true);
1308
					$spa->DelFolderStat();
1309
					$moreAvailableSent = true;
0 ignored issues
show
Unused Code introduced by
The assignment to $moreAvailableSent is dead and can be removed.
Loading history...
1310
				}
1311
			}
1312
1313
			self::$topCollector->AnnounceInformation(sprintf("Outgoing %d objects%s", $n, ($n >= $windowSize) ? " of " . $changecount : ""), $this->singleFolder);
1314
			$this->saveMultiFolderInfo("outgoing", $n);
1315
			$this->saveMultiFolderInfo("queued", $changecount);
1316
1317
			$this->globallyExportedItems += $n;
1318
1319
			// update folder status, if there is something set
1320
			if ($spa->GetFolderSyncRemaining() && $changecount > 0) {
0 ignored issues
show
Bug introduced by
The method GetFolderSyncRemaining() does not exist on SyncParameters. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

1320
			if ($spa->/** @scrutinizer ignore-call */ GetFolderSyncRemaining() && $changecount > 0) {
Loading history...
1321
				$spa->SetFolderSyncRemaining($changecount);
0 ignored issues
show
Bug introduced by
The method SetFolderSyncRemaining() does not exist on SyncParameters. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

1321
				$spa->/** @scrutinizer ignore-call */ 
1322
          SetFolderSyncRemaining($changecount);
Loading history...
1322
			}
1323
			// changecount is initialized with 'false', so 0 means no changes!
1324
			if ($changecount === 0 || ($changecount !== false && $changecount <= $windowSize)) {
1325
				self::$deviceManager->SetFolderSyncStatus($spa->GetFolderId(), DeviceManager::FLD_SYNC_COMPLETED);
1326
1327
				// we should update the folderstat, but we recheck to see if it changed since the exporter setup. If so, it's not updated to force another sync
1328
				if (self::$backend->HasFolderStats()) {
1329
					$newFolderStatAfterExport = self::$backend->GetFolderStat(GSync::GetAdditionalSyncFolderStore($spa->GetBackendFolderId()), $spa->GetBackendFolderId());
1330
					if ($newFolderStat === $newFolderStatAfterExport) {
1331
						$this->setFolderStat($spa, $newFolderStat);
1332
					}
1333
					else {
1334
						SLog::Write(LOGLEVEL_DEBUG, "Sync() Folderstat differs after export, force another exporter run.");
1335
					}
1336
				}
1337
			}
1338
			else {
1339
				self::$deviceManager->SetFolderSyncStatus($spa->GetFolderId(), DeviceManager::FLD_SYNC_INPROGRESS);
1340
			}
1341
		}
1342
1343
		self::$encoder->endTag();
1344
1345
		// Save the sync state for the next time
1346
		if ($spa->HasNewSyncKey()) {
1347
			self::$topCollector->AnnounceInformation("Saving state");
1348
1349
			try {
1350
				if ($exporter) {
0 ignored issues
show
introduced by
$exporter is of type IExportChanges, thus it always evaluated to true.
Loading history...
1351
					$state = $exporter->GetState();
1352
				}
1353
1354
				// nothing exported, but possibly imported - get the importer state
1355
				elseif ($sc->GetParameter($spa, "state") !== null) {
1356
					$state = $sc->GetParameter($spa, "state");
1357
				}
1358
1359
				// if a new request without state information (hierarchy) save an empty state
1360
				elseif (!$spa->HasSyncKey()) {
1361
					$state = "";
1362
				}
1363
			}
1364
			catch (StatusException $stex) {
1365
				$status = $stex->getCode();
1366
			}
1367
1368
			if (isset($state) && $status == SYNC_STATUS_SUCCESS) {
1369
				self::$deviceManager->GetStateManager()->SetSyncState($spa->GetNewSyncKey(), $state, $spa->GetFolderId());
1370
			}
1371
			else {
1372
				SLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): error saving '%s' - no state information available", $spa->GetNewSyncKey()));
0 ignored issues
show
Bug introduced by
It seems like $spa->GetNewSyncKey() can also be of type false; however, parameter $values of sprintf() does only seem to accept double|integer|string, maybe add an additional type check? ( Ignorable by Annotation )

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

1372
				SLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): error saving '%s' - no state information available", /** @scrutinizer ignore-type */ $spa->GetNewSyncKey()));
Loading history...
1373
			}
1374
		}
1375
1376
		// save SyncParameters
1377
		if ($status == SYNC_STATUS_SUCCESS && empty($actiondata["fetchids"])) {
1378
			$sc->SaveCollection($spa);
1379
		}
1380
1381
		return $status;
1382
	}
1383
1384
	/**
1385
	 * Loads the states and writes them into the SyncCollection Object and the actiondata failstate.
1386
	 *
1387
	 * @param SyncCollection $sc           SyncCollection object
1388
	 * @param SyncParameters $spa          SyncParameters object
1389
	 * @param array          $actiondata   Actiondata array
1390
	 * @param bool           $loadFailsafe (opt) default false - indicates if the failsafe states should be loaded
1391
	 *
1392
	 * @return status indicating if there were errors. If no errors, status is SYNC_STATUS_SUCCESS
1393
	 */
1394
	private function loadStates($sc, $spa, &$actiondata, $loadFailsafe = false) {
1395
		$status = SYNC_STATUS_SUCCESS;
1396
1397
		if ($sc->GetParameter($spa, "state") == null) {
1398
			SLog::Write(LOGLEVEL_DEBUG, sprintf("Sync->loadStates(): loading states for folder '%s'", $spa->GetFolderId()));
1399
1400
			try {
1401
				$sc->AddParameter($spa, "state", self::$deviceManager->GetStateManager()->GetSyncState($spa->GetSyncKey()));
1402
1403
				if ($loadFailsafe) {
1404
					// if this request was made before, there will be a failstate available
1405
					$actiondata["failstate"] = self::$deviceManager->GetStateManager()->GetSyncFailState();
1406
				}
1407
1408
				// if this is an additional folder the backend has to be setup correctly
1409
				if (!self::$backend->Setup(GSync::GetAdditionalSyncFolderStore($spa->GetBackendFolderId()))) {
1410
					throw new StatusException(sprintf("HandleSync() could not Setup() the backend for folder id %s/%s", $spa->GetFolderId(), $spa->GetBackendFolderId()), SYNC_STATUS_FOLDERHIERARCHYCHANGED);
1411
				}
1412
			}
1413
			catch (StateNotFoundException) {
1414
				$status = SYNC_STATUS_INVALIDSYNCKEY;
1415
				self::$topCollector->AnnounceInformation("StateNotFoundException", $this->singleFolder);
1416
				$this->saveMultiFolderInfo("exception", "StateNotFoundException");
1417
			}
1418
			catch (StatusException $stex) {
1419
				$status = $stex->getCode();
1420
				self::$topCollector->AnnounceInformation(sprintf("StatusException code: %d", $status), $this->singleFolder);
1421
				$this->saveMultiFolderInfo("exception", "StateNotFoundException");
1422
			}
1423
		}
1424
1425
		return $status;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $status also could return the type integer which is incompatible with the documented return type status.
Loading history...
1426
	}
1427
1428
	/**
1429
	 * Initializes the importer for the SyncParameters folder, loads necessary
1430
	 * states (incl. failsafe states) and initializes the conflict detection.
1431
	 *
1432
	 * @param SyncCollection $sc         SyncCollection object
1433
	 * @param SyncParameters $spa        SyncParameters object
1434
	 * @param array          $actiondata Actiondata array
1435
	 *
1436
	 * @return status indicating if there were errors. If no errors, status is SYNC_STATUS_SUCCESS
1437
	 */
1438
	private function getImporter($sc, $spa, &$actiondata) {
1439
		SLog::Write(LOGLEVEL_DEBUG, "Sync->getImporter(): initialize importer");
1440
		$status = SYNC_STATUS_SUCCESS;
0 ignored issues
show
Unused Code introduced by
The assignment to $status is dead and can be removed.
Loading history...
1441
1442
		// load the states with failsafe data
1443
		$status = $this->loadStates($sc, $spa, $actiondata, true);
1444
1445
		try {
1446
			if ($status == SYNC_STATUS_SUCCESS) {
0 ignored issues
show
introduced by
The condition $status == SYNC_STATUS_SUCCESS is always false.
Loading history...
1447
				// Configure importer with last state
1448
				$this->importer = self::$backend->GetImporter($spa->GetBackendFolderId());
1449
1450
				// if something goes wrong, ask the mobile to resync the hierarchy
1451
				if ($this->importer === false) {
1452
					throw new StatusException(sprintf("Sync->getImporter(): no importer for folder id %s/%s", $spa->GetFolderId(), $spa->GetBackendFolderId()), SYNC_STATUS_FOLDERHIERARCHYCHANGED);
1453
				}
1454
1455
				// if there is a valid state obtained after importing changes in a previous loop, we use that state
1456
				if (isset($actiondata["failstate"], $actiondata["failstate"]["failedsyncstate"])) {
1457
					$this->importer->Config($actiondata["failstate"]["failedsyncstate"], $spa->GetConflict());
1458
				}
1459
				else {
1460
					$this->importer->Config($sc->GetParameter($spa, "state"), $spa->GetConflict());
1461
				}
1462
1463
				// the CPO is also needed by the importer to check if imported changes are inside the sync window
1464
				$this->importer->ConfigContentParameters($spa->GetCPO());
1465
				$this->importer->LoadConflicts($spa->GetCPO(), $sc->GetParameter($spa, "state"));
1466
			}
1467
		}
1468
		catch (StatusException $stex) {
1469
			$status = $stex->getCode();
1470
		}
1471
1472
		return $status;
1473
	}
1474
1475
	/**
1476
	 * Imports a message.
1477
	 *
1478
	 * @param SyncParameters $spa          SyncParameters object
1479
	 * @param array          $actiondata   Actiondata array
1480
	 * @param int            $todo         WBXML flag indicating how message should be imported.
1481
	 *                                     Valid values: SYNC_ADD, SYNC_MODIFY, SYNC_REMOVE
1482
	 * @param SyncObject     $message      SyncObject message to be imported
1483
	 * @param string         $clientid     Client message identifier
1484
	 * @param string         $serverid     Server message identifier
1485
	 * @param string         $foldertype   On sms sync, this says "SMS", else false
1486
	 * @param int            $messageCount Counter of already imported messages
1487
	 *
1488
	 * @return - message related status are returned in the actiondata
0 ignored issues
show
Documentation Bug introduced by
The doc comment - at position 0 could not be parsed: Unknown type name '-' at position 0 in -.
Loading history...
1489
	 *
1490
	 * @throws StatusException in case the importer is not available
1491
	 */
1492
	private function importMessage($spa, &$actiondata, $todo, $message, $clientid, $serverid, $foldertype, $messageCount) {
1493
		// the importer needs to be available!
1494
		if ($this->importer == false) {
1495
			throw new StatusException("Sync->importMessage(): importer not available", SYNC_STATUS_SERVERERROR);
1496
		}
1497
1498
		// mark this state as used, e.g. for HeartBeat
1499
		self::$deviceManager->SetHeartbeatStateIntegrity($spa->GetFolderId(), $spa->GetUuid(), $spa->GetUuidCounter());
1500
1501
		// Detect incoming loop
1502
		// messages which were created/removed before will not have the same action executed again
1503
		// if a message is edited we perform this action "again", as the message could have been changed on the mobile in the meantime
1504
		$ignoreMessage = false;
1505
		if ($actiondata["failstate"]) {
1506
			// message was ADDED before, do NOT add it again
1507
			if ($todo == SYNC_ADD && isset($actiondata["failstate"]["clientids"][$clientid])) {
1508
				$ignoreMessage = true;
1509
1510
				// make sure no messages are sent back
1511
				self::$deviceManager->SetWindowSize($spa->GetFolderId(), 0);
1512
1513
				$actiondata["clientids"][$clientid] = $actiondata["failstate"]["clientids"][$clientid];
1514
				$actiondata["statusids"][$clientid] = $actiondata["failstate"]["statusids"][$clientid];
1515
1516
				SLog::Write(LOGLEVEL_INFO, sprintf("Mobile loop detected! Incoming new message '%s' was created on the server before. Replying with known new server id: %s", $clientid, $actiondata["clientids"][$clientid]));
1517
			}
1518
1519
			// message was REMOVED before, do NOT attempt to remove it again
1520
			if ($todo == SYNC_REMOVE && isset($actiondata["failstate"]["removeids"][$serverid])) {
1521
				$ignoreMessage = true;
1522
1523
				// make sure no messages are sent back
1524
				self::$deviceManager->SetWindowSize($spa->GetFolderId(), 0);
1525
1526
				$actiondata["removeids"][$serverid] = $actiondata["failstate"]["removeids"][$serverid];
1527
				$actiondata["statusids"][$serverid] = $actiondata["failstate"]["statusids"][$serverid];
1528
1529
				SLog::Write(LOGLEVEL_INFO, sprintf("Mobile loop detected! Message '%s' was deleted by the mobile before. Replying with known status: %s", $clientid, $actiondata["statusids"][$serverid]));
1530
			}
1531
		}
1532
1533
		if (!$ignoreMessage) {
1534
			switch ($todo) {
1535
				case SYNC_MODIFY:
1536
					self::$topCollector->AnnounceInformation(sprintf("Saving modified message %d", $messageCount));
1537
1538
					try {
1539
						// ignore sms messages
1540
						if ($foldertype == "SMS" || stripos($serverid, self::GSYNCIGNORESMS) !== false) {
1541
							SLog::Write(LOGLEVEL_DEBUG, "SMS sync are not supported. Ignoring message.");
1542
							// TODO we should update the SMS
1543
							$actiondata["statusids"][$serverid] = SYNC_STATUS_SUCCESS;
1544
						}
1545
						// check incoming message without logging WARN messages about errors
1546
						elseif (!($message instanceof SyncObject) || !$message->Check(true)) {
0 ignored issues
show
introduced by
$message is always a sub-type of SyncObject.
Loading history...
1547
							$actiondata["statusids"][$serverid] = SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR;
1548
						}
1549
						else {
1550
							// if there is just a read flag change, import it via ImportMessageReadFlag()
1551
							if (isset($message->read) && !isset($message->flag) && $message->getCheckedParameters() < 3) {
1552
								$response = $this->importer->ImportMessageReadFlag($serverid, $message->read);
1553
							}
1554
							else {
1555
								$response = $this->importer->ImportMessageChange($serverid, $message);
1556
							}
1557
1558
							$response->serverid = $serverid;
1559
							$actiondata["modifyids"][$serverid] = $response;
1560
							$actiondata["statusids"][$serverid] = SYNC_STATUS_SUCCESS;
1561
						}
1562
					}
1563
					catch (StatusException $stex) {
1564
						$actiondata["statusids"][$serverid] = $stex->getCode();
1565
					}
1566
					break;
1567
1568
				case SYNC_ADD:
1569
					self::$topCollector->AnnounceInformation(sprintf("Creating new message from mobile %d", $messageCount));
1570
1571
					try {
1572
						// mark the message as new message so SyncObject->Check() can differentiate
1573
						$message->flags = SYNC_NEWMESSAGE;
1574
1575
						// ignore sms messages
1576
						if ($foldertype == "SMS") {
1577
							SLog::Write(LOGLEVEL_DEBUG, "SMS sync are not supported. Ignoring message.");
1578
							// TODO we should create the SMS
1579
							// return a fake serverid which we can identify later
1580
							$actiondata["clientids"][$clientid] = self::GSYNCIGNORESMS . $clientid;
1581
							$actiondata["statusids"][$clientid] = SYNC_STATUS_SUCCESS;
1582
						}
1583
						// check incoming message without logging WARN messages about errors
1584
						elseif (!($message instanceof SyncObject) || !$message->Check(true)) {
0 ignored issues
show
introduced by
$message is always a sub-type of SyncObject.
Loading history...
1585
							$actiondata["clientids"][$clientid] = false;
1586
							$actiondata["statusids"][$clientid] = SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR;
1587
						}
1588
						else {
1589
							$actiondata["clientids"][$clientid] = false;
1590
							$actiondata["clientids"][$clientid] = $this->importer->ImportMessageChange(false, $message);
1591
							$actiondata["statusids"][$clientid] = SYNC_STATUS_SUCCESS;
1592
						}
1593
					}
1594
					catch (StatusException $stex) {
1595
						$actiondata["statusids"][$clientid] = $stex->getCode();
1596
					}
1597
					break;
1598
1599
				case SYNC_REMOVE:
1600
					self::$topCollector->AnnounceInformation(sprintf("Deleting message removed on mobile %d", $messageCount));
1601
1602
					try {
1603
						$actiondata["removeids"][] = $serverid;
1604
						// ignore sms messages
1605
						if ($foldertype == "SMS" || stripos($serverid, self::GSYNCIGNORESMS) !== false) {
1606
							SLog::Write(LOGLEVEL_DEBUG, "SMS sync are not supported. Ignoring message.");
1607
							// TODO we should delete the SMS
1608
							$actiondata["statusids"][$serverid] = SYNC_STATUS_SUCCESS;
1609
						}
1610
						else {
1611
							// if message deletions are to be moved, move them
1612
							if ($spa->GetDeletesAsMoves()) {
0 ignored issues
show
Bug introduced by
The method GetDeletesAsMoves() does not exist on SyncParameters. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

1612
							if ($spa->/** @scrutinizer ignore-call */ GetDeletesAsMoves()) {
Loading history...
1613
								$folderid = self::$backend->GetWasteBasket();
1614
1615
								if ($folderid) {
1616
									$actiondata["statusids"][$serverid] = SYNC_STATUS_SUCCESS;
1617
									$this->importer->ImportMessageMove($serverid, $folderid);
1618
1619
									break;
1620
								}
1621
1622
								SLog::Write(LOGLEVEL_WARN, "Message should be moved to WasteBasket, but the Backend did not return a destination ID. Message is hard deleted now!");
1623
							}
1624
1625
							$this->importer->ImportMessageDeletion($serverid);
1626
							$actiondata["statusids"][$serverid] = SYNC_STATUS_SUCCESS;
1627
						}
1628
					}
1629
					catch (StatusException $stex) {
1630
						if ($stex->getCode() != SYNC_MOVEITEMSSTATUS_SUCCESS) {
1631
							$actiondata["statusids"][$serverid] = SYNC_STATUS_OBJECTNOTFOUND;
1632
						}
1633
					}
1634
					break;
1635
			}
1636
			SLog::Write(LOGLEVEL_DEBUG, "Sync->importMessage(): message imported");
1637
		}
1638
	}
1639
1640
	/**
1641
	 * Keeps some interesting information about the sync process of several folders.
1642
	 *
1643
	 * @param mixed $key
1644
	 * @param mixed $value
1645
	 */
1646
	private function saveMultiFolderInfo($key, $value) {
1647
		if ($key == "incoming" || $key == "outgoing" || $key == "queued" || $key == "fetching") {
1648
			if (!isset($this->multiFolderInfo[$key])) {
1649
				$this->multiFolderInfo[$key] = 0;
1650
			}
1651
			$this->multiFolderInfo[$key] += $value;
1652
		}
1653
		if ($key == "exception") {
1654
			if (!isset($this->multiFolderInfo[$key])) {
1655
				$this->multiFolderInfo[$key] = [];
1656
			}
1657
			$this->multiFolderInfo[$key][] = $value;
1658
		}
1659
	}
1660
1661
	/**
1662
	 * Returns a single string with information about the multi folder synchronization.
1663
	 *
1664
	 * @param int $amountOfFolders
1665
	 *
1666
	 * @return string
1667
	 */
1668
	private function getMultiFolderInfoLine($amountOfFolders) {
1669
		$s = $amountOfFolders . " folders";
1670
		if (isset($this->multiFolderInfo["incoming"])) {
1671
			$s .= ": " . $this->multiFolderInfo["incoming"] . " saved";
1672
		}
1673
		if (isset($this->multiFolderInfo["outgoing"], $this->multiFolderInfo["queued"]) && $this->multiFolderInfo["outgoing"] > 0) {
1674
			$s .= sprintf(": Streamed %d out of %d", $this->multiFolderInfo["outgoing"], $this->multiFolderInfo["queued"]);
1675
		}
1676
		elseif (!isset($this->multiFolderInfo["outgoing"]) && !isset($this->multiFolderInfo["queued"])) {
1677
			$s .= ": no changes";
1678
		}
1679
		else {
1680
			if (isset($this->multiFolderInfo["outgoing"])) {
1681
				$s .= "/" . $this->multiFolderInfo["outgoing"] . " streamed";
1682
			}
1683
			if (isset($this->multiFolderInfo["queued"])) {
1684
				$s .= "/" . $this->multiFolderInfo["queued"] . " queued";
1685
			}
1686
		}
1687
		if (isset($this->multiFolderInfo["exception"])) {
1688
			$exceptions = array_count_values($this->multiFolderInfo["exception"]);
1689
			foreach ($exceptions as $name => $count) {
1690
				$s .= sprintf("-%s(%d)", $name, $count);
1691
			}
1692
		}
1693
1694
		return $s;
1695
	}
1696
1697
	/**
1698
	 * Sets the new folderstat and calculates & sets an expiration date for the folder stat.
1699
	 *
1700
	 * @param SyncParameters $spa
1701
	 * @param string         $newFolderStat
1702
	 */
1703
	private function setFolderStat($spa, $newFolderStat) {
1704
		$spa->SetFolderStat($newFolderStat);
0 ignored issues
show
Bug introduced by
The method SetFolderStat() does not exist on SyncParameters. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

1704
		$spa->/** @scrutinizer ignore-call */ 
1705
        SetFolderStat($newFolderStat);
Loading history...
1705
		$maxTimeout = 60 * 60 * 24 * 31; // one month
1706
1707
		$interval = Utils::GetFiltertypeInterval($spa->GetFilterType());
0 ignored issues
show
Bug introduced by
It seems like $spa->GetFilterType() can also be of type boolean; however, parameter $filtertype of Utils::GetFiltertypeInterval() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

1707
		$interval = Utils::GetFiltertypeInterval(/** @scrutinizer ignore-type */ $spa->GetFilterType());
Loading history...
1708
		$timeout = time() + (($interval && $interval < $maxTimeout) ? $interval : $maxTimeout);
1709
		// randomize timeout in 12h
1710
		$timeout -= random_int(0, 43200);
1711
		SLog::Write(LOGLEVEL_DEBUG, sprintf("Sync()->setFolderStat() on %s: %s expiring %s", $spa->getFolderId(), $newFolderStat, date('Y-m-d H:i:s', $timeout)));
0 ignored issues
show
Bug introduced by
The method getFolderId() does not exist on SyncParameters. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

1711
		SLog::Write(LOGLEVEL_DEBUG, sprintf("Sync()->setFolderStat() on %s: %s expiring %s", $spa->/** @scrutinizer ignore-call */ getFolderId(), $newFolderStat, date('Y-m-d H:i:s', $timeout)));
Loading history...
1712
		$spa->SetFolderStatTimeout($timeout);
0 ignored issues
show
Bug introduced by
The method SetFolderStatTimeout() does not exist on SyncParameters. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

1712
		$spa->/** @scrutinizer ignore-call */ 
1713
        SetFolderStatTimeout($timeout);
Loading history...
1713
	}
1714
}
1715