Ping::Handle()   F
last analyzed

Complexity

Conditions 47
Paths > 20000

Size

Total Lines 236
Code Lines 140

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 47
eloc 140
c 0
b 0
f 0
nc 58200
nop 1
dl 0
loc 236
rs 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 PING command
9
 */
10
11
class Ping extends RequestProcessor {
12
	/**
13
	 * Handles the Ping command.
14
	 *
15
	 * @param int $commandCode
16
	 *
17
	 * @return bool
18
	 */
19
	public function Handle($commandCode) {
20
		$interval = (defined('PING_INTERVAL') && PING_INTERVAL > 0) ? PING_INTERVAL : 30;
21
		$pingstatus = false;
22
		$fakechanges = [];
23
		$foundchanges = false;
24
25
		// Contains all requested folders (containers)
26
		$sc = new SyncCollections();
27
28
		// read from stream to see if the symc params are being sent
29
		$params_present = self::$decoder->getElementStartTag(SYNC_PING_PING);
30
31
		// Load all collections - do load states, check permissions and allow unconfirmed states
32
		try {
33
			$sc->LoadAllCollections(true, true, true, true, false);
34
		}
35
		catch (StateInvalidException) {
36
			// if no params are present, indicate to send params, else do hierarchy sync
37
			if (!$params_present) {
38
				$pingstatus = SYNC_PINGSTATUS_FAILINGPARAMS;
39
				self::$topCollector->AnnounceInformation("StateInvalidException: require PingParameters", true);
40
			}
41
			elseif (self::$deviceManager->IsHierarchySyncRequired()) {
42
				// we could be in a looping  - see LoopDetection->ProcessLoopDetectionIsHierarchySyncAdvised()
43
				$pingstatus = SYNC_PINGSTATUS_FOLDERHIERSYNCREQUIRED;
44
				self::$topCollector->AnnounceInformation("Potential loop detection: require HierarchySync", true);
45
			}
46
			else {
47
				// we do not have a ping status for this, but SyncCollections should have generated fake changes for the folders which are broken
48
				$fakechanges = $sc->GetChangedFolderIds();
49
				$foundchanges = true;
50
51
				self::$topCollector->AnnounceInformation("StateInvalidException: force sync", true);
52
			}
53
		}
54
		catch (StatusException) {
55
			$pingstatus = SYNC_PINGSTATUS_FOLDERHIERSYNCREQUIRED;
56
			self::$topCollector->AnnounceInformation("StatusException: require HierarchySync", true);
57
		}
58
59
		SLog::Write(LOGLEVEL_DEBUG, sprintf("HandlePing(): reference PolicyKey for PING: %s", $sc->GetReferencePolicyKey()));
60
61
		// receive PING initialization data
62
		if ($params_present) {
63
			self::$topCollector->AnnounceInformation("Processing PING data");
64
			SLog::Write(LOGLEVEL_DEBUG, "HandlePing(): initialization data received");
65
66
			if (self::$decoder->getElementStartTag(SYNC_PING_LIFETIME)) {
67
				$sc->SetLifetime(self::$decoder->getElementContent());
68
				self::$decoder->getElementEndTag();
69
			}
70
71
			if (($el = self::$decoder->getElementStartTag(SYNC_PING_FOLDERS)) && $el[EN_FLAGS] & EN_FLAGS_CONTENT) {
72
				// cache requested (pingable) folderids
73
				$pingable = [];
74
75
				while (self::$decoder->getElementStartTag(SYNC_PING_FOLDER)) {
76
					WBXMLDecoder::ResetInWhile("pingFolder");
77
					while (WBXMLDecoder::InWhile("pingFolder")) {
78
						if (self::$decoder->getElementStartTag(SYNC_PING_SERVERENTRYID)) {
79
							$folderid = self::$decoder->getElementContent();
80
							self::$decoder->getElementEndTag();
81
						}
82
						if (self::$decoder->getElementStartTag(SYNC_PING_FOLDERTYPE)) {
83
							$class = self::$decoder->getElementContent();
84
							self::$decoder->getElementEndTag();
85
						}
86
87
						$e = self::$decoder->peek();
88
						if ($e[EN_TYPE] == EN_TYPE_ENDTAG) {
89
							self::$decoder->getElementEndTag();
90
91
							break;
92
						}
93
					}
94
95
					$spa = $sc->GetCollection($folderid);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $folderid does not seem to be defined for all execution paths leading up to this point.
Loading history...
96
					if (!$spa) {
97
						// The requested collection is not synchronized.
98
						// check if the HierarchyCache is available, if not, trigger a HierarchySync
99
						try {
100
							self::$deviceManager->GetFolderClassFromCacheByID($folderid);
101
							// ignore all folders with SYNC_FOLDER_TYPE_UNKNOWN
102
							if (self::$deviceManager->GetFolderTypeFromCacheById($folderid) == SYNC_FOLDER_TYPE_UNKNOWN) {
103
								SLog::Write(LOGLEVEL_DEBUG, sprintf("HandlePing(): ignoring folder id '%s' as it's of type UNKNOWN ", $folderid));
104
105
								continue;
106
							}
107
						}
108
						catch (NoHierarchyCacheAvailableException) {
109
							SLog::Write(LOGLEVEL_INFO, sprintf("HandlePing(): unknown collection '%s', triggering HierarchySync", $folderid));
110
							$pingstatus = SYNC_PINGSTATUS_FOLDERHIERSYNCREQUIRED;
111
						}
112
113
						// Trigger a Sync request because then the device will be forced to resync this folder.
114
						$fakechanges[$folderid] = 1;
115
						$foundchanges = true;
116
					}
117
					elseif ($class == $spa->GetContentClass()) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $class does not seem to be defined for all execution paths leading up to this point.
Loading history...
Bug introduced by
The method GetContentClass() does not exist on SyncParameters. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

117
					elseif ($class == $spa->/** @scrutinizer ignore-call */ GetContentClass()) {
Loading history...
118
						$pingable[] = $folderid;
119
						SLog::Write(LOGLEVEL_DEBUG, sprintf("HandlePing(): using saved sync state for '%s' id '%s'", $spa->GetContentClass(), $folderid));
120
					}
121
				}
122
				if (!self::$decoder->getElementEndTag()) {
123
					return false;
124
				}
125
126
				// update pingable flags
127
				foreach ($sc as $folderid => $spa) {
128
					// if the folderid is in $pingable, we should ping it, else remove the flag
129
					if (in_array($folderid, $pingable)) {
130
						$spa->SetPingableFlag(true);
131
					}
132
					else {
133
						$spa->DelPingableFlag();
134
					}
135
				}
136
			}
137
			if (!self::$decoder->getElementEndTag()) {
138
				return false;
139
			}
140
141
			if (!$this->lifetimeBetweenBound($sc->GetLifetime())) {
142
				$pingstatus = SYNC_PINGSTATUS_HBOUTOFRANGE;
143
				SLog::Write(LOGLEVEL_DEBUG, sprintf("HandlePing(): ping lifetime not between bound (higher bound:'%d' lower bound:'%d' current lifetime:'%d'. Returning SYNC_PINGSTATUS_HBOUTOFRANGE.", PING_HIGHER_BOUND_LIFETIME, PING_LOWER_BOUND_LIFETIME, $sc->GetLifetime()));
0 ignored issues
show
Bug introduced by
PING_HIGHER_BOUND_LIFETIME of type false is incompatible with the type double|integer|string expected by parameter $values of sprintf(). ( Ignorable by Annotation )

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

143
				SLog::Write(LOGLEVEL_DEBUG, sprintf("HandlePing(): ping lifetime not between bound (higher bound:'%d' lower bound:'%d' current lifetime:'%d'. Returning SYNC_PINGSTATUS_HBOUTOFRANGE.", /** @scrutinizer ignore-type */ PING_HIGHER_BOUND_LIFETIME, PING_LOWER_BOUND_LIFETIME, $sc->GetLifetime()));
Loading history...
144
			}
145
			// save changed data
146
			foreach ($sc as $folderid => $spa) {
147
				$sc->SaveCollection($spa);
148
			}
149
		} // END SYNC_PING_PING
150
		else {
151
			// if no ping initialization data was sent, we check if we have pingable folders
152
			// if not, we indicate that there is nothing to do.
153
			if (!$sc->PingableFolders()) {
154
				$pingstatus = SYNC_PINGSTATUS_FAILINGPARAMS;
155
				SLog::Write(LOGLEVEL_DEBUG, "HandlePing(): no pingable folders found and no initialization data sent. Returning SYNC_PINGSTATUS_FAILINGPARAMS.");
156
			}
157
			elseif (!$this->lifetimeBetweenBound($sc->GetLifetime())) {
158
				$pingstatus = SYNC_PINGSTATUS_FAILINGPARAMS;
159
				SLog::Write(LOGLEVEL_DEBUG, sprintf("HandlePing(): ping lifetime not between bound (higher bound:'%d' lower bound:'%d' current lifetime:'%d'. Returning SYNC_PINGSTATUS_FAILINGPARAMS.", PING_HIGHER_BOUND_LIFETIME, PING_LOWER_BOUND_LIFETIME, $sc->GetLifetime()));
160
			}
161
		}
162
163
		// Check for changes on the default LifeTime, set interval and ONLY on pingable collections
164
		try {
165
			if (!$pingstatus && empty($fakechanges)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $pingstatus of type false|integer is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
166
				self::$deviceManager->DoAutomaticASDeviceSaving(false);
167
				$foundchanges = $sc->CheckForChanges($sc->GetLifetime(), $interval, true);
168
			}
169
		}
170
		catch (StatusException $ste) {
171
			switch ($ste->getCode()) {
172
				case SyncCollections::ERROR_NO_COLLECTIONS:
173
					$pingstatus = SYNC_PINGSTATUS_FAILINGPARAMS;
174
					break;
175
176
				case SyncCollections::ERROR_WRONG_HIERARCHY:
177
					$pingstatus = SYNC_PINGSTATUS_FOLDERHIERSYNCREQUIRED;
178
					self::$deviceManager->AnnounceProcessStatus(false, $pingstatus);
179
					break;
180
181
				case SyncCollections::OBSOLETE_CONNECTION:
182
					$foundchanges = false;
183
					break;
184
185
				case SyncCollections::HIERARCHY_CHANGED:
186
					$pingstatus = SYNC_PINGSTATUS_FOLDERHIERSYNCREQUIRED;
187
					break;
188
			}
189
		}
190
191
		self::$encoder->StartWBXML();
192
		self::$encoder->startTag(SYNC_PING_PING);
193
194
		self::$encoder->startTag(SYNC_PING_STATUS);
195
		if ($pingstatus) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $pingstatus of type false|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
196
			self::$encoder->content($pingstatus);
197
		}
198
		else {
199
			self::$encoder->content($foundchanges ? SYNC_PINGSTATUS_CHANGES : SYNC_PINGSTATUS_HBEXPIRED);
200
		}
201
		self::$encoder->endTag();
202
203
		if (!$pingstatus) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $pingstatus of type false|integer is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
204
			self::$encoder->startTag(SYNC_PING_FOLDERS);
205
206
			if (empty($fakechanges)) {
207
				$changes = $sc->GetChangedFolderIds();
208
			}
209
			else {
210
				$changes = $fakechanges;
211
			}
212
213
			$announceAggregated = false;
214
			if (count($changes) > 1) {
215
				$announceAggregated = 0;
216
			}
217
			foreach ($changes as $folderid => $changecount) {
218
				if ($changecount > 0) {
219
					self::$encoder->startTag(SYNC_PING_FOLDER);
220
					self::$encoder->content($folderid);
221
					self::$encoder->endTag();
222
					if ($announceAggregated === false) {
223
						if (empty($fakechanges)) {
224
							self::$topCollector->AnnounceInformation(sprintf("Found change in %s", $sc->GetCollection($folderid)->GetContentClass()), true);
225
						}
226
					}
227
					else {
228
						$announceAggregated += $changecount;
229
					}
230
					self::$deviceManager->AnnounceProcessStatus($folderid, SYNC_PINGSTATUS_CHANGES);
231
				}
232
			}
233
			if ($announceAggregated !== false) {
234
				self::$topCollector->AnnounceInformation(sprintf("Found %d changes in %d folders", $announceAggregated, count($changes)), true);
235
			}
236
			self::$encoder->endTag();
237
		}
238
		elseif ($pingstatus == SYNC_PINGSTATUS_HBOUTOFRANGE) {
239
			self::$encoder->startTag(SYNC_PING_LIFETIME);
240
			if ($sc->GetLifetime() > PING_HIGHER_BOUND_LIFETIME) {
241
				self::$encoder->content(PING_HIGHER_BOUND_LIFETIME);
242
			}
243
			else {
244
				self::$encoder->content(PING_LOWER_BOUND_LIFETIME);
245
			}
246
			self::$encoder->endTag();
247
		}
248
249
		self::$encoder->endTag();
250
251
		// update the waittime waited
252
		self::$waitTime = $sc->GetWaitedSeconds();
253
254
		return true;
255
	}
256
257
	/**
258
	 * Return true if the ping lifetime is between the specified bound (PING_HIGHER_BOUND_LIFETIME and PING_LOWER_BOUND_LIFETIME). If no bound are specified, it returns true.
259
	 *
260
	 * @param int $lifetime
261
	 *
262
	 * @return bool
263
	 */
264
	private function lifetimeBetweenBound($lifetime) {
265
		if (PING_HIGHER_BOUND_LIFETIME !== false && PING_LOWER_BOUND_LIFETIME !== false) {
0 ignored issues
show
introduced by
The condition PING_HIGHER_BOUND_LIFETIME !== false is always false.
Loading history...
266
			return $lifetime <= PING_HIGHER_BOUND_LIFETIME && $lifetime >= PING_LOWER_BOUND_LIFETIME;
267
		}
268
		if (PING_HIGHER_BOUND_LIFETIME !== false) {
0 ignored issues
show
introduced by
The condition PING_HIGHER_BOUND_LIFETIME !== false is always false.
Loading history...
269
			return $lifetime <= PING_HIGHER_BOUND_LIFETIME;
270
		}
271
		if (PING_LOWER_BOUND_LIFETIME !== false) {
0 ignored issues
show
introduced by
The condition PING_LOWER_BOUND_LIFETIME !== false is always false.
Loading history...
272
			return $lifetime >= PING_LOWER_BOUND_LIFETIME;
273
		}
274
275
		return true;
276
	}
277
}
278