From 0bfd238e3f31a073ca3903243a5a331bc4d0f548 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Wed, 6 Apr 2011 19:26:10 +0000 Subject: [PATCH] * CalDAV: store name part of URL from client PUT request, to fully comply with CalDAV spec --- calendar/inc/class.calendar_bo.inc.php | 4 +- calendar/inc/class.calendar_boupdate.inc.php | 4 +- calendar/inc/class.calendar_groupdav.inc.php | 97 ++++++++------------ calendar/inc/class.calendar_ical.inc.php | 8 +- calendar/inc/class.calendar_so.inc.php | 49 ++++++---- calendar/setup/setup.inc.php | 5 +- calendar/setup/tables_current.inc.php | 5 +- calendar/setup/tables_update.inc.php | 24 ++++- 8 files changed, 106 insertions(+), 90 deletions(-) diff --git a/calendar/inc/class.calendar_bo.inc.php b/calendar/inc/class.calendar_bo.inc.php index b5cd02c023..e35763b409 100644 --- a/calendar/inc/class.calendar_bo.inc.php +++ b/calendar/inc/class.calendar_bo.inc.php @@ -1,12 +1,12 @@ * @author Joerg Lehrke - * @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$ */ diff --git a/calendar/inc/class.calendar_boupdate.inc.php b/calendar/inc/class.calendar_boupdate.inc.php index c081a1126f..a069d190bc 100644 --- a/calendar/inc/class.calendar_boupdate.inc.php +++ b/calendar/inc/class.calendar_boupdate.inc.php @@ -1,12 +1,12 @@ * @author Joerg Lehrke - * @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$ */ diff --git a/calendar/inc/class.calendar_groupdav.inc.php b/calendar/inc/class.calendar_groupdav.inc.php index 0c20ccfe7a..adb0c85c68 100644 --- a/calendar/inc/class.calendar_groupdav.inc.php +++ b/calendar/inc/class.calendar_groupdav.inc.php @@ -1,13 +1,13 @@ - * @copyright (c) 2007-9 by Ralf Becker + * @copyright (c) 2007-11 by Ralf Becker * @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)); diff --git a/calendar/inc/class.calendar_ical.inc.php b/calendar/inc/class.calendar_ical.inc.php index 7c3ebc0b95..ba301f1cb4 100644 --- a/calendar/inc/class.calendar_ical.inc.php +++ b/calendar/inc/class.calendar_ical.inc.php @@ -1,6 +1,6 @@ @@ -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) { diff --git a/calendar/inc/class.calendar_so.inc.php b/calendar/inc/class.calendar_so.inc.php index 530be1614e..30429ebf0f 100644 --- a/calendar/inc/class.calendar_so.inc.php +++ b/calendar/inc/class.calendar_so.inc.php @@ -1,6 +1,6 @@ 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) diff --git a/calendar/setup/setup.inc.php b/calendar/setup/setup.inc.php index a0d0d288d0..0507c949ac 100755 --- a/calendar/setup/setup.inc.php +++ b/calendar/setup/setup.inc.php @@ -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', ), ); - - - diff --git a/calendar/setup/tables_current.inc.php b/calendar/setup/tables_current.inc.php index 5f42fe0588..8bf472d78d 100644 --- a/calendar/setup/tables_current.inc.php +++ b/calendar/setup/tables_current.inc.php @@ -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( diff --git a/calendar/setup/tables_update.inc.php b/calendar/setup/tables_update.inc.php index 89d4233786..101f1a3977 100644 --- a/calendar/setup/tables_update.inc.php +++ b/calendar/setup/tables_update.inc.php @@ -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'; +} +