Branch 'dev/recurring-invitations' - 3 commits - plugins/calendar plugins/libcalendaring plugins/libkolab

Thomas Brüderli bruederli at kolabsys.com
Tue Feb 17 11:38:53 CET 2015


 plugins/calendar/calendar.php                     |   27 ++-
 plugins/calendar/calendar_ui.js                   |    9 -
 plugins/calendar/drivers/calendar_driver.php      |   12 +
 plugins/calendar/drivers/kolab/kolab_calendar.php |   71 +++-----
 plugins/calendar/drivers/kolab/kolab_driver.php   |  182 ++++++++++++++++++++--
 plugins/libcalendaring/libcalendaring.js          |    1 
 plugins/libcalendaring/libcalendaring.php         |    6 
 plugins/libcalendaring/localization/en_US.inc     |    2 
 plugins/libkolab/lib/kolab_format_event.php       |    7 
 9 files changed, 246 insertions(+), 71 deletions(-)

New commits:
commit 8a90069071821f4cf2cdac6d7fce82cf12a32c11
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Tue Feb 17 11:36:01 2015 +0100

    - Support exceptions and iTip messages with thisansfuture range
    - Store two exceptions for the same occurence if necessary (with differing range)
    - Update attendee status from iTip REPLY to all exceptions stored for the event
    - Correctly handle exceptions on the first instance (main event)

diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php
index 786cd76..8e6a1a3 100644
--- a/plugins/calendar/calendar.php
+++ b/plugins/calendar/calendar.php
@@ -863,6 +863,7 @@ class calendar extends rcube_plugin
         $this->write_preprocess($event, $action);
         if ($success = $this->driver->new_event($event)) {
           $event['id'] = $event['uid'];
+          $event['_savemode'] = 'all';
           $this->cleanup_event($event);
         }
         $reload = $success && $event['recurrence'] ? 2 : 1;
@@ -1017,11 +1018,18 @@ class calendar extends rcube_plugin
             $itip = $this->load_itip();
             $itip->set_sender_email($reply_sender);
             $event['comment'] = $reply_comment;
+            $event['thisandfuture'] = $event['_savemode'] == 'future';
             if ($organizer && $itip->send_itip_message($event, 'REPLY', $organizer, 'itipsubject' . $status, 'itipmailbody' . $status))
               $this->rc->output->command('display_message', $this->gettext(array('name' => 'sentresponseto', 'vars' => array('mailto' => $organizer['name'] ? $organizer['name'] : $organizer['email']))), 'confirmation');
             else
               $this->rc->output->command('display_message', $this->gettext('itipresponseerror'), 'error');
           }
+
+          // refresh all calendars
+          if ($event['calendar'] != $ev['calendar']) {
+            $this->rc->output->command('plugin.refresh_calendar', array('source' => null, 'refetch' => true));
+            $reload = 0;
+          }
         }
         break;
 
@@ -1139,11 +1147,13 @@ class calendar extends rcube_plugin
 
       // make sure we have the complete record
       $event = $action == 'remove' ? $old : $this->driver->get_event($event);
+      $event['_savemode'] = $_savemode;
 
       // send notification for the main event when savemode is 'all'
       if ($_savemode == 'all' && $event['recurrence_id']) {
         $event['id'] = $event['recurrence_id'];
         $event = $this->driver->get_event($event);
+        unset($event['_instance'], $event['recurrence_date']);
       }
 
       // only notify if data really changed (TODO: do diff check on client already)
@@ -2763,9 +2773,11 @@ class calendar extends rcube_plugin
               }
             }
             $event_attendee = null;
+            $update_attendees = array();
             foreach ($event['attendees'] as $attendee) {
               if ($event['_sender'] && ($attendee['email'] == $event['_sender'] || $attendee['email'] == $event['_sender_utf'])) {
                 $event_attendee = $attendee;
+                $update_attendees[] = $attendee;
                 $metadata['fallback'] = $attendee['status'];
                 $metadata['attendee'] = $attendee['email'];
                 $metadata['rsvp'] = $attendee['rsvp'] || $attendee['role'] != 'NON-PARTICIPANT';
@@ -2775,9 +2787,12 @@ class calendar extends rcube_plugin
               }
               // also copy delegate attendee
               else if (!empty($attendee['delegated-from']) &&
-                       (stripos($attendee['delegated-from'], $event['_sender']) !== false || stripos($attendee['delegated-from'], $event['_sender_utf']) !== false) &&
-                       (!in_array($attendee['email'], $existing_attendee_emails))) {
-                $existing['attendees'][] = $attendee;
+                       (stripos($attendee['delegated-from'], $event['_sender']) !== false ||
+                        stripos($attendee['delegated-from'], $event['_sender_utf']) !== false)) {
+                $update_attendees[] = $attendee;
+                if (!in_array($attendee['email'], $existing_attendee_emails)) {
+                  $existing['attendees'][] = $attendee;
+                }
               }
             }
 
@@ -2794,12 +2809,12 @@ class calendar extends rcube_plugin
             // found matching attendee entry in both existing and new events
             if ($existing_attendee >= 0 && $event_attendee) {
               $existing['attendees'][$existing_attendee] = $event_attendee;
-              $success = $this->driver->edit_event($existing);
+              $success = $this->driver->update_attendees($existing, $update_attendees);
             }
             // update the entire attendees block
             else if (($event['sequence'] >= $existing['sequence'] || $event['changed'] >= $existing['changed']) && $event_attendee) {
               $existing['attendees'][] = $event_attendee;
-              $success = $this->driver->edit_event($existing);
+              $success = $this->driver->update_attendees($existing, $update_attendees);
             }
             else {
               $error_msg = $this->gettext('newerversionexists');
@@ -2855,7 +2870,7 @@ class calendar extends rcube_plugin
 
           // if the RSVP reply only refers to a single instance:
           // store unmodified master event with current instance as exception
-          if (!empty($instance) && $savemode != 'all') {
+          if (!empty($instance) && !empty($savemode) && $savemode != 'all') {
             $master = $this->lib->mail_get_itip_object($mbox, $uid, $mime_id, 'event');
             if ($master['recurrence'] && !$master['_instance']) {
               // compute recurring events until this instance's date
diff --git a/plugins/calendar/drivers/calendar_driver.php b/plugins/calendar/drivers/calendar_driver.php
index 742830b..aecd2e1 100644
--- a/plugins/calendar/drivers/calendar_driver.php
+++ b/plugins/calendar/drivers/calendar_driver.php
@@ -205,6 +205,18 @@ abstract class calendar_driver
   }
 
   /**
+   * Update the participant status for the given attendee
+   *
+   * @param array  Hash array with event properties
+   * @param array  List of hash arrays each represeting an updated attendee
+   * @return boolean True on success, False on error
+   */
+  public function update_attendees(&$event, $attendees)
+  {
+    return $this->edit_event($event);
+  }
+
+  /**
    * Move a single event
    *
    * @param array Hash array with event properties:
diff --git a/plugins/calendar/drivers/kolab/kolab_calendar.php b/plugins/calendar/drivers/kolab/kolab_calendar.php
index b5b272f..29bc01e 100644
--- a/plugins/calendar/drivers/kolab/kolab_calendar.php
+++ b/plugins/calendar/drivers/kolab/kolab_calendar.php
@@ -195,7 +195,11 @@ class kolab_calendar extends kolab_storage_folder_api
       if ($master_id != $id && ($record = $this->storage->get_object($master_id)))
         $this->events[$master_id] = $this->_to_rcube_event($record);
 
-      if (($master = $this->events[$master_id]) && $master['recurrence']) {
+      // check for match on the first instance already
+      if (($_instance = $this->events[$master_id]['_instance']) && $id == $master_id . '-' . $_instance) {
+        $this->events[$id] = $this->events[$master_id];
+      }
+      else if (($master = $this->events[$master_id]) && $master['recurrence']) {
         $this->get_recurring_events($record, $master['start'], null, $id);
       }
     }
@@ -274,17 +278,10 @@ class kolab_calendar extends kolab_storage_folder_api
         $add = true;
 
         // skip the first instance of a recurring event if listed in exdate
-        if ($virtual && (!empty($event['recurrence']['EXDATE']) || !empty($event['recurrence']['EXCEPTIONS']))) {
+        if ($virtual && !empty($event['recurrence']['EXDATE'])) {
           $event_date = $event['start']->format('Ymd');
           $exdates = (array)$event['recurrence']['EXDATE'];
 
-          // add dates from exceptions to list
-          if (is_array($event['recurrence']['EXCEPTIONS'])) {
-              foreach ($event['recurrence']['EXCEPTIONS'] as $exception) {
-                  $exdates[] = clone $exception['start'];
-              }
-          }
-
           foreach ($exdates as $exdate) {
             if ($exdate->format('Ymd') == $event_date) {
               $add = false;
@@ -293,6 +290,18 @@ class kolab_calendar extends kolab_storage_folder_api
           }
         }
 
+        // find and merge exception for the first instance
+        if (!empty($event['recurrence']) && is_array($event['recurrence']['EXCEPTIONS'])) {
+          $event_date = $event['start']->format('Ymd');
+          foreach ($event['recurrence']['EXCEPTIONS'] as $exception) {
+            $exdate = $exception['recurrence_date'] ? $exception['recurrence_date']->format('Ymd') : substr($exception['_instance'], 0, 8);
+            if ($exdate == $event_date) {
+              $event['_instance'] = $exception['_instance'];
+              kolab_driver::merge_event_data($event, $exception);
+            }
+          }
+        }
+
         if ($add)
           $events[] = $event;
       }
@@ -583,24 +592,29 @@ class kolab_calendar extends kolab_storage_folder_api
         $rec_event['id'] = $event['uid'] . '-' . $exception['_instance'];
         $rec_event['isexception'] = 1;
 
-        // found the specifically requested instance, exiting...
-        if ($rec_event['id'] == $event_id) {
+        // found the specifically requested instance: register exception (single occurrence wins)
+        if ($rec_event['id'] == $event_id && (!$this->events[$event_id] || $this->events[$event_id]['thisandfuture'])) {
           $rec_event['recurrence'] = $recurrence_rule;
           $rec_event['recurrence_id'] = $event['uid'];
-          $events[] = $rec_event;
           $this->events[$rec_event['id']] = $rec_event;
-          return $events;
         }
 
         // remember this exception's date
         $exdate = substr($exception['_instance'], 0, 8);
-        $exdata[$exdate] = $rec_event;
+        if (!$exdata[$exdate] || $exdata[$exdate]['thisandfuture']) {
+          $exdata[$exdate] = $rec_event;
+        }
         if ($rec_event['thisandfuture']) {
           $futuredata[$exdate] = $rec_event;
         }
       }
     }
 
+    // found the specifically requested instance, exiting...
+    if ($event_id && !empty($this->events[$event_id])) {
+      return array($this->events[$event_id]);
+    }
+
     // use libkolab to compute recurring events
     if (class_exists('kolabcalendaring')) {
         $recurrence = new kolab_date_recurrence($object);
@@ -627,7 +641,7 @@ class kolab_calendar extends kolab_storage_folder_api
         $rec_event['_instance'] = $instance_id;
 
         if ($overlay_data || $exdata[$datestr])  // copy data from exception
-          $this->_merge_event_data($rec_event, $exdata[$datestr] ?: $overlay_data);
+          kolab_driver::merge_event_data($rec_event, $exdata[$datestr] ?: $overlay_data);
 
         $rec_event['id'] = $rec_id;
         $rec_event['recurrence_id'] = $event['uid'];
@@ -652,37 +666,10 @@ class kolab_calendar extends kolab_storage_folder_api
   }
 
   /**
-   * 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','recurrence','recurrence_date','organizer','_attachments');
-
-    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')));
-          // set date value if overlay is an exception of the current instance
-          if (substr($overlay['_instance'], 0, 8) == substr($event['_instance'], 0, 8)) {
-            $event[$prop]->setDate(intval($value->format('Y')), intval($value->format('n')), intval($value->format('j')));
-          }
-        }
-      }
-      else if ($prop[0] != '_' && !in_array($prop, $forbidden))
-        $event[$prop] = $value;
-    }
-  }
-
-  /**
    * Convert from Kolab_Format to internal representation
    */
   private function _to_rcube_event($record)
   {
-    $record['id'] = $record['uid'];
     $record['calendar'] = $this->id;
     $record['links'] = $this->get_links($record['uid']);
 
diff --git a/plugins/calendar/drivers/kolab/kolab_driver.php b/plugins/calendar/drivers/kolab/kolab_driver.php
index 0cd962d..3e3f0fc 100644
--- a/plugins/calendar/drivers/kolab/kolab_driver.php
+++ b/plugins/calendar/drivers/kolab/kolab_driver.php
@@ -537,7 +537,7 @@ class kolab_driver extends calendar_driver
       $id = $event['id'] ?: $event['uid'];
       $cal = $event['calendar'];
 
-      // we're looking for a recurring instance: expand the ID to our internal convention for recurring instanced
+      // we're looking for a recurring instance: expand the ID to our internal convention for recurring instances
       if (!$event['id'] && $event['_instance']) {
         $id .= '-' . $event['_instance'];
       }
@@ -634,6 +634,48 @@ class kolab_driver extends calendar_driver
     return $ret;
   }
 
+  /**
+   * Update the participant status for the given attendees
+   *
+   * @see calendar_driver::update_attendees()
+   */
+  public function update_attendees(&$event, $attendees)
+  {
+    // for this-and-future updates, merge the updated attendees onto all exceptions in range
+    if (($event['_savemode'] == 'future' && $event['recurrence_id']) || !empty($event['recurrence'])) {
+      if (!($storage = $this->get_calendar($event['calendar'])))
+        return false;
+
+      // load master event
+      $master = $event['recurrence_id'] ? $storage->get_event($event['recurrence_id']) : $event;
+
+      // apply attendee update to each existing exception
+      if ($master['recurrence'] && !empty($master['recurrence']['EXCEPTIONS'])) {
+        $saved = false;
+        foreach ($master['recurrence']['EXCEPTIONS'] as $i => $exception) {
+          // merge the new event properties onto future exceptions
+          if ($exception['_instance'] >= $event['_instance']) {
+            self::merge_attendee_data($master['recurrence']['EXCEPTIONS'][$i], $attendees);
+          }
+          // update a specific instance
+          if ($exception['_instance'] == $event['_instance'] && $exception['thisandfuture']) {
+            $saved = true;
+          }
+        }
+
+        // add the given event as new exception
+        if (!$saved && $event['id'] != $master['id']) {
+          $event['thisandfuture'] = true;
+          $master['recurrence']['EXCEPTIONS'][] = $event;
+        }
+
+        return $this->update_event($master);
+      }
+    }
+
+    // just update the given event (instance)
+    return $this->update_event($event);
+  }
 
   /**
    * Move a single event
@@ -933,15 +975,10 @@ class kolab_driver extends calendar_driver
         }
 
         // save properties to a recurrence exception instance
-        if ($old['recurrence_id'] && is_array($master['recurrence']['EXCEPTIONS'])) {
-          foreach ($master['recurrence']['EXCEPTIONS'] as $i => $exception) {
-            if ($exception['_instance'] == $old['_instance']) {
-              $event['_instance'] = $old['_instance'];
-              $event['recurrence_date'] = $old['recurrence_date'];
-              $master['recurrence']['EXCEPTIONS'][$i] = $event;
-              $success = $storage->update_event($master, $old['id']);
-              break 2;
-            }
+        if ($old['_instance'] && is_array($master['recurrence']['EXCEPTIONS'])) {
+          if ($this->update_recurrence_exceptions($master, $event, $old, $savemode)) {
+            $success = $storage->update_event($master, $old['id']);
+            break;
           }
         }
 
@@ -962,7 +999,7 @@ class kolab_driver extends calendar_driver
         // save as new exception to master event
         if ($add_exception) {
           $event['_instance'] = $old['_instance'];
-          $event['recurrence_date'] = $old['recurrence_date'];
+          $event['recurrence_date'] = $old['recurrence_date'] ?: $old['start'];
           $master['recurrence']['EXCEPTIONS'][] = $event;
         }
         $success = $storage->update_event($master);
@@ -1004,6 +1041,8 @@ class kolab_driver extends calendar_driver
           $event['end'] = $master['end'];
         }
 
+        // TODO: forward changes to exceptions (which do not yet have differing values stored)
+
         // adjust recurrence-id when start changed and therefore the entire recurrence chain changes
         if (($old_start_date != $new_start_date || $old_start_time != $new_start_time) &&
               is_array($event['recurrence']) && is_array($event['recurrence']['EXCEPTIONS']) && !$with_exceptions) {
@@ -1071,6 +1110,120 @@ class kolab_driver extends calendar_driver
   }
 
   /**
+   * Apply the given changes to already existing exceptions
+   */
+  protected function update_recurrence_exceptions(&$master, $event, $old, $savemode)
+  {
+    $saved = false;
+    $existing = null;
+
+    foreach ($master['recurrence']['EXCEPTIONS'] as $i => $exception) {
+      // update a specific instance
+      if ($exception['_instance'] == $old['_instance']) {
+        $existing = $i;
+
+        // check savemode against existing exception mode.
+        // if matches, we can update this existing exception
+        if ((bool)$exception['thisandfuture'] === ($savemode == 'future')) {
+          $event['_instance'] = $old['_instance'];
+          $event['thisandfuture'] = $old['thisandfuture'];
+          $event['recurrence_date'] = $old['recurrence_date'];
+          $master['recurrence']['EXCEPTIONS'][$i] = $event;
+          $saved = true;
+        }
+      }
+      // merge the new event properties onto future exceptions
+      if ($savemode == 'future' && $exception['_instance'] >= $old['_instance']) {
+        unset($event['thisandfuture']);
+        self::merge_event_data($master['recurrence']['EXCEPTIONS'][$i], $event);
+      }
+    }
+/*
+    // we could not update the existing exception due to savemode mismatch...
+    if (!$saved && $existing !== null && $master['recurrence']['EXCEPTIONS'][$existing]['thisandfuture']) {
+      // ... try to move the existing this-and-future exception to the next occurrence
+      foreach ($this->get_recurring_events($master, $existing['start']) as $candidate) {
+        // our old this-and-future exception is obsolete
+        if ($candidate['thisandfuture']) {
+          unset($master['recurrence']['EXCEPTIONS'][$existing]);
+          $saved = true;
+          break;
+        }
+        // this occurrence doesn't yet have an exception
+        else if (!$candidate['isexception']) {
+          $event['_instance'] = $candidate['_instance'];
+          $event['recurrence_date'] = $candidate['recurrence_date'];
+          $master['recurrence']['EXCEPTIONS'][$i] = $event;
+          $saved = true;
+          break;
+        }
+      }
+    }
+*/
+
+    // returning false here will add a new exception
+    return $saved;
+  }
+
+  /**
+   * 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
+   */
+  public static function merge_event_data(&$event, $overlay)
+  {
+    static $forbidden = array('id','uid','recurrence','recurrence_date','thisandfuture','organizer','_attachments');
+
+    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')));
+          // set date value if overlay is an exception of the current instance
+          if (substr($overlay['_instance'], 0, 8) == substr($event['_instance'], 0, 8)) {
+            $event[$prop]->setDate(intval($value->format('Y')), intval($value->format('n')), intval($value->format('j')));
+          }
+        }
+      }
+      else if ($prop == 'thisandfuture' && $overlay['_instance'] == $event['_instance']) {
+        $event[$prop] = $value;
+      }
+      else if ($prop[0] != '_' && !in_array($prop, $forbidden))
+        $event[$prop] = $value;
+    }
+  }
+
+  /**
+   * Update attendee properties on the given event object
+   *
+   * @param array The event object to be altered
+   * @param array List of hash arrays each represeting an updated/added attendee
+   */
+  public static function merge_attendee_data(&$event, $attendees)
+  {
+    if (!empty($attendees) && !is_array($attendees[0])) {
+      $attendees = array($attendees);
+    }
+
+    foreach ($attendees as $attendee) {
+      $found = false;
+
+      foreach ($event['attendees'] as $i => $candidate) {
+        if ($candidate['email'] == $attendee['email']) {
+          $event['attendees'][$i] = $attendee;
+          $found = true;
+          break;
+        }
+      }
+
+      if (!$found) {
+        $event['attendees'][] = $attendee;
+      }
+    }
+  }
+
+  /**
    * Get events from source.
    *
    * @param  integer Event's new start (unix timestamp)
@@ -1520,6 +1673,13 @@ class kolab_driver extends calendar_driver
     if (empty($record['recurrence']))
       unset($record['recurrence']);
 
+    // add instance identifier to first occurrence (master event)
+    if ($record['recurrence'] && !$record['recurrence_id'] && !$record['_instance']) {
+      $recurrence_id_format = $event['allday'] ? 'Ymd' : 'Ymd\THis';
+      $record['recurrence_date'] = $record['start'];
+      $record['_instance'] = $record['recurrence_date']->format($recurrence_id_format);
+    }
+
     // remove internals
     unset($record['_mailbox'], $record['_msguid'], $record['_formatobj'], $record['_attachments'], $record['x-custom']);
 
diff --git a/plugins/libcalendaring/libcalendaring.php b/plugins/libcalendaring/libcalendaring.php
index 63a0548..c203854 100644
--- a/plugins/libcalendaring/libcalendaring.php
+++ b/plugins/libcalendaring/libcalendaring.php
@@ -1410,8 +1410,12 @@ class libcalendaring extends rcube_plugin
      */
     public static function identify_recurrence_instance(&$object)
     {
+        // for savemode=all, remove recurrence instance identifiers
+        if (!empty($object['_savemode']) && $object['_savemode'] == 'all') {
+            unset($object['_instance'], $object['recurrence_date']);
+        }
         // set instance and 'savemode' according to recurrence-id
-        if (!empty($object['recurrence_date']) && is_a($object['recurrence_date'], 'DateTime')) {
+        else if (!empty($object['recurrence_date']) && is_a($object['recurrence_date'], 'DateTime')) {
             $recurrence_id_format = $object['allday'] ? 'Ymd' : 'Ymd\THis';
             $object['_instance'] = $object['recurrence_date']->format($recurrence_id_format);
             $object['_savemode'] = $object['thisandfuture'] ? 'future' : 'current';
diff --git a/plugins/libcalendaring/localization/en_US.inc b/plugins/libcalendaring/localization/en_US.inc
index ca7d1fd..e5e0426 100644
--- a/plugins/libcalendaring/localization/en_US.inc
+++ b/plugins/libcalendaring/localization/en_US.inc
@@ -109,7 +109,7 @@ $labels['acceptattendee'] = 'Accept participant';
 $labels['declineattendee'] = 'Decline participant';
 $labels['declineattendeeconfirm'] = 'Enter a message to the declined participant (optional):';
 $labels['rsvpmodeall'] = 'The entire series';
-$labels['rsvpmodecurrent'] = 'This occurrence';
+$labels['rsvpmodecurrent'] = 'This occurrence only';
 $labels['rsvpmodefuture'] = 'This and future occurrences';
 
 $labels['itipsingleoccurrence'] = 'This is a <em>single occurrence</em> out of a series of events';


commit 7fd2eb873dbeed5fb955e459b20e1134c09892b0
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Mon Feb 16 18:45:25 2015 +0100

    Hide RSVP-mode menu when moving/resizing event dialog

diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js
index fcd360a..c9334ec 100644
--- a/plugins/calendar/calendar_ui.js
+++ b/plugins/calendar/calendar_ui.js
@@ -604,13 +604,16 @@ function rcube_calendar_ui(settings)
         },
         close: function() {
           $dialog.dialog('destroy').attr('aria-hidden', 'true').hide();
-          rcmail.command('menu-close','eventoptionsmenu')
+          rcmail.command('menu-close','eventoptionsmenu');
+          $('.libcal-rsvp-replymode').hide();
         },
         dragStart: function() {
-          rcmail.command('menu-close','eventoptionsmenu')
+          rcmail.command('menu-close','eventoptionsmenu');
+          $('.libcal-rsvp-replymode').hide();
         },
         resizeStart: function() {
-          rcmail.command('menu-close','eventoptionsmenu')
+          rcmail.command('menu-close','eventoptionsmenu');
+          $('.libcal-rsvp-replymode').hide();
         },
         buttons: buttons,
         minWidth: 320,
diff --git a/plugins/libcalendaring/libcalendaring.js b/plugins/libcalendaring/libcalendaring.js
index cd59f66..71c101d 100644
--- a/plugins/libcalendaring/libcalendaring.js
+++ b/plugins/libcalendaring/libcalendaring.js
@@ -967,6 +967,7 @@ rcube_libcalendaring.itip_rsvp_recurring = function(btn, callback)
 
     $.each(['all','current','future'], function(i, mode) {
         $('<li><a>' + rcmail.get_label('rsvpmode'+mode, 'libcalendaring') + '</a>')
+        .addClass('ui-menu-item')
         .attr('rel', mode)
         .appendTo(mnu);
     });


commit 6a5a8148348d0eddb452ff33affe4af87e9359cc
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Mon Feb 16 15:59:50 2015 +0100

    Don't remove properties from exceptions which are equal to the master event. KE17 says:
    
    A recurrence exception SHALL copy ALL properties of the base event, and adjust as required,
    and it SHALL NOT be applied on top of the orginial event properties (The exception replaces
    the complete original event definition for the specific occurrence).

diff --git a/plugins/libkolab/lib/kolab_format_event.php b/plugins/libkolab/lib/kolab_format_event.php
index 0fda1e3..979b33b 100644
--- a/plugins/libkolab/lib/kolab_format_event.php
+++ b/plugins/libkolab/lib/kolab_format_event.php
@@ -241,7 +241,6 @@ class kolab_format_event extends kolab_format_xcal
     private function compact_exception($exception, $master)
     {
         $forbidden = array('recurrence','organizer','_attachments');
-        $whitelist = array('start','end');
 
         foreach ($forbidden as $prop) {
             if (array_key_exists($prop, $exception)) {
@@ -249,12 +248,6 @@ class kolab_format_event extends kolab_format_xcal
             }
         }
 
-        foreach ($master as $prop => $value) {
-            if (isset($exception[$prop]) && gettype($exception[$prop]) == gettype($value) && $exception[$prop] == $value && !in_array($prop, $whitelist)) {
-                unset($exception[$prop]);
-            }
-        }
-
         // preserve this property for date serialization
         $exception['allday'] = $master['allday'];
 




More information about the commits mailing list