SyncObject   F
last analyzed

Complexity

Total Complexity 110

Size/Duplication

Total Lines 487
Duplicated Lines 0 %

Importance

Changes 3
Bugs 1 Features 1
Metric Value
eloc 213
c 3
b 1
f 1
dl 0
loc 487
rs 2
wmc 110

11 Methods

Rating   Name   Duplication   Size   Complexity  
A SupportsPrivateStripping() 0 2 1
F Check() 0 208 62
A GetNameFromPropertyValue() 0 6 2
A getCheckedParameters() 0 2 1
A __construct() 0 5 1
A postUnserialize() 0 2 1
A getUnsetVars() 0 2 1
C emptySupported() 0 33 12
C equals() 0 66 16
A StripData() 0 6 3
B __toString() 0 37 10

How to fix   Complexity   

Complex Class

Complex classes like SyncObject 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 SyncObject, 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-2022 grommunio GmbH
6
 *
7
 * Defines general behavior of sub-WBXML entities (Sync* objects) that can be
8
 * parsed directly (as a stream) from WBXML. They are automatically decoded
9
 * according to $mapping by the Streamer and the Sync WBXML mappings.
10
 */
11
12
abstract class SyncObject extends Streamer {
13
	public const STREAMER_CHECKS = 6;
14
	public const STREAMER_CHECK_REQUIRED = 7;
15
	public const STREAMER_CHECK_ZEROORONE = 8;
16
	public const STREAMER_CHECK_NOTALLOWED = 9;
17
	public const STREAMER_CHECK_ONEVALUEOF = 10;
18
	public const STREAMER_CHECK_SETZERO = "setToValue0";
19
	public const STREAMER_CHECK_SETONE = "setToValue1";
20
	public const STREAMER_CHECK_SETTWO = "setToValue2";
21
	public const STREAMER_CHECK_SETEMPTY = "setToValueEmpty";
22
	public const STREAMER_CHECK_CMPLOWER = 13;
23
	public const STREAMER_CHECK_CMPHIGHER = 14;
24
	public const STREAMER_CHECK_LENGTHMAX = 15;
25
	public const STREAMER_CHECK_EMAIL = 16;
26
27
	protected $unsetVars;
28
	protected $supportsPrivateStripping;
29
	protected $checkedParameters;
30
31
	public function __construct($mapping) {
32
		$this->unsetVars = [];
33
		$this->supportsPrivateStripping = false;
34
		$this->checkedParameters = false;
35
		parent::__construct($mapping);
36
	}
37
38
	/**
39
	 * Sets all supported but not transmitted variables
40
	 * of this SyncObject to an "empty" value, so they are deleted when being saved.
41
	 *
42
	 * @param array $supportedFields array with all supported fields, if available
43
	 *
44
	 * @return bool
45
	 */
46
	public function emptySupported($supportedFields) {
47
		// Some devices do not send supported tag. In such a case remove all not set properties.
48
		if ($supportedFields === false || !is_array($supportedFields) || (empty($supportedFields))) {
0 ignored issues
show
introduced by
The condition is_array($supportedFields) is always true.
Loading history...
49
			if (defined('UNSET_UNDEFINED_PROPERTIES') &&
50
					UNSET_UNDEFINED_PROPERTIES &&
51
					(
52
						$this instanceof SyncContact ||
53
						$this instanceof SyncAppointment ||
54
						$this instanceof SyncTask
55
					)) {
56
				SLog::Write(LOGLEVEL_INFO, sprintf("%s->emptySupported(): no supported list available, emptying all not set parameters", get_class($this)));
57
				$supportedFields = array_keys($this->mapping);
58
			}
59
			else {
60
				return false;
61
			}
62
		}
63
64
		foreach ($supportedFields as $field) {
65
			if (!isset($this->mapping[$field])) {
66
				SLog::Write(LOGLEVEL_WARN, sprintf("Field '%s' is supposed to be emptied but is not defined for '%s'", $field, get_class($this)));
67
68
				continue;
69
			}
70
			$var = $this->mapping[$field][self::STREAMER_VAR];
71
			// add var to $this->unsetVars if $var is not set
72
			if (!isset($this->{$var})) {
73
				$this->unsetVars[] = $var;
74
			}
75
		}
76
		SLog::Write(LOGLEVEL_DEBUG, sprintf("Supported variables to be unset: %s", implode(',', $this->unsetVars)));
77
78
		return true;
79
	}
80
81
	/**
82
	 * Compares this a SyncObject to another.
83
	 * In case that all available mapped fields are exactly EQUAL, it returns true.
84
	 *
85
	 * @see SyncObject
86
	 *
87
	 * @param SyncObject $odo               other SyncObject
88
	 * @param bool       $log               flag to turn on logging
89
	 * @param bool       $strictTypeCompare to enforce type matching
90
	 *
91
	 * @return bool
92
	 */
93
	public function equals($odo, $log = false, $strictTypeCompare = false) {
0 ignored issues
show
Unused Code introduced by
The parameter $log is not used and could be removed. ( Ignorable by Annotation )

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

93
	public function equals($odo, /** @scrutinizer ignore-unused */ $log = false, $strictTypeCompare = false) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
94
		if ($odo === false) {
0 ignored issues
show
introduced by
The condition $odo === false is always false.
Loading history...
95
			return false;
96
		}
97
98
		// check objecttype
99
		if (!$odo instanceof SyncObject) {
0 ignored issues
show
introduced by
$odo is always a sub-type of SyncObject.
Loading history...
100
			SLog::Write(LOGLEVEL_DEBUG, "SyncObject->equals() the target object is not a SyncObject");
101
102
			return false;
103
		}
104
105
		// check for mapped fields
106
		foreach ($this->mapping as $v) {
107
			$val = $v[self::STREAMER_VAR];
108
			// array of values?
109
			if (isset($v[self::STREAMER_ARRAY])) {
110
				// if neither array is created then don't fail the comparison
111
				if (!isset($this->{$val}) && !isset($odo->{$val})) {
112
					SLog::Write(LOGLEVEL_DEBUG, sprintf("SyncObject->equals() array '%s' is NOT SET in either object", $val));
113
114
					continue;
115
				}
116
				if (is_array($this->{$val}) && is_array($odo->{$val})) {
117
					// if both arrays exist then seek for differences in the arrays
118
					if (count(array_diff($this->{$val}, $odo->{$val})) + count(array_diff($odo->{$val}, $this->{$val})) > 0) {
119
						SLog::Write(LOGLEVEL_DEBUG, sprintf("SyncObject->equals() items in array '%s' differ", $val));
120
121
						return false;
122
					}
123
				}
124
				else {
125
					SLog::Write(LOGLEVEL_DEBUG, sprintf("SyncObject->equals() array '%s' is set in one but not the other object", $val));
126
127
					return false;
128
				}
129
			}
130
			else {
131
				if (isset($this->{$val}, $odo->{$val})) {
132
					if ($strictTypeCompare) {
133
						if ($this->{$val} !== $odo->{$val}) {
134
							SLog::Write(LOGLEVEL_DEBUG, sprintf("SyncObject->equals() false on field '%s': '%s' != '%s' using strictTypeCompare", $val, Utils::PrintAsString($this->{$val}), Utils::PrintAsString($odo->{$val})));
135
136
							return false;
137
						}
138
					}
139
					else {
140
						if ($this->{$val} != $odo->{$val}) {
141
							SLog::Write(LOGLEVEL_DEBUG, sprintf("SyncObject->equals() false on field '%s': '%s' != '%s'", $val, Utils::PrintAsString($this->{$val}), Utils::PrintAsString($odo->{$val})));
142
143
							return false;
144
						}
145
					}
146
				}
147
				elseif (!isset($this->{$val}) && !isset($odo->{$val})) {
148
					continue;
149
				}
150
				else {
151
					SLog::Write(LOGLEVEL_DEBUG, sprintf("SyncObject->equals() false because field '%s' is only defined at one obj: '%s' != '%s'", $val, Utils::PrintAsString(isset($this->{$val})), Utils::PrintAsString(isset($odo->{$val}))));
152
153
					return false;
154
				}
155
			}
156
		}
157
158
		return true;
159
	}
160
161
	/**
162
	 * String representation of the object.
163
	 *
164
	 * @return string
165
	 */
166
	public function __toString() {
167
		$str = get_class($this) . " (\n";
168
169
		$streamerVars = [];
170
		foreach ($this->mapping as $k => $v) {
171
			$streamerVars[$v[self::STREAMER_VAR]] = (isset($v[self::STREAMER_TYPE])) ? $v[self::STREAMER_TYPE] : false;
172
		}
173
174
		foreach (get_object_vars($this) as $k => $v) {
175
			if ($k == "mapping") {
176
				continue;
177
			}
178
179
			if (array_key_exists($k, $streamerVars)) {
180
				$strV = "(S) ";
181
			}
182
			else {
183
				$strV = "";
184
			}
185
186
			// self::STREAMER_ARRAY ?
187
			if (is_array($v)) {
188
				$str .= "\t" . $strV . $k . "(Array) size: " . count($v) . "\n";
189
				foreach ($v as $value) {
190
					$str .= "\t\t" . Utils::PrintAsString($value) . "\n";
191
				}
192
			}
193
			elseif ($v instanceof SyncObject) {
194
				$str .= "\t" . $strV . $k . " => " . str_replace("\n", "\n\t\t\t", $v->__toString()) . "\n";
195
			}
196
			else {
197
				$str .= "\t" . $strV . $k . " => " . (isset($this->{$k}) ? Utils::PrintAsString($this->{$k}) : "null") . "\n";
198
			}
199
		}
200
		$str .= ")";
201
202
		return $str;
203
	}
204
205
	/**
206
	 * Returns the properties which have to be unset on the server.
207
	 *
208
	 * @return array
209
	 */
210
	public function getUnsetVars() {
211
		return $this->unsetVars;
212
	}
213
214
	/**
215
	 * Removes not necessary data from the object.
216
	 *
217
	 * @param mixed $flags
218
	 *
219
	 * @return bool
220
	 */
221
	public function StripData($flags = 0) {
222
		if ($flags === 0 && isset($this->unsetVars)) {
223
			unset($this->unsetVars);
224
		}
225
226
		return parent::StripData($flags);
227
	}
228
229
	/**
230
	 * Indicates if a SyncObject supports the private flag and stripping of private data.
231
	 * If an object does not support it, it will not be sent to the client but permanently be excluded from the sync.
232
	 *
233
	 * @return bool - default false defined in constructor - overwritten by implementation
234
	 */
235
	public function SupportsPrivateStripping() {
236
		return $this->supportsPrivateStripping;
237
	}
238
239
	/**
240
	 * Indicates the amount of parameters that were set before Checks were executed and potentially set other parameters.
241
	 *
242
	 * @return bool/int	- returns false if Check() was not executed
0 ignored issues
show
Documentation Bug introduced by
The doc comment bool/int at position 0 could not be parsed: Unknown type name 'bool/int' at position 0 in bool/int.
Loading history...
243
	 */
244
	public function getCheckedParameters() {
245
		return $this->checkedParameters;
246
	}
247
248
	/**
249
	 * Method checks if the object has the minimum of required parameters
250
	 * and fulfills semantic dependencies.
251
	 *
252
	 * General checks:
253
	 *     STREAMER_CHECK_REQUIRED      may have as value false (do not fix, ignore object!) or set-to-values: STREAMER_CHECK_SETZERO/ONE/TWO, STREAMER_CHECK_SETEMPTY
254
	 *     STREAMER_CHECK_ZEROORONE     may be 0 or 1, if none of these, set-to-values: STREAMER_CHECK_SETZERO or STREAMER_CHECK_SETONE
255
	 *     STREAMER_CHECK_NOTALLOWED    fails if is set
256
	 *     STREAMER_CHECK_ONEVALUEOF    expects an array with accepted values, fails if value is not in array
257
	 *
258
	 * Comparison:
259
	 *     STREAMER_CHECK_CMPLOWER      compares if the current parameter is lower as a literal or another parameter of the same object
260
	 *     STREAMER_CHECK_CMPHIGHER     compares if the current parameter is higher as a literal or another parameter of the same object
261
	 *
262
	 * @param bool $logAsDebug (opt) default is false, so messages are logged in WARN log level
263
	 *
264
	 * @return bool
265
	 */
266
	public function Check($logAsDebug = false) {
267
		// semantic checks general "turn off switch"
268
		if (defined("DO_SEMANTIC_CHECKS") && DO_SEMANTIC_CHECKS === false) {
0 ignored issues
show
Bug introduced by
The constant DO_SEMANTIC_CHECKS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
269
			SLog::Write(LOGLEVEL_DEBUG, "SyncObject->Check(): semantic checks disabled. Check your config for 'DO_SEMANTIC_CHECKS'.");
270
271
			return true;
272
		}
273
274
		$defaultLogLevel = LOGLEVEL_WARN;
275
		$this->checkedParameters = 0;
276
277
		// in some cases non-false checks should not provoke a WARN log but only a DEBUG log
278
		if ($logAsDebug) {
279
			$defaultLogLevel = LOGLEVEL_DEBUG;
280
		}
281
282
		$objClass = get_class($this);
283
		foreach ($this->mapping as $k => $v) {
284
			// check sub-objects recursively
285
			if (isset($v[self::STREAMER_TYPE], $this->{$v[self::STREAMER_VAR]})) {
286
				if ($this->{$v[self::STREAMER_VAR]} instanceof SyncObject) {
287
					if (!$this->{$v[self::STREAMER_VAR]}->Check($logAsDebug)) {
288
						return false;
289
					}
290
				}
291
				elseif (is_array($this->{$v[self::STREAMER_VAR]})) {
292
					foreach ($this->{$v[self::STREAMER_VAR]} as $subobj) {
293
						if ($subobj instanceof SyncObject && !$subobj->Check($logAsDebug)) {
294
							return false;
295
						}
296
					}
297
				}
298
			}
299
300
			if (isset($v[self::STREAMER_CHECKS])) {
301
				foreach ($v[self::STREAMER_CHECKS] as $rule => $condition) {
302
					// count parameter if it's set
303
					if (isset($this->{$v[self::STREAMER_VAR]})) {
304
						++$this->checkedParameters;
305
					}
306
307
					// check REQUIRED settings
308
					if ($rule === self::STREAMER_CHECK_REQUIRED && (!isset($this->{$v[self::STREAMER_VAR]}) || $this->{$v[self::STREAMER_VAR]} === '')) {
309
						// parameter is not set but ..
310
						// requested to set to 0
311
						if ($condition === self::STREAMER_CHECK_SETZERO) {
312
							$this->{$v[self::STREAMER_VAR]} = 0;
313
							SLog::Write($defaultLogLevel, sprintf("SyncObject->Check(): Fixed object from type %s: parameter '%s' is set to 0", $objClass, $v[self::STREAMER_VAR]));
314
						}
315
						// requested to be set to 1
316
						elseif ($condition === self::STREAMER_CHECK_SETONE) {
317
							$this->{$v[self::STREAMER_VAR]} = 1;
318
							SLog::Write($defaultLogLevel, sprintf("SyncObject->Check(): Fixed object from type %s: parameter '%s' is set to 1", $objClass, $v[self::STREAMER_VAR]));
319
						}
320
						// requested to be set to 2
321
						elseif ($condition === self::STREAMER_CHECK_SETTWO) {
322
							$this->{$v[self::STREAMER_VAR]} = 2;
323
							SLog::Write($defaultLogLevel, sprintf("SyncObject->Check(): Fixed object from type %s: parameter '%s' is set to 2", $objClass, $v[self::STREAMER_VAR]));
324
						}
325
						// requested to be set to ''
326
						elseif ($condition === self::STREAMER_CHECK_SETEMPTY) {
327
							if (!isset($this->{$v[self::STREAMER_VAR]})) {
328
								$this->{$v[self::STREAMER_VAR]} = '';
329
								SLog::Write($defaultLogLevel, sprintf("SyncObject->Check(): Fixed object from type %s: parameter '%s' is set to ''", $objClass, $v[self::STREAMER_VAR]));
330
							}
331
						}
332
						// there is another value !== false
333
						elseif ($condition !== false) {
334
							$this->{$v[self::STREAMER_VAR]} = $condition;
335
							SLog::Write($defaultLogLevel, sprintf("SyncObject->Check(): Fixed object from type %s: parameter '%s' is set to '%s'", $objClass, $v[self::STREAMER_VAR], $condition));
336
						}
337
						// no fix available!
338
						else {
339
							SLog::Write($defaultLogLevel, sprintf("SyncObject->Check(): Unmet condition in object from type %s: parameter '%s' is required but not set. Check failed!", $objClass, $v[self::STREAMER_VAR]));
340
341
							return false;
342
						}
343
					} // end STREAMER_CHECK_REQUIRED
344
345
					// check STREAMER_CHECK_ZEROORONE
346
					if ($rule === self::STREAMER_CHECK_ZEROORONE && isset($this->{$v[self::STREAMER_VAR]})) {
347
						if ($this->{$v[self::STREAMER_VAR]} != 0 && $this->{$v[self::STREAMER_VAR]} != 1) {
348
							$newval = $condition === self::STREAMER_CHECK_SETZERO ? 0 : 1;
349
							$this->{$v[self::STREAMER_VAR]} = $newval;
350
							SLog::Write($defaultLogLevel, sprintf("SyncObject->Check(): Fixed object from type %s: parameter '%s' is set to '%s' as it was not 0 or 1", $objClass, $v[self::STREAMER_VAR], $newval));
351
						}
352
					}// end STREAMER_CHECK_ZEROORONE
353
354
					// check STREAMER_CHECK_ONEVALUEOF
355
					if ($rule === self::STREAMER_CHECK_ONEVALUEOF && isset($this->{$v[self::STREAMER_VAR]})) {
356
						if (!in_array($this->{$v[self::STREAMER_VAR]}, $condition)) {
357
							SLog::Write($defaultLogLevel, sprintf("SyncObject->Check(): object from type %s: parameter '%s'->'%s' is not in the range of allowed values.", $objClass, $v[self::STREAMER_VAR], $this->{$v[self::STREAMER_VAR]}));
358
359
							return false;
360
						}
361
					}// end STREAMER_CHECK_ONEVALUEOF
362
363
					// Check value compared to other value or literal
364
					if ($rule === self::STREAMER_CHECK_CMPHIGHER || $rule === self::STREAMER_CHECK_CMPLOWER) {
365
						if (isset($this->{$v[self::STREAMER_VAR]})) {
366
							$cmp = false;
367
							// directly compare against literals
368
							if (is_int($condition)) {
369
								$cmp = $condition;
370
							}
371
							// check for invalid compare-to
372
							elseif (!isset($this->mapping[$condition])) {
373
								SLog::Write(LOGLEVEL_ERROR, sprintf("SyncObject->Check(): Can not compare parameter '%s' against the other value '%s' as it is not defined object from type %s. Please report this! Check skipped!", $objClass, $v[self::STREAMER_VAR], $condition));
374
375
								continue;
376
							}
377
							else {
378
								$cmpPar = $this->mapping[$condition][self::STREAMER_VAR];
379
								if (isset($this->{$cmpPar})) {
380
									$cmp = $this->{$cmpPar};
381
								}
382
							}
383
384
							if ($cmp === false) {
385
								SLog::Write(LOGLEVEL_WARN, sprintf("SyncObject->Check(): Unmet condition in object from type %s: parameter '%s' can not be compared, as the comparable is not set. Check failed!", $objClass, $v[self::STREAMER_VAR]));
386
387
								return false;
388
							}
389
							if (
390
								($rule == self::STREAMER_CHECK_CMPHIGHER && intval($this->{$v[self::STREAMER_VAR]}) < $cmp) ||
391
								($rule == self::STREAMER_CHECK_CMPLOWER && intval($this->{$v[self::STREAMER_VAR]}) > $cmp)
392
							) {
393
								SLog::Write(LOGLEVEL_WARN, sprintf(
394
									"SyncObject->Check(): Unmet condition in object from type %s: parameter '%s' is %s than '%s'. Check failed!",
395
									$objClass,
396
									$v[self::STREAMER_VAR],
397
									($rule === self::STREAMER_CHECK_CMPHIGHER) ? 'LOWER' : 'HIGHER',
398
									isset($cmpPar) ? $cmpPar : $condition
399
								));
400
401
								return false;
402
							}
403
						}
404
					} // STREAMER_CHECK_CMP*
405
406
					// check STREAMER_CHECK_LENGTHMAX
407
					if ($rule === self::STREAMER_CHECK_LENGTHMAX && isset($this->{$v[self::STREAMER_VAR]})) {
408
						if (is_array($this->{$v[self::STREAMER_VAR]})) {
409
							// implosion takes 2bytes, so we just assume ", " here
410
							$chkstr = implode(", ", $this->{$v[self::STREAMER_VAR]});
411
						}
412
						else {
413
							$chkstr = $this->{$v[self::STREAMER_VAR]};
414
						}
415
416
						if (strlen($chkstr) > $condition) {
417
							SLog::Write(LOGLEVEL_WARN, sprintf("SyncObject->Check(): object from type %s: parameter '%s' is longer than %d. Check failed", $objClass, $v[self::STREAMER_VAR], $condition));
418
419
							return false;
420
						}
421
					}// end STREAMER_CHECK_LENGTHMAX
422
423
					// check STREAMER_CHECK_EMAIL
424
					// if $condition is false then the check really fails. Otherwise invalid emails are removed.
425
					// if nothing is left (all emails were false), the parameter is set to condition
426
					if ($rule === self::STREAMER_CHECK_EMAIL && isset($this->{$v[self::STREAMER_VAR]})) {
427
						if ($condition === false && ((is_array($this->{$v[self::STREAMER_VAR]}) && empty($this->{$v[self::STREAMER_VAR]})) || strlen($this->{$v[self::STREAMER_VAR]}) == 0)) {
428
							continue;
429
						}
430
431
						$as_array = false;
432
433
						if (is_array($this->{$v[self::STREAMER_VAR]})) {
434
							$mails = $this->{$v[self::STREAMER_VAR]};
435
							$as_array = true;
436
						}
437
						else {
438
							$mails = [$this->{$v[self::STREAMER_VAR]}];
439
						}
440
441
						$output = [];
442
						foreach ($mails as $mail) {
443
							if (!Utils::CheckEmail($mail)) {
444
								SLog::Write(LOGLEVEL_WARN, sprintf("SyncObject->Check(): object from type %s: parameter '%s' contains an invalid email address '%s'. Address is removed.", $objClass, $v[self::STREAMER_VAR], $mail));
445
							}
446
							else {
447
								$output[] = $mail;
448
							}
449
						}
450
						if (count($mails) != count($output)) {
451
							if ($condition === false) {
452
								return false;
453
							}
454
455
							// nothing left, use $condition as new value
456
							if (count($output) == 0) {
457
								$output[] = $condition;
458
							}
459
460
							// if we are allowed to rewrite the attribute, we do that
461
							if ($as_array) {
462
								$this->{$v[self::STREAMER_VAR]} = $output;
463
							}
464
							else {
465
								$this->{$v[self::STREAMER_VAR]} = $output[0];
466
							}
467
						}
468
					}// end STREAMER_CHECK_EMAIL
469
				} // foreach CHECKS
470
			} // isset CHECKS
471
		} // foreach mapping
472
473
		return true;
474
	}
475
476
	/**
477
	 * Returns human friendly property name from its value if a mapping is available.
478
	 *
479
	 * @param array $v
480
	 * @param mixed $val
481
	 *
482
	 * @return mixed
483
	 */
484
	public function GetNameFromPropertyValue($v, $val) {
485
		if (isset($v[self::STREAMER_VALUEMAP][$val])) {
486
			return $v[self::STREAMER_VALUEMAP][$val];
487
		}
488
489
		return $val;
490
	}
491
492
	/**
493
	 * Called after the SyncObject was unserialized.
494
	 *
495
	 * @return bool
496
	 */
497
	public function postUnserialize() {
498
		return true;
499
	}
500
}
501