Completed
Pull Request — master (#1112)
by
unknown
03:34
created

Contact::setPropertyByName()   C

Complexity

Conditions 11
Paths 30

Size

Total Lines 52
Code Lines 41

Duplication

Lines 11
Ratio 21.15 %

Code Coverage

Tests 5
CRAP Score 92.8972

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 11
loc 52
ccs 5
cts 41
cp 0.122
rs 6
cc 11
eloc 41
nc 30
nop 3
crap 92.8972

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
 * ownCloud - Contact object
4
 *
5
 * @author Thomas Tanghus
6
 * @copyright 2012-2014 Thomas Tanghus ([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
23
namespace OCA\Contacts;
24
25
use Exception;
26
use Sabre\VObject\Property;
27
use OCA\Contacts\Utils\Properties;
28
29
/**
30
 * Subclass this class or implement IPIMObject interface for PIM objects
31
 */
32
33
class Contact extends VObject\VCard implements IPIMObject {
34
35
	/**
36
	 * The name of the object type in this case VCARD.
37
	 *
38
	 * This is used when serializing the object.
39
	 *
40
	 * @var string
41
	 */
42
	public $name = 'VCARD';
43
44
	/**
45
	 * @brief language object
46
	 *
47
	 * @var \OCP\IL10N
48
	 */
49
	public static $l10n;
50
51
	protected $props = array();
52
53
	/**
54
	 * Create a new Contact object
55
	 *
56
	 * @param AddressBook $parent
57
	 * @param Backend\AbstractBackend $backend
58
	 * @param mixed $data
59
	 */
60 7
	public function __construct($parent, $backend, $data = null) {
61 7
		parent::__construct('VCARD');
62
63 7
		self::$l10n = $parent::$l10n;
0 ignored issues
show
Documentation Bug introduced by
It seems like $parent::$l10n of type object<OC_L10N> is incompatible with the declared type object<OCP\IL10N> of property $l10n.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
64
		//\OCP\Util::writeLog('contacts', __METHOD__ . ' , data: ' . print_r($data, true), \OCP\Util::DEBUG);
65 7
		$this->props['parent'] = $parent;
66 7
		$this->props['backend'] = $backend;
67 7
		$this->props['retrieved'] = false;
68 7
		$this->props['saved'] = false;
69
70 7
		if (!is_null($data)) {
71 7
			if ($data instanceof VObject\VCard) {
72 3
				foreach ($data->children as $child) {
73 3
					$this->add($child);
74 3
				}
75 3
				$this->setRetrieved(true);
76 7
			} elseif (is_array($data)) {
77 6
				foreach ($data as $key => $value) {
78
					switch ($key) {
79 6
						case 'id':
80 6
							$this->props['id'] = $value;
81 6
							break;
82 6
						case 'permissions':
83 6
							$this->props['permissions'] = $value;
84 6
							break;
85 6
						case 'lastmodified':
86 2
							$this->props['lastmodified'] = $value;
87 2
							break;
88 6
						case 'uri':
89 2
							$this->props['uri'] = $value;
90 2
							break;
91 6
						case 'carddata':
92 6
							$this->props['carddata'] = $value;
93 6
							$this->retrieve();
94 6
							break;
95 6
						case 'vcard':
96
							$this->props['vcard'] = $value;
97
							$this->retrieve();
98
							break;
99 6
						case 'displayname':
100 6
						case 'fullname':
101 6
							if(is_string($value)) {
102 6
								$this->props['displayname'] = $value;
103 6
								$this->FN = $value;
104
								// Set it to saved again as we're not actually changing anything
105 6
								$this->setSaved();
106 6
							}
107 6
							break;
108
					}
109 6
				}
110 6
			}
111 7
		}
112 7
	}
113
114
	/**
115
	 * @return array|null
116
	 */
117 2
	public function getMetaData() {
118 2
		if (!$this->hasPermission(\OCP\Constants::PERMISSION_READ)) {
119
			throw new Exception(self::$l10n->t('You do not have permissions to see this contact'), 403);
120
		}
121 2
		if (!isset($this->props['displayname'])) {
122
			if (!$this->retrieve()) {
123
				\OCP\Util::writeLog('contacts', __METHOD__.' error reading: '.print_r($this->props, true), \OCP\Util::ERROR);
124
				return null;
125
			}
126
		}
127
		return array(
128 2
			'id' => $this->getId(),
129 2
			'displayname' => $this->getDisplayName(),
130 2
			'permissions' => $this->getPermissions(),
131 2
			'lastmodified' => $this->lastModified(),
132 2
			'owner' => $this->getOwner(),
133 2
			'parent' => $this->getParent()->getId(),
134 2
			'backend' => $this->getBackend()->name,
135 2
		);
136
	}
137
138
	/**
139
	 * Get a unique key combined of backend name, address book id and contact id.
140
	 *
141
	 * @return string
142
	 */
143
	public function combinedKey() {
144
		return $this->getBackend()->name . '::' . $this->getParent()->getId() . '::' . $this->getId();
145
	}
146
147
	/**
148
	 * @return string|null
149
	 */
150 2
	public function getOwner() {
151 2
		return isset($this->props['owner'])
152 2
			? $this->props['owner']
153 2
			: $this->getParent()->getOwner();
154
	}
155
156
	/**
157
	 * @return string|null
158
	 */
159 8
	public function getId() {
160 8
		return isset($this->props['id']) ? $this->props['id'] : null;
161
	}
162
163
	/**
164
	 * @return string|null
165
	 */
166 5
	public function getDisplayName() {
167 5
		if (!$this->hasPermission(\OCP\Constants::PERMISSION_READ)) {
168 1
			throw new Exception(self::$l10n->t('You do not have permissions to see this contact'), 403);
169
		}
170 5
		return isset($this->props['displayname'])
171 5
			? $this->props['displayname']
172 5
			: (isset($this->FN) ? $this->FN : null);
173
	}
174
175
	/**
176
	 * @return string|null
177
	 */
178
	public function getURI() {
179
		return isset($this->props['uri']) ? $this->props['uri'] : null;
180
	}
181
182
	/**
183
	 * @return string
184
	 * TODO: Cache result.
185
	 */
186
	public function getETag() {
187
		$this->retrieve();
188
		return md5($this->serialize());
189
	}
190
191
	/**
192
	 * If this object is part of a collection return a reference
193
	 * to the parent object, otherwise return null.
194
	 * @return IPIMObject|null
195
	 */
196 7
	public function getParent() {
197 7
		return $this->props['parent'];
198
	}
199
200 3
	public function getBackend() {
201 3
		return $this->props['backend'];
202
	}
203
204
	/** CRUDS permissions (Create, Read, Update, Delete, Share)
205
	 *
206
	 * @return integer
207
	 */
208 6
	public function getPermissions() {
209 6
		return isset($this->props['permissions'])
210 6
			? $this->props['permissions']
211 6
			: $this->getParent()->getPermissions();
212
	}
213
214
	/**
215
	 * @param integer $permission
216
	 * @return integer
217
	 */
218 6
	public function hasPermission($permission) {
219 6
		return $this->getPermissions() & $permission;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->getPermissions() & $permission; (integer) is incompatible with the return type declared by the interface OCA\Contacts\IPIMObject::hasPermission 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...
220
	}
221
222
	/**
223
	 * Save the address book data to backend
224
	 * FIXME
225
	 *
226
	 * @param array $data
227
	 * @return bool
228
	 */
229
/*	public function update(array $data) {
230
231
		foreach($data as $key => $value) {
232
			switch($key) {
233
				case 'displayname':
234
					$this->addressBookInfo['displayname'] = $value;
235
					break;
236
				case 'description':
237
					$this->addressBookInfo['description'] = $value;
238
					break;
239
			}
240
		}
241
		return $this->props['backend']->updateContact(
242
			$this->getParent()->getId(),
243
			$this->getId(),
244
			$this
245
		);
246
	}
247
*/
248
	/**
249
	 * Delete the data from backend
250
	 *
251
	 * FIXME: Should be removed as it could leave the parent with a dataless object.
252
	 *
253
	 * @return bool
254
	 */
255
	public function delete() {
256
		if (!$this->hasPermission(\OCP\Constants::PERMISSION_DELETE)) {
257
			throw new Exception(self::$l10n->t('You do not have permissions to delete this contact'), 403);
258
		}
259
		return $this->props['backend']->deleteContact(
260
			$this->getParent()->getId(),
261
			$this->getId()
262
		);
263
	}
264
265
	/**
266
	 * Save the contact data to backend
267
	 *
268
	 * @return bool
269
	 */
270 3
	public function save($force = false) {
271 3
		if (!$this->hasPermission(\OCP\Constants::PERMISSION_UPDATE)) {
272
			throw new Exception(self::$l10n->t('You do not have permissions to update this contact'), 403);
273
		}
274 3
		if ($this->isSaved() && !$force) {
275
			\OCP\Util::writeLog('contacts', __METHOD__.' Already saved: ' . print_r($this->props, true), \OCP\Util::DEBUG);
276
			return true;
277
		}
278
279 3
		if (isset($this->FN)) {
280 3
			$this->props['displayname'] = (string)$this->FN;
281 3
		}
282
283 3
		if ($this->getId()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->getId() of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

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

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
284
			if (!$this->getBackend()->hasContactMethodFor(\OCP\Constants::PERMISSION_UPDATE)) {
285
				throw new Exception(self::$l10n->t('The backend for this contact does not support updating it'), 501);
286
			}
287
			if ($this->getBackend()
288
				->updateContact(
289
					$this->getParent()->getId(),
290
					$this->getId(),
291
					$this
292
				)
293
			) {
294
				$this->props['lastmodified'] = time();
295
				$this->setSaved(true);
296
				return true;
297
			} else {
298
				return false;
299
			}
300
		} else {
301 3
			if (!$this->getBackend()->hasContactMethodFor(\OCP\Constants::PERMISSION_CREATE)) {
302
				throw new Exception(self::$l10n->t('This backend does not support adding contacts'), 501);
303
			}
304 3
			$this->props['id'] = $this->getBackend()->createContact(
305 3
				$this->getParent()->getId(), $this
306 3
			);
307 3
			$this->setSaved(true);
308 3
			return $this->getId() !== false;
309
		}
310
	}
311
312
	/**
313
	 * Get the data from the backend
314
	 * FIXME: Clean this up and make sure the logic is OK.
315
	 *
316
	 * @return bool
317
	 */
318 7
	public function retrieve() {
319 6
		if ($this->isRetrieved()) {
320
			//\OCP\Util::writeLog('contacts', __METHOD__. ' children', \OCP\Util::DEBUG);
321 6
			return true;
322
		} else {
323 6
			$data = null;
324 6
			if(isset($this->props['vcard'])
325 6
				&& $this->props['vcard'] instanceof VObject\VCard) {
326
				foreach($this->props['vcard']->children() as $child) {
327
					$this->add($child);
328
					if($child->name === 'FN') {
329
						$this->props['displayname']
330
							= strtr($child->getValue(), array('\,' => ',', '\;' => ';', '\\\\' => '\\'));
331
					}
332
				}
333
				$this->setRetrieved(true);
334
				$this->setSaved(true);
335
				//$this->children = $this->props['vcard']->children();
336
				unset($this->props['vcard']);
337
				return true;
338 6
			} elseif (!isset($this->props['carddata'])) {
339 4
				$result = $this->props['backend']->getContact(
340 4
					$this->getParent()->getId(),
341 4
					$this->getId()
342 4
				);
343 4
				if ($result) {
344 4
					if (isset($result['vcard'])
345 4
						&& $result['vcard'] instanceof VObject\VCard) {
346
						foreach ($result['vcard']->children() as $child) {
347
							$this->add($child);
348
						}
349
						$this->setRetrieved(true);
350
						return true;
351 4
					} elseif (isset($result['carddata'])) {
352
						// Save internal values
353 4
						$data = $result['carddata'];
354 4
						$this->props['carddata'] = $result['carddata'];
355 4
						$this->props['lastmodified'] = isset($result['lastmodified'])
356 4
							? $result['lastmodified']
357 4
							: null;
358 4
						$this->props['displayname'] = $result['displayname'];
359 4
						$this->props['permissions'] = $result['permissions'];
360 4
					} else {
361
						\OCP\Util::writeLog('contacts', __METHOD__
362
							. ' Could not get vcard or carddata: '
363
							. $this->getId()
364
							. print_r($result, true), \OCP\Util::DEBUG);
365
						return false;
366
					}
367 4
				} else {
368
					\OCP\Util::writeLog('contacts', __METHOD__.' Error getting contact: ' . $this->getId(), \OCP\Util::DEBUG);
369
				}
370 6
			} elseif (isset($this->props['carddata'])) {
371 2
				$data = $this->props['carddata'];
372 2
			}
373
			try {
374 6
				$obj = \Sabre\VObject\Reader::read(
375 6
					$data,
376
					\Sabre\VObject\Reader::OPTION_IGNORE_INVALID_LINES
377 6
				);
378 6
				if ($obj) {
379 6
					foreach ($obj->children as $child) {
380 6
						if($child->name === 'VERSION' || $child->name === 'PRODID') {
381 6
							parent::__set($child->name, $child);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (__set() instead of retrieve()). Are you sure this is correct? If so, you might want to change this to $this->__set().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
382 7
						} else {
383 6
							$this->add($child);
384
						}
385 6
					}
386 6
					$this->setRetrieved(true);
387 6
					$this->setSaved(true);
388 6
				} else {
389
					\OCP\Util::writeLog('contacts', __METHOD__.' Error reading: ' . print_r($data, true), \OCP\Util::DEBUG);
390
					return false;
391
				}
392 6
			} catch (Exception $e) {
393
				\OCP\Util::writeLog('contacts', __METHOD__ .
394
					' Error parsing carddata  for: ' . $this->getId() . ' ' . $e->getMessage(),
395
						\OCP\Util::ERROR);
396
				return false;
397
			}
398
		}
399 6
		return true;
400
	}
401
402
	/**
403
	 * Get the PHOTO or LOGO
404
	 *
405
	 * @return \OCP\Image|null
406
	 */
407
	public function getPhoto() {
408
		$image = new \OCP\Image();
409
410
		if (isset($this->PHOTO)) {
411
			$photo = $this->PHOTO;
412
		} elseif (isset($this->LOGO)) {
413
			$photo = $this->LOGO;
414
		} else {
415
			return null;
416
		}
417
418
		$photovalue = $photo->getValue();
419
420
		if ( $photo instanceof \Sabre\VObject\Property\Uri && substr($photovalue, 0, 5) === 'data:' ) {
0 ignored issues
show
Bug introduced by
The class Sabre\VObject\Property\Uri 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...
421
			$mimeType = substr($photovalue, 5, strpos($photovalue, ',')-5);
422
			if (strpos($mimeType, ';')) {
423
			    $mimeType = substr($mimeType,0,strpos($mimeType, ';'));
0 ignored issues
show
Unused Code introduced by
$mimeType is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
424
			}
425
426
			$photovalue = substr($photovalue, strpos($photovalue,',')+1);
427
428
                        if ($image->loadFromBase64($photovalue)) {
0 ignored issues
show
Coding Style introduced by
Tabs must be used to indent lines; spaces are not allowed
Loading history...
429
                                return $image;
0 ignored issues
show
Coding Style introduced by
Tabs must be used to indent lines; spaces are not allowed
Loading history...
430
			}
431
		} elseif ($image->loadFromData($photovalue)) {
432
			return $image;
433
		}
434
435
		return null;
436
	}
437
438
	/**
439
	 * Set the contact photo.
440
	 *
441
	 * @param \OCP\Image $photo
442
	 */
443
	public function setPhoto(\OCP\Image $photo) {
444
		// For vCard 3.0 the type must be e.g. JPEG or PNG
445
		// For version 4.0 the full mimetype should be used.
446
		// https://tools.ietf.org/html/rfc2426#section-3.1.4
447
		if (isset($this->PHOTO)) {
448
			$this->remove('PHOTO');
0 ignored issues
show
Bug introduced by
The method remove() does not exist on OCA\Contacts\Contact. Did you maybe mean removeFromGroup()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
449
		}
450
		if (strval($this->VERSION) === '4.0') {
451
			$type = $photo->mimeType();
452
			$this->add('PHOTO', 'data:'.$type.';base64,'.base64_encode($photo->data()));
453
		} else {
454
			$type = explode('/', $photo->mimeType());
455
			$type = strtoupper(array_pop($type));
456
			$this->add('PHOTO', $photo->data(), ['ENCODING' => 'b', 'TYPE' => $type]);
457
		}
458
		$this->setSaved(false);
459
460
		return true;
461
	}
462
463
	/**
464
	* Get a property index in the contact by the checksum of its serialized value
465
	*
466
	* @param string $checksum An 8 char m5d checksum.
467
	* @return integer Property by reference
468
	* @throws Exception with error code 404 if the property is not found.
469
	*/
470
	public function getPropertyIndexByChecksum($checksum) {
471
		$this->retrieve();
472
		$idx = 0;
473 View Code Duplication
		foreach ($this->children as $i => &$property) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
474
			if (substr(md5($property->serialize()), 0, 8) == $checksum ) {
475
				return $idx;
476
			}
477
			$idx += 1;
478
		}
479
		throw new Exception(self::$l10n->t('Property not found'), 404);
480
	}
481
482
	/**
483
	* Get a property by the checksum of its serialized value
484
	*
485
	* @param string $checksum An 8 char m5d checksum.
486
	* @return \Sabre\VObject\Property Property by reference
487
	* @throws Exception with error code 404 if the property is not found.
488
	*/
489
	public function getPropertyByChecksum($checksum) {
490
		$this->retrieve();
491 View Code Duplication
		foreach ($this->children as $i => &$property) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
492
			if (substr(md5($property->serialize()), 0, 8) == $checksum ) {
493
				return $property;
494
			}
495
		}
496
		throw new Exception(self::$l10n->t('Property not found'), 404);
497
	}
498
499
	/**
500
	* Delete a property by the checksum of its serialized value
501
	* It is up to the caller to call ->save()
502
	*
503
	* @param string $checksum An 8 char m5d checksum.
504
	* @throws @see getPropertyByChecksum
505
	*/
506
	public function unsetPropertyByChecksum($checksum) {
507
		$idx = $this->getPropertyIndexByChecksum($checksum);
508
		unset($this->children[$idx]);
509
		$this->setSaved(false);
510
	}
511
512
	/**
513
	* Set a property by the checksum of its serialized value
514
	* It is up to the caller to call ->save()
515
	*
516
	* @param string $checksum An 8 char m5d checksum.
517
	* @param string $name Property name
518
	* @param mixed $value
519
	* @param array $parameters
520
	* @throws @see getPropertyByChecksum
521
	* @return string new checksum
522
	*/
523
	public function setPropertyByChecksum($checksum, $name, $value, $parameters=array()) {
524
		if ($checksum === 'new') {
525
			$property = $this->createProperty($name);
0 ignored issues
show
Bug introduced by
The method createProperty() does not exist on OCA\Contacts\Contact. Did you maybe mean create()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
526
			$this->add($property);
527
		} else {
528
			$property = $this->getPropertyByChecksum($checksum);
529
		}
530
		switch ($name) {
531
			case 'EMAIL':
532
			case 'CLOUD':
533
				$value = strtolower($value);
534
				$property->setValue($value);
535
				break;
536
			case 'ADR':
537
				if(is_array($value)) {
538
					$property->setParts($value);
539
				} else {
540
					$property->setValue($value);
541
				}
542
				break;
543
			case 'IMPP':
544
				if (is_null($parameters) || !isset($parameters['X-SERVICE-TYPE'])) {
545
					throw new \InvalidArgumentException(self::$l10n->t(' Missing IM parameter for: ') . $name. ' ' . $value, 412);
546
				}
547
				$serviceType = $parameters['X-SERVICE-TYPE'];
548
				if (is_array($serviceType)) {
549
					$serviceType = $serviceType[0];
550
				}
551
				$impp = Utils\Properties::getIMOptions($serviceType);
552
				if (is_null($impp)) {
553
					throw new \UnexpectedValueException(self::$l10n->t('Unknown IM: ') . $serviceType, 415);
554
				}
555
				$value = $impp['protocol'] . ':' . $value;
556
				$property->setValue($value);
557
				break;
558 View Code Duplication
			default:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
559
				\OCP\Util::writeLog('contacts', __METHOD__.' adding: '.$name. ' ' . $value, \OCP\Util::DEBUG);
560
				$property->setValue($value);
561
				break;
562
		}
563
		$this->setParameters($property, $parameters, true);
564
		$this->setSaved(false);
565
		return substr(md5($property->serialize()), 0, 8);
566
	}
567
568
	/**
569
	* Set a property by the property name.
570
	* It is up to the caller to call ->save()
571
	*
572
	* @param string $name Property name
573
	* @param mixed $value
574
	* @param array $parameters
575
	* @return bool
576
	*/
577 2
	public function setPropertyByName($name, $value, $parameters=array()) {
0 ignored issues
show
Unused Code introduced by
The parameter $parameters is not used and could be removed.

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

Loading history...
578
		// TODO: parameters are ignored for now.
579
		switch ($name) {
580
			case 'BDAY':
581
				try {
582
					$date = New \DateTime($value);
583
				} catch(Exception $e) {
584
					\OCP\Util::writeLog('contacts',
585
						__METHOD__.' DateTime exception: ' . $e->getMessage(),
586
						\OCP\Util::ERROR
587
					);
588
					return false;
589
				}
590
				$value = $date->format('Y-m-d');
591
				$this->BDAY = $value;
592
				$this->BDAY->add('VALUE', 'DATE');
0 ignored issues
show
Bug introduced by
The method add cannot be called on $this->BDAY (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
593
				//\OCP\Util::writeLog('contacts', __METHOD__.' BDAY: '.$this->BDAY->serialize(), \OCP\Util::DEBUG);
594
				break;
595
			case 'CATEGORIES':
596
			case 'N':
597
			case 'ORG':
598
				$property = $this->select($name);
599
				if (count($property) === 0) {
600
					$property = $this->createProperty($name);
0 ignored issues
show
Bug introduced by
The method createProperty() does not exist on OCA\Contacts\Contact. Did you maybe mean create()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
601
					$this->add($property);
602 View Code Duplication
				} elseif (count($property) > 1) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
603
					\OCP\Util::writeLog('contacts',
604 2
						__METHOD__.' more than one property for ' . $name,
605
						\OCP\Util::ERROR
606 2
					);
607
					return false;
608
				} else {
609
					// select returns an array...
610
					$property = array_shift($property);
611 2
				}
612
				if (($name === 'N') && !is_array($value)) {
613
					$value = explode(';', (string)$value);
614
				}
615
				if (is_array($value)) {
616
					$property->setParts($value);
617
				} else {
618 2
					$property->setValue($value);
619
				}
620
				break;
621 View Code Duplication
			default:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
622
				\OCP\Util::writeLog('contacts', __METHOD__.' adding: '.$name. ' ' . $value, \OCP\Util::DEBUG);
623
				$this->{$name} = $value;
624
				break;
625
		}
626
		$this->setSaved(false);
627
		return true;
628
	}
629
630
	protected function setParameters($property, $parameters, $reset = false) {
631
		if (!$parameters) {
632
			return;
633
		}
634
635
		if ($reset) {
636
			$property->parameters = array();
637
		}
638
		//debug('Setting parameters: ' . print_r($parameters, true));
639
		foreach ($parameters as $key => $parameter) {
640
			//debug('Adding parameter: ' . $key);
641
			if (is_array($parameter)) {
642
				foreach ($parameter as $val) {
643
					if (is_array($val)) {
644
						foreach ($val as $val2) {
645
							if (trim($key) && trim($val2)) {
646
								//debug('Adding parameter: '.$key.'=>'.print_r($val2, true));
647
								$property->add($key, strip_tags($val2));
648
							}
649
						}
650
					} else {
651
						if (trim($key) && trim($val)) {
652
							//debug('Adding parameter: '.$key.'=>'.print_r($val, true));
653
							$property->add($key, strip_tags($val));
654
						}
655
					}
656
				}
657
			} else {
658
				if (trim($key) && trim($parameter)) {
659
					//debug('Adding parameter: '.$key.'=>'.print_r($parameter, true));
660
					$property->add($key, strip_tags($parameter));
661
				}
662
			}
663
		}
664
	}
665
666 2
	public function lastModified() {
667 2
		if (!isset($this->props['lastmodified']) && !$this->isRetrieved()) {
668
			$this->retrieve();
669
		}
670 2
		return isset($this->props['lastmodified'])
671 2
			? $this->props['lastmodified']
672 2
			: null;
673
	}
674
675
	/**
676
	 * Merge in data from a multi-dimentional array
677
	 *
678
	 * NOTE: The data has actually already been merged client side!
679
	 * NOTE: The only properties coming from the web client are the ones
680
	 * defined in \OCA\Contacts\Utils\Properties::$indexProperties and
681
	 * UID is skipped for obvious reasons, and PHOTO is currently not updated.
682
	 * The data array has this structure:
683
	 *
684
	 * array(
685
	 * 	'EMAIL' => array(array('value' => '[email protected]', 'parameters' = array('TYPE' => array('HOME','VOICE'))))
686
	 * );
687
	 * @param array $data
688
	 * @return bool
689
	 */
690
	public function mergeFromArray(array $data) {
691
		foreach ($data as $name => $properties) {
692
			if (in_array($name, array('PHOTO', 'UID'))) {
693
				continue;
694
			}
695 View Code Duplication
			if (!is_array($properties)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
696
				\OCP\Util::writeLog('contacts', __METHOD__.' not an array?: ' .$name. ' '.print_r($properties, true), \OCP\Util::DEBUG);
697
			}
698
			if (in_array($name, Utils\Properties::$multiProperties)) {
699
				unset($this->{$name});
700
			}
701
			foreach ($properties as $parray) {
702
				\OCP\Util::writeLog('contacts', __METHOD__.' adding: ' .$name. ' '.print_r($parray['value'], true) . ' ' . print_r($parray['parameters'], true), \OCP\Util::DEBUG);
703
				if (in_array($name, Utils\Properties::$multiProperties)) {
704
					// TODO: wrap in try/catch, check return value
705
					$this->setPropertyByChecksum('new', $name, $parray['value'], $parray['parameters']);
706
				} else {
707
					// TODO: Check return value
708
					if (!isset($this->{$name})) {
709
						$this->setPropertyByName($name, $parray['value'], $parray['parameters']);
710
					}
711
				}
712
			}
713
		}
714
		$this->setSaved(false);
715
		return true;
716
	}
717
718 3
	public function __get($key) {
719 3
		if (!$this->isRetrieved()) {
720
			$this->retrieve();
721
		}
722
723 3
		return parent::__get($key);
724
	}
725
726 3
	public function __isset($key) {
727 3
		if (!$this->isRetrieved()) {
728
			$this->retrieve();
729
		}
730
731 3
		return parent::__isset($key);
732
	}
733
734 6
	public function __set($key, $value) {
735 6
		if (!$this->isRetrieved()) {
736 4
			$this->retrieve();
737 4
		}
738 6
		parent::__set($key, $value);
739 6
		if ($key === 'FN') {
740 6
			$this->props['displayname'] = $value;
741 6
		}
742 6
		$this->setSaved(false);
743 6
	}
744
745
	public function __unset($key) {
746
		if (!$this->isRetrieved()) {
747
			$this->retrieve();
748
		}
749
		parent::__unset($key);
750
		if ($key === 'PHOTO') {
751
			Properties::cacheThumbnail(
752
				$this->getBackend()->name,
753
				$this->getParent()->getId(),
754
				$this->getId(),
755
				null,
756
				$this,
757
				array('remove' => true)
758
			);
759
		}
760
		$this->setSaved(false);
761
	}
762
763
	/**
764
	 * @param boolean $state
765
	 */
766 7
	public function setRetrieved($state) {
767 7
		$this->props['retrieved'] = $state;
768 7
	}
769
770 7
	public function isRetrieved() {
771 7
		return $this->props['retrieved'];
772
	}
773
774 7
	public function setSaved($state = true) {
775 7
		$this->props['saved'] = $state;
776 7
	}
777
778 3
	public function isSaved() {
779 3
		return $this->props['saved'];
780
	}
781
782
	/**
783
	 * Generate an event to show in the calendar
784
	 *
785
	 * @return \Sabre\VObject\Component\VCalendar|null
786
	 */
787
	public function getBirthdayEvent() {
788
		if (!isset($this->BDAY)) {
789
			return null;
790
		}
791
		$birthday = $this->BDAY;
792
		if ((string)$birthday) {
793
			$title = str_replace('{name}',
794
				strtr((string)$this->FN, array('\,' => ',', '\;' => ';')),
795
				App::$l10n->t('{name}\'s Birthday')
796
			);
797
			try {
798
				$date = new \DateTime($birthday);
799
			} catch(Exception $e) {
800
				return null;
801
			}
802
			$vCal = new \Sabre\VObject\Component\VCalendar();
0 ignored issues
show
Bug introduced by
The call to VCalendar::__construct() misses a required argument $name.

This check looks for function calls that miss required arguments.

Loading history...
803
			$vCal->VERSION = '2.0';
804
			$vEvent = $vCal->createComponent('VEVENT');
0 ignored issues
show
Bug introduced by
The method createComponent() does not exist on Sabre\VObject\Component\VCalendar. Did you maybe mean create()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
805
			$vEvent->add('DTSTART');
806
			$vEvent->DTSTART->setDateTime(
807
				$date
808
			);
809
			$vEvent->DTSTART['VALUE'] = 'DATE';
810
			$vEvent->add('DTEND');
811
			$date->add(new \DateInterval('P1D'));
812
			$vEvent->DTEND->setDateTime(
813
				$date
814
			);
815
			$vEvent->DTEND['VALUE'] = 'DATE';
816
			$lm = new \DateTime('@' . $this->lastModified());
817
			$lm->setTimeZone(new \DateTimeZone('UTC'));
818
			$vEvent->DTSTAMP->setDateTime($lm);
819
			$vEvent->{'UID'} = $this->UID;
820
			$vEvent->{'RRULE'} = 'FREQ=YEARLY';
821
			$vEvent->{'SUMMARY'} = $title . ' (' . $date->format('Y') . ')';
822
			$vEvent->{'TRANSP'} = 'TRANSPARENT';
823
			$appInfo = \OCP\App::getAppInfo('contacts');
824
			$appVersion = \OCP\App::getAppVersion('contacts');
825
			$vCal->PRODID = '-//ownCloud//NONSGML '.$appInfo['name'].' '.$appVersion.'//EN';
826
			$vCal->add($vEvent);
827
			return $vCal;
828
		}
829
830
		return null;
831
	}
832
833
}
834