Sync::sendFolderStartTag()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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