* CalDAV: store name part of URL from client PUT request, to fully comply with CalDAV spec

This commit is contained in:
Ralf Becker 2011-04-06 19:26:10 +00:00
parent 3825fecf6d
commit 0bfd238e3f
8 changed files with 106 additions and 90 deletions

View File

@ -1,12 +1,12 @@
<?php <?php
/** /**
* eGroupWare - Calendar's buisness-object - access only * EGroupware - Calendar's buisness-object - access only
* *
* @link http://www.egroupware.org * @link http://www.egroupware.org
* @package calendar * @package calendar
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de> * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @author Joerg Lehrke <jlehrke@noc.de> * @author Joerg Lehrke <jlehrke@noc.de>
* @copyright (c) 2004-9 by RalfBecker-At-outdoor-training.de * @copyright (c) 2004-11 by RalfBecker-At-outdoor-training.de
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @version $Id$ * @version $Id$
*/ */

View File

@ -1,12 +1,12 @@
<?php <?php
/** /**
* eGroupWare - Calendar's buisness-object - access + update * EGroupware - Calendar's buisness-object - access + update
* *
* @link http://www.egroupware.org * @link http://www.egroupware.org
* @package calendar * @package calendar
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de> * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @author Joerg Lehrke <jlehrke@noc.de> * @author Joerg Lehrke <jlehrke@noc.de>
* @copyright (c) 2005-10 by RalfBecker-At-outdoor-training.de * @copyright (c) 2005-11 by RalfBecker-At-outdoor-training.de
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @version $Id$ * @version $Id$
*/ */

View File

@ -1,13 +1,13 @@
<?php <?php
/** /**
* eGroupWare: GroupDAV access: calendar handler * EGroupware: CalDAV / GroupDAV access: calendar handler
* *
* @link http://www.egroupware.org * @link http://www.egroupware.org
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package calendar * @package calendar
* @subpackage groupdav * @subpackage groupdav
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de> * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @copyright (c) 2007-9 by Ralf Becker <RalfBecker-AT-outdoor-training.de> * @copyright (c) 2007-11 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @version $Id$ * @version $Id$
*/ */
@ -57,9 +57,11 @@ class calendar_groupdav extends groupdav_handler
var $client_shared_uid_exceptions = true; var $client_shared_uid_exceptions = true;
/** /**
* Are we using id or uid for the path/url * Are we using id, uid or caldav_name for the path/url
*
* Get's set in constructor to 'caldav_name' and groupdav_handler::$path_extension = ''!
*/ */
const PATH_ATTRIBUTE = 'id'; static $path_attr = 'id';
/** /**
* Constructor * Constructor
@ -75,6 +77,13 @@ class calendar_groupdav extends groupdav_handler
$this->bo = new calendar_boupdate(); $this->bo = new calendar_boupdate();
$this->vCalendar = new Horde_iCalendar; $this->vCalendar = new Horde_iCalendar;
// since 1.9.003 we allow clients to specify the URL when creating a new event, as specified by CalDAV
if (version_compare($GLOBALS['egw_info']['apps']['calendar']['version'], '1.9.003', '>='))
{
self::$path_attr = 'caldav_name';
groupdav_handler::$path_extension = '';
}
} }
/** /**
@ -85,16 +94,18 @@ class calendar_groupdav extends groupdav_handler
*/ */
function get_path($event) function get_path($event)
{ {
if (is_numeric($event) && self::PATH_ATTRIBUTE == 'id') if (is_numeric($event) && self::$path_attr == 'id')
{ {
$name = $event; $name = $event;
} }
else else
{ {
if (!is_array($event)) $event = $this->bo->read($event); if (!is_array($event)) $event = $this->bo->read($event);
$name = $event[self::PATH_ATTRIBUTE]; $name = $event[self::$path_attr];
} }
return $name.'.ics'; $name .= groupdav_handler::$path_extension;
//error_log(__METHOD__.'('.array2string($event).") path_attr='".self::$path_attr."', path_extension='".groupdav_handler::$path_extension."' returning ".array2string($name));
return $name;
} }
/** /**
@ -332,15 +343,8 @@ class calendar_groupdav extends groupdav_handler
if ($id) if ($id)
{ {
if (is_numeric($id)) $cal_filters['query'][self::$path_attr] = groupdav_handler::$path_extension ?
{ basename($id,groupdav_handler::$path_extension) : $id;
$ids[] = (int)$id;
}
else
{
$cal_filters['query']['cal_uid'] = basename($id,'.ics');
}
} }
else // fetch all given url's else // fetch all given url's
{ {
@ -349,23 +353,14 @@ class calendar_groupdav extends groupdav_handler
if ($option['name'] == 'href') if ($option['name'] == 'href')
{ {
$parts = explode('/',$option['data']); $parts = explode('/',$option['data']);
if (!($id = basename(array_pop($parts),'.ics'))) continue; if (($id = array_pop($parts)))
if (is_numeric($id))
{ {
$ids[] = $id; $cal_filters['query'][self::$path_attr][] = groupdav_handler::$path_extension ?
} basename($id,groupdav_handler::$path_extension) : $id;
else // eg. lightning uses multiget after a PUT on the PUT url, which is the uid
{
$cal_filters['query']['cal_uid'][] = $id;
} }
} }
} }
} }
if ($ids)
{
$cal_filters['query'][] = 'egw_cal.cal_id IN ('.implode(',',array_map(create_function('$n','return (int)$n;'),$ids)).')';
}
if ($this->debug > 1) error_log(__FILE__ . __METHOD__ ."($options[path],...,$id) calendar-multiget: ids=".implode(',',$ids).', cal_filters='.array2string($cal_filters)); if ($this->debug > 1) error_log(__FILE__ . __METHOD__ ."($options[path],...,$id) calendar-multiget: ids=".implode(',',$ids).', cal_filters='.array2string($cal_filters));
} }
@ -573,32 +568,13 @@ class calendar_groupdav extends groupdav_handler
} }
else else
{ {
// new entry? // new entry
if (($foundEvents = $handler->search($vCalendar, null, false, $charset))) $eventId = -1;
{ $retval = '201 Created';
if (($eventId = array_shift($foundEvents)) &&
(list($eventId) = explode(':', $eventId)) &&
($oldEvent = $this->bo->read($eventId)))
{
$retval = '301 Moved Permanently';
}
else
{
// to be safe
$eventId = -1;
$retval = '201 Created';
}
}
else
{
// new entry
$eventId = -1;
$retval = '201 Created';
}
} }
if (!($cal_id = $handler->importVCal($vCalendar, $eventId, if (!($cal_id = $handler->importVCal($vCalendar, $eventId,
self::etag2value($this->http_if_match), false, 0, $this->principalURL, $user, $charset))) self::etag2value($this->http_if_match), false, 0, $this->principalURL, $user, $charset, $id)))
{ {
if ($this->debug) error_log(__METHOD__."(,$id) eventId=$eventId: importVCal('$options[content]') returned false"); if ($this->debug) error_log(__METHOD__."(,$id) eventId=$eventId: importVCal('$options[content]') returned false");
if ($eventId && $cal_id === false) if ($eventId && $cal_id === false)
@ -614,14 +590,15 @@ class calendar_groupdav extends groupdav_handler
} }
header('ETag: '.$this->get_etag($cal_id)); header('ETag: '.$this->get_etag($cal_id));
if ($retval !== true)
// send GroupDAV Location header only if we dont use caldav_name as path-attribute
if ($retval !== true && self::$path_attr != 'caldav_name')
{ {
$path = preg_replace('|(.*)/[^/]*|', '\1/', $options['path']); $path = preg_replace('|(.*)/[^/]*|', '\1/', $options['path']);
if ($this->debug) error_log(__METHOD__."(,$id,$user) cal_id=$cal_id: $retval"); if ($this->debug) error_log(__METHOD__."(,$id,$user) cal_id=$cal_id: $retval");
header('Location: '.$this->base_uri.$path.$this->get_path($cal_id)); header('Location: '.$this->base_uri.$path.$this->get_path($cal_id));
return $retval;
} }
return true; return $retval;
} }
/** /**
@ -773,18 +750,22 @@ class calendar_groupdav extends groupdav_handler
} }
return $event; return $event;
} }
return $this->bo->delete($id); return $this->bo->delete($event['id']);
} }
/** /**
* Read an entry * Read an entry
* *
* @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 * @return array|boolean array with entry, false if no read rights, null if $id does not exist
*/ */
function read($id) function read($id)
{ {
$event = $this->bo->read($id, null, true, 'server'); if (strpos($column=self::$path_attr,'_') === false) $column = 'cal_'.$column;
$event = $this->bo->read(array($column => $id), null, true, 'server');
if ($event) $event = array_shift($event); // read with array as 1. param, returns an array of events!
if (!($retval = $this->bo->check_perms(EGW_ACL_FREEBUSY,$event, 0, 'server'))) if (!($retval = $this->bo->check_perms(EGW_ACL_FREEBUSY,$event, 0, 'server')))
{ {
if ($this->debug > 0) error_log(__METHOD__."($id) no READ or FREEBUSY rights returning ".array2string($retval)); if ($this->debug > 0) error_log(__METHOD__."($id) no READ or FREEBUSY rights returning ".array2string($retval));

View File

@ -1,6 +1,6 @@
<?php <?php
/** /**
* iCal import and export via Horde iCalendar classes * EGroupware - Calendar iCal import and export via Horde iCalendar classes
* *
* @link http://www.egroupware.org * @link http://www.egroupware.org
* @author Lars Kneschke <lkneschke@egroupware.org> * @author Lars Kneschke <lkneschke@egroupware.org>
@ -1078,10 +1078,12 @@ class calendar_ical extends calendar_boupdate
* @param int $user=null account_id of owner, default null * @param int $user=null account_id of owner, default null
* @param string $charset The encoding charset for $text. Defaults to * @param string $charset The encoding charset for $text. Defaults to
* utf-8 for new format, iso-8859-1 for old format. * utf-8 for new format, iso-8859-1 for old format.
* @param string $caldav_name=null name from CalDAV client or null (to use default)
* @return int|boolean cal_id > 0 on success, false on failure or 0 for a failed etag|permission denied * @return int|boolean cal_id > 0 on success, false on failure or 0 for a failed etag|permission denied
*/ */
function importVCal($_vcalData, $cal_id=-1, $etag=null, $merge=false, $recur_date=0, $principalURL='', $user=null, $charset=null) function importVCal($_vcalData, $cal_id=-1, $etag=null, $merge=false, $recur_date=0, $principalURL='', $user=null, $charset=null, $caldav_name=null)
{ {
//error_log(__METHOD__."(, $cal_id, $etag, $merge, $recur_date, $principalURL, $user, $charset, $caldav_name)");
$this->events_imported = 0; $this->events_imported = 0;
$replace = $delete_exceptions= false; $replace = $delete_exceptions= false;
@ -1336,10 +1338,12 @@ class calendar_ical extends calendar_boupdate
// avoid that iCal changes the organizer, which is not allowed // avoid that iCal changes the organizer, which is not allowed
$event['owner'] = $event_info['stored_event']['owner']; $event['owner'] = $event_info['stored_event']['owner'];
} }
$event['caldav_name'] = $event_info['stored_event']['caldav_name'];
} }
else // common adjustments for new events else // common adjustments for new events
{ {
unset($event['id']); unset($event['id']);
if ($caldav_name) $event['caldav_name'] = $caldav_name;
// set non blocking all day depending on the user setting // set non blocking all day depending on the user setting
if (!empty($event['whole_day']) && $this->nonBlockingAllday) if (!empty($event['whole_day']) && $this->nonBlockingAllday)
{ {

View File

@ -1,6 +1,6 @@
<?php <?php
/** /**
* eGroupWare - Calendar's storage-object * EGroupware - Calendar's storage-object
* *
* @link http://www.egroupware.org * @link http://www.egroupware.org
* @package calendar * @package calendar
@ -121,7 +121,7 @@ class calendar_so
* *
* @param int|array|string $ids id or array of id's of the entries to read, or string with a single uid * @param int|array|string $ids id or array of id's of the entries to read, or string with a single uid
* @param int $recur_date=0 if set read the next recurrence at or after the timestamp, default 0 = read the initital one * @param int $recur_date=0 if set read the next recurrence at or after the timestamp, default 0 = read the initital one
* @return array|boolean array with id => data pairs or false if entry not found * @return array|boolean array with cal_id => event array pairs or false if entry not found
*/ */
function read($ids,$recur_date=0) function read($ids,$recur_date=0)
{ {
@ -142,23 +142,28 @@ class calendar_so
$group_by_cols .= ','.$this->repeats_table.'.'.implode(','.$this->repeats_table.'.',array_keys($table_def['fd'])); $group_by_cols .= ','.$this->repeats_table.'.'.implode(','.$this->repeats_table.'.',array_keys($table_def['fd']));
$where = array(); $where = array();
if (is_array($ids)) if (is_scalar($ids) && !is_numeric($ids)) // a single uid
{
array_walk($ids,create_function('&$val,$key','$val = (int) $val;'));
$where[] = $this->cal_table.'.cal_id IN ('.implode(',',$ids).')';
}
elseif (is_numeric($ids))
{
$where[] = $this->cal_table.'.cal_id = '.(int) $ids;
}
else
{ {
// We want only the parents to match // We want only the parents to match
$where['cal_uid'] = $ids; $where['cal_uid'] = $ids;
$where['cal_reference'] = 0; $where['cal_reference'] = 0;
$where['cal_recurrence'] = 0; $where['cal_recurrence'] = 0;
} }
elseif(is_array($ids) && isset($ids[count($ids)-1]) || is_scalar($ids)) // one or more cal_id's
{
$where['cal_id'] = $ids;
}
else // array with column => value pairs
{
$where = $ids;
}
if (isset($where['cal_id'])) // prevent non-unique column-name cal_id
{
$where[] = $this->db->expression($this->cal_table, $this->cal_table.'.',array(
'cal_id' => $where['cal_id'],
));
unset($where['cal_id']);
}
if ((int) $recur_date) if ((int) $recur_date)
{ {
$where[] = 'cal_start >= '.(int)$recur_date; $where[] = 'cal_start >= '.(int)$recur_date;
@ -920,7 +925,7 @@ ORDER BY cal_user_type, cal_usre_id
// add colum prefix 'cal_' if there's not already a 'recur_' prefix // add colum prefix 'cal_' if there's not already a 'recur_' prefix
foreach($event as $col => $val) foreach($event as $col => $val)
{ {
if ($col[0] != '#' && substr($col,0,6) != 'recur_' && $col != 'alarm' && $col != 'tz_id') if ($col[0] != '#' && substr($col,0,6) != 'recur_' && $col != 'alarm' && $col != 'tz_id' && $col != 'caldav_name')
{ {
$event['cal_'.$col] = $val; $event['cal_'.$col] = $val;
unset($event[$col]); unset($event[$col]);
@ -975,12 +980,20 @@ ORDER BY cal_user_type, cal_usre_id
} }
$etag = 0; $etag = 0;
} }
$update = array();
// event without uid or not strong enough uid
if (!isset($event['cal_uid']) || strlen($event['cal_uid']) < $minimum_uid_length) if (!isset($event['cal_uid']) || strlen($event['cal_uid']) < $minimum_uid_length)
{ {
// event (without uid), not strong enough uid $update['cal_uid'] = $event['cal_uid'] = $GLOBALS['egw']->common->generate_uid('calendar',$cal_id);
$event['cal_uid'] = $GLOBALS['egw']->common->generate_uid('calendar',$cal_id); }
$this->db->update($this->cal_table, array('cal_uid' => $event['cal_uid']), // set caldav_name, if not given by caller
array('cal_id' => $cal_id),__LINE__,__FILE__,'calendar'); if (empty($event['caldav_name']) && version_compare($GLOBALS['egw_info']['apps']['calendar']['version'], '1.9.003', '>='))
{
$update['caldav_name'] = $event['caldav_name'] = $cal_id.'.ics';
}
if ($update)
{
$this->db->update($this->cal_table, $update, array('cal_id' => $cal_id),__LINE__,__FILE__,'calendar');
} }
if ($event['recur_type'] == MCAL_RECUR_NONE) if ($event['recur_type'] == MCAL_RECUR_NONE)

View File

@ -10,7 +10,7 @@
*/ */
$setup_info['calendar']['name'] = 'calendar'; $setup_info['calendar']['name'] = 'calendar';
$setup_info['calendar']['version'] = '1.9.002'; $setup_info['calendar']['version'] = '1.9.003';
$setup_info['calendar']['app_order'] = 3; $setup_info['calendar']['app_order'] = 3;
$setup_info['calendar']['enable'] = 1; $setup_info['calendar']['enable'] = 1;
$setup_info['calendar']['index'] = 'calendar.calendar_uiviews.index'; $setup_info['calendar']['index'] = 'calendar.calendar_uiviews.index';
@ -66,6 +66,3 @@ $setup_info['calendar']['check_install'] = array(
'from' => 'Calendar', 'from' => 'Calendar',
), ),
); );

View File

@ -31,11 +31,12 @@ $phpgw_baseline = array(
'cal_created' => array('type' => 'int','precision' => '8','nullable' => False,'comment' => 'creation time of event'), 'cal_created' => array('type' => 'int','precision' => '8','nullable' => False,'comment' => 'creation time of event'),
'cal_recurrence' => array('type' => 'int','precision' => '8','nullable' => False,'default' => '0','comment' => 'cal_start of original recurrence for exception'), 'cal_recurrence' => array('type' => 'int','precision' => '8','nullable' => False,'default' => '0','comment' => 'cal_start of original recurrence for exception'),
'tz_id' => array('type' => 'int','precision' => '4','comment' => 'key into egw_cal_timezones'), 'tz_id' => array('type' => 'int','precision' => '4','comment' => 'key into egw_cal_timezones'),
'cal_deleted' => array('type' => 'int','precision' => '8','comment' => 'ts when event was deleted') 'cal_deleted' => array('type' => 'int','precision' => '8','comment' => 'ts when event was deleted'),
'caldav_name' => array('type' => 'varchar','precision' => '64','comment' => 'name part of CalDAV URL, if specified by client')
), ),
'pk' => array('cal_id'), 'pk' => array('cal_id'),
'fk' => array(), 'fk' => array(),
'ix' => array('cal_uid','cal_owner','cal_deleted'), 'ix' => array('cal_uid','cal_owner','cal_deleted','caldav_name'),
'uc' => array() 'uc' => array()
), ),
'egw_cal_holidays' => array( 'egw_cal_holidays' => array(

View File

@ -2152,3 +2152,23 @@ function calendar_upgrade1_9_001()
return $GLOBALS['setup_info']['calendar']['currentver'] = '1.9.002'; return $GLOBALS['setup_info']['calendar']['currentver'] = '1.9.002';
} }
/**
* Add column to store CalDAV name given by client
*/
function calendar_upgrade1_9_002()
{
$GLOBALS['egw_setup']->oProc->AddColumn('egw_cal','caldav_name',array(
'type' => 'varchar',
'precision' => '64',
'comment' => 'name part of CalDAV URL, if specified by client'
));
$GLOBALS['egw_setup']->db->query($sql='UPDATE egw_cal SET caldav_name='.
$GLOBALS['egw_setup']->db->concat(
$GLOBALS['egw_setup']->db->to_varchar('cal_id'),"'.ics'"),__LINE__,__FILE__);
$GLOBALS['egw_setup']->oProc->CreateIndex('egw_cal','caldav_name');
return $GLOBALS['setup_info']['calendar']['currentver'] = '1.9.003';
}