Merge branch 'dev/recurrence-exceptions'

This commit is contained in:
Thomas Bruederli 2013-02-28 08:25:52 +01:00
commit f320a772b0
8 changed files with 186 additions and 62 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

@ -507,8 +507,9 @@ function rcube_calendar_ui(settings)
// show warning if editing a recurring event
if (event.id && event.recurrence) {
var sel = event.thisandfuture ? 'future' : 'all';
$('#edit-recurring-warning').show();
$('input.edit-recurring-savemode[value="all"]').prop('checked', true);
$('input.edit-recurring-savemode[value="'+sel+'"]').prop('checked', true);
}
else
$('#edit-recurring-warning').hide();

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,39 @@ class kolab_calendar
$end->add(new DateInterval($intvl));
}
// add recurrence exceptions to output
$i = 0;
$events = array();
$exdates = array();
$futuredata = array();
if (is_array($event['recurrence']['EXCEPTIONS'])) {
// copy the recurrence rule from the master event (to be used in the UI)
$recurrence_rule = $event['recurrence'];
unset($recurrence_rule['EXCEPTIONS'], $recurrence_rule['EXDATE']);
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['recurrence'] = $recurrence_rule;
$rec_event['_instance'] = $i;
$events[] = $rec_event;
// found the specifically requested instance, exiting...
if ($rec_event['id'] == $event_id) {
$this->events[$rec_event['id']] = $rec_event;
return $events;
}
// remember this exception's date
$exdate = $rec_event['start']->format('Y-m-d');
$exdates[$exdate] = $rec_event['id'];
if ($rec_event['thisandfuture']) {
$futuredata[$exdate] = $rec_event;
}
}
}
// use libkolab to compute recurring events
if (class_exists('kolabcalendaring')) {
$recurrence = new kolab_date_recurrence($object);
@ -423,16 +461,24 @@ 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');
$rec_id = $event['uid'] . '-' . ++$i;
// skip if there's an exception at this date
$datestr = $next_event['start']->format('Y-m-d');
if ($exdates[$datestr]) {
// use this event data for future recurring instances
if ($futuredata[$datestr])
$overlay_data = $futuredata[$datestr];
continue;
}
// add to output if in range
$rec_id = $event['uid'] . '-' . ++$i;
if (($next_event['start'] <= $end && $next_event['end'] >= $start) || ($event_id && $rec_id == $event_id)) {
$rec_event = $this->_to_rcube_event($next_event);
if ($overlay_data) // copy data from a 'this-and-future' exception
$this->_merge_event_data($rec_event, $overlay_data);
$rec_event['id'] = $rec_id;
$rec_event['recurrence_id'] = $event['uid'];
$rec_event['_instance'] = $i;
@ -455,6 +501,27 @@ class kolab_calendar
return $events;
}
/**
* Merge certain properties from the overlay event to the base event object
*
* @param array The event object to be altered
* @param array The overlay event object to be merged over $event
*/
private function _merge_event_data(&$event, $overlay)
{
static $forbidden = array('id','uid','created','changed','recurrence','organizer','attendees','sequence');
foreach ($overlay as $prop => $value) {
// adjust time of the recurring event instance
if ($prop == 'start' || $prop == 'end') {
if (is_object($event[$prop]) && is_a($event[$prop], 'DateTime'))
$event[$prop]->setTime($value->format('G'), intval($value->format('i')), intval($value->format('s')));
}
else if ($prop[0] != '_' && !in_array($prop, $forbidden))
$event[$prop] = $value;
}
}
/**
* Convert from Kolab_Format to internal representation
*/

View file

@ -421,6 +421,14 @@ class kolab_driver extends calendar_driver
$savemode = $event['_savemode'];
}
// removing an exception instance
if ($event['recurrence_id']) {
$i = $event['_instance'] - 1;
if (!empty($master['recurrence']['EXCEPTIONS'][$i])) {
unset($master['recurrence']['EXCEPTIONS'][$i]);
}
}
switch ($savemode) {
case 'current':
$_SESSION['calendar_restore_event_data'] = $master;
@ -565,6 +573,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':
@ -581,55 +591,26 @@ class kolab_driver extends calendar_driver
$success = $storage->insert_event($event);
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;
$event['recurrence'] = array();
unset($event['recurrence_id']);
$event['uid'] = $this->cal->generate_uid();
$success = $storage->insert_event($event);
break;
case 'future':
if ($master['id'] != $event['id']) {
// set until-date on master event
$master['recurrence']['UNTIL'] = clone $old['start'];
$master['recurrence']['UNTIL']->sub(new DateInterval('P1D'));
unset($master['recurrence']['COUNT']);
$storage->update_event($master);
case 'current':
// recurring instances shall not store recurrence rules
$event['recurrence'] = array();
$event['thisandfuture'] = $savemode == 'future';
// save this instance as new recurring event
$event += $old;
$event['uid'] = $this->cal->generate_uid();
// if recurrence COUNT, update value to the correct number of future occurences
if ($event['recurrence']['COUNT']) {
$event['recurrence']['COUNT'] -= $old['_instance'];
}
// remove fixed weekday, will be re-set to the new weekday in kolab_calendar::insert_event()
if (strlen($event['recurrence']['BYDAY']) == 2)
unset($event['recurrence']['BYDAY']);
if ($master['recurrence']['BYMONTH'] == $master['start']->format('n'))
unset($event['recurrence']['BYMONTH']);
$success = $storage->insert_event($event);
// 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;
}
}
// save as new exception to master event
$master['recurrence']['EXCEPTIONS'][] = $event;
$success = $storage->update_event($master);
break;
default: // 'all' is default
$event['id'] = $master['id'];

View file

@ -109,6 +109,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
@ -95,6 +108,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), (bool)$exception['thisandfuture']);
$vexceptions->push($exevent->obj);
}
$this->obj->setExceptions($vexceptions);
}
// cache this data
$this->data = $object;
unset($this->data['_formatobj']);
@ -166,6 +191,22 @@ class kolab_format_event extends kolab_format_xcal
}
}
// read exception event objects
if (($exceptions = $this->obj->exceptions()) && $exceptions->size()) {
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);
}
}
}
}
// this is an exception object
else if ($this->obj->recurrenceID()->isValid()) {
$object['thisandfuture'] = $this->obj->thisAndFuture();
}
// merge with additional data, e.g. attachments from the message
if ($data) {
foreach ($data as $idx => $value) {
@ -201,4 +242,34 @@ class kolab_format_event extends kolab_format_xcal
return $tags;
}
/**
* Remove some attributes from the exception container
*/
private function compact_exception($exception, $master)
{
static $forbidden = array('recurrence','organizer','attendees','sequence');
$out = $exception;
foreach ($exception as $prop => $val) {
if (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)
{
foreach ($master as $prop => $value) {
if (empty($exception[$prop]) && !empty($value))
$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;
}
}