3 commits - plugins/calendar plugins/libcalendaring plugins/libkolab

Thomas Brüderli bruederli at kolabsys.com
Wed Jan 28 17:51:28 CET 2015


 plugins/calendar/calendar.php                   |   31 ++++++++++--
 plugins/calendar/calendar_ui.js                 |   58 ++++++++++++++++++------
 plugins/calendar/drivers/kolab/kolab_driver.php |   13 ++---
 plugins/calendar/localization/en_US.inc         |    1 
 plugins/calendar/skins/larry/calendar.css       |    1 
 plugins/libcalendaring/libvcalendar.php         |    7 ++
 plugins/libkolab/lib/kolab_format_event.php     |   14 ++++-
 7 files changed, 97 insertions(+), 28 deletions(-)

New commits:
commit d735c4721ef4976f3c128e1a32d22cd8a7c6020a
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Wed Jan 28 17:51:13 2015 +0100

    Catch parse errors of recurrence exceptions in iCal files

diff --git a/plugins/libcalendaring/libvcalendar.php b/plugins/libcalendaring/libvcalendar.php
index b81ae99..127ee36 100644
--- a/plugins/libcalendaring/libvcalendar.php
+++ b/plugins/libcalendaring/libvcalendar.php
@@ -320,7 +320,12 @@ class libvcalendar implements Iterator
                         if ($object['recurrence']) {
                             foreach ($vobject->children as $component) {
                                 if ($component->name == 'VEVENT' && isset($component->{'RECURRENCE-ID'})) {
-                                    $object['recurrence']['EXCEPTIONS'][] = $this->_to_array($component);
+                                    try {
+                                        $object['recurrence']['EXCEPTIONS'][] = $this->_to_array($component);
+                                    }
+                                    catch (Exception $e) {
+                                        console("iCal data parse error: " . $e->getMessage(), $component->serialize());
+                                    }
                                 }
                             }
                         }


commit 9d3a665d9cf7fd3e658dae457a0ed2a4dbed5d82
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Wed Jan 28 17:46:03 2015 +0100

    Modify calendar UI to properly handle updates on recurring events with attandees (#4318)
    
    Since the Kolab stack doesn't yet fully support invitations for recurring events,
    the calendar client prevents the user from modifying single recurrence instances
    if attandees are involved: options to update the "current" or "future" items
    are disabled and deleting a single event will update the main event and notify
    all attendees.

diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php
index db61d47..59008db 100644
--- a/plugins/calendar/calendar.php
+++ b/plugins/calendar/calendar.php
@@ -842,7 +842,7 @@ class calendar extends rcube_plugin
     $success = $reload = $got_msg = false;
     
     // don't notify if modifying a recurring instance (really?)
-    if ($event['_savemode'] && $event['_savemode'] != 'all' && $event['_notify'])
+    if ($event['_savemode'] && in_array($event['_savemode'], array('current','future')) && $event['_notify'] && $action != 'remove')
       unset($event['_notify']);
     // force notify if hidden + active
     else if ((int)$this->rc->config->get('calendar_itip_send_option', $this->defaults['calendar_itip_send_option']) === 1)
@@ -866,20 +866,35 @@ class calendar extends rcube_plugin
         
       case "edit":
         $this->write_preprocess($event, $action);
-        if ($success = $this->driver->edit_event($event))
-            $this->cleanup_event($event);
+        if ($success = $this->driver->edit_event($event)) {
+          $this->cleanup_event($event);
+          if ($success !== true) {
+            $event['id'] = $success;
+            $old = null;
+          }
+        }
         $reload =  $success && ($event['recurrence'] || $event['_savemode'] || $event['_fromcalendar']) ? 2 : 1;
         break;
       
       case "resize":
         $this->write_preprocess($event, $action);
-        $success = $this->driver->resize_event($event);
+        if ($success = $this->driver->resize_event($event)) {
+          if ($success !== true) {
+            $event['id'] = $success;
+            $old = null;
+          }
+        }
         $reload = $event['_savemode'] ? 2 : 1;
         break;
       
       case "move":
         $this->write_preprocess($event, $action);
-        $success = $this->driver->move_event($event);
+        if ($success = $this->driver->move_event($event)) {
+          if ($success !== true) {
+            $event['id'] = $success;
+            $old = null;
+          }
+        }
         $reload  = $success && $event['_savemode'] ? 2 : 1;
         break;
       
@@ -1110,6 +1125,12 @@ class calendar extends rcube_plugin
       // make sure we have the complete record
       $event = $action == 'remove' ? $old : $this->driver->get_event($event);
 
+      // sending notification on a recurrence instance -> re-send the main event
+      if ($event['recurrence_id']) {
+        $event = $this->driver->get_event(array('id' => $event['recurrence_id'], 'cal' => $event['calendar']));
+        $action = 'edit';
+      }
+
       // only notify if data really changed (TODO: do diff check on client already)
       if (!$old || $action == 'remove' || self::event_diff($event, $old)) {
         $sent = $this->notify_attendees($event, $old, $action, $event['_comment']);
diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js
index 8eb51c6..598a4d7 100644
--- a/plugins/calendar/calendar_ui.js
+++ b/plugins/calendar/calendar_ui.js
@@ -742,9 +742,11 @@ function rcube_calendar_ui(settings)
 
       // show warning if editing a recurring event
       if (event.id && event.recurrence) {
-        var sel = event.thisandfuture ? 'future' : (event.isexception ? 'current' : 'all');
+        var allow_exceptions = !has_attendees(event) || !is_organizer(event),
+          sel = event._savemode || (allow_exceptions && event.thisandfuture ? 'future' : (allow_exceptions && event.isexception ? 'current' : 'all'));
         $('#edit-recurring-warning').show();
         $('input.edit-recurring-savemode[value="'+sel+'"]').prop('checked', true);
+        $('input.edit-recurring-savemode[value="current"], input.edit-recurring-savemode[value="future"]').prop('disabled', !allow_exceptions);
       }
       else
         $('#edit-recurring-warning').hide();
@@ -764,6 +766,11 @@ function rcube_calendar_ui(settings)
         if (event.attendees) {
           for (j=0; j < event.attendees.length; j++) {
             data = event.attendees[j];
+            // reset attendee status
+            if (event._savemode == 'new' && data.role != 'ORGANIZER') {
+              data.status = 'NEEDS-ACTION';
+              delete data.noreply;
+            }
             add_attendee(data, !allow_invitations);
             if (allow_invitations && data.role != 'ORGANIZER' && !data.noreply)
               reply_selected++;
@@ -2519,12 +2526,13 @@ function rcube_calendar_ui(settings)
     var update_event_confirm = function(action, event, data)
     {
       if (!data) data = event;
-      var decline = false, notify = false, html = '', cal = me.calendars[event.calendar];
+      var decline = false, notify = false, html = '', cal = me.calendars[event.calendar],
+        _has_attendees = has_attendees(event), _is_organizer = is_organizer(event);
       
       // event has attendees, ask whether to notify them
-      if (has_attendees(event)) {
+      if (_has_attendees) {
         var checked = (settings.itip_notify & 1 ? ' checked="checked"' : '');
-        if (is_organizer(event)) {
+        if (_is_organizer) {
           notify = true;
           if (settings.itip_notify & 2) {
             html += '<div class="message">' +
@@ -2550,11 +2558,25 @@ function rcube_calendar_ui(settings)
       
       // recurring event: user needs to select the savemode
       if (event.recurrence) {
+        var disabled_state = '', message_label = (action == 'remove' ? 'removerecurringeventwarning' : 'changerecurringeventwarning');
+
+        if (_has_attendees) {
+          if (action == 'remove') {
+            if (!_is_organizer) {
+              message_label = 'removerecurringallonly';
+              disabled_state = ' disabled';
+            }
+          }
+          else if (is_organizer(event)) {
+            disabled_state = ' disabled';
+          }
+        }
+
         html += '<div class="message"><span class="ui-icon ui-icon-alert"></span>' +
-          rcmail.gettext((action == 'remove' ? 'removerecurringeventwarning' : 'changerecurringeventwarning'), 'calendar') + '</div>' +
+          rcmail.gettext(message_label, 'calendar') + '</div>' +
           '<div class="savemode">' +
-            '<a href="#current" class="button">' + rcmail.gettext('currentevent', 'calendar') + '</a>' +
-            '<a href="#future" class="button">' + rcmail.gettext('futurevents', 'calendar') + '</a>' +
+            '<a href="#current" class="button' + disabled_state + '">' + rcmail.gettext('currentevent', 'calendar') + '</a>' +
+            '<a href="#future" class="button' + disabled_state + '">' + rcmail.gettext('futurevents', 'calendar') + '</a>' +
             '<a href="#all" class="button">' + rcmail.gettext('allevents', 'calendar') + '</a>' +
             (action != 'remove' ? '<a href="#new" class="button">' + rcmail.gettext('saveasnew', 'calendar') + '</a>' : '') +
           '</div>';
@@ -2564,14 +2586,24 @@ function rcube_calendar_ui(settings)
       if (html) {
         var $dialog = $('<div>').html(html);
       
-        $dialog.find('a.button').button().click(function(e) {
+        $dialog.find('a.button').button().filter(':not(.disabled)').click(function(e) {
           data._savemode = String(this.href).replace(/.+#/, '');
           data._notify = settings.itip_notify;
-          if ($dialog.find('input.confirm-attendees-donotify').length)
-            data._notify = $dialog.find('input.confirm-attendees-donotify').get(0).checked ? 1 : 0;
-          if (decline && $dialog.find('input.confirm-attendees-decline:checked').length)
-            data.decline = 1;
-          update_event(action, data);
+
+          // open event edit dialog when saving as new
+          if (data._savemode == 'new') {
+            event._savemode = 'new';
+            event_edit_dialog('edit', event);
+            fc.fullCalendar('refetchEvents');
+          }
+          else {
+            if ($dialog.find('input.confirm-attendees-donotify').length)
+              data._notify = $dialog.find('input.confirm-attendees-donotify').get(0).checked ? 1 : 0;
+            if (decline && $dialog.find('input.confirm-attendees-decline:checked').length)
+              data.decline = 1;
+            update_event(action, data);
+          }
+
           $dialog.dialog("close");
           return false;
         });
diff --git a/plugins/calendar/drivers/kolab/kolab_driver.php b/plugins/calendar/drivers/kolab/kolab_driver.php
index 04e96dc..8a48405 100644
--- a/plugins/calendar/drivers/kolab/kolab_driver.php
+++ b/plugins/calendar/drivers/kolab/kolab_driver.php
@@ -889,15 +889,16 @@ class kolab_driver extends calendar_driver
         // save submitted data as new (non-recurring) event
         $event['recurrence'] = array();
         $event['uid'] = $this->cal->generate_uid();
-        unset($event['recurrence_id'], $event['id']);
+        unset($event['recurrence_id'], $event['id'], $event['_savemode'], $event['_fromcalendar'], $event['_identity'], $event['_notify']);
 
         // copy attachment data to new event
         foreach ((array)$event['attachments'] as $idx => $attachment) {
-          if (!$attachment['data'])
-            $attachment['data'] = $fromcalendar->get_attachment_body($attachment['id'], $event);
+          if (!$attachment['content'])
+            $event['attachments'][$idx]['content'] = $this->get_attachment_body($attachment['id'], $master);
         }
-        
-        $success = $storage->insert_event($event);
+
+        if ($success = $storage->insert_event($event))
+          $success = $event['uid'];
         break;
 
       case 'future':
@@ -907,7 +908,7 @@ class kolab_driver extends calendar_driver
         $event['thisandfuture'] = $savemode == 'future';
 
         // remove some internal properties which should not be saved
-        unset($event['_savemode'], $event['_fromcalendar'], $event['_identity']);
+        unset($event['_savemode'], $event['_fromcalendar'], $event['_identity'], $event['_notify']);
 
         // save properties to a recurrence exception instance
         if ($old['recurrence_id']) {
diff --git a/plugins/calendar/localization/en_US.inc b/plugins/calendar/localization/en_US.inc
index ebea976..85e7e7c 100644
--- a/plugins/calendar/localization/en_US.inc
+++ b/plugins/calendar/localization/en_US.inc
@@ -261,6 +261,7 @@ $labels['changeeventconfirm'] = 'Change event';
 $labels['removeeventconfirm'] = 'Delete event';
 $labels['changerecurringeventwarning'] = 'This is a recurring event. Would you like to edit the current event only, this and all future occurences, all occurences or save it as a new event?';
 $labels['removerecurringeventwarning'] = 'This is a recurring event. Would you like to delete the current event only, this and all future occurences or all occurences of this event?';
+$labels['removerecurringallonly'] = 'This is a recurring event. As a participant, you can only delete the entire event with all occurences.';
 $labels['currentevent'] = 'Current';
 $labels['futurevents'] = 'Future';
 $labels['allevents'] = 'All';
diff --git a/plugins/calendar/skins/larry/calendar.css b/plugins/calendar/skins/larry/calendar.css
index a239f2b..50f8b64 100644
--- a/plugins/calendar/skins/larry/calendar.css
+++ b/plugins/calendar/skins/larry/calendar.css
@@ -1056,6 +1056,7 @@ td.topalign {
 .event-update-confirm a.button {
 	margin: 0 0.5em 0 0.2em;
 	min-width: 5em;
+	text-align: center;
 }
 
 #event-rsvp,


commit 7fe08b2814ed99dc1cd186be16746e0be3c85140
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Wed Jan 28 17:27:16 2015 +0100

    Cleanup recurrence exception data when saving events (#4318)

diff --git a/plugins/libkolab/lib/kolab_format_event.php b/plugins/libkolab/lib/kolab_format_event.php
index da0e43a..8cad89a 100644
--- a/plugins/libkolab/lib/kolab_format_event.php
+++ b/plugins/libkolab/lib/kolab_format_event.php
@@ -96,11 +96,13 @@ class kolab_format_event extends kolab_format_xcal
         // save recurrence exceptions
         if (is_array($object['recurrence']) && $object['recurrence']['EXCEPTIONS']) {
             $vexceptions = new vectorevent;
-            foreach((array)$object['recurrence']['EXCEPTIONS'] as $exception) {
+            foreach((array)$object['recurrence']['EXCEPTIONS'] as $i => $exception) {
                 $exevent = new kolab_format_event;
-                $exevent->set($this->compact_exception($exception));  // only save differing values
+                $exevent->set(($compacted = $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);
+                // write cleaned-up exception data back to memory/cache
+                $object['recurrence']['EXCEPTIONS'][$i] = $this->expand_exception($compacted, $object);
             }
             $this->obj->setExceptions($vexceptions);
         }
@@ -207,7 +209,7 @@ class kolab_format_event extends kolab_format_xcal
     /**
      * Remove some attributes from the exception container
      */
-    private function compact_exception($exception)
+    private function compact_exception($exception, $master)
     {
       $forbidden = array('recurrence','organizer','attendees','sequence');
 
@@ -217,6 +219,12 @@ 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) {
+          unset($exception[$prop]);
+        }
+      }
+
       return $exception;
     }
 




More information about the commits mailing list