Blame view

sources/apps/contacts/lib/contact.php 22.5 KB
d1bafeea1   Kload   [fix] Upgrade to ...
1
2
3
4
5
  <?php
  /**
   * ownCloud - Contact object
   *
   * @author Thomas Tanghus
6d9380f96   Cédric Dupont   Update sources OC...
6
   * @copyright 2012-2014 Thomas Tanghus (thomas@tanghus.net)
d1bafeea1   Kload   [fix] Upgrade to ...
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
   *
   * This library is free software; you can redistribute it and/or
   * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
   * License as published by the Free Software Foundation; either
   * version 3 of the License, or any later version.
   *
   * This library is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
   *
   * You should have received a copy of the GNU Affero General Public
   * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
   *
   */
  
  namespace OCA\Contacts;
  
  use Sabre\VObject\Property,
  	OCA\Contacts\Utils\Properties;
  
  /**
   * Subclass this class or implement IPIMObject interface for PIM objects
   */
  
  class Contact extends VObject\VCard implements IPIMObject {
  
  	/**
  	 * The name of the object type in this case VCARD.
  	 *
  	 * This is used when serializing the object.
  	 *
  	 * @var string
  	 */
  	public $name = 'VCARD';
  
  	/**
  	 * @brief language object
  	 *
  	 * @var OC_L10N
  	 */
  	public static $l10n;
  
  	protected $props = array();
  
  	/**
  	 * Create a new Contact object
  	 *
  	 * @param AddressBook $parent
6d9380f96   Cédric Dupont   Update sources OC...
56
  	 * @param Backend\AbstractBackend $backend
d1bafeea1   Kload   [fix] Upgrade to ...
57
58
59
60
  	 * @param mixed $data
  	 */
  	public function __construct($parent, $backend, $data = null) {
  		self::$l10n = $parent::$l10n;
6d9380f96   Cédric Dupont   Update sources OC...
61
  		//\OCP\Util::writeLog('contacts', __METHOD__ . ' , data: ' . print_r($data, true), \OCP\Util::DEBUG);
d1bafeea1   Kload   [fix] Upgrade to ...
62
63
64
65
  		$this->props['parent'] = $parent;
  		$this->props['backend'] = $backend;
  		$this->props['retrieved'] = false;
  		$this->props['saved'] = false;
6d9380f96   Cédric Dupont   Update sources OC...
66
67
68
  		if (!is_null($data)) {
  			if ($data instanceof VObject\VCard) {
  				foreach ($data->children as $child) {
d1bafeea1   Kload   [fix] Upgrade to ...
69
70
71
  					$this->add($child);
  				}
  				$this->setRetrieved(true);
6d9380f96   Cédric Dupont   Update sources OC...
72
73
74
  			} elseif (is_array($data)) {
  				foreach ($data as $key => $value) {
  					switch ($key) {
d1bafeea1   Kload   [fix] Upgrade to ...
75
76
77
  						case 'id':
  							$this->props['id'] = $value;
  							break;
6d9380f96   Cédric Dupont   Update sources OC...
78
79
80
  						case 'permissions':
  							$this->props['permissions'] = $value;
  							break;
d1bafeea1   Kload   [fix] Upgrade to ...
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
  						case 'lastmodified':
  							$this->props['lastmodified'] = $value;
  							break;
  						case 'uri':
  							$this->props['uri'] = $value;
  							break;
  						case 'carddata':
  							$this->props['carddata'] = $value;
  							$this->retrieve();
  							break;
  						case 'vcard':
  							$this->props['vcard'] = $value;
  							$this->retrieve();
  							break;
  						case 'displayname':
  						case 'fullname':
  							if(is_string($value)) {
  								$this->props['displayname'] = $value;
  								$this->FN = $value;
  								// Set it to saved again as we're not actually changing anything
  								$this->setSaved();
  							}
  							break;
  					}
  				}
  			}
  		}
  	}
  
  	/**
  	 * @return array|null
  	 */
  	public function getMetaData() {
6d9380f96   Cédric Dupont   Update sources OC...
114
  		if (!$this->hasPermission(\OCP\PERMISSION_READ)) {
d1bafeea1   Kload   [fix] Upgrade to ...
115
116
  			throw new \Exception(self::$l10n->t('You do not have permissions to see this contact'), 403);
  		}
6d9380f96   Cédric Dupont   Update sources OC...
117
118
  		if (!isset($this->props['displayname'])) {
  			if (!$this->retrieve()) {
d1bafeea1   Kload   [fix] Upgrade to ...
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
  				\OCP\Util::writeLog('contacts', __METHOD__.' error reading: '.print_r($this->props, true), \OCP\Util::ERROR);
  				return null;
  			}
  		}
  		return array(
  			'id' => $this->getId(),
  			'displayname' => $this->getDisplayName(),
  			'permissions' => $this->getPermissions(),
  			'lastmodified' => $this->lastModified(),
  			'owner' => $this->getOwner(),
  			'parent' => $this->getParent()->getId(),
  			'backend' => $this->getBackend()->name,
  		);
  	}
  
  	/**
  	 * Get a unique key combined of backend name, address book id and contact id.
  	 *
  	 * @return string
  	 */
  	public function combinedKey() {
  		return $this->getBackend()->name . '::' . $this->getParent()->getId() . '::' . $this->getId();
  	}
  
  	/**
  	 * @return string|null
  	 */
  	public function getOwner() {
6d9380f96   Cédric Dupont   Update sources OC...
147
148
149
  		return isset($this->props['owner'])
  			? $this->props['owner']
  			: $this->getParent()->getOwner();
d1bafeea1   Kload   [fix] Upgrade to ...
150
151
152
153
154
155
156
157
158
159
160
161
  	}
  
  	/**
  	 * @return string|null
  	 */
  	public function getId() {
  		return isset($this->props['id']) ? $this->props['id'] : null;
  	}
  
  	/**
  	 * @return string|null
  	 */
6d9380f96   Cédric Dupont   Update sources OC...
162
163
  	public function getDisplayName() {
  		if (!$this->hasPermission(\OCP\PERMISSION_READ)) {
d1bafeea1   Kload   [fix] Upgrade to ...
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
  			throw new \Exception(self::$l10n->t('You do not have permissions to see this contact'), 403);
  		}
  		return isset($this->props['displayname'])
  			? $this->props['displayname']
  			: (isset($this->FN) ? $this->FN : null);
  	}
  
  	/**
  	 * @return string|null
  	 */
  	public function getURI() {
  		return isset($this->props['uri']) ? $this->props['uri'] : null;
  	}
  
  	/**
  	 * @return string|null
  	 * TODO: Cache result.
  	 */
  	public function getETag() {
  		$this->retrieve();
  		return md5($this->serialize());
  	}
  
  	/**
  	 * If this object is part of a collection return a reference
  	 * to the parent object, otherwise return null.
  	 * @return IPIMObject|null
  	 */
  	public function getParent() {
  		return $this->props['parent'];
  	}
  
  	public function getBackend() {
  		return $this->props['backend'];
  	}
  
  	/** CRUDS permissions (Create, Read, Update, Delete, Share)
  	 *
  	 * @return integer
  	 */
  	public function getPermissions() {
6d9380f96   Cédric Dupont   Update sources OC...
205
206
207
  		return isset($this->props['permissions'])
  			? $this->props['permissions']
  			: $this->getParent()->getPermissions();
d1bafeea1   Kload   [fix] Upgrade to ...
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
  	}
  
  	/**
  	 * @param integer $permission
  	 * @return bool
  	 */
  	public function hasPermission($permission) {
  		return $this->getPermissions() & $permission;
  	}
  
  	/**
  	 * Save the address book data to backend
  	 * FIXME
  	 *
  	 * @param array $data
  	 * @return bool
  	 */
  /*	public function update(array $data) {
  
  		foreach($data as $key => $value) {
  			switch($key) {
  				case 'displayname':
  					$this->addressBookInfo['displayname'] = $value;
  					break;
  				case 'description':
  					$this->addressBookInfo['description'] = $value;
  					break;
  			}
  		}
  		return $this->props['backend']->updateContact(
  			$this->getParent()->getId(),
  			$this->getId(),
  			$this
  		);
  	}
  */
  	/**
  	 * Delete the data from backend
  	 *
  	 * FIXME: Should be removed as it could leave the parent with a dataless object.
  	 *
  	 * @return bool
  	 */
  	public function delete() {
6d9380f96   Cédric Dupont   Update sources OC...
252
  		if (!$this->hasPermission(\OCP\PERMISSION_DELETE)) {
d1bafeea1   Kload   [fix] Upgrade to ...
253
254
255
256
257
258
259
260
261
262
263
264
265
266
  			throw new \Exception(self::$l10n->t('You do not have permissions to delete this contact'), 403);
  		}
  		return $this->props['backend']->deleteContact(
  			$this->getParent()->getId(),
  			$this->getId()
  		);
  	}
  
  	/**
  	 * Save the contact data to backend
  	 *
  	 * @return bool
  	 */
  	public function save($force = false) {
6d9380f96   Cédric Dupont   Update sources OC...
267
  		if (!$this->hasPermission(\OCP\PERMISSION_UPDATE)) {
d1bafeea1   Kload   [fix] Upgrade to ...
268
269
  			throw new \Exception(self::$l10n->t('You do not have permissions to update this contact'), 403);
  		}
6d9380f96   Cédric Dupont   Update sources OC...
270
  		if ($this->isSaved() && !$force) {
d1bafeea1   Kload   [fix] Upgrade to ...
271
272
273
  			\OCP\Util::writeLog('contacts', __METHOD__.' Already saved: ' . print_r($this->props, true), \OCP\Util::DEBUG);
  			return true;
  		}
6d9380f96   Cédric Dupont   Update sources OC...
274
275
  
  		if (isset($this->FN)) {
d1bafeea1   Kload   [fix] Upgrade to ...
276
277
  			$this->props['displayname'] = (string)$this->FN;
  		}
6d9380f96   Cédric Dupont   Update sources OC...
278
279
280
  
  		if ($this->getId()) {
  			if (!$this->getBackend()->hasContactMethodFor(\OCP\PERMISSION_UPDATE)) {
d1bafeea1   Kload   [fix] Upgrade to ...
281
282
  				throw new \Exception(self::$l10n->t('The backend for this contact does not support updating it'), 501);
  			}
6d9380f96   Cédric Dupont   Update sources OC...
283
  			if ($this->getBackend()
d1bafeea1   Kload   [fix] Upgrade to ...
284
285
286
287
288
289
290
291
292
293
294
295
296
  				->updateContact(
  					$this->getParent()->getId(),
  					$this->getId(),
  					$this
  				)
  			) {
  				$this->props['lastmodified'] = time();
  				$this->setSaved(true);
  				return true;
  			} else {
  				return false;
  			}
  		} else {
6d9380f96   Cédric Dupont   Update sources OC...
297
298
  			if (!$this->getBackend()->hasContactMethodFor(\OCP\PERMISSION_CREATE)) {
  				throw new \Exception(self::$l10n->t('This backend does not support adding contacts'), 501);
d1bafeea1   Kload   [fix] Upgrade to ...
299
300
301
302
303
304
305
306
307
308
309
310
  			}
  			$this->props['id'] = $this->getBackend()->createContact(
  				$this->getParent()->getId(), $this
  			);
  			$this->setSaved(true);
  			return $this->getId() !== false;
  		}
  	}
  
  	/**
  	 * Get the data from the backend
  	 * FIXME: Clean this up and make sure the logic is OK.
6d9380f96   Cédric Dupont   Update sources OC...
311
312
  	 *
  	 * @return bool
d1bafeea1   Kload   [fix] Upgrade to ...
313
314
  	 */
  	public function retrieve() {
6d9380f96   Cédric Dupont   Update sources OC...
315
  		if ($this->isRetrieved() || count($this->children) > 1) {
d1bafeea1   Kload   [fix] Upgrade to ...
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
  			//\OCP\Util::writeLog('contacts', __METHOD__. ' children', \OCP\Util::DEBUG);
  			return true;
  		} else {
  			$data = null;
  			if(isset($this->props['vcard'])
  				&& $this->props['vcard'] instanceof VObject\VCard) {
  				foreach($this->props['vcard']->children() as $child) {
  					$this->add($child);
  					if($child->name === 'FN') {
  						$this->props['displayname']
  							= strtr($child->value, array('\,' => ',', '\;' => ';', '\\\\' => '\\'));
  					}
  				}
  				$this->setRetrieved(true);
  				$this->setSaved(true);
  				//$this->children = $this->props['vcard']->children();
  				unset($this->props['vcard']);
  				return true;
6d9380f96   Cédric Dupont   Update sources OC...
334
  			} elseif (!isset($this->props['carddata'])) {
d1bafeea1   Kload   [fix] Upgrade to ...
335
336
337
338
  				$result = $this->props['backend']->getContact(
  					$this->getParent()->getId(),
  					$this->getId()
  				);
6d9380f96   Cédric Dupont   Update sources OC...
339
340
  				if ($result) {
  					if (isset($result['vcard'])
d1bafeea1   Kload   [fix] Upgrade to ...
341
  						&& $result['vcard'] instanceof VObject\VCard) {
6d9380f96   Cédric Dupont   Update sources OC...
342
  						foreach ($result['vcard']->children() as $child) {
d1bafeea1   Kload   [fix] Upgrade to ...
343
344
345
346
  							$this->add($child);
  						}
  						$this->setRetrieved(true);
  						return true;
6d9380f96   Cédric Dupont   Update sources OC...
347
  					} elseif (isset($result['carddata'])) {
d1bafeea1   Kload   [fix] Upgrade to ...
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
  						// Save internal values
  						$data = $result['carddata'];
  						$this->props['carddata'] = $result['carddata'];
  						$this->props['lastmodified'] = isset($result['lastmodified'])
  							? $result['lastmodified']
  							: null;
  						$this->props['displayname'] = $result['displayname'];
  						$this->props['permissions'] = $result['permissions'];
  					} else {
  						\OCP\Util::writeLog('contacts', __METHOD__
  							. ' Could not get vcard or carddata: '
  							. $this->getId()
  							. print_r($result, true), \OCP\Util::DEBUG);
  						return false;
  					}
  				} else {
  					\OCP\Util::writeLog('contacts', __METHOD__.' Error getting contact: ' . $this->getId(), \OCP\Util::DEBUG);
  				}
6d9380f96   Cédric Dupont   Update sources OC...
366
  			} elseif (isset($this->props['carddata'])) {
d1bafeea1   Kload   [fix] Upgrade to ...
367
368
369
370
371
372
373
  				$data = $this->props['carddata'];
  			}
  			try {
  				$obj = \Sabre\VObject\Reader::read(
  					$data,
  					\Sabre\VObject\Reader::OPTION_IGNORE_INVALID_LINES
  				);
6d9380f96   Cédric Dupont   Update sources OC...
374
375
  				if ($obj) {
  					foreach ($obj->children as $child) {
d1bafeea1   Kload   [fix] Upgrade to ...
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
  						$this->add($child);
  					}
  					$this->setRetrieved(true);
  					$this->setSaved(true);
  				} else {
  					\OCP\Util::writeLog('contacts', __METHOD__.' Error reading: ' . print_r($data, true), \OCP\Util::DEBUG);
  					return false;
  				}
  			} catch (\Exception $e) {
  				\OCP\Util::writeLog('contacts', __METHOD__ .
  					' Error parsing carddata  for: ' . $this->getId() . ' ' . $e->getMessage(),
  						\OCP\Util::ERROR);
  				return false;
  			}
  		}
  		return true;
  	}
  
  	/**
6d9380f96   Cédric Dupont   Update sources OC...
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
  	 * Get the PHOTO or LOGO
  	 *
  	 * @return \OCP\Image|null
  	 */
  	public function getPhoto() {
  		$image = new \OCP\Image();
  
  		if (isset($this->PHOTO) && $image->loadFromBase64((string)$this->PHOTO)) {
  			return $image;
  		} elseif (isset($this->LOGO) && $image->loadFromBase64((string)$this->LOGO)) {
  			return $image;
  		}
  	}
  
  	/**
  	 * Set the contact photo.
  	 *
  	 * @param \OCP\Image $photo
  	 */
  	public function setPhoto(\OCP\Image $photo) {
  		// For vCard 3.0 the type must be e.g. JPEG or PNG
  		// For version 4.0 the full mimetype should be used.
  		// https://tools.ietf.org/html/rfc2426#section-3.1.4
  		if (strval($this->VERSION) === '4.0') {
  			$type = $photo->mimeType();
  		} else {
  			$type = explode('/', $photo->mimeType());
  			$type = strtoupper(array_pop($type));
  		}
  		if (isset($this->PHOTO)) {
  			$property = $this->PHOTO;
  			if (!$property) {
  				return false;
  			}
  			$property->setValue(strval($photo));
  			$property->parameters = array();
  			$property->parameters[]
  				= new \Sabre\VObject\Parameter('ENCODING', 'b');
  			$property->parameters[]
  				= new \Sabre\VObject\Parameter('TYPE', $photo->mimeType());
  			$this->PHOTO = $property;
  		} else {
  			$this->add('PHOTO',
  				strval($photo), array('ENCODING' => 'b',
  				'TYPE' => $type));
  			// TODO: Fix this hack
  			$this->setSaved(false);
  		}
  
  		return true;
  
  	}
  
  	/**
d1bafeea1   Kload   [fix] Upgrade to ...
449
450
451
452
453
454
455
456
457
  	* Get a property index in the contact by the checksum of its serialized value
  	*
  	* @param string $checksum An 8 char m5d checksum.
  	* @return \Sabre\VObject\Property Property by reference
  	* @throws An exception with error code 404 if the property is not found.
  	*/
  	public function getPropertyIndexByChecksum($checksum) {
  		$this->retrieve();
  		$idx = 0;
6d9380f96   Cédric Dupont   Update sources OC...
458
459
  		foreach ($this->children as $i => &$property) {
  			if (substr(md5($property->serialize()), 0, 8) == $checksum ) {
d1bafeea1   Kload   [fix] Upgrade to ...
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
  				return $idx;
  			}
  			$idx += 1;
  		}
  		throw new \Exception(self::$l10n->t('Property not found'), 404);
  	}
  
  	/**
  	* Get a property by the checksum of its serialized value
  	*
  	* @param string $checksum An 8 char m5d checksum.
  	* @return \Sabre\VObject\Property Property by reference
  	* @throws An exception with error code 404 if the property is not found.
  	*/
  	public function getPropertyByChecksum($checksum) {
  		$this->retrieve();
6d9380f96   Cédric Dupont   Update sources OC...
476
477
  		foreach ($this->children as $i => &$property) {
  			if (substr(md5($property->serialize()), 0, 8) == $checksum ) {
d1bafeea1   Kload   [fix] Upgrade to ...
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
  				return $property;
  			}
  		}
  		throw new \Exception(self::$l10n->t('Property not found'), 404);
  	}
  
  	/**
  	* Delete a property by the checksum of its serialized value
  	* It is up to the caller to call ->save()
  	*
  	* @param string $checksum An 8 char m5d checksum.
  	* @throws @see getPropertyByChecksum
  	*/
  	public function unsetPropertyByChecksum($checksum) {
  		$idx = $this->getPropertyIndexByChecksum($checksum);
  		unset($this->children[$idx]);
  		$this->setSaved(false);
  	}
  
  	/**
  	* Set a property by the checksum of its serialized value
  	* It is up to the caller to call ->save()
  	*
  	* @param string $checksum An 8 char m5d checksum.
  	* @param string $name Property name
  	* @param mixed $value
  	* @param array $parameters
  	* @throws @see getPropertyByChecksum
  	* @return string new checksum
  	*/
  	public function setPropertyByChecksum($checksum, $name, $value, $parameters=array()) {
6d9380f96   Cédric Dupont   Update sources OC...
509
  		if ($checksum === 'new') {
d1bafeea1   Kload   [fix] Upgrade to ...
510
511
512
513
514
  			$property = Property::create($name);
  			$this->add($property);
  		} else {
  			$property = $this->getPropertyByChecksum($checksum);
  		}
6d9380f96   Cédric Dupont   Update sources OC...
515
  		switch ($name) {
d1bafeea1   Kload   [fix] Upgrade to ...
516
517
518
519
520
521
522
523
524
525
526
527
  			case 'EMAIL':
  				$value = strtolower($value);
  				$property->setValue($value);
  				break;
  			case 'ADR':
  				if(is_array($value)) {
  					$property->setParts($value);
  				} else {
  					$property->setValue($value);
  				}
  				break;
  			case 'IMPP':
6d9380f96   Cédric Dupont   Update sources OC...
528
  				if (is_null($parameters) || !isset($parameters['X-SERVICE-TYPE'])) {
d1bafeea1   Kload   [fix] Upgrade to ...
529
530
531
  					throw new \InvalidArgumentException(self::$l10n->t(' Missing IM parameter for: ') . $name. ' ' . $value, 412);
  				}
  				$serviceType = $parameters['X-SERVICE-TYPE'];
6d9380f96   Cédric Dupont   Update sources OC...
532
  				if (is_array($serviceType)) {
d1bafeea1   Kload   [fix] Upgrade to ...
533
534
535
  					$serviceType = $serviceType[0];
  				}
  				$impp = Utils\Properties::getIMOptions($serviceType);
6d9380f96   Cédric Dupont   Update sources OC...
536
  				if (is_null($impp)) {
d1bafeea1   Kload   [fix] Upgrade to ...
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
  					throw new \UnexpectedValueException(self::$l10n->t('Unknown IM: ') . $serviceType, 415);
  				}
  				$value = $impp['protocol'] . ':' . $value;
  				$property->setValue($value);
  				break;
  			default:
  				\OCP\Util::writeLog('contacts', __METHOD__.' adding: '.$name. ' ' . $value, \OCP\Util::DEBUG);
  				$property->setValue($value);
  				break;
  		}
  		$this->setParameters($property, $parameters, true);
  		$this->setSaved(false);
  		return substr(md5($property->serialize()), 0, 8);
  	}
  
  	/**
  	* Set a property by the property name.
  	* It is up to the caller to call ->save()
  	*
  	* @param string $name Property name
  	* @param mixed $value
  	* @param array $parameters
  	* @return bool
  	*/
  	public function setPropertyByName($name, $value, $parameters=array()) {
  		// TODO: parameters are ignored for now.
6d9380f96   Cédric Dupont   Update sources OC...
563
  		switch ($name) {
d1bafeea1   Kload   [fix] Upgrade to ...
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
  			case 'BDAY':
  				try {
  					$date = New \DateTime($value);
  				} catch(\Exception $e) {
  					\OCP\Util::writeLog('contacts',
  						__METHOD__.' DateTime exception: ' . $e->getMessage(),
  						\OCP\Util::ERROR
  					);
  					return false;
  				}
  				$value = $date->format('Y-m-d');
  				$this->BDAY = $value;
  				$this->BDAY->add('VALUE', 'DATE');
  				//\OCP\Util::writeLog('contacts', __METHOD__.' BDAY: '.$this->BDAY->serialize(), \OCP\Util::DEBUG);
  				break;
  			case 'CATEGORIES':
  			case 'N':
  			case 'ORG':
  				$property = $this->select($name);
6d9380f96   Cédric Dupont   Update sources OC...
583
  				if (count($property) === 0) {
d1bafeea1   Kload   [fix] Upgrade to ...
584
585
586
587
588
589
  					$property = \Sabre\VObject\Property::create($name);
  					$this->add($property);
  				} else {
  					// Actually no idea why this works
  					$property = array_shift($property);
  				}
6d9380f96   Cédric Dupont   Update sources OC...
590
  				if (is_array($value)) {
d1bafeea1   Kload   [fix] Upgrade to ...
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
  					$property->setParts($value);
  				} else {
  					$this->{$name} = $value;
  				}
  				break;
  			default:
  				\OCP\Util::writeLog('contacts', __METHOD__.' adding: '.$name. ' ' . $value, \OCP\Util::DEBUG);
  				$this->{$name} = $value;
  				break;
  		}
  		$this->setSaved(false);
  		return true;
  	}
  
  	protected function setParameters($property, $parameters, $reset = false) {
6d9380f96   Cédric Dupont   Update sources OC...
606
  		if (!$parameters) {
d1bafeea1   Kload   [fix] Upgrade to ...
607
608
  			return;
  		}
6d9380f96   Cédric Dupont   Update sources OC...
609
  		if ($reset) {
d1bafeea1   Kload   [fix] Upgrade to ...
610
611
612
  			$property->parameters = array();
  		}
  		//debug('Setting parameters: ' . print_r($parameters, true));
6d9380f96   Cédric Dupont   Update sources OC...
613
  		foreach ($parameters as $key => $parameter) {
d1bafeea1   Kload   [fix] Upgrade to ...
614
  			//debug('Adding parameter: ' . $key);
6d9380f96   Cédric Dupont   Update sources OC...
615
616
617
618
619
  			if (is_array($parameter)) {
  				foreach ($parameter as $val) {
  					if (is_array($val)) {
  						foreach ($val as $val2) {
  							if (trim($key) && trim($val2)) {
d1bafeea1   Kload   [fix] Upgrade to ...
620
621
622
623
624
  								//debug('Adding parameter: '.$key.'=>'.print_r($val2, true));
  								$property->add($key, strip_tags($val2));
  							}
  						}
  					} else {
6d9380f96   Cédric Dupont   Update sources OC...
625
  						if (trim($key) && trim($val)) {
d1bafeea1   Kload   [fix] Upgrade to ...
626
627
628
629
630
631
  							//debug('Adding parameter: '.$key.'=>'.print_r($val, true));
  							$property->add($key, strip_tags($val));
  						}
  					}
  				}
  			} else {
6d9380f96   Cédric Dupont   Update sources OC...
632
  				if (trim($key) && trim($parameter)) {
d1bafeea1   Kload   [fix] Upgrade to ...
633
634
635
636
637
638
639
640
  					//debug('Adding parameter: '.$key.'=>'.print_r($parameter, true));
  					$property->add($key, strip_tags($parameter));
  				}
  			}
  		}
  	}
  
  	public function lastModified() {
6d9380f96   Cédric Dupont   Update sources OC...
641
  		if (!isset($this->props['lastmodified']) && !$this->isRetrieved()) {
d1bafeea1   Kload   [fix] Upgrade to ...
642
643
644
645
646
647
648
649
650
651
652
653
  			$this->retrieve();
  		}
  		return isset($this->props['lastmodified'])
  			? $this->props['lastmodified']
  			: null;
  	}
  
  	/**
  	 * Merge in data from a multi-dimentional array
  	 *
  	 * NOTE: The data has actually already been merged client side!
  	 * NOTE: The only properties coming from the web client are the ones
6d9380f96   Cédric Dupont   Update sources OC...
654
  	 * defined in \OCA\Contacts\Utils\Properties::$indexProperties and
d1bafeea1   Kload   [fix] Upgrade to ...
655
656
657
658
659
660
661
662
663
664
  	 * UID is skipped for obvious reasons, and PHOTO is currently not updated.
  	 * The data array has this structure:
  	 *
  	 * array(
  	 * 	'EMAIL' => array(array('value' => 'johndoe@example.com', 'parameters' = array('TYPE' => array('HOME','VOICE'))))
  	 * );
  	 * @param array $data
  	 * @return bool
  	 */
  	public function mergeFromArray(array $data) {
6d9380f96   Cédric Dupont   Update sources OC...
665
666
  		foreach ($data as $name => $properties) {
  			if (in_array($name, array('PHOTO', 'UID'))) {
d1bafeea1   Kload   [fix] Upgrade to ...
667
668
  				continue;
  			}
6d9380f96   Cédric Dupont   Update sources OC...
669
  			if (!is_array($properties)) {
d1bafeea1   Kload   [fix] Upgrade to ...
670
671
  				\OCP\Util::writeLog('contacts', __METHOD__.' not an array?: ' .$name. ' '.print_r($properties, true), \OCP\Util::DEBUG);
  			}
6d9380f96   Cédric Dupont   Update sources OC...
672
  			if (in_array($name, Utils\Properties::$multiProperties)) {
d1bafeea1   Kload   [fix] Upgrade to ...
673
674
  				unset($this->{$name});
  			}
6d9380f96   Cédric Dupont   Update sources OC...
675
  			foreach ($properties as $parray) {
d1bafeea1   Kload   [fix] Upgrade to ...
676
  				\OCP\Util::writeLog('contacts', __METHOD__.' adding: ' .$name. ' '.print_r($parray['value'], true) . ' ' . print_r($parray['parameters'], true), \OCP\Util::DEBUG);
6d9380f96   Cédric Dupont   Update sources OC...
677
  				if (in_array($name, Utils\Properties::$multiProperties)) {
d1bafeea1   Kload   [fix] Upgrade to ...
678
679
680
681
  					// TODO: wrap in try/catch, check return value
  					$this->setPropertyByChecksum('new', $name, $parray['value'], $parray['parameters']);
  				} else {
  					// TODO: Check return value
6d9380f96   Cédric Dupont   Update sources OC...
682
  					if (!isset($this->{$name})) {
d1bafeea1   Kload   [fix] Upgrade to ...
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
  						$this->setPropertyByName($name, $parray['value'], $parray['parameters']);
  					}
  				}
  			}
  		}
  		$this->setSaved(false);
  		return true;
  	}
  
  	/**
  	 * Merge in data from another VCard
  	 * Used on import if a matching UID is found. Returns true if any updates
  	 * take place, otherwise false.
  	 *
  	 * @param VCard $vcard
  	 * @return bool
  	 */
  	public function mergeFromVCard(VCard $vcard) {
  		$updated = false;
6d9380f96   Cédric Dupont   Update sources OC...
702
703
  		foreach ($vcard->children as $property) {
  			if (in_array($property->name, array('REV', 'UID'))) {
d1bafeea1   Kload   [fix] Upgrade to ...
704
705
706
  				continue;
  			}
  			\OCP\Util::writeLog('contacts', __METHOD__.' merging: ' .$property->name, \OCP\Util::DEBUG);
6d9380f96   Cédric Dupont   Update sources OC...
707
  			if (in_array($property->name, Utils\Properties::$multiProperties)) {
d1bafeea1   Kload   [fix] Upgrade to ...
708
  				$ownproperties = $this->select($property->name);
6d9380f96   Cédric Dupont   Update sources OC...
709
  				if (count($ownproperties) === 0) {
d1bafeea1   Kload   [fix] Upgrade to ...
710
711
712
713
714
  					// We don't have any instances of this property, so just add it.
  					$this->add($property);
  					$updated = true;
  					continue;
  				} else {
6d9380f96   Cédric Dupont   Update sources OC...
715
716
  					foreach ($ownproperties as $ownproperty) {
  						if (strtolower($property->value) === strtolower($ownproperty->value)) {
d1bafeea1   Kload   [fix] Upgrade to ...
717
718
719
720
721
722
723
724
  							// We already have this property, so skip both loops
  							continue 2;
  						}
  					}
  					$this->add($property);
  					$updated = true;
  				}
  			} else {
6d9380f96   Cédric Dupont   Update sources OC...
725
  				if(!isset($this->{$property->name})) {
d1bafeea1   Kload   [fix] Upgrade to ...
726
727
728
729
730
731
732
733
734
735
736
737
  					$this->add($property);
  					$updated = true;
  				} else {
  					$this->setPropertyByName($property->name, $property->value, $property->parameters);
  				}
  			}
  		}
  
  		$this->setSaved(!$updated);
  
  		return $updated;
  	}
6d9380f96   Cédric Dupont   Update sources OC...
738
739
  	public function __get($key) {
  		if (!$this->isRetrieved()) {
d1bafeea1   Kload   [fix] Upgrade to ...
740
741
742
743
744
  			$this->retrieve();
  		}
  
  		return parent::__get($key);
  	}
6d9380f96   Cédric Dupont   Update sources OC...
745
746
  	public function __isset($key) {
  		if (!$this->isRetrieved()) {
d1bafeea1   Kload   [fix] Upgrade to ...
747
748
749
750
751
752
753
  			$this->retrieve();
  		}
  
  		return parent::__isset($key);
  	}
  
  	public function __set($key, $value) {
6d9380f96   Cédric Dupont   Update sources OC...
754
  		if (!$this->isRetrieved()) {
d1bafeea1   Kload   [fix] Upgrade to ...
755
756
757
  			$this->retrieve();
  		}
  		parent::__set($key, $value);
6d9380f96   Cédric Dupont   Update sources OC...
758
  		if ($key === 'FN') {
d1bafeea1   Kload   [fix] Upgrade to ...
759
760
761
762
763
764
  			$this->props['displayname'] = $value;
  		}
  		$this->setSaved(false);
  	}
  
  	public function __unset($key) {
6d9380f96   Cédric Dupont   Update sources OC...
765
  		if (!$this->isRetrieved()) {
d1bafeea1   Kload   [fix] Upgrade to ...
766
767
768
  			$this->retrieve();
  		}
  		parent::__unset($key);
6d9380f96   Cédric Dupont   Update sources OC...
769
  		if ($key === 'PHOTO') {
d1bafeea1   Kload   [fix] Upgrade to ...
770
771
772
773
774
  			Properties::cacheThumbnail(
  				$this->getBackend()->name,
  				$this->getParent()->getId(),
  				$this->getId(),
  				null,
6d9380f96   Cédric Dupont   Update sources OC...
775
  				$this,
d1bafeea1   Kload   [fix] Upgrade to ...
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
  				array('remove' => true)
  			);
  		}
  		$this->setSaved(false);
  	}
  
  	public function setRetrieved($state) {
  		$this->props['retrieved'] = $state;
  	}
  
  	public function isRetrieved() {
  		return $this->props['retrieved'];
  	}
  
  	public function setSaved($state = true) {
  		$this->props['saved'] = $state;
  	}
  
  	public function isSaved() {
  		return $this->props['saved'];
  	}
  
  	/**
  	 * Generate an event to show in the calendar
  	 *
  	 * @return \Sabre\VObject\Component\VCalendar|null
  	 */
  	public function getBirthdayEvent() {
6d9380f96   Cédric Dupont   Update sources OC...
804
  		if (!isset($this->BDAY)) {
d1bafeea1   Kload   [fix] Upgrade to ...
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
  			return;
  		}
  		$birthday = $this->BDAY;
  		if ((string)$birthday) {
  			$title = str_replace('{name}',
  				strtr((string)$this->FN, array('\,' => ',', '\;' => ';')),
  				App::$l10n->t('{name}\'s Birthday')
  			);
  			try {
  				$date = new \DateTime($birthday);
  			} catch(\Exception $e) {
  				return;
  			}
  			$vevent = \Sabre\VObject\Component::create('VEVENT');
  			$vevent->add('DTSTART');
  			$vevent->DTSTART->setDateTime(
  				$date,
  				\Sabre\VObject\Property\DateTime::DATE
  			);
  			$vevent->add('DURATION', 'P1D');
  			$vevent->{'UID'} = $this->UID;
  			$vevent->{'RRULE'} = 'FREQ=YEARLY';
  			$vevent->{'SUMMARY'} = $title . ' (' . $date->format('Y') . ')';
  			$vcal = \Sabre\VObject\Component::create('VCALENDAR');
  			$vcal->VERSION = '2.0';
  			$appinfo = \OCP\App::getAppInfo('contacts');
  			$appversion = \OCP\App::getAppVersion('contacts');
  			$vcal->PRODID = '-//ownCloud//NONSGML '.$appinfo['name'].' '.$appversion.'//EN';
  			$vcal->add($vevent);
  			return $vcal;
  		}
  	}
  
  }