1 | <?php |
||||||
2 | /* |
||||||
3 | * SPDX-License-Identifier: AGPL-3.0-only |
||||||
4 | * SPDX-FileCopyrightText: Copyright 2007-2016 Zarafa Deutschland GmbH |
||||||
5 | * SPDX-FileCopyrightText: Copyright 2020-2024 grommunio GmbH |
||||||
6 | * |
||||||
7 | * Provides the SYNC command |
||||||
8 | */ |
||||||
9 | |||||||
10 | class Sync extends RequestProcessor { |
||||||
11 | // Ignored SMS identifier |
||||||
12 | public const GSYNCIGNORESMS = "ZPISMS"; |
||||||
13 | private $importer; |
||||||
14 | private $globallyExportedItems; |
||||||
15 | private $singleFolder; |
||||||
16 | private $multiFolderInfo; |
||||||
17 | private $startTagsSent = false; |
||||||
18 | private $startFolderTagSent = false; |
||||||
19 | |||||||
20 | /** |
||||||
21 | * Handles the Sync command |
||||||
22 | * Performs the synchronization of messages. |
||||||
23 | * |
||||||
24 | * @param int $commandCode |
||||||
25 | * |
||||||
26 | * @return bool |
||||||
27 | */ |
||||||
28 | public function Handle($commandCode) { |
||||||
29 | // Contains all requested folders (containers) |
||||||
30 | $sc = new SyncCollections(); |
||||||
31 | $status = SYNC_STATUS_SUCCESS; |
||||||
32 | $wbxmlproblem = false; |
||||||
33 | $emptysync = false; |
||||||
34 | $this->singleFolder = true; |
||||||
35 | $this->multiFolderInfo = []; |
||||||
36 | $this->globallyExportedItems = 0; |
||||||
37 | |||||||
38 | // check if the hierarchySync was fully completed |
||||||
39 | if (USE_PARTIAL_FOLDERSYNC) { |
||||||
40 | if (self::$deviceManager->GetFolderSyncComplete() === false) { |
||||||
41 | SLog::Write(LOGLEVEL_INFO, "Request->HandleSync(): Sync request aborted, as exporting of folders has not yet completed"); |
||||||
42 | self::$topCollector->AnnounceInformation("Aborted due incomplete folder sync", true); |
||||||
43 | $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED; |
||||||
44 | } |
||||||
45 | else { |
||||||
46 | SLog::Write(LOGLEVEL_INFO, "Request->HandleSync(): FolderSync marked as complete"); |
||||||
47 | } |
||||||
48 | } |
||||||
49 | |||||||
50 | // Start Synchronize |
||||||
51 | if (self::$decoder->getElementStartTag(SYNC_SYNCHRONIZE)) { |
||||||
52 | // AS 1.0 sends version information in WBXML |
||||||
53 | if (self::$decoder->getElementStartTag(SYNC_VERSION)) { |
||||||
54 | $sync_version = self::$decoder->getElementContent(); |
||||||
55 | SLog::Write(LOGLEVEL_DEBUG, sprintf("WBXML sync version: '%s'", $sync_version)); |
||||||
56 | if (!self::$decoder->getElementEndTag()) { |
||||||
57 | return false; |
||||||
58 | } |
||||||
59 | } |
||||||
60 | |||||||
61 | // Syncing specified folders |
||||||
62 | // Android still sends heartbeat sync even if all syncfolders are disabled. |
||||||
63 | // Check if Folders tag is empty (<Folders/>) and only sync if there are |
||||||
64 | // some folders in the request. |
||||||
65 | $startTag = self::$decoder->getElementStartTag(SYNC_FOLDERS); |
||||||
66 | if (isset($startTag[EN_FLAGS]) && $startTag[EN_FLAGS]) { |
||||||
67 | while (self::$decoder->getElementStartTag(SYNC_FOLDER)) { |
||||||
68 | $actiondata = []; |
||||||
69 | $actiondata["requested"] = true; |
||||||
70 | $actiondata["clientids"] = []; |
||||||
71 | $actiondata["modifyids"] = []; |
||||||
72 | $actiondata["removeids"] = []; |
||||||
73 | $actiondata["fetchids"] = []; |
||||||
74 | $actiondata["statusids"] = []; |
||||||
75 | |||||||
76 | // read class, synckey and folderid without SyncParameters Object for now |
||||||
77 | $class = $synckey = $folderid = false; |
||||||
0 ignored issues
–
show
Unused Code
introduced
by
![]() |
|||||||
78 | |||||||
79 | // if there are already collections in SyncCollections, this is min. the second folder |
||||||
80 | if ($sc->HasCollections()) { |
||||||
81 | $this->singleFolder = false; |
||||||
82 | } |
||||||
83 | |||||||
84 | // for AS versions < 2.5 |
||||||
85 | if (self::$decoder->getElementStartTag(SYNC_FOLDERTYPE)) { |
||||||
86 | $class = self::$decoder->getElementContent(); |
||||||
87 | SLog::Write(LOGLEVEL_DEBUG, sprintf("Sync folder: '%s'", $class)); |
||||||
88 | |||||||
89 | if (!self::$decoder->getElementEndTag()) { |
||||||
90 | return false; |
||||||
91 | } |
||||||
92 | } |
||||||
93 | |||||||
94 | // SyncKey |
||||||
95 | if (self::$decoder->getElementStartTag(SYNC_SYNCKEY)) { |
||||||
96 | $synckey = "0"; |
||||||
97 | if (($synckey = self::$decoder->getElementContent()) !== false) { |
||||||
98 | if (!self::$decoder->getElementEndTag()) { |
||||||
99 | return false; |
||||||
100 | } |
||||||
101 | } |
||||||
102 | } |
||||||
103 | else { |
||||||
104 | return false; |
||||||
105 | } |
||||||
106 | |||||||
107 | // FolderId |
||||||
108 | if (self::$decoder->getElementStartTag(SYNC_FOLDERID)) { |
||||||
109 | $folderid = self::$decoder->getElementContent(); |
||||||
110 | |||||||
111 | if (!self::$decoder->getElementEndTag()) { |
||||||
112 | return false; |
||||||
113 | } |
||||||
114 | } |
||||||
115 | |||||||
116 | // compatibility mode AS 1.0 - get folderid which was sent during GetHierarchy() |
||||||
117 | if (!$folderid && $class) { |
||||||
118 | $folderid = self::$deviceManager->GetFolderIdFromCacheByClass($class); |
||||||
119 | } |
||||||
120 | |||||||
121 | // folderid HAS TO BE known by now, so we retrieve the correct SyncParameters object for an update |
||||||
122 | try { |
||||||
123 | $spa = self::$deviceManager->GetStateManager()->GetSynchedFolderState($folderid); |
||||||
124 | |||||||
125 | // TODO remove resync of folders |
||||||
126 | // this forces a resync of all states |
||||||
127 | if (!$spa instanceof SyncParameters) { |
||||||
128 | throw new StateInvalidException("Saved state are not of type SyncParameters"); |
||||||
129 | } |
||||||
130 | |||||||
131 | // new/resync requested |
||||||
132 | if ($synckey == "0") { |
||||||
133 | $spa->RemoveSyncKey(); |
||||||
134 | $spa->DelFolderStat(); |
||||||
0 ignored issues
–
show
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
![]() |
|||||||
135 | $spa->SetMoveState(false); |
||||||
0 ignored issues
–
show
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
![]() |
|||||||
136 | } |
||||||
137 | elseif ($synckey !== false) { |
||||||
138 | if ($synckey !== $spa->GetSyncKey() && $synckey !== $spa->GetNewSyncKey()) { |
||||||
139 | SLog::Write(LOGLEVEL_DEBUG, "HandleSync(): Synckey does not match latest saved for this folder or there is a move state, removing folderstat to force Exporter setup"); |
||||||
140 | $spa->DelFolderStat(); |
||||||
141 | } |
||||||
142 | $spa->SetSyncKey($synckey); |
||||||
143 | } |
||||||
144 | } |
||||||
145 | catch (StateInvalidException $stie) { |
||||||
146 | $spa = new SyncParameters(); |
||||||
147 | $status = SYNC_STATUS_INVALIDSYNCKEY; |
||||||
148 | self::$topCollector->AnnounceInformation("State invalid - Resync folder", $this->singleFolder); |
||||||
149 | self::$deviceManager->ForceFolderResync($folderid); |
||||||
150 | $this->saveMultiFolderInfo("exception", "StateInvalidException"); |
||||||
151 | } |
||||||
152 | |||||||
153 | // update folderid.. this might be a new object |
||||||
154 | $spa->SetFolderId($folderid); |
||||||
0 ignored issues
–
show
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
![]() |
|||||||
155 | $spa->SetBackendFolderId(self::$deviceManager->GetBackendIdForFolderId($folderid)); |
||||||
0 ignored issues
–
show
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
![]() |
|||||||
156 | |||||||
157 | if ($class !== false) { |
||||||
158 | $spa->SetContentClass($class); |
||||||
0 ignored issues
–
show
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
![]() |
|||||||
159 | } |
||||||
160 | |||||||
161 | // Get class for as versions >= 12.0 |
||||||
162 | if (!$spa->HasContentClass()) { |
||||||
0 ignored issues
–
show
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
![]() |
|||||||
163 | try { |
||||||
164 | $spa->SetContentClass(self::$deviceManager->GetFolderClassFromCacheByID($spa->GetFolderId())); |
||||||
0 ignored issues
–
show
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
![]() |
|||||||
165 | SLog::Write(LOGLEVEL_DEBUG, sprintf("GetFolderClassFromCacheByID from Device Manager: '%s' for id:'%s'", $spa->GetContentClass(), $spa->GetFolderId())); |
||||||
0 ignored issues
–
show
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
![]() |
|||||||
166 | } |
||||||
167 | catch (NoHierarchyCacheAvailableException $nhca) { |
||||||
168 | $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED; |
||||||
169 | self::$deviceManager->ForceFullResync(); |
||||||
170 | } |
||||||
171 | } |
||||||
172 | |||||||
173 | // done basic SPA initialization/loading -> add to SyncCollection |
||||||
174 | $sc->AddCollection($spa); |
||||||
175 | $sc->AddParameter($spa, "requested", true); |
||||||
176 | |||||||
177 | if ($spa->HasContentClass()) { |
||||||
178 | self::$topCollector->AnnounceInformation(sprintf("%s request", $spa->GetContentClass()), $this->singleFolder); |
||||||
179 | } |
||||||
180 | else { |
||||||
181 | SLog::Write(LOGLEVEL_WARN, "Not possible to determine class of request. Request did not contain class and apparently there is an issue with the HierarchyCache."); |
||||||
182 | } |
||||||
183 | |||||||
184 | // SUPPORTED properties |
||||||
185 | if (($se = self::$decoder->getElementStartTag(SYNC_SUPPORTED)) !== false) { |
||||||
186 | // LG phones send an empty supported tag, so only read the contents if available here |
||||||
187 | // if <Supported/> is received, it's as no supported fields would have been sent at all. |
||||||
188 | // unsure if this is the correct approach, or if in this case some default list should be used |
||||||
189 | if ($se[EN_FLAGS] & EN_FLAGS_CONTENT) { |
||||||
190 | $supfields = []; |
||||||
191 | WBXMLDecoder::ResetInWhile("syncSupported"); |
||||||
192 | while (WBXMLDecoder::InWhile("syncSupported")) { |
||||||
193 | $el = self::$decoder->getElement(); |
||||||
194 | |||||||
195 | if ($el[EN_TYPE] == EN_TYPE_ENDTAG) { |
||||||
196 | break; |
||||||
197 | } |
||||||
198 | |||||||
199 | $supfields[] = $el[EN_TAG]; |
||||||
200 | } |
||||||
201 | self::$deviceManager->SetSupportedFields($spa->GetFolderId(), $supfields); |
||||||
202 | } |
||||||
203 | } |
||||||
204 | |||||||
205 | // Deletes as moves can be an empty tag as well as have value |
||||||
206 | if (self::$decoder->getElementStartTag(SYNC_DELETESASMOVES)) { |
||||||
207 | $spa->SetDeletesAsMoves(true); |
||||||
0 ignored issues
–
show
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
![]() |
|||||||
208 | if (($dam = self::$decoder->getElementContent()) !== false) { |
||||||
209 | $spa->SetDeletesAsMoves((bool) $dam); |
||||||
210 | if (!self::$decoder->getElementEndTag()) { |
||||||
211 | return false; |
||||||
212 | } |
||||||
213 | } |
||||||
214 | } |
||||||
215 | |||||||
216 | // Get changes can be an empty tag as well as have value |
||||||
217 | // code block partly contributed by dw2412 |
||||||
218 | if ($starttag = self::$decoder->getElementStartTag(SYNC_GETCHANGES)) { |
||||||
219 | $sc->AddParameter($spa, "getchanges", true); |
||||||
220 | if (($gc = self::$decoder->getElementContent()) !== false) { |
||||||
221 | $sc->AddParameter($spa, "getchanges", $gc); |
||||||
222 | } |
||||||
223 | // read the endtag if SYNC_GETCHANGES wasn't an empty tag |
||||||
224 | if ($starttag[EN_FLAGS] & EN_FLAGS_CONTENT) { |
||||||
225 | if (!self::$decoder->getElementEndTag()) { |
||||||
226 | return false; |
||||||
227 | } |
||||||
228 | } |
||||||
229 | } |
||||||
230 | |||||||
231 | if (self::$decoder->getElementStartTag(SYNC_WINDOWSIZE)) { |
||||||
232 | $ws = self::$decoder->getElementContent(); |
||||||
233 | // normalize windowsize |
||||||
234 | if ($ws == 0 || $ws > WINDOW_SIZE_MAX) { |
||||||
235 | $ws = WINDOW_SIZE_MAX; |
||||||
236 | } |
||||||
237 | |||||||
238 | $spa->SetWindowSize($ws); |
||||||
0 ignored issues
–
show
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
![]() |
|||||||
239 | |||||||
240 | // also announce the currently requested window size to the DeviceManager |
||||||
241 | self::$deviceManager->SetWindowSize($spa->GetFolderId(), $spa->GetWindowSize()); |
||||||
0 ignored issues
–
show
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
![]() |
|||||||
242 | |||||||
243 | if (!self::$decoder->getElementEndTag()) { |
||||||
244 | return false; |
||||||
245 | } |
||||||
246 | } |
||||||
247 | |||||||
248 | // conversation mode requested |
||||||
249 | if (self::$decoder->getElementStartTag(SYNC_CONVERSATIONMODE)) { |
||||||
250 | $spa->SetConversationMode(true); |
||||||
0 ignored issues
–
show
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
![]() |
|||||||
251 | if (($conversationmode = self::$decoder->getElementContent()) !== false) { |
||||||
252 | $spa->SetConversationMode((bool) $conversationmode); |
||||||
253 | if (!self::$decoder->getElementEndTag()) { |
||||||
254 | return false; |
||||||
255 | } |
||||||
256 | } |
||||||
257 | } |
||||||
258 | |||||||
259 | // Do not truncate by default |
||||||
260 | $spa->SetTruncation(SYNC_TRUNCATION_ALL); |
||||||
0 ignored issues
–
show
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
![]() |
|||||||
261 | |||||||
262 | // use default conflict handling if not specified by the mobile |
||||||
263 | $spa->SetConflict(SYNC_CONFLICT_DEFAULT); |
||||||
0 ignored issues
–
show
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
![]() |
|||||||
264 | |||||||
265 | // save the current filtertype because it might have been changed on the mobile |
||||||
266 | $currentFilterType = $spa->GetFilterType(); |
||||||
0 ignored issues
–
show
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
![]() |
|||||||
267 | |||||||
268 | while (self::$decoder->getElementStartTag(SYNC_OPTIONS)) { |
||||||
269 | $firstOption = true; |
||||||
270 | WBXMLDecoder::ResetInWhile("syncOptions"); |
||||||
271 | while (WBXMLDecoder::InWhile("syncOptions")) { |
||||||
272 | // foldertype definition |
||||||
273 | if (self::$decoder->getElementStartTag(SYNC_FOLDERTYPE)) { |
||||||
274 | $foldertype = self::$decoder->getElementContent(); |
||||||
275 | SLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): specified options block with foldertype '%s'", $foldertype)); |
||||||
276 | |||||||
277 | // switch the foldertype for the next options |
||||||
278 | $spa->UseCPO($foldertype); |
||||||
279 | |||||||
280 | // save the current filtertype because it might have been changed on the mobile |
||||||
281 | $currentFilterType = $spa->GetFilterType(); |
||||||
282 | |||||||
283 | // set to synchronize all changes. The mobile could overwrite this value |
||||||
284 | $spa->SetFilterType(SYNC_FILTERTYPE_ALL); |
||||||
0 ignored issues
–
show
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
![]() |
|||||||
285 | |||||||
286 | if (!self::$decoder->getElementEndTag()) { |
||||||
287 | return false; |
||||||
288 | } |
||||||
289 | } |
||||||
290 | // if no foldertype is defined, use default cpo |
||||||
291 | elseif ($firstOption) { |
||||||
292 | $spa->UseCPO(); |
||||||
293 | // save the current filtertype because it might have been changed on the mobile |
||||||
294 | $currentFilterType = $spa->GetFilterType(); |
||||||
295 | // set to synchronize all changes. The mobile could overwrite this value |
||||||
296 | $spa->SetFilterType(SYNC_FILTERTYPE_ALL); |
||||||
297 | } |
||||||
298 | $firstOption = false; |
||||||
299 | |||||||
300 | if (self::$decoder->getElementStartTag(SYNC_FILTERTYPE)) { |
||||||
301 | $spa->SetFilterType(self::$decoder->getElementContent()); |
||||||
302 | if (!self::$decoder->getElementEndTag()) { |
||||||
303 | return false; |
||||||
304 | } |
||||||
305 | } |
||||||
306 | if (self::$decoder->getElementStartTag(SYNC_TRUNCATION)) { |
||||||
307 | $spa->SetTruncation(self::$decoder->getElementContent()); |
||||||
308 | if (!self::$decoder->getElementEndTag()) { |
||||||
309 | return false; |
||||||
310 | } |
||||||
311 | } |
||||||
312 | if (self::$decoder->getElementStartTag(SYNC_RTFTRUNCATION)) { |
||||||
313 | $spa->SetRTFTruncation(self::$decoder->getElementContent()); |
||||||
0 ignored issues
–
show
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
![]() |
|||||||
314 | if (!self::$decoder->getElementEndTag()) { |
||||||
315 | return false; |
||||||
316 | } |
||||||
317 | } |
||||||
318 | |||||||
319 | if (self::$decoder->getElementStartTag(SYNC_MIMESUPPORT)) { |
||||||
320 | $spa->SetMimeSupport(self::$decoder->getElementContent()); |
||||||
0 ignored issues
–
show
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
![]() |
|||||||
321 | if (!self::$decoder->getElementEndTag()) { |
||||||
322 | return false; |
||||||
323 | } |
||||||
324 | } |
||||||
325 | |||||||
326 | if (self::$decoder->getElementStartTag(SYNC_MIMETRUNCATION)) { |
||||||
327 | $spa->SetMimeTruncation(self::$decoder->getElementContent()); |
||||||
0 ignored issues
–
show
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
![]() |
|||||||
328 | if (!self::$decoder->getElementEndTag()) { |
||||||
329 | return false; |
||||||
330 | } |
||||||
331 | } |
||||||
332 | |||||||
333 | if (self::$decoder->getElementStartTag(SYNC_CONFLICT)) { |
||||||
334 | $spa->SetConflict(self::$decoder->getElementContent()); |
||||||
335 | if (!self::$decoder->getElementEndTag()) { |
||||||
336 | return false; |
||||||
337 | } |
||||||
338 | } |
||||||
339 | |||||||
340 | while (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_BODYPREFERENCE)) { |
||||||
341 | if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_TYPE)) { |
||||||
342 | $bptype = self::$decoder->getElementContent(); |
||||||
343 | $spa->BodyPreference($bptype); |
||||||
0 ignored issues
–
show
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
![]() |
|||||||
344 | if (!self::$decoder->getElementEndTag()) { |
||||||
345 | return false; |
||||||
346 | } |
||||||
347 | } |
||||||
348 | |||||||
349 | if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_TRUNCATIONSIZE)) { |
||||||
350 | $spa->BodyPreference($bptype)->SetTruncationSize(self::$decoder->getElementContent()); |
||||||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||||||
351 | if (!self::$decoder->getElementEndTag()) { |
||||||
352 | return false; |
||||||
353 | } |
||||||
354 | } |
||||||
355 | |||||||
356 | if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_ALLORNONE)) { |
||||||
357 | $spa->BodyPreference($bptype)->SetAllOrNone(self::$decoder->getElementContent()); |
||||||
358 | if (!self::$decoder->getElementEndTag()) { |
||||||
359 | return false; |
||||||
360 | } |
||||||
361 | } |
||||||
362 | |||||||
363 | if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_PREVIEW)) { |
||||||
364 | $spa->BodyPreference($bptype)->SetPreview(self::$decoder->getElementContent()); |
||||||
365 | if (!self::$decoder->getElementEndTag()) { |
||||||
366 | return false; |
||||||
367 | } |
||||||
368 | } |
||||||
369 | |||||||
370 | if (!self::$decoder->getElementEndTag()) { |
||||||
371 | return false; |
||||||
372 | } |
||||||
373 | } |
||||||
374 | |||||||
375 | if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_BODYPARTPREFERENCE)) { |
||||||
376 | if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_TYPE)) { |
||||||
377 | $bpptype = self::$decoder->getElementContent(); |
||||||
378 | $spa->BodyPartPreference($bpptype); |
||||||
0 ignored issues
–
show
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
![]() |
|||||||
379 | if (!self::$decoder->getElementEndTag()) { |
||||||
380 | return false; |
||||||
381 | } |
||||||
382 | } |
||||||
383 | |||||||
384 | if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_TRUNCATIONSIZE)) { |
||||||
385 | $spa->BodyPartPreference($bpptype)->SetTruncationSize(self::$decoder->getElementContent()); |
||||||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||||||
386 | if (!self::$decoder->getElementEndTag()) { |
||||||
387 | return false; |
||||||
388 | } |
||||||
389 | } |
||||||
390 | |||||||
391 | if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_ALLORNONE)) { |
||||||
392 | $spa->BodyPartPreference($bpptype)->SetAllOrNone(self::$decoder->getElementContent()); |
||||||
393 | if (!self::$decoder->getElementEndTag()) { |
||||||
394 | return false; |
||||||
395 | } |
||||||
396 | } |
||||||
397 | |||||||
398 | if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_PREVIEW)) { |
||||||
399 | $spa->BodyPartPreference($bpptype)->SetPreview(self::$decoder->getElementContent()); |
||||||
400 | if (!self::$decoder->getElementEndTag()) { |
||||||
401 | return false; |
||||||
402 | } |
||||||
403 | } |
||||||
404 | |||||||
405 | if (!self::$decoder->getElementEndTag()) { |
||||||
406 | return false; |
||||||
407 | } |
||||||
408 | } |
||||||
409 | |||||||
410 | if (self::$decoder->getElementStartTag(SYNC_RIGHTSMANAGEMENT_SUPPORT)) { |
||||||
411 | $spa->SetRmSupport(self::$decoder->getElementContent()); |
||||||
0 ignored issues
–
show
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
![]() |
|||||||
412 | if (!self::$decoder->getElementEndTag()) { |
||||||
413 | return false; |
||||||
414 | } |
||||||
415 | } |
||||||
416 | |||||||
417 | $e = self::$decoder->peek(); |
||||||
418 | if ($e[EN_TYPE] == EN_TYPE_ENDTAG) { |
||||||
419 | self::$decoder->getElementEndTag(); |
||||||
420 | |||||||
421 | break; |
||||||
422 | } |
||||||
423 | } |
||||||
424 | } |
||||||
425 | |||||||
426 | // limit items to be synchronized to the mobiles if configured |
||||||
427 | $maxAllowed = self::$deviceManager->GetFilterType($spa->GetFolderId(), $spa->GetBackendFolderId()); |
||||||
428 | if ($maxAllowed > SYNC_FILTERTYPE_ALL && |
||||||
429 | (!$spa->HasFilterType() || $spa->GetFilterType() == SYNC_FILTERTYPE_ALL || $spa->GetFilterType() > $maxAllowed)) { |
||||||
0 ignored issues
–
show
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
![]() |
|||||||
430 | SLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): FilterType applied globally or specifically, using value: %s", $maxAllowed)); |
||||||
431 | $spa->SetFilterType($maxAllowed); |
||||||
432 | } |
||||||
433 | |||||||
434 | if ($currentFilterType != $spa->GetFilterType()) { |
||||||
435 | SLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): FilterType has changed (old: '%s', new: '%s'), removing folderstat to force Exporter setup", $currentFilterType, $spa->GetFilterType())); |
||||||
436 | $spa->DelFolderStat(); |
||||||
437 | } |
||||||
438 | |||||||
439 | // Check if the hierarchycache is available. If not, trigger a HierarchySync |
||||||
440 | if (self::$deviceManager->IsHierarchySyncRequired()) { |
||||||
441 | $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED; |
||||||
442 | SLog::Write(LOGLEVEL_DEBUG, "HierarchyCache is also not available. Triggering HierarchySync to device"); |
||||||
443 | } |
||||||
444 | |||||||
445 | // AS16: Check if this is a DRAFTS folder - if so, disable FilterType |
||||||
446 | if (Request::GetProtocolVersion() >= 16.0 && self::$deviceManager->GetFolderTypeFromCacheById($spa->GetFolderId()) == SYNC_FOLDER_TYPE_DRAFTS) { |
||||||
447 | $spa->SetFilterType(SYNC_FILTERTYPE_DISABLE); |
||||||
448 | SLog::Write(LOGLEVEL_DEBUG, "HandleSync(): FilterType has been disabled as this is a DRAFTS folder."); |
||||||
449 | } |
||||||
450 | |||||||
451 | if (($el = self::$decoder->getElementStartTag(SYNC_PERFORM)) && ($el[EN_FLAGS] & EN_FLAGS_CONTENT)) { |
||||||
452 | // We can not proceed here as the content class is unknown |
||||||
453 | if ($status != SYNC_STATUS_SUCCESS) { |
||||||
454 | SLog::Write(LOGLEVEL_WARN, "Ignoring all incoming actions as global status indicates problem."); |
||||||
455 | $wbxmlproblem = true; |
||||||
456 | |||||||
457 | break; |
||||||
458 | } |
||||||
459 | |||||||
460 | $performaction = true; |
||||||
461 | |||||||
462 | // unset the importer |
||||||
463 | $this->importer = false; |
||||||
464 | |||||||
465 | $nchanges = 0; |
||||||
466 | WBXMLDecoder::ResetInWhile("syncActions"); |
||||||
467 | while (WBXMLDecoder::InWhile("syncActions")) { |
||||||
468 | // ADD, MODIFY, REMOVE or FETCH |
||||||
469 | $element = self::$decoder->getElement(); |
||||||
470 | |||||||
471 | if ($element[EN_TYPE] != EN_TYPE_STARTTAG) { |
||||||
472 | self::$decoder->ungetElement($element); |
||||||
473 | |||||||
474 | break; |
||||||
475 | } |
||||||
476 | |||||||
477 | if ($status == SYNC_STATUS_SUCCESS) { |
||||||
478 | ++$nchanges; |
||||||
479 | } |
||||||
480 | |||||||
481 | // Foldertype sent when syncing SMS |
||||||
482 | if (self::$decoder->getElementStartTag(SYNC_FOLDERTYPE)) { |
||||||
483 | $foldertype = self::$decoder->getElementContent(); |
||||||
484 | SLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): incoming data with foldertype '%s'", $foldertype)); |
||||||
485 | |||||||
486 | if (!self::$decoder->getElementEndTag()) { |
||||||
487 | return false; |
||||||
488 | } |
||||||
489 | } |
||||||
490 | else { |
||||||
491 | $foldertype = false; |
||||||
492 | } |
||||||
493 | |||||||
494 | $serverid = false; |
||||||
495 | if (self::$decoder->getElementStartTag(SYNC_SERVERENTRYID)) { |
||||||
496 | if (($serverid = self::$decoder->getElementContent()) !== false) { |
||||||
497 | if (!self::$decoder->getElementEndTag()) { // end serverid |
||||||
498 | return false; |
||||||
499 | } |
||||||
500 | } |
||||||
501 | } |
||||||
502 | // get the instanceId if available |
||||||
503 | $instanceid = false; |
||||||
504 | if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_INSTANCEID)) { |
||||||
505 | if (($instanceid = self::$decoder->getElementContent()) !== false) { |
||||||
506 | if (!self::$decoder->getElementEndTag()) { // end instanceid |
||||||
507 | return false; |
||||||
508 | } |
||||||
509 | } |
||||||
510 | } |
||||||
511 | |||||||
512 | if (self::$decoder->getElementStartTag(SYNC_CLIENTENTRYID)) { |
||||||
513 | $clientid = self::$decoder->getElementContent(); |
||||||
514 | |||||||
515 | if (!self::$decoder->getElementEndTag()) { // end clientid |
||||||
516 | return false; |
||||||
517 | } |
||||||
518 | } |
||||||
519 | else { |
||||||
520 | $clientid = false; |
||||||
521 | } |
||||||
522 | |||||||
523 | // Get the SyncMessage if sent |
||||||
524 | if (($el = self::$decoder->getElementStartTag(SYNC_DATA)) && ($el[EN_FLAGS] & EN_FLAGS_CONTENT)) { |
||||||
525 | $message = GSync::getSyncObjectFromFolderClass($spa->GetContentClass()); |
||||||
526 | $message->Decode(self::$decoder); |
||||||
527 | |||||||
528 | // set Ghosted fields |
||||||
529 | $message->emptySupported(self::$deviceManager->GetSupportedFields($spa->GetFolderId())); |
||||||
530 | |||||||
531 | if (!self::$decoder->getElementEndTag()) { // end applicationdata |
||||||
532 | return false; |
||||||
533 | } |
||||||
534 | } |
||||||
535 | else { |
||||||
536 | $message = false; |
||||||
537 | } |
||||||
538 | |||||||
539 | // InstanceID sent: do action to a recurrency exception |
||||||
540 | if ($instanceid) { |
||||||
541 | // for delete actions we don't have an ASObject |
||||||
542 | if (!$message) { |
||||||
543 | $message = GSync::getSyncObjectFromFolderClass($spa->GetContentClass()); |
||||||
544 | $message->Decode(self::$decoder); |
||||||
545 | } |
||||||
546 | $message->instanceid = $instanceid; |
||||||
0 ignored issues
–
show
|
|||||||
547 | if ($element[EN_TAG] == SYNC_REMOVE) { |
||||||
548 | $message->instanceiddelete = true; |
||||||
0 ignored issues
–
show
|
|||||||
549 | $element[EN_TAG] = SYNC_MODIFY; |
||||||
550 | } |
||||||
551 | } |
||||||
552 | |||||||
553 | switch ($element[EN_TAG]) { |
||||||
554 | case SYNC_FETCH: |
||||||
555 | array_push($actiondata["fetchids"], $serverid); |
||||||
556 | break; |
||||||
557 | |||||||
558 | default: |
||||||
559 | // get the importer |
||||||
560 | if ($this->importer === false) { |
||||||
561 | $status = $this->getImporter($sc, $spa, $actiondata); |
||||||
562 | } |
||||||
563 | |||||||
564 | if ($status == SYNC_STATUS_SUCCESS) { |
||||||
565 | $this->importMessage($spa, $actiondata, $element[EN_TAG], $message, $clientid, $serverid, $foldertype, $nchanges); |
||||||
0 ignored issues
–
show
$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
![]() |
|||||||
566 | } |
||||||
567 | else { |
||||||
568 | SLog::Write(LOGLEVEL_WARN, "Ignored incoming change, global status indicates problem."); |
||||||
569 | } |
||||||
570 | break; |
||||||
571 | } |
||||||
572 | |||||||
573 | if ($actiondata["fetchids"]) { |
||||||
574 | self::$topCollector->AnnounceInformation(sprintf("Fetching %d", $nchanges)); |
||||||
575 | } |
||||||
576 | else { |
||||||
577 | self::$topCollector->AnnounceInformation(sprintf("Incoming %d", $nchanges)); |
||||||
578 | } |
||||||
579 | |||||||
580 | if (!self::$decoder->getElementEndTag()) { // end add/change/delete/move |
||||||
581 | return false; |
||||||
582 | } |
||||||
583 | } |
||||||
584 | |||||||
585 | if ($status == SYNC_STATUS_SUCCESS && $this->importer !== false) { |
||||||
586 | SLog::Write(LOGLEVEL_INFO, sprintf("Processed '%d' incoming changes", $nchanges)); |
||||||
587 | if (!$actiondata["fetchids"]) { |
||||||
588 | self::$topCollector->AnnounceInformation(sprintf("%d incoming", $nchanges), $this->singleFolder); |
||||||
589 | $this->saveMultiFolderInfo("incoming", $nchanges); |
||||||
590 | } |
||||||
591 | |||||||
592 | try { |
||||||
593 | // Save the updated state, which is used for the exporter later |
||||||
594 | $sc->AddParameter($spa, "state", $this->importer->GetState()); |
||||||
595 | } |
||||||
596 | catch (StatusException $stex) { |
||||||
597 | $status = $stex->getCode(); |
||||||
598 | } |
||||||
599 | |||||||
600 | // Check if changes are requested - if not and there are no changes to be exported anyway, update folderstat! |
||||||
601 | if (!$sc->GetParameter($spa, "getchanges") && !$sc->CountChange($spa->GetFolderId())) { |
||||||
602 | SLog::Write(LOGLEVEL_DEBUG, "Incoming changes, no export requested: update folderstat"); |
||||||
603 | $newFolderStatAfterImport = self::$backend->GetFolderStat(GSync::GetAdditionalSyncFolderStore($spa->GetBackendFolderId()), $spa->GetBackendFolderId()); |
||||||
604 | $this->setFolderStat($spa, $newFolderStatAfterImport); |
||||||
605 | } |
||||||
606 | } |
||||||
607 | |||||||
608 | if (!self::$decoder->getElementEndTag()) { // end PERFORM |
||||||
609 | return false; |
||||||
610 | } |
||||||
611 | } |
||||||
612 | |||||||
613 | // save the failsafe state |
||||||
614 | if (!empty($actiondata["statusids"])) { |
||||||
615 | unset($actiondata["failstate"]); |
||||||
616 | $actiondata["failedsyncstate"] = $sc->GetParameter($spa, "state"); |
||||||
617 | self::$deviceManager->GetStateManager()->SetSyncFailState($actiondata); |
||||||
618 | } |
||||||
619 | |||||||
620 | // save actiondata |
||||||
621 | $sc->AddParameter($spa, "actiondata", $actiondata); |
||||||
622 | |||||||
623 | if (!self::$decoder->getElementEndTag()) { // end collection |
||||||
624 | return false; |
||||||
625 | } |
||||||
626 | |||||||
627 | // AS14 does not send GetChanges anymore. We should do it if there were no incoming changes |
||||||
628 | if (!isset($performaction) && !$sc->GetParameter($spa, "getchanges") && $spa->HasSyncKey()) { |
||||||
629 | $sc->AddParameter($spa, "getchanges", true); |
||||||
630 | } |
||||||
631 | } // END FOLDER |
||||||
632 | |||||||
633 | if (!$wbxmlproblem && !self::$decoder->getElementEndTag()) { // end collections |
||||||
634 | return false; |
||||||
635 | } |
||||||
636 | } // end FOLDERS |
||||||
637 | |||||||
638 | if (self::$decoder->getElementStartTag(SYNC_HEARTBEATINTERVAL)) { |
||||||
639 | $hbinterval = self::$decoder->getElementContent(); |
||||||
640 | if (!self::$decoder->getElementEndTag()) { // SYNC_HEARTBEATINTERVAL |
||||||
641 | return false; |
||||||
642 | } |
||||||
643 | } |
||||||
644 | |||||||
645 | if (self::$decoder->getElementStartTag(SYNC_WAIT)) { |
||||||
646 | $wait = self::$decoder->getElementContent(); |
||||||
647 | if (!self::$decoder->getElementEndTag()) { // SYNC_WAIT |
||||||
648 | return false; |
||||||
649 | } |
||||||
650 | |||||||
651 | // internally the heartbeat interval and the wait time are the same |
||||||
652 | // heartbeat is in seconds, wait in minutes |
||||||
653 | $hbinterval = $wait * 60; |
||||||
654 | } |
||||||
655 | |||||||
656 | if (self::$decoder->getElementStartTag(SYNC_WINDOWSIZE)) { |
||||||
657 | $sc->SetGlobalWindowSize(self::$decoder->getElementContent()); |
||||||
658 | SLog::Write(LOGLEVEL_DEBUG, "Sync(): Global WindowSize requested: " . $sc->GetGlobalWindowSize()); |
||||||
659 | if (!self::$decoder->getElementEndTag()) { // SYNC_WINDOWSIZE |
||||||
660 | return false; |
||||||
661 | } |
||||||
662 | } |
||||||
663 | |||||||
664 | if (self::$decoder->getElementStartTag(SYNC_PARTIAL)) { |
||||||
665 | $partial = true; |
||||||
666 | } |
||||||
667 | else { |
||||||
668 | $partial = false; |
||||||
669 | } |
||||||
670 | |||||||
671 | if (!$wbxmlproblem && !self::$decoder->getElementEndTag()) { // end sync |
||||||
672 | return false; |
||||||
673 | } |
||||||
674 | } |
||||||
675 | // we did not receive a SYNCHRONIZE block - assume empty sync |
||||||
676 | else { |
||||||
677 | $emptysync = true; |
||||||
678 | } |
||||||
679 | // END SYNCHRONIZE |
||||||
680 | |||||||
681 | // check heartbeat/wait time |
||||||
682 | if (isset($hbinterval)) { |
||||||
683 | if ($hbinterval < 60 || $hbinterval > 3540) { |
||||||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||||||
684 | $status = SYNC_STATUS_INVALIDWAITORHBVALUE; |
||||||
685 | SLog::Write(LOGLEVEL_WARN, sprintf("HandleSync(): Invalid heartbeat or wait value '%s'", $hbinterval)); |
||||||
686 | } |
||||||
687 | } |
||||||
688 | |||||||
689 | // Partial & Empty Syncs need saved data to proceed with synchronization |
||||||
690 | if ($status == SYNC_STATUS_SUCCESS && ($emptysync === true || $partial === true)) { |
||||||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||||||
691 | SLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Partial or Empty sync requested. Retrieving data of synchronized folders.")); |
||||||
692 | |||||||
693 | // Load all collections - do not overwrite existing (received!), load states, check permissions and only load confirmed states! |
||||||
694 | try { |
||||||
695 | $sc->LoadAllCollections(false, true, true, true, true); |
||||||
696 | } |
||||||
697 | catch (StateInvalidException $siex) { |
||||||
698 | $status = SYNC_STATUS_INVALIDSYNCKEY; |
||||||
699 | self::$topCollector->AnnounceInformation("StateNotFoundException", $this->singleFolder); |
||||||
700 | $this->saveMultiFolderInfo("exception", "StateNotFoundException"); |
||||||
701 | } |
||||||
702 | catch (StatusException $stex) { |
||||||
703 | $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED; |
||||||
704 | self::$topCollector->AnnounceInformation(sprintf("StatusException code: %d", $status), $this->singleFolder); |
||||||
705 | $this->saveMultiFolderInfo("exception", "StatusException"); |
||||||
706 | } |
||||||
707 | |||||||
708 | // update a few values |
||||||
709 | foreach ($sc as $folderid => $spa) { |
||||||
710 | // manually set getchanges parameter for this collection if it is synchronized |
||||||
711 | if ($spa->HasSyncKey()) { |
||||||
712 | $actiondata = $sc->GetParameter($spa, "actiondata"); |
||||||
713 | // request changes if no other actions are executed |
||||||
714 | if (empty($actiondata["modifyids"]) && empty($actiondata["clientids"]) && empty($actiondata["removeids"])) { |
||||||
715 | $sc->AddParameter($spa, "getchanges", true); |
||||||
716 | } |
||||||
717 | |||||||
718 | // announce WindowSize to DeviceManager |
||||||
719 | self::$deviceManager->SetWindowSize($folderid, $spa->GetWindowSize()); |
||||||
720 | } |
||||||
721 | } |
||||||
722 | if (!$sc->HasCollections()) { |
||||||
723 | $status = SYNC_STATUS_SYNCREQUESTINCOMPLETE; |
||||||
724 | } |
||||||
725 | } |
||||||
726 | elseif (isset($hbinterval)) { |
||||||
727 | // load the hierarchy data - there are no permissions to verify so we just set it to false |
||||||
728 | if (!$sc->LoadCollection(false, true, false)) { |
||||||
0 ignored issues
–
show
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
![]() |
|||||||
729 | $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED; |
||||||
730 | self::$topCollector->AnnounceInformation(sprintf("StatusException code: %d", $status), $this->singleFolder); |
||||||
731 | $this->saveMultiFolderInfo("exception", "StatusException"); |
||||||
732 | } |
||||||
733 | } |
||||||
734 | |||||||
735 | // HEARTBEAT |
||||||
736 | if ($status == SYNC_STATUS_SUCCESS && isset($hbinterval)) { |
||||||
737 | $interval = (defined('PING_INTERVAL') && PING_INTERVAL > 0) ? PING_INTERVAL : 30; |
||||||
738 | $sc->SetLifetime($hbinterval); |
||||||
739 | |||||||
740 | // states are lazy loaded - we have to make sure that they are there! |
||||||
741 | $loadstatus = SYNC_STATUS_SUCCESS; |
||||||
742 | foreach ($sc as $folderid => $spa) { |
||||||
743 | // some androids do heartbeat on the OUTBOX folder, with weird results |
||||||
744 | // we do not load the state so we will never get relevant changes on the OUTBOX folder |
||||||
745 | if (self::$deviceManager->GetFolderTypeFromCacheById($folderid) == SYNC_FOLDER_TYPE_OUTBOX) { |
||||||
746 | SLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Heartbeat on Outbox folder not allowed")); |
||||||
747 | |||||||
748 | continue; |
||||||
749 | } |
||||||
750 | |||||||
751 | $fad = []; |
||||||
752 | // if loading the states fails, we do not enter heartbeat, but we keep $status on SYNC_STATUS_SUCCESS |
||||||
753 | // so when the changes are exported the correct folder gets an SYNC_STATUS_INVALIDSYNCKEY |
||||||
754 | if ($loadstatus == SYNC_STATUS_SUCCESS) { |
||||||
755 | $loadstatus = $this->loadStates($sc, $spa, $fad); |
||||||
756 | } |
||||||
757 | } |
||||||
758 | |||||||
759 | if ($loadstatus == SYNC_STATUS_SUCCESS) { |
||||||
760 | $foundchanges = false; |
||||||
761 | |||||||
762 | try { |
||||||
763 | // always check for changes |
||||||
764 | SLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Entering Heartbeat mode")); |
||||||
765 | $foundchanges = $sc->CheckForChanges($sc->GetLifetime(), $interval); |
||||||
766 | } |
||||||
767 | catch (StatusException $stex) { |
||||||
768 | if ($stex->getCode() == SyncCollections::OBSOLETE_CONNECTION) { |
||||||
769 | $status = SYNC_COMMONSTATUS_SYNCSTATEVERSIONINVALID; |
||||||
770 | } |
||||||
771 | else { |
||||||
772 | $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED; |
||||||
773 | self::$topCollector->AnnounceInformation(sprintf("StatusException code: %d", $status), $this->singleFolder); |
||||||
774 | $this->saveMultiFolderInfo("exception", "StatusException"); |
||||||
775 | } |
||||||
776 | } |
||||||
777 | |||||||
778 | // update the waittime waited |
||||||
779 | self::$waitTime = $sc->GetWaitedSeconds(); |
||||||
780 | |||||||
781 | // in case there are no changes and no other request has synchronized while we waited, we can reply with an empty response |
||||||
782 | if (!$foundchanges && $status == SYNC_STATUS_SUCCESS) { |
||||||
783 | // if there were changes to the SPA or CPOs we need to save this before we terminate |
||||||
784 | // only save if the state was not modified by some other request, if so, return state invalid status |
||||||
785 | foreach ($sc as $folderid => $spa) { |
||||||
786 | if (self::$deviceManager->CheckHearbeatStateIntegrity($spa->GetFolderId(), $spa->GetUuid(), $spa->GetUuidCounter())) { |
||||||
787 | $status = SYNC_COMMONSTATUS_SYNCSTATEVERSIONINVALID; |
||||||
788 | } |
||||||
789 | else { |
||||||
790 | $sc->SaveCollection($spa); |
||||||
791 | } |
||||||
792 | } |
||||||
793 | |||||||
794 | if ($status == SYNC_STATUS_SUCCESS) { |
||||||
795 | SLog::Write(LOGLEVEL_DEBUG, "No changes found and no other process changed states. Replying with empty response and closing connection."); |
||||||
796 | self::$specialHeaders = []; |
||||||
797 | self::$specialHeaders[] = "Connection: close"; |
||||||
798 | |||||||
799 | return true; |
||||||
800 | } |
||||||
801 | } |
||||||
802 | |||||||
803 | if ($foundchanges) { |
||||||
804 | foreach ($sc->GetChangedFolderIds() as $folderid => $changecount) { |
||||||
805 | // check if there were other sync requests for a folder during the heartbeat |
||||||
806 | $spa = $sc->GetCollection($folderid); |
||||||
807 | if ($changecount > 0 && $sc->WaitedForChanges() && self::$deviceManager->CheckHearbeatStateIntegrity($spa->GetFolderId(), $spa->GetUuid(), $spa->GetUuidCounter())) { |
||||||
0 ignored issues
–
show
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
![]() 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
![]() |
|||||||
808 | SLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): heartbeat: found %d changes in '%s' which was already synchronized. Heartbeat aborted!", $changecount, $folderid)); |
||||||
809 | $status = SYNC_COMMONSTATUS_SYNCSTATEVERSIONINVALID; |
||||||
810 | } |
||||||
811 | else { |
||||||
812 | SLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): heartbeat: found %d changes in '%s'", $changecount, $folderid)); |
||||||
813 | } |
||||||
814 | } |
||||||
815 | } |
||||||
816 | } |
||||||
817 | } |
||||||
818 | |||||||
819 | // Start the output |
||||||
820 | SLog::Write(LOGLEVEL_DEBUG, "HandleSync(): Start Output"); |
||||||
821 | |||||||
822 | // global status |
||||||
823 | // SYNC_COMMONSTATUS_* start with values from 101 |
||||||
824 | if ($status != SYNC_COMMONSTATUS_SUCCESS && ($status == SYNC_STATUS_FOLDERHIERARCHYCHANGED || $status > 100)) { |
||||||
825 | self::$deviceManager->AnnounceProcessStatus($folderid, $status); |
||||||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||||||
826 | $this->sendStartTags(); |
||||||
827 | self::$encoder->startTag(SYNC_STATUS); |
||||||
828 | self::$encoder->content($status); |
||||||
829 | self::$encoder->endTag(); |
||||||
830 | self::$encoder->endTag(); // SYNC_SYNCHRONIZE |
||||||
831 | |||||||
832 | return true; |
||||||
833 | } |
||||||
834 | |||||||
835 | // Loop through requested folders |
||||||
836 | foreach ($sc as $folderid => $spa) { |
||||||
837 | // get actiondata |
||||||
838 | $actiondata = $sc->GetParameter($spa, "actiondata"); |
||||||
839 | |||||||
840 | if ($status == SYNC_STATUS_SUCCESS && (!$spa->GetContentClass() || !$spa->GetFolderId())) { |
||||||
841 | SLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): no content class or folderid found for collection.")); |
||||||
842 | |||||||
843 | continue; |
||||||
844 | } |
||||||
845 | |||||||
846 | if (!$sc->GetParameter($spa, "requested")) { |
||||||
847 | SLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): partial sync for folder class '%s' with id '%s'", $spa->GetContentClass(), $spa->GetFolderId())); |
||||||
848 | // reload state and initialize StateMachine correctly |
||||||
849 | $sc->AddParameter($spa, "state", null); |
||||||
850 | $status = $this->loadStates($sc, $spa, $actiondata); |
||||||
851 | } |
||||||
852 | |||||||
853 | // initialize exporter to get changecount |
||||||
854 | $changecount = false; |
||||||
855 | $exporter = false; |
||||||
856 | $streamimporter = false; |
||||||
857 | $newFolderStat = false; |
||||||
858 | $setupExporter = true; |
||||||
859 | |||||||
860 | // TODO we could check against $sc->GetChangedFolderIds() on heartbeat so we do not need to configure all exporter again |
||||||
861 | if ($status == SYNC_STATUS_SUCCESS && ($sc->GetParameter($spa, "getchanges") || !$spa->HasSyncKey())) { |
||||||
862 | // no need to run the exporter if the globalwindowsize is already full - if collection already has a synckey |
||||||
863 | if ($sc->GetGlobalWindowSize() == $this->globallyExportedItems && $spa->HasSyncKey()) { |
||||||
864 | SLog::Write(LOGLEVEL_DEBUG, sprintf("Sync(): no exporter setup for '%s' as GlobalWindowSize is full.", $spa->GetFolderId())); |
||||||
865 | $setupExporter = false; |
||||||
866 | } |
||||||
867 | // if the maximum request timeout is reached, stop processing other collections |
||||||
868 | if (Request::IsRequestTimeoutReached()) { |
||||||
869 | SLog::Write(LOGLEVEL_DEBUG, sprintf("Sync(): no exporter setup for '%s' as request timeout reached, omitting output for collection.", $spa->GetFolderId())); |
||||||
870 | $setupExporter = false; |
||||||
871 | } |
||||||
872 | |||||||
873 | // if max memory allocation is reached, stop processing other collections |
||||||
874 | if (Request::IsRequestMemoryLimitReached()) { |
||||||
875 | SLog::Write(LOGLEVEL_DEBUG, sprintf("Sync(): no exporter setup for '%s' as max memory allocatation reached, omitting output for collection.", $spa->GetFolderId())); |
||||||
876 | $setupExporter = false; |
||||||
877 | } |
||||||
878 | |||||||
879 | // force exporter run if there is a saved status |
||||||
880 | if ($setupExporter && self::$deviceManager->HasFolderSyncStatus($spa->GetFolderId())) { |
||||||
881 | SLog::Write(LOGLEVEL_DEBUG, sprintf("Sync(): forcing exporter setup for '%s' as a sync status is saved - ignoring backend folder stats", $spa->GetFolderId())); |
||||||
882 | } |
||||||
883 | // compare the folder statistics if the backend supports this |
||||||
884 | elseif ($setupExporter && self::$backend->HasFolderStats()) { |
||||||
885 | // check if the folder stats changed -> if not, don't setup the exporter, there are no changes! |
||||||
886 | $newFolderStat = self::$backend->GetFolderStat(GSync::GetAdditionalSyncFolderStore($spa->GetBackendFolderId()), $spa->GetBackendFolderId()); |
||||||
887 | if ($newFolderStat !== false && !$spa->IsExporterRunRequired($newFolderStat, true)) { |
||||||
888 | $changecount = 0; |
||||||
889 | $setupExporter = false; |
||||||
890 | } |
||||||
891 | } |
||||||
892 | |||||||
893 | // Do a full Exporter setup if we can't avoid it |
||||||
894 | if ($setupExporter) { |
||||||
895 | // make sure the states are loaded |
||||||
896 | $status = $this->loadStates($sc, $spa, $actiondata); |
||||||
897 | |||||||
898 | if ($status == SYNC_STATUS_SUCCESS) { |
||||||
899 | try { |
||||||
900 | // if this is an additional folder the backend has to be setup correctly |
||||||
901 | if (!self::$backend->Setup(GSync::GetAdditionalSyncFolderStore($spa->GetBackendFolderId()))) { |
||||||
902 | throw new StatusException(sprintf("HandleSync() could not Setup() the backend for folder id %s/%s", $spa->GetFolderId(), $spa->GetBackendFolderId()), SYNC_STATUS_FOLDERHIERARCHYCHANGED); |
||||||
903 | } |
||||||
904 | |||||||
905 | // Use the state from the importer, as changes may have already happened |
||||||
906 | $exporter = self::$backend->GetExporter($spa->GetBackendFolderId()); |
||||||
907 | |||||||
908 | if ($exporter === false) { |
||||||
909 | throw new StatusException(sprintf("HandleSync() could not get an exporter for folder id %s/%s", $spa->GetFolderId(), $spa->GetBackendFolderId()), SYNC_STATUS_FOLDERHIERARCHYCHANGED); |
||||||
910 | } |
||||||
911 | } |
||||||
912 | catch (StatusException $stex) { |
||||||
913 | $status = $stex->getCode(); |
||||||
914 | } |
||||||
915 | |||||||
916 | try { |
||||||
917 | // Stream the messages directly to the PDA |
||||||
918 | $streamimporter = new ImportChangesStream(self::$encoder, GSync::getSyncObjectFromFolderClass($spa->GetContentClass())); |
||||||
0 ignored issues
–
show
GSync::getSyncObjectFrom...spa->GetContentClass()) of type string is incompatible with the type SyncObject expected by parameter $class of ImportChangesStream::__construct() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
919 | |||||||
920 | if ($exporter !== false) { |
||||||
921 | $exporter->Config($sc->GetParameter($spa, "state")); |
||||||
922 | $exporter->ConfigContentParameters($spa->GetCPO()); |
||||||
923 | $exporter->InitializeExporter($streamimporter); |
||||||
924 | |||||||
925 | $changecount = $exporter->GetChangeCount(); |
||||||
926 | } |
||||||
927 | } |
||||||
928 | catch (StatusException $stex) { |
||||||
929 | if ($stex->getCode() === SYNC_FSSTATUS_CODEUNKNOWN && $spa->HasSyncKey()) { |
||||||
930 | $status = SYNC_STATUS_INVALIDSYNCKEY; |
||||||
931 | } |
||||||
932 | else { |
||||||
933 | $status = $stex->getCode(); |
||||||
934 | } |
||||||
935 | } |
||||||
936 | |||||||
937 | if (!$spa->HasSyncKey()) { |
||||||
938 | self::$topCollector->AnnounceInformation(sprintf("Exporter registered. %d objects queued.", $changecount), $this->singleFolder); |
||||||
939 | $this->saveMultiFolderInfo("queued", $changecount); |
||||||
940 | // update folder status as initialized |
||||||
941 | $spa->SetFolderSyncTotal($changecount); |
||||||
942 | $spa->SetFolderSyncRemaining($changecount); |
||||||
943 | if ($changecount > 0) { |
||||||
944 | self::$deviceManager->SetFolderSyncStatus($folderid, DeviceManager::FLD_SYNC_INITIALIZED); |
||||||
945 | } |
||||||
946 | } |
||||||
947 | elseif ($status != SYNC_STATUS_SUCCESS) { |
||||||
948 | self::$topCollector->AnnounceInformation(sprintf("StatusException code: %d", $status), $this->singleFolder); |
||||||
949 | $this->saveMultiFolderInfo("exception", "StatusException"); |
||||||
950 | } |
||||||
951 | self::$deviceManager->AnnounceProcessStatus($spa->GetFolderId(), $status); |
||||||
952 | } |
||||||
953 | } |
||||||
954 | } |
||||||
955 | |||||||
956 | // Get a new sync key to output to the client if any changes have been send by the mobile or a new synckey is to be sent |
||||||
957 | if (!empty($actiondata["modifyids"]) || |
||||||
958 | !empty($actiondata["clientids"]) || |
||||||
959 | !empty($actiondata["removeids"]) || |
||||||
960 | (!$spa->HasSyncKey() && $status == SYNC_STATUS_SUCCESS)) { |
||||||
961 | $spa->SetNewSyncKey(self::$deviceManager->GetStateManager()->GetNewSyncKey($spa->GetSyncKey())); |
||||||
962 | } |
||||||
963 | // get a new synckey only if we did not reach the global limit yet |
||||||
964 | else { |
||||||
965 | // when reaching the global limit for changes of all collections, stop processing other collections |
||||||
966 | if ($sc->GetGlobalWindowSize() <= $this->globallyExportedItems) { |
||||||
967 | SLog::Write(LOGLEVEL_DEBUG, "Global WindowSize for amount of exported changes reached, omitting output for collection."); |
||||||
968 | |||||||
969 | continue; |
||||||
970 | } |
||||||
971 | |||||||
972 | // get a new synckey if there are changes are we did not reach the limit yet |
||||||
973 | if ($changecount > 0) { |
||||||
974 | $spa->SetNewSyncKey(self::$deviceManager->GetStateManager()->GetNewSyncKey($spa->GetSyncKey())); |
||||||
975 | } |
||||||
976 | } |
||||||
977 | |||||||
978 | // Fir AS 14.0+ omit output for folder, if there were no incoming or outgoing changes and no Fetch |
||||||
979 | if (Request::GetProtocolVersion() >= 14.0 && !$spa->HasNewSyncKey() && $changecount == 0 && empty($actiondata["fetchids"]) && $status == SYNC_STATUS_SUCCESS && |
||||||
980 | !$spa->HasConfirmationChanged() && ($newFolderStat === false || !$spa->IsExporterRunRequired($newFolderStat))) { |
||||||
981 | SLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync: No changes found for %s folder id '%s'. Omitting output.", $spa->GetContentClass(), $spa->GetFolderId())); |
||||||
982 | |||||||
983 | continue; |
||||||
984 | } |
||||||
985 | |||||||
986 | // if there are no other responses sent, we should end with a global status |
||||||
987 | if ($status == SYNC_STATUS_FOLDERHIERARCHYCHANGED && $this->startTagsSent === false) { |
||||||
988 | $this->sendStartTags(); |
||||||
989 | self::$encoder->startTag(SYNC_STATUS); |
||||||
990 | self::$encoder->content($status); |
||||||
991 | self::$encoder->endTag(); |
||||||
992 | self::$encoder->endTag(); // SYNC_SYNCHRONIZE |
||||||
993 | |||||||
994 | return true; |
||||||
995 | } |
||||||
996 | |||||||
997 | // there is something to send here, sync folder to output |
||||||
998 | $this->syncFolder($sc, $spa, $exporter, $changecount, $streamimporter, $status, $newFolderStat); |
||||||
0 ignored issues
–
show
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
![]() 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
![]() |
|||||||
999 | |||||||
1000 | // reset status for the next folder |
||||||
1001 | $status = SYNC_STATUS_SUCCESS; |
||||||
1002 | } // END foreach collection |
||||||
1003 | |||||||
1004 | // SYNC_FOLDERS - only if the starttag was sent |
||||||
1005 | if ($this->startFolderTagSent) { |
||||||
1006 | self::$encoder->endTag(); |
||||||
1007 | } |
||||||
1008 | |||||||
1009 | // Check if there was any response - in case of an empty sync request, we shouldn't send an empty answer |
||||||
1010 | if (!$this->startTagsSent && $emptysync === true) { |
||||||
1011 | $this->sendStartTags(); |
||||||
1012 | self::$encoder->startTag(SYNC_STATUS); |
||||||
1013 | self::$encoder->content(SYNC_STATUS_SYNCREQUESTINCOMPLETE); |
||||||
1014 | self::$encoder->endTag(); |
||||||
1015 | } |
||||||
1016 | |||||||
1017 | // SYNC_SYNCHRONIZE - only if the starttag was sent |
||||||
1018 | if ($this->startTagsSent) { |
||||||
1019 | self::$encoder->endTag(); |
||||||
1020 | } |
||||||
1021 | |||||||
1022 | // final top announcement for a multi-folder sync |
||||||
1023 | if ($sc->GetCollectionCount() > 1) { |
||||||
1024 | self::$topCollector->AnnounceInformation($this->getMultiFolderInfoLine($sc->GetCollectionCount()), true); |
||||||
1025 | SLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync: Processed %d folders", $sc->GetCollectionCount())); |
||||||
1026 | } |
||||||
1027 | |||||||
1028 | // update the waittime waited |
||||||
1029 | self::$waitTime = $sc->GetWaitedSeconds(); |
||||||
1030 | |||||||
1031 | return true; |
||||||
1032 | } |
||||||
1033 | |||||||
1034 | /** |
||||||
1035 | * Sends the SYNC_SYNCHRONIZE once per request. |
||||||
1036 | */ |
||||||
1037 | private function sendStartTags() { |
||||||
1038 | if ($this->startTagsSent === false) { |
||||||
1039 | self::$encoder->startWBXML(); |
||||||
1040 | self::$encoder->startTag(SYNC_SYNCHRONIZE); |
||||||
1041 | $this->startTagsSent = true; |
||||||
1042 | } |
||||||
1043 | } |
||||||
1044 | |||||||
1045 | /** |
||||||
1046 | * Sends the SYNC_FOLDERS once per request. |
||||||
1047 | */ |
||||||
1048 | private function sendFolderStartTag() { |
||||||
1049 | $this->sendStartTags(); |
||||||
1050 | if ($this->startFolderTagSent === false) { |
||||||
1051 | self::$encoder->startTag(SYNC_FOLDERS); |
||||||
1052 | $this->startFolderTagSent = true; |
||||||
1053 | } |
||||||
1054 | } |
||||||
1055 | |||||||
1056 | /** |
||||||
1057 | * Synchronizes a folder to the output stream. Changes for this folders are expected. |
||||||
1058 | * |
||||||
1059 | * @param SyncCollections $sc |
||||||
1060 | * @param SyncParameters $spa |
||||||
1061 | * @param IExportChanges $exporter Fully configured exporter for this folder |
||||||
1062 | * @param int $changecount Amount of changes expected |
||||||
1063 | * @param ImportChangesStream $streamimporter Output stream |
||||||
1064 | * @param int $status current status of the folder processing |
||||||
1065 | * @param string $newFolderStat the new folder stat to be set if everything was exported |
||||||
1066 | * |
||||||
1067 | * @return int sync status code |
||||||
1068 | * |
||||||
1069 | * @throws StatusException |
||||||
1070 | */ |
||||||
1071 | private function syncFolder($sc, $spa, $exporter, $changecount, $streamimporter, $status, $newFolderStat) { |
||||||
1072 | $actiondata = $sc->GetParameter($spa, "actiondata"); |
||||||
1073 | |||||||
1074 | // send the WBXML start tags (if not happened already) |
||||||
1075 | $this->sendFolderStartTag(); |
||||||
1076 | self::$encoder->startTag(SYNC_FOLDER); |
||||||
1077 | |||||||
1078 | if ($spa->HasContentClass()) { |
||||||
1079 | SLog::Write(LOGLEVEL_DEBUG, sprintf("Folder type: %s", $spa->GetContentClass())); |
||||||
1080 | // AS 12.0 devices require content class |
||||||
1081 | if (Request::GetProtocolVersion() < 12.1) { |
||||||
1082 | self::$encoder->startTag(SYNC_FOLDERTYPE); |
||||||
1083 | self::$encoder->content($spa->GetContentClass()); |
||||||
1084 | self::$encoder->endTag(); |
||||||
1085 | } |
||||||
1086 | } |
||||||
1087 | |||||||
1088 | self::$encoder->startTag(SYNC_SYNCKEY); |
||||||
1089 | if ($status == SYNC_STATUS_SUCCESS && $spa->HasNewSyncKey()) { |
||||||
1090 | self::$encoder->content($spa->GetNewSyncKey()); |
||||||
1091 | } |
||||||
1092 | else { |
||||||
1093 | self::$encoder->content($spa->GetSyncKey()); |
||||||
1094 | } |
||||||
1095 | self::$encoder->endTag(); |
||||||
1096 | |||||||
1097 | self::$encoder->startTag(SYNC_FOLDERID); |
||||||
1098 | self::$encoder->content($spa->GetFolderId()); |
||||||
1099 | self::$encoder->endTag(); |
||||||
1100 | |||||||
1101 | self::$encoder->startTag(SYNC_STATUS); |
||||||
1102 | self::$encoder->content($status); |
||||||
1103 | self::$encoder->endTag(); |
||||||
1104 | |||||||
1105 | // announce failing status to the process loop detection |
||||||
1106 | if ($status !== SYNC_STATUS_SUCCESS) { |
||||||
1107 | self::$deviceManager->AnnounceProcessStatus($spa->GetFolderId(), $status); |
||||||
1108 | } |
||||||
1109 | |||||||
1110 | // Output IDs and status for incoming items & requests |
||||||
1111 | if ($status == SYNC_STATUS_SUCCESS && ( |
||||||
1112 | !empty($actiondata["clientids"]) || |
||||||
1113 | !empty($actiondata["modifyids"]) || |
||||||
1114 | !empty($actiondata["removeids"]) || |
||||||
1115 | !empty($actiondata["fetchids"]) |
||||||
1116 | )) { |
||||||
1117 | self::$encoder->startTag(SYNC_REPLIES); |
||||||
1118 | // output result of all new incoming items |
||||||
1119 | foreach ($actiondata["clientids"] as $clientid => $response) { |
||||||
1120 | self::$encoder->startTag(SYNC_ADD); |
||||||
1121 | self::$encoder->startTag(SYNC_CLIENTENTRYID); |
||||||
1122 | self::$encoder->content($clientid); |
||||||
1123 | self::$encoder->endTag(); |
||||||
1124 | if (!empty($response->serverid)) { |
||||||
1125 | self::$encoder->startTag(SYNC_SERVERENTRYID); |
||||||
1126 | self::$encoder->content($response->serverid); |
||||||
1127 | self::$encoder->endTag(); |
||||||
1128 | } |
||||||
1129 | self::$encoder->startTag(SYNC_STATUS); |
||||||
1130 | self::$encoder->content(isset($actiondata["statusids"][$clientid]) ? $actiondata["statusids"][$clientid] : SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR); |
||||||
1131 | self::$encoder->endTag(); |
||||||
1132 | if (!empty($response->hasResponse)) { |
||||||
1133 | self::$encoder->startTag(SYNC_DATA); |
||||||
1134 | $response->Encode(self::$encoder); |
||||||
1135 | self::$encoder->endTag(); |
||||||
1136 | } |
||||||
1137 | self::$encoder->endTag(); |
||||||
1138 | } |
||||||
1139 | |||||||
1140 | // loop through modify operations which were not a success, send status |
||||||
1141 | foreach ($actiondata["modifyids"] as $serverid => $response) { |
||||||
1142 | if (isset($actiondata["statusids"][$serverid]) && ($actiondata["statusids"][$serverid] !== SYNC_STATUS_SUCCESS || !empty($response->hasResponse))) { |
||||||
1143 | self::$encoder->startTag(SYNC_MODIFY); |
||||||
1144 | self::$encoder->startTag(SYNC_SERVERENTRYID); |
||||||
1145 | self::$encoder->content($serverid); |
||||||
1146 | self::$encoder->endTag(); |
||||||
1147 | self::$encoder->startTag(SYNC_STATUS); |
||||||
1148 | self::$encoder->content($actiondata["statusids"][$serverid]); |
||||||
1149 | self::$encoder->endTag(); |
||||||
1150 | if (!empty($response->hasResponse)) { |
||||||
1151 | self::$encoder->startTag(SYNC_DATA); |
||||||
1152 | $response->Encode(self::$encoder); |
||||||
1153 | self::$encoder->endTag(); |
||||||
1154 | } |
||||||
1155 | self::$encoder->endTag(); |
||||||
1156 | } |
||||||
1157 | } |
||||||
1158 | |||||||
1159 | // loop through remove operations which were not a success, send status |
||||||
1160 | foreach ($actiondata["removeids"] as $serverid) { |
||||||
1161 | if (isset($actiondata["statusids"][$serverid]) && $actiondata["statusids"][$serverid] !== SYNC_STATUS_SUCCESS) { |
||||||
1162 | self::$encoder->startTag(SYNC_REMOVE); |
||||||
1163 | self::$encoder->startTag(SYNC_SERVERENTRYID); |
||||||
1164 | self::$encoder->content($serverid); |
||||||
1165 | self::$encoder->endTag(); |
||||||
1166 | self::$encoder->startTag(SYNC_STATUS); |
||||||
1167 | self::$encoder->content($actiondata["statusids"][$serverid]); |
||||||
1168 | self::$encoder->endTag(); |
||||||
1169 | self::$encoder->endTag(); |
||||||
1170 | } |
||||||
1171 | } |
||||||
1172 | |||||||
1173 | if (!empty($actiondata["fetchids"])) { |
||||||
1174 | self::$topCollector->AnnounceInformation(sprintf("Fetching %d objects ", count($actiondata["fetchids"])), $this->singleFolder); |
||||||
1175 | $this->saveMultiFolderInfo("fetching", count($actiondata["fetchids"])); |
||||||
1176 | } |
||||||
1177 | |||||||
1178 | foreach ($actiondata["fetchids"] as $id) { |
||||||
1179 | $data = false; |
||||||
1180 | |||||||
1181 | try { |
||||||
1182 | $fetchstatus = SYNC_STATUS_SUCCESS; |
||||||
1183 | |||||||
1184 | // if this is an additional folder the backend has to be setup correctly |
||||||
1185 | if (!self::$backend->Setup(GSync::GetAdditionalSyncFolderStore($spa->GetBackendFolderId()))) { |
||||||
1186 | throw new StatusException(sprintf("HandleSync(): could not Setup() the backend to fetch in folder id %s/%s", $spa->GetFolderId(), $spa->GetBackendFolderId()), SYNC_STATUS_OBJECTNOTFOUND); |
||||||
1187 | } |
||||||
1188 | |||||||
1189 | $data = self::$backend->Fetch($spa->GetBackendFolderId(), $id, $spa->GetCPO()); |
||||||
1190 | |||||||
1191 | // check if the message is broken |
||||||
1192 | if (GSync::GetDeviceManager(false) && GSync::GetDeviceManager()->DoNotStreamMessage($id, $data)) { |
||||||
1193 | SLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): message not to be streamed as requested by DeviceManager, id = %s", $id)); |
||||||
1194 | $fetchstatus = SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR; |
||||||
1195 | } |
||||||
1196 | } |
||||||
1197 | catch (StatusException $stex) { |
||||||
1198 | $fetchstatus = $stex->getCode(); |
||||||
1199 | } |
||||||
1200 | |||||||
1201 | self::$encoder->startTag(SYNC_FETCH); |
||||||
1202 | self::$encoder->startTag(SYNC_SERVERENTRYID); |
||||||
1203 | self::$encoder->content($id); |
||||||
1204 | self::$encoder->endTag(); |
||||||
1205 | |||||||
1206 | self::$encoder->startTag(SYNC_STATUS); |
||||||
1207 | self::$encoder->content($fetchstatus); |
||||||
1208 | self::$encoder->endTag(); |
||||||
1209 | |||||||
1210 | if ($data !== false && $status == SYNC_STATUS_SUCCESS) { |
||||||
1211 | self::$encoder->startTag(SYNC_DATA); |
||||||
1212 | $data->Encode(self::$encoder); |
||||||
1213 | self::$encoder->endTag(); |
||||||
1214 | } |
||||||
1215 | else { |
||||||
1216 | SLog::Write(LOGLEVEL_WARN, sprintf("Unable to Fetch '%s'", $id)); |
||||||
1217 | } |
||||||
1218 | self::$encoder->endTag(); |
||||||
1219 | } |
||||||
1220 | self::$encoder->endTag(); |
||||||
1221 | } |
||||||
1222 | |||||||
1223 | if ($sc->GetParameter($spa, "getchanges") && $spa->HasFolderId() && $spa->HasContentClass() && $spa->HasSyncKey()) { |
||||||
0 ignored issues
–
show
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
![]() |
|||||||
1224 | $moreAvailableSent = false; |
||||||
1225 | $windowSize = self::$deviceManager->GetWindowSize($spa->GetFolderId(), $spa->GetUuid(), $spa->GetUuidCounter(), $changecount); |
||||||
1226 | |||||||
1227 | // limit windowSize to the max available limit of the global window size left |
||||||
1228 | $globallyAvailable = $sc->GetGlobalWindowSize() - $this->globallyExportedItems; |
||||||
1229 | if ($changecount > $globallyAvailable && $windowSize > $globallyAvailable) { |
||||||
1230 | SLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Limit window size to %d as the global window size limit will be reached", $globallyAvailable)); |
||||||
1231 | $windowSize = $globallyAvailable; |
||||||
1232 | } |
||||||
1233 | // send <MoreAvailable/> if there are more changes than fit in the folder windowsize |
||||||
1234 | // or there is a move state (another sync should be done afterwards) |
||||||
1235 | if ($changecount > $windowSize) { |
||||||
1236 | self::$encoder->startTag(SYNC_MOREAVAILABLE, false, true); |
||||||
1237 | $moreAvailableSent = true; |
||||||
1238 | $spa->DelFolderStat(); |
||||||
1239 | } |
||||||
1240 | } |
||||||
1241 | |||||||
1242 | // Stream outgoing changes |
||||||
1243 | if ($status == SYNC_STATUS_SUCCESS && $sc->GetParameter($spa, "getchanges") == true && $windowSize > 0 && (bool) $exporter) { |
||||||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||||||
1244 | self::$topCollector->AnnounceInformation(sprintf("Streaming data of %d objects", ($changecount > $windowSize) ? $windowSize : $changecount)); |
||||||
1245 | |||||||
1246 | // Output message changes per folder |
||||||
1247 | self::$encoder->startTag(SYNC_PERFORM); |
||||||
1248 | |||||||
1249 | $n = 0; |
||||||
1250 | WBXMLDecoder::ResetInWhile("syncSynchronize"); |
||||||
1251 | while (WBXMLDecoder::InWhile("syncSynchronize")) { |
||||||
1252 | try { |
||||||
1253 | $progress = $exporter->Synchronize(); |
||||||
1254 | if (!is_array($progress)) { |
||||||
1255 | break; |
||||||
1256 | } |
||||||
1257 | ++$n; |
||||||
1258 | if ($n % 10 == 0) { |
||||||
1259 | self::$topCollector->AnnounceInformation(sprintf("Streamed data of %d objects out of %d", $n, ($changecount > $windowSize) ? $windowSize : $changecount)); |
||||||
1260 | } |
||||||
1261 | } |
||||||
1262 | catch (SyncObjectBrokenException $mbe) { |
||||||
1263 | $brokenSO = $mbe->GetSyncObject(); |
||||||
1264 | if (!$brokenSO) { |
||||||
1265 | SLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): Caught SyncObjectBrokenException but broken SyncObject not available. This should be fixed in the backend.")); |
||||||
1266 | } |
||||||
1267 | else { |
||||||
1268 | if (!isset($brokenSO->id)) { |
||||||
1269 | $brokenSO->id = "Unknown ID"; |
||||||
1270 | SLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): Caught SyncObjectBrokenException but no ID of object set. This should be fixed in the backend.")); |
||||||
1271 | } |
||||||
1272 | self::$deviceManager->AnnounceIgnoredMessage($spa->GetFolderId(), $brokenSO->id, $brokenSO); |
||||||
1273 | } |
||||||
1274 | } |
||||||
1275 | // something really bad happened while exporting changes |
||||||
1276 | catch (StatusException $stex) { |
||||||
1277 | $status = $stex->getCode(); |
||||||
1278 | // during export we found out that the states should be thrown away |
||||||
1279 | if ($status == SYNC_STATUS_INVALIDSYNCKEY) { |
||||||
1280 | self::$deviceManager->ForceFolderResync($spa->GetFolderId()); |
||||||
1281 | |||||||
1282 | break; |
||||||
1283 | } |
||||||
1284 | } |
||||||
1285 | |||||||
1286 | if ($n >= $windowSize || Request::IsRequestTimeoutReached() || Request::IsRequestMemoryLimitReached()) { |
||||||
1287 | SLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Exported maxItems of messages: %d / %d", $n, $changecount)); |
||||||
1288 | |||||||
1289 | break; |
||||||
1290 | } |
||||||
1291 | } |
||||||
1292 | |||||||
1293 | // $progress is not an array when exporting the last message |
||||||
1294 | // so we get the number to display from the streamimporter if it's available |
||||||
1295 | if ((bool) $streamimporter) { |
||||||
1296 | $n = $streamimporter->GetImportedMessages(); |
||||||
1297 | } |
||||||
1298 | |||||||
1299 | self::$encoder->endTag(); |
||||||
1300 | |||||||
1301 | // log the request timeout |
||||||
1302 | if (Request::IsRequestTimeoutReached() || Request::IsRequestMemoryLimitReached()) { |
||||||
1303 | SLog::Write(LOGLEVEL_DEBUG, "HandleSync(): Stopping export as limits of request timeout or available memory are almost reached!"); |
||||||
1304 | // Send a <MoreAvailable/> tag if we reached the request timeout or max memory, there are more changes and a moreavailable was not already send |
||||||
1305 | if (!$moreAvailableSent && ($n > $windowSize)) { |
||||||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||||||
1306 | self::$encoder->startTag(SYNC_MOREAVAILABLE, false, true); |
||||||
1307 | $spa->DelFolderStat(); |
||||||
1308 | $moreAvailableSent = true; |
||||||
0 ignored issues
–
show
|
|||||||
1309 | } |
||||||
1310 | } |
||||||
1311 | |||||||
1312 | self::$topCollector->AnnounceInformation(sprintf("Outgoing %d objects%s", $n, ($n >= $windowSize) ? " of " . $changecount : ""), $this->singleFolder); |
||||||
1313 | $this->saveMultiFolderInfo("outgoing", $n); |
||||||
1314 | $this->saveMultiFolderInfo("queued", $changecount); |
||||||
1315 | |||||||
1316 | $this->globallyExportedItems += $n; |
||||||
1317 | |||||||
1318 | // update folder status, if there is something set |
||||||
1319 | if ($spa->GetFolderSyncRemaining() && $changecount > 0) { |
||||||
0 ignored issues
–
show
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
![]() |
|||||||
1320 | $spa->SetFolderSyncRemaining($changecount); |
||||||
0 ignored issues
–
show
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
![]() |
|||||||
1321 | } |
||||||
1322 | // changecount is initialized with 'false', so 0 means no changes! |
||||||
1323 | if ($changecount === 0 || ($changecount !== false && $changecount <= $windowSize)) { |
||||||
1324 | self::$deviceManager->SetFolderSyncStatus($spa->GetFolderId(), DeviceManager::FLD_SYNC_COMPLETED); |
||||||
1325 | |||||||
1326 | // we should update the folderstat, but we recheck to see if it changed since the exporter setup. If so, it's not updated to force another sync |
||||||
1327 | if (self::$backend->HasFolderStats()) { |
||||||
1328 | $newFolderStatAfterExport = self::$backend->GetFolderStat(GSync::GetAdditionalSyncFolderStore($spa->GetBackendFolderId()), $spa->GetBackendFolderId()); |
||||||
1329 | if ($newFolderStat === $newFolderStatAfterExport) { |
||||||
1330 | $this->setFolderStat($spa, $newFolderStat); |
||||||
1331 | } |
||||||
1332 | else { |
||||||
1333 | SLog::Write(LOGLEVEL_DEBUG, "Sync() Folderstat differs after export, force another exporter run."); |
||||||
1334 | } |
||||||
1335 | } |
||||||
1336 | } |
||||||
1337 | else { |
||||||
1338 | self::$deviceManager->SetFolderSyncStatus($spa->GetFolderId(), DeviceManager::FLD_SYNC_INPROGRESS); |
||||||
1339 | } |
||||||
1340 | } |
||||||
1341 | |||||||
1342 | self::$encoder->endTag(); |
||||||
1343 | |||||||
1344 | // Save the sync state for the next time |
||||||
1345 | if ($spa->HasNewSyncKey()) { |
||||||
1346 | self::$topCollector->AnnounceInformation("Saving state"); |
||||||
1347 | |||||||
1348 | try { |
||||||
1349 | if ($exporter) { |
||||||
0 ignored issues
–
show
|
|||||||
1350 | $state = $exporter->GetState(); |
||||||
1351 | } |
||||||
1352 | |||||||
1353 | // nothing exported, but possibly imported - get the importer state |
||||||
1354 | elseif ($sc->GetParameter($spa, "state") !== null) { |
||||||
1355 | $state = $sc->GetParameter($spa, "state"); |
||||||
1356 | } |
||||||
1357 | |||||||
1358 | // if a new request without state information (hierarchy) save an empty state |
||||||
1359 | elseif (!$spa->HasSyncKey()) { |
||||||
1360 | $state = ""; |
||||||
1361 | } |
||||||
1362 | } |
||||||
1363 | catch (StatusException $stex) { |
||||||
1364 | $status = $stex->getCode(); |
||||||
1365 | } |
||||||
1366 | |||||||
1367 | if (isset($state) && $status == SYNC_STATUS_SUCCESS) { |
||||||
1368 | self::$deviceManager->GetStateManager()->SetSyncState($spa->GetNewSyncKey(), $state, $spa->GetFolderId()); |
||||||
1369 | } |
||||||
1370 | else { |
||||||
1371 | SLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): error saving '%s' - no state information available", $spa->GetNewSyncKey())); |
||||||
0 ignored issues
–
show
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
![]() |
|||||||
1372 | } |
||||||
1373 | } |
||||||
1374 | |||||||
1375 | // save SyncParameters |
||||||
1376 | if ($status == SYNC_STATUS_SUCCESS && empty($actiondata["fetchids"])) { |
||||||
1377 | $sc->SaveCollection($spa); |
||||||
1378 | } |
||||||
1379 | |||||||
1380 | return $status; |
||||||
1381 | } |
||||||
1382 | |||||||
1383 | /** |
||||||
1384 | * Loads the states and writes them into the SyncCollection Object and the actiondata failstate. |
||||||
1385 | * |
||||||
1386 | * @param SyncCollection $sc SyncCollection object |
||||||
1387 | * @param SyncParameters $spa SyncParameters object |
||||||
1388 | * @param array $actiondata Actiondata array |
||||||
1389 | * @param bool $loadFailsafe (opt) default false - indicates if the failsafe states should be loaded |
||||||
1390 | * |
||||||
1391 | * @return status indicating if there were errors. If no errors, status is SYNC_STATUS_SUCCESS |
||||||
1392 | */ |
||||||
1393 | private function loadStates($sc, $spa, &$actiondata, $loadFailsafe = false) { |
||||||
1394 | $status = SYNC_STATUS_SUCCESS; |
||||||
1395 | |||||||
1396 | if ($sc->GetParameter($spa, "state") == null) { |
||||||
1397 | SLog::Write(LOGLEVEL_DEBUG, sprintf("Sync->loadStates(): loading states for folder '%s'", $spa->GetFolderId())); |
||||||
1398 | |||||||
1399 | try { |
||||||
1400 | $sc->AddParameter($spa, "state", self::$deviceManager->GetStateManager()->GetSyncState($spa->GetSyncKey())); |
||||||
1401 | |||||||
1402 | if ($loadFailsafe) { |
||||||
1403 | // if this request was made before, there will be a failstate available |
||||||
1404 | $actiondata["failstate"] = self::$deviceManager->GetStateManager()->GetSyncFailState(); |
||||||
1405 | } |
||||||
1406 | |||||||
1407 | // if this is an additional folder the backend has to be setup correctly |
||||||
1408 | if (!self::$backend->Setup(GSync::GetAdditionalSyncFolderStore($spa->GetBackendFolderId()))) { |
||||||
1409 | throw new StatusException(sprintf("HandleSync() could not Setup() the backend for folder id %s/%s", $spa->GetFolderId(), $spa->GetBackendFolderId()), SYNC_STATUS_FOLDERHIERARCHYCHANGED); |
||||||
1410 | } |
||||||
1411 | } |
||||||
1412 | catch (StateNotFoundException $snfex) { |
||||||
1413 | $status = SYNC_STATUS_INVALIDSYNCKEY; |
||||||
1414 | self::$topCollector->AnnounceInformation("StateNotFoundException", $this->singleFolder); |
||||||
1415 | $this->saveMultiFolderInfo("exception", "StateNotFoundException"); |
||||||
1416 | } |
||||||
1417 | catch (StatusException $stex) { |
||||||
1418 | $status = $stex->getCode(); |
||||||
1419 | self::$topCollector->AnnounceInformation(sprintf("StatusException code: %d", $status), $this->singleFolder); |
||||||
1420 | $this->saveMultiFolderInfo("exception", "StateNotFoundException"); |
||||||
1421 | } |
||||||
1422 | } |
||||||
1423 | |||||||
1424 | return $status; |
||||||
0 ignored issues
–
show
|
|||||||
1425 | } |
||||||
1426 | |||||||
1427 | /** |
||||||
1428 | * Initializes the importer for the SyncParameters folder, loads necessary |
||||||
1429 | * states (incl. failsafe states) and initializes the conflict detection. |
||||||
1430 | * |
||||||
1431 | * @param SyncCollection $sc SyncCollection object |
||||||
1432 | * @param SyncParameters $spa SyncParameters object |
||||||
1433 | * @param array $actiondata Actiondata array |
||||||
1434 | * |
||||||
1435 | * @return status indicating if there were errors. If no errors, status is SYNC_STATUS_SUCCESS |
||||||
1436 | */ |
||||||
1437 | private function getImporter($sc, $spa, &$actiondata) { |
||||||
1438 | SLog::Write(LOGLEVEL_DEBUG, "Sync->getImporter(): initialize importer"); |
||||||
1439 | $status = SYNC_STATUS_SUCCESS; |
||||||
0 ignored issues
–
show
|
|||||||
1440 | |||||||
1441 | // load the states with failsafe data |
||||||
1442 | $status = $this->loadStates($sc, $spa, $actiondata, true); |
||||||
1443 | |||||||
1444 | try { |
||||||
1445 | if ($status == SYNC_STATUS_SUCCESS) { |
||||||
0 ignored issues
–
show
|
|||||||
1446 | // Configure importer with last state |
||||||
1447 | $this->importer = self::$backend->GetImporter($spa->GetBackendFolderId()); |
||||||
1448 | |||||||
1449 | // if something goes wrong, ask the mobile to resync the hierarchy |
||||||
1450 | if ($this->importer === false) { |
||||||
1451 | throw new StatusException(sprintf("Sync->getImporter(): no importer for folder id %s/%s", $spa->GetFolderId(), $spa->GetBackendFolderId()), SYNC_STATUS_FOLDERHIERARCHYCHANGED); |
||||||
1452 | } |
||||||
1453 | |||||||
1454 | // if there is a valid state obtained after importing changes in a previous loop, we use that state |
||||||
1455 | if (isset($actiondata["failstate"], $actiondata["failstate"]["failedsyncstate"])) { |
||||||
1456 | $this->importer->Config($actiondata["failstate"]["failedsyncstate"], $spa->GetConflict()); |
||||||
1457 | } |
||||||
1458 | else { |
||||||
1459 | $this->importer->Config($sc->GetParameter($spa, "state"), $spa->GetConflict()); |
||||||
1460 | } |
||||||
1461 | |||||||
1462 | // the CPO is also needed by the importer to check if imported changes are inside the sync window |
||||||
1463 | $this->importer->ConfigContentParameters($spa->GetCPO()); |
||||||
1464 | $this->importer->LoadConflicts($spa->GetCPO(), $sc->GetParameter($spa, "state")); |
||||||
1465 | } |
||||||
1466 | } |
||||||
1467 | catch (StatusException $stex) { |
||||||
1468 | $status = $stex->getCode(); |
||||||
1469 | } |
||||||
1470 | |||||||
1471 | return $status; |
||||||
1472 | } |
||||||
1473 | |||||||
1474 | /** |
||||||
1475 | * Imports a message. |
||||||
1476 | * |
||||||
1477 | * @param SyncParameters $spa SyncParameters object |
||||||
1478 | * @param array $actiondata Actiondata array |
||||||
1479 | * @param int $todo WBXML flag indicating how message should be imported. |
||||||
1480 | * Valid values: SYNC_ADD, SYNC_MODIFY, SYNC_REMOVE |
||||||
1481 | * @param SyncObject $message SyncObject message to be imported |
||||||
1482 | * @param string $clientid Client message identifier |
||||||
1483 | * @param string $serverid Server message identifier |
||||||
1484 | * @param string $foldertype On sms sync, this says "SMS", else false |
||||||
1485 | * @param int $messageCount Counter of already imported messages |
||||||
1486 | * |
||||||
1487 | * @return - message related status are returned in the actiondata |
||||||
0 ignored issues
–
show
|
|||||||
1488 | * |
||||||
1489 | * @throws StatusException in case the importer is not available |
||||||
1490 | */ |
||||||
1491 | private function importMessage($spa, &$actiondata, $todo, $message, $clientid, $serverid, $foldertype, $messageCount) { |
||||||
1492 | // the importer needs to be available! |
||||||
1493 | if ($this->importer == false) { |
||||||
1494 | throw new StatusException("Sync->importMessage(): importer not available", SYNC_STATUS_SERVERERROR); |
||||||
1495 | } |
||||||
1496 | |||||||
1497 | // mark this state as used, e.g. for HeartBeat |
||||||
1498 | self::$deviceManager->SetHeartbeatStateIntegrity($spa->GetFolderId(), $spa->GetUuid(), $spa->GetUuidCounter()); |
||||||
1499 | |||||||
1500 | // Detect incoming loop |
||||||
1501 | // messages which were created/removed before will not have the same action executed again |
||||||
1502 | // if a message is edited we perform this action "again", as the message could have been changed on the mobile in the meantime |
||||||
1503 | $ignoreMessage = false; |
||||||
1504 | if ($actiondata["failstate"]) { |
||||||
1505 | // message was ADDED before, do NOT add it again |
||||||
1506 | if ($todo == SYNC_ADD && isset($actiondata["failstate"]["clientids"][$clientid])) { |
||||||
1507 | $ignoreMessage = true; |
||||||
1508 | |||||||
1509 | // make sure no messages are sent back |
||||||
1510 | self::$deviceManager->SetWindowSize($spa->GetFolderId(), 0); |
||||||
1511 | |||||||
1512 | $actiondata["clientids"][$clientid] = $actiondata["failstate"]["clientids"][$clientid]; |
||||||
1513 | $actiondata["statusids"][$clientid] = $actiondata["failstate"]["statusids"][$clientid]; |
||||||
1514 | |||||||
1515 | SLog::Write(LOGLEVEL_INFO, sprintf("Mobile loop detected! Incoming new message '%s' was created on the server before. Replying with known new server id: %s", $clientid, $actiondata["clientids"][$clientid])); |
||||||
1516 | } |
||||||
1517 | |||||||
1518 | // message was REMOVED before, do NOT attempt to remove it again |
||||||
1519 | if ($todo == SYNC_REMOVE && isset($actiondata["failstate"]["removeids"][$serverid])) { |
||||||
1520 | $ignoreMessage = true; |
||||||
1521 | |||||||
1522 | // make sure no messages are sent back |
||||||
1523 | self::$deviceManager->SetWindowSize($spa->GetFolderId(), 0); |
||||||
1524 | |||||||
1525 | $actiondata["removeids"][$serverid] = $actiondata["failstate"]["removeids"][$serverid]; |
||||||
1526 | $actiondata["statusids"][$serverid] = $actiondata["failstate"]["statusids"][$serverid]; |
||||||
1527 | |||||||
1528 | SLog::Write(LOGLEVEL_INFO, sprintf("Mobile loop detected! Message '%s' was deleted by the mobile before. Replying with known status: %s", $clientid, $actiondata["statusids"][$serverid])); |
||||||
1529 | } |
||||||
1530 | } |
||||||
1531 | |||||||
1532 | if (!$ignoreMessage) { |
||||||
1533 | switch ($todo) { |
||||||
1534 | case SYNC_MODIFY: |
||||||
1535 | self::$topCollector->AnnounceInformation(sprintf("Saving modified message %d", $messageCount)); |
||||||
1536 | |||||||
1537 | try { |
||||||
1538 | // ignore sms messages |
||||||
1539 | if ($foldertype == "SMS" || stripos($serverid, self::GSYNCIGNORESMS) !== false) { |
||||||
1540 | SLog::Write(LOGLEVEL_DEBUG, "SMS sync are not supported. Ignoring message."); |
||||||
1541 | // TODO we should update the SMS |
||||||
1542 | $actiondata["statusids"][$serverid] = SYNC_STATUS_SUCCESS; |
||||||
1543 | } |
||||||
1544 | // check incoming message without logging WARN messages about errors |
||||||
1545 | elseif (!($message instanceof SyncObject) || !$message->Check(true)) { |
||||||
0 ignored issues
–
show
|
|||||||
1546 | $actiondata["statusids"][$serverid] = SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR; |
||||||
1547 | } |
||||||
1548 | else { |
||||||
1549 | // if there is just a read flag change, import it via ImportMessageReadFlag() |
||||||
1550 | if (isset($message->read) && !isset($message->flag) && $message->getCheckedParameters() < 3) { |
||||||
1551 | $response = $this->importer->ImportMessageReadFlag($serverid, $message->read); |
||||||
1552 | } |
||||||
1553 | else { |
||||||
1554 | $response = $this->importer->ImportMessageChange($serverid, $message); |
||||||
1555 | } |
||||||
1556 | |||||||
1557 | $response->serverid = $serverid; |
||||||
1558 | $actiondata["modifyids"][$serverid] = $response; |
||||||
1559 | $actiondata["statusids"][$serverid] = SYNC_STATUS_SUCCESS; |
||||||
1560 | } |
||||||
1561 | } |
||||||
1562 | catch (StatusException $stex) { |
||||||
1563 | $actiondata["statusids"][$serverid] = $stex->getCode(); |
||||||
1564 | } |
||||||
1565 | break; |
||||||
1566 | |||||||
1567 | case SYNC_ADD: |
||||||
1568 | self::$topCollector->AnnounceInformation(sprintf("Creating new message from mobile %d", $messageCount)); |
||||||
1569 | |||||||
1570 | try { |
||||||
1571 | // mark the message as new message so SyncObject->Check() can differentiate |
||||||
1572 | $message->flags = SYNC_NEWMESSAGE; |
||||||
1573 | |||||||
1574 | // ignore sms messages |
||||||
1575 | if ($foldertype == "SMS") { |
||||||
1576 | SLog::Write(LOGLEVEL_DEBUG, "SMS sync are not supported. Ignoring message."); |
||||||
1577 | // TODO we should create the SMS |
||||||
1578 | // return a fake serverid which we can identify later |
||||||
1579 | $actiondata["clientids"][$clientid] = self::GSYNCIGNORESMS . $clientid; |
||||||
1580 | $actiondata["statusids"][$clientid] = SYNC_STATUS_SUCCESS; |
||||||
1581 | } |
||||||
1582 | // check incoming message without logging WARN messages about errors |
||||||
1583 | elseif (!($message instanceof SyncObject) || !$message->Check(true)) { |
||||||
0 ignored issues
–
show
|
|||||||
1584 | $actiondata["clientids"][$clientid] = false; |
||||||
1585 | $actiondata["statusids"][$clientid] = SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR; |
||||||
1586 | } |
||||||
1587 | else { |
||||||
1588 | $actiondata["clientids"][$clientid] = false; |
||||||
1589 | $actiondata["clientids"][$clientid] = $this->importer->ImportMessageChange(false, $message); |
||||||
1590 | $actiondata["statusids"][$clientid] = SYNC_STATUS_SUCCESS; |
||||||
1591 | } |
||||||
1592 | } |
||||||
1593 | catch (StatusException $stex) { |
||||||
1594 | $actiondata["statusids"][$clientid] = $stex->getCode(); |
||||||
1595 | } |
||||||
1596 | break; |
||||||
1597 | |||||||
1598 | case SYNC_REMOVE: |
||||||
1599 | self::$topCollector->AnnounceInformation(sprintf("Deleting message removed on mobile %d", $messageCount)); |
||||||
1600 | |||||||
1601 | try { |
||||||
1602 | $actiondata["removeids"][] = $serverid; |
||||||
1603 | // ignore sms messages |
||||||
1604 | if ($foldertype == "SMS" || stripos($serverid, self::GSYNCIGNORESMS) !== false) { |
||||||
1605 | SLog::Write(LOGLEVEL_DEBUG, "SMS sync are not supported. Ignoring message."); |
||||||
1606 | // TODO we should delete the SMS |
||||||
1607 | $actiondata["statusids"][$serverid] = SYNC_STATUS_SUCCESS; |
||||||
1608 | } |
||||||
1609 | else { |
||||||
1610 | // if message deletions are to be moved, move them |
||||||
1611 | if ($spa->GetDeletesAsMoves()) { |
||||||
0 ignored issues
–
show
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
![]() |
|||||||
1612 | $folderid = self::$backend->GetWasteBasket(); |
||||||
1613 | |||||||
1614 | if ($folderid) { |
||||||
1615 | $actiondata["statusids"][$serverid] = SYNC_STATUS_SUCCESS; |
||||||
1616 | $this->importer->ImportMessageMove($serverid, $folderid); |
||||||
1617 | |||||||
1618 | break; |
||||||
1619 | } |
||||||
1620 | |||||||
1621 | SLog::Write(LOGLEVEL_WARN, "Message should be moved to WasteBasket, but the Backend did not return a destination ID. Message is hard deleted now!"); |
||||||
1622 | } |
||||||
1623 | |||||||
1624 | $this->importer->ImportMessageDeletion($serverid); |
||||||
1625 | $actiondata["statusids"][$serverid] = SYNC_STATUS_SUCCESS; |
||||||
1626 | } |
||||||
1627 | } |
||||||
1628 | catch (StatusException $stex) { |
||||||
1629 | if ($stex->getCode() != SYNC_MOVEITEMSSTATUS_SUCCESS) { |
||||||
1630 | $actiondata["statusids"][$serverid] = SYNC_STATUS_OBJECTNOTFOUND; |
||||||
1631 | } |
||||||
1632 | } |
||||||
1633 | break; |
||||||
1634 | } |
||||||
1635 | SLog::Write(LOGLEVEL_DEBUG, "Sync->importMessage(): message imported"); |
||||||
1636 | } |
||||||
1637 | } |
||||||
1638 | |||||||
1639 | /** |
||||||
1640 | * Keeps some interesting information about the sync process of several folders. |
||||||
1641 | * |
||||||
1642 | * @param mixed $key |
||||||
1643 | * @param mixed $value |
||||||
1644 | */ |
||||||
1645 | private function saveMultiFolderInfo($key, $value) { |
||||||
1646 | if ($key == "incoming" || $key == "outgoing" || $key == "queued" || $key == "fetching") { |
||||||
1647 | if (!isset($this->multiFolderInfo[$key])) { |
||||||
1648 | $this->multiFolderInfo[$key] = 0; |
||||||
1649 | } |
||||||
1650 | $this->multiFolderInfo[$key] += $value; |
||||||
1651 | } |
||||||
1652 | if ($key == "exception") { |
||||||
1653 | if (!isset($this->multiFolderInfo[$key])) { |
||||||
1654 | $this->multiFolderInfo[$key] = []; |
||||||
1655 | } |
||||||
1656 | $this->multiFolderInfo[$key][] = $value; |
||||||
1657 | } |
||||||
1658 | } |
||||||
1659 | |||||||
1660 | /** |
||||||
1661 | * Returns a single string with information about the multi folder synchronization. |
||||||
1662 | * |
||||||
1663 | * @param int $amountOfFolders |
||||||
1664 | * |
||||||
1665 | * @return string |
||||||
1666 | */ |
||||||
1667 | private function getMultiFolderInfoLine($amountOfFolders) { |
||||||
1668 | $s = $amountOfFolders . " folders"; |
||||||
1669 | if (isset($this->multiFolderInfo["incoming"])) { |
||||||
1670 | $s .= ": " . $this->multiFolderInfo["incoming"] . " saved"; |
||||||
1671 | } |
||||||
1672 | if (isset($this->multiFolderInfo["outgoing"], $this->multiFolderInfo["queued"]) && $this->multiFolderInfo["outgoing"] > 0) { |
||||||
1673 | $s .= sprintf(": Streamed %d out of %d", $this->multiFolderInfo["outgoing"], $this->multiFolderInfo["queued"]); |
||||||
1674 | } |
||||||
1675 | elseif (!isset($this->multiFolderInfo["outgoing"]) && !isset($this->multiFolderInfo["queued"])) { |
||||||
1676 | $s .= ": no changes"; |
||||||
1677 | } |
||||||
1678 | else { |
||||||
1679 | if (isset($this->multiFolderInfo["outgoing"])) { |
||||||
1680 | $s .= "/" . $this->multiFolderInfo["outgoing"] . " streamed"; |
||||||
1681 | } |
||||||
1682 | if (isset($this->multiFolderInfo["queued"])) { |
||||||
1683 | $s .= "/" . $this->multiFolderInfo["queued"] . " queued"; |
||||||
1684 | } |
||||||
1685 | } |
||||||
1686 | if (isset($this->multiFolderInfo["exception"])) { |
||||||
1687 | $exceptions = array_count_values($this->multiFolderInfo["exception"]); |
||||||
1688 | foreach ($exceptions as $name => $count) { |
||||||
1689 | $s .= sprintf("-%s(%d)", $name, $count); |
||||||
1690 | } |
||||||
1691 | } |
||||||
1692 | |||||||
1693 | return $s; |
||||||
1694 | } |
||||||
1695 | |||||||
1696 | /** |
||||||
1697 | * Sets the new folderstat and calculates & sets an expiration date for the folder stat. |
||||||
1698 | * |
||||||
1699 | * @param SyncParameters $spa |
||||||
1700 | * @param string $newFolderStat |
||||||
1701 | */ |
||||||
1702 | private function setFolderStat($spa, $newFolderStat) { |
||||||
1703 | $spa->SetFolderStat($newFolderStat); |
||||||
0 ignored issues
–
show
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
![]() |
|||||||
1704 | $maxTimeout = 60 * 60 * 24 * 31; // one month |
||||||
1705 | |||||||
1706 | $interval = Utils::GetFiltertypeInterval($spa->GetFilterType()); |
||||||
0 ignored issues
–
show
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
![]() |
|||||||
1707 | $timeout = time() + (($interval && $interval < $maxTimeout) ? $interval : $maxTimeout); |
||||||
1708 | // randomize timeout in 12h |
||||||
1709 | $timeout -= rand(0, 43200); |
||||||
1710 | SLog::Write(LOGLEVEL_DEBUG, sprintf("Sync()->setFolderStat() on %s: %s expiring %s", $spa->getFolderId(), $newFolderStat, date('Y-m-d H:i:s', $timeout))); |
||||||
0 ignored issues
–
show
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
![]() |
|||||||
1711 | $spa->SetFolderStatTimeout($timeout); |
||||||
0 ignored issues
–
show
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
![]() |
|||||||
1712 | } |
||||||
1713 | } |
||||||
1714 |