Save changes in a recurring event as exception to the master event

This commit is contained in:
Thomas Bruederli 2013-01-23 14:45:41 +01:00
parent 53c77796dd
commit 91779df09a
7 changed files with 128 additions and 27 deletions

View file

@ -1096,6 +1096,7 @@ class calendar extends rcube_plugin
$event['recurrence_text'] = $this->_recurrence_text($event['recurrence']);
if ($event['recurrence']['UNTIL'])
$event['recurrence']['UNTIL'] = $this->lib->adjust_timezone($event['recurrence']['UNTIL'])->format('c');
unset($event['recurrence']['EXCEPTIONS']);
}
foreach ((array)$event['attachments'] as $k => $attachment) {
@ -1109,7 +1110,7 @@ class calendar extends rcube_plugin
'title' => strval($event['title']),
'description' => strval($event['description']),
'location' => strval($event['location']),
'className' => ($addcss ? 'fc-event-cal-'.asciiwords($event['calendar'], true).' ' : '') . 'fc-event-cat-' . asciiwords(strtolower($event['categories']), true),
'className' => ($addcss ? 'fc-event-cal-'.asciiwords($event['calendar'], true).' ' : '') . 'fc-event-cat-' . asciiwords(strtolower(join('-', (array)$event['categories'])), true),
'allDay' => ($event['allday'] == 1),
) + $event;
}

View file

@ -46,6 +46,7 @@
* 'COUNT' => 1..n, // number of times
* // + more properties (see http://www.kanzaki.com/docs/ical/recur.html)
* 'EXDATE' => array(), // list of DateTime objects of exception Dates/Times
* 'EXCEPTIONS' => array(<event>), list of event objects which denote exceptions in the recurrence chain
* ),
* 'recurrence_id' => 'ID of the recurrence group', // usually the ID of the starting event
* 'categories' => 'Event category',

View file

@ -312,7 +312,7 @@ class kolab_calendar
* @return boolean True on success, False on error
*/
public function update_event($event)
public function update_event($event, $exception_id = null)
{
$updated = false;
$old = $this->storage->get_object($event['id']);
@ -333,6 +333,11 @@ class kolab_calendar
else {
$updated = true;
$this->events[$event['id']] = $this->_to_rcube_event($object);
// refresh local cache with recurring instances
if ($exception_id) {
$this->_get_recurring_events($object, $event['start'], $event['end'], $exception_id);
}
}
return $updated;
@ -413,6 +418,24 @@ class kolab_calendar
$end->add(new DateInterval($intvl));
}
// add recurrence exceptions to output
$i = 0;
$events = array();
if (is_array($event['recurrence']['EXCEPTIONS'])) {
foreach ($event['recurrence']['EXCEPTIONS'] as $exception) {
$rec_event = $this->_to_rcube_event($exception);
$rec_event['id'] = $event['uid'] . '-' . ++$i;
$rec_event['recurrence_id'] = $event['uid'];
$rec_event['_instance'] = $i;
$events[] = $rec_event;
if ($rec_event['id'] == $event_id) {
$this->events[$rec_event['id']] = $rec_event;
return $events;
}
}
}
// use libkolab to compute recurring events
if (class_exists('kolabcalendaring')) {
$recurrence = new kolab_date_recurrence($object);
@ -423,8 +446,6 @@ class kolab_calendar
$recurrence = new calendar_recurrence($this->cal, $event);
}
$i = 0;
$events = array();
while ($next_event = $recurrence->next_instance()) {
$rec_start = $next_event['start']->format('U');
$rec_end = $next_event['end']->format('U');

View file

@ -565,6 +565,8 @@ class kolab_driver extends calendar_driver
// keep saved exceptions (not submitted by the client)
if ($old['recurrence']['EXDATE'])
$event['recurrence']['EXDATE'] = $old['recurrence']['EXDATE'];
if ($old['recurrence']['EXCEPTIONS'])
$event['recurrence']['EXCEPTIONS'] = $old['recurrence']['EXCEPTIONS'];
switch ($savemode) {
case 'new':
@ -582,26 +584,11 @@ class kolab_driver extends calendar_driver
break;
case 'current':
// modifying the first instance => just move to next occurence
if ($master['id'] == $event['id']) {
$recurring = reset($storage->_get_recurring_events($event, $event['start'], null, $event['id'].'-1'));
$master['start'] = $recurring['start'];
$master['end'] = $recurring['end'];
if ($master['recurrence']['COUNT'])
$master['recurrence']['COUNT']--;
}
else { // add exception to master event
$master['recurrence']['EXDATE'][] = $old['start'];
}
$storage->update_event($master);
// insert new event for this occurence
$event += $old;
// save as exception to master event
$event['recurrence'] = array();
unset($event['recurrence_id']);
$event['uid'] = $this->cal->generate_uid();
$success = $storage->insert_event($event);
$master['recurrence']['EXCEPTIONS'][] = $event;
# $master['recurrence']['EXDATE'][] = $event['start'];
$success = $storage->update_event($master);
break;
case 'future':
@ -632,6 +619,17 @@ class kolab_driver extends calendar_driver
}
default: // 'all' is default
// save properties to a recurrence exception instance
if ($old['recurrence_id']) {
$i = $old['_instance'] - 1;
if (!empty($master['recurrence']['EXCEPTIONS'][$i])) {
$master['recurrence']['EXCEPTIONS'][$i] = $event;
$success = $storage->update_event($master, $old['id']);
break;
}
}
$event['id'] = $master['id'];
$event['uid'] = $master['uid'];

View file

@ -107,6 +107,8 @@ class libcalendaring extends rcube_plugin
$dt = new DateTime('@'.$td);
else if (is_string($dt))
$dt = new DateTime($dt);
else
return $dt;
$dt->setTimezone($this->timezone);
return $dt;

View file

@ -30,6 +30,19 @@ class kolab_format_event extends kolab_format_xcal
protected $read_func = 'readEvent';
protected $write_func = 'writeEvent';
/**
* Default constructor
*/
function __construct($data = null, $version = 3.0)
{
parent::__construct(is_string($data) ? $data : null, $version);
// got an Event object as argument
if (is_object($data) && is_a($data, $this->objclass)) {
$this->obj = $data;
$this->loaded = true;
}
}
/**
* Clones into an instance of libcalendaring's extended EventCal class
@ -90,6 +103,18 @@ class kolab_format_event extends kolab_format_xcal
}
$this->obj->setAttachments($vattach);
// save recurrence exceptions
if ($object['recurrence']['EXCEPTIONS']) {
$vexceptions = new vectorevent;
foreach((array)$object['recurrence']['EXCEPTIONS'] as $exception) {
$exevent = new kolab_format_event;
$exevent->set($this->compact_exception($exception, $object)); // only save differing values
$exevent->obj->setRecurrenceID(self::get_datetime($exception['start'], null, true), false);
$vexceptions->push($exevent->obj);
}
$this->obj->setExceptions($vexceptions);
}
// cache this data
$this->data = $object;
unset($this->data['_formatobj']);
@ -160,6 +185,18 @@ class kolab_format_event extends kolab_format_xcal
}
}
// read exception event objects
if ($exceptions = $this->obj->exceptions()) {
for ($i=0; $i < $exceptions->size(); $i++) {
if (($exobj = $exceptions->get($i))) {
$exception = new kolab_format_event($exobj);
if ($exception->is_valid()) {
$object['recurrence']['EXCEPTIONS'][] = $this->expand_exception($exception->to_array(), $object);
}
}
}
}
// merge with additional data, e.g. attachments from the message
if ($data) {
foreach ($data as $idx => $value) {
@ -195,4 +232,45 @@ class kolab_format_event extends kolab_format_xcal
return $tags;
}
/**
* Reduce the exception container to attributes which differ from the master event
*/
private function compact_exception($exception, $master)
{
static $mandatory = array('uid','created','start');
static $forbidden = array('recurrence','attendees','sequence');
$out = $exception;
foreach ($exception as $prop => $val) {
if (in_array($prop, $mandatory))
continue;
if (is_object($exception[$prop]) && is_a($exception[$prop], 'DateTime'))
$equals = $exception[$prop] <> $master[$prop];
else
$equals = $exception[$prop] == $master[$prop];
if ($equals || in_array($prop, $forbidden)) {
unset($out[$prop]);
}
}
return $out;
}
/**
* Copy attributes not specified by the exception from the master event
*/
private function expand_exception($exception, $master)
{
static $forbidden = array('recurrence');
foreach ($master as $prop => $value) {
if (empty($exception[$prop]) && !in_array($prop, $forbidden))
$exception[$prop] = $value;
}
return $exception;
}
}

View file

@ -113,7 +113,7 @@ abstract class kolab_format_xcal extends kolab_format
);
// read organizer and attendees
if ($organizer = $this->obj->organizer()) {
if (($organizer = $this->obj->organizer()) && ($organizer->email() || $organizer->name())) {
$object['organizer'] = array(
'email' => $organizer->email(),
'name' => $organizer->name(),
@ -170,9 +170,9 @@ abstract class kolab_format_xcal extends kolab_format
$object['recurrence']['BYMONTH'] = join(',', self::vector2array($bymonth));
}
if ($exceptions = $this->obj->exceptionDates()) {
for ($i=0; $i < $exceptions->size(); $i++) {
if ($exdate = self::php_datetime($exceptions->get($i)))
if ($exdates = $this->obj->exceptionDates()) {
for ($i=0; $i < $exdates->size(); $i++) {
if ($exdate = self::php_datetime($exdates->get($i)))
$object['recurrence']['EXDATE'][] = $exdate;
}
}