Sync   F
last analyzed

Complexity

Total Complexity 378

Size/Duplication

Total Lines 1702
Duplicated Lines 0 %

Importance

Changes 7
Bugs 1 Features 0
Metric Value
eloc 881
c 7
b 1
f 0
dl 0
loc 1702
rs 1.719
wmc 378

10 Methods

Rating   Name   Duplication   Size   Complexity  
F importMessage() 0 145 29
A setFolderStat() 0 10 3
A loadStates() 0 32 6
A sendFolderStartTag() 0 5 2
A sendStartTags() 0 5 2
A getImporter() 0 35 5
F Handle() 0 1004 236
B saveMultiFolderInfo() 0 12 8
F syncFolder() 0 310 77
B getMultiFolderInfoLine() 0 27 10

How to fix   Complexity   

Complex Class

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

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

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

1
<?php
2
/*
3
 * SPDX-License-Identifier: AGPL-3.0-only
4
 * SPDX-FileCopyrightText: Copyright 2007-2016 Zarafa Deutschland GmbH
5
 * SPDX-FileCopyrightText: Copyright 2020-2024 grommunio GmbH
6
 *
7
 * 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