From 65957c8737e2d8ae52bd2e623d6afcfa3bdf79f4 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Tue, 19 Feb 2013 02:21:29 +0100 Subject: [PATCH 001/236] Contacts: Move vCard validation and repair to SabreDAV subclasses. --- appinfo/app.php | 8 +- appinfo/remote.php | 2 +- import.php | 79 +++++++++------ lib/app.php | 1 + lib/sabre/plugin.php | 68 +++++++++++++ lib/sabre/vcard.php | 234 +++++++++++++++++++++++++++++++++++++++++++ lib/vcard.php | 136 +------------------------ 7 files changed, 362 insertions(+), 166 deletions(-) create mode 100644 lib/sabre/plugin.php create mode 100644 lib/sabre/vcard.php diff --git a/appinfo/app.php b/appinfo/app.php index 4dc70a9f..a80b9f8b 100644 --- a/appinfo/app.php +++ b/appinfo/app.php @@ -6,12 +6,18 @@ OC::$CLASSPATH['OCA\Contacts\Hooks'] = 'contacts/lib/hooks.php'; OC::$CLASSPATH['OCA\Contacts\Share_Backend_Contact'] = 'contacts/lib/share/contact.php'; OC::$CLASSPATH['OCA\Contacts\Share_Backend_Addressbook'] = 'contacts/lib/share/addressbook.php'; OC::$CLASSPATH['OCA\Contacts\AddressbookProvider'] = 'contacts/lib/addressbookprovider.php'; +OC::$CLASSPATH['OCA\Contacts\CardDAVPlugin'] = 'contacts/lib/sabre/plugin.php'; +OC::$CLASSPATH['OCA\Contacts\VCardObject'] = 'contacts/lib/sabre/vcard.php'; OC::$CLASSPATH['OC_Connector_Sabre_CardDAV'] = 'contacts/lib/sabre/backend.php'; OC::$CLASSPATH['OC_Connector_Sabre_CardDAV_AddressBookRoot'] = 'contacts/lib/sabre/addressbookroot.php'; OC::$CLASSPATH['OC_Connector_Sabre_CardDAV_UserAddressBooks'] = 'contacts/lib/sabre/useraddressbooks.php'; OC::$CLASSPATH['OC_Connector_Sabre_CardDAV_AddressBook'] = 'contacts/lib/sabre/addressbook.php'; OC::$CLASSPATH['OC_Connector_Sabre_CardDAV_Card'] = 'contacts/lib/sabre/card.php'; -OC::$CLASSPATH['OCA\\Contacts\\SearchProvider'] = 'contacts/lib/search.php'; +OC::$CLASSPATH['OCA\Contacts\SearchProvider'] = 'contacts/lib/search.php'; + +require_once __DIR__ . '/../lib/sabre/vcard.php'; +Sabre\VObject\Component::$classMap['VCARD'] = 'OCA\Contacts\VCardObject'; + OCP\Util::connectHook('OC_User', 'post_createUser', 'OCA\Contacts\Hooks', 'createUser'); OCP\Util::connectHook('OC_User', 'post_deleteUser', 'OCA\Contacts\Hooks', 'deleteUser'); OCP\Util::connectHook('OC_Calendar', 'getEvents', 'OCA\Contacts\Hooks', 'getBirthdayEvents'); diff --git a/appinfo/remote.php b/appinfo/remote.php index 13914096..dde3a37c 100644 --- a/appinfo/remote.php +++ b/appinfo/remote.php @@ -54,7 +54,7 @@ $server->httpRequest = $requestBackend; $server->setBaseUri($baseuri); // Add plugins $server->addPlugin(new Sabre_DAV_Auth_Plugin($authBackend, 'ownCloud')); -$server->addPlugin(new Sabre_CardDAV_Plugin()); +$server->addPlugin(new OCA\Contacts\CardDAVPlugin()); $server->addPlugin(new Sabre_DAVACL_Plugin()); $server->addPlugin(new Sabre_DAV_Browser_Plugin(false)); // Show something in the Browser, but no upload $server->addPlugin(new Sabre_CardDAV_VCFExportPlugin()); diff --git a/import.php b/import.php index 0942c9fc..d79c3a8d 100644 --- a/import.php +++ b/import.php @@ -5,11 +5,16 @@ * later. * See the COPYING-README file. */ + +namespace OCA\Contacts; + +use Sabre\VObject; + //check for addressbooks rights or create new one ob_start(); -OCP\JSON::checkLoggedIn(); -OCP\App::checkAppEnabled('contacts'); +\OCP\JSON::checkLoggedIn(); +\OCP\App::checkAppEnabled('contacts'); session_write_close(); $nl = "\n"; @@ -18,42 +23,42 @@ global $progresskey; $progresskey = 'contacts.import-' . (isset($_GET['progresskey'])?$_GET['progresskey']:''); if (isset($_GET['progress']) && $_GET['progress']) { - echo OC_Cache::get($progresskey); + echo \OC_Cache::get($progresskey); die; } function writeProgress($pct) { global $progresskey; - OC_Cache::set($progresskey, $pct, 300); + \OC_Cache::set($progresskey, $pct, 300); } writeProgress('10'); $view = $file = null; if(isset($_POST['fstype']) && $_POST['fstype'] == 'OC_FilesystemView') { - $view = OCP\Files::getStorage('contacts'); + $view = \OCP\Files::getStorage('contacts'); $file = $view->file_get_contents('/imports/' . $_POST['file']); } else { $file = \OC\Files\Filesystem::file_get_contents($_POST['path'] . '/' . $_POST['file']); } if(!$file) { - OCP\JSON::error(array('data' => array('message' => 'Import file was empty.'))); + \OCP\JSON::error(array('data' => array('message' => 'Import file was empty.'))); exit(); } if(isset($_POST['method']) && $_POST['method'] == 'new') { - $id = OCA\Contacts\Addressbook::add(OCP\USER::getUser(), + $id = Addressbook::add(\OCP\USER::getUser(), $_POST['addressbookname']); if(!$id) { - OCP\JSON::error( + \OCP\JSON::error( array( 'data' => array('message' => 'Error creating address book.') ) ); exit(); } - OCA\Contacts\Addressbook::setActive($id, 1); + Addressbook::setActive($id, 1); }else{ $id = $_POST['id']; if(!$id) { - OCP\JSON::error( + \OCP\JSON::error( array( 'data' => array( 'message' => 'Error getting the ID of the address book.', @@ -64,9 +69,9 @@ if(isset($_POST['method']) && $_POST['method'] == 'new') { exit(); } try { - OCA\Contacts\Addressbook::find($id); // is owner access check - } catch(Exception $e) { - OCP\JSON::error( + Addressbook::find($id); // is owner access check + } catch(\Exception $e) { + \OCP\JSON::error( array( 'data' => array( 'message' => $e->getMessage(), @@ -104,7 +109,7 @@ $imported = 0; $failed = 0; $partial = 0; if(!count($parts) > 0) { - OCP\JSON::error( + \OCP\JSON::error( array( 'data' => array( 'message' => 'No contacts to import in ' @@ -115,53 +120,61 @@ if(!count($parts) > 0) { ); if(isset($_POST['fstype']) && $_POST['fstype'] == 'OC_FilesystemView') { if(!$view->unlink('/imports/' . $_POST['file'])) { - OCP\Util::writeLog('contacts', + \OCP\Util::writeLog('contacts', 'Import: Error unlinking OC_FilesystemView ' . '/' . $_POST['file'], - OCP\Util::ERROR); + \OCP\Util::ERROR); } } exit(); } foreach($parts as $part) { try { - $vcard = Sabre\VObject\Reader::read($part); - } catch (Sabre\VObject\ParseException $e) { + $vcard = VObject\Reader::read($part); + } catch (VObject\ParseException $e) { try { - $vcard = Sabre\VObject\Reader::read($part, Sabre\VObject\Reader::OPTION_IGNORE_INVALID_LINES); + $vcard = VObject\Reader::read($part, VObject\Reader::OPTION_IGNORE_INVALID_LINES); $partial += 1; - OCP\Util::writeLog('contacts', + \OCP\Util::writeLog('contacts', 'Import: Retrying reading card. Error parsing VCard: ' . $e->getMessage(), - OCP\Util::ERROR); - } catch (Exception $e) { + \OCP\Util::ERROR); + } catch (\Exception $e) { $failed += 1; - OCP\Util::writeLog('contacts', + \OCP\Util::writeLog('contacts', 'Import: skipping card. Error parsing VCard: ' . $e->getMessage(), - OCP\Util::ERROR); + \OCP\Util::ERROR); continue; // Ditch cards that can't be parsed by Sabre. } } try { - OCA\Contacts\VCard::add($id, $vcard); + $vcard->validate(VCardObject::REPAIR|VCardObject::UPGRADE); + } catch (\Exception $e) { + OCP\Util::writeLog('contacts', __LINE__ . ' ' . + 'Error validating vcard: ' . $e->getMessage() . $nl . $vcard->serialize(), + \OCP\Util::ERROR); + $failed += 1; + } + try { + VCard::add($id, $vcard); $imported += 1; - } catch (Exception $e) { - OCP\Util::writeLog('contacts', - 'Error importing vcard: ' . $e->getMessage() . $nl . $vcard, - OCP\Util::ERROR); + } catch (\Exception $e) { + \OCP\Util::writeLog('contacts', __LINE__ . ' ' . + 'Error importing vcard: ' . $e->getMessage() . $nl . $vcard->serialize(), + \OCP\Util::ERROR); $failed += 1; } } //done the import writeProgress('100'); sleep(3); -OC_Cache::remove($progresskey); +\OC_Cache::remove($progresskey); if(isset($_POST['fstype']) && $_POST['fstype'] == 'OC_FilesystemView') { if(!$view->unlink('/imports/' . $_POST['file'])) { - OCP\Util::writeLog('contacts', + \OCP\Util::writeLog('contacts', 'Import: Error unlinking OC_FilesystemView ' . '/' . $_POST['file'], - OCP\Util::ERROR); + \OCP\Util::ERROR); } } -OCP\JSON::success( +\OCP\JSON::success( array( 'data' => array( 'imported'=>$imported, diff --git a/lib/app.php b/lib/app.php index 949ca9f1..dbb4feb8 100644 --- a/lib/app.php +++ b/lib/app.php @@ -161,6 +161,7 @@ class App { public static function getTypesOfProperty($prop) { $l = self::$l10n; switch($prop) { + case 'LABEL': case 'ADR': case 'IMPP': return array( diff --git a/lib/sabre/plugin.php b/lib/sabre/plugin.php new file mode 100644 index 00000000..500897eb --- /dev/null +++ b/lib/sabre/plugin.php @@ -0,0 +1,68 @@ +. + * + */ + +namespace OCA\Contacts; + +use Sabre\VObject; + +/** + * This class overrides Sabre_CardDAV_Plugin::validateVCard() to be able + * to import partially invalid vCards by ignoring invalid lines and to + * validate and upgrade using \OCA\Contacts\VCardObject. +*/ +class CardDAVPlugin extends \Sabre_CardDAV_Plugin { + + /** + * Checks if the submitted vCard data is in fact, valid. + * + * An exception is thrown if it's not. + * + * @param resource|string $data + * @return void + */ + protected function validateVCard(&$data) { + \OCP\Util::writeLog('contacts', __METHOD__, \OCP\Util::DEBUG); + + // If it's a stream, we convert it to a string first. + if (is_resource($data)) { + $data = stream_get_contents($data); + } + + // Converting the data to unicode, if needed. + $data = \Sabre_DAV_StringUtil::ensureUTF8($data); + + try { + $vobj = VObject\Reader::read($data, VObject\Reader::OPTION_IGNORE_INVALID_LINE); + } catch (VObject\ParseException $e) { + throw new \Sabre_DAV_Exception_UnsupportedMediaType('This resource only supports valid vcard data. Parse error: ' . $e->getMessage()); + } + + if ($vobj->name !== 'VCARD') { + throw new \Sabre_DAV_Exception_UnsupportedMediaType('This collection can only support vcard objects.'); + } + + $vobj->validate(VCardObject::REPAIR|VCardObject::UPGRADE); + $data = $vobj->serialize(); + } +} \ No newline at end of file diff --git a/lib/sabre/vcard.php b/lib/sabre/vcard.php new file mode 100644 index 00000000..d4e55bb7 --- /dev/null +++ b/lib/sabre/vcard.php @@ -0,0 +1,234 @@ +. + * + */ + +namespace OCA\Contacts; + +use Sabre\VObject; + +/** + * This class overrides \Sabre\VObject\Component\VCard::validate() to be add + * to import partially invalid vCards by ignoring invalid lines and to + * validate and upgrade using .... +*/ +class VCardObject extends VObject\Component\VCard { + + /** + * The following constants are used by the validate() method. + */ + const REPAIR = 1; + const UPGRADE = 2; + + /** + * VCards with version 2.1, 3.0 and 4.0 are found. + * + * If the VCARD doesn't know its version, 3.0 is assumed and if + * option UPGRADE is given it will be upgraded to version 3.0. + */ + const DEFAULT_VERSION = '3.0'; + + /** + * @brief Format property TYPE parameters for upgrading from v. 2.1 + * @param $property Reference to a \Sabre\VObject\Property. + * In version 2.1 e.g. a phone can be formatted like: TEL;HOME;CELL:123456789 + * This has to be changed to either TEL;TYPE=HOME,CELL:123456789 or TEL;TYPE=HOME;TYPE=CELL:123456789 - both are valid. + */ + protected function formatPropertyTypes(&$property) { + foreach($property->parameters as $key=>&$parameter) { + $types = App::getTypesOfProperty($property->name); + if(is_array($types) && in_array(strtoupper($parameter->name), array_keys($types)) + || strtoupper($parameter->name) == 'PREF') { + unset($property->parameters[$key]); + $property->add('TYPE', $parameter->name); + } + } + } + + /** + * @brief Decode properties for upgrading from v. 2.1 + * @param $property Reference to a \Sabre\VObject\Property. + * The only encoding allowed in version 3.0 is 'b' for binary. All encoded strings + * must therefore be decoded and the parameters removed. + */ + protected function decodeProperty(&$property) { + // Check out for encoded string and decode them :-[ + foreach($property->parameters as $key=>&$parameter) { + if(strtoupper($parameter->name) == 'ENCODING') { + // what other kinds of encodings could be used? + if(strtoupper($parameter->value) == 'QUOTED-PRINTABLE') { + // Decode quoted-printable and strip any control chars + // except \n and \r + $property->value = preg_replace( + '/[\x00-\x09\x0B\x0C\x0E-\x1F\x7F]/', + '', + quoted_printable_decode($property->value) + ); + if(function_exists('iconv')) { + $property->value = str_replace("\r\n", "\n", + iconv( + mb_detect_encoding( + $property->value, + 'UTF-8, ISO-8859-1' + ), 'utf-8', + $property->value)); + } else { + $property->value = str_replace("\r\n", "\n", + mb_convert_encoding( + $property->value, + 'UTF-8', + mb_detect_encoding($property->value, 'UTF-8, ISO-8859-1'), + $property->value)); + } + unset($property->parameters[$key]); + } + } elseif(strtoupper($parameter->name) == 'CHARSET') { + // TODO: Should probably use this while decoding. + unset($property->parameters[$key]); + } + } + } + + /** + * Validates the node for correctness. + * + * The following options are supported: + * - Node::REPAIR - If something is broken, and automatic repair may + * be attempted. + * - VCardObject::UPGRADE - If needed the vCard will be upgraded to version 3.0. + * + * An array is returned with warnings. + * + * Every item in the array has the following properties: + * * level - (number between 1 and 3 with severity information) + * * message - (human readable message) + * * node - (reference to the offending node) + * + * @param int $options + * @return array + */ + public function validate($options = 0) { + + $warnings = array(); + \OCP\Util::writeLog('contacts', __METHOD__.' options: '.$options, \OCP\Util::DEBUG); + + $version = $this->select('VERSION'); + if (count($version) !== 1) { + $warnings[] = array( + 'level' => 1, + 'message' => 'The VERSION property must appear in the VCARD component exactly 1 time', + 'node' => $this, + ); + if ($options & self::REPAIR) { + $this->VERSION = self::DEFAULT_VERSION; + if (!$options & self::UPGRADE) { + $options |= self::UPGRADE; + } + } + } else { + $version = (string)$this->VERSION; + if ($version!=='2.1' && $version!=='3.0' && $version!=='4.0') { + $warnings[] = array( + 'level' => 1, + 'message' => 'Only vcard version 4.0 (RFC6350), version 3.0 (RFC2426) or version 2.1 (icm-vcard-2.1) are supported.', + 'node' => $this, + ); + if ($options & self::REPAIR) { + $this->VERSION = self::DEFAULT_VERSION; + if (!$options & self::UPGRADE) { + $options |= self::UPGRADE; + } + } + } + + } + $fn = $this->select('FN'); + if (count($fn) !== 1) { + $warnings[] = array( + 'level' => 1, + 'message' => 'The FN property must appear in the VCARD component exactly 1 time', + 'node' => $this, + ); + if (($options & self::REPAIR) && count($fn) === 0) { + // We're going to try to see if we can use the contents of the + // N property. + if (isset($this->N)) { + $value = explode(';', (string)$this->N); + if (isset($value[1]) && $value[1]) { + $this->FN = $value[1] . ' ' . $value[0]; + } else { + $this->FN = $value[0]; + } + // Otherwise, the ORG property may work + } elseif (isset($this->ORG)) { + $this->FN = (string)$this->ORG; + } elseif (isset($this->EMAIL)) { + $this->FN = (string)$this->EMAIL[0]; + } + + } + } + + $n = $this->select('N'); + if (count($n) !== 1) { + $warnings[] = array( + 'level' => 1, + 'message' => 'The N property must appear in the VCARD component exactly 1 time', + 'node' => $this, + ); + // TODO: Make a better effort parsing FN. + if (($options & self::REPAIR) && count($n) === 0) { + // Take 2 first name parts of 'FN' and reverse. + $slice = array_reverse(array_slice(explode(' ', (string)$this->FN), 0, 2)); + if(count($slice) < 2) { // If not enought, add one more... + $slice[] = ""; + } + $this->N = implode(';', $slice).';;;'; + } + } + + if (!isset($this->UID)) { + $warnings[] = array( + 'level' => 1, + 'message' => 'Every vCard must have a UID', + 'node' => $this, + ); + if ($options & self::REPAIR) { + $this->UID = substr(md5(rand().time()), 0, 10); + } + } + + if ($options & self::UPGRADE) { + foreach($this->children as &$property) { + $this->decodeProperty($property); + $this->formatPropertyTypes($property); + } + } + + return array_merge( + parent::validate($options), + $warnings + ); + + } +} \ No newline at end of file diff --git a/lib/vcard.php b/lib/vcard.php index dd3b372a..28ee9b59 100644 --- a/lib/vcard.php +++ b/lib/vcard.php @@ -155,48 +155,6 @@ class VCard { return $result->fetchRow(); } - /** - * @brief Format property TYPE parameters for upgrading from v. 2.1 - * @param $property Reference to a Sabre_VObject_Property. - * In version 2.1 e.g. a phone can be formatted like: TEL;HOME;CELL:123456789 - * This has to be changed to either TEL;TYPE=HOME,CELL:123456789 or TEL;TYPE=HOME;TYPE=CELL:123456789 - both are valid. - */ - public static function formatPropertyTypes(&$property) { - foreach($property->parameters as $key=>&$parameter) { - $types = App::getTypesOfProperty($property->name); - if(is_array($types) && in_array(strtoupper($parameter->name), array_keys($types)) || strtoupper($parameter->name) == 'PREF') { - $property->parameters[] = new \Sabre\VObject\Parameter('TYPE', $parameter->name); - } - unset($property->parameters[$key]); - } - } - - /** - * @brief Decode properties for upgrading from v. 2.1 - * @param $property Reference to a Sabre_VObject_Property. - * The only encoding allowed in version 3.0 is 'b' for binary. All encoded strings - * must therefor be decoded and the parameters removed. - */ - public static function decodeProperty(&$property) { - // Check out for encoded string and decode them :-[ - foreach($property->parameters as $key=>&$parameter) { - if(strtoupper($parameter->name) == 'ENCODING') { - if(strtoupper($parameter->value) == 'QUOTED-PRINTABLE') { // what kind of other encodings could be used? - // Decode quoted-printable and strip any control chars - // except \n and \r - $property->value = preg_replace( - '/[\x00-\x09\x0B\x0C\x0E-\x1F\x7F]/', - '', - quoted_printable_decode($property->value) - ); - unset($property->parameters[$key]); - } - } elseif(strtoupper($parameter->name) == 'CHARSET') { - unset($property->parameters[$key]); - } - } - } - /** * @brief Checks if a contact with the same UID already exist in the address book. * @param $aid Address book ID. @@ -234,92 +192,6 @@ class VCard { } } - /** - * @brief Tries to update imported VCards to adhere to rfc2426 (VERSION: 3.0) and add mandatory fields if missing. - * @param aid Address book id. - * @param vcard A Sabre\VObject\Component of type VCARD (passed by reference). - */ - protected static function updateValuesFromAdd($aid, &$vcard) { // any suggestions for a better method name? ;-) - $stringprops = array('N', 'FN', 'ORG', 'NICK', 'ADR', 'NOTE'); - $typeprops = array('ADR', 'TEL', 'EMAIL'); - $upgrade = false; - $fn = $n = $uid = $email = $org = null; - $version = isset($vcard->VERSION) ? $vcard->VERSION : null; - // Add version if needed - if($version && $version < '3.0') { - $upgrade = true; - //OCP\Util::writeLog('contacts', 'OCA\Contacts\VCard::updateValuesFromAdd. Updating from version: '.$version, OCP\Util::DEBUG); - } - foreach($vcard->children as &$property) { - // Decode string properties and remove obsolete properties. - if($upgrade && in_array($property->name, $stringprops)) { - self::decodeProperty($property); - } - if(function_exists('iconv')) { - $property->value = str_replace("\r\n", "\n", iconv(mb_detect_encoding($property->value, 'UTF-8, ISO-8859-1'), 'utf-8', $property->value)); - } else { - $property->value = str_replace("\r\n", "\n", mb_convert_encoding($property->value, 'UTF-8', mb_detect_encoding($property->value, 'UTF-8, ISO-8859-1'), $property->value)); - } - if(in_array($property->name, $stringprops)) { - $property->value = strip_tags($property->value); - } - // Fix format of type parameters. - if($upgrade && in_array($property->name, $typeprops)) { - //OCP\Util::writeLog('contacts', 'OCA\Contacts\VCard::updateValuesFromAdd. before: '.$property->serialize(), OCP\Util::DEBUG); - self::formatPropertyTypes($property); - //OCP\Util::writeLog('contacts', 'OCA\Contacts\VCard::updateValuesFromAdd. after: '.$property->serialize(), OCP\Util::DEBUG); - } - if($property->name == 'FN') { - $fn = $property->value; - } - else if($property->name == 'N') { - $n = $property->value; - } - else if($property->name == 'UID') { - $uid = $property->value; - } - else if($property->name == 'ORG') { - $org = $property->value; - } - else if($property->name == 'EMAIL' && is_null($email)) { // only use the first email as substitute for missing N or FN. - $email = $property->value; - } - } - // Check for missing 'N', 'FN' and 'UID' properties - if(!$fn) { - if($n && $n != ';;;;') { - $fn = join(' ', array_reverse(array_slice(explode(';', $n), 0, 2))); - } elseif($email) { - $fn = $email; - } elseif($org) { - $fn = $org; - } else { - $fn = 'Unknown Name'; - } - $vcard->FN = $fn; - //OCP\Util::writeLog('contacts', 'OCA\Contacts\VCard::updateValuesFromAdd. Added missing \'FN\' field: '.$fn, OCP\Util::DEBUG); - } - if(!$n || $n == ';;;;') { // Fix missing 'N' field. Ugly hack ahead ;-) - $slice = array_reverse(array_slice(explode(' ', $fn), 0, 2)); // Take 2 first name parts of 'FN' and reverse. - if(count($slice) < 2) { // If not enought, add one more... - $slice[] = ""; - } - $n = implode(';', $slice).';;;'; - $vcard->N = $n; - //OCP\Util::writeLog('contacts', 'OCA\Contacts\VCard::updateValuesFromAdd. Added missing \'N\' field: '.$n, OCP\Util::DEBUG); - } - if(!$uid) { - $uid = substr(md5(rand().time()), 0, 10); - $vcard->add('UID', $uid); - //OCP\Util::writeLog('contacts', 'OCA\Contacts\VCard::updateValuesFromAdd. Added missing \'UID\' field: '.$uid, OCP\Util::DEBUG); - } - if(self::trueUID($aid, $uid)) { - $vcard->{'UID'} = $uid; - } - $now = new \DateTime; - $vcard->{'REV'} = $now->format(\DateTime::W3C); - } - /** * @brief Adds a card * @param $aid integer Addressbook id @@ -344,10 +216,12 @@ class VCard { ); } } - if(!$isChecked) { - self::updateValuesFromAdd($aid, $card); + $uid = $vcard->UID; + if(self::trueUID($aid, $uid)) { + $vcard->UID = $uid; } - $card->{'VERSION'} = '3.0'; + $now = new \DateTime; + $vcard->REV = $now->format(\DateTime::W3C); // Add product ID is missing. //$prodid = trim($card->getAsString('PRODID')); //if(!$prodid) { From 2fecf91483a666bf5f2626dd459f9c85f08f95a2 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Fri, 22 Feb 2013 23:54:21 +0100 Subject: [PATCH 002/236] Updated header: --- lib/vcard.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vcard.php b/lib/vcard.php index 28ee9b59..cab95cf6 100644 --- a/lib/vcard.php +++ b/lib/vcard.php @@ -4,7 +4,7 @@ * * @author Jakob Sack * @copyright 2011 Jakob Sack mail@jakobsack.de - * @copyright 2012 Thomas Tanghus + * @copyright 2012-2013 Thomas Tanghus * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE From 4aa894d0aaa4a643150d1d4f5b8b41f6ba634caa Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Sat, 23 Feb 2013 00:30:06 +0100 Subject: [PATCH 003/236] Fix variable name. --- lib/vcard.php | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/vcard.php b/lib/vcard.php index cab95cf6..bdd2d646 100644 --- a/lib/vcard.php +++ b/lib/vcard.php @@ -195,13 +195,13 @@ class VCard { /** * @brief Adds a card * @param $aid integer Addressbook id - * @param $card Sabre\VObject\Component vCard file + * @param $vcard \Sabre\VObject\Component vCard object * @param $uri string the uri of the card, default based on the UID * @param $isChecked boolean If the vCard should be checked for validity and version. * @return insertid on success or false. */ - public static function add($aid, VObject\Component $card, $uri=null, $isChecked=false) { - if(is_null($card)) { + public static function add($aid, VObject\Component $vcard, $uri=null, $isChecked=false) { + if(is_null($vcard)) { \OCP\Util::writeLog('contacts', __METHOD__ . ', No vCard supplied', \OCP\Util::ERROR); return null; }; @@ -223,20 +223,20 @@ class VCard { $now = new \DateTime; $vcard->REV = $now->format(\DateTime::W3C); // Add product ID is missing. - //$prodid = trim($card->getAsString('PRODID')); + //$prodid = trim($vcard->getAsString('PRODID')); //if(!$prodid) { - if(!isset($card->PRODID)) { + if(!isset($vcard->PRODID)) { $appinfo = \OCP\App::getAppInfo('contacts'); $appversion = \OCP\App::getAppVersion('contacts'); $prodid = '-//ownCloud//NONSGML '.$appinfo['name'].' '.$appversion.'//EN'; - $card->add('PRODID', $prodid); + $vcard->add('PRODID', $prodid); } - $fn = isset($card->FN) ? $card->FN : ''; + $fn = isset($vcard->FN) ? $vcard->FN : ''; - $uri = isset($uri) ? $uri : $card->UID . '.vcf'; + $uri = isset($uri) ? $uri : $vcard->UID . '.vcf'; - $data = $card->serialize(); + $data = $vcard->serialize(); $stmt = \OCP\DB::prepare( 'INSERT INTO `*PREFIX*contacts_cards` (`addressbookid`,`fullname`,`carddata`,`uri`,`lastmodified`) VALUES(?,?,?,?,?)' ); try { $result = $stmt->execute(array($aid, $fn, $data, $uri, time())); @@ -250,8 +250,8 @@ class VCard { return false; } $newid = \OCP\DB::insertid('*PREFIX*contacts_cards'); - App::loadCategoriesFromVCard($newid, $card); - App::updateDBProperties($newid, $card); + App::loadCategoriesFromVCard($newid, $vcard); + App::updateDBProperties($newid, $vcard); App::cacheThumbnail($newid); Addressbook::touch($aid); From 1e4bc27e028d9439a0f17a698c470ba74826c8c0 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Sat, 23 Feb 2013 00:31:36 +0100 Subject: [PATCH 004/236] Use VObject\StringUtil::convertToUTF8() to decode v. 2.1 properties. --- lib/sabre/vcard.php | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/lib/sabre/vcard.php b/lib/sabre/vcard.php index d4e55bb7..2d262760 100644 --- a/lib/sabre/vcard.php +++ b/lib/sabre/vcard.php @@ -79,27 +79,12 @@ class VCardObject extends VObject\Component\VCard { if(strtoupper($parameter->value) == 'QUOTED-PRINTABLE') { // Decode quoted-printable and strip any control chars // except \n and \r - $property->value = preg_replace( - '/[\x00-\x09\x0B\x0C\x0E-\x1F\x7F]/', - '', - quoted_printable_decode($property->value) + $property->value = str_replace( + "\r\n", "\n", + VObject\StringUtil::convertToUTF8( + quoted_printable_decode($property->value) + ) ); - if(function_exists('iconv')) { - $property->value = str_replace("\r\n", "\n", - iconv( - mb_detect_encoding( - $property->value, - 'UTF-8, ISO-8859-1' - ), 'utf-8', - $property->value)); - } else { - $property->value = str_replace("\r\n", "\n", - mb_convert_encoding( - $property->value, - 'UTF-8', - mb_detect_encoding($property->value, 'UTF-8, ISO-8859-1'), - $property->value)); - } unset($property->parameters[$key]); } } elseif(strtoupper($parameter->name) == 'CHARSET') { From b752145ce32c239c6589d751ec1a378f222086ee Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Sat, 23 Feb 2013 00:32:21 +0100 Subject: [PATCH 005/236] Also set version when upgrading. --- lib/sabre/vcard.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/sabre/vcard.php b/lib/sabre/vcard.php index 2d262760..547be337 100644 --- a/lib/sabre/vcard.php +++ b/lib/sabre/vcard.php @@ -204,6 +204,7 @@ class VCardObject extends VObject\Component\VCard { } if ($options & self::UPGRADE) { + $this->VERSION = self::DEFAULT_VERSION; foreach($this->children as &$property) { $this->decodeProperty($property); $this->formatPropertyTypes($property); From 75c1302197a10399e77da9a3e5d6ca604d2ea616 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Sat, 23 Feb 2013 16:24:09 +0100 Subject: [PATCH 006/236] Upgrade: ENCODING: BASE64 -> 'b' and format image mime types. --- lib/sabre/vcard.php | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/sabre/vcard.php b/lib/sabre/vcard.php index 547be337..b1bc02fd 100644 --- a/lib/sabre/vcard.php +++ b/lib/sabre/vcard.php @@ -75,7 +75,6 @@ class VCardObject extends VObject\Component\VCard { // Check out for encoded string and decode them :-[ foreach($property->parameters as $key=>&$parameter) { if(strtoupper($parameter->name) == 'ENCODING') { - // what other kinds of encodings could be used? if(strtoupper($parameter->value) == 'QUOTED-PRINTABLE') { // Decode quoted-printable and strip any control chars // except \n and \r @@ -86,10 +85,11 @@ class VCardObject extends VObject\Component\VCard { ) ); unset($property->parameters[$key]); + } else if(strtoupper($parameter->value) == 'BASE64') { + $parameter->value = 'b'; } } elseif(strtoupper($parameter->name) == 'CHARSET') { - // TODO: Should probably use this while decoding. - unset($property->parameters[$key]); + unset($property->parameters[$key]); } } } @@ -115,7 +115,6 @@ class VCardObject extends VObject\Component\VCard { public function validate($options = 0) { $warnings = array(); - \OCP\Util::writeLog('contacts', __METHOD__.' options: '.$options, \OCP\Util::DEBUG); $version = $this->select('VERSION'); if (count($version) !== 1) { @@ -208,6 +207,15 @@ class VCardObject extends VObject\Component\VCard { foreach($this->children as &$property) { $this->decodeProperty($property); $this->formatPropertyTypes($property); + \OCP\Util::writeLog('contacts', __METHOD__.' upgrade: '.$property->name, \OCP\Util::DEBUG); + switch((string)$property->name) { + case 'LOGO': + case 'SOUND': + case 'PHOTO': + if(isset($property['TYPE']) && strpos((string)$property['TYPE'], '/') === false) { + $property['TYPE'] = 'image/' . strtolower($property['TYPE']); + } + } } } From 219ce43fb289abde9779eaf8c8fc2442f2b13fba Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Sat, 23 Feb 2013 21:26:40 +0100 Subject: [PATCH 007/236] Contacts: Read and echo image instead of redirect. Fewer request == faster. --- thumbnail.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/thumbnail.php b/thumbnail.php index 009f6b10..e45cd56e 100644 --- a/thumbnail.php +++ b/thumbnail.php @@ -27,14 +27,20 @@ session_write_close(); //OCP\Util::writeLog('contacts', OCP\Util::getRequestUri(), OCP\Util::DEBUG); function getStandardImage() { + $image = new \OC_Image(); + $file = __DIR__ . DIRECTORY_SEPARATOR . 'img' . DIRECTORY_SEPARATOR . 'person.png'; + OCP\Response::setLastModifiedHeader(filemtime($file)); OCP\Response::enableCaching(); - OCP\Response::redirect(OCP\Util::imagePath('contacts', 'person.png')); + $image->loadFromFile($file); + $image(); + exit(); } if(!extension_loaded('gd') || !function_exists('gd_info')) { OCP\Util::writeLog('contacts', 'thumbnail.php. GD module not installed', OCP\Util::DEBUG); - getStandardImage(); + OCP\Response::enableCaching(); + OCP\Response::redirect(OCP\Util::imagePath('contacts', 'person.png')); exit(); } From 51c9517fb16cf0db90ab7703efeda957893cc84c Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Sat, 23 Feb 2013 22:27:04 +0100 Subject: [PATCH 008/236] Contacts: Update location.hash on open/close. --- js/app.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/js/app.js b/js/app.js index 08edcb1f..82300eaa 100644 --- a/js/app.js +++ b/js/app.js @@ -285,13 +285,15 @@ OC.Contacts = OC.Contacts || { $(window).trigger('beforeunload'); }); - $(window).bind('hashchange', function() { + this.hashChange = function() { console.log('hashchange', window.location.hash) var id = parseInt(window.location.hash.substr(1)); if(id) { self.openContact(id); } - }); + } + + $(window).bind('hashchange', this.hashChange); // App specific events $(document).bind('status.contact.deleted', function(e, data) { @@ -1324,6 +1326,7 @@ OC.Contacts = OC.Contacts || { this.$rightContent.scrollTop(this.contacts.contactPos(id)-30); }, closeContact: function(id) { + $(window).unbind('hashchange', this.hashChange); if(typeof this.currentid === 'number') { var contact = this.contacts.findById(id); if(contact && contact.close()) { @@ -1342,12 +1345,15 @@ OC.Contacts = OC.Contacts || { $(document).trigger('status.nomorecontacts'); } //$('body').unbind('click', this.bodyListener); + window.location.hash = ''; + $(window).bind('hashchange', this.hashChange); }, openContact: function(id) { console.log('Contacts.openContact', id); if(this.currentid) { this.closeContact(this.currentid); } + $(window).unbind('hashchange', this.hashChange); this.currentid = parseInt(id); console.log('Contacts.openContact, Favorite', this.currentid, this.groups.isFavorite(this.currentid), this.groups); this.setAllChecked(false); @@ -1381,10 +1387,12 @@ OC.Contacts = OC.Contacts || { if($contactelem.find($(e.target)).length === 0) { self.closeContact(self.currentid); } - }; + };*/ + window.location.hash = this.currentid.toString(); setTimeout(function() { - $('body').bind('click', self.bodyListener); - }, 500);*/ + //$('body').bind('click', self.bodyListener); + $(window).bind('hashchange', this.hashChange); + }, 500); }, update: function() { console.log('update'); From d74ef9de9e9ba026697ee04901246e93bfc893b1 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Sat, 23 Feb 2013 22:45:21 +0100 Subject: [PATCH 009/236] Contacts: Trigger hashchange onpopstate. --- js/app.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/app.js b/js/app.js index 82300eaa..5f570ed4 100644 --- a/js/app.js +++ b/js/app.js @@ -288,11 +288,12 @@ OC.Contacts = OC.Contacts || { this.hashChange = function() { console.log('hashchange', window.location.hash) var id = parseInt(window.location.hash.substr(1)); - if(id) { + if(id && id !== self.currentid) { self.openContact(id); } } + $(window).bind('popstate', this.hashChange); $(window).bind('hashchange', this.hashChange); // App specific events From 0aaddf0ca59f7deb37a34e9757df2c92f20bc96e Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Sat, 23 Feb 2013 22:51:28 +0100 Subject: [PATCH 010/236] Contacts: Close contact on empty hash. --- js/app.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/js/app.js b/js/app.js index 5f570ed4..df4b6ab5 100644 --- a/js/app.js +++ b/js/app.js @@ -290,6 +290,8 @@ OC.Contacts = OC.Contacts || { var id = parseInt(window.location.hash.substr(1)); if(id && id !== self.currentid) { self.openContact(id); + } else if(!id && self.currentid) { + self.closeContact(self.currentid); } } From 06291c9f41009de302a1d644be07a410208ae720 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Sat, 23 Feb 2013 22:59:47 +0100 Subject: [PATCH 011/236] Contacts: Use FontAwesome icon-cloud. --- css/contacts.css | 2 -- templates/contacts.php | 8 ++++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/css/contacts.css b/css/contacts.css index efcdd74b..9ee35b08 100644 --- a/css/contacts.css +++ b/css/contacts.css @@ -187,7 +187,6 @@ .no-svg .mail { background-image:url('%webroot%/core/img/actions/mail.png'); } .no-svg .import, .no-svg .upload { background-image:url('%webroot%/core/img/actions/upload.png'); } .no-svg .export, .no-svg .download { background-image:url('%webroot%/core/img/actions/download.png'); } -/*.no-svg .cloud { background-image:url('%webroot%/core/img/places/picture.png'); }*/ .no-svg .globe { background-image:url('%webroot%/core/img/actions/public.png'); } .no-svg .settings { background-image:url('%webroot%/core/img/actions/settings.svg'); } .no-svg .starred { background-image:url('%appswebroot%/contacts/img/starred.png'); background-size: contain; } @@ -208,7 +207,6 @@ .svg .mail { background-image:url('%webroot%/core/img/actions/mail.svg'); } .svg .import,.svg .upload { background-image:url('%webroot%/core/img/actions/upload.svg'); } .svg .export,.svg .download { background-image:url('%webroot%/core/img/actions/download.svg'); } -/*.svg .cloud { background-image:url('%webroot%/core/img/places/picture.svg'); }*/ .svg .globe { background-image:url('%webroot%/core/img/actions/public.svg'); } .svg .settings { background-image:url('%webroot%/core/img/actions/settings.svg'); } .svg .starred { background-image:url('%appswebroot%/contacts/img/starred.svg'); background-size: contain; } diff --git a/templates/contacts.php b/templates/contacts.php index 7dfce36e..151c03c0 100644 --- a/templates/contacts.php +++ b/templates/contacts.php @@ -158,10 +158,10 @@
  • From cab4ceea847c251b7a03402a740de4215b2325f8 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Sun, 24 Feb 2013 02:20:19 +0100 Subject: [PATCH 012/236] Contacts: Fix undefined indexes on search. --- lib/search.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/search.php b/lib/search.php index 162b914b..23b9a93e 100644 --- a/lib/search.php +++ b/lib/search.php @@ -12,7 +12,7 @@ class SearchProvider extends \OC_Search_Provider{ $link = \OCP\Util::linkTo('contacts', 'index.php').'#' . $vcard['id']; $props = array(); foreach(array('EMAIL', 'NICKNAME', 'ORG') as $searchvar) { - if(count($result[$searchvar]) > 0 && strlen($result[$searchvar][0]) > 3) { + if(isset($result[$searchvar]) && count($result[$searchvar]) > 0 && strlen($result[$searchvar][0]) > 3) { $props = array_merge($props, $result[$searchvar]); } } From a5e5d6ad0c27f9f642cd28e18345efb7c3892921 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Sun, 24 Feb 2013 02:48:43 +0100 Subject: [PATCH 013/236] Contacts: Select first *visible* contact on load. --- js/contacts.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/js/contacts.js b/js/contacts.js index e5d41590..4af14a09 100644 --- a/js/contacts.js +++ b/js/contacts.js @@ -1307,7 +1307,7 @@ OC.Contacts = OC.Contacts || {}; }; Contact.prototype.next = function() { - var $next = this.$listelem.next('tr'); + var $next = this.$listelem.next('tr:visible'); if($next.length > 0) { this.$listelem.removeClass('active'); $next.addClass('active'); @@ -1320,7 +1320,7 @@ OC.Contacts = OC.Contacts || {}; }; Contact.prototype.prev = function() { - var $prev = this.$listelem.prev('tr'); + var $prev = this.$listelem.prev('tr:visible'); if($prev.length > 0) { this.$listelem.removeClass('active'); $prev.addClass('active'); @@ -1643,6 +1643,7 @@ OC.Contacts = OC.Contacts || {}; }; ContactList.prototype.setCurrent = function(id, deselect_other) { + console.log('ContactList.setCurrent', id); if(!id) { return; } @@ -1749,6 +1750,7 @@ OC.Contacts = OC.Contacts || {}; } setTimeout(function() { self.doSort(); + self.setCurrent(self.$contactList.find('tr:visible:first-child').data('id'), false); } , 2000); $(document).trigger('status.contacts.loaded', { @@ -1756,7 +1758,6 @@ OC.Contacts = OC.Contacts || {}; numcontacts: jsondata.data.contacts.length, is_indexed: jsondata.data.is_indexed }); - self.setCurrent(self.$contactList.find('tr:first-child').data('id'), false); } if(typeof cb === 'function') { cb(); From 7f7fd44de5acc20383923cc826a57235bae130a1 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Tue, 26 Feb 2013 21:59:38 +0100 Subject: [PATCH 014/236] Contacts: Construct FN based on N if empty --- js/contacts.js | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/js/contacts.js b/js/contacts.js index 4af14a09..0829fd65 100644 --- a/js/contacts.js +++ b/js/contacts.js @@ -348,14 +348,11 @@ OC.Contacts = OC.Contacts || {}; self.data.N[0]['value'][1] = nvalue[0] || ''; self.data.N[0]['value'][2] = nvalue.length > 2 && nvalue.slice(1, nvalue.length-1).join(' ') || ''; setTimeout(function() { - // TODO: Hint to user to check if name is properly formatted - //console.log('auto creating N', self.data.N[0].value) self.saveProperty({name:'N', value:self.data.N[0].value.join(';')}); setTimeout(function() { self.$fullelem.find('.fullname').next('.action.edit').trigger('click'); OC.notify({message:t('contacts', 'Is this correct?')}); - } - , 1000); + }, 1000); } , 500); } @@ -369,6 +366,25 @@ OC.Contacts = OC.Contacts || {}; self.$fullelem.find('#n_' + idx).val(val); }); } + var $fullname = self.$fullelem.find('.fullname'), fullname = ''; + var update_fn = false; + if(!self.data.FN) { + self.data.FN = [{name:'N', value:'', parameters:[]}]; + } + if(self.data.FN[0]['value'] === '') { + self.data.FN[0]['value'] = value[1] + ' ' + value[0]; + $fullname.val(self.data.FN[0]['value']); + update_fn = true; + } else if($fullname.val()[0] === ' ') { + self.data.FN[0]['value'] = value[1] + ' ' + value[0]; + $fullname.val(self.data.FN[0]['value']); + update_fn = true; + } + if(update_fn) { + setTimeout(function() { + self.saveProperty({name:'FN', value:self.data.FN[0]['value']}); + }, 1000); + } case 'NICKNAME': case 'BDAY': case 'ORG': From 5e4198bee7c4df4aed3f0f4e240ecd398663d9cf Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Tue, 26 Feb 2013 22:24:54 +0100 Subject: [PATCH 015/236] Contacts: Make sure multi value fields are saved in correct order. --- js/contacts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/contacts.js b/js/contacts.js index 0829fd65..c6c0d8d7 100644 --- a/js/contacts.js +++ b/js/contacts.js @@ -618,7 +618,7 @@ OC.Contacts = OC.Contacts || {}; } else if($elem.length > 1) { var retval = []; $.each($elem, function(idx, e) { - retval.push($(e).val()); + retval[parseInt($(e).attr('name').substr(6,1))] = $(e).val(); }); return retval; } From b5a0bbb7aafb64cb818de7e07538559599d4be0f Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Tue, 26 Feb 2013 22:25:57 +0100 Subject: [PATCH 016/236] Contacts: Test for more ways to construct name. --- js/contacts.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/js/contacts.js b/js/contacts.js index c6c0d8d7..207ddcf4 100644 --- a/js/contacts.js +++ b/js/contacts.js @@ -375,7 +375,12 @@ OC.Contacts = OC.Contacts || {}; self.data.FN[0]['value'] = value[1] + ' ' + value[0]; $fullname.val(self.data.FN[0]['value']); update_fn = true; - } else if($fullname.val()[0] === ' ') { + } else if($fullname.val() == value[1] + ' ') { + console.log('change', value); + self.data.FN[0]['value'] = value[1] + ' ' + value[0]; + $fullname.val(self.data.FN[0]['value']); + update_fn = true; + } else if($fullname.val() == ' ' + value[0]) { self.data.FN[0]['value'] = value[1] + ' ' + value[0]; $fullname.val(self.data.FN[0]['value']); update_fn = true; From 7047e3e7b3da9f11a458601c242afc4eaf41c6d6 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Wed, 27 Feb 2013 02:46:48 +0100 Subject: [PATCH 017/236] Contacts: Fix copy/paste mistake. --- lib/vcard.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/vcard.php b/lib/vcard.php index bdd2d646..def10711 100644 --- a/lib/vcard.php +++ b/lib/vcard.php @@ -355,7 +355,7 @@ class VCard { $addressbook_permissions = $sharedAddressbook['permissions']; } if ($sharedContact) { - $contact_permissions = $sharedEvent['permissions']; + $contact_permissions = $sharedContact['permissions']; } $permissions = max($addressbook_permissions, $contact_permissions); if (!($permissions & \OCP\PERMISSION_UPDATE)) { @@ -467,7 +467,7 @@ class VCard { $addressbook_permissions = $sharedAddressbook['permissions']; } if ($sharedContact) { - $contact_permissions = $sharedEvent['permissions']; + $contact_permissions = $sharedContact['permissions']; } $permissions = max($addressbook_permissions, $contact_permissions); From 93315a05fad7aefad0b1edc808c1f93828dddfd7 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Wed, 27 Feb 2013 02:53:25 +0100 Subject: [PATCH 018/236] Contacts: Use isAdminUser() --- lib/vcard.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vcard.php b/lib/vcard.php index def10711..fed48f9b 100644 --- a/lib/vcard.php +++ b/lib/vcard.php @@ -450,7 +450,7 @@ class VCard { ); } - if ($addressbook['userid'] != \OCP\User::getUser() && !\OC_Group::inGroup(\OCP\User::getUser(), 'admin')) { + if ($addressbook['userid'] != \OCP\User::getUser() && !\OC_User::isAdminUser(\OCP\User::getUser())) { \OCP\Util::writeLog('contacts', __METHOD__.', ' . $addressbook['userid'] . ' != ' . \OCP\User::getUser(), \OCP\Util::DEBUG); $sharedAddressbook = \OCP\Share::getItemSharedWithBySource( From ec24a73c48198e64b8c35c6e75bf9bfce6f1090e Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Tue, 5 Mar 2013 02:30:03 +0100 Subject: [PATCH 019/236] Contacts: Properly escape commas and semi-colons. --- ajax/contact/saveproperty.php | 4 ++ appinfo/app.php | 9 ++++ lib/sabre/stringproperty.php | 80 +++++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+) create mode 100644 lib/sabre/stringproperty.php diff --git a/ajax/contact/saveproperty.php b/ajax/contact/saveproperty.php index 2df497a4..421ddedf 100644 --- a/ajax/contact/saveproperty.php +++ b/ajax/contact/saveproperty.php @@ -69,6 +69,7 @@ $checksum = isset($_POST['checksum'])?$_POST['checksum']:null; debug('value: ' . print_r($value, 1)); $multi_properties = array('EMAIL', 'TEL', 'IMPP', 'ADR', 'URL'); +$string_properties = array('FN', 'NICKNAME', 'NOTE', 'EMAIL', 'TEL', 'IMPP', 'ADR', 'URL'); if(!$name) { bailOut(App::$l10n->t('element name is not set.')); @@ -195,6 +196,9 @@ if(!$value) { unset($vcard->{$name}); } } else { + if(in_array($name, $string_properties)) { + $value = strtr($value, array(',' => '\,', ';' => '\;')); + } /* setting value */ switch($element) { case 'BDAY': diff --git a/appinfo/app.php b/appinfo/app.php index a80b9f8b..32bfa12d 100644 --- a/appinfo/app.php +++ b/appinfo/app.php @@ -8,6 +8,7 @@ OC::$CLASSPATH['OCA\Contacts\Share_Backend_Addressbook'] = 'contacts/lib/share/a OC::$CLASSPATH['OCA\Contacts\AddressbookProvider'] = 'contacts/lib/addressbookprovider.php'; OC::$CLASSPATH['OCA\Contacts\CardDAVPlugin'] = 'contacts/lib/sabre/plugin.php'; OC::$CLASSPATH['OCA\Contacts\VCardObject'] = 'contacts/lib/sabre/vcard.php'; +OC::$CLASSPATH['OCA\Contacts\StringProperty'] = 'contacts/lib/sabre/stringproperty.php'; OC::$CLASSPATH['OC_Connector_Sabre_CardDAV'] = 'contacts/lib/sabre/backend.php'; OC::$CLASSPATH['OC_Connector_Sabre_CardDAV_AddressBookRoot'] = 'contacts/lib/sabre/addressbookroot.php'; OC::$CLASSPATH['OC_Connector_Sabre_CardDAV_UserAddressBooks'] = 'contacts/lib/sabre/useraddressbooks.php'; @@ -16,7 +17,15 @@ OC::$CLASSPATH['OC_Connector_Sabre_CardDAV_Card'] = 'contacts/lib/sabre/card.php OC::$CLASSPATH['OCA\Contacts\SearchProvider'] = 'contacts/lib/search.php'; require_once __DIR__ . '/../lib/sabre/vcard.php'; +//require_once __DIR__ . '/../lib/sabre/stringproperty.php'; Sabre\VObject\Component::$classMap['VCARD'] = 'OCA\Contacts\VCardObject'; +Sabre\VObject\Property::$classMap['FN'] = 'OCA\Contacts\StringProperty'; +Sabre\VObject\Property::$classMap['NOTE'] = 'OCA\Contacts\StringProperty'; +Sabre\VObject\Property::$classMap['NICKNAME'] = 'OCA\Contacts\StringProperty'; +Sabre\VObject\Property::$classMap['EMAIL'] = 'OCA\Contacts\StringProperty'; +Sabre\VObject\Property::$classMap['TEL'] = 'OCA\Contacts\StringProperty'; +Sabre\VObject\Property::$classMap['IMPP'] = 'OCA\Contacts\StringProperty'; +Sabre\VObject\Property::$classMap['URL'] = 'OCA\Contacts\StringProperty'; OCP\Util::connectHook('OC_User', 'post_createUser', 'OCA\Contacts\Hooks', 'createUser'); OCP\Util::connectHook('OC_User', 'post_deleteUser', 'OCA\Contacts\Hooks', 'deleteUser'); diff --git a/lib/sabre/stringproperty.php b/lib/sabre/stringproperty.php new file mode 100644 index 00000000..32da015d --- /dev/null +++ b/lib/sabre/stringproperty.php @@ -0,0 +1,80 @@ +. + * + */ + +namespace OCA\Contacts; + +use Sabre\VObject; + +/** + * This class overrides \Sabre\VObject\Property::serialize() properly + * escape commas and semi-colons in string properties. +*/ +class StringProperty extends VObject\Property { + + /** + * Turns the object back into a serialized blob. + * + * @return string + */ + public function serialize() { + + $str = $this->name; + if ($this->group) { + $str = $this->group . '.' . $this->name; + } + + foreach($this->parameters as $param) { + $str.=';' . $param->serialize(); + } + + $src = array( + "\n",); + /* ';', + ',', + );*/ + $out = array( + '\n',); + /* '\;', + '\,', + );*/ + $str.=':' . str_replace($src, $out, $this->value); + + $out = ''; + while(strlen($str) > 0) { + if (strlen($str) > 75) { + $out .= mb_strcut($str, 0, 75, 'utf-8') . "\r\n"; + $str = ' ' . mb_strcut($str, 75, strlen($str), 'utf-8'); + } else { + $out .= $str . "\r\n"; + $str = ''; + break; + } + } + + return $out; + + } + +} \ No newline at end of file From ac19b7f0f33f7978d4dc72db2b3984d9d736b194 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Tue, 5 Mar 2013 11:08:04 +0100 Subject: [PATCH 020/236] Contacts: Namespace CardDAV backend. --- appinfo/app.php | 12 +++---- appinfo/remote.php | 6 ++-- lib/{sabre => carddav}/addressbook.php | 34 ++++++++++--------- lib/{sabre => carddav}/addressbookroot.php | 6 ++-- lib/{sabre => carddav}/backend.php | 37 +++++++++++---------- lib/{sabre => carddav}/card.php | 22 +++++++----- lib/{sabre => carddav}/plugin.php | 5 +-- lib/{sabre => carddav}/stringproperty.php | 0 lib/{sabre => carddav}/useraddressbooks.php | 8 +++-- lib/{sabre => carddav}/vcard.php | 0 10 files changed, 72 insertions(+), 58 deletions(-) rename lib/{sabre => carddav}/addressbook.php (82%) rename lib/{sabre => carddav}/addressbookroot.php (87%) rename lib/{sabre => carddav}/backend.php (81%) rename lib/{sabre => carddav}/card.php (78%) rename lib/{sabre => carddav}/plugin.php (95%) rename lib/{sabre => carddav}/stringproperty.php (100%) rename lib/{sabre => carddav}/useraddressbooks.php (82%) rename lib/{sabre => carddav}/vcard.php (100%) diff --git a/appinfo/app.php b/appinfo/app.php index 32bfa12d..187b1375 100644 --- a/appinfo/app.php +++ b/appinfo/app.php @@ -6,14 +6,14 @@ OC::$CLASSPATH['OCA\Contacts\Hooks'] = 'contacts/lib/hooks.php'; OC::$CLASSPATH['OCA\Contacts\Share_Backend_Contact'] = 'contacts/lib/share/contact.php'; OC::$CLASSPATH['OCA\Contacts\Share_Backend_Addressbook'] = 'contacts/lib/share/addressbook.php'; OC::$CLASSPATH['OCA\Contacts\AddressbookProvider'] = 'contacts/lib/addressbookprovider.php'; -OC::$CLASSPATH['OCA\Contacts\CardDAVPlugin'] = 'contacts/lib/sabre/plugin.php'; OC::$CLASSPATH['OCA\Contacts\VCardObject'] = 'contacts/lib/sabre/vcard.php'; OC::$CLASSPATH['OCA\Contacts\StringProperty'] = 'contacts/lib/sabre/stringproperty.php'; -OC::$CLASSPATH['OC_Connector_Sabre_CardDAV'] = 'contacts/lib/sabre/backend.php'; -OC::$CLASSPATH['OC_Connector_Sabre_CardDAV_AddressBookRoot'] = 'contacts/lib/sabre/addressbookroot.php'; -OC::$CLASSPATH['OC_Connector_Sabre_CardDAV_UserAddressBooks'] = 'contacts/lib/sabre/useraddressbooks.php'; -OC::$CLASSPATH['OC_Connector_Sabre_CardDAV_AddressBook'] = 'contacts/lib/sabre/addressbook.php'; -OC::$CLASSPATH['OC_Connector_Sabre_CardDAV_Card'] = 'contacts/lib/sabre/card.php'; +OC::$CLASSPATH['OCA\Contacts\CardDAV\Backend'] = 'contacts/lib/sabre/backend.php'; +OC::$CLASSPATH['OCA\Contacts\CardDAV\Plugin'] = 'contacts/lib/sabre/plugin.php'; +OC::$CLASSPATH['OCA\Contacts\CardDAV\AddressBookRoot'] = 'contacts/lib/sabre/addressbookroot.php'; +OC::$CLASSPATH['OCA\Contacts\CardDAV\UserAddressBooks'] = 'contacts/lib/sabre/useraddressbooks.php'; +OC::$CLASSPATH['OCA\Contacts\CardDAV\AddressBook'] = 'contacts/lib/sabre/addressbook.php'; +OC::$CLASSPATH['OCA\Contacts\CardDAV\Card'] = 'contacts/lib/sabre/card.php'; OC::$CLASSPATH['OCA\Contacts\SearchProvider'] = 'contacts/lib/search.php'; require_once __DIR__ . '/../lib/sabre/vcard.php'; diff --git a/appinfo/remote.php b/appinfo/remote.php index dde3a37c..cad18d3f 100644 --- a/appinfo/remote.php +++ b/appinfo/remote.php @@ -33,14 +33,14 @@ OC_App::loadApps($RUNTIME_APPTYPES); // Backends $authBackend = new OC_Connector_Sabre_Auth(); $principalBackend = new OC_Connector_Sabre_Principal(); -$carddavBackend = new OC_Connector_Sabre_CardDAV(); +$carddavBackend = new OCA\Contacts\CardDAV\Backend(); $requestBackend = new OC_Connector_Sabre_Request(); // Root nodes $principalCollection = new Sabre_CalDAV_Principal_Collection($principalBackend); $principalCollection->disableListing = true; // Disable listening -$addressBookRoot = new OC_Connector_Sabre_CardDAV_AddressBookRoot($principalBackend, $carddavBackend); +$addressBookRoot = new OCA\Contacts\CardDAV\AddressBookRoot($principalBackend, $carddavBackend); $addressBookRoot->disableListing = true; // Disable listening $nodes = array( @@ -54,7 +54,7 @@ $server->httpRequest = $requestBackend; $server->setBaseUri($baseuri); // Add plugins $server->addPlugin(new Sabre_DAV_Auth_Plugin($authBackend, 'ownCloud')); -$server->addPlugin(new OCA\Contacts\CardDAVPlugin()); +$server->addPlugin(new OCA\Contacts\CardDAV\Plugin()); $server->addPlugin(new Sabre_DAVACL_Plugin()); $server->addPlugin(new Sabre_DAV_Browser_Plugin(false)); // Show something in the Browser, but no upload $server->addPlugin(new Sabre_CardDAV_VCFExportPlugin()); diff --git a/lib/sabre/addressbook.php b/lib/carddav/addressbook.php similarity index 82% rename from lib/sabre/addressbook.php rename to lib/carddav/addressbook.php index dffe321e..1cc3717a 100644 --- a/lib/sabre/addressbook.php +++ b/lib/carddav/addressbook.php @@ -20,14 +20,18 @@ * */ +namespace OCA\Contacts\CardDAV; + +use OCA\Contacts; + /** * This class overrides __construct to get access to $addressBookInfo and * $carddavBackend, Sabre_CardDAV_AddressBook::getACL() to return read/write * permissions based on user and shared state and it overrides * Sabre_CardDAV_AddressBook::getChild() and Sabre_CardDAV_AddressBook::getChildren() - * to instantiate OC_Connector_Sabre_CardDAV_Cards. + * to instantiate \OCA\Contacts\CardDAV\Cards. */ -class OC_Connector_Sabre_CardDAV_AddressBook extends Sabre_CardDAV_AddressBook { +class AddressBook extends \Sabre_CardDAV_AddressBook { /** * CardDAV backend @@ -43,7 +47,7 @@ class OC_Connector_Sabre_CardDAV_AddressBook extends Sabre_CardDAV_AddressBook { * @param array $addressBookInfo */ public function __construct( - Sabre_CardDAV_Backend_Abstract $carddavBackend, + \Sabre_CardDAV_Backend_Abstract $carddavBackend, array $addressBookInfo) { $this->carddavBackend = $carddavBackend; @@ -70,21 +74,21 @@ class OC_Connector_Sabre_CardDAV_AddressBook extends Sabre_CardDAV_AddressBook { $writeprincipal = $this->getOwner(); $createprincipal = $this->getOwner(); $deleteprincipal = $this->getOwner(); - $uid = OCA\Contacts\Addressbook::extractUserID($this->getOwner()); + $uid = Contacts\Addressbook::extractUserID($this->getOwner()); - if($uid != OCP\USER::getUser()) { - $sharedAddressbook = OCP\Share::getItemSharedWithBySource('addressbook', $this->addressBookInfo['id']); - if ($sharedAddressbook && ($sharedAddressbook['permissions'] & OCP\PERMISSION_CREATE)) { - $createprincipal = 'principals/' . OCP\USER::getUser(); + if($uid != \OCP\USER::getUser()) { + $sharedAddressbook = \OCP\Share::getItemSharedWithBySource('addressbook', $this->addressBookInfo['id']); + if ($sharedAddressbook && ($sharedAddressbook['permissions'] & \OCP\PERMISSION_CREATE)) { + $createprincipal = 'principals/' . \OCP\USER::getUser(); } - if ($sharedAddressbook && ($sharedAddressbook['permissions'] & OCP\PERMISSION_READ)) { - $readprincipal = 'principals/' . OCP\USER::getUser(); + if ($sharedAddressbook && ($sharedAddressbook['permissions'] & \OCP\PERMISSION_READ)) { + $readprincipal = 'principals/' . \OCP\USER::getUser(); } if ($sharedAddressbook && ($sharedAddressbook['permissions'] & OCP\PERMISSION_UPDATE)) { - $writeprincipal = 'principals/' . OCP\USER::getUser(); + $writeprincipal = 'principals/' . \OCP\USER::getUser(); } if ($sharedAddressbook && ($sharedAddressbook['permissions'] & OCP\PERMISSION_DELETE)) { - $deleteprincipal = 'principals/' . OCP\USER::getUser(); + $deleteprincipal = 'principals/' . \OCP\USER::getUser(); } } @@ -175,8 +179,8 @@ class OC_Connector_Sabre_CardDAV_AddressBook extends Sabre_CardDAV_AddressBook { public function getChild($name) { $obj = $this->carddavBackend->getCard($this->addressBookInfo['id'],$name); - if (!$obj) throw new Sabre_DAV_Exception_NotFound('Card not found'); - return new OC_Connector_Sabre_CardDAV_Card($this->carddavBackend,$this->addressBookInfo,$obj); + if (!$obj) throw new \Sabre_DAV_Exception_NotFound('Card not found'); + return new Card($this->carddavBackend,$this->addressBookInfo,$obj); } @@ -190,7 +194,7 @@ class OC_Connector_Sabre_CardDAV_AddressBook extends Sabre_CardDAV_AddressBook { $objs = $this->carddavBackend->getCards($this->addressBookInfo['id']); $children = array(); foreach($objs as $obj) { - $children[] = new OC_Connector_Sabre_CardDAV_Card($this->carddavBackend,$this->addressBookInfo,$obj); + $children[] = new Card($this->carddavBackend,$this->addressBookInfo,$obj); } return $children; diff --git a/lib/sabre/addressbookroot.php b/lib/carddav/addressbookroot.php similarity index 87% rename from lib/sabre/addressbookroot.php rename to lib/carddav/addressbookroot.php index 69cd6021..65bad678 100644 --- a/lib/sabre/addressbookroot.php +++ b/lib/carddav/addressbookroot.php @@ -20,11 +20,13 @@ * */ +namespace OCA\Contacts\CardDAV; + /** * This class overrides Sabre_CardDAV_AddressBookRoot::getChildForPrincipal() * to instantiate OC_Connector_CardDAV_UserAddressBooks. */ -class OC_Connector_Sabre_CardDAV_AddressBookRoot extends Sabre_CardDAV_AddressBookRoot { +class AddressBookRoot extends \Sabre_CardDAV_AddressBookRoot { /** * This method returns a node for a principal. @@ -38,7 +40,7 @@ class OC_Connector_Sabre_CardDAV_AddressBookRoot extends Sabre_CardDAV_AddressBo */ public function getChildForPrincipal(array $principal) { - return new OC_Connector_Sabre_CardDAV_UserAddressBooks($this->carddavBackend, $principal['uri']); + return new UserAddressBooks($this->carddavBackend, $principal['uri']); } diff --git a/lib/sabre/backend.php b/lib/carddav/backend.php similarity index 81% rename from lib/sabre/backend.php rename to lib/carddav/backend.php index d39bb2dd..666b6a3f 100644 --- a/lib/sabre/backend.php +++ b/lib/carddav/backend.php @@ -20,10 +20,11 @@ * */ -/** - * This CardDAV backend uses PDO to store addressbooks - */ -class OC_Connector_Sabre_CardDAV extends Sabre_CardDAV_Backend_Abstract { +namespace OCA\Contacts\CardDAV; + +use OCA\Contacts; + +class Backend extends \Sabre_CardDAV_Backend_Abstract { /** * Returns the list of addressbooks for a specific user. * @@ -31,11 +32,11 @@ class OC_Connector_Sabre_CardDAV extends Sabre_CardDAV_Backend_Abstract { * @return array */ public function getAddressBooksForUser($principaluri) { - $data = OCA\Contacts\Addressbook::allWherePrincipalURIIs($principaluri); + $data = Contacts\Addressbook::allWherePrincipalURIIs($principaluri); $addressbooks = array(); foreach($data as $i) { - if($i['userid'] != OCP\USER::getUser()) { + if($i['userid'] != \OCP\USER::getUser()) { $i['uri'] = $i['uri'] . '_shared_by_' . $i['userid']; } $addressbooks[] = array( @@ -43,7 +44,7 @@ class OC_Connector_Sabre_CardDAV extends Sabre_CardDAV_Backend_Abstract { 'uri' => $i['uri'], 'principaluri' => 'principals/'.$i['userid'], '{DAV:}displayname' => $i['displayname'], - '{' . Sabre_CardDAV_Plugin::NS_CARDDAV . '}addressbook-description' + '{' . \Sabre_CardDAV_Plugin::NS_CARDDAV . '}addressbook-description' => $i['description'], '{http://calendarserver.org/ns/}getctag' => $i['ctag'], ); @@ -73,7 +74,7 @@ class OC_Connector_Sabre_CardDAV extends Sabre_CardDAV_Backend_Abstract { case '{DAV:}displayname' : $name = $newvalue; break; - case '{' . Sabre_CardDAV_Plugin::NS_CARDDAV + case '{' . \Sabre_CardDAV_Plugin::NS_CARDDAV . '}addressbook-description' : $description = $newvalue; break; @@ -84,7 +85,7 @@ class OC_Connector_Sabre_CardDAV extends Sabre_CardDAV_Backend_Abstract { } } - OCA\Contacts\Addressbook::edit($addressbookid, $name, $description); + Contacts\Addressbook::edit($addressbookid, $name, $description); return true; @@ -109,18 +110,18 @@ class OC_Connector_Sabre_CardDAV extends Sabre_CardDAV_Backend_Abstract { case '{DAV:}displayname' : $displayname = $newvalue; break; - case '{' . Sabre_CardDAV_Plugin::NS_CARDDAV + case '{' . \Sabre_CardDAV_Plugin::NS_CARDDAV . '}addressbook-description' : $description = $newvalue; break; default : - throw new Sabre_DAV_Exception_BadRequest('Unknown property: ' + throw new \Sabre_DAV_Exception_BadRequest('Unknown property: ' . $property); } } - OCA\Contacts\Addressbook::addFromDAVData( + Contacts\Addressbook::addFromDAVData( $principaluri, $url, $name, @@ -135,7 +136,7 @@ class OC_Connector_Sabre_CardDAV extends Sabre_CardDAV_Backend_Abstract { * @return void */ public function deleteAddressBook($addressbookid) { - OCA\Contacts\Addressbook::delete($addressbookid); + Contacts\Addressbook::delete($addressbookid); } /** @@ -145,7 +146,7 @@ class OC_Connector_Sabre_CardDAV extends Sabre_CardDAV_Backend_Abstract { * @return array */ public function getCards($addressbookid) { - $data = OCA\Contacts\VCard::all($addressbookid); + $data = Contacts\VCard::all($addressbookid); $cards = array(); foreach($data as $i) { //OCP\Util::writeLog('contacts', __METHOD__.', uri: ' . $i['uri'], OCP\Util::DEBUG); @@ -169,7 +170,7 @@ class OC_Connector_Sabre_CardDAV extends Sabre_CardDAV_Backend_Abstract { * @return array */ public function getCard($addressbookid, $carduri) { - return OCA\Contacts\VCard::findWhereDAVDataIs($addressbookid, $carduri); + return Contacts\VCard::findWhereDAVDataIs($addressbookid, $carduri); } @@ -182,7 +183,7 @@ class OC_Connector_Sabre_CardDAV extends Sabre_CardDAV_Backend_Abstract { * @return bool */ public function createCard($addressbookid, $carduri, $carddata) { - OCA\Contacts\VCard::addFromDAVData($addressbookid, $carduri, $carddata); + Contacts\VCard::addFromDAVData($addressbookid, $carduri, $carddata); return true; } @@ -195,7 +196,7 @@ class OC_Connector_Sabre_CardDAV extends Sabre_CardDAV_Backend_Abstract { * @return bool */ public function updateCard($addressbookid, $carduri, $carddata) { - return OCA\Contacts\VCard::editFromDAVData( + return Contacts\VCard::editFromDAVData( $addressbookid, $carduri, $carddata ); } @@ -208,6 +209,6 @@ class OC_Connector_Sabre_CardDAV extends Sabre_CardDAV_Backend_Abstract { * @return bool */ public function deleteCard($addressbookid, $carduri) { - return OCA\Contacts\VCard::deleteFromDAVData($addressbookid, $carduri); + return Contacts\VCard::deleteFromDAVData($addressbookid, $carduri); } } diff --git a/lib/sabre/card.php b/lib/carddav/card.php similarity index 78% rename from lib/sabre/card.php rename to lib/carddav/card.php index da141e72..f26c446b 100644 --- a/lib/sabre/card.php +++ b/lib/carddav/card.php @@ -20,11 +20,15 @@ * */ +namespace OCA\Contacts\CardDAV; + +use OCA\Contacts; + /** * This class overrides Sabre_CardDAV_Card::getACL() * to return read/write permissions based on user and shared state. */ -class OC_Connector_Sabre_CardDAV_Card extends Sabre_CardDAV_Card { +class Card extends \Sabre_CardDAV_Card { /** * Array with information about the containing addressbook @@ -40,7 +44,7 @@ class OC_Connector_Sabre_CardDAV_Card extends Sabre_CardDAV_Card { * @param array $addressBookInfo * @param array $cardData */ - public function __construct(Sabre_CardDAV_Backend_Abstract $carddavBackend, array $addressBookInfo, array $cardData) { + public function __construct(\Sabre_CardDAV_Backend_Abstract $carddavBackend, array $addressBookInfo, array $cardData) { $this->addressBookInfo = $addressBookInfo; parent::__construct($carddavBackend, $addressBookInfo, $cardData); @@ -63,15 +67,15 @@ class OC_Connector_Sabre_CardDAV_Card extends Sabre_CardDAV_Card { $readprincipal = $this->getOwner(); $writeprincipal = $this->getOwner(); - $uid = OCA\Contacts\Addressbook::extractUserID($this->getOwner()); + $uid = Contacts\Addressbook::extractUserID($this->getOwner()); - if($uid != OCP\USER::getUser()) { - $sharedAddressbook = OCP\Share::getItemSharedWithBySource('addressbook', $this->addressBookInfo['id']); - if ($sharedAddressbook && ($sharedAddressbook['permissions'] & OCP\PERMISSION_READ)) { - $readprincipal = 'principals/' . OCP\USER::getUser(); + if($uid != \OCP\USER::getUser()) { + $sharedAddressbook = \OCP\Share::getItemSharedWithBySource('addressbook', $this->addressBookInfo['id']); + if ($sharedAddressbook && ($sharedAddressbook['permissions'] & \OCP\PERMISSION_READ)) { + $readprincipal = 'principals/' . \OCP\USER::getUser(); } - if ($sharedAddressbook && ($sharedAddressbook['permissions'] & OCP\PERMISSION_UPDATE)) { - $writeprincipal = 'principals/' . OCP\USER::getUser(); + if ($sharedAddressbook && ($sharedAddressbook['permissions'] & \OCP\PERMISSION_UPDATE)) { + $writeprincipal = 'principals/' . \OCP\USER::getUser(); } } diff --git a/lib/sabre/plugin.php b/lib/carddav/plugin.php similarity index 95% rename from lib/sabre/plugin.php rename to lib/carddav/plugin.php index 500897eb..780652a9 100644 --- a/lib/sabre/plugin.php +++ b/lib/carddav/plugin.php @@ -22,16 +22,17 @@ * */ -namespace OCA\Contacts; +namespace OCA\Contacts\CardDAV; use Sabre\VObject; +use OCA\Contacts; /** * This class overrides Sabre_CardDAV_Plugin::validateVCard() to be able * to import partially invalid vCards by ignoring invalid lines and to * validate and upgrade using \OCA\Contacts\VCardObject. */ -class CardDAVPlugin extends \Sabre_CardDAV_Plugin { +class Plugin extends \Sabre_CardDAV_Plugin { /** * Checks if the submitted vCard data is in fact, valid. diff --git a/lib/sabre/stringproperty.php b/lib/carddav/stringproperty.php similarity index 100% rename from lib/sabre/stringproperty.php rename to lib/carddav/stringproperty.php diff --git a/lib/sabre/useraddressbooks.php b/lib/carddav/useraddressbooks.php similarity index 82% rename from lib/sabre/useraddressbooks.php rename to lib/carddav/useraddressbooks.php index 328b433b..22262d86 100644 --- a/lib/sabre/useraddressbooks.php +++ b/lib/carddav/useraddressbooks.php @@ -20,11 +20,13 @@ * */ +namespace OCA\Contacts\CardDAV; + /** * This class overrides Sabre_CardDAV_UserAddressBooks::getChildren() - * to instantiate OC_Connector_Sabre_CardDAV_AddressBooks. + * to instantiate \OCA\Contacts\CardDAV\AddressBooks. */ -class OC_Connector_Sabre_CardDAV_UserAddressBooks extends Sabre_CardDAV_UserAddressBooks { +class UserAddressBooks extends \Sabre_CardDAV_UserAddressBooks { /** * Returns a list of addressbooks @@ -36,7 +38,7 @@ class OC_Connector_Sabre_CardDAV_UserAddressBooks extends Sabre_CardDAV_UserAddr $addressbooks = $this->carddavBackend->getAddressbooksForUser($this->principalUri); $objs = array(); foreach($addressbooks as $addressbook) { - $objs[] = new OC_Connector_Sabre_CardDAV_AddressBook($this->carddavBackend, $addressbook); + $objs[] = new AddressBook($this->carddavBackend, $addressbook); } return $objs; diff --git a/lib/sabre/vcard.php b/lib/carddav/vcard.php similarity index 100% rename from lib/sabre/vcard.php rename to lib/carddav/vcard.php From 99e8f5b5f6636ee7f3b60ba9d7bea5fd6b89c046 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Tue, 5 Mar 2013 11:36:35 +0100 Subject: [PATCH 021/236] Contacts: Properly namespace VObject subclasses. --- appinfo/app.php | 22 ++++++++++----------- import.php | 2 +- lib/carddav/plugin.php | 4 ++-- lib/{carddav => vobject}/stringproperty.php | 2 +- lib/{carddav => vobject}/vcard.php | 11 ++++++----- 5 files changed, 21 insertions(+), 20 deletions(-) rename lib/{carddav => vobject}/stringproperty.php (98%) rename lib/{carddav => vobject}/vcard.php (95%) diff --git a/appinfo/app.php b/appinfo/app.php index 187b1375..d775b04b 100644 --- a/appinfo/app.php +++ b/appinfo/app.php @@ -6,8 +6,8 @@ OC::$CLASSPATH['OCA\Contacts\Hooks'] = 'contacts/lib/hooks.php'; OC::$CLASSPATH['OCA\Contacts\Share_Backend_Contact'] = 'contacts/lib/share/contact.php'; OC::$CLASSPATH['OCA\Contacts\Share_Backend_Addressbook'] = 'contacts/lib/share/addressbook.php'; OC::$CLASSPATH['OCA\Contacts\AddressbookProvider'] = 'contacts/lib/addressbookprovider.php'; -OC::$CLASSPATH['OCA\Contacts\VCardObject'] = 'contacts/lib/sabre/vcard.php'; -OC::$CLASSPATH['OCA\Contacts\StringProperty'] = 'contacts/lib/sabre/stringproperty.php'; +OC::$CLASSPATH['OCA\Contacts\VObject\VCard'] = 'contacts/lib/vobject/vcard.php'; +OC::$CLASSPATH['OCA\Contacts\VObject\StringProperty'] = 'contacts/lib/vobject/stringproperty.php'; OC::$CLASSPATH['OCA\Contacts\CardDAV\Backend'] = 'contacts/lib/sabre/backend.php'; OC::$CLASSPATH['OCA\Contacts\CardDAV\Plugin'] = 'contacts/lib/sabre/plugin.php'; OC::$CLASSPATH['OCA\Contacts\CardDAV\AddressBookRoot'] = 'contacts/lib/sabre/addressbookroot.php'; @@ -16,16 +16,16 @@ OC::$CLASSPATH['OCA\Contacts\CardDAV\AddressBook'] = 'contacts/lib/sabre/address OC::$CLASSPATH['OCA\Contacts\CardDAV\Card'] = 'contacts/lib/sabre/card.php'; OC::$CLASSPATH['OCA\Contacts\SearchProvider'] = 'contacts/lib/search.php'; -require_once __DIR__ . '/../lib/sabre/vcard.php'; +require_once __DIR__ . '/../lib/vobject/vcard.php'; //require_once __DIR__ . '/../lib/sabre/stringproperty.php'; -Sabre\VObject\Component::$classMap['VCARD'] = 'OCA\Contacts\VCardObject'; -Sabre\VObject\Property::$classMap['FN'] = 'OCA\Contacts\StringProperty'; -Sabre\VObject\Property::$classMap['NOTE'] = 'OCA\Contacts\StringProperty'; -Sabre\VObject\Property::$classMap['NICKNAME'] = 'OCA\Contacts\StringProperty'; -Sabre\VObject\Property::$classMap['EMAIL'] = 'OCA\Contacts\StringProperty'; -Sabre\VObject\Property::$classMap['TEL'] = 'OCA\Contacts\StringProperty'; -Sabre\VObject\Property::$classMap['IMPP'] = 'OCA\Contacts\StringProperty'; -Sabre\VObject\Property::$classMap['URL'] = 'OCA\Contacts\StringProperty'; +Sabre\VObject\Component::$classMap['VCARD'] = 'OCA\Contacts\VObject\VCard'; +Sabre\VObject\Property::$classMap['FN'] = 'OCA\Contacts\VObject\StringProperty'; +Sabre\VObject\Property::$classMap['NOTE'] = 'OCA\Contacts\VObject\StringProperty'; +Sabre\VObject\Property::$classMap['NICKNAME'] = 'OCA\Contacts\VObject\StringProperty'; +Sabre\VObject\Property::$classMap['EMAIL'] = 'OCA\Contacts\VObject\StringProperty'; +Sabre\VObject\Property::$classMap['TEL'] = 'OCA\Contacts\VObject\StringProperty'; +Sabre\VObject\Property::$classMap['IMPP'] = 'OCA\Contacts\VObject\StringProperty'; +Sabre\VObject\Property::$classMap['URL'] = 'OCA\Contacts\VObject\StringProperty'; OCP\Util::connectHook('OC_User', 'post_createUser', 'OCA\Contacts\Hooks', 'createUser'); OCP\Util::connectHook('OC_User', 'post_deleteUser', 'OCA\Contacts\Hooks', 'deleteUser'); diff --git a/import.php b/import.php index d79c3a8d..7006a001 100644 --- a/import.php +++ b/import.php @@ -146,7 +146,7 @@ foreach($parts as $part) { } } try { - $vcard->validate(VCardObject::REPAIR|VCardObject::UPGRADE); + $vcard->validate(VCard::REPAIR|VCard::UPGRADE); } catch (\Exception $e) { OCP\Util::writeLog('contacts', __LINE__ . ' ' . 'Error validating vcard: ' . $e->getMessage() . $nl . $vcard->serialize(), diff --git a/lib/carddav/plugin.php b/lib/carddav/plugin.php index 780652a9..22c3e615 100644 --- a/lib/carddav/plugin.php +++ b/lib/carddav/plugin.php @@ -30,7 +30,7 @@ use OCA\Contacts; /** * This class overrides Sabre_CardDAV_Plugin::validateVCard() to be able * to import partially invalid vCards by ignoring invalid lines and to - * validate and upgrade using \OCA\Contacts\VCardObject. + * validate and upgrade using \OCA\Contacts\VCard. */ class Plugin extends \Sabre_CardDAV_Plugin { @@ -63,7 +63,7 @@ class Plugin extends \Sabre_CardDAV_Plugin { throw new \Sabre_DAV_Exception_UnsupportedMediaType('This collection can only support vcard objects.'); } - $vobj->validate(VCardObject::REPAIR|VCardObject::UPGRADE); + $vobj->validate(VCard::REPAIR|VCard::UPGRADE); $data = $vobj->serialize(); } } \ No newline at end of file diff --git a/lib/carddav/stringproperty.php b/lib/vobject/stringproperty.php similarity index 98% rename from lib/carddav/stringproperty.php rename to lib/vobject/stringproperty.php index 32da015d..3466ca1c 100644 --- a/lib/carddav/stringproperty.php +++ b/lib/vobject/stringproperty.php @@ -23,7 +23,7 @@ * */ -namespace OCA\Contacts; +namespace OCA\Contacts\VObject; use Sabre\VObject; diff --git a/lib/carddav/vcard.php b/lib/vobject/vcard.php similarity index 95% rename from lib/carddav/vcard.php rename to lib/vobject/vcard.php index b1bc02fd..cb4bd335 100644 --- a/lib/carddav/vcard.php +++ b/lib/vobject/vcard.php @@ -23,8 +23,9 @@ * */ -namespace OCA\Contacts; +namespace OCA\Contacts\VObject; +use OCA\Contacts; use Sabre\VObject; /** @@ -32,7 +33,7 @@ use Sabre\VObject; * to import partially invalid vCards by ignoring invalid lines and to * validate and upgrade using .... */ -class VCardObject extends VObject\Component\VCard { +class VCard extends VObject\Component\VCard { /** * The following constants are used by the validate() method. @@ -56,7 +57,7 @@ class VCardObject extends VObject\Component\VCard { */ protected function formatPropertyTypes(&$property) { foreach($property->parameters as $key=>&$parameter) { - $types = App::getTypesOfProperty($property->name); + $types = Contacts\App::getTypesOfProperty($property->name); if(is_array($types) && in_array(strtoupper($parameter->name), array_keys($types)) || strtoupper($parameter->name) == 'PREF') { unset($property->parameters[$key]); @@ -98,9 +99,9 @@ class VCardObject extends VObject\Component\VCard { * Validates the node for correctness. * * The following options are supported: - * - Node::REPAIR - If something is broken, and automatic repair may + * - VCard::REPAIR - If something is broken, and automatic repair may * be attempted. - * - VCardObject::UPGRADE - If needed the vCard will be upgraded to version 3.0. + * - VCard::UPGRADE - If needed the vCard will be upgraded to version 3.0. * * An array is returned with warnings. * From 9f8ff3c8cdbaa564a313e7e99611e1f7779fdeeb Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Tue, 5 Mar 2013 12:05:09 +0100 Subject: [PATCH 022/236] Contacts: Fix paths for CardDAV classes. --- appinfo/app.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/appinfo/app.php b/appinfo/app.php index d775b04b..048a6020 100644 --- a/appinfo/app.php +++ b/appinfo/app.php @@ -8,16 +8,16 @@ OC::$CLASSPATH['OCA\Contacts\Share_Backend_Addressbook'] = 'contacts/lib/share/a OC::$CLASSPATH['OCA\Contacts\AddressbookProvider'] = 'contacts/lib/addressbookprovider.php'; OC::$CLASSPATH['OCA\Contacts\VObject\VCard'] = 'contacts/lib/vobject/vcard.php'; OC::$CLASSPATH['OCA\Contacts\VObject\StringProperty'] = 'contacts/lib/vobject/stringproperty.php'; -OC::$CLASSPATH['OCA\Contacts\CardDAV\Backend'] = 'contacts/lib/sabre/backend.php'; -OC::$CLASSPATH['OCA\Contacts\CardDAV\Plugin'] = 'contacts/lib/sabre/plugin.php'; -OC::$CLASSPATH['OCA\Contacts\CardDAV\AddressBookRoot'] = 'contacts/lib/sabre/addressbookroot.php'; -OC::$CLASSPATH['OCA\Contacts\CardDAV\UserAddressBooks'] = 'contacts/lib/sabre/useraddressbooks.php'; -OC::$CLASSPATH['OCA\Contacts\CardDAV\AddressBook'] = 'contacts/lib/sabre/addressbook.php'; -OC::$CLASSPATH['OCA\Contacts\CardDAV\Card'] = 'contacts/lib/sabre/card.php'; +OC::$CLASSPATH['OCA\Contacts\CardDAV\Backend'] = 'contacts/lib/carddav/backend.php'; +OC::$CLASSPATH['OCA\Contacts\CardDAV\Plugin'] = 'contacts/lib/carddav/plugin.php'; +OC::$CLASSPATH['OCA\Contacts\CardDAV\AddressBookRoot'] = 'contacts/lib/carddav/addressbookroot.php'; +OC::$CLASSPATH['OCA\Contacts\CardDAV\UserAddressBooks'] = 'contacts/lib/carddav/useraddressbooks.php'; +OC::$CLASSPATH['OCA\Contacts\CardDAV\AddressBook'] = 'contacts/lib/carddav/addressbook.php'; +OC::$CLASSPATH['OCA\Contacts\CardDAV\Card'] = 'contacts/lib/carddav/card.php'; OC::$CLASSPATH['OCA\Contacts\SearchProvider'] = 'contacts/lib/search.php'; require_once __DIR__ . '/../lib/vobject/vcard.php'; -//require_once __DIR__ . '/../lib/sabre/stringproperty.php'; +//require_once __DIR__ . '/../lib/vobject/stringproperty.php'; Sabre\VObject\Component::$classMap['VCARD'] = 'OCA\Contacts\VObject\VCard'; Sabre\VObject\Property::$classMap['FN'] = 'OCA\Contacts\VObject\StringProperty'; Sabre\VObject\Property::$classMap['NOTE'] = 'OCA\Contacts\VObject\StringProperty'; From 604b819014052b63e31a3f741e63c9488b652398 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Sun, 10 Mar 2013 12:34:41 +0100 Subject: [PATCH 023/236] Contacts: Added backends for database and shared contacts. --- lib/backend/abstractbackend.php | 177 +++++++++++++ lib/backend/database.php | 450 ++++++++++++++++++++++++++++++++ lib/backend/shared.php | 106 ++++++++ 3 files changed, 733 insertions(+) create mode 100644 lib/backend/abstractbackend.php create mode 100644 lib/backend/database.php create mode 100644 lib/backend/shared.php diff --git a/lib/backend/abstractbackend.php b/lib/backend/abstractbackend.php new file mode 100644 index 00000000..d8eb7585 --- /dev/null +++ b/lib/backend/abstractbackend.php @@ -0,0 +1,177 @@ +. + * + */ + +namespace OCA\Contacts\Backend; + +/** + * Subclass this class for Cantacts backends + */ + +abstract class AbstractBackend { + + /** + * Returns the list of addressbooks for a specific user. + * + * The returned arrays MUST contain a unique 'id' for the + * backend and a 'displayname', and it MAY contain a + * 'description'. + * + * @param string $principaluri + * @return array + */ + public function getAddressBooksForUser($userid) { + } + + /** + * Updates an addressbook's properties + * + * The $properties array contains the changes to be made. + * + * Currently the only ones supported are 'displayname' and + * 'description', but backends can implement additional. + * + * @param string $addressbookid + * @param array $properties + * @return bool + */ + public function updateAddressBook($addressbookid, array $properties) { + } + + /** + * Creates a new address book + * + * Currently the only ones supported are 'displayname' and + * 'description', but backends can implement additional. + * 'displayname' MUST be present. + * + * @param array $properties + * @return string|false The ID if the newly created AddressBook or false on error. + */ + public function createAddressBook(array $properties) { + } + + /** + * Deletes an entire addressbook and all its contents + * + * @param string $addressbookid + * @return bool + */ + public function deleteAddressBook($addressbookid) { + } + + /** + * @brief Get the last modification time for an address book. + * + * Must return a UNIX time stamp or null if the backend + * doesn't support it. + * + * @param string $addressbookid + * @returns int | null + */ + public static function lastModifiedAddressBook($addressbookid) { + } + + /** + * Returns all contacts for a specific addressbook id. + * + * The returned array MUST contain the unique ID of the contact mapped to 'id', a + * displayname mapped to 'displayname' and an integer 'permissions' value using there + * ownCloud CRUDS constants (which MUST be at least \OCP\PERMISSION_READ), and SHOULD + * contain the properties of the contact formatted as a vCard 3.0 + * https://tools.ietf.org/html/rfc2426 mapped to 'carddata' or as an + * \OCA\Contacts\VObject\VCard object mapped to 'vcard'. + * + * Example: + * + * array( + * 0 => array('id' => '4e111fef5df', 'permissions' => 1, 'displayname' => 'John Q. Public', 'vcard' => $object), + * 1 => array('id' => 'bbcca2d1535', 'permissions' => 32, 'displayname' => 'Jane Doe', 'carddata' => $data) + * ); + * + * For contacts that contain loads of data, the 'carddata' or 'vcard' MAY be omitted + * as it can be fetched later. + * + * TODO: Some sort of ETag? + * + * @param string $addressbookid + * @param bool $omitdata Don't fetch the entire carddata or vcard. + * @return array + */ + public function getContacts($addressbookid, $limit = null, $offset = null, $omitdata = false) { + } + + /** + * Returns a specfic contact. + * + * Same as getContacts except that either 'carddata' or 'vcard' is mandatory. + * + * @param string $addressbookid + * @param mixed $id + * @return array + */ + public function getContact($addressbookid, $id) { + } + + /** + * Creates a new contact + * + * @param string $addressbookid + * @param string $carddata + * @return bool + */ + public function createContact($addressbookid, $carddata) { + } + + /** + * Updates a contact + * + * @param string $addressbookid + * @param mixed $id + * @param string $carddata + * @return bool + */ + public function updateContact($addressbookid, $id, $carddata) { + } + + /** + * Deletes a contact + * + * @param string $addressbookid + * @param mixed $id + * @return bool + */ + public function deleteContact($addressbookid, $id) { + } + + /** + * @brief Get the last modification time for a contact. + * + * Must return a UNIX time stamp or null if the backend + * doesn't support it. + * + * @param string $addressbookid + * @param mixed $id + * @returns int | null + */ + public function lastModifiedContact($addressbookid, $contact) { + } +} \ No newline at end of file diff --git a/lib/backend/database.php b/lib/backend/database.php new file mode 100644 index 00000000..0b11ebd0 --- /dev/null +++ b/lib/backend/database.php @@ -0,0 +1,450 @@ +. + * + */ + +namespace OCA\Contacts\Backend; + +use OCA\Contacts\Contact; + +/** + * Subclass this class for Cantacts backends + */ + +class Database extends AbstractBackend { + + /** + * Sets up the backend + * + * @param string $addressBooksTableName + * @param string $cardsTableName + */ + public function __construct( + $userid = null, + $addressBooksTableName = '*PREFIX*contacts_addressbooks', + $cardsTableName = '*PREFIX*contacts_cards' + ) { + $this->userid = $userid ? $userid : \OCP\User::getUser(); + $this->addressBooksTableName = $addressBooksTableName; + $this->cardsTableName = $cardsTableName; + $this->addressbooks = array(); + } + + public function hasAddressBook($addressbookid) { + if($this->addressbooks) { + foreach($this->addressbooks as $addressbook) { + if($addressbook['id'] === $addressbookid) { + return true; + } + } + return false; + } + try { + $query = 'SELECT `id` FROM `' . $this->addressBooksTableName + . '` WHERE `id` = ?'; + \OCP\Util::writeLog('contacts', __METHOD__.' query: ' + . $query, \OCP\Util::DEBUG); + $stmt = \OCP\DB::prepare($query); + $result = $stmt->execute(array($addressbookid)); + if (\OC_DB::isError($result)) { + \OCP\Util::write('contacts', __METHOD__. 'DB error: ' + . \OC_DB::getErrorMessage($result), \OCP\Util::ERROR); + return false; + } + if($result->fetchOne()) { + return true; + } + } catch(\Exception $e) { + \OCP\Util::writeLog('contacts', __METHOD__.' exception: ' + . $e->getMessage(), \OCP\Util::ERROR); + return false; + } + return false; + } + + /** + * Returns the list of addressbooks for a specific user. + * + * TODO: Create default if none exists. + * + * @param string $principaluri + * @return array + */ + public function getAddressBooksForUser($userid = null) { + $userid = $userid ? $userid : $this->userid; + + try { + $stmt = \OCP\DB::prepare( 'SELECT * FROM `' + . $this->addressBooksTableName + . '` WHERE `userid` = ? ORDER BY `displayname`' ); + $result = $stmt->execute(array($userid)); + if (\OC_DB::isError($result)) { + \OCP\Util::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OCP\Util::ERROR); + return $this->addressbooks; + } + } catch(\Exception $e) { + \OCP\Util::writeLog('contacts', __METHOD__.' exception: ' . $e->getMessage(), \OCP\Util::ERROR); + return $this->addressbooks; + } + + while( $row = $result->fetchRow()) { + $row['permissions'] = \OCP\PERMISSION_ALL; + $this->addressbooks[] = $row; + } + return $this->addressbooks; + } + + /** + * Updates an addressbook's properties + * + * @param string $addressbookid + * @param array $changes + * @return bool + */ + public function updateAddressBook($addressbookid, array $changes) { + if(count($changes) === 0) { + return false; + } + + $query = 'UPDATE `' . $this->addressBooksTableName . '` SET '; + + $updates = array(); + + if(isset($changes['displayname'])) { + $query .= '`displayname` = ?, '; + $updates[] = $changes['displayname']; + } + + if(isset($changes['description'])) { + $query .= '`description` = ?, '; + $updates[] = $changes['description']; + } + + $query .= '`ctag` = `ctag` + 1 WHERE `id` = ?'; + $updates[] = $addressbookid; + + try { + $stmt = \OCP\DB::prepare($query); + $result = $stmt->execute($updates); + if (\OC_DB::isError($result)) { + \OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OC_Log::ERROR); + return false; + } + } catch(Exception $e) { + \OCP\Util::writeLog('contacts', __METHOD__ . ', exception: ' . $e->getMessage(), \OCP\Util::ERROR); + return false; + } + + return true; + } + + /** + * Creates a new address book + * + * Supported properties are 'displayname', 'description' and 'uri'. + * 'uri' is supported to allow to add from CardDAV requests, and MUST + * be used for the 'uri' database field if present. + * 'displayname' MUST be present. + * + * @param array $properties + * @return string|false The ID if the newly created AddressBook or false on error. + */ + public function createAddressBook(array $properties, $userid = null) { + $userid = $userid ? $userid : $this->userid; + if(count($changes) === 0) { + return false; + } + + $query = 'INSERT INTO `' . $this->addressBooksTableName . '` ' + . '(`userid`,`displayname`,`uri`,`description`,`ctag`) VALUES(?,?,?,?,?)'; + + $updates = array($userid, $properties['displayname']); + $updates[] = isset($properties['uri']) + ? $properties['uri'] + : $this->createAddressBookURI($properties['displayname']); + $updates[] = isset($properties['description']) ? $properties['description'] : ''; + + try { + $stmt = \OCP\DB::prepare($query); + $result = $stmt->execute($updates); + if (\OC_DB::isError($result)) { + \OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OC_Log::ERROR); + return false; + } + } catch(Exception $e) { + \OCP\Util::writeLog('contacts', __METHOD__ . ', exception: ' . $e->getMessage(), \OCP\Util::ERROR); + return false; + } + + return \OCP\DB::insertid($this->addressBooksTableName); + } + + /** + * Deletes an entire addressbook and all its contents + * + * @param string $addressbookid + * @return bool + */ + public function deleteAddressBook($addressbookid) { + \OC_Hook::emit('\OCA\Contacts\VCard', 'pre_deleteAddressBook', + array('id' => $addressbookid) + ); + + $stmt = \OCP\DB::prepare('DELETE FROM `' . $this->cardsTableName . '` WHERE `addressbookid` = ?'); + try { + $stmt->execute(array($addressbookid)); + } catch(\Exception $e) { + \OCP\Util::writeLog('contacts', __METHOD__. + ', exception: ' . $e->getMessage(), \OCP\Util::ERROR); + return false; + } + + $stmt = \OCP\DB::prepare('DELETE FROM `' . $this->addressBooksTableName . '` WHERE `id` = ?'); + try { + $stmt->execute(array($addressbookid)); + } catch(\Exception $e) { + \OCP\Util::writeLog('contacts', __METHOD__. + ', exception: ' . $e->getMessage(), \OCP\Util::ERROR); + return false; + } + + return true; + } + + /** + * @brief Updates ctag for addressbook + * @param integer $id + * @return boolean + */ + public static function touchAddressBook($id) { + $stmt = \OCP\DB::prepare( 'UPDATE `' . $this->addressBooksTableName . '` SET `ctag` = `ctag` + 1 WHERE `id` = ?' ); + $stmt->execute(array($id)); + + return true; + } + + public static function lastModifiedAddressBook($addressbookid) { + $sql = 'SELECT MAX(`lastmodified`) FROM `' . $this->cardsTableName . '`, `' . $this->addressBooksTableName . '` ' . + 'WHERE `' . $this->cardsTableName . '`.`addressbookid` = `*PREFIX*contacts_addressbooks`.`id` AND ' . + '`' . $this->addressBooksTableName . '`.`userid` = ? AND `' . $this->addressBooksTableName . '`.`id` = ?'; + $stmt = \OCP\DB::prepare($sql); + $result = $stmt->execute(array($this->userid, $addressbookid)); + if (\OC_DB::isError($result)) { + \OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OC_Log::ERROR); + return null; + } + return $result->fetchOne(); + } + + /** + * Returns all contacts for a specific addressbook id. + * + * @param string $addressbookid + * @param bool $omitdata Don't fetch the entire carddata or vcard. + * @return array + */ + public function getContacts($addressbookid, $limit = null, $offset = null, $omitdata = false) { + $cards = array(); + try { + $qfields = $omitdata ? '`id`, `fullname`' : '*'; + $query = 'SELECT ' . $qfields . ' FROM `' . $this->cardsTableName + . '` WHERE `addressbookid` = ? ORDER BY `fullname`'; + $stmt = \OCP\DB::prepare($query, $limit, $offset); + $result = $stmt->execute(array($addressbookid)); + if (\OC_DB::isError($result)) { + \OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OCP\Util::ERROR); + return $cards; + } + } catch(\Exception $e) { + \OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), \OCP\Util::ERROR); + return $cards; + } + + if(!is_null($result)) { + while( $row = $result->fetchRow()) { + $row['permissions'] = \OCP\PERMISSION_ALL; + $cards[] = $row; + } + } + + return $cards; + } + + /** + * Returns a specfic contact. + * + * Same as getContacts except that either 'carddata' or 'vcard' is mandatory. + * + * @param string $addressbookid + * @param mixed $id Contact ID + * @return mixed + */ + public function getContact($addressbookid, $id) { + $where_query = ''; + //if(isset) + try { + $stmt = \OCP\DB::prepare( 'SELECT * FROM `' . $this->cardsTableName . '` WHERE `id` = ?' ); + $result = $stmt->execute(array($id)); + if (\OC_DB::isError($result)) { + \OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OCP\Util::ERROR); + return false; + } + } catch(\Exception $e) { + \OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), \OCP\Util::ERROR); + \OCP\Util::writeLog('contacts', __METHOD__.', id: '. $id, \OCP\Util::DEBUG); + return false; + } + + $row = $result->fetchRow(); + $row['permissions'] = \OCP\PERMISSION_ALL; + return $row; + } + + /** + * Creates a new contact + * + * @param string $addressbookid + * @param Contact $contact + * @return bool + */ + public function createContact($addressbookid, $contact, $uri = null) { + $uri = is_null($uri) ? $contact->UID . '.vcf' : $uri; + + $now = new \DateTime; + $contact->REV = $now->format(\DateTime::W3C); + + $data = $contact->serialize(); + $stmt = \OCP\DB::prepare('INSERT INTO `' + . $this->cardsTableName . '` (`addressbookid`,`fullname`,`carddata`,`uri`,`lastmodified`) VALUES(?,?,?,?,?)' ); + try { + $result = $stmt->execute(array($addressbookid, (string)$contact->FN, $contact->serialize(), $uri, time())); + if (\OC_DB::isError($result)) { + \OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OCP\Util::ERROR); + return false; + } + } catch(\Exception $e) { + \OCP\Util::writeLog('contacts', __METHOD__.', exception: '.$e->getMessage(), \OCP\Util::ERROR); + return false; + } + $newid = \OCP\DB::insertid($this->cardsTableName); + + $this->touch(addressbookid); + \OC_Hook::emit('\OCA\Contacts\VCard', 'post_createVCard', + array('id' => $id, 'contact' => $contact) + ); + return $newid; + } + + /** + * Updates a contact + * + * @param string $addressbookid + * @param string $id Contact ID + * @param string $carddata + * @return bool + */ + public function updateContact($addressbookid, $id, $contact) { + $now = new \DateTime; + $contact->REV = $now->format(\DateTime::W3C); + + $data = $card->serialize(); + $stmt = \OCP\DB::prepare( 'UPDATE `' . $this->cardsTableName + . '` SET `fullname` = ?,`carddata` = ?, `lastmodified` = ? WHERE `id` = ?' ); + try { + $result = $stmt->execute(array($contact->FN, $data, time(), $id)); + if (\OC_DB::isError($result)) { + \OCP\Util::writeLog('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OCP\Util::ERROR); + return false; + } + } catch(\Exception $e) { + \OCP\Util::writeLog('contacts', __METHOD__.', exception: ' + . $e->getMessage(), \OCP\Util::ERROR); + \OCP\Util::writeLog('contacts', __METHOD__.', id' . $id, \OCP\Util::DEBUG); + return false; + } + + $this->touch(addressbookid); + \OC_Hook::emit('\OCA\Contacts\VCard', 'post_updateVCard', + array('id' => $id, 'contact' => $contact) + ); + return true; + } + + /** + * Deletes a contact + * + * @param string $addressbookid + * @param string $id + * @return bool + */ + public function deleteContact($addressbookid, $id) { + \OC_Hook::emit('\OCA\Contacts\VCard', 'pre_deleteContact', + array('id' => $id) + ); + $stmt = \OCP\DB::prepare('DELETE FROM `' . $this->cardsTableName + . '` WHERE `id` = ? AND `addressbookid` = ?'); + try { + $stmt->execute(array($id, $addressbookid)); + if (\OC_DB::isError($result)) { + \OCP\Util::writeLog('contacts', __METHOD__. 'DB error: ' + . \OC_DB::getErrorMessage($result), \OCP\Util::ERROR); + return false; + } + } catch(\Exception $e) { + \OCP\Util::writeLog('contacts', __METHOD__. + ', exception: ' . $e->getMessage(), \OCP\Util::ERROR); + \OCP\Util::writeLog('contacts', __METHOD__.', id: ' + . $id, \OCP\Util::DEBUG); + return false; + } + return true; + } + + private function createAddressBookURI($displayname, $userid = null) { + $userid = $userid ? $userid : \OCP\User::getUser(); + $name = str_replace(' ', '_', strtolower($displayname)); + try { + $stmt = \OCP\DB::prepare('SELECT `uri` FROM `' . $this->addressBooksTableName . '` WHERE `userid` = ? '); + $result = $stmt->execute(array($userid)); + if (\OC_DB::isError($result)) { + \OC_Log::write('contacts', __METHOD__. 'DB error: ' . \OC_DB::getErrorMessage($result), \OC_Log::ERROR); + return $name; + } + } catch(Exception $e) { + \OCP\Util::writeLog('contacts', __METHOD__ . ' exception: ' . $e->getMessage(), \OCP\Util::ERROR); + return $name; + } + $uris = array(); + while($row = $result->fetchRow()) { + $uris[] = $row['uri']; + } + + $newname = $name; + $i = 1; + while(in_array($newname, $existing)) { + $newname = $name.$i; + $i = $i + 1; + } + return $newname; + } + + public function lastModifiedContact($addressbookid, $id) { + } +} \ No newline at end of file diff --git a/lib/backend/shared.php b/lib/backend/shared.php new file mode 100644 index 00000000..1afe2bfc --- /dev/null +++ b/lib/backend/shared.php @@ -0,0 +1,106 @@ +. + * + */ + +namespace OCA\Contacts\Backend; + +use OCA\Contacts\Contact; + +/** + * Subclass this class for Cantacts backends + */ + +class Shared extends Database { + + public $addressbooks = array(); + + /** + * Returns the list of addressbooks for a specific user. + * + * TODO: Create default if none exists. + * + * @param string $principaluri + * @return array + */ + public function getAddressBooksForUser($userid = null) { + $userid = $userid ? $userid : $this->userid; + + $this->addressbooks = \OCP\Share::getItemsSharedWith( + 'addressbook', + \OCA\Contacts\Share_Backend_Addressbook::FORMAT_ADDRESSBOOKS + ); + + return $this->addressbooks; + } + + /** + * Returns all contacts for a specific addressbook id. + * + * TODO: Check for parent permissions + * + * @param string $addressbookid + * @param bool $omitdata Don't fetch the entire carddata or vcard. + * @return array + */ + public function getContacts($addressbookid, $limit = null, $offset = null, $omitdata = false) { + $permissions = 0; + if(!$this->addressbooks) { + $this->addressbooks = \OCP\Share::getItemsSharedWith( + 'addressbook', + \OCA\Contacts\Share_Backend_Addressbook::FORMAT_ADDRESSBOOKS + ); + } + + foreach($this->addressbooks as $addressbook) { + if($addressbook['id'] === $addressbookid) { + $permissions = $addressbook['permissions']; + break; + } + } + + $cards = array(); + try { + $qfields = $omitdata ? '`id`, `fullname`' : '*'; + $query = 'SELECT ' . $qfields . ' FROM `' . $this->cardsTableName + . '` WHERE `addressbookid` = ? ORDER BY `fullname`'; + $stmt = \OCP\DB::prepare($query, $limit, $offset); + $result = $stmt->execute(array($addressbookid)); + if (\OC_DB::isError($result)) { + \OC_Log::write('contacts', __METHOD__. 'DB error: ' + . \OC_DB::getErrorMessage($result), \OCP\Util::ERROR); + return $cards; + } + } catch(\Exception $e) { + \OCP\Util::writeLog('contacts', __METHOD__.', exception: ' + . $e->getMessage(), \OCP\Util::ERROR); + return $cards; + } + + if(!is_null($result)) { + while( $row = $result->fetchRow()) { + $row['permissions'] = $permissions; + $cards[] = $row; + } + } + + return $cards; + } +} \ No newline at end of file From efad313280a284af47d5a50630e0b0e0c6821efe Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Sun, 10 Mar 2013 12:36:08 +0100 Subject: [PATCH 024/236] Contacts: Typo in CardDAV plugin. --- lib/carddav/plugin.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/carddav/plugin.php b/lib/carddav/plugin.php index 22c3e615..0602e8c9 100644 --- a/lib/carddav/plugin.php +++ b/lib/carddav/plugin.php @@ -25,7 +25,7 @@ namespace OCA\Contacts\CardDAV; use Sabre\VObject; -use OCA\Contacts; +use OCA\Contacts\VObject\VCard; /** * This class overrides Sabre_CardDAV_Plugin::validateVCard() to be able @@ -54,7 +54,7 @@ class Plugin extends \Sabre_CardDAV_Plugin { $data = \Sabre_DAV_StringUtil::ensureUTF8($data); try { - $vobj = VObject\Reader::read($data, VObject\Reader::OPTION_IGNORE_INVALID_LINE); + $vobj = VObject\Reader::read($data, VObject\Reader::OPTION_IGNORE_INVALID_LINES); } catch (VObject\ParseException $e) { throw new \Sabre_DAV_Exception_UnsupportedMediaType('This resource only supports valid vcard data. Parse error: ' . $e->getMessage()); } From 275c98cce8495aee9a6265d94bafa352508a97c9 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Sun, 10 Mar 2013 12:38:27 +0100 Subject: [PATCH 025/236] Contacts: Generate prettier target for shared address books. --- lib/share/addressbook.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/share/addressbook.php b/lib/share/addressbook.php index f90c1e5c..fef54ad6 100644 --- a/lib/share/addressbook.php +++ b/lib/share/addressbook.php @@ -41,17 +41,18 @@ class Share_Backend_Addressbook implements \OCP\Share_Backend_Collection { * If it does generate a new name e.g. name_# */ public function generateTarget($itemSource, $shareWith, $exclude = null) { - $addressbook = Addressbook::find( $itemSource ); + $addressbook = Addressbook::find($itemSource); $user_addressbooks = array(); foreach(Addressbook::all($shareWith) as $user_addressbook) { $user_addressbooks[] = $user_addressbook['displayname']; } - $name = $addressbook['displayname']; + $name = $addressbook['displayname'] . '(' . $addressbook['userid'] . ')'; $suffix = ''; while (in_array($name.$suffix, $user_addressbooks)) { $suffix++; } + $suffix = $suffix ? ' ' . $suffix : ''; return $name.$suffix; } From c7dfc8fc334ad99f786685db857224823ae4ec55 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Sun, 10 Mar 2013 12:40:11 +0100 Subject: [PATCH 026/236] Contacts: Add generic PIM classes + Contact class. --- lib/abstractpimcollection.php | 144 ++++++++++++++++++++++++++++++++ lib/abstractpimobject.php | 116 ++++++++++++++++++++++++++ lib/contact.php | 153 ++++++++++++++++++++++++++++++++++ lib/ipimobject.php | 92 ++++++++++++++++++++ 4 files changed, 505 insertions(+) create mode 100644 lib/abstractpimcollection.php create mode 100644 lib/abstractpimobject.php create mode 100644 lib/contact.php create mode 100644 lib/ipimobject.php diff --git a/lib/abstractpimcollection.php b/lib/abstractpimcollection.php new file mode 100644 index 00000000..26403126 --- /dev/null +++ b/lib/abstractpimcollection.php @@ -0,0 +1,144 @@ +. + * + */ + +namespace OCA\Contacts; + +/** + * Subclass this for PIM collections + */ + +abstract class PIMCollectionAbstract extends PIMObjectAbstract implements Iterator, Countable, ArrayAccess { + + // Iterator properties + + protected $objects = array(); + + protected $counter = 0; + + /** + * Returns a specific child node, referenced by its id + * + * This method must throw Sabre_DAV_Exception_NotFound if the node does not + * exist. + * + * @param string $id + * @return IPIMObject + */ + abstract function getChild($id); + + /** + * Returns an array with all the child nodes + * + * @return IPIMObject[] + */ + abstract function getChildren($limit = null, $offset = null); + + /** + * Checks if a child-node with the specified id exists + * + * @param string $id + * @return bool + */ + function childExists($id); + + // Iterator methods + + public function rewind() { + $this->counter = 0; + } + + public function next() { + $this->counter++; + } + + public function valid() { + return array_key_exists($this->counter, $this->objects); + } + + public function current() { + return $this->objects[$this->counter]; + } + + /** Implementations can choose to return the current objects ID/UUID + * to be able to iterate over the collection with ID => Object pairs: + * foreach($collection as $id => $object) {} + */ + public function key() { + return $this->counter; + } + + // Countable method. + + /** + * For implementations using a backend where fetching all object at once + * would give too much overhead, they can maintain an internal count value + * and fetch objects progressively. Simply watch the diffence between + * $this->counter, the value of count($this->objects) and the internal + * value, and fetch more objects when needed. + */ + public function count() { + return count($this->objects); + } + + // ArrayAccess methods + + public function offsetSet($offset, $value) { + if (is_null($offset)) { + $this->objects[] = $value; + } else { + $this->objects[$offset] = $value; + } + } + + public function offsetExists($offset) { + return isset($this->objects[$offset]); + } + + public function offsetUnset($offset) { + unset($this->objects[$offset]); + } + + public function offsetGet($offset) { + return isset($this->objects[$offset]) ? $this->objects[$offset] : null; + } + + // Magic property accessors + // TODO: They should go in the implementations. + + public function __set($id, $value) { + + } + + public function __get($id) { + + } + + public function __isset($id) { + + } + + public function __unset($id) { + + } + + +} \ No newline at end of file diff --git a/lib/abstractpimobject.php b/lib/abstractpimobject.php new file mode 100644 index 00000000..f2342947 --- /dev/null +++ b/lib/abstractpimobject.php @@ -0,0 +1,116 @@ +. + * + */ + +namespace OCA\Contacts; + +/** + * Subclass this class or implement IPIMObject interface for PIM objects + */ + +abstract class PIMObjectAbstract implements IPIMObject { + + /** + * This variable holds the ID of this object. + * Depending on the backend, this can be either a string + * or an integer, so we treat them all as strings. + * + * @var string + */ + protected $id; + + /** + * This variable holds the owner of this object. + * + * @var string + */ + protected $owner; + + /** + * This variable holds the parent of this object if any. + * + * @var string|null + */ + protected $parent; + + /** + * This variable holds the permissions of this object. + * + * @var integer + */ + protected $permissions; + + /** + * @return string + */ + public function getId() { + return $this->id; + } + + /** + * @return string|null + */ + public function getOwner() { + return $this->owner; + } + + /** + * 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->parent; + } + + /** CRUDS permissions (Create, Read, Update, Delete, Share) using a bitmask of + * + * \OCP\PERMISSION_CREATE + * \OCP\PERMISSION_READ + * \OCP\PERMISSION_UPDATE + * \OCP\PERMISSION_DELETE + * \OCP\PERMISSION_SHARE + * or + * \OCP\PERMISSION_ALL + * + * @return integer + */ + function getPermissions() { + return $this->permissions; + } + + /** + * @param integer $permission + * @return boolean + */ + function hasPermission($permission) { + return $this->getPermissions() & $permission; + } + + /** + * Save the contact data to backend + * + * @return bool + */ + public function save() { + } + +} \ No newline at end of file diff --git a/lib/contact.php b/lib/contact.php new file mode 100644 index 00000000..de9cc3dc --- /dev/null +++ b/lib/contact.php @@ -0,0 +1,153 @@ +. + * + */ + +namespace OCA\Contacts; + +/** + * 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'; + + protected $props = array(); + + public function __construct($parent, $backend) { + //\OCP\Util::writeLog('contacts', __METHOD__, \OCP\Util::DEBUG); + $this->props['parent'] = $parent; + $this->props['backend'] = $backend; + } + + /** + * @return string|null + */ + public function getOwner() { + return $this->props['owner']; + } + + /** + * @return string|null + */ + public function getId() { + return $this->props['id']; + } + + /** + * 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']; + } + + /** CRUDS permissions (Create, Read, Update, Delete, Share) + * + * @return integer + */ + function getPermissions() { + return $this->props['permissions']; + } + + /** + * @param integer $permission + * @return bool + */ + function hasPermission($permission) { + return $this->getPermissions() & $permission; + } + + /** + * Save the contact data to backend + * + * @return bool + */ + public function save() { + if($this->id) { + return $this->props['backend']->updateContact( + $this->props['parent']->getID(), $this->props['id'], $this->serialize() + ); + } else { + $this->props['id'] = $this->props['backend']->createContact( + $this->parent->getID(), $this->serialize() + ); + if($this->props['id'] !== false) { + $this->parent->setChildID(); + } + return $this->props['id'] !== false; + } + } + + public function read($data = null) { + // NOTE: Maybe this will mess with + // the magic accessors. + if(!$this->children) { + if(!isset($this->props['carddata']) && is_null($data)) { + $result = $this->props['backend']->getContact($this->parent->getID, $this->id); + if($result) { + if(isset($result['vcard']) && $result['vcard'] instanceof Contact) { + $this->children = $result['vcard']->children; + 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 { + return false; + } + } + } + try { + $obj = \Sabre\VObject\Reader::read( + $data, + \Sabre\VObject\Reader::OPTION_IGNORE_INVALID_LINES); + $this->children = $obj->children; + } catch (\Exception $e) { + \OCP\Util::writeLog('contacts', __METHOD__ . + 'Error parsing carddata: ' . $e->getMessage(), + \OCP\Util::ERROR); + return false; + } + } + return true; + } + + public function lastModified() { + if(!isset($this->props['lastmodified'])) { + $this->read(); + } + return isset($this->props['lastmodified']) + ? isset($this->props['lastmodified']) + : null; + } +} \ No newline at end of file diff --git a/lib/ipimobject.php b/lib/ipimobject.php new file mode 100644 index 00000000..2eb4a5d6 --- /dev/null +++ b/lib/ipimobject.php @@ -0,0 +1,92 @@ +. + * + */ + +namespace OCA\Contacts; + +/** + * Implement this interface for PIM objects + */ + +interface IPIMObject { + + /** + * If this object is part of a collection return a reference + * to the parent object, otherwise return null. + * @return IPIMObject|null + */ + //function getParent(); + + /** + * @return string + */ + public function getId(); + + /** + * @return string|null + */ + function getOwner(); + + /** + * If this object is part of a collection return a reference + * to the parent object, otherwise return null. + * @return IPIMObject|null + */ + function getParent(); + + + /** CRUDS permissions (Create, Read, Update, Delete, Share) using a bitmask of + * + * \OCP\PERMISSION_CREATE + * \OCP\PERMISSION_READ + * \OCP\PERMISSION_UPDATE + * \OCP\PERMISSION_DELETE + * \OCP\PERMISSION_SHARE + * or + * \OCP\PERMISSION_ALL + * + * @return integer + */ + function getPermissions(); + + /** + * @param integer $permission + * @return boolean + */ + function hasPermission($permission); + + /** + * Save the contact data to backend + * + * @return bool + */ + public function save(); + + /** + * @brief Get the last modification time for the object. + * + * Must return a UNIX time stamp or null if the backend + * doesn't support it. + * + * @returns int | null + */ + public function lastModified(); +} \ No newline at end of file From affa2196a8f0daf994ac11c4ad1d9c69ccfcdee9 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Sun, 10 Mar 2013 12:40:59 +0100 Subject: [PATCH 027/236] Contacts: Basic route. --- appinfo/routes.php | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 appinfo/routes.php diff --git a/appinfo/routes.php b/appinfo/routes.php new file mode 100644 index 00000000..ce6188b3 --- /dev/null +++ b/appinfo/routes.php @@ -0,0 +1,21 @@ +create('contacts_index', '/') + ->actionInclude('contacts/index.php'); +// ->action( +// function($params){ +// // +// } +// ); + + From 832dcd0bfb69c7e74a77d30e4acc11d47369d342 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Sun, 10 Mar 2013 12:42:38 +0100 Subject: [PATCH 028/236] Contacts: Log exception. --- ajax/contact/list.php | 1 + 1 file changed, 1 insertion(+) diff --git a/ajax/contact/list.php b/ajax/contact/list.php index 4251ee5b..d11580d1 100644 --- a/ajax/contact/list.php +++ b/ajax/contact/list.php @@ -89,6 +89,7 @@ if($contacts_alphabet) { 'data' => $details, ); } catch (Exception $e) { + \OCP\Util::writeLog('contacts', 'Exception: ' . $e->getMessage(), \OCP\Util::DEBUG); continue; } // This should never execute. From 361253078755d7728db09757b2ade0b528b62acd Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Sun, 10 Mar 2013 12:47:06 +0100 Subject: [PATCH 029/236] Contacts: Make sure all parameters are saved. --- ajax/contact/saveproperty.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ajax/contact/saveproperty.php b/ajax/contact/saveproperty.php index 421ddedf..d3ec402e 100644 --- a/ajax/contact/saveproperty.php +++ b/ajax/contact/saveproperty.php @@ -52,6 +52,11 @@ function setParameters($property, $parameters, $reset = false) { } } } + } else { + if(trim($key) && trim($parameter)) { + debug('Adding parameter: '.$key.'=>'.print_r($parameter, true)); + $property->add($key, strip_tags($parameter)); + } } } } From e811e010c4581c242b9a1dfa44a1528c1b7090dd Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Sun, 10 Mar 2013 12:48:37 +0100 Subject: [PATCH 030/236] Contacts: Remove stale code. --- ajax/contact/saveproperty.php | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/ajax/contact/saveproperty.php b/ajax/contact/saveproperty.php index d3ec402e..022aeb40 100644 --- a/ajax/contact/saveproperty.php +++ b/ajax/contact/saveproperty.php @@ -74,7 +74,7 @@ $checksum = isset($_POST['checksum'])?$_POST['checksum']:null; debug('value: ' . print_r($value, 1)); $multi_properties = array('EMAIL', 'TEL', 'IMPP', 'ADR', 'URL'); -$string_properties = array('FN', 'NICKNAME', 'NOTE', 'EMAIL', 'TEL', 'IMPP', 'ADR', 'URL'); +$string_properties = array('FN', 'TITLE', 'ROLE', 'NICKNAME', 'NOTE', 'EMAIL', 'TEL', 'IMPP', 'ADR', 'URL'); if(!$name) { bailOut(App::$l10n->t('element name is not set.')); @@ -90,11 +90,6 @@ if(is_array($value)) { // NOTE: Important, otherwise the compound value will be // set in the order the fields appear in the form! ksort($value); - //if($name == 'CATEGORIES') { - // $value = VCard::escapeDelimiters($value, ','); - //} else { - // $value = VCard::escapeDelimiters($value, ';'); - //} } else { $value = trim(strip_tags($value)); } @@ -201,9 +196,6 @@ if(!$value) { unset($vcard->{$name}); } } else { - if(in_array($name, $string_properties)) { - $value = strtr($value, array(',' => '\,', ';' => '\;')); - } /* setting value */ switch($element) { case 'BDAY': From e8a2b6fc7403a971fdeaccb6e3ec4501bb7bb873 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Sun, 10 Mar 2013 12:49:36 +0100 Subject: [PATCH 031/236] Contacts: Avoid double escaping on serialize. --- lib/vobject/stringproperty.php | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/vobject/stringproperty.php b/lib/vobject/stringproperty.php index 3466ca1c..daf3c2ba 100644 --- a/lib/vobject/stringproperty.php +++ b/lib/vobject/stringproperty.php @@ -1,6 +1,6 @@ value); + ); + $value = strtr($this->value, array('\,' => ',', '\;' => ';', '\\\\' => '\\')); + $str.=':' . str_replace($src, $out, $value); $out = ''; while(strlen($str) > 0) { From 293c0b260dea5411a1b1b77be87e627177322463 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Sun, 10 Mar 2013 12:50:37 +0100 Subject: [PATCH 032/236] Contacts: Use new backends for CardDAV. --- lib/carddav/addressbook.php | 8 ++-- lib/carddav/backend.php | 77 ++++++++++++++++++++++++++----------- 2 files changed, 59 insertions(+), 26 deletions(-) diff --git a/lib/carddav/addressbook.php b/lib/carddav/addressbook.php index 1cc3717a..18ad0176 100644 --- a/lib/carddav/addressbook.php +++ b/lib/carddav/addressbook.php @@ -84,10 +84,10 @@ class AddressBook extends \Sabre_CardDAV_AddressBook { if ($sharedAddressbook && ($sharedAddressbook['permissions'] & \OCP\PERMISSION_READ)) { $readprincipal = 'principals/' . \OCP\USER::getUser(); } - if ($sharedAddressbook && ($sharedAddressbook['permissions'] & OCP\PERMISSION_UPDATE)) { + if ($sharedAddressbook && ($sharedAddressbook['permissions'] & \OCP\PERMISSION_UPDATE)) { $writeprincipal = 'principals/' . \OCP\USER::getUser(); } - if ($sharedAddressbook && ($sharedAddressbook['permissions'] & OCP\PERMISSION_DELETE)) { + if ($sharedAddressbook && ($sharedAddressbook['permissions'] & \OCP\PERMISSION_DELETE)) { $deleteprincipal = 'principals/' . \OCP\USER::getUser(); } } @@ -179,7 +179,9 @@ class AddressBook extends \Sabre_CardDAV_AddressBook { public function getChild($name) { $obj = $this->carddavBackend->getCard($this->addressBookInfo['id'],$name); - if (!$obj) throw new \Sabre_DAV_Exception_NotFound('Card not found'); + if (!$obj) { + throw new \Sabre_DAV_Exception_NotFound('Card not found'); + } return new Card($this->carddavBackend,$this->addressBookInfo,$obj); } diff --git a/lib/carddav/backend.php b/lib/carddav/backend.php index 666b6a3f..355c63d5 100644 --- a/lib/carddav/backend.php +++ b/lib/carddav/backend.php @@ -25,6 +25,12 @@ namespace OCA\Contacts\CardDAV; use OCA\Contacts; class Backend extends \Sabre_CardDAV_Backend_Abstract { + + public function __construct($backends) { + //\OCP\Util::writeLog('contacts', __METHOD__, \OCP\Util::DEBUG); + $this->backends = $backends; + } + /** * Returns the list of addressbooks for a specific user. * @@ -32,12 +38,17 @@ class Backend extends \Sabre_CardDAV_Backend_Abstract { * @return array */ public function getAddressBooksForUser($principaluri) { - $data = Contacts\Addressbook::allWherePrincipalURIIs($principaluri); + $userid = $this->userIDByPrincipal($principaluri); + $user_addressbooks = array(); + foreach($this->backends as $backend) { + $user_addressbooks = array_merge($user_addressbooks, $backend->getAddressBooksForUser($userid)); + } $addressbooks = array(); - foreach($data as $i) { + foreach($user_addressbooks as $i) { if($i['userid'] != \OCP\USER::getUser()) { $i['uri'] = $i['uri'] . '_shared_by_' . $i['userid']; + $i['displayname'] = $i['displayname'] . ' shared by ' . $i['userid']; } $addressbooks[] = array( 'id' => $i['id'], @@ -68,15 +79,16 @@ class Backend extends \Sabre_CardDAV_Backend_Abstract { public function updateAddressBook($addressbookid, array $mutations) { $name = null; $description = null; + $changes = array(); foreach($mutations as $property=>$newvalue) { switch($property) { case '{DAV:}displayname' : - $name = $newvalue; + $changes['name'] = $newvalue; break; case '{' . \Sabre_CardDAV_Plugin::NS_CARDDAV . '}addressbook-description' : - $description = $newvalue; + $changes['description'] = $newvalue; break; default : // If any unsupported values were being updated, we must @@ -85,7 +97,7 @@ class Backend extends \Sabre_CardDAV_Backend_Abstract { } } - Contacts\Addressbook::edit($addressbookid, $name, $description); + $this->backend->updateAddressBook($addressbookid, $changes); return true; @@ -101,18 +113,18 @@ class Backend extends \Sabre_CardDAV_Backend_Abstract { */ public function createAddressBook($principaluri, $url, array $properties) { - $displayname = null; - $description = null; + $properties = array(); + $userid = $this->userIDByPrincipal($principaluri); foreach($properties as $property=>$newvalue) { switch($property) { case '{DAV:}displayname' : - $displayname = $newvalue; + $properties['displayname'] = $newvalue; break; case '{' . \Sabre_CardDAV_Plugin::NS_CARDDAV . '}addressbook-description' : - $description = $newvalue; + $properties['description'] = $newvalue; break; default : throw new \Sabre_DAV_Exception_BadRequest('Unknown property: ' @@ -121,12 +133,9 @@ class Backend extends \Sabre_CardDAV_Backend_Abstract { } - Contacts\Addressbook::addFromDAVData( - $principaluri, - $url, - $name, - $description - ); + $properties['uri'] = $uri; + + $this->backend->createAddressBook($properties, $userid); } /** @@ -136,7 +145,7 @@ class Backend extends \Sabre_CardDAV_Backend_Abstract { * @return void */ public function deleteAddressBook($addressbookid) { - Contacts\Addressbook::delete($addressbookid); + $this->backend->deleteAddressBook($addressbookid); } /** @@ -146,17 +155,22 @@ class Backend extends \Sabre_CardDAV_Backend_Abstract { * @return array */ public function getCards($addressbookid) { - $data = Contacts\VCard::all($addressbookid); + $contacts = array(); + foreach($this->backends as $backend) { + if($backend->hasAddressBook($addressbookid)) { + $contacts = $backend->getContacts($addressbookid); + } + } $cards = array(); - foreach($data as $i) { + foreach($contacts as $contact) { //OCP\Util::writeLog('contacts', __METHOD__.', uri: ' . $i['uri'], OCP\Util::DEBUG); $cards[] = array( - 'id' => $i['id'], + 'id' => $contact['id'], //'carddata' => $i['carddata'], - 'size' => strlen($i['carddata']), - 'etag' => '"' . md5($i['carddata']) . '"', - 'uri' => $i['uri'], - 'lastmodified' => $i['lastmodified'] ); + 'size' => strlen($contact['carddata']), + 'etag' => '"' . md5($contact['carddata']) . '"', + 'uri' => $contact['uri'], + 'lastmodified' => $contact['lastmodified'] ); } return $cards; @@ -211,4 +225,21 @@ class Backend extends \Sabre_CardDAV_Backend_Abstract { public function deleteCard($addressbookid, $carduri) { return Contacts\VCard::deleteFromDAVData($addressbookid, $carduri); } + + /** + * @brief gets the userid from a principal path + * @return string + */ + public function userIDByPrincipal($principaluri) { + list(, $userid) = \Sabre_DAV_URLUtil::splitPath($principaluri); + return $userid; + } + + public function getBackendForAddressBook($addressbookid) { + foreach($this->backends as $backend) { + if($backend->hasAddressBook($addressbookid)) { + return $backend; + } + } + } } From edc10d08e9a082fee349341211cc1a71e9cb65c3 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Sun, 10 Mar 2013 12:51:21 +0100 Subject: [PATCH 033/236] Contacts: Added missing twitter scheme. --- lib/app.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/app.php b/lib/app.php index dbb4feb8..db684d0c 100644 --- a/lib/app.php +++ b/lib/app.php @@ -103,7 +103,7 @@ class App { 'twitter' => array( 'displayname' => (string)$l10n->t('Twitter'), 'xname' => 'X-TWITTER', - 'protocol' => null, + 'protocol' => 'twitter', ), 'googletalk' => array( 'displayname' => (string)$l10n->t('GoogleTalk'), From dffda6b0219f7453cb35a9371abba78a81fdea88 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Sun, 10 Mar 2013 12:53:01 +0100 Subject: [PATCH 034/236] Contacts: Add class paths --- appinfo/app.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/appinfo/app.php b/appinfo/app.php index 048a6020..e9aa4cdf 100644 --- a/appinfo/app.php +++ b/appinfo/app.php @@ -3,6 +3,10 @@ OC::$CLASSPATH['OCA\Contacts\App'] = 'contacts/lib/app.php'; OC::$CLASSPATH['OCA\Contacts\Addressbook'] = 'contacts/lib/addressbook.php'; OC::$CLASSPATH['OCA\Contacts\VCard'] = 'contacts/lib/vcard.php'; OC::$CLASSPATH['OCA\Contacts\Hooks'] = 'contacts/lib/hooks.php'; +OC::$CLASSPATH['OCA\Contacts\Backend\AbstractBackend'] = 'contacts/lib/backend/abstractbackend.php'; +OC::$CLASSPATH['OCA\Contacts\Backend\Database'] = 'contacts/lib/backend/database.php'; +OC::$CLASSPATH['OCA\Contacts\Backend\Shared'] = 'contacts/lib/backend/shared.php'; +OC::$CLASSPATH['OCA\Contacts\IPIMObject'] = 'contacts/lib/ipimobject.php'; OC::$CLASSPATH['OCA\Contacts\Share_Backend_Contact'] = 'contacts/lib/share/contact.php'; OC::$CLASSPATH['OCA\Contacts\Share_Backend_Addressbook'] = 'contacts/lib/share/addressbook.php'; OC::$CLASSPATH['OCA\Contacts\AddressbookProvider'] = 'contacts/lib/addressbookprovider.php'; @@ -16,10 +20,12 @@ OC::$CLASSPATH['OCA\Contacts\CardDAV\AddressBook'] = 'contacts/lib/carddav/addre OC::$CLASSPATH['OCA\Contacts\CardDAV\Card'] = 'contacts/lib/carddav/card.php'; OC::$CLASSPATH['OCA\Contacts\SearchProvider'] = 'contacts/lib/search.php'; -require_once __DIR__ . '/../lib/vobject/vcard.php'; +require_once __DIR__ . '/../lib/contact.php'; //require_once __DIR__ . '/../lib/vobject/stringproperty.php'; -Sabre\VObject\Component::$classMap['VCARD'] = 'OCA\Contacts\VObject\VCard'; +Sabre\VObject\Component::$classMap['VCARD'] = 'OCA\Contacts\Contact'; Sabre\VObject\Property::$classMap['FN'] = 'OCA\Contacts\VObject\StringProperty'; +Sabre\VObject\Property::$classMap['TITLE'] = 'OCA\Contacts\VObject\StringProperty'; +Sabre\VObject\Property::$classMap['ROLE'] = 'OCA\Contacts\VObject\StringProperty'; Sabre\VObject\Property::$classMap['NOTE'] = 'OCA\Contacts\VObject\StringProperty'; Sabre\VObject\Property::$classMap['NICKNAME'] = 'OCA\Contacts\VObject\StringProperty'; Sabre\VObject\Property::$classMap['EMAIL'] = 'OCA\Contacts\VObject\StringProperty'; @@ -35,7 +41,7 @@ OCP\Util::connectHook('OC_Calendar', 'getSources', 'OCA\Contacts\Hooks', 'getCal OCP\App::addNavigationEntry( array( 'id' => 'contacts_index', 'order' => 10, - 'href' => OCP\Util::linkTo( 'contacts', 'index.php' ), + 'href' => \OC_Helper::linkToRoute('contacts_index'), 'icon' => OCP\Util::imagePath( 'contacts', 'contacts.svg' ), 'name' => OC_L10N::get('contacts')->t('Contacts') )); From d286fb653bd84261f5a80225cf9138bb3d8391c4 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Sun, 10 Mar 2013 12:53:34 +0100 Subject: [PATCH 035/236] Contacts: Load backends in remote.php --- appinfo/remote.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/appinfo/remote.php b/appinfo/remote.php index cad18d3f..0e58ef5d 100644 --- a/appinfo/remote.php +++ b/appinfo/remote.php @@ -33,7 +33,11 @@ OC_App::loadApps($RUNTIME_APPTYPES); // Backends $authBackend = new OC_Connector_Sabre_Auth(); $principalBackend = new OC_Connector_Sabre_Principal(); -$carddavBackend = new OCA\Contacts\CardDAV\Backend(); + +$addressbookbackends = array(); +$addressbookbackends[] = new OCA\Contacts\Backend\Shared(); +$addressbookbackends[] = new OCA\Contacts\Backend\Database(); +$carddavBackend = new OCA\Contacts\CardDAV\Backend($addressbookbackends); $requestBackend = new OC_Connector_Sabre_Request(); // Root nodes From 052db9fd1dfef0887f45bda12a27373e54d3ce97 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Sun, 10 Mar 2013 12:54:05 +0100 Subject: [PATCH 036/236] Contacts: Removed stale files. --- ajax/editaddress.php | 41 ----------------------------------------- ajax/editname.php | 34 ---------------------------------- ajax/importdialog.php | 15 --------------- 3 files changed, 90 deletions(-) delete mode 100644 ajax/editaddress.php delete mode 100644 ajax/editname.php delete mode 100644 ajax/importdialog.php diff --git a/ajax/editaddress.php b/ajax/editaddress.php deleted file mode 100644 index de36b02c..00000000 --- a/ajax/editaddress.php +++ /dev/null @@ -1,41 +0,0 @@ - - * This file is licensed under the Affero General Public License version 3 or - * later. - * See the COPYING-README file. - */ - - -OCP\JSON::checkLoggedIn(); -OCP\JSON::checkAppEnabled('contacts'); - -$id = $_GET['id']; -$checksum = isset($_GET['checksum'])?$_GET['checksum']:''; -$vcard = OCA\Contacts\App::getContactVCard($id); -$adr_types = OCA\Contacts\App::getTypesOfProperty('ADR'); - -$tmpl = new OCP\Template("contacts", "part.edit_address_dialog"); -if($checksum) { - $line = OCA\Contacts\App::getPropertyLineByChecksum($vcard, $checksum); - $element = $vcard->children[$line]; - $adr = OCA\Contacts\VCard::structureProperty($element); - $types = array(); - if(isset($adr['parameters']['TYPE'])) { - if(is_array($adr['parameters']['TYPE'])) { - $types = array_map('htmlspecialchars', $adr['parameters']['TYPE']); - $types = array_map('strtoupper', $types); - } else { - $types = array(strtoupper(htmlspecialchars($adr['parameters']['TYPE']))); - } - } - $tmpl->assign('types', $types, false); - $adr = array_map('htmlspecialchars', $adr['value']); - $tmpl->assign('adr', $adr, false); -} - -$tmpl->assign('id', $id); -$tmpl->assign('adr_types', $adr_types); - -$page = $tmpl->fetchPage(); -OCP\JSON::success(array('data' => array('page'=>$page, 'checksum'=>$checksum))); diff --git a/ajax/editname.php b/ajax/editname.php deleted file mode 100644 index bac9717c..00000000 --- a/ajax/editname.php +++ /dev/null @@ -1,34 +0,0 @@ - - * This file is licensed under the Affero General Public License version 3 or - * later. - * See the COPYING-README file. - */ - - -OCP\JSON::checkLoggedIn(); -OCP\JSON::checkAppEnabled('contacts'); -require_once 'loghandler.php'; - -$tmpl = new OCP\Template("contacts", "part.edit_name_dialog"); - -$id = isset($_GET['id'])?$_GET['id']:''; - -if($id) { - $vcard = OCA\Contacts\App::getContactVCard($id); - $name = array('', '', '', '', ''); - if($vcard->__isset('N')) { - $property = $vcard->__get('N'); - if($property) { - $name = OCA\Contacts\VCard::structureProperty($property); - } - } - $name = array_map('htmlspecialchars', $name['value']); - $tmpl->assign('name', $name, false); - $tmpl->assign('id', $id, false); -} else { - bailOut(OCA\Contacts\App::$l10n->t('Contact ID is missing.')); -} -$page = $tmpl->fetchPage(); -OCP\JSON::success(array('data' => array('page'=>$page))); diff --git a/ajax/importdialog.php b/ajax/importdialog.php deleted file mode 100644 index f44539cf..00000000 --- a/ajax/importdialog.php +++ /dev/null @@ -1,15 +0,0 @@ - - * This file is licensed under the Affero General Public License version 3 or - * later. - * See the COPYING-README file. - */ - - -OCP\JSON::checkLoggedIn(); -OCP\App::checkAppEnabled('contacts'); -$tmpl = new OCP\Template('contacts', 'part.import'); -$tmpl->assign('path', $_POST['path']); -$tmpl->assign('filename', $_POST['filename']); -$tmpl->printpage(); From 29dc6cf0b0b230ba8d63017274dab42494fb8a11 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Mon, 11 Mar 2013 16:22:27 +0100 Subject: [PATCH 037/236] Contacts: Convinience method for getting backend in carddav. --- lib/carddav/backend.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/carddav/backend.php b/lib/carddav/backend.php index 355c63d5..b07a3e31 100644 --- a/lib/carddav/backend.php +++ b/lib/carddav/backend.php @@ -145,7 +145,8 @@ class Backend extends \Sabre_CardDAV_Backend_Abstract { * @return void */ public function deleteAddressBook($addressbookid) { - $this->backend->deleteAddressBook($addressbookid); + $backend = $this->getBackendForAddressBook($addressbookid); + $backend->deleteAddressBook($addressbookid); } /** @@ -156,11 +157,9 @@ class Backend extends \Sabre_CardDAV_Backend_Abstract { */ public function getCards($addressbookid) { $contacts = array(); - foreach($this->backends as $backend) { - if($backend->hasAddressBook($addressbookid)) { - $contacts = $backend->getContacts($addressbookid); - } - } + $backend = $this->getBackendForAddressBook($addressbookid); + $contacts = $backend->getContacts($addressbookid); + $cards = array(); foreach($contacts as $contact) { //OCP\Util::writeLog('contacts', __METHOD__.', uri: ' . $i['uri'], OCP\Util::DEBUG); @@ -184,6 +183,7 @@ class Backend extends \Sabre_CardDAV_Backend_Abstract { * @return array */ public function getCard($addressbookid, $carduri) { + $backend = $this->getBackendForAddressBook($addressbookid); return Contacts\VCard::findWhereDAVDataIs($addressbookid, $carduri); } From 55cef1600d84e9155ec8872900494ae546f7b62e Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Tue, 12 Mar 2013 00:27:06 +0100 Subject: [PATCH 038/236] Contacts: Wrong id used. --- js/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/app.js b/js/app.js index df4b6ab5..dc0a6b74 100644 --- a/js/app.js +++ b/js/app.js @@ -487,7 +487,7 @@ OC.Contacts = OC.Contacts || { $(document).bind('status.group.contactadded', function(e, result) { console.log('status.group.contactadded', result); - var contact = self.contacts.findById(self.currentid); + var contact = self.contacts.findById(result.contactid); if(contact === null) { return false; } From e414c4814880e29c1c781553cb8c27485e150725 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Tue, 12 Mar 2013 00:28:41 +0100 Subject: [PATCH 039/236] Contacts: Much improved d'n'd. --- js/app.js | 2 ++ js/contacts.js | 49 +++++++++++++++++++++++++++++++++++++++---------- js/groups.js | 24 ++++++++++++++++-------- 3 files changed, 57 insertions(+), 18 deletions(-) diff --git a/js/app.js b/js/app.js index dc0a6b74..1a099aca 100644 --- a/js/app.js +++ b/js/app.js @@ -171,6 +171,7 @@ OC.Contacts = OC.Contacts || { this.contacts = new OC.Contacts.ContactList( this.$contactList, this.$contactListItemTemplate, + this.$contactDragItemTemplate, this.$contactFullTemplate, this.detailTemplates ); @@ -218,6 +219,7 @@ OC.Contacts = OC.Contacts || { }); this.$groupListItemTemplate = $('#groupListItemTemplate'); this.$contactListItemTemplate = $('#contactListItemTemplate'); + this.$contactDragItemTemplate = $('#contactDragItemTemplate'); this.$contactFullTemplate = $('#contactFullTemplate'); this.$contactDetailsTemplate = $('#contactDetailsTemplate'); this.$rightContent = $('#rightcontent'); diff --git a/js/contacts.js b/js/contacts.js index 207ddcf4..e48b69a3 100644 --- a/js/contacts.js +++ b/js/contacts.js @@ -13,16 +13,16 @@ OC.Contacts = OC.Contacts || {}; * @param fulltemplate the jquery object used to render the entire contact * @param detailtemplates A map of jquery objects used to render the contact parts e.g. EMAIL, TEL etc. */ - var Contact = function(parent, id, access, data, listtemplate, fulltemplate, detailtemplates) { + var Contact = function(parent, id, access, data, listtemplate, dragtemplate, fulltemplate, detailtemplates) { //console.log('contact:', id, access); //parent, id, data, listtemplate, fulltemplate); this.parent = parent, this.id = id, this.access = access, this.data = data, + this.$dragTemplate = dragtemplate, this.$listTemplate = listtemplate, this.$fullTemplate = fulltemplate; this.detailTemplates = detailtemplates; - this.undoQueue = []; this.multi_properties = ['EMAIL', 'TEL', 'IMPP', 'ADR', 'URL']; }; @@ -658,6 +658,20 @@ OC.Contacts = OC.Contacts || {}; return ptype ? ptype.toUpperCase() : null; }; + /** + * Render an element item to be shown during drag. + * @return A jquery object + */ + Contact.prototype.renderDragItem = function() { + if(typeof this.$dragelem === 'undefined') { + this.$dragelem = this.$dragTemplate.octemplate({ + id: this.id, + name: this.getPreferredValue('FN', '') + }); + } + return this.$dragelem; + } + /** * Render the list item * @return A jquery object to be inserted in the DOM @@ -1353,13 +1367,20 @@ OC.Contacts = OC.Contacts || {}; } }; - var ContactList = function(contactlist, contactlistitemtemplate, contactfulltemplate, contactdetailtemplates) { + var ContactList = function( + contactlist, + contactlistitemtemplate, + contactdragitemtemplate, + contactfulltemplate, + contactdetailtemplates + ) { //console.log('ContactList', contactlist, contactlistitemtemplate, contactfulltemplate, contactdetailtemplates); var self = this; this.length = 0; this.contacts = {}; this.deletionQueue = []; this.$contactList = contactlist; + this.$contactDragItemTemplate = contactdragitemtemplate; this.$contactListItemTemplate = contactlistitemtemplate; this.$contactFullTemplate = contactfulltemplate; this.contactDetailTemplates = contactdetailtemplates; @@ -1605,12 +1626,15 @@ OC.Contacts = OC.Contacts || {}; * @param contact jQuery object. */ ContactList.prototype.insertContact = function($contact) { - $contact.draggable({ + $contact.find('td.name').draggable({ distance: 10, revert: 'invalid', //containment: '#content', - opacity: 0.8, helper: 'clone', - zIndex: 1000 + helper: function (e,ui) { + return $(this).clone().appendTo('body').css('zIndex', 5).show(); + }, + opacity: 0.8, + scope: 'contacts' }); var name = $contact.find('.nametext').text().toLowerCase(); var added = false; @@ -1639,6 +1663,7 @@ OC.Contacts = OC.Contacts || {}; {owner:OC.currentUser, permissions: 31}, null, this.$contactListItemTemplate, + this.$contactDragItemTemplate, this.$contactFullTemplate, this.contactDetailTemplates ); @@ -1748,18 +1773,22 @@ OC.Contacts = OC.Contacts || {}; self.addressbooks[parseInt(contact.aid)], contact.data, self.$contactListItemTemplate, + self.$contactDragItemTemplate, self.$contactFullTemplate, self.contactDetailTemplates ); self.length +=1; var $item = self.contacts[parseInt(contact.id)].renderListItem(); items.push($item.get(0)); - $item.draggable({ + $item.find('td.name').draggable({ + cursor: 'move', distance: 10, revert: 'invalid', - //containment: '#content', - opacity: 0.8, helper: 'clone', - zIndex: 1000 + helper: function (e,ui) { + return self.contacts[parseInt(contact.id)].renderDragItem().appendTo('body'); + }, + opacity: 1, + scope: 'contacts' }); if(items.length === 100) { self.$contactList.append(items); diff --git a/js/groups.js b/js/groups.js index 1da6ea43..bf4de4ba 100644 --- a/js/groups.js +++ b/js/groups.js @@ -361,12 +361,13 @@ OC.Contacts = OC.Contacts || {}; GroupList.prototype.contactDropped = function(event, ui) { var dragitem = ui.draggable, droptarget = $(this); console.log('dropped', dragitem); - if(dragitem.is('tr')) { - console.log('tr dropped', dragitem.data('id'), 'on', $(this).data('id')); + if(dragitem.is('.name')) { + var id = dragitem.parent().data('id'); + console.log('contact dropped', id, 'on', $(this).data('id')); if($(this).data('type') === 'fav') { - $(this).data('obj').setAsFavorite(dragitem.data('id'), true); + $(this).data('obj').setAsFavorite(id, true); } else { - $(this).data('obj').addTo(dragitem.data('id'), $(this).data('id')); + $(this).data('obj').addTo(id, $(this).data('id')); } } }; @@ -585,7 +586,7 @@ OC.Contacts = OC.Contacts || {}; GroupList.prototype.loadGroups = function(numcontacts, cb) { var self = this; - var acceptdrop = 'tr.contact'; + var acceptdrop = '.dragContact'; var $groupList = this.$groupList; var tmpl = this.$groupListItemTemplate; @@ -609,9 +610,12 @@ OC.Contacts = OC.Contacts || {}; $elem.data('contacts', contacts).find('.numcontacts').before(''); $elem.droppable({ drop: self.contactDropped, + over: function( event, ui ) { + console.log('over favorites', ui.draggable); + }, activeClass: 'ui-state-active', hoverClass: 'ui-state-hover', - accept: acceptdrop + scope: 'contacts' }); if(contacts.length === 0) { $elem.hide(); @@ -633,8 +637,12 @@ OC.Contacts = OC.Contacts || {}; $elem.data('id', category.id); $elem.droppable({ drop: self.contactDropped, - activeClass: 'ui-state-hover', - accept: acceptdrop + over: function( event, ui ) { + console.log('over group', ui.draggable); + }, + activeClass: 'ui-state-active', + hoverClass: 'ui-state-hover', + scope: 'contacts' }); $elem.appendTo($groupList); }); From 0cc8c59ab2afa6d50f24b8a7709fcd6f3d748547 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Tue, 12 Mar 2013 00:29:18 +0100 Subject: [PATCH 040/236] Contacts: Style drag element. --- css/contacts.css | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/css/contacts.css b/css/contacts.css index 9ee35b08..9cb1a0b1 100644 --- a/css/contacts.css +++ b/css/contacts.css @@ -231,7 +231,17 @@ .control > * { background: none repeat scroll 0 0 #F8F8F8; color: #555 !important; font-size: 100%; margin: 0px; } .ui-draggable { height: 3em; z-index: 1000; } -.ui-draggable-dragging { width: 70%; cursor: move; } +.ui-draggable-dragging { + cursor: move; + background-repeat: no-repeat !important; + background-color: #fff; + z-index: 5; + width: 200px; height: 30px; + text-indent: 30px; + font-weight: bold; + border: thin solid silver; border-radius: 3px; + padding: 3px; +} .ui-state-hover { border: 1px solid dashed; z-index: 1; } /* Properties */ From 86ed8652752bcf04348d3c47816ace2a73818e00 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Tue, 12 Mar 2013 00:30:05 +0100 Subject: [PATCH 041/236] Contacts: d'n'd item template --- templates/contacts.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/templates/contacts.php b/templates/contacts.php index 151c03c0..fe4839fc 100644 --- a/templates/contacts.php +++ b/templates/contacts.php @@ -142,6 +142,13 @@ + + + + From 583a52e9670a4d5f5464f74520254fd834c93204 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Fri, 5 Apr 2013 18:28:15 +0200 Subject: [PATCH 201/236] Contacts: Add requirements to routes. --- appinfo/routes.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/appinfo/routes.php b/appinfo/routes.php index efa7f41e..d7eab1b2 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -47,6 +47,7 @@ $this->create('contacts_address_books_for_user', 'addressbooks/{user}/') )); } ) + ->requirements(array('user')) ->defaults(array('user' => \OCP\User::getUser())); $this->create('contacts_address_book_collection', 'addressbook/{user}/{backend}/{addressbookid}/contacts') @@ -78,6 +79,7 @@ $this->create('contacts_address_book_collection', 'addressbook/{user}/{backend}/ )); } ) + ->requirements(array('user', 'backend', 'addressbookid')) ->defaults(array('user' => \OCP\User::getUser())); $this->create('contacts_address_book_add', 'addressbook/{user}/{backend}/add') @@ -96,6 +98,7 @@ $this->create('contacts_address_book_add', 'addressbook/{user}/{backend}/add') )); } ) + ->requirements(array('user', 'backend', 'addressbookid')) ->defaults(array('user' => \OCP\User::getUser())); $this->create('contacts_address_book_delete', 'addressbook/{user}/{backend}/{addressbookid}/delete') @@ -111,6 +114,7 @@ $this->create('contacts_address_book_delete', 'addressbook/{user}/{backend}/{add \OCP\JSON::success(); } ) + ->requirements(array('user', 'backend', 'addressbookid')) ->defaults(array('user' => \OCP\User::getUser())); $this->create('contacts_address_book_add_contact', 'addressbook/{user}/{backend}/{addressbookid}/contact/add') @@ -130,6 +134,7 @@ $this->create('contacts_address_book_add_contact', 'addressbook/{user}/{backend} )); } ) + ->requirements(array('user', 'backend', 'addressbookid')) ->defaults(array('user' => \OCP\User::getUser())); $this->create('contacts_address_book_delete_contact', 'addressbook/{user}/{backend}/{addressbookid}/contact/{contactid}/delete') @@ -146,6 +151,7 @@ $this->create('contacts_address_book_delete_contact', 'addressbook/{user}/{backe \OCP\JSON::success(); } ) + ->requirements(array('user', 'backend', 'addressbookid', 'contactid')) ->defaults(array('user' => \OCP\User::getUser())); $this->create('contacts_contact_photo', 'addressbook/{user}/{backend}/{addressbookid}/contact/{contactid}/photo') @@ -191,6 +197,7 @@ $this->create('contacts_contact_photo', 'addressbook/{user}/{backend}/{addressbo } } ) + ->requirements(array('user', 'backend', 'addressbook', 'contactid')) ->defaults(array('user' => \OCP\User::getUser())); $this->create('contacts_contact_delete_property', 'addressbook/{user}/{backend}/{addressbookid}/contact/{contactid}/property/delete') @@ -243,6 +250,7 @@ $this->create('contacts_contact_delete_property', 'addressbook/{user}/{backend}/ )); } ) + ->requirements(array('user', 'backend', 'addressbook', 'contactid')) ->defaults(array('user' => \OCP\User::getUser())); // Save a single property. @@ -296,6 +304,7 @@ $this->create('contacts_contact_save_property', 'addressbook/{user}/{backend}/{a \OCP\JSON::success(array('data' => $response)); } ) + ->requirements(array('user', 'backend', 'addressbook', 'contactid')) ->defaults(array('user' => \OCP\User::getUser())); // Save all properties. Used for merging contacts. @@ -325,6 +334,7 @@ $this->create('contacts_contact_save_all', 'addressbook/{user}/{backend}/{addres \OCP\JSON::success(array('data' => $data)); } ) + ->requirements(array('user', 'backend', 'addressbook', 'contactid')) ->defaults(array('user' => \OCP\User::getUser())); $this->create('contacts_categories_list', 'groups/{user}/') @@ -359,6 +369,7 @@ $this->create('contacts_categories_list', 'groups/{user}/') ); } ) + ->requirements(array('user')) ->defaults(array('user' => \OCP\User::getUser())); $this->create('contacts_categories_add', 'groups/{user}/add') @@ -383,6 +394,7 @@ $this->create('contacts_categories_add', 'groups/{user}/add') } } ) + ->requirements(array('user')) ->defaults(array('user' => \OCP\User::getUser())); $this->create('contacts_categories_delete', 'groups/{user}/delete') @@ -403,6 +415,7 @@ $this->create('contacts_categories_delete', 'groups/{user}/delete') \OCP\JSON::success(); } ) + ->requirements(array('user')) ->defaults(array('user' => \OCP\User::getUser())); $this->create('contacts_categories_addto', 'groups/{user}/addto/{categoryid}') @@ -432,6 +445,7 @@ $this->create('contacts_categories_addto', 'groups/{user}/addto/{categoryid}') \OCP\JSON::success(); } ) + ->requirements(array('user', 'categoryid')) ->defaults(array('user' => \OCP\User::getUser())); $this->create('contacts_categories_removefrom', 'groups/{user}/removefrom/{categoryid}') @@ -461,6 +475,7 @@ $this->create('contacts_categories_removefrom', 'groups/{user}/removefrom/{categ \OCP\JSON::success(); } ) + ->requirements(array('user', 'categoryid')) ->defaults(array('user' => \OCP\User::getUser())); $this->create('contacts_setpreference', 'preference/{user}/set') @@ -494,6 +509,7 @@ $this->create('contacts_setpreference', 'preference/{user}/set') } } ) + ->requirements(array('user')) ->defaults(array('user' => \OCP\User::getUser())); $this->create('contacts_index_properties', 'indexproperties/{user}/') @@ -507,4 +523,5 @@ $this->create('contacts_index_properties', 'indexproperties/{user}/') \OCP\JSON::success(array('isIndexed' => true)); } ) + ->requirements(array('user')) ->defaults(array('user' => \OCP\User::getUser())); From a4f8b0002c94bf24ec0ccca6a2c820d333d599a7 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Fri, 5 Apr 2013 19:05:47 +0200 Subject: [PATCH 202/236] ocdialog: Use spacing instead of separator under title. --- css/jquery.ocdialog.css | 2 ++ js/jquery.ocdialog.js | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/css/jquery.ocdialog.css b/css/jquery.ocdialog.css index c4e94b0b..b77ca6e0 100644 --- a/css/jquery.ocdialog.css +++ b/css/jquery.ocdialog.css @@ -8,6 +8,7 @@ .oc-dialog-title { font-weight: bold; font-size: 110%; + margin-bottom: 10px; } .oc-dialog-content { } @@ -18,6 +19,7 @@ position: relative; bottom: 0; display: block; + margin-top: 10px; } .oc-dialog-close { diff --git a/js/jquery.ocdialog.js b/js/jquery.ocdialog.js index feb14f5e..f52ffec7 100644 --- a/js/jquery.ocdialog.js +++ b/js/jquery.ocdialog.js @@ -81,7 +81,7 @@ switch(key) { case 'title': var $title = $('

    ' + this.options.title - + '


    '); + + ''); //
    '); if(this.$title) { this.$title.replaceWith($title); } else { From 32ba67735e20de52a2b7396fed858b9e99548eeb Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Fri, 5 Apr 2013 19:06:26 +0200 Subject: [PATCH 203/236] Contacts: Better wording for merge dialog. --- js/app.js | 4 ++-- templates/contacts.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/js/app.js b/js/app.js index 052cf9d9..a40ce41e 100644 --- a/js/app.js +++ b/js/app.js @@ -1401,7 +1401,7 @@ OC.Contacts = OC.Contacts || { height: 'auto', width: 'auto', buttons: [ { - text: t('contacts', 'OK'), + text: t('contacts', 'Merge contacts'), click:function() { // Do the merging, use $(this) to get dialog var contactid = $(this).find('input:radio:checked').val(); @@ -1610,7 +1610,7 @@ OC.Contacts = OC.Contacts || { }, cloudPhotoSelected:function(metadata, path) { var self = this; - console.log('cloudPhotoSelected, id', id); + console.log('cloudPhotoSelected', metadata); $.getJSON(OC.filePath('contacts', 'ajax', 'oc_photo.php'), {path: path, contact: metadata},function(jsondata) { if(jsondata.status == 'success') { diff --git a/templates/contacts.php b/templates/contacts.php index 1747e794..ced03cf5 100644 --- a/templates/contacts.php +++ b/templates/contacts.php @@ -144,7 +144,7 @@ From 1589866f8cecbf033a5ed9161ae66f8df0a0240d Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Sun, 7 Apr 2013 04:56:54 +0200 Subject: [PATCH 213/236] Contacts: Removed js compatibility functions now in core. --- js/app.js | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/js/app.js b/js/app.js index 472f1069..47ff72b6 100644 --- a/js/app.js +++ b/js/app.js @@ -6,30 +6,6 @@ Modernizr.load({ ] }); -if (typeof Object.create !== 'function') { - Object.create = function (o) { - function F() {} - F.prototype = o; - return new F(); - }; -} - - -if (typeof Object.keys !== 'function') { - Object.keys = function(o) { - if (o !== Object(o)) { - throw new TypeError('Object.keys called on a non-object'); - } - var k=[],p; - for (p in o) { - if (Object.prototype.hasOwnProperty.call(o,p)) { - k.push(p); - } - } - return k; - } -} - (function($) { $.QueryString = (function(a) { if (a == "") return {}; From f0e6472d732090e5562cddf241624925e5ee0096 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Sun, 7 Apr 2013 16:47:28 +0200 Subject: [PATCH 214/236] Contacts: Port fixes from master/stable5 --- js/app.js | 11 +++++++---- js/contacts.js | 4 ++++ lib/carddav/addressbook.php | 15 ++++++++++++++- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/js/app.js b/js/app.js index 47ff72b6..4db43e42 100644 --- a/js/app.js +++ b/js/app.js @@ -829,9 +829,12 @@ OC.Contacts = OC.Contacts || { * * @param object $list A jquery object of an unordered list * @param object book An object with the properties 'id', 'name' and 'permissions'. + * @param bool add Whether the address book list should be updated. */ - var appendAddressBook = function($list, book) { - self.contacts.setAddressbook(book); + var appendAddressBook = function($list, book, add) { + if(add) { + self.contacts.setAddressbook(book); + } var $li = self.$addressbookTmpl.octemplate({ id: book.id, permissions: book.permissions, @@ -911,7 +914,7 @@ OC.Contacts = OC.Contacts || { } else { var book = response.data; var $list = self.$settings.find('[data-id="addressbooks"]').next('ul'); - appendAddressBook($list, book); + appendAddressBook($list, book, true); } $addinput.removeClass('loading'); $addAddressbookPart.find('button input').prop('disabled', false); @@ -949,7 +952,7 @@ OC.Contacts = OC.Contacts || { $list.empty(); $.each(self.contacts.addressbooks, function(id, book) { - appendAddressBook($list, book); + appendAddressBook($list, book, false); }); if(typeof OC.Share !== 'undefined') { OC.Share.loadIcons('addressbook'); diff --git a/js/contacts.js b/js/contacts.js index 5259e33b..d91f3530 100644 --- a/js/contacts.js +++ b/js/contacts.js @@ -407,6 +407,10 @@ OC.Contacts = OC.Contacts || {}; var checksum = self.checksumFor(obj); var value = self.valueFor(obj); var parameters = self.parametersFor(obj); + if(parameters['TYPE'] && parameters['TYPE'].indexOf('PREF') !== -1) { + parameters['PREF'] = 1; + parameters['TYPE'].splice(parameters['TYPE'].indexOf('PREF', 1)); + } if(checksum && checksum !== 'new') { self.pushToUndo({ action:'save', diff --git a/lib/carddav/addressbook.php b/lib/carddav/addressbook.php index 49d6cd25..55735291 100644 --- a/lib/carddav/addressbook.php +++ b/lib/carddav/addressbook.php @@ -76,6 +76,19 @@ class AddressBook extends \Sabre_CardDAV_AddressBook { $deleteprincipal = $this->getOwner(); $uid = $this->carddavBackend->userIDByPrincipal($this->getOwner()); + $readWriteACL = array( + array( + 'privilege' => '{DAV:}read', + 'principal' => 'principals/' . OCP\User::getUser(), + 'protected' => true, + ), + array( + 'privilege' => '{DAV:}write', + 'principal' => 'principals/' . OCP\User::getUser(), + 'protected' => true, + ), + ); + if($uid != \OCP\USER::getUser()) { $sharedAddressbook = \OCP\Share::getItemSharedWithBySource('addressbook', $this->addressBookInfo['id']); if($sharedAddressbook) { @@ -83,7 +96,7 @@ class AddressBook extends \Sabre_CardDAV_AddressBook { && ($sharedAddressbook['permissions'] & OCP\PERMISSION_UPDATE) && ($sharedAddressbook['permissions'] & OCP\PERMISSION_DELETE) ) { - return parent::getACL(); + return $readWriteACL; } if ($sharedAddressbook['permissions'] & OCP\PERMISSION_CREATE) { $createprincipal = 'principals/' . OCP\USER::getUser(); From 499c69f15e29fc0a38f80f002e97ef7da59d7119 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Sun, 7 Apr 2013 21:42:27 +0200 Subject: [PATCH 215/236] Contacts: Also return uri from db backend --- lib/backend/database.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/backend/database.php b/lib/backend/database.php index f2329c39..4d949cf3 100644 --- a/lib/backend/database.php +++ b/lib/backend/database.php @@ -98,7 +98,7 @@ class Database extends AbstractBackend { } // Hmm, not found. Lets query the db. try { - $query = 'SELECT `id`, `displayname`, `description`, `userid` AS `owner`, `ctag` AS `lastmodified` FROM `' + $query = 'SELECT `id`, `displayname`, `description`, `userid` AS `owner`, `ctag` AS `lastmodified`, `uri` FROM `' . $this->addressBooksTableName . '` WHERE `id` = ?'; if(!isset(self::$preparedQueries['getaddressbook'])) { From ee2c52c31ed21370d5edf3147d083ee985ab7721 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Sun, 7 Apr 2013 21:49:53 +0200 Subject: [PATCH 216/236] Contacts: Format shared address book display name better --- lib/share/addressbook.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/share/addressbook.php b/lib/share/addressbook.php index 00f98bfc..48b32f60 100644 --- a/lib/share/addressbook.php +++ b/lib/share/addressbook.php @@ -89,7 +89,7 @@ class Share_Backend_Addressbook implements \OCP\Share_Backend_Collection { // . (int)$include, \OCP\Util::DEBUG); $addressbook = $this->backend->getAddressBook($item['item_source']); if ($addressbook) { - $addressbook['displayname'] = $item['item_target']; + $addressbook['displayname'] = $addressbook['displayname'] . ' (' . $addressbook['owner'] . ')'; $addressbook['permissions'] = $item['permissions']; $addressbooks[] = $addressbook; } From 9cfde339afa19bd03113a18e4534f14a95b8cc03 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Sun, 7 Apr 2013 21:50:34 +0200 Subject: [PATCH 217/236] ocdialog: Get title from element if not set in options --- js/jquery.ocdialog.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/js/jquery.ocdialog.js b/js/jquery.ocdialog.js index e9d08515..852d0559 100644 --- a/js/jquery.ocdialog.js +++ b/js/jquery.ocdialog.js @@ -16,6 +16,9 @@ height: this.element[0].style.height, }; + this.originalTitle = this.element.attr('title'); + this.options.title = this.options.title || this.originalTitle; + this.$dialog = $('
    ') .attr({ // Setting tabIndex makes the div focusable @@ -24,7 +27,7 @@ }) .insertBefore(this.element); this.$dialog.append(this.element.detach()); - this.element.addClass('oc-dialog-content').appendTo(this.$dialog); + this.element.removeAttr('title').addClass('oc-dialog-content').appendTo(this.$dialog); this.$dialog.css({ display: 'inline-block', @@ -159,6 +162,9 @@ width: this.$dialog.innerWidth() + 'px' }); }, + widget: function() { + return this.$dialog + }, close: function() { console.log('close'); var self = this; @@ -176,6 +182,10 @@ if(this.$buttonrow) { this.$buttonrow.remove() } + + if(this.originalTitle) { + this.element.attr('title', this.originalTitle); + } this.element.removeClass('oc-dialog-content') .css(this.originalCss).detach().insertBefore(this.$dialog); this.$dialog.remove(); From fb385150dc8328f6e014ee431d0363cc57fc4636 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Sun, 7 Apr 2013 22:03:42 +0200 Subject: [PATCH 218/236] Contacts: Fix namespace --- lib/carddav/addressbook.php | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/carddav/addressbook.php b/lib/carddav/addressbook.php index 55735291..ffe5f2cc 100644 --- a/lib/carddav/addressbook.php +++ b/lib/carddav/addressbook.php @@ -79,36 +79,36 @@ class AddressBook extends \Sabre_CardDAV_AddressBook { $readWriteACL = array( array( 'privilege' => '{DAV:}read', - 'principal' => 'principals/' . OCP\User::getUser(), + 'principal' => 'principals/' . \OCP\User::getUser(), 'protected' => true, ), array( 'privilege' => '{DAV:}write', - 'principal' => 'principals/' . OCP\User::getUser(), + 'principal' => 'principals/' . \OCP\User::getUser(), 'protected' => true, ), ); - if($uid != \OCP\USER::getUser()) { + if($uid != \OCP\User::getUser()) { $sharedAddressbook = \OCP\Share::getItemSharedWithBySource('addressbook', $this->addressBookInfo['id']); if($sharedAddressbook) { - if(($sharedAddressbook['permissions'] & OCP\PERMISSION_CREATE) - && ($sharedAddressbook['permissions'] & OCP\PERMISSION_UPDATE) - && ($sharedAddressbook['permissions'] & OCP\PERMISSION_DELETE) + if(($sharedAddressbook['permissions'] & \OCP\PERMISSION_CREATE) + && ($sharedAddressbook['permissions'] & \OCP\PERMISSION_UPDATE) + && ($sharedAddressbook['permissions'] & \OCP\PERMISSION_DELETE) ) { return $readWriteACL; } - if ($sharedAddressbook['permissions'] & OCP\PERMISSION_CREATE) { - $createprincipal = 'principals/' . OCP\USER::getUser(); + if ($sharedAddressbook['permissions'] & \OCP\PERMISSION_CREATE) { + $createprincipal = 'principals/' . \OCP\User::getUser(); } - if ($sharedAddressbook['permissions'] & OCP\PERMISSION_READ) { - $readprincipal = 'principals/' . OCP\USER::getUser(); + if ($sharedAddressbook['permissions'] & \OCP\PERMISSION_READ) { + $readprincipal = 'principals/' . \OCP\User::getUser(); } - if ($sharedAddressbook['permissions'] & OCP\PERMISSION_UPDATE) { - $writeprincipal = 'principals/' . OCP\USER::getUser(); + if ($sharedAddressbook['permissions'] & \OCP\PERMISSION_UPDATE) { + $writeprincipal = 'principals/' . \OCP\User::getUser(); } - if ($sharedAddressbook['permissions'] & OCP\PERMISSION_DELETE) { - $deleteprincipal = 'principals/' . OCP\USER::getUser(); + if ($sharedAddressbook['permissions'] & \OCP\PERMISSION_DELETE) { + $deleteprincipal = 'principals/' . \OCP\User::getUser(); } } } else { From f160332dcbb48f32b65d2568e79df909569037b9 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Sun, 7 Apr 2013 22:04:17 +0200 Subject: [PATCH 219/236] Better variable names --- lib/carddav/backend.php | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/carddav/backend.php b/lib/carddav/backend.php index 9ca526f5..e4443272 100644 --- a/lib/carddav/backend.php +++ b/lib/carddav/backend.php @@ -39,25 +39,25 @@ class Backend extends \Sabre_CardDAV_Backend_Abstract { */ public function getAddressBooksForUser($principaluri) { $userid = $this->userIDByPrincipal($principaluri); - $user_addressbooks = array(); + $addressBooks = array(); foreach($this->backends as $backend) { - $user_addressbooks = array_merge($user_addressbooks, $backend->getAddressBooksForUser($userid)); + $addressBooks = array_merge($addressBooks, $backend->getAddressBooksForUser($userid)); } $addressbooks = array(); - foreach($user_addressbooks as $i) { - if($i['owner'] != \OCP\USER::getUser()) { - $i['uri'] = $i['uri'] . '_shared_by_' . $i['owner']; - $i['displayname'] = $i['displayname'] . ' shared by ' . $i['owner']; + foreach($addressBooks as $addressBook) { + if($addressBook['owner'] != \OCP\USER::getUser()) { + $addressBook['uri'] = $addressBook['uri'] . '_shared_by_' . $addressBook['owner']; + $addressBook['displayname'] = $addressBook['displayname']; } $addressbooks[] = array( - 'id' => $i['id'], - 'uri' => $i['uri'], - 'principaluri' => 'principals/'.$i['owner'], - '{DAV:}displayname' => $i['displayname'], + 'id' => $addressBook['id'], + 'uri' => $addressBook['uri'], + 'principaluri' => 'principals/'.$addressBook['owner'], + '{DAV:}displayname' => $addressBook['displayname'], '{' . \Sabre_CardDAV_Plugin::NS_CARDDAV . '}addressbook-description' - => $i['description'], - '{http://calendarserver.org/ns/}getctag' => $i['lastmodified'], + => $addressBook['description'], + '{http://calendarserver.org/ns/}getctag' => $addressBook['lastmodified'], ); } From 2e23ba5bb13a273772193c92683c41653b5214c7 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Sun, 7 Apr 2013 22:27:43 +0200 Subject: [PATCH 220/236] Contacts: Use the return value from the address book backend --- lib/carddav/backend.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/carddav/backend.php b/lib/carddav/backend.php index e4443272..2a023449 100644 --- a/lib/carddav/backend.php +++ b/lib/carddav/backend.php @@ -97,9 +97,7 @@ class Backend extends \Sabre_CardDAV_Backend_Abstract { } } - $this->backend->updateAddressBook($addressbookid, $changes); - - return true; + return $this->backend->updateAddressBook($addressbookid, $changes); } From 221f1bbcdf034c9210f1c5df2071b1feaff01c21 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Sun, 7 Apr 2013 22:28:14 +0200 Subject: [PATCH 221/236] Contacts: Add docstring --- lib/carddav/backend.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/carddav/backend.php b/lib/carddav/backend.php index 2a023449..2f2cd843 100644 --- a/lib/carddav/backend.php +++ b/lib/carddav/backend.php @@ -238,6 +238,13 @@ class Backend extends \Sabre_CardDAV_Backend_Abstract { return $userid; } + /** + * Get the backend for an address book + * + * FIXME: Find a better way to get the right backend if possible. + * @param string $addressbookid + * @return \OCA\Contacts\Backend\AbstractBackend + */ public function getBackendForAddressBook($addressbookid) { foreach($this->backends as $backend) { if($backend->hasAddressBook($addressbookid)) { From ac0d12470bfa23b0d9d07abd5d003a3e8043ef20 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Sun, 7 Apr 2013 22:33:40 +0200 Subject: [PATCH 222/236] Contacts: Rename 'database' backend to 'local' --- appinfo/routes.php | 4 ++-- import.php | 2 +- js/app.js | 6 +++--- js/contacts.js | 2 +- js/storage.js | 8 ++++---- lib/app.php | 6 +++--- lib/backend/database.php | 2 +- lib/hooks.php | 4 ++-- 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/appinfo/routes.php b/appinfo/routes.php index d7eab1b2..dd9e85bc 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -88,7 +88,7 @@ $this->create('contacts_address_book_add', 'addressbook/{user}/{backend}/add') function($params) { session_write_close(); $app = new App($params['user']); - $backend = App::getBackend('database', $params['user']); + $backend = App::getBackend('local', $params['user']); $id = $backend->createAddressBook($_POST); if($id === false) { bailOut(App::$l10n->t('Error creating address book')); @@ -107,7 +107,7 @@ $this->create('contacts_address_book_delete', 'addressbook/{user}/{backend}/{add function($params) { session_write_close(); $app = new App($params['user']); - $backend = App::getBackend('database', $params['user']); + $backend = App::getBackend('local', $params['user']); if(!$backend->deleteAddressBook($params['addressbookid'])) { bailOut(App::$l10n->t('Error deleting address book')); } diff --git a/import.php b/import.php index 14cfcb14..6190686b 100644 --- a/import.php +++ b/import.php @@ -65,7 +65,7 @@ if(!$id) { } $app = new App(); -$addressBook = $app->getAddressBook('database', $id); +$addressBook = $app->getAddressBook('local', $id); //analyse the contacts file writeProgress('40'); diff --git a/js/app.js b/js/app.js index 4db43e42..127d6ce5 100644 --- a/js/app.js +++ b/js/app.js @@ -847,7 +847,7 @@ OC.Contacts = OC.Contacts || { var id = parseInt($(this).parents('li').first().data('id')); console.log('delete', id); var $li = $(this).parents('li').first(); - $.when(self.storage.deleteAddressBook('database',id)) + $.when(self.storage.deleteAddressBook('local',id)) .then(function(response) { if(!response.error) { self.contacts.unsetAddressbook(id); @@ -906,7 +906,7 @@ OC.Contacts = OC.Contacts || { $addinput.addClass('loading'); $addAddressbookPart.find('button input').prop('disabled', true); console.log('adding', name); - $.when(self.storage.addAddressBook('database', + $.when(self.storage.addAddressBook('local', {displayname: name, description: ''})).then(function(response) { if(response.error) { OC.notify({message: response.message}); @@ -966,7 +966,7 @@ OC.Contacts = OC.Contacts || { var addAddressbookCallback = function(select, name) { var id = false; - $.when(this.storage.addAddressBook('database', + $.when(this.storage.addAddressBook('local', {name: name, description: ''})).then(function(response) { if(response.error) { OC.notify({message: response.message}); diff --git a/js/contacts.js b/js/contacts.js index d91f3530..7a1a5521 100644 --- a/js/contacts.js +++ b/js/contacts.js @@ -1788,7 +1788,7 @@ OC.Contacts = OC.Contacts || {}; * { * contactid: '1234', * addressbookid: '4321', - * backend: 'database' + * backend: 'local' * } */ ContactList.prototype.delayedDelete = function(data) { diff --git a/js/storage.js b/js/storage.js index fc286bdf..cf844b8b 100644 --- a/js/storage.js +++ b/js/storage.js @@ -42,7 +42,7 @@ OC.Contacts = OC.Contacts || {}; * * @return An array containing object of address book metadata e.g.: * { - * backend:'database', + * backend:'local', * id:'1234' * permissions:31, * displayname:'Contacts' @@ -59,7 +59,7 @@ OC.Contacts = OC.Contacts || {}; /** * Add an address book to a specific backend * - * @param string backend - currently defaults to 'database' + * @param string backend - currently defaults to 'local' * @param object params An object {displayname:"My contacts", description:""} * @return An array containing contact data e.g.: * { @@ -77,7 +77,7 @@ OC.Contacts = OC.Contacts || {}; return this.requestRoute( 'contacts_address_book_add', 'POST', - {user: this.user, backend: 'database'}, + {user: this.user, backend: 'local'}, parameters ); } @@ -93,7 +93,7 @@ OC.Contacts = OC.Contacts || {}; return this.requestRoute( 'contacts_address_book_delete', 'POST', - {user: this.user, backend: 'database', addressbookid: addressbookid} + {user: this.user, backend: 'local', addressbookid: addressbookid} ); } diff --git a/lib/app.php b/lib/app.php index 4fb1fdff..66186456 100644 --- a/lib/app.php +++ b/lib/app.php @@ -42,7 +42,7 @@ class App { * @var array */ public static $backendClasses = array( - 'database' => 'OCA\Contacts\Backend\Database', + 'local' => 'OCA\Contacts\Backend\Database', 'shared' => 'OCA\Contacts\Backend\Shared', ); @@ -67,7 +67,7 @@ class App { * @return \Backend\AbstractBackend */ static public function getBackend($name, $user = null) { - $name = $name ? $name : 'database'; + $name = $name ? $name : 'local'; if (isset(self::$backendClasses[$name])) { return new self::$backendClasses[$name]($user); } else { @@ -89,7 +89,7 @@ class App { foreach(array_keys(self::$backendClasses) as $backendName) { $backend = self::getBackend($backendName, $this->user); $addressBooks = $backend->getAddressBooksForUser(); - if($backendName === 'database' && count($addressBooks) === 0) { + if($backendName === 'local' && count($addressBooks) === 0) { $id = $backend->createAddressBook(array('displayname' => 'Contacts')); if($id !== false) { $addressBook = $backend->getAddressBook($id); diff --git a/lib/backend/database.php b/lib/backend/database.php index 4d949cf3..00fab651 100644 --- a/lib/backend/database.php +++ b/lib/backend/database.php @@ -32,7 +32,7 @@ use Sabre\VObject\Reader; class Database extends AbstractBackend { - public $name = 'database'; + public $name = 'local'; static private $preparedQueries = array(); /** diff --git a/lib/hooks.php b/lib/hooks.php index 85f03b61..eb0e54f2 100644 --- a/lib/hooks.php +++ b/lib/hooks.php @@ -98,7 +98,7 @@ class Hooks{ $categories = new \OC_VCategories('contact'); $app = new App(); - $backend = $app->getBackend('database'); + $backend = $app->getBackend('local'); $addressBookInfos = $backend->getAddressBooksForUser(); foreach($addressBookInfos as $addressBookInfo) { @@ -126,7 +126,7 @@ class Hooks{ $limit = 10; $app = new App(); - $backend = $app->getBackend('database'); + $backend = $app->getBackend('local'); $addressBookInfos = $backend->getAddressBooksForUser(); foreach($addressBookInfos as $addressBookInfo) { From a308a3ed21917ad7c911ab389d6012c9523f610f Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Mon, 8 Apr 2013 01:27:35 +0200 Subject: [PATCH 223/236] Contacts: Use a backend name/id combo for CardDAV --- lib/backend/database.php | 9 +++-- lib/carddav/addressbook.php | 5 ++- lib/carddav/backend.php | 77 +++++++++++++++++++------------------ lib/carddav/card.php | 3 +- 4 files changed, 50 insertions(+), 44 deletions(-) diff --git a/lib/backend/database.php b/lib/backend/database.php index 00fab651..7c491c35 100644 --- a/lib/backend/database.php +++ b/lib/backend/database.php @@ -386,6 +386,7 @@ class Database extends AbstractBackend { * @return array */ public function getContacts($addressbookid, $limit = null, $offset = null, $omitdata = false) { + //\OCP\Util::writeLog('contacts', __METHOD__.' addressbookid: ' . $addressbookid, \OCP\Util::DEBUG); $cards = array(); try { $qfields = $omitdata ? '`id`, `fullname` AS `displayname`' : '*'; @@ -413,7 +414,7 @@ class Database extends AbstractBackend { } /** - * Returns a specfic contact. + * Returns a specific contact. * * The $id for Database and Shared backends can be an array containing * either 'id' or 'uri' to be able to play seamlessly with the @@ -429,10 +430,8 @@ class Database extends AbstractBackend { * @return array|false */ public function getContact($addressbookid, $id, $noCollection = false) { - //\OCP\Util::writeLog('contacts', __METHOD__.' identifier: ' - // . print_r($id, true), \OCP\Util::DEBUG); + //\OCP\Util::writeLog('contacts', __METHOD__.' identifier: ' . $addressbookid . ' ' . $id['uri'], \OCP\Util::DEBUG); - $ids = array($id); $where_query = '`id` = ?'; if(is_array($id)) { $where_query = ''; @@ -445,8 +444,10 @@ class Database extends AbstractBackend { throw new \Exception( __METHOD__ . ' If second argument is an array, either \'id\' or \'uri\' has to be set.' ); + return false; } } + $ids = array($id); if(!$noCollection) { $where_query .= ' AND `addressbookid` = ?'; diff --git a/lib/carddav/addressbook.php b/lib/carddav/addressbook.php index ffe5f2cc..0d6f4abb 100644 --- a/lib/carddav/addressbook.php +++ b/lib/carddav/addressbook.php @@ -89,8 +89,9 @@ class AddressBook extends \Sabre_CardDAV_AddressBook { ), ); - if($uid != \OCP\User::getUser()) { - $sharedAddressbook = \OCP\Share::getItemSharedWithBySource('addressbook', $this->addressBookInfo['id']); + if($uid !== \OCP\User::getUser()) { + list($backendName, $id) = explode('::', $this->addressBookInfo['id']); + $sharedAddressbook = \OCP\Share::getItemSharedWithBySource('addressbook', $id); if($sharedAddressbook) { if(($sharedAddressbook['permissions'] & \OCP\PERMISSION_CREATE) && ($sharedAddressbook['permissions'] & \OCP\PERMISSION_UPDATE) diff --git a/lib/carddav/backend.php b/lib/carddav/backend.php index 2f2cd843..7ca74eef 100644 --- a/lib/carddav/backend.php +++ b/lib/carddav/backend.php @@ -39,29 +39,28 @@ class Backend extends \Sabre_CardDAV_Backend_Abstract { */ public function getAddressBooksForUser($principaluri) { $userid = $this->userIDByPrincipal($principaluri); - $addressBooks = array(); + $userAddressBooks = array(); foreach($this->backends as $backend) { - $addressBooks = array_merge($addressBooks, $backend->getAddressBooksForUser($userid)); - } - $addressbooks = array(); + $addressBooks = $backend->getAddressBooksForUser($userid); - foreach($addressBooks as $addressBook) { - if($addressBook['owner'] != \OCP\USER::getUser()) { - $addressBook['uri'] = $addressBook['uri'] . '_shared_by_' . $addressBook['owner']; - $addressBook['displayname'] = $addressBook['displayname']; + foreach($addressBooks as $addressBook) { + if($addressBook['owner'] != \OCP\USER::getUser()) { + $addressBook['uri'] = $addressBook['uri'] . '_shared_by_' . $addressBook['owner']; + $addressBook['displayname'] = $addressBook['displayname']; + } + $userAddressbooks[] = array( + 'id' => $backend->name . '::' . $addressBook['id'], + 'uri' => $addressBook['uri'], + 'principaluri' => 'principals/'.$addressBook['owner'], + '{DAV:}displayname' => $addressBook['displayname'], + '{' . \Sabre_CardDAV_Plugin::NS_CARDDAV . '}addressbook-description' + => $addressBook['description'], + '{http://calendarserver.org/ns/}getctag' => $addressBook['lastmodified'], + ); } - $addressbooks[] = array( - 'id' => $addressBook['id'], - 'uri' => $addressBook['uri'], - 'principaluri' => 'principals/'.$addressBook['owner'], - '{DAV:}displayname' => $addressBook['displayname'], - '{' . \Sabre_CardDAV_Plugin::NS_CARDDAV . '}addressbook-description' - => $addressBook['description'], - '{http://calendarserver.org/ns/}getctag' => $addressBook['lastmodified'], - ); } - return $addressbooks; + return $userAddressbooks; } @@ -97,7 +96,8 @@ class Backend extends \Sabre_CardDAV_Backend_Abstract { } } - return $this->backend->updateAddressBook($addressbookid, $changes); + list($id, $backend) = $this->getBackendForAddressBook($addressbookid); + return $backend->updateAddressBook($id, $changes); } @@ -133,7 +133,8 @@ class Backend extends \Sabre_CardDAV_Backend_Abstract { $properties['uri'] = $uri; - $this->backend->createAddressBook($properties, $userid); + list(,$backend) = $this->getBackendForAddressBook($addressbookid); + $backend->createAddressBook($properties, $userid); } /** @@ -143,8 +144,8 @@ class Backend extends \Sabre_CardDAV_Backend_Abstract { * @return void */ public function deleteAddressBook($addressbookid) { - $backend = $this->getBackendForAddressBook($addressbookid); - $backend->deleteAddressBook($addressbookid); + list($id, $backend) = $this->getBackendForAddressBook($addressbookid); + $backend->deleteAddressBook($id); } /** @@ -155,8 +156,8 @@ class Backend extends \Sabre_CardDAV_Backend_Abstract { */ public function getCards($addressbookid) { $contacts = array(); - $backend = $this->getBackendForAddressBook($addressbookid); - $contacts = $backend->getContacts($addressbookid); + list($id, $backend) = $this->getBackendForAddressBook($addressbookid); + $contacts = $backend->getContacts($id); $cards = array(); foreach($contacts as $contact) { @@ -181,8 +182,9 @@ class Backend extends \Sabre_CardDAV_Backend_Abstract { * @return array */ public function getCard($addressbookid, $carduri) { - $backend = $this->getBackendForAddressBook($addressbookid); - $contact = $backend->getContact($addressbookid, array('uri' => $carduri)); + \OCP\Util::writeLog('contacts', __METHOD__.' identifier: ' . $carduri . ' ' . print_r($addressbookid, true), \OCP\Util::DEBUG); + list($id, $backend) = $this->getBackendForAddressBook($addressbookid); + $contact = $backend->getContact($id, array('uri' => $carduri)); return ($contact ? $contact : false); } @@ -200,8 +202,8 @@ class Backend extends \Sabre_CardDAV_Backend_Abstract { * @return string|null */ public function createCard($addressbookid, $carduri, $carddata) { - $backend = $this->getBackendForAddressBook($addressbookid); - $backend->createContact($addressbookid, $carddata, $carduri); + list($id, $backend) = $this->getBackendForAddressBook($addressbookid); + $backend->createContact($id, $carddata, $carduri); } /** @@ -213,8 +215,8 @@ class Backend extends \Sabre_CardDAV_Backend_Abstract { * @return null */ public function updateCard($addressbookid, $carduri, $carddata) { - $backend = $this->getBackendForAddressBook($addressbookid); - $backend->updateContact($addressbookid, array('uri' => $carduri,), $carddata); + list($id, $backend) = $this->getBackendForAddressBook($addressbookid); + $backend->updateContact($id, array('uri' => $carduri,), $carddata); } /** @@ -225,8 +227,8 @@ class Backend extends \Sabre_CardDAV_Backend_Abstract { * @return bool */ public function deleteCard($addressbookid, $carduri) { - $backend = $this->getBackendForAddressBook($addressbookid); - return $backend->deleteContact($addressbookid); + list($id, $backend) = $this->getBackendForAddressBook($addressbookid); + return $backend->deleteContact($id); } /** @@ -241,15 +243,16 @@ class Backend extends \Sabre_CardDAV_Backend_Abstract { /** * Get the backend for an address book * - * FIXME: Find a better way to get the right backend if possible. - * @param string $addressbookid - * @return \OCA\Contacts\Backend\AbstractBackend + * @param mixed $addressbookid + * @return array(string, \OCA\Contacts\Backend\AbstractBackend) */ public function getBackendForAddressBook($addressbookid) { + list($backendName, $id) = explode('::', $addressbookid); foreach($this->backends as $backend) { - if($backend->hasAddressBook($addressbookid)) { - return $backend; + if($backend->name === $backendName && $backend->hasAddressBook($id)) { + return array($id, $backend); } } + throw new \Sabre_DAV_Exception_NotFound('Backend not found: ' . $addressbookid); } } diff --git a/lib/carddav/card.php b/lib/carddav/card.php index b7eefc4d..7d27bd34 100644 --- a/lib/carddav/card.php +++ b/lib/carddav/card.php @@ -70,7 +70,8 @@ class Card extends \Sabre_CardDAV_Card { $uid = $this->carddavBackend->userIDByPrincipal($this->getOwner()); if($uid != \OCP\USER::getUser()) { - $sharedAddressbook = \OCP\Share::getItemSharedWithBySource('addressbook', $this->addressBookInfo['id']); + list($backendName, $id) = explode('::', $this->addressBookInfo['id']); + $sharedAddressbook = \OCP\Share::getItemSharedWithBySource('addressbook', $id); if ($sharedAddressbook && ($sharedAddressbook['permissions'] & \OCP\PERMISSION_READ)) { $readprincipal = 'principals/' . \OCP\USER::getUser(); } From 3c1ee05d5379213c008612f2867fe5564473cb2d Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Mon, 8 Apr 2013 22:35:28 +0200 Subject: [PATCH 224/236] Contacts: Improved image upload --- ajax/currentphoto.php | 43 ++++++++++++++++++++++++++++++------------- ajax/savecrop.php | 31 ++++++++++++++++++------------- ajax/uploadphoto.php | 42 ++++++++++++++++++++++++++++-------------- appinfo/classpath.php | 28 ++++++++++++++++++++++++++++ js/app.js | 34 ++++++++++++++++++++++------------ 5 files changed, 126 insertions(+), 52 deletions(-) create mode 100644 appinfo/classpath.php diff --git a/ajax/currentphoto.php b/ajax/currentphoto.php index b6b10770..605ec0d0 100644 --- a/ajax/currentphoto.php +++ b/ajax/currentphoto.php @@ -20,22 +20,39 @@ * */ +namespace OCA\Contacts; + // Firefox and Konqueror tries to download application/json for me. --Arthur -OCP\JSON::setContentTypeHeader('text/plain'); -OCP\JSON::checkLoggedIn(); -OCP\JSON::checkAppEnabled('contacts'); +\OCP\JSON::setContentTypeHeader('text/plain'); +\OCP\JSON::checkLoggedIn(); +\OCP\JSON::checkAppEnabled('contacts'); require_once 'loghandler.php'; -if (!isset($_GET['id'])) { - bailOut(OCA\Contacts\App::$l10n->t('No contact ID was submitted.')); +$contactid = isset($_GET['contactid']) ? $_GET['contactid'] : ''; +$addressbookid = isset($_GET['addressbookid']) ? $_GET['addressbookid'] : ''; +$backend = isset($_GET['backend']) ? $_GET['backend'] : ''; + +if(!$contactid) { + bailOut('Missing contact id.'); } -$contact = OCA\Contacts\App::getContactVCard($_GET['id']); +if(!$addressbookid) { + bailOut('Missing address book id.'); +} + +$app = new App(); +// FIXME: Get backend and addressbookid +$contact = $app->getContact($backend, $addressbookid, $contactid); +if(!$contact) { + \OC_Cache::remove($tmpkey); + bailOut(App::$l10n + ->t('Error getting contact object.')); +} // invalid vcard -if( is_null($contact)) { - bailOut(OCA\Contacts\App::$l10n->t('Error reading contact photo.')); +if(!$contact) { + bailOut(App::$l10n->t('Error reading contact photo.')); } else { - $image = new OC_Image(); + $image = new \OC_Image(); if(!isset($contact->PHOTO) || !$image->loadFromBase64((string)$contact->PHOTO)) { if(isset($contact->LOGO)) { $image->loadFromBase64((string)$contact->LOGO); @@ -43,13 +60,13 @@ if( is_null($contact)) { } if($image->valid()) { $tmpkey = 'contact-photo-'.$contact->UID; - if(OC_Cache::set($tmpkey, $image->data(), 600)) { - OCP\JSON::success(array('data' => array('id'=>$_GET['id'], 'tmp'=>$tmpkey))); + if(\OC_Cache::set($tmpkey, $image->data(), 600)) { + \OCP\JSON::success(array('data' => array('id'=>$_GET['id'], 'tmp'=>$tmpkey))); exit(); } else { - bailOut(OCA\Contacts\App::$l10n->t('Error saving temporary file.')); + bailOut(App::$l10n->t('Error saving temporary file.')); } } else { - bailOut(OCA\Contacts\App::$l10n->t('The loading photo is not valid.')); + bailOut(App::$l10n->t('The loading photo is not valid.')); } } diff --git a/ajax/savecrop.php b/ajax/savecrop.php index a38a41b1..68350d4b 100644 --- a/ajax/savecrop.php +++ b/ajax/savecrop.php @@ -47,10 +47,14 @@ if($tmpkey == '') { bailOut('Missing key to temporary file.'); } -if($id == '') { +if($contactid == '') { bailOut('Missing contact id.'); } +if($addressbookid == '') { + bailOut('Missing address book id.'); +} + \OCP\Util::writeLog('contacts', 'savecrop.php: key: '.$tmpkey, \OCP\Util::DEBUG); $app = new App(); @@ -75,6 +79,12 @@ if($data) { if(($image->width() <= 200 && $image->height() <= 200) || $image->resize(200)) { + // 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 + $type = strval($contact->VERSION) === '4.0' + ? $image->mimeType() + : strtoupper(array_pop(explode('/', $image->mimeType()))); if(isset($contact->PHOTO)) { \OCP\Util::writeLog('contacts', 'savecrop.php: PHOTO property exists.', @@ -86,23 +96,20 @@ if($data) { ->t('Error getting PHOTO property.')); } $property->setValue(strval($image)); + $property->parameters = []; + /*$property->ENCODING = 'b'; + $property->TYPE = $type;*/ $property->parameters[] - = new Sabre\VObject\Parameter('ENCODING', 'b'); + = new \Sabre\VObject\Parameter('ENCODING', 'b'); $property->parameters[] - = new Sabre\VObject\Parameter('TYPE', $image->mimeType()); + = new \Sabre\VObject\Parameter('TYPE', $image->mimeType()); $contact->PHOTO = $property; } else { \OCP\Util::writeLog('contacts', 'savecrop.php: files: Adding PHOTO property.', \OCP\Util::DEBUG); - // 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 - $type = strval($contact->VERSION) === '4.0' - ? $image->mimeType() - : strtoupper(array_pop(explode('/', $image->mimeType()))); $contact->add('PHOTO', - $image->__toString(), array('ENCODING' => 'b', + strval($image), array('ENCODING' => 'b', 'TYPE' => $type)); } if(!$contact->save()) { @@ -111,10 +118,8 @@ if($data) { $thumbnail = $contact->cacheThumbnail($image); \OCP\JSON::success(array( 'data' => array( - 'width' => $image->width(), - 'height' => $image->height(), + 'id' => $contactid, 'thumbnail' => $thumbnail, - 'lastmodified' => App::lastModified($contact)->format('U') ) )); } else { diff --git a/ajax/uploadphoto.php b/ajax/uploadphoto.php index a26560ba..ab76fa04 100644 --- a/ajax/uploadphoto.php +++ b/ajax/uploadphoto.php @@ -29,13 +29,22 @@ OCP\JSON::callCheck(); OCP\JSON::setContentTypeHeader('text/plain; charset=utf-8'); require_once 'loghandler.php'; $l10n = OCA\Contacts\App::$l10n; + +$contactid = isset($_POST['contactid']) ? $_POST['contactid'] : ''; +$addressbookid = isset($_POST['addressbookid']) ? $_POST['addressbookid'] : ''; +$backend = isset($_POST['backend']) ? $_POST['backend'] : ''; + +if($contactid == '') { + bailOut('Missing contact id.'); +} + +if($addressbookid == '') { + bailOut('Missing address book id.'); +} + // If it is a Drag'n'Drop transfer it's handled here. $fn = (isset($_SERVER['HTTP_X_FILE_NAME']) ? $_SERVER['HTTP_X_FILE_NAME'] : false); if ($fn) { - if (!isset($_GET['id'])) { - bailOut($l10n->t('No contact ID was submitted.')); - } - $id = $_GET['id']; $tmpkey = 'contact-photo-'.md5($fn); $data = file_get_contents('php://input'); $image = new OC_Image(); @@ -50,10 +59,14 @@ if ($fn) { if(OC_Cache::set($tmpkey, $image->data(), 600)) { OCP\JSON::success(array( 'data' => array( - 'mime'=>$_SERVER['CONTENT_TYPE'], - 'name'=>$fn, - 'id'=>$id, - 'tmp'=>$tmpkey))); + 'mime'=> $_SERVER['CONTENT_TYPE'], + 'name'=> $fn, + 'contactid'=> $id, + 'addressbookid'=> addressbookid, + 'backend'=> $backend, + 'tmp'=>$tmpkey + )) + ); exit(); } else { bailOut($l10n->t('Couldn\'t save temporary image: ').$tmpkey); @@ -63,10 +76,6 @@ if ($fn) { } } -// Uploads from file dialog are handled here. -if (!isset($_POST['id'])) { - bailOut($l10n->t('No contact ID was submitted.')); -} if (!isset($_FILES['imagefile'])) { bailOut($l10n->t('No file was uploaded. Unknown error')); } @@ -101,9 +110,14 @@ if(file_exists($file['tmp_name'])) { 'mime'=>$file['type'], 'size'=>$file['size'], 'name'=>$file['name'], - 'id'=>$_POST['id'], 'tmp'=>$tmpkey, - ))); + ), + 'metadata' => array( + 'contactid'=> $contactid, + 'addressbookid'=> $addressbookid, + 'backend'=> $backend, + ), + )); exit(); } else { bailOut($l10n->t('Couldn\'t save temporary image: ').$tmpkey); diff --git a/appinfo/classpath.php b/appinfo/classpath.php new file mode 100644 index 00000000..3dee4b79 --- /dev/null +++ b/appinfo/classpath.php @@ -0,0 +1,28 @@ + $('#max_upload').val()){ OC.notify({ @@ -1602,7 +1608,10 @@ OC.Contacts = OC.Contacts || { var response=jQuery.parseJSON(target.contents().text()); if(response != undefined && response.status == 'success') { console.log('response', response); - self.editPhoto(self.currentid, response.data.tmp); + self.editPhoto( + response.metadata, + response.data.tmp + ); //alert('File: ' + file.tmp + ' ' + file.name + ' ' + file.mime); } else { OC.notify({message:response.data.message}); @@ -1619,7 +1628,7 @@ OC.Contacts = OC.Contacts || { if(jsondata.status == 'success') { //alert(jsondata.data.page); self.editPhoto(metadata, jsondata.data.tmp); - $('#edit_photo_dialog_img').html(jsondata.data.page); + //$('#edit_photo_dialog_img').html(jsondata.data.page); } else{ OC.notify({message: jsondata.data.message}); @@ -1666,7 +1675,7 @@ OC.Contacts = OC.Contacts || { var $dlg = this.$cropBoxTmpl.octemplate( { backend: metadata.backend, - addressbookid: metadata.parent, + addressbookid: metadata.addressbookid, contactid: metadata.contactid, tmpkey: tmpkey }); @@ -1709,25 +1718,26 @@ OC.Contacts = OC.Contacts || { }); }).error(function () { OC.notify({message:t('contacts','Error loading profile picture.')}); - }).attr('src', OC.linkTo('contacts', 'tmpphoto.php')+'?tmpkey='+tmpkey); + }).attr('src', OC.linkTo('contacts', 'tmpphoto.php')+'?tmpkey='+tmpkey+'&refresh='+Math.random()); }, savePhoto:function($dlg) { var form = $dlg.find('#cropform'); q = form.serialize(); console.log('savePhoto', q); $.post(OC.filePath('contacts', 'ajax', 'savecrop.php'), q, function(response) { - var jsondata = $.parseJSON(response); - console.log('savePhoto, jsondata', typeof jsondata); - if(jsondata && jsondata.status === 'success') { + //var jsondata = $.parseJSON(response); + console.log('savePhoto, response', typeof response); + if(response && response.status === 'success') { // load cropped photo. $(document).trigger('status.contact.photoupdated', { - id: jsondata.data.id + id: response.data.id, + thumbnail: response.data.thumbnail }); } else { - if(!jsondata) { + if(!response) { OC.notify({message:t('contacts', 'Network or server error. Please inform administrator.')}); } else { - OC.notify({message: jsondata.data.message}); + OC.notify({message: response.data.message}); } } }); From 7b977bce45cb675eba937aaf78e84efc308687ca Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Mon, 8 Apr 2013 22:37:37 +0200 Subject: [PATCH 225/236] Contacts: Removed some console.log() --- js/app.js | 16 +++--------- js/contacts.js | 68 ++++++++++++++++++++++++++------------------------ js/storage.js | 4 +-- 3 files changed, 42 insertions(+), 46 deletions(-) diff --git a/js/app.js b/js/app.js index 9ef6eb43..260e4a6f 100644 --- a/js/app.js +++ b/js/app.js @@ -280,7 +280,7 @@ OC.Contacts = OC.Contacts || { }); this.hashChange = function() { - console.log('hashchange', window.location.hash) + //console.log('hashchange', window.location.hash) var id = parseInt(window.location.hash.substr(1)); if(id && id !== self.currentid) { self.openContact(id); @@ -452,7 +452,6 @@ OC.Contacts = OC.Contacts || { }); return; } - console.log('Found merger'); $.each(data.mergees, function(idx, id) { var contact = self.contacts.findById(id); if(!contact) { @@ -460,14 +459,12 @@ OC.Contacts = OC.Contacts || { } mergees.push(contact); }); - console.log('Found mergees'); if(!merger.merge(mergees)) { $(document).trigger('status.contact.error', { message: t('contacts', 'Merge failed.') }); return; } - console.log('Ready to save'); merger.saveAll(function(response) { if(response.error) { $(document).trigger('status.contact.error', { @@ -521,9 +518,8 @@ OC.Contacts = OC.Contacts || { } $.each(result.contacts, function(idx, contactid) { var contact = self.contacts.findById(contactid); - console.log('contactid', contactid, contact); - self.contacts.findById(contactid).removeFromGroup(result.groupname); + contact.removeFromGroup(result.groupname); }); }); @@ -813,14 +809,12 @@ OC.Contacts = OC.Contacts || { if(event.ctrlKey || event.metaKey) { event.stopPropagation(); event.preventDefault(); - console.log('select', event); self.dontScroll = true; self.contacts.select($(this).data('id'), true); return; } if($(event.target).is('a.mailto')) { var mailto = 'mailto:' + $.trim($(this).find('.email').text()); - console.log('mailto', mailto); try { window.location.href=mailto; } catch(e) { @@ -912,7 +906,7 @@ OC.Contacts = OC.Contacts || { var name = $addinput.val().trim(); $addinput.addClass('loading'); $addAddressbookPart.find('button input').prop('disabled', true); - console.log('adding', name); + //console.log('adding', name); $.when(self.storage.addAddressBook('local', {displayname: name, description: ''})).then(function(response) { if(response.error) { @@ -948,10 +942,9 @@ OC.Contacts = OC.Contacts || { if($(this).next('ul').is(':visible')) { return; } - console.log('settings'); + //console.log('settings'); var $list = $(this).next('ul'); if($(this).data('id') === 'addressbooks') { - console.log('addressbooks'); if(!self.$addressbookTmpl) { self.$addressbookTmpl = $('#addressbookTemplate'); @@ -967,7 +960,6 @@ OC.Contacts = OC.Contacts || { $list.find('a.action.share').css('display', 'none'); } } else if($(this).data('id') === 'import') { - console.log('import'); $('.import-upload').show(); $('.import-select').hide(); diff --git a/js/contacts.js b/js/contacts.js index 7a1a5521..381dd189 100644 --- a/js/contacts.js +++ b/js/contacts.js @@ -101,9 +101,7 @@ OC.Contacts = OC.Contacts || {}; throw new Error('BadArgument: Why should I merge with myself?'); } $.each(mergee.data, function(name, properties) { - console.log('property', name, properties); if(self.multi_properties.indexOf(name) === -1) { - console.log('non-multi', name, properties); if(self.data[name] && self.data[name].length > 0) { // If the property exists don't touch it. return true; // continue @@ -163,7 +161,7 @@ OC.Contacts = OC.Contacts || {}; newvalue: params.newvalue, oldvalue: params.oldvalue }); - console.log('undoQueue', this.undoQueue); + //console.log('undoQueue', this.undoQueue); } Contact.prototype.addProperty = function($option, name) { @@ -304,7 +302,7 @@ OC.Contacts = OC.Contacts || {}; }) .fail(function(jqxhr, textStatus, error) { var err = textStatus + ', ' + error; - console.log( "Request Failed: " + err); + console.warn( "Request Failed: " + err); $(document).trigger('status.contact.error', { message: t('contacts', 'Failed deleting property: {error}', {error:err}) }); @@ -333,7 +331,7 @@ OC.Contacts = OC.Contacts || {}; var self = this; this.setAsSaving(this.$fullelem, true); var data = JSON.stringify(this.data); - console.log('stringified', data); + //console.log('stringified', data); $.when(this.storage.saveAllProperties(this.metadata.backend, this.metadata.parent, this.id, data)) .then(function(response) { if(!response.error) { @@ -522,7 +520,7 @@ OC.Contacts = OC.Contacts || {}; $fullname.val(self.data.FN[0]['value']); update_fn = true; } else if($fullname.val() == value[1] + ' ') { - console.log('change', value); + //console.log('change', value); self.data.FN[0]['value'] = value[1] + ' ' + value[0]; $fullname.val(self.data.FN[0]['value']); update_fn = true; @@ -736,7 +734,8 @@ OC.Contacts = OC.Contacts || {}; if($elements.length > 1) { args['value'] = []; $.each($elements, function(idx, e) { - args['value'].push($(e).val()); + args['value'][parseInt($(e).attr('name').substr(6,1))] = $(e).val(); + //args['value'].push($(e).val()); }); } else { args['value'] = $elements.val(); @@ -874,6 +873,9 @@ OC.Contacts = OC.Contacts || {}; || this.metadata.permissions & OC.PERMISSION_DELETE)) { this.$listelem.find('input:checkbox').prop('disabled', true).css('opacity', '0'); } + if(isnew) { + this.setThumbnail(); + } return this.$listelem; }; @@ -925,7 +927,7 @@ OC.Contacts = OC.Contacts || {}; * - Add method to change address book. */ $.each(availableAddressBooks, function(idx, addressBook) { - console.log('addressBook', idx, addressBook); + //console.log('addressBook', idx, addressBook); var $option = $(''); @@ -1055,7 +1057,7 @@ OC.Contacts = OC.Contacts || {}; if($(this).hasClass('value') && this.value === this.defaultValue) { return; } - console.log('change', this.defaultValue, this.value); + //console.log('change', this.defaultValue, this.value); this.defaultValue = this.value; self.saveProperty({obj:event.target}); }); @@ -1364,7 +1366,7 @@ OC.Contacts = OC.Contacts || {}; * Set a thumbnail for the contact if a PHOTO property exists */ Contact.prototype.setThumbnail = function($elem, refresh) { - if(!this.data.thumbnail) { + if(!this.data.thumbnail && !refresh) { return; } if(!$elem) { @@ -1373,8 +1375,12 @@ OC.Contacts = OC.Contacts || {}; if(!$elem.hasClass('thumbnail') && !refresh) { return; } - $elem.removeClass('thumbnail'); - $elem.css('background-image', 'url(data:image/png;base64,' + this.data.thumbnail + ')'); + if(this.data.thumbnail) { + $elem.removeClass('thumbnail'); + $elem.css('background-image', 'url(data:image/png;base64,' + this.data.thumbnail + ')'); + } else { + $elem.addClass('thumbnail'); + } } /** @@ -1387,11 +1393,13 @@ OC.Contacts = OC.Contacts || {}; parent = this.metadata.parent, src; + var $phototools = this.$fullelem.find('#phototools'); if(!this.$photowrapper) { this.$photowrapper = this.$fullelem.find('#photowrapper'); } var finishLoad = function(image) { + console.log('finishLoad', self.getDisplayName(), image.width, image.height); $(image).addClass('contactphoto'); self.$photowrapper.css({width: image.width + 10, height: image.height + 10}); self.$photowrapper.removeClass('loading').removeClass('wait'); @@ -1399,7 +1407,6 @@ OC.Contacts = OC.Contacts || {}; }; this.$photowrapper.addClass('loading').addClass('wait'); - var $phototools = this.$fullelem.find('#phototools'); if(this.getPreferredValue('PHOTO', null) === null) { $.when(this.storage.getDefaultPhoto()) .then(function(image) { @@ -1450,13 +1457,11 @@ OC.Contacts = OC.Contacts || {}; $phototools.find('.delete').hide(); $phototools.find('.edit').hide(); } - $(document).bind('status.contact.photoupdated', function(e, result) { + $(document).bind('status.contact.photoupdated', function(e, data) { + console.log('status.contact.photoupdated', data); self.loadPhoto(true); - var refreshstr = '&refresh='+Math.random(); - // TODO: Use setThumbnail - self.getListItemElement().find('td.name') - .css('background', 'url(' + OC.filePath('', '', 'remote.php') - +'/contactthumbnail?backend='+backend+'&parent='+parent+'id='+id+refreshstr + ')'); + self.data.thumbnail = data.thumbnail; + self.setThumbnail(null, true); }); } }; @@ -1646,7 +1651,7 @@ OC.Contacts = OC.Contacts || {}; $(document).bind('status.contact.updated', function(e, data) { if(['FN', 'EMAIL', 'TEL', 'ADR', 'CATEGORIES'].indexOf(data.property) !== -1) { data.contact.getListItemElement().remove(); - self.insertContact(self.contacts[String(data.contact.id)].renderListItem(true)); + self.insertContact(data.contact.renderListItem(true)); } }); }; @@ -1805,19 +1810,19 @@ OC.Contacts = OC.Contacts || {}; } } else if(utils.isArray(data)) { $.each(data, function(idx, contact) { - console.log('delayedDelete, meta:', contact); + //console.log('delayedDelete, meta:', contact); self.deletionQueue.push(contact); }); //$.extend(this.deletionQueue, data); } else { throw { name: 'WrongParameterType', message: 'ContactList.delayedDelete only accept objects or arrays.'}; } - console.log('delayedDelete, deletionQueue', this.deletionQueue); + //console.log('delayedDelete, deletionQueue', this.deletionQueue); $.each(this.deletionQueue, function(idx, contact) { - console.log('delayedDelete', contact); + //console.log('delayedDelete', contact); contact.detach().setChecked(false); }); - console.log('deletionQueue', this.deletionQueue); + //console.log('deletionQueue', this.deletionQueue); if(!window.onbeforeunload) { window.onbeforeunload = function(e) { e = e || window.event; @@ -1835,14 +1840,14 @@ OC.Contacts = OC.Contacts || {}; message:t('contacts','Click to undo deletion of {num} contacts', {num: self.deletionQueue.length}), //timeout:5, timeouthandler:function() { - console.log('timeout'); + //console.log('timeout'); // Don't fire all deletes at once self.deletionTimer = setInterval(function() { self.deleteContacts(); }, 500); }, clickhandler:function() { - console.log('clickhandler'); + //console.log('clickhandler'); $.each(self.deletionQueue, function(idx, contact) { self.insertContact(contact.getListItemElement()); }); @@ -1859,7 +1864,7 @@ OC.Contacts = OC.Contacts || {}; */ ContactList.prototype.deleteContacts = function() { var self = this; - console.log('ContactList.deleteContacts, deletionQueue', this.deletionQueue); + //console.log('ContactList.deleteContacts, deletionQueue', this.deletionQueue); if(typeof this.deletionTimer === 'undefined') { console.log('No deletion timer!'); window.onbeforeunload = null; @@ -1962,7 +1967,6 @@ OC.Contacts = OC.Contacts || {}; this.contactDetailTemplates ); if(utils.isUInt(this.currentContact)) { - console.assert(typeof this.currentContact == 'number', 'this.currentContact is not a number'); this.contacts[this.currentContact].close(); } return contact.renderContact(props); @@ -2055,7 +2059,7 @@ OC.Contacts = OC.Contacts || {}; addressBook['backend'], addressBook['id'], function(cbresponse) { - console.log('loaded', idx, cbresponse); + //console.log('loaded', idx, cbresponse); num -= 1; if(num === 0) { if(self.length > 0) { @@ -2091,7 +2095,7 @@ OC.Contacts = OC.Contacts || {}; }) .fail(function(jqxhr, textStatus, error) { var err = textStatus + ', ' + error; - console.log( "Request Failed: " + err); + console.warn( "Request Failed: " + err); $(document).trigger('status.contact.error', { message: t('contacts', 'Failed loading address books: {error}', {error:err}) }); @@ -2105,7 +2109,7 @@ OC.Contacts = OC.Contacts || {}; ContactList.prototype.loadContacts = function(backend, addressBookId, cb) { var self = this; $.when(this.storage.getContacts(backend, addressBookId)).then(function(response) { - console.log('ContactList.loadContacts', response); + //console.log('ContactList.loadContacts', response); if(!response.error) { var items = []; $.each(response.data.contacts, function(c, contact) { @@ -2156,7 +2160,7 @@ OC.Contacts = OC.Contacts || {}; }) .fail(function(jqxhr, textStatus, error) { var err = textStatus + ', ' + error; - console.log( "Request Failed: " + err); + console.warn( "Request Failed: " + err); cb({error: true, message: err}); }); }; diff --git a/js/storage.js b/js/storage.js index cf844b8b..15a902f0 100644 --- a/js/storage.js +++ b/js/storage.js @@ -179,7 +179,7 @@ OC.Contacts = OC.Contacts || {}; var url = OC.Router.generate( 'contacts_contact_photo', {user: this.user, backend: backend, addressbookid: addressbookid, contactid: contactid} - ); // TODO: Test when modifying pic if this is needed: + '?refresh=' + Math.random(); + ); var defer = $.Deferred(); $.when( $(photo).load(function() { @@ -188,7 +188,7 @@ OC.Contacts = OC.Contacts || {}; .error(function() { console.log('Error loading default photo', arguments) }) - .attr('src', url) + .attr('src', url + '?refresh=' + Math.random()) ) .fail(function(jqxhr, textStatus, error) { defer.reject(); From 2b031845481a7e736f23132b2cadf9f47bd339fe Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Mon, 8 Apr 2013 22:38:18 +0200 Subject: [PATCH 226/236] Contacts: Moved classpaths to classpath.php --- appinfo/app.php | 47 +++++++++------------------------------------- appinfo/remote.php | 1 + 2 files changed, 10 insertions(+), 38 deletions(-) diff --git a/appinfo/app.php b/appinfo/app.php index 2aa3ad2c..6ded24f9 100644 --- a/appinfo/app.php +++ b/appinfo/app.php @@ -1,44 +1,15 @@ Date: Mon, 8 Apr 2013 22:39:22 +0200 Subject: [PATCH 227/236] Contacts: ksort on array values --- appinfo/routes.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/appinfo/routes.php b/appinfo/routes.php index dd9e85bc..bdb4a18a 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -282,6 +282,11 @@ $this->create('contacts_contact_save_property', 'addressbook/{user}/{backend}/{a if(!$name) { bailOut(App::$l10n->t('Property name is not set.')); } + if(is_array($value)) { + // NOTE: Important, otherwise the compound value will be + // set in the order the fields appear in the form! + ksort($value); + } if(!$checksum && in_array($name, Utils\Properties::$multi_properties)) { bailOut(App::$l10n->t('Property checksum is not set.')); } elseif($checksum && in_array($name, Utils\Properties::$multi_properties)) { From 5f14c98dd59cf7b3237fd32a9a66b3b65aa493d8 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Mon, 8 Apr 2013 22:39:53 +0200 Subject: [PATCH 228/236] Contacts: Fix export --- export.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/export.php b/export.php index 4c08a68f..38b5cd4c 100644 --- a/export.php +++ b/export.php @@ -44,7 +44,7 @@ if(!is_null($bookid)) { } elseif(!is_null($contactid)) { try { $app = new OCA\Contacts\App(); - $contact = $app->getContact($_GET['backend'], $_GET['parent'], $_GET['contactid']); + $contact = $app->getContact($_GET['backend'], $_GET['addressbookid'], $_GET['contactid']); $data = $contact->serialize(); } catch(Exception $e) { OCP\JSON::error( From 2bf485fd10b430729465d79c6f3e75a9db0b4041 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Mon, 8 Apr 2013 22:40:53 +0200 Subject: [PATCH 229/236] Contacts: Fix image upload form --- templates/contacts.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/templates/contacts.php b/templates/contacts.php index ef21b73d..21bae190 100644 --- a/templates/contacts.php +++ b/templates/contacts.php @@ -1,5 +1,7 @@
    - + + + From d300a100c5fc3c65f9b91264d4ff56475377ac46 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Mon, 8 Apr 2013 22:41:51 +0200 Subject: [PATCH 230/236] Contacts: Fix description on StringProperty --- lib/vobject/stringproperty.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/vobject/stringproperty.php b/lib/vobject/stringproperty.php index daf3c2ba..5b686470 100644 --- a/lib/vobject/stringproperty.php +++ b/lib/vobject/stringproperty.php @@ -2,8 +2,7 @@ /** * ownCloud - VObject String Property * - * This component represents the BEGIN:VCARD and END:VCARD found in every - * vcard. + * This class adds escaping/unescaping of simple string properties. * * @author Thomas Tanghus * @author Evert Pot (http://www.rooftopsolutions.nl/) From ad1664e6584c584e1bb2b8cdddb387d46f167843 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Tue, 9 Apr 2013 17:20:30 +0200 Subject: [PATCH 231/236] Contacts: Add Utils\Properties::generateUID() --- lib/utils/properties.php | 4 ++++ lib/vobject/vcard.php | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/utils/properties.php b/lib/utils/properties.php index 7742b84b..cd286045 100644 --- a/lib/utils/properties.php +++ b/lib/utils/properties.php @@ -186,6 +186,10 @@ Class Properties { ); } + public static function generateUID($app = 'contacts') { + return date('Ymd\\THis') . '.' . time(). '@' . OCP\Util::getServerHostName(); + } + /** * Update the contact property index. * diff --git a/lib/vobject/vcard.php b/lib/vobject/vcard.php index bbf3d147..36085ec1 100644 --- a/lib/vobject/vcard.php +++ b/lib/vobject/vcard.php @@ -25,7 +25,7 @@ namespace OCA\Contacts\VObject; -use OCA\Contacts; +use OCA\Contacts\Utils; use Sabre\VObject; /** @@ -57,7 +57,7 @@ class VCard extends VObject\Component\VCard { */ protected function formatPropertyTypes(&$property) { foreach($property->parameters as $key=>&$parameter) { - $types = Contacts\Utils\Properties::getTypesForProperty($property->name); + $types = Utils\Properties::getTypesForProperty($property->name); if(is_array($types) && in_array(strtoupper($parameter->name), array_keys($types)) || strtoupper($parameter->name) == 'PREF') { unset($property->parameters[$key]); @@ -199,7 +199,7 @@ class VCard extends VObject\Component\VCard { 'node' => $this, ); if ($options & self::REPAIR) { - $this->UID = substr(md5(rand().time()), 0, 10); + $this->UID = Utils\Properties::generateUID(); } } From bf620ad512d01689fbb4c7b059fa242952703ea7 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Tue, 9 Apr 2013 23:19:22 +0200 Subject: [PATCH 232/236] Contacts: Use a more generic error message for failed ajax requests. --- js/storage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/storage.js b/js/storage.js index 15a902f0..aa47c411 100644 --- a/js/storage.js +++ b/js/storage.js @@ -418,7 +418,7 @@ OC.Contacts = OC.Contacts || {}; defer.reject( new JSONResponse({ status:'error', - data:{message:t('contacts', 'Failed loading address books: {error}', {error:textStatus + ', ' + error})} + data:{message:t('contacts', 'Request failed: {error}', {error:textStatus + ', ' + error})} }) ); }); From 917a40e4dfbe01650296ce335b0c8011e8547ce6 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Thu, 11 Apr 2013 15:35:17 +0000 Subject: [PATCH 233/236] Contacts: Use done() and fail() from $.ajax() --- js/storage.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/js/storage.js b/js/storage.js index aa47c411..e7d41628 100644 --- a/js/storage.js +++ b/js/storage.js @@ -412,7 +412,7 @@ OC.Contacts = OC.Contacts || {}; data: params }; var defer = $.Deferred(); - $.when($.ajax(ajaxParams)).then(function(response) { + /*$.when($.ajax(ajaxParams)).then(function(response) { defer.resolve(new JSONResponse(response)); }).fail(function(jqxhr, textStatus, error) { defer.reject( @@ -421,7 +421,21 @@ OC.Contacts = OC.Contacts || {}; data:{message:t('contacts', 'Request failed: {error}', {error:textStatus + ', ' + error})} }) ); - }); + });*/ + + var jqxhr = $.ajax(ajaxParams) + .done(function(response) { + defer.resolve(new JSONResponse(response)); + }) + .fail(function(jqxhr, textStatus, error) { + defer.reject( + new JSONResponse({ + status:'error', + data:{message:t('contacts', 'Request failed: {error}', {error:textStatus + ', ' + error})} + }) + ); + }); + return defer.promise(); } From 9b063b4e200a73530055f5c2bb64e77d4047a221 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Fri, 12 Apr 2013 11:43:56 +0200 Subject: [PATCH 234/236] Contacts: Debug Sabre exceptions if DEBUG is set --- appinfo/remote.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/appinfo/remote.php b/appinfo/remote.php index 0dd72163..0702c5e7 100644 --- a/appinfo/remote.php +++ b/appinfo/remote.php @@ -64,5 +64,8 @@ $server->addPlugin(new Sabre_DAVACL_Plugin()); $server->addPlugin(new Sabre_DAV_Browser_Plugin(false)); // Show something in the Browser, but no upload $server->addPlugin(new Sabre_CardDAV_VCFExportPlugin()); +if(defined('DEBUG') && DEBUG) { + $server->debugExceptions = true; +} // And off we go! $server->exec(); From 92d0c8357457135d15c484af363bf56ade26329e Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Mon, 15 Apr 2013 22:47:11 +0200 Subject: [PATCH 235/236] Contacts: Use octemplate from core. --- index.php | 2 +- js/octemplate.js | 54 ------------------------------------------------ 2 files changed, 1 insertion(+), 55 deletions(-) delete mode 100644 js/octemplate.js diff --git a/index.php b/index.php index e92444d9..b77aed01 100644 --- a/index.php +++ b/index.php @@ -34,8 +34,8 @@ $maxUploadFilesize = \OCP\Util::maxUploadFilesize('/'); \OCP\Util::addscript('', 'multiselect'); \OCP\Util::addscript('', 'jquery.multiselect'); \OCP\Util::addscript('', 'oc-vcategories'); +\OCP\Util::addscript('', 'octemplate'); \OCP\Util::addscript('contacts', 'modernizr.custom'); -\OCP\Util::addscript('contacts', 'octemplate'); \OCP\Util::addscript('contacts', 'app'); \OCP\Util::addscript('contacts', 'contacts'); \OCP\Util::addscript('contacts', 'storage'); diff --git a/js/octemplate.js b/js/octemplate.js deleted file mode 100644 index 44acf734..00000000 --- a/js/octemplate.js +++ /dev/null @@ -1,54 +0,0 @@ -(function( $ ) { - /** - * Object Template - * Inspired by micro templating done by e.g. underscore.js - */ - var Template = { - init: function(vars, options, elem) { - // Mix in the passed in options with the default options - this.vars = vars; - this.options = $.extend({},this.options,options); - - this.elem = elem; - var self = this; - - if(typeof this.options.escapeFunction === 'function') { - $.each(this.vars, function(key, val) { - if(typeof val === 'string') { - self.vars[key] = self.options.escapeFunction(val); - } - }); - } - - var _html = this._build(this.vars); - return $(_html); - }, - // From stackoverflow.com/questions/1408289/best-way-to-do-variable-interpolation-in-javascript - _build: function(o){ - var data = this.elem.attr('type') === 'text/template' ? this.elem.html() : this.elem.get(0).outerHTML; - try { - return data.replace(/{([^{}]*)}/g, - function (a, b) { - var r = o[b]; - return typeof r === 'string' || typeof r === 'number' ? r : a; - } - ); - } catch(e) { - console.error(e, 'data:', data) - } - }, - options: { - escapeFunction: function(str) {return $('').text(str).html();} - } - }; - - $.fn.octemplate = function(vars, options) { - var vars = vars ? vars : {}; - if(this.length) { - var _template = Object.create(Template); - return _template.init(vars, options, this); - } - }; - -})( jQuery ); - From 2fbbc01f91aacb12cd130fe7c2e4968ea9da85a2 Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Wed, 17 Apr 2013 14:37:22 +0200 Subject: [PATCH 236/236] Remove obsolete TODOs --- lib/backend/database.php | 1 - lib/backend/shared.php | 2 -- 2 files changed, 3 deletions(-) diff --git a/lib/backend/database.php b/lib/backend/database.php index 7c491c35..72e034f4 100644 --- a/lib/backend/database.php +++ b/lib/backend/database.php @@ -237,7 +237,6 @@ class Database extends AbstractBackend { /** * Deletes an entire addressbook and all its contents * - * TODO: Delete contacts as well. * @param string $addressbookid * @return bool */ diff --git a/lib/backend/shared.php b/lib/backend/shared.php index 95163859..114e149c 100644 --- a/lib/backend/shared.php +++ b/lib/backend/shared.php @@ -36,8 +36,6 @@ class Shared extends Database { /** * Returns the list of addressbooks for a specific user. * - * TODO: Create default if none exists. - * * @param string $principaluri * @return array */