Completed
Pull Request — master (#1124)
by Thomas
06:21
created

Contact::__get()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.256

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 7
ccs 3
cts 5
cp 0.6
rs 9.4286
cc 2
eloc 4
nc 2
nop 1
crap 2.256
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 View Code Duplication
		if (strval($this->VERSION) === '4.0') {
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...
448
			$type = $photo->mimeType();
449
		} else {
450
			$type = explode('/', $photo->mimeType());
451
			$type = strtoupper(array_pop($type));
452
		}
453
		if (isset($this->PHOTO)) {
454
			$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...
455
		}
456
		$this->add('PHOTO', $photo->data(), ['ENCODING' => 'b', 'TYPE' => $type]);
457
		$this->setSaved(false);
458
459
		return true;
460
	}
461
462
	/**
463
	* Get a property index in the contact by the checksum of its serialized value
464
	*
465
	* @param string $checksum An 8 char m5d checksum.
466
	* @return integer Property by reference
467
	* @throws Exception with error code 404 if the property is not found.
468
	*/
469
	public function getPropertyIndexByChecksum($checksum) {
470
		$this->retrieve();
471
		$idx = 0;
472 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...
473
			if (substr(md5($property->serialize()), 0, 8) == $checksum ) {
474
				return $idx;
475
			}
476
			$idx += 1;
477
		}
478
		throw new Exception(self::$l10n->t('Property not found'), 404);
479
	}
480
481
	/**
482
	* Get a property by the checksum of its serialized value
483
	*
484
	* @param string $checksum An 8 char m5d checksum.
485
	* @return \Sabre\VObject\Property Property by reference
486
	* @throws Exception with error code 404 if the property is not found.
487
	*/
488
	public function getPropertyByChecksum($checksum) {
489
		$this->retrieve();
490 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...
491
			if (substr(md5($property->serialize()), 0, 8) == $checksum ) {
492
				return $property;
493
			}
494
		}
495
		throw new Exception(self::$l10n->t('Property not found'), 404);
496
	}
497
498
	/**
499
	* Delete a property by the checksum of its serialized value
500
	* It is up to the caller to call ->save()
501
	*
502
	* @param string $checksum An 8 char m5d checksum.
503
	* @throws @see getPropertyByChecksum
504
	*/
505
	public function unsetPropertyByChecksum($checksum) {
506
		$idx = $this->getPropertyIndexByChecksum($checksum);
507
		unset($this->children[$idx]);
508
		$this->setSaved(false);
509
	}
510
511
	/**
512
	* Set a property by the checksum of its serialized value
513
	* It is up to the caller to call ->save()
514
	*
515
	* @param string $checksum An 8 char m5d checksum.
516
	* @param string $name Property name
517
	* @param mixed $value
518
	* @param array $parameters
519
	* @throws @see getPropertyByChecksum
520
	* @return string new checksum
521
	*/
522
	public function setPropertyByChecksum($checksum, $name, $value, $parameters=array()) {
523
		if ($checksum === 'new') {
524
			$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...
525
			$this->add($property);
526
		} else {
527
			$property = $this->getPropertyByChecksum($checksum);
528
		}
529
		switch ($name) {
530
			case 'EMAIL':
531
			case 'CLOUD':
532
				$value = strtolower($value);
533
				$property->setValue($value);
534
				break;
535
			case 'ADR':
536
				if(is_array($value)) {
537
					$property->setParts($value);
538
				} else {
539
					$property->setValue($value);
540
				}
541
				break;
542
			case 'IMPP':
543
				if (is_null($parameters) || !isset($parameters['X-SERVICE-TYPE'])) {
544
					throw new \InvalidArgumentException(self::$l10n->t(' Missing IM parameter for: ') . $name. ' ' . $value, 412);
545
				}
546
				$serviceType = $parameters['X-SERVICE-TYPE'];
547
				if (is_array($serviceType)) {
548
					$serviceType = $serviceType[0];
549
				}
550
				$impp = Utils\Properties::getIMOptions($serviceType);
551
				if (is_null($impp)) {
552
					throw new \UnexpectedValueException(self::$l10n->t('Unknown IM: ') . $serviceType, 415);
553
				}
554
				$value = $impp['protocol'] . ':' . $value;
555
				$property->setValue($value);
556
				break;
557 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...
558
				\OCP\Util::writeLog('contacts', __METHOD__.' adding: '.$name. ' ' . $value, \OCP\Util::DEBUG);
559
				$property->setValue($value);
560
				break;
561
		}
562
		$this->setParameters($property, $parameters, true);
563
		$this->setSaved(false);
564
		return substr(md5($property->serialize()), 0, 8);
565
	}
566
567
	/**
568
	* Set a property by the property name.
569
	* It is up to the caller to call ->save()
570
	*
571
	* @param string $name Property name
572
	* @param mixed $value
573
	* @param array $parameters
574
	* @return bool
575
	*/
576 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...
577
		// TODO: parameters are ignored for now.
578
		switch ($name) {
579
			case 'BDAY':
580
				try {
581
					$date = New \DateTime($value);
582
				} catch(Exception $e) {
583
					\OCP\Util::writeLog('contacts',
584
						__METHOD__.' DateTime exception: ' . $e->getMessage(),
585
						\OCP\Util::ERROR
586
					);
587
					return false;
588
				}
589
				$value = $date->format('Y-m-d');
590
				$this->BDAY = $value;
591
				$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...
592
				//\OCP\Util::writeLog('contacts', __METHOD__.' BDAY: '.$this->BDAY->serialize(), \OCP\Util::DEBUG);
593
				break;
594
			case 'CATEGORIES':
595
			case 'N':
596
			case 'ORG':
597
				$property = $this->select($name);
598
				if (count($property) === 0) {
599
					$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...
600
					$this->add($property);
601 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...
602
					\OCP\Util::writeLog('contacts',
603
						__METHOD__.' more than one property for ' . $name,
604 2
						\OCP\Util::ERROR
605
					);
606 2
					return false;
607
				} else {
608
					// select returns an array...
609
					$property = array_shift($property);
610
				}
611 2
				if (($name === 'N') && !is_array($value)) {
612
					$value = explode(';', (string)$value);
613
				}
614
				if (is_array($value)) {
615
					$property->setParts($value);
616
				} else {
617
					$property->setValue($value);
618 2
				}
619
				break;
620 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...
621
				\OCP\Util::writeLog('contacts', __METHOD__.' adding: '.$name. ' ' . $value, \OCP\Util::DEBUG);
622
				$this->{$name} = $value;
623
				break;
624
		}
625
		$this->setSaved(false);
626
		return true;
627
	}
628
629
	protected function setParameters($property, $parameters, $reset = false) {
630
		if (!$parameters) {
631
			return;
632
		}
633
634
		if ($reset) {
635
			$property->parameters = array();
636
		}
637
		//debug('Setting parameters: ' . print_r($parameters, true));
638
		foreach ($parameters as $key => $parameter) {
639
			//debug('Adding parameter: ' . $key);
640
			if (is_array($parameter)) {
641
				foreach ($parameter as $val) {
642
					if (is_array($val)) {
643
						foreach ($val as $val2) {
644
							if (trim($key) && trim($val2)) {
645
								//debug('Adding parameter: '.$key.'=>'.print_r($val2, true));
646
								$property->add($key, strip_tags($val2));
647
							}
648
						}
649
					} else {
650
						if (trim($key) && trim($val)) {
651
							//debug('Adding parameter: '.$key.'=>'.print_r($val, true));
652
							$property->add($key, strip_tags($val));
653
						}
654
					}
655
				}
656
			} else {
657
				if (trim($key) && trim($parameter)) {
658
					//debug('Adding parameter: '.$key.'=>'.print_r($parameter, true));
659
					$property->add($key, strip_tags($parameter));
660
				}
661
			}
662
		}
663
	}
664
665 2
	public function lastModified() {
666 2
		if (!isset($this->props['lastmodified']) && !$this->isRetrieved()) {
667
			$this->retrieve();
668
		}
669 2
		return isset($this->props['lastmodified'])
670 2
			? $this->props['lastmodified']
671 2
			: null;
672
	}
673
674
	/**
675
	 * Merge in data from a multi-dimentional array
676
	 *
677
	 * NOTE: The data has actually already been merged client side!
678
	 * NOTE: The only properties coming from the web client are the ones
679
	 * defined in \OCA\Contacts\Utils\Properties::$indexProperties and
680
	 * UID is skipped for obvious reasons, and PHOTO is currently not updated.
681
	 * The data array has this structure:
682
	 *
683
	 * array(
684
	 * 	'EMAIL' => array(array('value' => '[email protected]', 'parameters' = array('TYPE' => array('HOME','VOICE'))))
685
	 * );
686
	 * @param array $data
687
	 * @return bool
688
	 */
689
	public function mergeFromArray(array $data) {
690
		foreach ($data as $name => $properties) {
691
			if (in_array($name, array('PHOTO', 'UID'))) {
692
				continue;
693
			}
694 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...
695
				\OCP\Util::writeLog('contacts', __METHOD__.' not an array?: ' .$name. ' '.print_r($properties, true), \OCP\Util::DEBUG);
696
			}
697
			if (in_array($name, Utils\Properties::$multiProperties)) {
698
				unset($this->{$name});
699
			}
700
			foreach ($properties as $parray) {
701
				\OCP\Util::writeLog('contacts', __METHOD__.' adding: ' .$name. ' '.print_r($parray['value'], true) . ' ' . print_r($parray['parameters'], true), \OCP\Util::DEBUG);
702
				if (in_array($name, Utils\Properties::$multiProperties)) {
703
					// TODO: wrap in try/catch, check return value
704
					$this->setPropertyByChecksum('new', $name, $parray['value'], $parray['parameters']);
705
				} else {
706
					// TODO: Check return value
707
					if (!isset($this->{$name})) {
708
						$this->setPropertyByName($name, $parray['value'], $parray['parameters']);
709
					}
710
				}
711
			}
712
		}
713
		$this->setSaved(false);
714
		return true;
715
	}
716
717 3
	public function __get($key) {
718 3
		if (!$this->isRetrieved()) {
719
			$this->retrieve();
720
		}
721
722 3
		return parent::__get($key);
723
	}
724
725 3
	public function __isset($key) {
726 3
		if (!$this->isRetrieved()) {
727
			$this->retrieve();
728
		}
729
730 3
		return parent::__isset($key);
731
	}
732
733 6
	public function __set($key, $value) {
734 6
		if (!$this->isRetrieved()) {
735 4
			$this->retrieve();
736 4
		}
737 6
		parent::__set($key, $value);
738 6
		if ($key === 'FN') {
739 6
			$this->props['displayname'] = $value;
740 6
		}
741 6
		$this->setSaved(false);
742 6
	}
743
744
	public function __unset($key) {
745
		if (!$this->isRetrieved()) {
746
			$this->retrieve();
747
		}
748
		parent::__unset($key);
749
		if ($key === 'PHOTO') {
750
			Properties::cacheThumbnail(
751
				$this->getBackend()->name,
752
				$this->getParent()->getId(),
753
				$this->getId(),
754
				null,
755
				$this,
756
				array('remove' => true)
757
			);
758
		}
759
		$this->setSaved(false);
760
	}
761
762
	/**
763
	 * @param boolean $state
764
	 */
765 7
	public function setRetrieved($state) {
766 7
		$this->props['retrieved'] = $state;
767 7
	}
768
769 7
	public function isRetrieved() {
770 7
		return $this->props['retrieved'];
771
	}
772
773 7
	public function setSaved($state = true) {
774 7
		$this->props['saved'] = $state;
775 7
	}
776
777 3
	public function isSaved() {
778 3
		return $this->props['saved'];
779
	}
780
781
	/**
782
	 * Generate an event to show in the calendar
783
	 *
784
	 * @return \Sabre\VObject\Component\VCalendar|null
785
	 */
786
	public function getBirthdayEvent() {
787
		if (!isset($this->BDAY)) {
788
			return null;
789
		}
790
		$birthday = $this->BDAY;
791
		if ((string)$birthday) {
792
			$title = str_replace('{name}',
793
				strtr((string)$this->FN, array('\,' => ',', '\;' => ';')),
794
				App::$l10n->t('{name}\'s Birthday')
795
			);
796
			try {
797
				$date = new \DateTime($birthday);
798
			} catch(Exception $e) {
799
				return null;
800
			}
801
			$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...
802
			$vCal->VERSION = '2.0';
803
			$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...
804
			$vEvent->add('DTSTART');
805
			$vEvent->DTSTART->setDateTime(
806
				$date
807
			);
808
			$vEvent->DTSTART['VALUE'] = 'DATE';
809
			$vEvent->add('DTEND');
810
			$date->add(new \DateInterval('P1D'));
811
			$vEvent->DTEND->setDateTime(
812
				$date
813
			);
814
			$vEvent->DTEND['VALUE'] = 'DATE';
815
			$lm = new \DateTime('@' . $this->lastModified());
816
			$lm->setTimeZone(new \DateTimeZone('UTC'));
817
			$vEvent->DTSTAMP->setDateTime($lm);
818
			$vEvent->{'UID'} = $this->UID;
819
			$vEvent->{'RRULE'} = 'FREQ=YEARLY';
820
			$vEvent->{'SUMMARY'} = $title . ' (' . $date->format('Y') . ')';
821
			$vEvent->{'TRANSP'} = 'TRANSPARENT';
822
			$appInfo = \OCP\App::getAppInfo('contacts');
823
			$appVersion = \OCP\App::getAppVersion('contacts');
824
			$vCal->PRODID = '-//ownCloud//NONSGML '.$appInfo['name'].' '.$appVersion.'//EN';
825
			$vCal->add($vEvent);
826
			return $vCal;
827
		}
828
829
		return null;
830
	}
831
832
}
833