From 87ee0f008889557662316e18b9f7efe7e34af732 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Tue, 5 Apr 2011 20:39:13 +0000 Subject: [PATCH] * CardDAV: store name part of URL from client PUT request, to fully comply with CardDAV spec --- .../inc/class.addressbook_groupdav.inc.php | 83 ++++++++++--------- addressbook/inc/class.addressbook_sql.inc.php | 18 +++- phpgwapi/inc/class.groupdav.inc.php | 8 +- phpgwapi/inc/class.groupdav_handler.inc.php | 17 +++- .../inc/class.groupdav_principals.inc.php | 10 ++- phpgwapi/setup/setup.inc.php | 3 +- phpgwapi/setup/tables_current.inc.php | 5 +- phpgwapi/setup/tables_update.inc.php | 19 +++++ 8 files changed, 106 insertions(+), 57 deletions(-) diff --git a/addressbook/inc/class.addressbook_groupdav.inc.php b/addressbook/inc/class.addressbook_groupdav.inc.php index 2025c992cb..64560d8680 100644 --- a/addressbook/inc/class.addressbook_groupdav.inc.php +++ b/addressbook/inc/class.addressbook_groupdav.inc.php @@ -7,7 +7,7 @@ * @package addressbook * @subpackage groupdav * @author Ralf Becker - * @copyright (c) 2007-9 by Ralf Becker + * @copyright (c) 2007-11 by Ralf Becker * @version $Id$ */ @@ -77,9 +77,11 @@ class addressbook_groupdav extends groupdav_handler var $charset = 'utf-8'; /** - * What attribute is used to construct the path, default id, can be uid too + * Which attribute to use to contruct name part of url/path + * + * @var string */ - const PATH_ATTRIBUTE = 'id'; + static $path_attr = 'id'; /** * Constructor @@ -94,6 +96,18 @@ class addressbook_groupdav extends groupdav_handler parent::__construct($app,$debug,$base_uri,$principalURL); $this->bo = new addressbook_bo(); + + // since 1.9.007 we allow clients to specify the URL when creating a new contact, as specified by CardDAV + if ($this->bo->account_repository != 'ldap' && + version_compare($GLOBALS['egw_info']['apps']['phpgwapi']['version'], '1.9.007', '>=')) + { + self::$path_attr = 'carddav_name'; + groupdav_handler::$path_extension = ''; + } + else + { + groupdav_handler::$path_extension = '.vcf'; + } } /** @@ -104,7 +118,7 @@ class addressbook_groupdav extends groupdav_handler */ static function get_path($contact) { - return $contact[self::PATH_ATTRIBUTE].'.vcf'; + return $contact[self::$path_attr].groupdav_handler::$path_extension; } /** @@ -169,7 +183,9 @@ class addressbook_groupdav extends groupdav_handler unset($filter['address_data']); $files = array(); // we query etag and modified, as LDAP does not have the strong sql etag - if (($contacts =& $this->bo->search(array(),array('id','uid','etag','modified'),'egw_addressbook.contact_id','','',False,'AND',$start,$filter))) + $cols = array('id','uid','etag','modified'); + if (!in_array(self::$path_attr,$cols)) $cols[] = self::$path_attr; + if (($contacts =& $this->bo->search(array(),$cols,'egw_addressbook.contact_id','','',False,'AND',$start,$filter))) { foreach($contacts as &$contact) { @@ -258,15 +274,18 @@ class addressbook_groupdav extends groupdav_handler if ($option['name'] == 'href') { $parts = explode('/',$option['data']); - if (($id = array_pop($parts))) $ids[] = basename($id,'.vcf'); + if (($id = array_pop($parts))) + { + $ids[] = groupdav_handler::$path_extension ? basename($id,groupdav_handler::$path_extension) : $id; + } } } - if ($ids) $filters[self::PATH_ATTRIBUTE] = $ids; + if ($ids) $filters[self::$path_attr] = $ids; if ($this->debug) error_log(__METHOD__."($path,,,$user) addressbook-multiget: ids=".implode(',',$ids)); } elseif ($id) { - $filters[self::PATH_ATTRIBUTE] = basename($id,'.vcf'); + $filters[self::$path_attr] = groupdav_handler::$path_extension ? basename($id,groupdav_handler::$path_extension) : $id; } return true; } @@ -337,38 +356,20 @@ class addressbook_groupdav extends groupdav_handler } } - if (is_array($oldContact)) + $contact = $handler->vcardtoegw($vCard, $charset); + + if (is_array($oldContact) || ($oldContact = $this->bo->read(array('contact_uid' => $contact['uid'])))) { $contactId = $oldContact['id']; $retval = true; } else { - // new entry? - if (($foundContacts = $handler->search($vCard, null, false, $charset))) - { - if (($contactId = array_shift($foundContacts)) && - ($oldContact = $this->bo->read($contactId))) - { - $retval = '301 Moved Permanently'; - } - else - { - // to be safe - $contactId = -1; - $retval = '201 Created'; - } - } - else - { - // new entry - $contactId = -1; - $retval = '201 Created'; - } + // new entry + $contactId = -1; + $retval = '201 Created'; } - $contact = $handler->vcardtoegw($vCard, $charset); - if (is_array($contact['cat_id'])) { $contact['cat_id'] = implode(',',$this->bo->find_or_add_categories($contact['cat_id'], $contactId)); @@ -384,6 +385,11 @@ class addressbook_groupdav extends groupdav_handler $contact['uid'] = $oldContact['uid']; $contact['owner'] = $oldContact['owner']; $contact['private'] = $oldContact['private']; + $contact['carddav_name'] = $oldContact['carddav_name']; + } + else + { + $contact['carddav_name'] = $id; } // only set owner, if user is explicitly specified in URL (check via prefix, NOT for /addressbook/ !) if ($prefix) @@ -415,14 +421,15 @@ class addressbook_groupdav extends groupdav_handler } header('ETag: '.$this->get_etag($contact)); - if ($retval !== true) + + // send GroupDAV Location header only if we dont use carddav_name as path-attribute + if ($retval !== true && self::$path_attr == 'id') { $path = preg_replace('|(.*)/[^/]*|', '\1/', $options['path']); header($h='Location: '.$this->base_uri.$path.self::get_path($contact)); if ($this->debug) error_log(__METHOD__."($method,,$id) header('$h'): $retval"); - return $retval; } - return true; + return $retval; } /** @@ -547,18 +554,18 @@ class addressbook_groupdav extends groupdav_handler { return '412 Precondition Failed'; } - //return $ok; + return true; } /** * Read a contact * - * @param string/id $id + * @param string|id $id * @return array/boolean array with entry, false if no read rights, null if $id does not exist */ function read($id) { - return $this->bo->read(self::PATH_ATTRIBUTE == 'id' ? $id : array(self::PATH_ATTRIBUTE => $id)); + return $this->bo->read(array(self::$path_attr => $id)); } /** diff --git a/addressbook/inc/class.addressbook_sql.inc.php b/addressbook/inc/class.addressbook_sql.inc.php index 1a553ad2ea..ce86346ec9 100644 --- a/addressbook/inc/class.addressbook_sql.inc.php +++ b/addressbook/inc/class.addressbook_sql.inc.php @@ -108,7 +108,7 @@ class addressbook_sql extends so_sql_cf if (isset($param['advanced_search']) && !empty($param['advanced_search'])) $advanced_search = true; $wildcard ='%'; if ($advanced_search || (isset($param['wildcard']) && !empty($param['wildcard']))) $wildcard = ($param['wildcard']?$param['wildcard']:''); - + // fix cat_id filter to search in comma-separated multiple cats and return subcats if ((int)$filter['cat_id']) { @@ -632,12 +632,22 @@ class addressbook_sql extends so_sql_cf } } + $update = array(); // enforce a minium uid strength - if (!$err && (!isset($this->data['uid']) - || strlen($this->data['uid']) < $minimum_uid_length)) { - parent::update(array('uid' => common::generate_uid('addressbook',$this->data['id']))); + if (!isset($this->data['uid']) || strlen($this->data['uid']) < $minimum_uid_length) + { + $update['uid'] = common::generate_uid('addressbook',$this->data['id']); //echo "

set uid={$this->data['uid']}, etag={$this->data['etag']}

"; } + // set carddav_name, if not given by caller + if (empty($this->data['carddav_name']) && version_compare($GLOBALS['egw_info']['apps']['phpgwapi']['version'], '1.9.007', '>=')) + { + $update['carddav_name'] = $this->data['id'].'.vcf'; + } + if (!$err && $update) + { + parent::update($update); + } return $err; } diff --git a/phpgwapi/inc/class.groupdav.inc.php b/phpgwapi/inc/class.groupdav.inc.php index b017d73944..9a73b4938c 100644 --- a/phpgwapi/inc/class.groupdav.inc.php +++ b/phpgwapi/inc/class.groupdav.inc.php @@ -7,7 +7,7 @@ * @package api * @subpackage groupdav * @author Ralf Becker - * @copyright (c) 2007-10 by Ralf Becker + * @copyright (c) 2007-11 by Ralf Becker * @version $Id$ */ @@ -122,7 +122,6 @@ class groupdav extends HTTP_WebDAV_Server */ var $accounts; - function __construct() { if (!$this->debug) $this->debug = (int)$GLOBALS['egw_info']['user']['preferences']['groupdav']['debug_level']; @@ -966,10 +965,7 @@ class groupdav extends HTTP_WebDAV_Server $user = $GLOBALS['egw_info']['user']['account_id']; } - if (($id = array_pop($parts))) - { - list($id) = explode('.',$id); // remove evtl. .ics extension - } + $id = array_pop($parts); $ok = $id && $user && in_array($app,array('addressbook','calendar','infolog','principals')); if ($this->debug) diff --git a/phpgwapi/inc/class.groupdav_handler.inc.php b/phpgwapi/inc/class.groupdav_handler.inc.php index 8ccbce4024..5342f1f39f 100644 --- a/phpgwapi/inc/class.groupdav_handler.inc.php +++ b/phpgwapi/inc/class.groupdav_handler.inc.php @@ -7,7 +7,7 @@ * @package api * @subpackage groupdav * @author Ralf Becker - * @copyright (c) 2007-9 by Ralf Becker + * @copyright (c) 2007-11 by Ralf Becker * @version $Id$ */ @@ -78,6 +78,13 @@ abstract class groupdav_handler */ var $agent; + /** + * Extension to append to url/path + * + * @var string + */ + static $path_extension = '.ics'; + /** * Constructor * @@ -105,7 +112,6 @@ abstract class groupdav_handler $this->agent = self::get_agent(); $this->egw_charset = translation::charset(); - $this->accounts = $GLOBALS['egw']->accounts; } /** @@ -230,12 +236,14 @@ abstract class groupdav_handler * * @param string $method GET, PUT, DELETE * @param array &$options - * @param int $id + * @param int|string &$id on return self::$path_extension got removed * @param boolean &$return_no_access=false if set to true on call, instead of '403 Forbidden' the entry is returned and $return_no_access===false * @return array|string entry on success, string with http-error-code on failure, null for PUT on an unknown id */ - function _common_get_put_delete($method,&$options,$id,&$return_no_access=false) + function _common_get_put_delete($method,&$options,&$id,&$return_no_access=false) { + if (self::$path_extension) $id = basename($id,self::$path_extension); + if ($this->app != 'principals' && !$GLOBALS['egw_info']['user']['apps'][$this->app]) { if ($this->debug) error_log(__METHOD__."($method,,$id) 403 Forbidden: no app rights for '$this->app'"); @@ -535,6 +543,7 @@ class groupdav_propfind_iterator implements Iterator $this->start = 0; $this->files = $this->common_files; + if (!$this->files) $this->next(); // otherwise valid will return false and nothing get returned reset($this->files); } diff --git a/phpgwapi/inc/class.groupdav_principals.inc.php b/phpgwapi/inc/class.groupdav_principals.inc.php index a8838e4ba3..42c8647a8e 100644 --- a/phpgwapi/inc/class.groupdav_principals.inc.php +++ b/phpgwapi/inc/class.groupdav_principals.inc.php @@ -7,7 +7,7 @@ * @package api * @subpackage groupdav * @author Ralf Becker - * @copyright (c) 2008-10 by Ralf Becker + * @copyright (c) 2008-11 by Ralf Becker * @version $Id$ */ @@ -18,6 +18,12 @@ */ class groupdav_principals extends groupdav_handler { + /** + * Reference to the accounts class + * + * @var accounts + */ + var $accounts; /** * Constructor @@ -30,6 +36,8 @@ class groupdav_principals extends groupdav_handler function __construct($app,$debug=null,$base_uri=null,$principalURL=null) { parent::__construct($app,$debug,$base_uri,$principalURL); + + $this->accounts = $GLOBALS['egw']->accounts; } /** diff --git a/phpgwapi/setup/setup.inc.php b/phpgwapi/setup/setup.inc.php index eaffa1bb6f..131705877a 100755 --- a/phpgwapi/setup/setup.inc.php +++ b/phpgwapi/setup/setup.inc.php @@ -12,7 +12,7 @@ /* Basic information about this app */ $setup_info['phpgwapi']['name'] = 'phpgwapi'; $setup_info['phpgwapi']['title'] = 'eGroupWare API'; -$setup_info['phpgwapi']['version'] = '1.9.006'; +$setup_info['phpgwapi']['version'] = '1.9.007'; $setup_info['phpgwapi']['versions']['current_header'] = '1.29'; $setup_info['phpgwapi']['enable'] = 3; $setup_info['phpgwapi']['app_order'] = 1; @@ -74,4 +74,3 @@ $setup_info['groupdav']['author'] = $setup_info['groupdav']['maintainer'] = arra $setup_info['groupdav']['license'] = 'GPL'; $setup_info['groupdav']['hooks']['preferences'] = 'groupdav_hooks::menus'; $setup_info['groupdav']['hooks']['settings'] = 'groupdav_hooks::settings'; - diff --git a/phpgwapi/setup/tables_current.inc.php b/phpgwapi/setup/tables_current.inc.php index 774a74f564..5c48b8afce 100644 --- a/phpgwapi/setup/tables_current.inc.php +++ b/phpgwapi/setup/tables_current.inc.php @@ -336,11 +336,12 @@ $phpgw_baseline = array( 'contact_etag' => array('type' => 'int','precision' => '4','default' => '0'), 'contact_uid' => array('type' => 'varchar','precision' => '255'), 'adr_one_countrycode' => array('type' => 'varchar','precision' => '2'), - 'adr_two_countrycode' => array('type' => 'varchar','precision' => '2') + 'adr_two_countrycode' => array('type' => 'varchar','precision' => '2'), + 'carddav_name' => array('type' => 'varchar','precision' => '64','comment' => 'name part of CardDAV URL, if specified by client') ), 'pk' => array('contact_id'), 'fk' => array(), - 'ix' => array('contact_owner','cat_id','n_fileas','contact_uid',array('n_family','n_given'),array('n_given','n_family'),array('org_name','n_family','n_given')), + 'ix' => array('contact_owner','cat_id','n_fileas','contact_uid','caldav_name',array('n_family','n_given'),array('n_given','n_family'),array('org_name','n_family','n_given')), 'uc' => array('account_id') ), 'egw_addressbook_extra' => array( diff --git a/phpgwapi/setup/tables_update.inc.php b/phpgwapi/setup/tables_update.inc.php index 628773dfc7..d23f99a14c 100644 --- a/phpgwapi/setup/tables_update.inc.php +++ b/phpgwapi/setup/tables_update.inc.php @@ -154,3 +154,22 @@ function phpgwapi_upgrade1_9_005() return $GLOBALS['setup_info']['phpgwapi']['currentver'] = '1.9.006'; } +/** + * Add column to store CalDAV name given by client + */ +function phpgwapi_upgrade1_9_006() +{ + $GLOBALS['egw_setup']->oProc->AddColumn('egw_addressbook','carddav_name',array( + 'type' => 'varchar', + 'precision' => '64', + 'comment' => 'name part of CardDAV URL, if specified by client' + )); + $GLOBALS['egw_setup']->db->query($sql='UPDATE egw_addressbook SET carddav_name='. + $GLOBALS['egw_setup']->db->concat( + $GLOBALS['egw_setup']->db->to_varchar('contact_id'),"'.vcf'"),__LINE__,__FILE__); + + $GLOBALS['egw_setup']->oProc->CreateIndex('egw_addressbook','carddav_name'); + + return $GLOBALS['setup_info']['phpgwapi']['currentver'] = '1.9.007'; +} +