* 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
/**
* eGroupWare - Calendar's buisness-object - access only
* EGroupware - Calendar's buisness-object - access only
*
* @link http://www.egroupware.org
* @package calendar
* @author Ralf Becker <RalfBecker-AT-outdoor-training.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
* @version $Id$
*/

View File

@ -1,12 +1,12 @@
<?php
/**
* eGroupWare - Calendar's buisness-object - access + update
* EGroupware - Calendar's buisness-object - access + update
*
* @link http://www.egroupware.org
* @package calendar
* @author Ralf Becker <RalfBecker-AT-outdoor-training.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
* @version $Id$
*/

View File

@ -1,13 +1,13 @@
<?php
/**
* eGroupWare: GroupDAV access: calendar handler
* EGroupware: CalDAV / GroupDAV access: calendar handler
*
* @link http://www.egroupware.org
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package calendar
* @subpackage groupdav
* @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$
*/
@ -57,9 +57,11 @@ class calendar_groupdav extends groupdav_handler
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
@ -75,6 +77,13 @@ class calendar_groupdav extends groupdav_handler
$this->bo = new calendar_boupdate();
$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)
{
if (is_numeric($event) && self::PATH_ATTRIBUTE == 'id')
if (is_numeric($event) && self::$path_attr == 'id')
{
$name = $event;
}
else
{
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 (is_numeric($id))
{
$ids[] = (int)$id;
}
else
{
$cal_filters['query']['cal_uid'] = basename($id,'.ics');
}
$cal_filters['query'][self::$path_attr] = groupdav_handler::$path_extension ?
basename($id,groupdav_handler::$path_extension) : $id;
}
else // fetch all given url's
{
@ -349,23 +353,14 @@ class calendar_groupdav extends groupdav_handler
if ($option['name'] == 'href')
{
$parts = explode('/',$option['data']);
if (!($id = basename(array_pop($parts),'.ics'))) continue;
if (is_numeric($id))
if (($id = array_pop($parts)))
{
$ids[] = $id;
}
else // eg. lightning uses multiget after a PUT on the PUT url, which is the uid
{
$cal_filters['query']['cal_uid'][] = $id;
$cal_filters['query'][self::$path_attr][] = groupdav_handler::$path_extension ?
basename($id,groupdav_handler::$path_extension) : $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));
}
@ -573,32 +568,13 @@ class calendar_groupdav extends groupdav_handler
}
else
{
// new entry?
if (($foundEvents = $handler->search($vCalendar, null, false, $charset)))
{
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';
}
// new entry
$eventId = -1;
$retval = '201 Created';
}
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 ($eventId && $cal_id === false)
@ -614,14 +590,15 @@ class calendar_groupdav extends groupdav_handler
}
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']);
if ($this->debug) error_log(__METHOD__."(,$id,$user) cal_id=$cal_id: $retval");
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 $this->bo->delete($id);
return $this->bo->delete($event['id']);
}
/**
* Read an entry
*
* @param string/id $id
* @return array/boolean array with entry, false if no read rights, null if $id does not exist
* @param string|id $id
* @return array|boolean array with entry, false if no read rights, null if $id does not exist
*/
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 ($this->debug > 0) error_log(__METHOD__."($id) no READ or FREEBUSY rights returning ".array2string($retval));

View File

@ -1,6 +1,6 @@
<?php
/**
* iCal import and export via Horde iCalendar classes
* EGroupware - Calendar iCal import and export via Horde iCalendar classes
*
* @link http://www.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 string $charset The encoding charset for $text. Defaults to
* 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
*/
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;
$replace = $delete_exceptions= false;
@ -1336,10 +1338,12 @@ class calendar_ical extends calendar_boupdate
// avoid that iCal changes the organizer, which is not allowed
$event['owner'] = $event_info['stored_event']['owner'];
}
$event['caldav_name'] = $event_info['stored_event']['caldav_name'];
}
else // common adjustments for new events
{
unset($event['id']);
if ($caldav_name) $event['caldav_name'] = $caldav_name;
// set non blocking all day depending on the user setting
if (!empty($event['whole_day']) && $this->nonBlockingAllday)
{

View File

@ -1,6 +1,6 @@
<?php
/**
* eGroupWare - Calendar's storage-object
* EGroupware - Calendar's storage-object
*
* @link http://www.egroupware.org
* @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 $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)
{
@ -142,23 +142,28 @@ class calendar_so
$group_by_cols .= ','.$this->repeats_table.'.'.implode(','.$this->repeats_table.'.',array_keys($table_def['fd']));
$where = array();
if (is_array($ids))
{
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
if (is_scalar($ids) && !is_numeric($ids)) // a single uid
{
// We want only the parents to match
$where['cal_uid'] = $ids;
$where['cal_reference'] = 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)
{
$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
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;
unset($event[$col]);
@ -975,12 +980,20 @@ ORDER BY cal_user_type, cal_usre_id
}
$etag = 0;
}
$update = array();
// event without uid or not strong enough uid
if (!isset($event['cal_uid']) || strlen($event['cal_uid']) < $minimum_uid_length)
{
// event (without uid), not strong enough uid
$event['cal_uid'] = $GLOBALS['egw']->common->generate_uid('calendar',$cal_id);
$this->db->update($this->cal_table, array('cal_uid' => $event['cal_uid']),
array('cal_id' => $cal_id),__LINE__,__FILE__,'calendar');
$update['cal_uid'] = $event['cal_uid'] = $GLOBALS['egw']->common->generate_uid('calendar',$cal_id);
}
// set caldav_name, if not given by caller
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)

View File

@ -10,7 +10,7 @@
*/
$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']['enable'] = 1;
$setup_info['calendar']['index'] = 'calendar.calendar_uiviews.index';
@ -66,6 +66,3 @@ $setup_info['calendar']['check_install'] = array(
'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_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'),
'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'),
'fk' => array(),
'ix' => array('cal_uid','cal_owner','cal_deleted'),
'ix' => array('cal_uid','cal_owner','cal_deleted','caldav_name'),
'uc' => array()
),
'egw_cal_holidays' => array(

View File

@ -2094,7 +2094,7 @@ function calendar_upgrade1_7_011()
{
return $GLOBALS['setup_info']['calendar']['currentver'] = '1.9.001';
}
function calendar_upgrade1_8()
{
calendar_upgrade1_7_010();
@ -2104,7 +2104,7 @@ function calendar_upgrade1_8()
/**
* Convert bool column cal_deleted with egw_api_content_history table to a unix timestamp
*
*
* @return string
*/
function calendar_upgrade1_9_001()
@ -2152,3 +2152,23 @@ function calendar_upgrade1_9_001()
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';
}