From 01ce3bd88d94e0fcf00848c410637009e36edd17 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Mon, 30 Jan 2012 05:11:05 +0000 Subject: [PATCH] * CalDAV/CardDAV: /addressbook-accounts/ and PROPPATCH for addressbooks/calendars for every namespace but CalDAV, CardDAV or Calendarserver (displayname or *-describtion are ok) --- .../inc/class.addressbook_groupdav.inc.php | 14 +- phpgwapi/inc/class.groupdav.inc.php | 127 ++++++++++++------ phpgwapi/inc/class.groupdav_handler.inc.php | 2 +- phpgwapi/inc/class.groupdav_hooks.inc.php | 2 +- .../inc/class.groupdav_principals.inc.php | 5 +- 5 files changed, 101 insertions(+), 49 deletions(-) diff --git a/addressbook/inc/class.addressbook_groupdav.inc.php b/addressbook/inc/class.addressbook_groupdav.inc.php index 75cfb1c0a3..2717afc41d 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-11 by Ralf Becker + * @copyright (c) 2007-12 by Ralf Becker * @version $Id$ */ @@ -117,7 +117,7 @@ class addressbook_groupdav extends groupdav_handler { $filter = array(); // show addressbook of a single user? - if ($user && $path != '/addressbook/') $filter['contact_owner'] = $user; + if ($user && $path != '/addressbook/' || $user === 0) $filter['contact_owner'] = $user; // should we hide the accounts addressbook if ($GLOBALS['egw_info']['user']['preferences']['addressbook']['hide_accounts']) $filter['account_id'] = null; @@ -327,7 +327,7 @@ class addressbook_groupdav extends groupdav_handler 10 */ - foreach($options['other'] as $option) + foreach((array)$options['other'] as $option) { switch($option['name']) { @@ -580,9 +580,11 @@ class addressbook_groupdav extends groupdav_handler */ public function extra_properties(array $props=array(), $displayname, $base_uri=null, $user=null) { - // addressbook description - $displayname = translation::convert(lang('Addressbook of'),translation::charset(),'utf-8').' '.$displayname; - $props['addressbook-description'] = HTTP_WebDAV_Server::mkprop(groupdav::CARDDAV,'addressbook-description',$displayname); + if (!isset($props['addressbook-description'])) + { + // default addressbook description: can be overwritten via PROPPATCH, in which case it's already set + $props['addressbook-description'] = HTTP_WebDAV_Server::mkprop(groupdav::CARDDAV,'addressbook-description',$props['displayname']); + } // supported reports (required property for CardDAV) $props['supported-report-set'] = HTTP_WebDAV_Server::mkprop('supported-report-set',array( HTTP_WebDAV_Server::mkprop('supported-report',array( diff --git a/phpgwapi/inc/class.groupdav.inc.php b/phpgwapi/inc/class.groupdav.inc.php index 1657f11d98..5c4f935056 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-11 by Ralf Becker + * @copyright (c) 2007-12 by Ralf Becker * @version $Id$ */ @@ -371,7 +371,8 @@ class groupdav extends HTTP_WebDAV_Server if ($method != 'REPORT' && !$id) // no self URL for REPORT requests (only PROPFIND) or propfinds on an id { // KAddressbook doubles the folder, if the self URL contains the GroupDAV/CalDAV resourcetypes - $files['files'][0] = $this->add_app($app,$app=='addressbook'&&$handler->get_agent()=='kde',$user,$path.$app.'/'); + $files['files'][0] = $this->add_app($app,$app=='addressbook'&&$handler->get_agent()=='kde',$user, + $user || $app != 'addressbook' ? $path.$app.'/' : '/addressbook-accounts/'); // Hack for iOS 5.0.1 addressbook to stop asking directory gateway permissions with depth=1 if ($method == 'PROPFIND' && $options['path'] == '/addressbook/' && $handler->get_agent() == 'dataaccess') @@ -514,24 +515,31 @@ class groupdav extends HTTP_WebDAV_Server * @param string $name property name * @param string $ns=null namespace, if that is to be checked too * @param boolean $return_prop=false if true return the property array with values for 'name', 'xmlns', 'attrs', 'children' - * @return boolean|string|array true: $name explicitly requested (or autoindex), 'allprop' requested, false: $name was not requested + * @return boolean|string|array true: $name explicitly requested (or autoindex), "all": allprop or "names": propname requested, false: $name was not requested */ function prop_requested($name, $ns=null, $return_prop=false) { if (!is_array($this->propfind_options) || !isset($this->propfind_options['props'])) { - return true; // no props set, should happen only in autoindex, we return true to show all available props + $ret = true; // no props set, should happen only in autoindex, we return true to show all available props } - $ret = false; - foreach($this->propfind_options['props'] as $prop) + elseif (!is_array($this->propfind_options['props'])) { - if ($prop['name'] == $name && (is_null($ns) || $prop['xmlns'] == $ns)) - { - $ret = $return_prop ? $prop : true; - break; - } - if ($prop['name'] == 'allprop') $ret = 'allprop'; + $ret = $this->propfind_options['props']; // "all": allprop or "names": propname } + else + { + $ret = false; + foreach($this->propfind_options['props'] as $prop) + { + if ($prop['name'] == $name && (is_null($ns) || $prop['xmlns'] == $ns)) + { + $ret = $return_prop ? $prop : true; + break; + } + } + } + //error_log(__METHOD__."('$name', '$ns', $return_prop) propfind_options=".array2string($this->propfind_options)); return $ret; } @@ -592,6 +600,12 @@ class groupdav extends HTTP_WebDAV_Server $files['files'][] = $this->add_app($app,false,$user,$path.$app.'/'); + // only add global /addressbook-accounts/ as the one in home-set is added (and controled) by add_shared + if ($path == '/' && $app == 'addressbook' && + !$GLOBALS['egw_info']['user']['preferences']['addressbook']['hide_accounts']) + { + $files['files'][] = $this->add_app($app,false,0,$path.$app.'-accounts/'); + } // added shared calendars or addressbooks $this->add_shared($files['files'], $path, $app, $user); } @@ -781,10 +795,18 @@ class groupdav extends HTTP_WebDAV_Server // add props modifyable via proppatch from client, eg. calendar-color, see self::$proppatch_props foreach((array)$GLOBALS['egw_info']['user']['preferences'][$app] as $name => $value) { - list($prop,$prop4user) = explode(':', $name); - if ($prop4user == $user && isset(self::$proppatch_props[$prop])) + unset($ns); + list($prop,$prop4user,$ns) = explode(':', $name, 3); + if ($prop4user == (string)$user && isset(self::$proppatch_props[$prop]) && !isset($ns)) { $props[$prop] = self::mkprop(self::$proppatch_props[$prop], $prop, $value); + //error_log(__METHOD__."() explicit ".self::$proppatch_props[$prop].":$prop=".array2string($value)); + } + // props in arbitrary namespaces not mentioned in self::$ns_needs_explicit_named_props + elseif(isset($ns) && !in_array($ns,self::$ns_needs_explicit_named_props)) + { + $props[] = self::mkprop($ns, $prop, $value); + //error_log(__METHOD__."() arbitrary $ns:$prop=".array2string($value)); } } @@ -998,7 +1020,6 @@ class groupdav extends HTTP_WebDAV_Server echo "\t\t".$value."\n\t\n"; } echo "\n"; - $dav = array(1); $allow = false; $this->OPTIONS($options['path'], $dav, $allow); @@ -1122,55 +1143,74 @@ class groupdav extends HTTP_WebDAV_Server } /** - * props modifyable via proppatch from client, eg. calendar-color + * Namespaces which need to be eplicitly named in self::$proppatch_props, + * because we consider them protected, if not explicitly named + * + * @var array + */ + static $ns_needs_explicit_named_props = array(self::DAV, self::CALDAV, self::CARDDAV, self::CALENDARSERVER); + /** + * props modifyable via proppatch from client for name-spaces mentioned in self::$ns_needs_explicit_named_props + * + * Props named here are stored in prefs without namespace! * * @var array name => namespace pairs */ static $proppatch_props = array( 'displayname' => self::DAV, 'calendar-description' => self::CALDAV, - 'calendar-color' => self::ICAL, + 'addressbook-description' => self::CARDDAV, + 'calendar-color' => self::ICAL, // only mentioned that old prefs still work 'calendar-order' => self::ICAL, ); /** * PROPPATCH method handler * - * @param array general parameter passing array - * @return mixed boolean true on success, false on failure or string with http status (eg. '404 Not Found') + * @param array &$options general parameter passing array + * @return string with responsedescription or null, individual status in $options['props'][]['status'] */ function PROPPATCH(&$options) { if ($this->debug) error_log(__CLASS__."::$method(".array2string($options).')'); // parse path in form [/account_lid]/app[/more] - if (!self::_parse_path($options['path'],$id,$app,$user,$user_prefix)) + self::_parse_path($options['path'],$id,$app,$user,$user_prefix); // allways returns false if eg. !$id + if (!$app || $app == 'principals' || $id) { if ($this->debug > 1) error_log(__CLASS__."::$method: user='$user', app='$app', id='$id': 404 not found!"); - return '404 Not Found'; + foreach($options['props'] as &$prop) $prop['status'] = '403 Forbidden'; + return 'NOT allowed to PROPPATCH that resource!'; } - /* allow handlers to implement own proppatch handler - if (($handler = self::app_handler($app)) && method_exists($handler, 'proppatch')) - { - return $handler->proppatch($options, $user); - }*/ - // store selected props in preferences, eg. calendar-color, see self::$proppatch_props - foreach($options['props'] as $prop) + foreach($options['props'] as &$prop) { - if (isset(self::$proppatch_props[$prop['name']])) + if ((isset(self::$proppatch_props[$prop['name']]) && self::$proppatch_props[$prop['name']] === $prop['xmlns'] || + !in_array($prop['xmlns'],self::$ns_needs_explicit_named_props))) { - $GLOBALS['egw']->preferences->add($app, $prop['name'].':'.$user, $prop['val']); - $need_save = true; + $name = $prop['name'].':'.$user.(isset(self::$proppatch_props[$prop['name']]) && + self::$proppatch_props[$prop['name']] == $prop['ns'] ? '' : ':'.$prop['ns']); + //error_log("preferences['user']['$app']['$name']=".array2string($GLOBALS['egw_info']['user']['preferences'][$app][$name]).($GLOBALS['egw_info']['user']['preferences'][$app][$name] !== $prop['val'] ? ' !== ':' === ')."prop['val']=".array2string($prop['val'])); + if ($GLOBALS['egw_info']['user']['preferences'][$app][$name] !== $prop['val']) // nothing to change otherwise + { + if (isset($prop['val'])) + { + $GLOBALS['egw']->preferences->add($app, $name, $prop['val']); + } + else + { + $GLOBALS['egw']->preferences->delete($app, $name); + } + $need_save = true; + } + $prop['status'] = '200 OK'; + } + else + { + $prop['status'] = '409 Conflict'; // could also be "403 Forbidden" } } - if ($need_save) - { - $GLOBALS['egw']->preferences->save_repository(); - - return ''; // this is as the filesystem example handler does it, no true or false ... - } - return '501 Not Implemented'; + if ($need_save) $GLOBALS['egw']->preferences->save_repository(); } /** @@ -1391,11 +1431,18 @@ class groupdav extends HTTP_WebDAV_Server $app = array_shift($parts); + // /addressbook-accounts/ + if (!$account_id && $app == 'addressbook-accounts') + { + $app = 'addressbook'; + $user = 0; + $user_prefix = '/'; + } // shared calendars/addressbooks at //(calendar|addressbook|infolog)- - if ($account_id == $GLOBALS['egw_info']['user']['account_id'] && strpos($app, '-') !== false) + elseif ($account_id == $GLOBALS['egw_info']['user']['account_id'] && strpos($app, '-') !== false) { list($app, $username) = explode('-', $app, 2); - if ($username == 'accounts') + if ($username == 'accounts' && !$GLOBALS['egw_info']['user']['preferences']['addressbook']['hide_accounts']) { $account_id = 0; } diff --git a/phpgwapi/inc/class.groupdav_handler.inc.php b/phpgwapi/inc/class.groupdav_handler.inc.php index 3c91d2d379..f36610bcaf 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-11 by Ralf Becker + * @copyright (c) 2007-12 by Ralf Becker * @version $Id$ */ diff --git a/phpgwapi/inc/class.groupdav_hooks.inc.php b/phpgwapi/inc/class.groupdav_hooks.inc.php index 907eb4c637..c5db8540ee 100644 --- a/phpgwapi/inc/class.groupdav_hooks.inc.php +++ b/phpgwapi/inc/class.groupdav_hooks.inc.php @@ -7,7 +7,7 @@ * @package api * @subpackage groupdav * @author Ralf Becker - * @copyright (c) 2010 by Ralf Becker + * @copyright (c) 2010-12 by Ralf Becker * @version $Id$ */ diff --git a/phpgwapi/inc/class.groupdav_principals.inc.php b/phpgwapi/inc/class.groupdav_principals.inc.php index 8460ce09ee..580d8223ce 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-11 by Ralf Becker + * @copyright (c) 2008-12 by Ralf Becker * @version $Id$ */ @@ -791,6 +791,9 @@ class groupdav_principals extends groupdav_handler HTTP_WebDAV_Server::mkprop('href','MAILTO:'.$account['account_email'])), // CardDAV 'addressbook-home-set' => HTTP_WebDAV_Server::mkprop(groupdav::CARDDAV,'addressbook-home-set',$addressbooks), + 'principal-address' => HTTP_WebDAV_Server::mkprop(groupdav::CARDDAV,'principal-address', + $GLOBALS['egw_info']['user']['preferences']['addressbook']['hide_accounts'] ? '' : + HTTP_WebDAV_Server::mkprop('href',$this->base_uri.'/addressbook-accounts/'.$account['person_id'].'.vcf')), // CardDAV directory 'directory-gateway' => HTTP_WebDAV_Server::mkprop(groupdav::CARDDAV, 'directory-gateway',array( HTTP_WebDAV_Server::mkprop('href', $this->base_uri.'/addressbook/'))),