FolderSync   B
last analyzed

Complexity

Total Complexity 43

Size/Duplication

Total Lines 250
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 127
c 0
b 0
f 0
dl 0
loc 250
rs 8.96
wmc 43

1 Method

Rating   Name   Duplication   Size   Complexity  
F Handle() 0 242 43

How to fix   Complexity   

Complex Class

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

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

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

1
<?php
2
3
/*
4
 * SPDX-License-Identifier: AGPL-3.0-only
5
 * SPDX-FileCopyrightText: Copyright 2007-2016 Zarafa Deutschland GmbH
6
 * SPDX-FileCopyrightText: Copyright 2020-2024 grommunio GmbH
7
 *
8
 * Provides the FOLDERSYNC command
9
 */
10
11
class FolderSync extends RequestProcessor {
12
	/**
13
	 * Handles the FolderSync command.
14
	 *
15
	 * @param int $commandCode
16
	 *
17
	 * @return bool
18
	 */
19
	public function Handle($commandCode) {
20
		// Parse input
21
		if (!self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_FOLDERSYNC)) {
22
			return false;
23
		}
24
25
		if (!self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_SYNCKEY)) {
26
			return false;
27
		}
28
29
		$synckey = self::$decoder->getElementContent();
30
31
		if (!self::$decoder->getElementEndTag()) {
32
			return false;
33
		}
34
35
		// every FolderSync with SyncKey 0 should return the supported AS version & command headers
36
		if ($synckey == "0") {
37
			self::$specialHeaders = [];
38
			self::$specialHeaders[] = GSync::GetSupportedProtocolVersions();
39
			self::$specialHeaders[] = GSync::GetSupportedCommands();
40
		}
41
42
		$status = SYNC_FSSTATUS_SUCCESS;
43
		$newsynckey = $synckey;
44
45
		try {
46
			$syncstate = self::$deviceManager->GetStateManager()->GetSyncState($synckey);
47
48
			// We will be saving the sync state under 'newsynckey'
49
			$newsynckey = self::$deviceManager->GetStateManager()->GetNewSyncKey($synckey);
50
51
			// there are no SyncParameters for the hierarchy, but we use it to save the latest synckeys
52
			$spa = self::$deviceManager->GetStateManager()->GetSynchedFolderState(false);
53
		}
54
		catch (StateInvalidException|StateNotFoundException) {
55
			$status = SYNC_FSSTATUS_SYNCKEYERROR;
56
		}
57
58
		// The ChangesWrapper caches all imports in-memory, so we can send a change count
59
		// before sending the actual data.
60
		// the HierarchyCache is notified and the changes from the PIM are transmitted to the actual backend
61
		$changesMem = self::$deviceManager->GetHierarchyChangesWrapper();
62
63
		// the hierarchyCache should now fully be initialized - check for changes in the additional folders
64
		$changesMem->Config(GSync::GetAdditionalSyncFolders(false), ChangesMemoryWrapper::SYNCHRONIZING);
65
66
		// reset to default store in backend
67
		self::$backend->Setup(false);
68
69
		// process incoming changes
70
		if (self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_CHANGES)) {
71
			// Ignore <Count> if present
72
			if (self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_COUNT)) {
73
				self::$decoder->getElementContent();
74
				if (!self::$decoder->getElementEndTag()) {
75
					return false;
76
				}
77
			}
78
79
			// Process the changes (either <Add>, <Modify>, or <Remove>)
80
			$element = self::$decoder->getElement();
81
82
			if ($element[EN_TYPE] != EN_TYPE_STARTTAG) {
83
				return false;
84
			}
85
86
			$importer = false;
87
			WBXMLDecoder::ResetInWhile("folderSyncIncomingChange");
88
			while (WBXMLDecoder::InWhile("folderSyncIncomingChange")) {
89
				$folder = new SyncFolder();
90
				if (!$folder->Decode(self::$decoder)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $folder->Decode(self::decoder) of type false|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
91
					break;
92
				}
93
94
				// add the backendId to the SyncFolder object
95
				$folder->BackendId = self::$deviceManager->GetBackendIdForFolderId($folder->serverid);
96
97
				try {
98
					if ($status == SYNC_FSSTATUS_SUCCESS && !$importer) {
99
						// Configure the backends importer with last state
100
						$importer = self::$backend->GetImporter();
101
						$importer->Config($syncstate);
102
						// the messages from the PIM will be forwarded to the backend
103
						$changesMem->forwardImporter($importer);
104
					}
105
106
					if ($status == SYNC_FSSTATUS_SUCCESS) {
107
						switch ($element[EN_TAG]) {
108
							case SYNC_ADD:
109
							case SYNC_MODIFY:
110
								$serverid = $changesMem->ImportFolderChange($folder);
0 ignored issues
show
Unused Code introduced by
The assignment to $serverid is dead and can be removed.
Loading history...
111
								break;
112
113
							case SYNC_REMOVE:
114
								$serverid = $changesMem->ImportFolderDeletion($folder);
115
								break;
116
						}
117
					}
118
					else {
119
						SLog::Write(LOGLEVEL_WARN, sprintf("Request->HandleFolderSync(): ignoring incoming folderchange for folder '%s' as status indicates problem.", $folder->displayname));
120
						self::$topCollector->AnnounceInformation("Incoming change ignored", true);
121
					}
122
				}
123
				catch (StatusException $stex) {
124
					$status = $stex->getCode();
125
				}
126
			}
127
128
			if (!self::$decoder->getElementEndTag()) {
129
				return false;
130
			}
131
		}
132
		// no incoming changes
133
		else {
134
			// check for a potential process loop
135
			if ($synckey != "0" && self::$deviceManager->IsHierarchyFullResyncRequired()) {
136
				$status = SYNC_FSSTATUS_SYNCKEYERROR;
137
				self::$deviceManager->AnnounceProcessStatus(false, $status);
138
			}
139
		}
140
141
		if (!self::$decoder->getElementEndTag()) {
142
			return false;
143
		}
144
145
		// We have processed incoming foldersync requests, now send the PIM
146
		// our changes
147
148
		// Output our WBXML reply now
149
		self::$encoder->StartWBXML();
150
151
		self::$encoder->startTag(SYNC_FOLDERHIERARCHY_FOLDERSYNC);
152
153
		if ($status == SYNC_FSSTATUS_SUCCESS) {
154
			try {
155
				// do nothing if this is an invalid device id (like the 'validate' Androids internal client sends)
156
				if (!Request::IsValidDeviceID()) {
157
					throw new StatusException(sprintf("Request::IsValidDeviceID() indicated that '%s' is not a valid device id", Request::GetDeviceID()), SYNC_FSSTATUS_SERVERERROR);
158
				}
159
160
				// Changes from backend are sent to the MemImporter and processed for the HierarchyCache.
161
				// The state which is saved is from the backend, as the MemImporter is only a proxy.
162
				$exporter = self::$backend->GetExporter();
163
164
				$exporter->Config($syncstate);
165
				$exporter->InitializeExporter($changesMem);
166
167
				// Stream all changes to the ImportExportChangesMem
168
				$totalChanges = $exporter->GetChangeCount();
169
				$exported = 0;
170
				$partial = false;
171
				while (is_array($exporter->Synchronize())) {
172
					++$exported;
173
174
					if (time() % 4) {
175
						self::$topCollector->AnnounceInformation(sprintf("Exported %d from %d folders", $exported, $totalChanges));
176
					}
177
178
					// if partial sync is allowed, stop if this takes too long
179
					if (USE_PARTIAL_FOLDERSYNC && Request::IsRequestTimeoutReached()) {
180
						SLog::Write(LOGLEVEL_WARN, sprintf("Request->HandleFolderSync(): Exporting folders is too slow. In %d seconds only %d from %d changes were processed.", time() - $_SERVER["REQUEST_TIME"], $exported, $totalChanges));
181
						self::$topCollector->AnnounceInformation(sprintf("Partial export of %d out of %d folders", $exported, $totalChanges), true);
182
						self::$deviceManager->SetFolderSyncComplete(false);
183
						$partial = true;
184
185
						break;
186
					}
187
				}
188
189
				// update the foldersync complete flag
190
				if (USE_PARTIAL_FOLDERSYNC && $partial === false && self::$deviceManager->GetFolderSyncComplete() === false) {
191
					// say that we are done with partial syncing
192
					self::$deviceManager->SetFolderSyncComplete(true);
193
					// reset the loop data to prevent any loop detection to kick in now
194
					self::$deviceManager->ClearLoopDetectionData(Request::GetAuthUserString(), Request::GetDeviceID());
195
					SLog::Write(LOGLEVEL_INFO, "Request->HandleFolderSync(): Chunked exporting of folders completed successfully");
196
				}
197
198
				// get the new state from the backend
199
				$newsyncstate = (isset($exporter)) ? $exporter->GetState() : "";
200
			}
201
			catch (StatusException $stex) {
202
				if ($stex->getCode() == SYNC_FSSTATUS_CODEUNKNOWN) {
203
					$status = SYNC_FSSTATUS_SYNCKEYERROR;
204
				}
205
				else {
206
					$status = $stex->getCode();
207
				}
208
			}
209
		}
210
211
		self::$encoder->startTag(SYNC_FOLDERHIERARCHY_STATUS);
212
		self::$encoder->content($status);
213
		self::$encoder->endTag();
214
215
		if ($status == SYNC_FSSTATUS_SUCCESS) {
216
			self::$encoder->startTag(SYNC_FOLDERHIERARCHY_SYNCKEY);
217
			$synckey = ($changesMem->IsStateChanged()) ? $newsynckey : $synckey;
218
			self::$encoder->content($synckey);
219
			self::$encoder->endTag();
220
221
			// Stream folders directly to the PDA
222
			$streamimporter = new ImportChangesStream(self::$encoder, false);
0 ignored issues
show
Bug introduced by
false of type false is incompatible with the type SyncObject expected by parameter $objclass of ImportChangesStream::__construct(). ( Ignorable by Annotation )

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

222
			$streamimporter = new ImportChangesStream(self::$encoder, /** @scrutinizer ignore-type */ false);
Loading history...
223
			$changesMem->InitializeExporter($streamimporter);
224
			$changeCount = $changesMem->GetChangeCount();
225
226
			self::$encoder->startTag(SYNC_FOLDERHIERARCHY_CHANGES);
227
228
			self::$encoder->startTag(SYNC_FOLDERHIERARCHY_COUNT);
229
			self::$encoder->content($changeCount);
230
			self::$encoder->endTag();
231
			while ($changesMem->Synchronize());
232
233
			self::$encoder->endTag();
234
			self::$topCollector->AnnounceInformation(sprintf("Outgoing %d folders", $changeCount), true);
235
236
			if ($changeCount == 0) {
237
				self::$deviceManager->CheckFolderData();
238
			}
239
			// everything fine, save the sync state for the next time
240
			if ($synckey == $newsynckey) {
241
				self::$deviceManager->GetStateManager()->SetSyncState($newsynckey, $newsyncstate);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $newsyncstate does not seem to be defined for all execution paths leading up to this point.
Loading history...
242
243
				// update SPA & save it
244
				$spa->SetSyncKey($newsynckey);
245
				$spa->SetFolderId(false);
246
247
				// invalidate all pingable flags
248
				SyncCollections::InvalidatePingableFlags();
249
			}
250
			// save the SyncParameters if it changed or the reference policy key is not set or different
251
			if ($spa->IsDataChanged() || !$spa->HasReferencePolicyKey() || GSync::GetProvisioningManager()->ProvisioningRequired($spa->GetReferencePolicyKey(), true, false)) {
252
				// saves the SPA (while updating the reference policy key)
253
				$spa->SetLastSynctime(time());
254
				self::$deviceManager->GetStateManager()->SetSynchedFolderState($spa);
255
			}
256
		}
257
258
		self::$encoder->endTag();
259
260
		return true;
261
	}
262
}
263