. * */ namespace OCA\Contacts; use Sabre\VObject\Property; /** * Subclass this class or implement IPIMObject interface for PIM objects */ class Contact extends VObject\VCard implements IPIMObject { const THUMBNAIL_PREFIX = 'contact-thumbnail-'; const THUMBNAIL_SIZE = 28; /** * The name of the object type in this case VCARD. * * This is used when serializing the object. * * @var string */ public $name = 'VCARD'; protected $props = array(); /** * Create a new Contact object * * @param AddressBook $parent * @param AbstractBackend $backend * @param mixed $data */ public function __construct($parent, $backend, $data = null) { //\OCP\Util::writeLog('contacts', __METHOD__ . ' ' . print_r($data, true), \OCP\Util::DEBUG); //\OCP\Util::writeLog('contacts', __METHOD__, \OCP\Util::DEBUG); $this->props['parent'] = $parent; $this->props['backend'] = $backend; $this->props['retrieved'] = false; $this->props['saved'] = false; if(!is_null($data)) { if($data instanceof VObject\VCard) { foreach($data->children as $child) { $this->add($child); $this->setRetrieved(true); } } elseif(is_array($data)) { foreach($data as $key => $value) { switch($key) { case 'id': $this->props['id'] = $value; break; 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': $this->props['displayname'] = $value; $this->FN = $value; break; } } } } } /** * @return array|null */ public function getMetaData() { if(!isset($this->props['displayname'])) { if(!$this->retrieve()) { \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, ); } /** * @return string|null */ public function getOwner() { return $this->props['parent']->getOwner(); } /** * @return string|null */ public function getId() { return isset($this->props['id']) ? $this->props['id'] : null; } /** * @return string|null */ function getDisplayName() { return isset($this->props['displayname']) ? $this->props['displayname'] : null; } /** * @return string|null */ public function getURI() { return isset($this->props['uri']) ? $this->props['uri'] : null; } /** * If this object is part of a collection return a reference * to the parent object, otherwise return null. * @return IPIMObject|null */ function getParent() { return $this->props['parent']; } function getBackend() { return $this->props['backend']; } /** CRUDS permissions (Create, Read, Update, Delete, Share) * * @return integer */ function getPermissions() { return $this->props['parent']->getPermissions(); } /** * @param integer $permission * @return bool */ 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 * * @return bool */ public function delete() { return $this->props['backend']->deleteContact( $this->getParent()->getId(), $this->getId() ); } /** * Save the contact data to backend * * @return bool */ public function save($force = false) { if($this->isSaved() && !$force) { \OCP\Util::writeLog('contacts', __METHOD__.' Already saved: ' . print_r($this->props, true), \OCP\Util::DEBUG); return true; } if(isset($this->FN)) { $this->props['displayname'] = (string)$this->FN; } if($this->getId()) { if($this->props['backend'] ->updateContact( $this->getParent()->getId(), $this->getId(), $this->serialize() ) ) { $this->props['lastmodified'] = time(); $this->setSaved(true); return true; } else { return false; } } else { //print(__METHOD__.' ' . print_r($this->getParent(), true)); $this->props['id'] = $this->props['backend']->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. */ public function retrieve() { //error_log(__METHOD__); //\OCP\Util::writeLog('contacts', __METHOD__.' ' . print_r($this->props, true), \OCP\Util::DEBUG); if($this->isRetrieved()) { //\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->children = $this->props['vcard']->children(); unset($this->props['vcard']); return true; } elseif(!isset($this->props['carddata'])) { $result = $this->props['backend']->getContact( $this->getParent()->getId(), $this->id ); if($result) { if(isset($result['vcard']) && $result['vcard'] instanceof VObject\VCard) { foreach($result['vcard']->children() as $child) { $this->add($child); } $this->setRetrieved(true); return true; } elseif(isset($result['carddata'])) { // Save internal values $data = $result['carddata']; $this->props['carddata'] = $result['carddata']; $this->props['lastmodified'] = $result['lastmodified']; $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); } } elseif(isset($this->props['carddata'])) { $data = $this->props['carddata']; //error_log(__METHOD__.' data: '.print_r($data, true)); } try { $obj = \Sabre\VObject\Reader::read( $data, \Sabre\VObject\Reader::OPTION_IGNORE_INVALID_LINES ); if($obj) { foreach($obj->children as $child) { $this->add($child); } $this->setRetrieved(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; } /** * 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; foreach($this->children as $i => &$property) { if(substr(md5($property->serialize()), 0, 8) == $checksum ) { return $idx; } $idx += 1; } throw new Exception('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(); foreach($this->children as $i => &$property) { if(substr(md5($property->serialize()), 0, 8) == $checksum ) { return $property; } } throw new \Exception('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()) { if($checksum === 'new') { $property = Property::create($name); $this->add($property); } else { $property = $this->getPropertyByChecksum($checksum); } switch($name) { case 'EMAIL': $value = strtolower($value); $property->setValue($value); break; case 'ADR': if(is_array($value)) { $property->setParts($value); } else { debug('Saving ADR ' . $value); $property->setValue($value); } break; case 'IMPP': if(is_null($parameters) || !isset($parameters['X-SERVICE-TYPE'])) { bailOut(App::$l10n->t('Missing IM parameter.')); } $impp = Utils\Properties::getIMOptions($parameters['X-SERVICE-TYPE']); if(is_null($impp)) { bailOut(App::$l10n->t('Unknown IM: '.$parameters['X-SERVICE-TYPE'])); } $value = $impp['protocol'] . ':' . $value; $property->setValue($value); break; default: $property->setValue($value); break; } $this->setParameters($property, $parameters); $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. switch($name) { 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 'N': $property = $this->select($name); if(count($property) === 0) { $property = VObject\Property::create($name); $this->add($property); } else { // Actually no idea why this works $property = array_shift($property); } if(is_array($value)) { $property->setParts($value); } else { $this->N = $value; } break; default: $this->{$name} = $value; break; } $this->setSaved(false); return true; } protected function setParameters($property, $parameters, $reset = false) { if(!$parameters) { return; } if($reset) { $property->parameters = array(); } debug('Setting parameters: ' . print_r($parameters, true)); foreach($parameters as $key => $parameter) { debug('Adding parameter: ' . $key); if(is_array($parameter)) { foreach($parameter as $val) { if(is_array($val)) { foreach($val as $val2) { if(trim($key) && trim($val2)) { debug('Adding parameter: '.$key.'=>'.print_r($val2, true)); $property->add($key, strip_tags($val2)); } } } else { if(trim($key) && trim($val)) { debug('Adding parameter: '.$key.'=>'.print_r($val, true)); $property->add($key, strip_tags($val)); } } } } else { if(trim($key) && trim($parameter)) { debug('Adding parameter: '.$key.'=>'.print_r($parameter, true)); $property->add($key, strip_tags($parameter)); } } } } public function lastModified() { if(!isset($this->props['lastmodified'])) { $this->retrieve(); } return isset($this->props['lastmodified']) ? $this->props['lastmodified'] : null; } public function cacheThumbnail(\OC_Image $image = null) { $key = $this->getBackend()->name . '::' . $this->getParent()->getId() . '::' . $this->getId(); if(\OC_Cache::hasKey(self::THUMBNAIL_PREFIX . $key) && $image === null) { return \OC_Cache::get(self::THUMBNAIL_PREFIX . $key); } if(is_null($image)) { $this->retrieve(); $image = new \OC_Image(); if(!isset($this->PHOTO) && !isset($this->LOGO)) { return false; } if(!$image->loadFromBase64((string)$this->PHOTO)) { if(!$image->loadFromBase64((string)$this->LOGO)) { return false; } } } if(!$image->centerCrop()) { \OCP\Util::writeLog('contacts', 'thumbnail.php. Couldn\'t crop thumbnail for ID ' . $key, \OCP\Util::ERROR); return false; } if(!$image->resize(self::THUMBNAIL_SIZE)) { \OCP\Util::writeLog('contacts', 'thumbnail.php. Couldn\'t resize thumbnail for ID ' . $key, \OCP\Util::ERROR); return false; } // Cache for around a month \OC_Cache::set(self::THUMBNAIL_PREFIX . $key, $image->data(), 3000000); \OCP\Util::writeLog('contacts', 'Caching ' . $key, \OCP\Util::DEBUG); return \OC_Cache::get(self::THUMBNAIL_PREFIX . $key); } public function __set($key, $value) { parent::__set($key, $value); $this->setSaved(false); } public function __unset($id) { parent::__unset($key, $value); $this->setSaved(false); } protected function setRetrieved($state) { $this->props['retrieved'] = $state; } protected function isRetrieved() { return $this->props['retrieved']; } protected function setSaved($state) { $this->props['saved'] = $state; } protected function isSaved() { return $this->props['saved']; } }