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

134
							$spa->/** @scrutinizer ignore-call */ 
135
             DelFolderStat();
Loading history...
135
							$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

135
							$spa->/** @scrutinizer ignore-call */ 
136
             SetMoveState(false);
Loading history...
136
						}
137
						elseif ($synckey !== false) {
138
							if ($synckey !== $spa->GetSyncKey() && $synckey !== $spa->GetNewSyncKey()) {
139
								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");
140
								$spa->DelFolderStat();
141
							}
142
							$spa->SetSyncKey($synckey);
143
						}
144
					}
145
					catch (StateInvalidException $stie) {
146
						$spa = new SyncParameters();
147
						$status = SYNC_STATUS_INVALIDSYNCKEY;
148
						self::$topCollector->AnnounceInformation("State invalid - Resync folder", $this->singleFolder);
149
						self::$deviceManager->ForceFolderResync($folderid);
150
						$this->saveMultiFolderInfo("exception", "StateInvalidException");
151
					}
152
153
					// update folderid.. this might be a new object
154
					$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

154
					$spa->/** @scrutinizer ignore-call */ 
155
           SetFolderId($folderid);
Loading history...
155
					$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

155
					$spa->/** @scrutinizer ignore-call */ 
156
           SetBackendFolderId(self::$deviceManager->GetBackendIdForFolderId($folderid));
Loading history...
156
157
					if ($class !== false) {
158
						$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

158
						$spa->/** @scrutinizer ignore-call */ 
159
            SetContentClass($class);
Loading history...
159
					}
160
161
					// Get class for as versions >= 12.0
162
					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

162
					if (!$spa->/** @scrutinizer ignore-call */ HasContentClass()) {
Loading history...
163
						try {
164
							$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

164
							$spa->SetContentClass(self::$deviceManager->GetFolderClassFromCacheByID($spa->/** @scrutinizer ignore-call */ GetFolderId()));
Loading history...
165
							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

165
							SLog::Write(LOGLEVEL_DEBUG, sprintf("GetFolderClassFromCacheByID from Device Manager: '%s' for id:'%s'", $spa->/** @scrutinizer ignore-call */ GetContentClass(), $spa->GetFolderId()));
Loading history...
166
						}
167
						catch (NoHierarchyCacheAvailableException $nhca) {
168
							$status = SYNC_STATUS_FOLDERHIERARCHYCHANGED;
169
							self::$deviceManager->ForceFullResync();
170
						}
171
					}
172
173
					// done basic SPA initialization/loading -> add to SyncCollection
174
					$sc->AddCollection($spa);
175
					$sc->AddParameter($spa, "requested", true);
176
177
					if ($spa->HasContentClass()) {
178
						self::$topCollector->AnnounceInformation(sprintf("%s request", $spa->GetContentClass()), $this->singleFolder);
179
					}
180
					else {
181
						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.");
182
					}
183
184
					// SUPPORTED properties
185
					if (($se = self::$decoder->getElementStartTag(SYNC_SUPPORTED)) !== false) {
186
						// LG phones send an empty supported tag, so only read the contents if available here
187
						// if <Supported/> is received, it's as no supported fields would have been sent at all.
188
						// unsure if this is the correct approach, or if in this case some default list should be used
189
						if ($se[EN_FLAGS] & EN_FLAGS_CONTENT) {
190
							$supfields = [];
191
							WBXMLDecoder::ResetInWhile("syncSupported");
192
							while (WBXMLDecoder::InWhile("syncSupported")) {
193
								$el = self::$decoder->getElement();
194
195
								if ($el[EN_TYPE] == EN_TYPE_ENDTAG) {
196
									break;
197
								}
198
199
								$supfields[] = $el[EN_TAG];
200
							}
201
							self::$deviceManager->SetSupportedFields($spa->GetFolderId(), $supfields);
202
						}
203
					}
204
205
					// Deletes as moves can be an empty tag as well as have value
206
					if (self::$decoder->getElementStartTag(SYNC_DELETESASMOVES)) {
207
						$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

207
						$spa->/** @scrutinizer ignore-call */ 
208
            SetDeletesAsMoves(true);
Loading history...
208
						if (($dam = self::$decoder->getElementContent()) !== false) {
209
							$spa->SetDeletesAsMoves((bool) $dam);
210
							if (!self::$decoder->getElementEndTag()) {
211
								return false;
212
							}
213
						}
214
					}
215
216
					// Get changes can be an empty tag as well as have value
217
					// code block partly contributed by dw2412
218
					if ($starttag = self::$decoder->getElementStartTag(SYNC_GETCHANGES)) {
219
						$sc->AddParameter($spa, "getchanges", true);
220
						if (($gc = self::$decoder->getElementContent()) !== false) {
221
							$sc->AddParameter($spa, "getchanges", $gc);
222
						}
223
						// read the endtag if SYNC_GETCHANGES wasn't an empty tag
224
						if ($starttag[EN_FLAGS] & EN_FLAGS_CONTENT) {
225
							if (!self::$decoder->getElementEndTag()) {
226
								return false;
227
							}
228
						}
229
					}
230
231
					if (self::$decoder->getElementStartTag(SYNC_WINDOWSIZE)) {
232
						$ws = self::$decoder->getElementContent();
233
						// normalize windowsize
234
						if ($ws == 0 || $ws > WINDOW_SIZE_MAX) {
235
							$ws = WINDOW_SIZE_MAX;
236
						}
237
238
						$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

238
						$spa->/** @scrutinizer ignore-call */ 
239
            SetWindowSize($ws);
Loading history...
239
240
						// also announce the currently requested window size to the DeviceManager
241
						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

241
						self::$deviceManager->SetWindowSize($spa->GetFolderId(), $spa->/** @scrutinizer ignore-call */ GetWindowSize());
Loading history...
242
243
						if (!self::$decoder->getElementEndTag()) {
244
							return false;
245
						}
246
					}
247
248
					// conversation mode requested
249
					if (self::$decoder->getElementStartTag(SYNC_CONVERSATIONMODE)) {
250
						$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

250
						$spa->/** @scrutinizer ignore-call */ 
251
            SetConversationMode(true);
Loading history...
251
						if (($conversationmode = self::$decoder->getElementContent()) !== false) {
252
							$spa->SetConversationMode((bool) $conversationmode);
253
							if (!self::$decoder->getElementEndTag()) {
254
								return false;
255
							}
256
						}
257
					}
258
259
					// Do not truncate by default
260
					$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

260
					$spa->/** @scrutinizer ignore-call */ 
261
           SetTruncation(SYNC_TRUNCATION_ALL);
Loading history...
261
262
					// use default conflict handling if not specified by the mobile
263
					$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

263
					$spa->/** @scrutinizer ignore-call */ 
264
           SetConflict(SYNC_CONFLICT_DEFAULT);
Loading history...
264
265
					// save the current filtertype because it might have been changed on the mobile
266
					$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

266
					/** @scrutinizer ignore-call */ 
267
     $currentFilterType = $spa->GetFilterType();
Loading history...
267
268
					while (self::$decoder->getElementStartTag(SYNC_OPTIONS)) {
269
						$firstOption = true;
270
						WBXMLDecoder::ResetInWhile("syncOptions");
271
						while (WBXMLDecoder::InWhile("syncOptions")) {
272
							// foldertype definition
273
							if (self::$decoder->getElementStartTag(SYNC_FOLDERTYPE)) {
274
								$foldertype = self::$decoder->getElementContent();
275
								SLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): specified options block with foldertype '%s'", $foldertype));
276
277
								// switch the foldertype for the next options
278
								$spa->UseCPO($foldertype);
279
280
								// save the current filtertype because it might have been changed on the mobile
281
								$currentFilterType = $spa->GetFilterType();
282
283
								// set to synchronize all changes. The mobile could overwrite this value
284
								$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

284
								$spa->/** @scrutinizer ignore-call */ 
285
              SetFilterType(SYNC_FILTERTYPE_ALL);
Loading history...
285
286
								if (!self::$decoder->getElementEndTag()) {
287
									return false;
288
								}
289
							}
290
							// if no foldertype is defined, use default cpo
291
							elseif ($firstOption) {
292
								$spa->UseCPO();
293
								// save the current filtertype because it might have been changed on the mobile
294
								$currentFilterType = $spa->GetFilterType();
295
								// set to synchronize all changes. The mobile could overwrite this value
296
								$spa->SetFilterType(SYNC_FILTERTYPE_ALL);
297
							}
298
							$firstOption = false;
299
300
							if (self::$decoder->getElementStartTag(SYNC_FILTERTYPE)) {
301
								$spa->SetFilterType(self::$decoder->getElementContent());
302
								if (!self::$decoder->getElementEndTag()) {
303
									return false;
304
								}
305
							}
306
							if (self::$decoder->getElementStartTag(SYNC_TRUNCATION)) {
307
								$spa->SetTruncation(self::$decoder->getElementContent());
308
								if (!self::$decoder->getElementEndTag()) {
309
									return false;
310
								}
311
							}
312
							if (self::$decoder->getElementStartTag(SYNC_RTFTRUNCATION)) {
313
								$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

313
								$spa->/** @scrutinizer ignore-call */ 
314
              SetRTFTruncation(self::$decoder->getElementContent());
Loading history...
314
								if (!self::$decoder->getElementEndTag()) {
315
									return false;
316
								}
317
							}
318
319
							if (self::$decoder->getElementStartTag(SYNC_MIMESUPPORT)) {
320
								$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

320
								$spa->/** @scrutinizer ignore-call */ 
321
              SetMimeSupport(self::$decoder->getElementContent());
Loading history...
321
								if (!self::$decoder->getElementEndTag()) {
322
									return false;
323
								}
324
							}
325
326
							if (self::$decoder->getElementStartTag(SYNC_MIMETRUNCATION)) {
327
								$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

327
								$spa->/** @scrutinizer ignore-call */ 
328
              SetMimeTruncation(self::$decoder->getElementContent());
Loading history...
328
								if (!self::$decoder->getElementEndTag()) {
329
									return false;
330
								}
331
							}
332
333
							if (self::$decoder->getElementStartTag(SYNC_CONFLICT)) {
334
								$spa->SetConflict(self::$decoder->getElementContent());
335
								if (!self::$decoder->getElementEndTag()) {
336
									return false;
337
								}
338
							}
339
340
							while (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_BODYPREFERENCE)) {
341
								if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_TYPE)) {
342
									$bptype = self::$decoder->getElementContent();
343
									$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

343
									$spa->/** @scrutinizer ignore-call */ 
344
               BodyPreference($bptype);
Loading history...
344
									if (!self::$decoder->getElementEndTag()) {
345
										return false;
346
									}
347
								}
348
349
								if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_TRUNCATIONSIZE)) {
350
									$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...
351
									if (!self::$decoder->getElementEndTag()) {
352
										return false;
353
									}
354
								}
355
356
								if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_ALLORNONE)) {
357
									$spa->BodyPreference($bptype)->SetAllOrNone(self::$decoder->getElementContent());
358
									if (!self::$decoder->getElementEndTag()) {
359
										return false;
360
									}
361
								}
362
363
								if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_PREVIEW)) {
364
									$spa->BodyPreference($bptype)->SetPreview(self::$decoder->getElementContent());
365
									if (!self::$decoder->getElementEndTag()) {
366
										return false;
367
									}
368
								}
369
370
								if (!self::$decoder->getElementEndTag()) {
371
									return false;
372
								}
373
							}
374
375
							if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_BODYPARTPREFERENCE)) {
376
								if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_TYPE)) {
377
									$bpptype = self::$decoder->getElementContent();
378
									$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

378
									$spa->/** @scrutinizer ignore-call */ 
379
               BodyPartPreference($bpptype);
Loading history...
379
									if (!self::$decoder->getElementEndTag()) {
380
										return false;
381
									}
382
								}
383
384
								if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_TRUNCATIONSIZE)) {
385
									$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...
386
									if (!self::$decoder->getElementEndTag()) {
387
										return false;
388
									}
389
								}
390
391
								if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_ALLORNONE)) {
392
									$spa->BodyPartPreference($bpptype)->SetAllOrNone(self::$decoder->getElementContent());
393
									if (!self::$decoder->getElementEndTag()) {
394
										return false;
395
									}
396
								}
397
398
								if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_PREVIEW)) {
399
									$spa->BodyPartPreference($bpptype)->SetPreview(self::$decoder->getElementContent());
400
									if (!self::$decoder->getElementEndTag()) {
401
										return false;
402
									}
403
								}
404
405
								if (!self::$decoder->getElementEndTag()) {
406
									return false;
407
								}
408
							}
409
410
							if (self::$decoder->getElementStartTag(SYNC_RIGHTSMANAGEMENT_SUPPORT)) {
411
								$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

411
								$spa->/** @scrutinizer ignore-call */ 
412
              SetRmSupport(self::$decoder->getElementContent());
Loading history...
412
								if (!self::$decoder->getElementEndTag()) {
413
									return false;
414
								}
415
							}
416
417
							$e = self::$decoder->peek();
418
							if ($e[EN_TYPE] == EN_TYPE_ENDTAG) {
419
								self::$decoder->getElementEndTag();
420
421
								break;
422
							}
423
						}
424
					}
425
426
					// limit items to be synchronized to the mobiles if configured
427
					$maxAllowed = self::$deviceManager->GetFilterType($spa->GetFolderId(), $spa->GetBackendFolderId());
428
					if ($maxAllowed > SYNC_FILTERTYPE_ALL &&
429
						(!$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

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

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

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

807
						if ($changecount > 0 && $sc->WaitedForChanges() && self::$deviceManager->CheckHearbeatStateIntegrity($spa->GetFolderId(), $spa->/** @scrutinizer ignore-call */ GetUuid(), $spa->GetUuidCounter())) {
Loading history...
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

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

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

998
			$this->syncFolder($sc, $spa, $exporter, $changecount, $streamimporter, /** @scrutinizer ignore-type */ $status, $newFolderStat);
Loading history...
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

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

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

1319
			if ($spa->/** @scrutinizer ignore-call */ GetFolderSyncRemaining() && $changecount > 0) {
Loading history...
1320
				$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

1320
				$spa->/** @scrutinizer ignore-call */ 
1321
          SetFolderSyncRemaining($changecount);
Loading history...
1321
			}
1322
			// changecount is initialized with 'false', so 0 means no changes!
1323
			if ($changecount === 0 || ($changecount !== false && $changecount <= $windowSize)) {
1324
				self::$deviceManager->SetFolderSyncStatus($spa->GetFolderId(), DeviceManager::FLD_SYNC_COMPLETED);
1325
1326
				// 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
1327
				if (self::$backend->HasFolderStats()) {
1328
					$newFolderStatAfterExport = self::$backend->GetFolderStat(GSync::GetAdditionalSyncFolderStore($spa->GetBackendFolderId()), $spa->GetBackendFolderId());
1329
					if ($newFolderStat === $newFolderStatAfterExport) {
1330
						$this->setFolderStat($spa, $newFolderStat);
1331
					}
1332
					else {
1333
						SLog::Write(LOGLEVEL_DEBUG, "Sync() Folderstat differs after export, force another exporter run.");
1334
					}
1335
				}
1336
			}
1337
			else {
1338
				self::$deviceManager->SetFolderSyncStatus($spa->GetFolderId(), DeviceManager::FLD_SYNC_INPROGRESS);
1339
			}
1340
		}
1341
1342
		self::$encoder->endTag();
1343
1344
		// Save the sync state for the next time
1345
		if ($spa->HasNewSyncKey()) {
1346
			self::$topCollector->AnnounceInformation("Saving state");
1347
1348
			try {
1349
				if ($exporter) {
0 ignored issues
show
introduced by
$exporter is of type IExportChanges, thus it always evaluated to true.
Loading history...
1350
					$state = $exporter->GetState();
1351
				}
1352
1353
				// nothing exported, but possibly imported - get the importer state
1354
				elseif ($sc->GetParameter($spa, "state") !== null) {
1355
					$state = $sc->GetParameter($spa, "state");
1356
				}
1357
1358
				// if a new request without state information (hierarchy) save an empty state
1359
				elseif (!$spa->HasSyncKey()) {
1360
					$state = "";
1361
				}
1362
			}
1363
			catch (StatusException $stex) {
1364
				$status = $stex->getCode();
1365
			}
1366
1367
			if (isset($state) && $status == SYNC_STATUS_SUCCESS) {
1368
				self::$deviceManager->GetStateManager()->SetSyncState($spa->GetNewSyncKey(), $state, $spa->GetFolderId());
1369
			}
1370
			else {
1371
				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

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

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

1703
		$spa->/** @scrutinizer ignore-call */ 
1704
        SetFolderStat($newFolderStat);
Loading history...
1704
		$maxTimeout = 60 * 60 * 24 * 31; // one month
1705
1706
		$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

1706
		$interval = Utils::GetFiltertypeInterval(/** @scrutinizer ignore-type */ $spa->GetFilterType());
Loading history...
1707
		$timeout = time() + (($interval && $interval < $maxTimeout) ? $interval : $maxTimeout);
1708
		// randomize timeout in 12h
1709
		$timeout -= rand(0, 43200);
1710
		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

1710
		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...
1711
		$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

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