From 3bd6cf612aa29fde902bb6a88b18b548b3aaa14b Mon Sep 17 00:00:00 2001 From: "Michael J. Rubinsky" Date: Wed, 7 Jul 2010 18:53:29 -0400 Subject: [PATCH] More complete support for reccuring event exceptions when dealing with iCalendar files Bug: 9091 --- framework/iCalendar/iCalendar.php | 17 ++++++--- kronolith/lib/Api.php | 10 ++--- kronolith/lib/Event.php | 77 +++++++++++++++++++++++++++++++++++--- kronolith/lib/Kronolith.php | 1 + kronolith/lib/View/ExportEvent.php | 3 +- 5 files changed, 89 insertions(+), 19 deletions(-) diff --git a/framework/iCalendar/iCalendar.php b/framework/iCalendar/iCalendar.php index fbc8985c4..5147b8f88 100644 --- a/framework/iCalendar/iCalendar.php +++ b/framework/iCalendar/iCalendar.php @@ -315,13 +315,20 @@ class Horde_iCalendar { /** * Add a vCalendar component (eg vEvent, vTimezone, etc.). * - * @param Horde_iCalendar $component Component (subclass) to add. + * @param mixed Either a Horde_iCalendar component (subclass) or an array + * of them. */ - function addComponent($component) + function addComponent($components) { - if ($component instanceOf Horde_iCalendar) { - $component->_container = &$this; - $this->_components[] = &$component; + if (!is_array($components)) { + $components = array($components); + } + + foreach ($components as $component) { + if ($component instanceOf Horde_iCalendar) { + $component->_container = &$this; + $this->_components[] = $component; + } } } diff --git a/kronolith/lib/Api.php b/kronolith/lib/Api.php index 351cbb448..b5f858b17 100644 --- a/kronolith/lib/Api.php +++ b/kronolith/lib/Api.php @@ -488,7 +488,7 @@ class Kronolith_Api extends Horde_Registry_Api $calendar = Kronolith::getDefaultCalendar(); } - if ($calendar === false || + if ($calendar === false || !array_key_exists($calendar, Kronolith::listCalendars(false, Horde_Perms::READ))) { throw new Horde_Exception_PermissionDenied(); } @@ -564,7 +564,7 @@ class Kronolith_Api extends Horde_Registry_Api } else { $changes['modify'] = $uids; } - + /* No way to figure out if this was an exception, so we must include all */ $changes['delete'] = $this->listBy('delete', $start, $c, $end); @@ -751,8 +751,7 @@ class Kronolith_Api extends Horde_Registry_Api $iCal->setAttribute('X-WR-CALNAME', Horde_String::convertCharset($share->get('name'), Horde_Nls::getCharset(), 'utf-8')); // Create a new vEvent. - $vEvent = $event->toiCalendar($iCal); - $iCal->addComponent($vEvent); + $iCal->addComponent($event->toiCalendar($iCal)); return $iCal->exportvCalendar(); @@ -806,8 +805,7 @@ class Kronolith_Api extends Horde_Registry_Api foreach ($events as $dayevents) { foreach ($dayevents as $event) { - $vEvent = $event->toiCalendar($iCal); - $iCal->addComponent($vEvent); + $iCal->addComponent($event->toiCalendar($iCal)); } } diff --git a/kronolith/lib/Event.php b/kronolith/lib/Event.php index 096165a6c..a5d60755d 100644 --- a/kronolith/lib/Event.php +++ b/kronolith/lib/Event.php @@ -523,13 +523,13 @@ abstract class Kronolith_Event * @param Horde_iCalendar &$calendar A Horde_iCalendar object that acts as * a container. * - * @return Horde_iCalendar_vevent The vEvent object for this event. + * @return array An array of Horde_iCalendar_vevent objects for this event. */ public function toiCalendar(&$calendar) { $vEvent = &Horde_iCalendar::newComponent('vevent', $calendar); $v1 = $calendar->getAttribute('VERSION') == '1.0'; - + $vEvents = array(); if ($this->isAllDay()) { $vEvent->setAttribute('DTSTART', $this->start, array('VALUE' => 'DATE')); $vEvent->setAttribute('DTEND', $this->end, array('VALUE' => 'DATE')); @@ -744,8 +744,41 @@ abstract class Kronolith_Event $vEvent->setAttribute('RRULE', $rrule); } - // Exceptions. + // Exceptions. An exception with no replacement event is represented + // by EXDATE, and those with replacement events are represented by + // a new vEvent element. We get all known replacement events first, + // then remove the exceptionoriginaldate from the list of the event + // exceptions. Any exceptions left should represent exceptions with + // no replacement. $exceptions = $this->recurrence->getExceptions(); + $kronolith_driver = Kronolith::getDriver(null, $this->calendar); + $search = new StdClass(); + $search->start = $this->recurrence->getRecurStart(); + $search->end = $this->recurrence->getRecurEnd(); + $search->baseid = $this->uid; + $results = $kronolith_driver->search($search); + foreach ($results as $days) { + foreach ($days as $exceptionEvent) { + // Need to change the UID so it links to the original + // recurring event. + $exceptionEvent->uid = $this->uid; + $vEventException = $exceptionEvent->toIcalendar($calendar); + // This should never happen, but protect against it anyway. + if (count($vEventException) > 1) { + throw new Kronolith_Exception(_("Unable to parse event.")); + } + $vEventException = array_pop($vEventException); + $vEventException->setAttribute('RECURRENCE-ID', $exceptionEvent->exceptionoriginaldate->timestamp()); + $originaldate = $exceptionEvent->exceptionoriginaldate->format('Ymd'); + $key = array_search($originaldate, $exceptions); + if ($key !== false) { + unset($exceptions[$key]); + } + $vEvents[] = $vEventException; + } + } + + /* The remaining exceptions represent deleted recurrences */ foreach ($exceptions as $exception) { if (!empty($exception)) { list($year, $month, $mday) = sscanf($exception, '%04d%02d%02d'); @@ -761,8 +794,9 @@ abstract class Kronolith_Event } } } + array_unshift($vEvents, $vEvent); - return $vEvent; + return $vEvents; } /** @@ -987,7 +1021,23 @@ abstract class Kronolith_Event $this->recurrence->fromRRule10($rrule); } - // Exceptions. + /* Delete all existing exceptions to this event if it already exists */ + if (!empty($this->uid)) { + $kronolith_driver = Kronolith::getDriver(null, $this->calendar); + $search = new StdClass(); + $search->start = $this->recurrence->getRecurStart(); + $search->end = $this->recurrence->getRecurEnd(); + $search->baseid = $this->uid; + $results = $kronolith_driver->search($search); + foreach ($results as $days) { + foreach ($days as $exception) { + $kronolith_driver->deleteEvent($exception->id); + } + } + } + + // Exceptions. EXDATE represents deleted events, just add the + // exception, no new event is needed. $exdates = $vEvent->getAttributeValues('EXDATE'); if (is_array($exdates)) { foreach ($exdates as $exdate) { @@ -1000,6 +1050,21 @@ abstract class Kronolith_Event } } + // RECURRENCE-ID indicates that this event represents an exception + $recurrenceid = $vEvent->getAttribute('RECURRENCE-ID'); + if (!($recurrenceid instanceof PEAR_Error)) { + $kronolith_driver = Kronolith::getDriver(null, $this->calendar); + $originaldt = new Horde_Date($recurrenceid); + $this->exceptionoriginaldate = $originaldt; + $this->baseid = $this->uid; + $this->uid = null; + $originalEvent = $kronolith_driver->getByUID($this->baseid); + $originalEvent->recurrence->addException($originaldt->format('Y'), + $originaldt->format('m'), + $originaldt->format('d')); + $originalEvent->save(); + } + $this->initialized = true; } @@ -2774,7 +2839,7 @@ abstract class Kronolith_Event /** * Getter for geo data - * + * * @return array An array of lat/lng data. */ public function getGeolocation() diff --git a/kronolith/lib/Kronolith.php b/kronolith/lib/Kronolith.php index e855d8515..5f924df25 100644 --- a/kronolith/lib/Kronolith.php +++ b/kronolith/lib/Kronolith.php @@ -2243,6 +2243,7 @@ class Kronolith $iCal->setAttribute('X-WR-CALNAME', Horde_String::convertCharset($share->get('name'), Horde_Nls::getCharset(), 'utf-8')); $vevent = $event->toiCalendar($iCal); if ($action == self::ITIP_CANCEL && !empty($instance)) { + $vevent = array_pop($vevent); $vevent->setAttribute('RECURRENCE-ID', $instance, array('VALUE' => 'DATE')); } $iCal->addComponent($vevent); diff --git a/kronolith/lib/View/ExportEvent.php b/kronolith/lib/View/ExportEvent.php index b815d0af7..ffe4edb3c 100644 --- a/kronolith/lib/View/ExportEvent.php +++ b/kronolith/lib/View/ExportEvent.php @@ -36,8 +36,7 @@ class Kronolith_View_ExportEvent { } } - $vEvent = $event->toiCalendar($iCal); - $iCal->addComponent($vEvent); + $iCal->addComponent($event->toiCalendar($iCal)); $content = $iCal->exportvCalendar(); $GLOBALS['browser']->downloadHeaders( -- 2.11.0