Merge branch 'dev/recurrence-exceptions'
This commit is contained in:
commit
f320a772b0
8 changed files with 186 additions and 62 deletions
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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,26 +418,67 @@ 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);
|
||||
}
|
||||
else {
|
||||
// fallback to local recurrence implementation
|
||||
require_once($this->cal->home . '/lib/calendar_recurrence.php');
|
||||
$recurrence = new calendar_recurrence($this->cal, $event);
|
||||
// fallback to local recurrence implementation
|
||||
require_once($this->cal->home . '/lib/calendar_recurrence.php');
|
||||
$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
|
||||
*/
|
||||
|
|
|
@ -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,56 +591,27 @@ 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);
|
||||
break;
|
||||
// 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'];
|
||||
$event['uid'] = $master['uid'];
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue