Handle iTip CANCEL request of a recurring event instance correctly.
authorMichael J. Rubinsky <mrubinsk@horde.org>
Thu, 8 Jul 2010 23:56:04 +0000 (19:56 -0400)
committerMichael J. Rubinsky <mrubinsk@horde.org>
Fri, 9 Jul 2010 15:25:17 +0000 (11:25 -0400)
According to RFC 2446, when cancelling an instance of a recurring
event, the RECURRENCE-ID value MUST be specified along with the UID.
If cancelling the entire event series, the RECURRENCE-ID value
MUST NOT be included.

Bug: 8956

imp/lib/Mime/Viewer/Itip.php
kronolith/lib/Api.php
kronolith/lib/Kronolith.php

index a7e7f7f..e734df4 100644 (file)
@@ -115,8 +115,14 @@ class IMP_Horde_Mime_Viewer_Itip extends Horde_Mime_Viewer_Driver
                 // vEvent cancellation.
                 if ($registry->hasMethod('calendar/delete')) {
                     $guid = $components[$key]->getAttribute('UID');
+                    $deleteParams = array('guid' => $guid);
+                    $instance = $components[$key]->getAttribute('RECURRENCE-ID');
+                    if (!($instance instanceof PEAR_Error)) {
+                        // This is a cancellation of a recurring event instance.
+                        $deleteParams['recurrenceId'] = $instance;
+                    }
                     try {
-                        $registry->call('calendar/delete', array('guid' => $guid));
+                        $registry->call('calendar/delete', $deleteParams);
                         $msgs[] = array('success', _("Event successfully deleted."));
                     } catch (Horde_Exception $e) {
                         $msgs[] = array('error', _("There was an error deleting the event:") . ' ' . $e->getMessage());
@@ -715,7 +721,7 @@ class IMP_Horde_Mime_Viewer_Itip extends Horde_Mime_Viewer_Driver
             } else {
                 $desc = _("%s has cancelled an instance of the recurring \"%s\".");
                 if ($registry->hasMethod('calendar/replace')) {
-                    $options[] = '<option value="import">' . _("Update in my calendar") . '</option>';
+                    $options[] = '<option value="delete">' . _("Update in my calendar") . '</option>';
                 }
             }
             break;
index b5f858b..24f3e09 100644 (file)
@@ -818,12 +818,16 @@ class Kronolith_Api extends Horde_Registry_Api
     /**
      * Deletes an event identified by UID.
      *
-     * @param string|array $uid  A single UID or an array identifying the
-     *                           event(s) to delete.
+     * @param string|array $uid     A single UID or an array identifying the
+     *                              event(s) to delete.
+     *
+     * @param string $recurrenceId  The reccurenceId for the event instance, if
+     *                              this is a deletion of a recurring event
+     *                              instance ($uid must not be an array).
      *
      * @throws Kronolith_Exception
      */
-    public function delete($uid)
+    public function delete($uid, $recurrenceId = null)
     {
         // Handle an array of UIDs for convenience of deleting multiple events
         // at once.
@@ -869,7 +873,15 @@ class Kronolith_Api extends Horde_Registry_Api
             throw new Horde_Exception_PermissionDenied();
         }
 
-        $kronolith_driver->deleteEvent($event->id);
+        if ($recurrenceId && $event->recurs()) {
+            $deleteDate = new Horde_Date($recurrenceId);
+            $event->recurrence->addException($deleteDate->format('Y'), $deleteDate->format('m'), $deleteDate->format('d'));
+            $event->save();
+        } elseif ($recurrenceId) {
+            throw new Kronolith_Exception(_("Unable to delete event. An exception date was provided but the event does not seem to be recurring."));
+        } else {
+            $kronolith_driver->deleteEvent($event->id);
+        }
     }
 
     /**
index 5f924df..12acdf3 100644 (file)
@@ -2243,8 +2243,11 @@ 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)) {
+                // Recurring event instance deletion, need to specify the
+                // RECURRENCE-ID but NOT the EXDATE.
                 $vevent = array_pop($vevent);
                 $vevent->setAttribute('RECURRENCE-ID', $instance, array('VALUE' => 'DATE'));
+                $vevent->removeAttribute('EXDATE');
             }
             $iCal->addComponent($vevent);