Ping   C
last analyzed

Complexity

Total Complexity 53

Size/Duplication

Total Lines 265
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 148
c 0
b 0
f 0
dl 0
loc 265
rs 6.96
wmc 53

2 Methods

Rating   Name   Duplication   Size   Complexity  
A lifetimeBetweenBound() 0 12 6
F Handle() 0 236 47

How to fix   Complexity   

Complex Class

Complex classes like Ping 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 Ping, and based on these observations, apply Extract Interface, too.

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

116
					elseif ($class == $spa->/** @scrutinizer ignore-call */ GetContentClass()) {
Loading history...
117
						$pingable[] = $folderid;
118
						SLog::Write(LOGLEVEL_DEBUG, sprintf("HandlePing(): using saved sync state for '%s' id '%s'", $spa->GetContentClass(), $folderid));
119
					}
120
				}
121
				if (!self::$decoder->getElementEndTag()) {
122
					return false;
123
				}
124
125
				// update pingable flags
126
				foreach ($sc as $folderid => $spa) {
127
					// if the folderid is in $pingable, we should ping it, else remove the flag
128
					if (in_array($folderid, $pingable)) {
129
						$spa->SetPingableFlag(true);
130
					}
131
					else {
132
						$spa->DelPingableFlag();
133
					}
134
				}
135
			}
136
			if (!self::$decoder->getElementEndTag()) {
137
				return false;
138
			}
139
140
			if (!$this->lifetimeBetweenBound($sc->GetLifetime())) {
141
				$pingstatus = SYNC_PINGSTATUS_HBOUTOFRANGE;
142
				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

142
				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...
143
			}
144
			// save changed data
145
			foreach ($sc as $folderid => $spa) {
146
				$sc->SaveCollection($spa);
147
			}
148
		} // END SYNC_PING_PING
149
		else {
150
			// if no ping initialization data was sent, we check if we have pingable folders
151
			// if not, we indicate that there is nothing to do.
152
			if (!$sc->PingableFolders()) {
153
				$pingstatus = SYNC_PINGSTATUS_FAILINGPARAMS;
154
				SLog::Write(LOGLEVEL_DEBUG, "HandlePing(): no pingable folders found and no initialization data sent. Returning SYNC_PINGSTATUS_FAILINGPARAMS.");
155
			}
156
			elseif (!$this->lifetimeBetweenBound($sc->GetLifetime())) {
157
				$pingstatus = SYNC_PINGSTATUS_FAILINGPARAMS;
158
				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()));
159
			}
160
		}
161
162
		// Check for changes on the default LifeTime, set interval and ONLY on pingable collections
163
		try {
164
			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...
165
				self::$deviceManager->DoAutomaticASDeviceSaving(false);
166
				$foundchanges = $sc->CheckForChanges($sc->GetLifetime(), $interval, true);
167
			}
168
		}
169
		catch (StatusException $ste) {
170
			switch ($ste->getCode()) {
171
				case SyncCollections::ERROR_NO_COLLECTIONS:
172
					$pingstatus = SYNC_PINGSTATUS_FAILINGPARAMS;
173
					break;
174
175
				case SyncCollections::ERROR_WRONG_HIERARCHY:
176
					$pingstatus = SYNC_PINGSTATUS_FOLDERHIERSYNCREQUIRED;
177
					self::$deviceManager->AnnounceProcessStatus(false, $pingstatus);
178
					break;
179
180
				case SyncCollections::OBSOLETE_CONNECTION:
181
					$foundchanges = false;
182
					break;
183
184
				case SyncCollections::HIERARCHY_CHANGED:
185
					$pingstatus = SYNC_PINGSTATUS_FOLDERHIERSYNCREQUIRED;
186
					break;
187
			}
188
		}
189
190
		self::$encoder->StartWBXML();
191
		self::$encoder->startTag(SYNC_PING_PING);
192
193
		self::$encoder->startTag(SYNC_PING_STATUS);
194
		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...
195
			self::$encoder->content($pingstatus);
196
		}
197
		else {
198
			self::$encoder->content($foundchanges ? SYNC_PINGSTATUS_CHANGES : SYNC_PINGSTATUS_HBEXPIRED);
199
		}
200
		self::$encoder->endTag();
201
202
		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...
203
			self::$encoder->startTag(SYNC_PING_FOLDERS);
204
205
			if (empty($fakechanges)) {
206
				$changes = $sc->GetChangedFolderIds();
207
			}
208
			else {
209
				$changes = $fakechanges;
210
			}
211
212
			$announceAggregated = false;
213
			if (count($changes) > 1) {
214
				$announceAggregated = 0;
215
			}
216
			foreach ($changes as $folderid => $changecount) {
217
				if ($changecount > 0) {
218
					self::$encoder->startTag(SYNC_PING_FOLDER);
219
					self::$encoder->content($folderid);
220
					self::$encoder->endTag();
221
					if ($announceAggregated === false) {
222
						if (empty($fakechanges)) {
223
							self::$topCollector->AnnounceInformation(sprintf("Found change in %s", $sc->GetCollection($folderid)->GetContentClass()), true);
224
						}
225
					}
226
					else {
227
						$announceAggregated += $changecount;
228
					}
229
					self::$deviceManager->AnnounceProcessStatus($folderid, SYNC_PINGSTATUS_CHANGES);
230
				}
231
			}
232
			if ($announceAggregated !== false) {
233
				self::$topCollector->AnnounceInformation(sprintf("Found %d changes in %d folders", $announceAggregated, count($changes)), true);
234
			}
235
			self::$encoder->endTag();
236
		}
237
		elseif ($pingstatus == SYNC_PINGSTATUS_HBOUTOFRANGE) {
238
			self::$encoder->startTag(SYNC_PING_LIFETIME);
239
			if ($sc->GetLifetime() > PING_HIGHER_BOUND_LIFETIME) {
240
				self::$encoder->content(PING_HIGHER_BOUND_LIFETIME);
241
			}
242
			else {
243
				self::$encoder->content(PING_LOWER_BOUND_LIFETIME);
244
			}
245
			self::$encoder->endTag();
246
		}
247
248
		self::$encoder->endTag();
249
250
		// update the waittime waited
251
		self::$waitTime = $sc->GetWaitedSeconds();
252
253
		return true;
254
	}
255
256
	/**
257
	 * 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.
258
	 *
259
	 * @param int $lifetime
260
	 *
261
	 * @return bool
262
	 */
263
	private function lifetimeBetweenBound($lifetime) {
264
		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...
265
			return $lifetime <= PING_HIGHER_BOUND_LIFETIME && $lifetime >= PING_LOWER_BOUND_LIFETIME;
266
		}
267
		if (PING_HIGHER_BOUND_LIFETIME !== false) {
0 ignored issues
show
introduced by
The condition PING_HIGHER_BOUND_LIFETIME !== false is always false.
Loading history...
268
			return $lifetime <= PING_HIGHER_BOUND_LIFETIME;
269
		}
270
		if (PING_LOWER_BOUND_LIFETIME !== false) {
0 ignored issues
show
introduced by
The condition PING_LOWER_BOUND_LIFETIME !== false is always false.
Loading history...
271
			return $lifetime >= PING_LOWER_BOUND_LIFETIME;
272
		}
273
274
		return true;
275
	}
276
}
277