Completed
Push — fix_broken_js_unit_test-d6ae96... ( d6ae96 )
by Thomas
21:39
created

Object   C

Complexity

Total Complexity 56

Size/Duplication

Total Lines 457
Duplicated Lines 8.53 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 6
Bugs 1 Features 0
Metric Value
wmc 56
c 6
b 1
f 0
lcom 1
cbo 5
dl 39
loc 457
rs 6.5957

29 Methods

Rating   Name   Duplication   Size   Complexity  
A fromVObject() 0 5 1
A setCalendar() 0 3 1
A getCalendar() 0 3 1
A getCalendarid() 0 3 1
A setUri() 0 3 1
A getUri() 0 3 1
A setEtag() 0 3 1
A getEtag() 0 7 3
A setRuds() 0 7 2
B getRuds() 0 14 5
B setVObject() 0 25 4
B getVObject() 0 29 4
A getUpdatedFields() 0 19 2
A touch() 0 7 1
A doesAllow() 0 3 1
A getCalendarData() 0 3 1
A setCalendarData() 0 12 3
A generateEtag() 0 6 1
A getType() 0 5 1
A getStartDate() 10 10 3
A getEndDate() 10 10 3
A getRepeating() 0 7 2
A getSummary() 9 9 2
A getLastModified() 10 10 2
A isInTimeRange() 0 4 1
A getObjectName() 0 3 1
A registerTypes() 0 10 1
A registerMandatory() 0 5 1
B isValid() 0 15 5

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Object 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 Object, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * ownCloud - Calendar App
4
 *
5
 * @author Georg Ehrke
6
 * @copyright 2014 Georg Ehrke <[email protected]>
7
 *
8
 * This library is free software; you can redistribute it and/or
9
 * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
10
 * License as published by the Free Software Foundation; either
11
 * version 3 of the License, or any later version.
12
 *
13
 * This library is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
17
 *
18
 * You should have received a copy of the GNU Affero General Public
19
 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
20
 *
21
 */
22
namespace OCA\Calendar\Db;
23
24
use OCA\Calendar\CorruptDataException;
25
use OCA\Calendar\ICalendar;
26
use OCA\Calendar\IObject;
27
use Sabre\VObject\Component\VCalendar;
28
use Sabre\VObject\Reader;
29
use Sabre\VObject\ParseException;
30
use Sabre\VObject\Property\Text as TextProperty;
31
use Sabre\VObject\Property\Integer as IntegerProperty;
32
use Sabre\VObject\Property\ICalendar\DateTime as SDateTime;
33
use Sabre\VObject\Property\ICalendar\Date as SDate;
34
use OCA\Calendar\Utility\SabreUtility;
35
use DateTime;
36
37
class Object extends Entity implements IObject {
0 ignored issues
show
Bug introduced by
There is at least one abstract method in this class. Maybe declare it as abstract, or implement the remaining methods: columnToProperty, fromParams, fromRow, propertyToColumn, resetUpdatedFields, slugify
Loading history...
38
39
	/**
40
	 * @var integer
41
	 */
42
	public $id;
43
44
45
	/**
46
	 * @var ICalendar
47
	 */
48
	public $calendar;
49
50
51
	/**
52
	 * @var string
53
	 */
54
	public $uri;
55
56
57
	/**
58
	 * @var string
59
	 */
60
	public $etag;
61
62
63
	/**
64
	 * @var integer
65
	 */
66
	public $ruds;
67
68
69
	/**
70
	 * @var VCalendar
71
	 */
72
	public $vObject;
73
74
75
	/**
76
	 * @var string
77
	 */
78
	private $objectName;
79
80
81
	/**
82
	 * take data from vobject and put into this Object object
83
	 * @param VCalendar $vcalendar
84
	 * @throws CorruptDataException
85
	 * @return $this
86
	 */
87
	public static function fromVObject(VCalendar $vcalendar) {
88
		/** @var Object $instance */
89
		$instance = new static();
90
		return $instance->setVObject($vcalendar, true);
91
	}
92
93
94
	/**
95
	 * @param ICalendar $calendar
96
	 * @return $this
97
	 */
98
	public function setCalendar(ICalendar $calendar) {
99
		return $this->setter('calendar', [$calendar]);
100
	}
101
102
103
	/**
104
	 * @return ICalendar
105
	 */
106
	public function getCalendar() {
107
		return $this->getter('calendar');
108
	}
109
110
	public function getCalendarid() {
111
		return $this->getCalendar()->getId();
112
	}
113
114
115
	/**
116
	 * @param string $uri
117
	 * @return $this
118
	 */
119
	public function setUri($uri) {
120
		return $this->setter('uri', [$uri]);
121
	}
122
123
124
	/**
125
	 * @return string
126
	 */
127
	public function getUri() {
128
		return $this->getter('uri');
129
	}
130
131
132
	/**
133
	 * @param string $etag
134
	 * @return $this
135
	 */
136
	public function setEtag($etag) {
137
		return $this->setter('etag', [$etag]);
138
	}
139
140
141
	/**
142
	 * @param bool $force generate etag if none stored
143
	 * @return mixed (string|null)
144
	 */
145
	public function getEtag($force=false) {
146
		if ($force && $this->etag === null) {
147
			$this->generateEtag();
148
		}
149
150
		return $this->getter('etag');
151
	}
152
153
154
	/**
155
	 * @param integer $ruds
156
	 * @return $this
157
	 */
158
	public function setRuds($ruds) {
159
		if ($ruds & Permissions::CREATE) {
160
			$ruds -= Permissions::CREATE;
161
		}
162
163
		$this->setter('ruds', [$ruds]);
164
	}
165
166
167
	/**
168
	 * @param boolean $force return value all the time
169
	 * @return mixed (integer|null)
170
	 */
171
	public function getRuds($force=false) {
172
		if ($force && $this->ruds === null) {
173
			if ($this->calendar instanceof ICalendar) {
174
				$cruds = $this->calendar->getCruds();
175
				if ($cruds & Permissions::CREATE) {
176
					return $cruds - Permissions::CREATE;
177
				} else {
178
					return $cruds;
179
				}
180
			}
181
		}
182
183
		return $this->getter('ruds');
184
	}
185
186
187
	/**
188
	 * @param VCalendar $vobject
189
	 * @param boolean $autoAssignUri
190
	 * @throws CorruptDataException
191
	 * @return $this
192
	 */
193
	public function setVObject(VCalendar $vobject, $autoAssignUri=false) {
194
		$uidCount = SabreUtility::countUniqueUIDs($vobject);
195
		$objectName = SabreUtility::getObjectName($vobject);
196
197
		if ($uidCount === 0) {
198
			throw new CorruptDataException(
199
				'Object may not be empty!'
200
			);
201
		}
202
		if ($uidCount > 1) {
203
			throw new CorruptDataException(
204
				'Resource can\'t store multiple objects'
205
			);
206
		}
207
208
		$this->setter('vObject', [$vobject]);
209
		$this->objectName = $objectName;
0 ignored issues
show
Documentation Bug introduced by
It seems like $objectName can also be of type boolean. However, the property $objectName is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
210
211
		if ($autoAssignUri) {
212
			$uid = $this->vObject->{$this->getObjectName()}->{'UID'}->getValue();
213
			$this->setUri($uid . '.ics');
214
		}
215
216
		return $this;
217
	}
218
219
220
	/**
221
	 * @return VCalendar
222
	 */
223
	public function getVObject() {
224
		$vobject =  $this->getter('vObject');
225
		$objectName = $this->getObjectName();
226
227
		$props = [
228
			new TextProperty($vobject, 'X-OC-URI', $this->getUri()),
229
			new TextProperty($vobject, 'X-OC-ETAG', $this->getEtag(true)),
230
			new IntegerProperty($vobject, 'X-OC-RUDS', $this->getRuds())
231
		];
232
233
		/** @var \Sabre\vobject\Component\VCalendar $vobject */
234
		$_objects = $vobject->select($objectName);
235
		$vobject->remove($objectName);
236
		foreach($_objects as &$_object) {
237
			/** @var \Sabre\VObject\Component $_object */
238
			foreach ($props as $prop) {
239
				/** @var \Sabre\VObject\Property $prop */
240
				if ($prop->getValue() === null) {
241
					continue;
242
				} else {
243
					$_object->remove($prop->name);
244
					$_object->add($prop);
245
				}
246
			}
247
			$vobject->add($_object);
248
		}
249
250
		return $vobject;
251
	}
252
253
254
	/**
255
	 * @return array array of updated fields for update query
256
	 */
257
	public function getUpdatedFields() {
258
		$updatedFields = parent::getUpdatedFields();
259
260
		$properties = [
261
			'uri', 'type', 'startDate',
262
			'endDate', 'calendarid', 'repeating',
263
			'summary', 'calendarData', 'lastModified',
264
		];
265
266
		foreach($properties as $property) {
267
			$updatedFields[$property] = true;
268
		}
269
270
		unset($updatedFields['calendar']);
271
		unset($updatedFields['vobject']);
272
		unset($updatedFields['objectName']);
273
274
		return $updatedFields;
275
	}
276
277
278
	/**
279
	 * set lastModified to now and update ETag
280
	 * @return $this
281
	 */
282
	public function touch() {
283
		$now = new DateTime();
284
		//TODO - fix for multiple objects
285
		$this->vObject->{$this->getObjectName()}->{'LAST-MODIFIED'}->setDateTime($now);
286
		$this->generateEtag();
287
		return $this;
288
	}
289
290
291
	/**
292
	 * does an object allow
293
	 * @param integer $cruds
294
	 * @return integer
295
	 */
296
	public function doesAllow($cruds) {
297
		return ($this->ruds & $cruds);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->ruds & $cruds; (integer) is incompatible with the return type declared by the interface OCA\Calendar\IObject::doesAllow of type boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
298
	}
299
300
301
	/**
302
	 * get text/calendar representation of stored object
303
	 * @return integer
304
	 */
305
	public function getCalendarData() {
306
		return $this->vObject->serialize();
307
	}
308
309
310
	/**
311
	 * set the calendarData
312
	 * @param string $data CalendarData
313
	 * @throws CorruptDataException
314
	 * @return $this
315
	 */
316
	public function setCalendarData($data) {
317
		try {
318
			$vobject = Reader::read($data);
319
			if (!($vobject instanceof VCalendar)) {
0 ignored issues
show
Bug introduced by
The class Sabre\VObject\Component\VCalendar does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
320
				$msg = 'CalendarData is not actual calendar-data!';
321
				throw new CorruptDataException($msg);
322
			}
323
			return $this->setVObject($vobject);
324
		} catch(ParseException $ex) {
0 ignored issues
show
Bug introduced by
The class Sabre\VObject\ParseException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
325
			throw new CorruptDataException($ex->getMessage(), $ex->getCode(), $ex);
326
		}
327
	}
328
329
330
	/**
331
	 * update Etag
332
	 * @return $this
333
	 */
334
	public function generateEtag() {
335
		$etag  = $this->getUri();
336
		$etag .= $this->getCalendarData();
337
338
		return $this->setter('etag', [md5($etag)]);
339
	}
340
341
342
	/**
343
	 * get type of stored object
344
	 * @return integer
345
	 */
346
	public function getType() {
347
		return ObjectType::getTypeByString(
348
			$this->getObjectName()
349
		);
350
	}
351
352
353
	/**
354
	 * get startDate
355
	 * @return DateTime
356
	 */
357 View Code Duplication
	public function getStartDate() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
358
		/** @var \Sabre\VObject\Component $object */
359
		$object = $this->vObject->{$this->getObjectName()};
360
		$realStart = SabreUtility::getDTStart($object);
361
		if ($realStart instanceof SDateTime || $realStart instanceof SDate) {
0 ignored issues
show
Bug introduced by
The class Sabre\VObject\Property\ICalendar\DateTime does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
Bug introduced by
The class Sabre\VObject\Property\ICalendar\Date does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
362
			return $realStart->getDateTime();
363
		} else {
364
			return null;
365
		}
366
	}
367
368
369
	/**
370
	 * get endDate
371
	 * @return DateTime
372
	 */
373 View Code Duplication
	public function getEndDate() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
374
		/** @var \Sabre\VObject\Component $object */
375
		$object = $this->vObject->{$this->getObjectName()};
376
		$realEnd = SabreUtility::getDTEnd($object);
377
		if ($realEnd instanceof SDateTime || $realEnd instanceof SDate) {
0 ignored issues
show
Bug introduced by
The class Sabre\VObject\Property\ICalendar\DateTime does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
Bug introduced by
The class Sabre\VObject\Property\ICalendar\Date does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
378
			return $realEnd->getDateTime();
379
		} else {
380
			return null;
381
		}
382
	}
383
384
385
	/**
386
	 * get whether or not object is repeating
387
	 * @return boolean
388
	 */
389
	public function getRepeating() {
390
		$objectName = $this->getObjectName();
391
392
		//TODO - fix for multiple objects
393
		return (isset($this->vObject->{$objectName}->{'RRULE'}) ||
394
				isset($this->vObject->{$objectName}->{'RDATE'}));
395
	}
396
397
398
	/**
399
	 * get summary of object
400
	 * @return mixed (string|null)
401
	 */
402 View Code Duplication
	public function getSummary() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
403
		$objectName = $this->getObjectName();
404
405
		if (isset($this->vObject->{$objectName}->{'SUMMARY'})) {
406
			return $this->vObject->{$objectName}->{'SUMMARY'}->getValue();
407
		}
408
409
		return null;
410
	}
411
412
413
	/**
414
	 * get last modified of object
415
	 * @return mixed (DateTime|null)
416
	 */
417 View Code Duplication
	public function getLastModified() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
418
		$objectName = $this->getObjectName();
419
420
		//TODO - fix for multiple objects
421
		if (isset($this->vObject->{$objectName}->{'LAST-MODIFIED'})) {
422
			return $this->vObject->{$objectName}->{'LAST-MODIFIED'}->getDateTime();
423
		}
424
425
		return null;
426
	}
427
428
429
	/**
430
	 * @param DateTime $start
431
	 * @param DateTime $end
432
	 * @return boolean
433
	 */
434
	public function isInTimeRange(DateTime $start, DateTime $end) {
435
		$objectName = $this->getObjectName();
436
		return $this->vObject->{$objectName}->isInTimeRange($start, $end);
437
	}
438
439
440
	/**
441
	 * get name of property inside $this->vobject
442
	 * @return string
443
	 */
444
	private function getObjectName() {
445
		return $this->objectName;
446
	}
447
448
449
	/**
450
	 * register field types
451
	 */
452
	protected function registerTypes() {
453
		$this->addType('uri', 'string');
454
		$this->addType('etag', 'string');
455
		$this->addType('ruds', 'integer');
456
457
		$this->addAdvancedFieldType('calendar',
458
			'OCA\\Calendar\\ICalendar');
459
		$this->addAdvancedFieldType('vObject',
460
			'Sabre\\VObject\\Component\\VCalendar');
461
	}
462
463
464
	/**
465
	 * register mandatory fields
466
	 */
467
	protected function registerMandatory() {
468
		$this->addMandatory('calendar');
469
		$this->addMandatory('uri');
470
		$this->addMandatory('vObject');
471
	}
472
473
474
	/**
475
	 * check if object is valid
476
	 * @return bool
477
	 */
478
	public function isValid() {
479
		$typeChecker = parent::isValid();
480
		if (!$typeChecker) {
481
			return false;
482
		}
483
484
		$validate = $this->vObject->validate();
485
		foreach ($validate as $item) {
486
			if (isset($item['level']) && intval($item['level']) === 3) {
487
				return false;
488
			}
489
		}
490
491
		return true;
492
	}
493
}