5 commits - plugins/calendar plugins/libcalendaring plugins/libkolab

Thomas Brüderli bruederli at kolabsys.com
Thu Oct 17 14:35:50 CEST 2013


 plugins/calendar/calendar.php                                   |   25 
 plugins/calendar/calendar_ui.js                                 |    7 
 plugins/calendar/drivers/kolab/kolab_calendar.php               |    2 
 plugins/libcalendaring/lib/Sabre/VObject/Parameter.php          |    6 
 plugins/libcalendaring/lib/Sabre/VObject/Property.php           |    2 
 plugins/libcalendaring/lib/Sabre/VObject/RecurrenceIterator.php |  158 ++--
 plugins/libcalendaring/lib/Sabre/VObject/Version.php            |    2 
 plugins/libcalendaring/libvcalendar.php                         |  188 ++++-
 plugins/libcalendaring/tests/libvcalendar.php                   |   16 
 plugins/libcalendaring/tests/resources/attachment.ics           |  344 ++++++++++
 plugins/libkolab/lib/kolab_format_xcal.php                      |    2 
 plugins/libkolab/lib/kolab_storage_cache.php                    |    4 
 12 files changed, 655 insertions(+), 101 deletions(-)

New commits:
commit 8039a2f6e5a1050bbeeddb746cd6515cf268da51
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Thu Oct 17 14:32:50 2013 +0200

    Use new ical parser iterator to import large files and avoid htp connection timeouts

diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php
index de4645e..a31bec7 100644
--- a/plugins/calendar/calendar.php
+++ b/plugins/calendar/calendar.php
@@ -968,7 +968,7 @@ class calendar extends rcube_plugin
       rcube_upload_progress();
     }
 
-    $calendar = get_input_value('calendar', RCUBE_INPUT_GPC);
+    @set_time_limit(0);
 
     // process uploaded file if there is no error
     $err = $_FILES['_data']['error'];
@@ -976,22 +976,23 @@ class calendar extends rcube_plugin
     if (!$err && $_FILES['_data']['tmp_name']) {
       $calendar = get_input_value('calendar', RCUBE_INPUT_GPC);
       $rangestart = $_REQUEST['_range'] ? date_create("now -" . intval($_REQUEST['_range']) . " months") : 0;
-      $count = $errors = 0;
-
-      try {
-          $events = $this->get_ical()->import_from_file($_FILES['_data']['tmp_name'], 'UTF-8', true);
-      }
-      catch (Exception $e) {
-          $errors = 1;
-          $msg = $e->getMessage();
-          $events = array();
-      }
+      $user_email = $this->rc->user->get_username();
+
+      $ical = $this->get_ical();
+      $errors = !$ical->fopen($_FILES['_data']['tmp_name']);
+      $count = $i = 0;
+      foreach ($ical as $event) {
+        // keep the browser connection alive on long import jobs
+        if (++$i > 100 && $i % 100 == 0) {
+            echo "<!-- -->";
+            ob_flush();
+        }
 
-      foreach ($events as $event) {
         // TODO: correctly handle recurring events which start before $rangestart
         if ($event['end'] < $rangestart && (!$event['recurrence'] || ($event['recurrence']['until'] && $event['recurrence']['until'] < $rangestart)))
           continue;
 
+        $event['_owner'] = $user_email;
         $event['calendar'] = $calendar;
         if ($this->driver->new_event($event)) {
           $count++;
diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js
index d627253..21aedc1 100644
--- a/plugins/calendar/calendar_ui.js
+++ b/plugins/calendar/calendar_ui.js
@@ -1970,10 +1970,15 @@ function rcube_calendar_ui(settings)
               rcmail.display_message(rcmail.get_label('importerror', 'calendar'), 'error');
           });
 
-          // display upload indicator
+          // display upload indicator (with extended timeout)
+          var timeout = rcmail.env.request_timeout;
+          rcmail.env.request_timeout = 600;
           me.import_succeeded = null;
           me.saving_lock = rcmail.set_busy(true, 'uploading');
           $('.ui-dialog-buttonpane button', $dialog.parent()).button('disable');
+
+          // restore settings
+          rcmail.env.request_timeout = timeout;
         }
       };
       


commit 7af1dda119d0eed71f9506fd1628bd368d74fcd5
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Thu Oct 17 14:21:10 2013 +0200

    Only save email alarms if the owner's email address is available

diff --git a/plugins/libkolab/lib/kolab_format_xcal.php b/plugins/libkolab/lib/kolab_format_xcal.php
index 284a068..500dfa2 100644
--- a/plugins/libkolab/lib/kolab_format_xcal.php
+++ b/plugins/libkolab/lib/kolab_format_xcal.php
@@ -373,7 +373,7 @@ abstract class kolab_format_xcal extends kolab_format
         if ($object['alarms']) {
             list($offset, $type) = explode(":", $object['alarms']);
 
-            if ($type == 'EMAIL') {  // email alarms implicitly go to event owner
+            if ($type == 'EMAIL' && !empty($object['_owner'])) {  // email alarms implicitly go to event owner
                 $recipients = new vectorcontactref;
                 $recipients->push(new ContactReference(ContactReference::EmailReference, $object['_owner']));
                 $alarm = new Alarm($object['title'], strval($object['description']), $recipients);


commit 595f1b6a8c8ad2416607d4104924af9df013cd99
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Thu Oct 17 13:11:46 2013 +0200

    Only keep the last created object in memory (#2353)

diff --git a/plugins/calendar/drivers/kolab/kolab_calendar.php b/plugins/calendar/drivers/kolab/kolab_calendar.php
index d238e90..7caf0d0 100644
--- a/plugins/calendar/drivers/kolab/kolab_calendar.php
+++ b/plugins/calendar/drivers/kolab/kolab_calendar.php
@@ -317,7 +317,7 @@ class kolab_calendar
     }
     else {
       $event['id'] = $event['uid'];
-      $this->events[$event['uid']] = $this->_to_rcube_event($object);
+      $this->events = array($event['uid'] => $this->_to_rcube_event($object));
     }
     
     return $saved;
diff --git a/plugins/libkolab/lib/kolab_storage_cache.php b/plugins/libkolab/lib/kolab_storage_cache.php
index ce7c45b..905231b 100644
--- a/plugins/libkolab/lib/kolab_storage_cache.php
+++ b/plugins/libkolab/lib/kolab_storage_cache.php
@@ -265,8 +265,8 @@ class kolab_storage_cache
         }
 
         // keep a copy in memory for fast access
-        $this->objects[$msguid] = $object;
-        $this->uid2msg[$object['uid']] = $msguid;
+        $this->objects = array($msguid => $object);
+        $this->uid2msg = array($object['uid'] => $msguid);
     }
 
 


commit 5aa78e41f76c599ba9d4a1eeb4c046597bbe11ba
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Thu Oct 17 13:10:19 2013 +0200

    Refactored ical parser to allow iteration over large files (#2353)

diff --git a/plugins/libcalendaring/libvcalendar.php b/plugins/libcalendaring/libvcalendar.php
index 245f819..49f197e 100644
--- a/plugins/libcalendaring/libvcalendar.php
+++ b/plugins/libcalendaring/libvcalendar.php
@@ -37,13 +37,17 @@ if (!class_exists('\Sabre\VObject\Reader')) {
  * and place the lib files in this plugin's lib directory
  *
  */
-class libvcalendar
+class libvcalendar implements Iterator
 {
     private $timezone;
     private $attach_uri = null;
     private $prodid = '-//Roundcube//Roundcube libcalendaring//Sabre//Sabre VObject//EN';
     private $type_component_map = array('event' => 'VEVENT', 'task' => 'VTODO');
     private $attendee_keymap = array('name' => 'CN', 'status' => 'PARTSTAT', 'role' => 'ROLE', 'cutype' => 'CUTYPE', 'rsvp' => 'RSVP');
+    private $iteratorkey = 0;
+    private $charset;
+    private $forward_exceptions;
+    private $fp;
 
     public $method;
     public $agent = '';
@@ -98,6 +102,13 @@ class libvcalendar
     {
         $this->method = '';
         $this->objects = array();
+        $this->freebusy = array();
+        $this->iteratorkey = 0;
+
+        if ($this->fp) {
+            fclose($this->fp);
+            $this->fp = null;
+        }
     }
 
     /**
@@ -108,17 +119,19 @@ class libvcalendar
     * @param  boolean True if parsing exceptions should be forwarded to the caller
     * @return array List of events extracted from the input
     */
-    public function import($vcal, $charset = 'UTF-8', $forward_exceptions = false)
+    public function import($vcal, $charset = 'UTF-8', $forward_exceptions = false, $memcheck = true)
     {
         // TODO: convert charset to UTF-8 if other
 
         try {
             // estimate the memory usage and try to avoid fatal errors when allowed memory gets exhausted
-            $count = substr_count($vcal, 'BEGIN:VEVENT');
-            $expected_memory = $count * 70*1024;  // assume ~ 70K per event (empirically determined)
+            if ($memcheck) {
+                $count = substr_count($vcal, 'BEGIN:VEVENT');
+                $expected_memory = $count * 70*1024;  // assume ~ 70K per event (empirically determined)
 
-            if (!rcube_utils::mem_check($expected_memory)) {
-                throw new Exception("iCal file too big");
+                if (!rcube_utils::mem_check($expected_memory)) {
+                    throw new Exception("iCal file too big");
+                }
             }
 
             $vobject = VObject\Reader::read($vcal, VObject\Reader::OPTION_FORGIVING | VObject\Reader::OPTION_IGNORE_INVALID_LINES);
@@ -126,15 +139,16 @@ class libvcalendar
                 return $this->import_from_vobject($vobject);
         }
         catch (Exception $e) {
-            rcube::raise_error(array(
-                'code' => 600, 'type' => 'php',
-                'file' => __FILE__, 'line' => __LINE__,
-                'message' => "iCal data parse error: " . $e->getMessage()),
-                true, false);
-
             if ($forward_exceptions) {
                 throw $e;
             }
+            else {
+                rcube::raise_error(array(
+                    'code' => 600, 'type' => 'php',
+                    'file' => __FILE__, 'line' => __LINE__,
+                    'message' => "iCal data parse error: " . $e->getMessage()),
+                    true, false);
+            }
         }
 
         return array();
@@ -150,17 +164,118 @@ class libvcalendar
     */
     public function import_from_file($filepath, $charset = 'UTF-8', $forward_exceptions = false)
     {
-        $this->objects = array();
-        $fp = fopen($filepath, 'r');
+        if ($this->fopen($filepath, $charset, $forward_exceptions)) {
+            while ($this->_parse_next(false)) {
+                // nop
+            }
+
+            fclose($this->fp);
+            $this->fp = null;
+        }
+
+        return $this->objects;
+    }
+
+    /**
+     * Open a file to read iCalendar events sequentially
+     *
+     * @param  string File path to read from
+     * @param  string Input charset (from envelope)
+     * @param  boolean True if parsing exceptions should be forwarded to the caller
+     * @return boolean True if file contents are considered valid
+     */
+    public function fopen($filepath, $charset = 'UTF-8', $forward_exceptions = false)
+    {
+        $this->reset();
+
+        // just to be sure...
+        @ini_set('auto_detect_line_endings', true);
+
+        $this->charset = $charset;
+        $this->forward_exceptions = $forward_exceptions;
+        $this->fp = fopen($filepath, 'r');
 
         // check file content first
-        $begin = fread($fp, 1024);
+        $begin = fread($this->fp, 1024);
         if (!preg_match('/BEGIN:VCALENDAR/i', $begin)) {
-            return $this->objects;
+            return false;
         }
-        fclose($fp);
 
-        return $this->import(file_get_contents($filepath), $charset, $forward_exceptions);
+        // read vcalendar header (with timezone defintion)
+        $this->vhead = '';
+        fseek($this->fp, 0);
+        while (($line = fgets($this->fp, 512)) !== false) {
+            if (preg_match('/BEGIN:(VEVENT|VTODO|VFREEBUSY)/i', $line))
+                break;
+            $this->vhead .= $line;
+        }
+        fseek($this->fp, -strlen($line), SEEK_CUR);
+
+        return $this->_parse_next();
+    }
+
+    /**
+     * Parse the next event/todo/freebusy object from the input file
+     */
+    private function _parse_next($reset = true)
+    {
+        if ($reset) {
+            $this->iteratorkey = 0;
+            $this->objects = array();
+            $this->freebusy = array();
+        }
+
+        $next = $this->_next_component();
+        $buffer = $next;
+
+        // load the next component(s) too, as they could contain recurrence exceptions
+        while (preg_match('/(RRULE|RECURRENCE-ID)[:;]/i', $next)) {
+            $next = $this->_next_component();
+            $buffer .= $next;
+        }
+
+        // parse the vevent block surrounded with the vcalendar heading
+        if (strlen($buffer) && preg_match('/BEGIN:(VEVENT|VTODO|VFREEBUSY)/i', $buffer)) {
+            try {
+                $this->import($this->vhead . $buffer . "END:VCALENDAR", $this->charset, true, false);
+            }
+            catch (Exception $e) {
+                if ($this->forward_exceptions) {
+                    throw new VObject\ParseException($e->getMessage() . " in\n" . $buffer);
+                }
+                else {
+                    // write the failing section to error log
+                    rcube::raise_error(array(
+                        'code' => 600, 'type' => 'php',
+                        'file' => __FILE__, 'line' => __LINE__,
+                        'message' => $e->getMessage() . " in\n" . $buffer),
+                        true, false);
+                }
+
+                // advance to next
+                return $this->_parse_next($reset);
+            }
+
+            return count($this->objects) > 0;
+        }
+
+        return false;
+    }
+
+    /**
+     * Helper method to read the next calendar component from the file
+     */
+    private function _next_component()
+    {
+        $buffer = '';
+        while (($line = fgets($this->fp, 1024)) !== false) {
+            $buffer .= $line;
+            if (preg_match('/END:(VEVENT|VTODO|VFREEBUSY)/i', $line)) {
+                break;
+            }
+        }
+
+        return $buffer;
     }
 
     /**
@@ -171,7 +286,7 @@ class libvcalendar
      */
     public function import_from_vobject($vobject)
     {
-        $this->objects = $this->freebusy = $seen = array();
+        $seen = array();
 
         if ($vobject->name == 'VCALENDAR') {
             $this->method = strval($vobject->METHOD);
@@ -853,6 +968,41 @@ class libvcalendar
         }
     }
 
+
+    /*** Implement PHP 5 Iterator interface to make foreach work ***/
+
+    function current()
+    {
+        return $this->objects[$this->iteratorkey];
+    }
+
+    function key()
+    {
+        return $this->iteratorkey;
+    }
+
+    function next()
+    {
+        $this->iteratorkey++;
+
+        // read next chunk if we're reading from a file
+        if (!$this->objects[$this->iteratorkey] && $this->fp) {
+            $this->_parse_next(true);
+        }
+
+        return $this->valid();
+    }
+
+    function rewind()
+    {
+        $this->iteratorkey = 0;
+    }
+
+    function valid()
+    {
+        return !empty($this->objects[$this->iteratorkey]);
+    }
+
 }
 
 
diff --git a/plugins/libcalendaring/tests/libvcalendar.php b/plugins/libcalendaring/tests/libvcalendar.php
index 4bb7941..a40ee00 100644
--- a/plugins/libcalendaring/tests/libvcalendar.php
+++ b/plugins/libcalendaring/tests/libvcalendar.php
@@ -171,6 +171,22 @@ class libvcalendar_test extends PHPUnit_Framework_TestCase
     }
 
     /**
+     * @depends test_import_from_file
+     */
+    function test_attachment()
+    {
+        $ical = new libvcalendar();
+
+        $events = $ical->import_from_file(__DIR__ . '/resources/attachment.ics', 'UTF-8');
+        $event = $events[0];
+
+        $this->assertEquals(2, count($events));
+        $this->assertEquals(1, count($event['attachments']));
+        $this->assertEquals('image/png', $event['attachments'][0]['mimetype']);
+        $this->assertEquals('500px-Opensource.svg.png', $event['attachments'][0]['name']);
+    }
+
+    /**
      * @depends test_import
      */
     function test_freebusy()
diff --git a/plugins/libcalendaring/tests/resources/attachment.ics b/plugins/libcalendaring/tests/resources/attachment.ics
new file mode 100644
index 0000000..827cc76
--- /dev/null
+++ b/plugins/libcalendaring/tests/resources/attachment.ics
@@ -0,0 +1,344 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Roundcube//Roundcube libcalendaring 1.0-git//Sabre//Sabre VObject
+  2.1.3//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+UID:93B331265CF47061F888BC0A3F0FBD7F-FCBB6C4091F28CA0
+DTSTAMP;VALUE=DATE-TIME:20131017T084408Z
+CREATED;VALUE=DATE-TIME:20131017T083610Z
+LAST-MODIFIED;VALUE=DATE-TIME:20131017T083610Z
+DTSTART;VALUE=DATE-TIME;TZID=Europe/Zurich:20131017T110000
+DTEND;VALUE=DATE-TIME;TZID=Europe/Zurich:20131017T120000
+SUMMARY:Event with attachment
+LOCATION:Test lab
+TRANSP:OPAQUE
+CLASS:PUBLIC
+ORGANIZER;CN=Bruederli:mailto:thomas.bruederli at example.org
+ATTACH;VALUE=BINARY;ENCODING=BASE64;FMTTYPE=image/png;X-LABEL=500px-Opensou
+ rce.svg.png:iVBORw0KGgoAAAANSUhEUgAAATwAAAE8CAYAAABdBQ0GAAAACXBIWXMAAAsTAA
+ ALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iA
+ lEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIis
+ r74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz
+ /SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAY
+ CdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgw
+ ABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEO
+ cqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9
+ eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZr
+ IPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFH
+ BPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t
+ 8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmS
+ cQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQi
+ iGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4
+ IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDua
+ g3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7Ei
+ rAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqE
+ u0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBoj
+ k8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt
+ 2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX
+ 6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlS
+ aVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z
+ /YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1
+ ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aed
+ pr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6
+ feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8
+ o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+
+ eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZn
+ w7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt
+ 6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRK
+ dPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7
+ XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/
+ 0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc
+ 5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+
+ qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpx
+ apLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkk
+ xTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty
+ 8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2
+ pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6w
+ tVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm
+ 6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+l
+ Q27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHt
+ xwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTrado
+ x7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fy
+ z4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdO
+ o8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9
+ zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDY
+ brnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bC
+ xh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0
+ hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAOB5JREFUeNrsnXl0HFeV
+ /7+3etGulmRJlm15keUt9jgGR1VyrLBDMoRlfmyBDBMYfjBsA2HffmEbtgFmhgkTwjJs2YAk7B
+ kCwSEkECSsKtmJ7XiRJVmWtViLtbWWVlcv7/eH2x4SYtWr7uru6u77OUcn58Svuqvve/Wte9+7
+ 7z4SQoBhGKYQUNgEDMOw4DEMw7DgMQzDsOAxDMOw4DEMw7DgMQzDsOAxDMOw4DEMw7DgMQzDsO
+ AxDMOw4DEMw4LHMAzDgscwDJPbeJO5qK6uji3HSKNpWoCIFI/H4zFNs/JJb1xFMYloAQC8Xu9C
+ e3u7yRZjZJiYmLB9DSVTHooFr3Bpa2vzR6PRdQA2xOPxDUS0SgixAsBf/tUm/iqT/BoTwORT/s
+ 4BOEdE5wCcEUKcjsVipw8ePDjJvcKCx4LHpAKpqrqBiHYB2CmE2AygCcAGAKvhrqmQOQCnAZwC
+ cIqIjgM4FA6Hnzh06NAidyULHgsec5Fdu3aV+v3+XQCeQUS7hBA7AfxNCt6ZW4gD6ANwCMARIj
+ qsKMqB/fv3D3Kvs+Cx4BUILS0taxRFeTaAKwHsFULsQpJzuTnKKIA/E9Gj8Xi8o6am5uC+ffsi
+ PDJY8Fjw8oDW1tZNQohrhBBtANoArGOrPIkQgC4AjwL4g8/n+2NHR8cSm4UFjwUvR0LUoqKi5w
+ ohXgzgbwFsYqvYYgHA74joASJ6oLOz8zSbhAWPre0i9uzZszIWi70KwN8BeDaAYraKY5wgol8L
+ IX5mGEYHAD71igWPyTR79+6tjUQirwJwHYDnAPCwVdLOEIAfCyHu6erq0ln8WPCYNNLW1lYeiU
+ SuE0JcB+AFcNlig6Io8Hq98Hq98Pl8UBQFinI+g8XjOa/HRHTx/wkhEI/HAQDxeBwXxmIsFkMs
+ FkMkEkE0GkU0GnVjd5wmonsB3KXr+hEenSx4jENomqYB+CchxOsAlGfjHnw+H4qKiuD3+1FUVH
+ Txz+fzXRS5C6LmNEIIRKNRRCIRxGIxhMNhmKaJpaUlmKaJcDiMcDiMLJ6x3AngO36//+729vZ5
+ HrEseIx9kQsAuEEI8U8ALs+ksJWVlaG4uBilpaUoLS1FcXFx2sTMSVE0TROhUAiLi4tP+m8GhX
+ AewA8BfNswjC4exSx4jAWqqm4novcLIf4eQEm6Q9Dy8nKUl5ejoqICpaWl8Pv9eWXPeDyOUCiE
+ +fl5zM3NYX5+HuFwOBNf/RgRfaWqquoezvNjwWP+2qN7HoAPCCGuBUDp+A6Px4PKykpUVlaivL
+ wcZWVlIKKCs3U4HL4ogMFgEKFQKJ1fN0hEt8ZisW8dOHBghkc6C17BcvXVV/ump6evI6L3CSGu
+ SMd3lJaWIhAIIBAIoKKi4uKCAfNkAZydnb34F4vF0vE1c0T0XSHEzYZhDLDVWfAKhra2Nq9pmj
+ cA+ATOb8p3DCJCIBBAdXU1AoEAioqK2OA2Q+C5uTnMzMxgeno6HeFvlIhuF0J8loWPBS/fhU4x
+ TfN1AD4FYIuTIldZWYmamhpUV1fD5/OxsR1ACIH5+XlMTk5ienoapuloqT6TiL4Tj8e/0NXVNc
+ zWZsHLJ0jTtFcJIT4NYIdTIldRUYGamhrU1NSwyGVA/Obm5jA1NYXJyUkncwNDRPQtRVG+uH//
+ /jG2NAteTqNp2lVCiK8C2O3E5/n9fqxYsQJ1dXUoKSlhA2cp7J2amsLExASCwaBTHzsP4ItCiK
+ 90dXWF2MoseLkmdOuEEF8E8DqkuOp6IWStr69HVVUVLzy4iFAohImJCUxOTjoV8p4hoo/qun43
+ eOsaC57bSWz/+qgQ4v1IMY/O4/Ggrq4ODQ0NvPiQA17f9PQ0RkdHMT/vyGaLR4UQ7+nq6nqMrc
+ uC51av7johxL8hxZpzfr8fDQ0NqKurg9frZcPmGMFgEKOjo5iZmUl1l0eciL4fjUY/wmd3sOC5
+ SegahRBfB/CyVD6nrKwMq1atQk1NTUEmBOcbi4uLGBsbw7lz5y4WSUiScQDvMQzjbrYqC142w1
+ fFNM23A/hXpHAGRFlZGRobG1FVVcVGzUNM08TIyAgmJiZSEj4iuh/A23VdH2KrsuBl2qvbKoT4
+ DoCrkv2MkpISNDY2orq6mj26AiAcDl8UvhRC3Tki+pjP5/tGe3t7vJDtyYKXGa/Oa5rmBwF8Gk
+ BSKwklJSVYs2YNh64FytLSEkZGRnDu3LlUhO9RAG82DKOHBY8FLy2oqtoM4C4Ae5K53uv1Yu3a
+ tairq2OhY7C4uIjBwUHMzMwk/REAPmgYxjdRgCksLHjpFbs3AfgqgAq71yqKgpUrV2L16tW86s
+ r8FbOzsxgYGEilast9iqK8pbOzc6KQ7MaClwZaW1urhRDfEkK8Jpnrq6ursW7dOhQX87k6zKWJ
+ x+MYGxvDyMhIstvWRgG8yTCMB1jwWPCSQtO05wkhbgew1u61xcXF2LBhAwKBAD/NjDTRaBSDg4
+ PJLmwIAP/l8/k+Wghn67LgOQepqnoTgH8BYGsfFxFh1apVWLNmDW8BY5Jmbm4Op06dwtJSUrr1
+ GIBXGYbRz4LHgmfl1QWEEHciiSTisrIyNDU1oaysjJ9YxpEwd3h4GGfPnk3G25skotfruv5bFj
+ wWvKdlz549fxOLxX4GYLOd6xRFQWNjIxoaGnj1lXGcxcVFnDp1CgsLC3YvjQH4lGEYX0AeruKy
+ 4KWAqqqvBfAd2DwGsbS0FBs3bmSvjkm7tzc0NITR0dFkvL37fD7fDR0dHcF8sgkLXnKQpmmfF0
+ J8zNZFRKivr8e6det4ro7JGLOzs+jv70+m9PwJAC81DKOPBa9ABW/37t1FXq/3tsQB19L4fD5s
+ 3LiR974yWSESiWBgYACTk7YLqIwT0d/pur6fBa/ABK+lpaWGiH4Jm3thA4EAmpubuaQ6k3XGxs
+ Zw5swZuwUJQkR0g67rPy1EwSvIWExV1WYi2m9X7FatWoWtW7ey2DGuYOXKlbjsssvsFoctEULc
+ q6rqBwrRZgXn4bW0tLQS0a8A1Mpe4/F40NTUhBUrVvBTxrgyxO3t7U3mfI1b/X7/jbladYU9PG
+ vP7rlE9Ds7YldcXIzt27ez2DGuxefzYdu2bWhoaLB76T9HIpE7tm7d6ikUWxWM4Kmq+mIA98NG
+ 2klVVRV27NiB0tJSfqoYd4dqRFi/fj2am5ttZQ0IIV5fWVn547a2Nj8LXv6I3SsA/AKAtHLV19
+ djy5YtXN2EySlqa2uxbds2u/PMrzBN8xe7du3K+zd73gueqqqvB3AvAKk3GBFh7dq1aGpq4l0T
+ TE5SUVGB7du3263Q82K/33+/pmkV+WybvBY8TdPeCOB2AFJumqIoaG5uxurVq/mpYXKaC3PPFR
+ W29Ou5QogH2trayvPVLnkreKqqvkYI8V0AUhOyFyZ+eXGCyRcujOmamho7l+01TfOX+Rre5qXg
+ qar6EpwvxS4ldn6/H9u2bbP7NmQY9z/gioJNmzbZTSV7flFR0b35uJCRd4KnqurzAfwYknN2F8
+ SOV2KZfIWI0NTUZCttRQjxEtM0f5BvKSt5JXiqqu4F8D8ASmTaFxUVYfv27SgpKeGngsl70Vu/
+ fr3d+elXV1ZWfhdA3qze5Y3gaZq2E8CvIZl6UlJSgu3bt9vdlsMwOc3atWuxdq2tEwveqGnaF1
+ nw3OXZ1Qshfg1A6gCJkpISbNu2DX6/n58ApuBYvXo11q9fbye8/bCqqu9gwXMBidWk+wA0yrQv
+ Li5msWMKnoaGBqxZs8bOJbdomvZyFrwssnXrVo/f7/8RgFaZ9hcWKFjsGAZobGzEqlWrZJt7hB
+ A/am1tVVnwskRlZeUtAKTeOn6/P5lSOgyT16xduxb19fWyzUvj8fjPNE1rzNXfm7OCp6rqOwFI
+ zStcSMDkw7AZ5skQETZs2IDaWukCQo1CiPtzNTE5JwUvkX7yn1J+uMeDLVu2cOoJwywjehs3bk
+ R1dbXsJZf7/f7vsOBlgD179qyEZDEAIkJzczPKy8t5VDOMxLNiY7fR9aqqvpcFL420tbV5Y7HY
+ vQCklpfWr19v563FMAWNx+PBpk2b7Mxzf1lV1Wex4KUJ0zS/DODZMm0bGhqwcuVKHsUMYwO/32
+ +nDqQPwL2apuVMeaGcETxVVV8D4H0ybaurq7Fu3ToevQyTBKWlpdi0aZNs5eQGAPe2tbXlRKXc
+ nBA8VVXXA/hvmbZlZWVobm7m4p0MkwKBQAAbNmyQaiuEaDNN8xMseA6QqNZwO4AqS//a58PmzZ
+ vh8Xh4xDJMitTV1dnJ0bspkT3BgpcKlZWVHwLwHKt2F1aZOLGYYZxj/fr1KCsrk2nqAfCDvXv3
+ VrLgJUliG8tnZNo2NjYiEAjwCGUYJwVCUbBlyxbZQ4E2RCKRr7HgJYGqqmXxePwunF8JWpbq6m
+ o7ewIZhrGB3++3My9+g6Zp17Pg2YSIvgxgi1W74uJibNy4kRcpGCaNBAIBNDbKbaEVQtzq1lQV
+ VwqeqqrPF0JY7pMlImzatInPjmWYDLBq1SrZaaNqIcS3WfAkaGlpKcH5FBRLl62xsVF2QpVhmN
+ SjLmzcuFF2Pu9aN4a2iguN+i8Amq3aVVZW8rwdw2QYv9+PpqYm2dD25t27d7vq3FNXCZ6mabsh
+ sZvC6/XyvB3DZInq6mrZ/Lx6r9f7FRa8p6Gtrc0rhPgOAMsJufXr13O+HcNkkXXr1knVlxRCvE
+ HTtBex4D2FSCTyAQDPlHm72ChWyDBMGvB4PGhqapKKsoQQ31RV1RWT7a4QPE3T1gkhPiljZDun
+ LTEMkz4qKytRV1cn03QjgI+z4P3vG+DfIHGe7Nq1azmUZRgXsXbtWtlDsd6nqmpTtu8364KXKC
+ D4Gqt2FRUVdjYyMwyTAbxer2zUVQTgPwpa8Nra2hQAX4FFzh0RSc8XMAyTWWpqamQri79C07Tn
+ FazgRSKRfwTQYtWuoaGBD+FhGBezbt06qYKhQoibEyXfCkvw9u7dWymE+LxVO5/Ph9WrV/OIYh
+ gXU1xcjIaGBpmmlwcCgX8qOMGLRqPvx/ny0MuyZs0a3ivLMDnA6tWrpRYwhBCfzlaaSlYEr6Wl
+ pUYIYbmjorS0lBcqGCZH8Hg8shVVVhLRuwpG8IjoQwAsK6OuX7+eFyoYJoeora2VOgdaCPGhbF
+ RHzrjgqapaD+DdVu2qq6tRWVnJI4hhcggikj0xcEUkErkx7wWPiD4MoMzKaGvWrOHRwzA5SEVF
+ BaqqqmSafqC1tbU6k/eWUcHTNG21EOKdVu1qamq4zh3D5DCSDkuVEOL9eSt4iYWKEvbuGCa/KS
+ 8vl0pGFkK854orrqjK1H1lTPASP+ptloH9ihWcZMwweeLlSSw6Vng8nrfkneAlflQFe3cMUxiU
+ lZWhpqZGxst7d1tbW0aSbTMieIninpYrs3V1dVJFBRmGyQ1Wr14t4+WtM03z7/NG8EzTvA7AOi
+ vvbuXKlTxCGCaPKC0tlT3p7L35FNJ+0KpBIBBAaWkpjxCGyTMk99g+U1XV5+e84Gma9mxIlG6X
+ NArDMDlGIBCQSjMjorSnqGTCw3urVYOysjJZt5dhmBxEZrpKCPG3mqY1pvM+0ip4ra2t1UKIVz
+ phDIZhcpcVK1bIHM/gEUK8OWcFTwhxAywSjf1+P1asWMEjgmHyGEVRZKet3pSohJ4W0pr7IoSw
+ TCisq6uTqpTK5A6maWJpaeniXzgcRiwWQzQahRACsVgM8XgcHo8HXq8XiqJAURT4fD4UFxc/6Y
+ /HRv5QW1uLwcFBxOPx5ZqtN03zagAP5JTgqap6JYCdy7UhItlj3hiXEovFsLCwgNnZWczPz2Nx
+ cRHRaFTq2kgkAqvxUVxcjLKyMlRWVqKiooLzNHMYr9eL6upqTE5OWvX7W3JO8IjozUKIZdsEAg
+ E+djEHCYVCmJqaQjAYxPz8vNUbO5UIAaFQCKFQCOfOnQMAFBUVobKyElVVVaiqqmIPMMeor6+3
+ FDwhxMv27Nmzcv/+/WM5IXi7d+8uEkK8WiacZXInTJ2ensa5c+cwPz+ftfsIh8OYmJjAxMQEvF
+ 4vampqLhad5GKx7ueCl760tLRcM388Hn81gFtzQvC8Xu81Qohl80z8fr9szSwmiywuLmJkZART
+ U1Ow8tgzTTQaxfj4OMbHx1FcXIxVq1ahtraWvT4Xc2Eaa3Bw0MrLe206BC9dI+M6qwYrVqzgge
+ li5ufncfLkSTzxxBOYnJx0ndg9laWlJfT39+Pw4cMYHR1FLBbjTnQptbW1Mt54W0tLi+OVRBxX
+ nL179xYLIV4u86MZdwpHd3c3jh49iunpadcL3dOFvAMDAzh06BDGx8dz7v4LAb/fL7PRQCGi17
+ he8CKRyLWwKANVUlLC+2ZdRjwex9DQEI4cOYKZmZmc/z2RSAT9/f04fvw4FhYWuINdhkzZKACO
+ C57jc3hE9Bqrtyp7d+5iZmYGp0+fRjgcTufXBAEsAFggolkAEEIU4fz5JlWJl6Tj43Fubg5Hjx
+ 5FfX091q5dC4/Hwx3uAqqrq6EoitUK/5Wapq3Tdf2MKwUvsTr7Upkfy2SfWCyGgYEBTExMOPmx
+ xwD8CcBxACeEECfn5uYGuru7LSfVrrjiiiqPx7NFCLENwFYAuwFcBaA8lRsSQmBsbAwzMzPYuH
+ Ejn4bnArxeLyorK62iCRJCvALAV10peF6v99lCiGUHZ2lpKZdwdwELCwvo7e21Sg+QYZaIfgbg
+ QSHEw4ZhjCb7QQcOHJgBoCf+AABXX321b3p6WiOi5ycG/zOT/fxwOIwTJ05gzZo1soUpmTSyYs
+ UKmemTa50UPEpmUvdS+XOapv27EOIDy127du1arF69mns7i4yPj2NgYCCVhOEYgN8CuFMI8cuu
+ rq5Qpu5d07SdiT3arweQ9ECqrKxEc3Mz/H4/D4gsRhgHDx60GodLQoiapxtjyUQmjnp4QogXW7
+ WRnKxk0kA8Hkd/f//FXQtJenO3CCFuTcWTSwVd148A+PDWrVs/FggEXiKE+AiAvXY/JxgM4ujR
+ o9i8eTPKy8t5cGQBj8eDqqoqTE1NLdesmIieD+B+V4W0mqatE0JsX65NSUkJ74XM4tu0t7c32R
+ XYCSK62ev1fq2joyPoht+TmBO8D8B9mqY9D8BNQogX2PkM0zRx4sQJbNq0iZPgs0QgELASPBDR
+ NU4JnpNpKdfK/Dgm80QiERw/fjwZsQsR0SdN09yg6/oX3CJ2T+P1Pazr+gsBtAF4zO6L4OTJk6
+ l4vUyKgicROV7r1Pc5JnhCiGus2vBbNPOEw2EcO3YsmVy0XwHYoev6Zw8dOrSYC7/VMIyOYDCo
+ ArgRwIyNsYtTp05hdHSUB0yGKSoqksnJbVZVtdk1gpco2PecZb9IUVBRUcE9nEGi0ShOnjxpdy
+ U2COB1hmG8zDCM/lz7zd3d3THDMG4hoh0AHrYjemfOnGFPz6VeXmIezx2CFw6HLwOwbHJdZWUl
+ 753NIBdCtcVFW87Z4wBaDMO4J9d/v67rI36//4VE9EmcX1WWEr3+/n7Mzs7yAMogMpGfEGKvE9
+ /liAIR0VVOqDjj2PQC+vr6MDc3Z6cPv+vz+a40DKMnX+zQ3t4e13X9s0T0IgBTMtfE43H09PRk
+ tQRWoVFeXi6zA+YqJ77LKZfrShY89zA0NITp6Wk7l3xW1/W3dHR0LOWjPXRdfxjAswAMynrHPT
+ 09ME2TB1MGUBRFZvfLJlVV690ieG3L/eOFswqY9DM7O4uzZ8/KNo8DuNEwjE/mu10Mwzjm8Xja
+ cH7rmyWmaaK/v5+rrWTQy0tVZzIieHv27FkJYNNybcrKyngbTwYwTRN9fX2yD2kMwOsNw7ilUO
+ yzf//+wVgs9mwAXTLtZ2Zm7Lw8mBSQWdCUmTpLu+DFYjHLyUTerJ1+LszbWR2Mc6E5Eb3VMIy7
+ C81OBw8enFQU5VoA3bLTA3bmQpnkKCsrs1zUdGLhwomQtsUhd5VJgdHRUQSD0nnB/0/X9e8Vqq
+ 06OzsniOiFAIZkXiSnTp3iCsppRlEUmXy8Z7S1taW0O8wJwdtt9UPKysq4R9NIOBzG8PCwVFsi
+ utkwjC8Wus10XR8iomsBWOagLC0tSduXSWtYWxyJRC7LtuBdvtw/lpaWcv5dmjl9+rSsB/IbXd
+ ffzxa7KHpHiOi1OL94Y+lB28xpZGwiGQk+I2uC19raWgeLEj1cyj29zMzMyO6RHfb5fG8AwMuO
+ Txa93wKw9HiFEBgYGOBV2zQioxVCiJ1ZE7x4PL7TiR/BJEcsFsPp06elmgK4vqOjg/dNPQ3BYP
+ CTAB6VaGd5iDSTPEVFRTIJyNkTPJkvZ8FLH2NjY1LnUBDRlw3DeJQt9vR0d3fHiOgfAFhurxga
+ GkqlcCqz/DiVqYaePcEjol1Wbbice3qIx+MYGxuTaToQDoc/xxazDG3PEJGlncLhMHt52Q1r17
+ S0tCRdRThVD+9yKxfV6/VyL6aBiYkJ2a1P78mV8k7Zpqqq6is4f/jQsoyMjPBcXvYED0SUtJeX
+ kuAJIS5j7y473p3kDoAHDcP4JVtMjn379kUAvNeq3dLSEnt5aUJyC+qOjAueqqoNAEqtPDzGea
+ ampmTPkP0sW8sehmHsw1+cmnYpeMtZepDUjKaMCx4RbbBqwydCpQdJ7+JRXqhIemxbzuUtLi4m
+ U0WakRA8q333RJR5wRNCWAoeV0hxHtM0pQpUEtHn2VrJoev6rwAcsmrH1ZHT8rKxdJSEEBszLn
+ gALAWPQ9r0eHcSE+aPJRJqmSTf55BIRp6amuLFizQg4ShtSPazOaTNw3CWiL7LlkqNWCz2cwDT
+ Vt62jYINjCQSulGtaVpSFYXTFtJ6PB74fD7uPYfDWYl5I9Pr9d7D1kqNgwcPhonoXqt2SZ7zy6
+ QYGco4XBkNadm7cx5Jb+I3vIXMobhWiDsd6hPGWQ8PQoh1mRa8ZbOd2btzHslClHewpZzBMIwO
+ AH3LtQmFQrJFVxlJJLWjLpOCRwBWLNeAd1g4j8TqbFgI8Ru2lHNOHhH9wsLT4IrIDiOpHUltL0
+ tK8Hbv3l1jdS0LnrOEw2GZZOM/d3V1hdhajoa1lod5s+BlRfAy5+H5fL4VVm0kyrwwNpAsPvkw
+ W8rx8OpRAFEH+oaRRFI7ViTz2UkJnhCiRmKgcM+x4OU8HR0dQViccsY7Lpz38Kx2W8hokGOCJ+
+ NOckjrfEhr9R7y+/2PsaWch4gOLvfvsViMD+121t4y+pE5Dw9AFQteZgmFLKfmhtrb2+fZUmnB
+ smTU0tISW8lhL8+C6kyGtJanbfDB25kVPCI6yVZKGz0seJn38iyoSOZz03acGC9aOEckEpE5la
+ ybLZUehBAnWPAyi8RJh0lpV1IXERGfrJ1hwZN4KHl3Rfo450QfMY56eElVF042pPU4cMOMJDJn
+ zhIRJ4OlCcMwFmBxdi0f7JNxwUtq72qyIa3l7l4+fNs5JB8mXrBIL3OpvpQYeSSmxJJaFU1Wlb
+ iyp8s8PCEE72JPL8vaNxqNsoUyS1kmBc8JhWYc9PA4pM2uh8eFQHNDPzjuzBOEELy1Jb2wfTM7
+ nnNL8HhOI+NvO55mSC/LrgrynHXmo5pMCh6vwWcQmRVvIqpkS6WVChY8V5FUxQYlk1/GpM/DE0
+ LwiUnppdQBL5xxLqRNyunikDYHkCl5TURr2VLpYc+ePSthMYfH1YFyQz+SFTzeR5NhwZMIa9ex
+ pdLmbVjalo8kzThJ1eRKVvAsa42zh+cckocTr2dLZU/w+NCqjHt4SeWdpk3wOBHTWSQ8iI1spb
+ QJXrNVG4nDoxlnBW82mc9Ndi8tC16GKS0ttWpSq2kah7XpYbeVB15SUsJWchAJ/cic4BGR5Zfx
+ ZuqMCx4AaGyptLCsXUtKSniV1lmP2tLDk9EgDmlzmLKyMpmBcjVbyllUVd0MoCnVvmEc9e4y6+
+ EpinLOoZtmJJH0Iq5hSzkLEVnaVNL7ZhwUPCHEeMYEr7OzcxoWqSlcENHxB0/mwVqnqup2tpaj
+ /K0T3jcjj+SBSKOZDGkBYIQFL7NUVFiX8SeiV7GlnOGKK66oEkK8YLk2Ho8H5eVcANxJZLSDiE
+ YyLXijDqg0Y4OqqioZV/8NbCln8Hg818GiKEMgEODq3lkQPCHE2YwKHhGdtYrDuUaYs5SXl8sc
+ X7dJVdUr2VqOYPnyqKzkmg3ZEDwAmRU8IcSwxb+zl+cwRCTl5QF4E1srNVpaWrYJIfZa9Ud1dT
+ Uby2EkdCMei8UyG9ISUZ9Vm3A4zL3nMCtWSB24/g+tra11bK2UXi7vB7BsrFpRUcFbytKAxJGX
+ wwcPHkxKXFLx8PocuHHGJoFAQOYhKxFC3MjWSo7Ey+IfrNrV1fE7JUse3qlkPzutgsceXnrCWh
+ kvTwjxdlVVOV8iubH9HlhUOPZ4PBzOpoFoNGo5h0dEvRkXvKKiolMAYix4mae+vl5mZbAWwAfY
+ WvbYs2fPShnvuLa2lreTZSeclXK2HBe89vZ2E8AQh7SZp7i4GDU1NTJNP5goXslIEovFPg2Lcu
+ 5EhIaGBjZWGpB0kjIf0iY4biV4nJqSHiQfuIp4PP4vbC05NE27DMBbrNrV1NRwOag0EQqFLNsQ
+ 0bFsCd4Ri7clh7Vpory8XDYR+S0tLS2tbDHr50gI8XVYnGhPRFi9ejVbK3uCF66qqjqRLcF73A
+ nFZpJj3bp1MnN5HiL67u7du7kG+fLe3dsAPNeqXW1tLRcLSCOLi5bngx3ft29f0vtWUxI8Ijri
+ wA9gkqSkpAS1tbUyTXd4vd6vssUuKXY7hRD/Yfnm8HjQ2NjIBksTkhHhkVS+IyXBS7iWERa87N
+ HY2Ciz3QxCiLepqvp6ttiT2bt3b6UQ4sewOIYRAFavXs2Jxmn27qzm/GWcrLQJXsK1XHYCcX5+
+ nnsyjfj9fqxbJ13Z/Vuapu1kq51n69atnkgkcieArVZtS0tLsWrVKjZaGpHRCiHEoawJXkJxDy
+ 7376Zp8sJFmqmtrZXdY1smhPi1pmkclwGorKy8BcDLJcY4Nm7cyFVRXCB4Pp/vYFYFD8ABqwYL
+ Cwvcm2mEiLBhwwbZRNhGIcS+Qt9rq6rqRwG8Q6btqlWruMhnBpDQid6Ojo5zqXxHyoInhNjvhH
+ IzqVFUVIT166WPpr1MCPGblpaWmkK0laqq7wDwBZm2JSUlWLNmDQ+wNCMTCRJRV6rfk7Lg+f3+
+ QwCWXZmYm5vjHs0AdXV1squ2EEJcQUQPFZqnp6rqewHcCotKKMD5VdnNmzdDURQeXGlGRiNknK
+ u0C157e3sUQJeVq8ol3zPDhg0b7JyR+ox4PP5IoczpaZr2CQD/KSN2ANDU1MTnzWaIYDBo2YaI
+ /px1wUvcyH4LZWYvL0N4PB5s2rRJKlUlwXYhRKemaXl7pu3u3buLVFW9TQjxGdlrGhoaZGsPMp
+ kRvCWfz/e4KwQPQLsTCs44Q2lpKTZt2mQnFFsthHhE07Tr8s0Wra2tdR6P53cA3ih7TXV1tZ1U
+ HyZFwuGwTKGRrkTBkuwLXiwW+yMsSkWx4GWWQCCApqYmO6kUJUKIe1RV/fquXbvyYu9Ua2vrC+
+ Px+CEAV8leU1FRgebmZk5BySCzs1Jnav/eie9yRPAOHDgwA4v0lFAoxPl4Gaa2thbr16+3+/C+
+ w+/3G6qq7s3V393W1lauqurN8Xh8HwDpbOGysjJs3ryZ69y5L5wFET3kGsGTVeCZmRnu3Qyzcu
+ VKu54eAGwH8CdN0769d+/e2lz6vZqmvdo0zeMA3gPJxYkLnt22bdvg8/l40GQQIYSMh7fo8/n2
+ O/F9jgkeEbHguZS6urpkRI+EEG+JRCJ9mqZ9WtO0gNvDV1VV/5zYF2tr1bmyshJbt261s9DDOM
+ Tc3Byi0ahVs0edmL8DLGp/2SEej/+JiEJY5iyAYDCIWCzGIUOWRM/v96O3t1dmgD1JD4QQnwJw
+ o6Zp/0lE3+zs7JxwSeiqRCKRFwkhborH489K1i4bNmzgXLssMT09LdPsQae+z7Fe7urqCll5ef
+ F4nBcvskggEMD27duTrdZbLYT4TDweH1RV9Y7W1lY1i2FrQFXV95qmeUII8QAA22JHRFi7di02
+ btzIYpdFZKI+IcT9rhO8xI39j1Wbqakp7uUsUlJSgh07dkjvyHgaigDcEI/HdVVV79i9e3dGk9
+ VUVX2tEKIX5xOINyfzGX6/H9u2bePKxVkmFArJpKP0dXV1nXDqOx0VPCL6HwDCyoWNx+Pc21nE
+ 6/WiubkZGzduTHV64Qav13sPbCwOpCh2VwP4Ec6fyJYU1dXV2LlzJyorK3kgZJnJyUmZZr9y8j
+ sdFTxd10cAPLZcm1gsJpt3w6SZurq6lB9+IcQLNE17aYZu+eZkxdXj8aCpqQlbtmzhxYkcEjwi
+ +qVrBe8vvDwnlJ3JAEVFRdi2bRuampqSFgIhxJ503+cVV1xRBWBbMteuWLECO3fuRH19PXe4S1
+ hYWJAJZ2eqqqr+5GrBA3CP5a+YmUEsFuNedwlEhPr6euzatUv2kO+nXp/2szg9Ho+w690VFxdj
+ 27Zt2LRpE4qK+AyjHPTufp7KgT0ZETxd148DOGwV1nJOnvvwer1oamrCjh07UF1dbefSgXTfm6
+ 7rswB6ZL3WDRs2YOfOnQgEAtyxLkMIIbV4KYS42+nvTtd6vOWNjo+Pc8+7lLKyMmzZskX2hK5Z
+ r9d7Z4Zu7UcyjS6//HKsXLmS001cyuzsrMw203G/3/97p787LSNCCGEZ1s7NzcnE8EwWkTlxjo
+ ju7ujoyFRH3gaLLAAigmma3Hku5tw5qSrtP03U2nS/4HV1dZ0C0Gnl1kr+cCYLRCIR2aTQ2zN1
+ T4Zh9AN4lMdV7mKaptTuCiL6YTq+P20+PxHdJqP0VudQMtlhampKJl+y2zCMP2f41r7P4yp3mZ
+ yclBlXPbqut6fj+9M5yfEjWJx1EQ6HefHCpUxMSG2XvS3T9+X3+38CYMFqXPEWRvchhJAdV9+z
+ mrpwneAlVtV+atWOFy/cx+LiosyReXEiuivT99be3j5PRJbjisNa9xEMBhEKhayaRYnojnTdQ7
+ qXsb5r1WB2dlZqcpxxnXf3oK7rQ1nyFCzD2unpac71dBmjo6MyzR5I7NjKPcEzDOOPAE5aublj
+ Y2M8GlwUdsjkSBHR7dm6R7/f/0cAp5drE4vFeEePi1haWpLaUkpE307nfaTbwxMAvmbVaHJy0m
+ 6NNiZNzM7OyqR1zMbj8V9k6x7b29vjAO6QGVeMe7w7iYWkMz6f71fpvI+0Z2YmVmvnrd7GPJfn
+ DmTmvojo7q6urlA27zORDrPsE8S5nu4gEonIjqtbEi+z3BU8XdfniMhyzmVsbIzLRrlgYMrkSA
+ kh7sj2vSZyPTknLwcYHx+XmU+dj8Vi30n3vWRk7w0R/ReAZdXMNE328rKMjdy7Dpfc8m0yHivn
+ 5GWPWCwmO0d/Z+L0w9wXvM7Ozl5IFPI7e/Yse3nuD2fvcMv9EhHn5OWAdxeJWBY8iRPRLZm4n0
+ zurv6SVQPTNHmiOUssLi5ifn7ecmBCYrEgUySmSzgnz6XE43HZVJRfJKos5Y/gJcKgR6zaDQ8P
+ s5eXBSRfNFnLvUslrOWcvOx5d5KFHP41U/eU0fo5RPQFqzbhcFg28ZVxCBuT+3e47d59Pt8fwD
+ l5riMWi+Hs2bNSL1HDMLryUvB0XX8QgGHVbmRkhN/IGUQ2904I8XO33XsijcGyHh8LXmYZHR2V
+ 9e4+n8n7yniFRCL6nFUb0zR590UGkfTu7s127t0ycE6ei4hEIrJzd48ahvGHvBY8XdfvA2BZUm
+ h0dJR3X2RocEqe/n6bW3+DYRh9kMjJ46mSzDAyMiL17BLR/8v0vWWrBvbHZR7E4eFhHj1pRrI+
+ 2UkX5d5d6uG5Xea3ck5eellaWpLKpyWi+3Vd/1Om7y8rgmcYxu8BPGjVbmxsjMOQDAieE2LiAn
+ 4MzsnLOkNDQzIvUBGPxz+RjfvL2iknRPRxWMy7CCFw5swZHkVpIhdz7y6FrutzAH7mhMAzyREM
+ BmXte29XV9djBSV4uq7rRGR52M/09LRUWRkmPd4dgN+5MPfuUtxm1WBqaoozANKADeckDOBj2b
+ rPbJ9j9xEAlit/AwMDnIychgEquTp7e678Jr/f/wgkcvJk6v0x9hgfH5epkg0ANycOYyo8wdN1
+ /QyAf7dqFwqFZJMYGUkkc++Cbsy9uxTt7e1SZed5q5mzmKaJoSGpIGDU5/N9IZv36oaTir8EwH
+ I5dmRkhBcwHETyob/Hxbl3l/JcbwPn5GWUM2fOyKaQfbyjoyOrq0ZZFzzDMBYAfNiqXTwex8DA
+ AI8uB4hGozmfe7fMeJLKyWMvzxlmZmZkV/oP+P3+72f7fhWXDNIfEdFDMsblgZo6smeDuj33bh
+ ksV5W5Tl7qxGIxnD59WqqpEOLt6a5mnDOCd/6lK94BwDLOGBgYQDgc5tGW5nBW5iB1t0JE90Ii
+ J29ubo4HQwoMDg7KPou3ZrJAQC4IHgzD6JGpphKNRnH69Gl+OydJKBSSyr1TFOXOXP2NiZw8y8
+ UWjhaSJxgMylYoH0rk3LoCxU1GjEajXwZgWQhwZmaGy8Gn0bsD8ND+/fsHc/ynWs4XcU5e0s8p
+ Tp06Jet03Jh4AbHgPZWDBw+GAbwZgOUoHBwc5AO87c8byArebbn+WxM5ecuucnFOXnL09/fLhr
+ I/MQzDVWlNituMaRjGn4noZqt2sVgMfX19nJBsA9ncO9M0f5HrvzWRk2cZlnNYa4/x8XHZl8Q4
+ gH922/0rbjSq1+v9OIBjVu0WFxc5VcX5cPaeQ4cO5YXrzDl5zmLneSOidxqG4bp5J1cKXkdHx5
+ KiKP8IwDKb0cYbp6CxkXt3e7785kROXrtDYX5BE4vF0NvbKxtR/VDX9Z+68XcobjVwZ2enAcnD
+ Pfr7+xEKhXhULkMB5N5dittkPF9e9XfsGTsrhHi3W3+H4mYj+/3+zwF4XMZ76enp4QrJKYazid
+ y7vHryEzl5y4bonJO3PKOjo7KVdQQRvbWrq8u1IZerBa+9vd0UQlwPiyRS4Hx+GefnXdo2knXv
+ 7sq33y5bJ4/D2qcnGAzaqUl5q67rv3Lz71HcbvCurq4TAN4lG7aNjIzwKE3uYX4oUb0m70gsXi
+ wL5+Q9vefb29sr60Q8FovFPuj236TkguENw7gNwA9l2g4PD8tOzhcEQgjZzd135KsNioqKHgaw
+ rJhzTt5f2+PkyZOIRCIyzeeJ6PpEHi0LnhP4fL53AOiTecD7+vpkQriCYHZ2ViZJNBgOh3+Wrz
+ ZI5ORJFRRg/vcZkk3sJ6J36rrenQu/zZsrndDR0RFUVfXVOJ9mUGr1durt7cVll12GoqKigh68
+ kpPNeZN7t8xDebsQ4iYAdKk2c3NzCIfDBT9mzpw5YydK+m9d15+U4O3m4zCVXOoIwzAeB/BW2f
+ mHkydPFvTKbTQalQrT8jmcvUBnZ2cvJHLyCv3s2rGxMdlDtAFgv9/vf3cu/T4l1zrEMIwfyGw9
+ A85nhttIlsw7pqampHLvdF1vLxCTWCZVF3JO3tTUlJ2dS2NE9Jr29nYzl36jkosdU1VV9WEAf5
+ RpOzs7i76+voIcxJLeyh3Is9y7S+Hz+Tgnz5nnJEJE1+XQaXa5LXj79u2LKIryagC9dt5chSR6
+ srl3hRDOXiBxngLn5D2F+fl59PT02ImE3qPr+h9z8bcqudpJnZ2dEwD+DoDUobVjY2MYHBwsmE
+ Es6d39Pl9z7y454BXFMqwtpJy8UCiEkydP2vm9XzIM4xs52/+53FmGYRwD8EoAUvMIZ8+exfDw
+ cN4PYiGE7GLF7SgwvF7v78E5eQCApaUlnDhxQjbXDgDuCwaDN+X0Cy/XO80wjN8T0btk2w8NDe
+ X9boxgMFjwuXeXQrZOnmQ6T86LnUR9xItBlWma13d3d+e066vkQ+fpuv5tAJ+RbT84OJjXnp5k
+ oYB78z33bhkP+HZYLNRIvjRyWuxs/L5eRVFelg/jRcmXTjQM41MApOcWhoaG8lL0ZHPvkEd175
+ IYKz0o0Jy8UChkV+yGFEV5cWLOPOdR8qkzg8Hgu4noB3ZEb3BwMK9WbyXr3vUWUO7dpTzcgju7
+ dnFxEcePH7cjdrNEdG0iaTsvyCvB6+7ujvl8vv8L4Ley14yMjORVWSnJlArLkC7f8Xq996CAcv
+ Lm5uZw/PhxOwsUiwCu1XX9SD71u5JvA7m9vd00TfOVAH4ve834+DhOnTqV8zsyOPdOnkLKyZud
+ nUV3d7edbZaLRPR/8rD6df4JHgAcOnRo0TTNlwH4gx3PyGY+Uk56d0T0cKHl3qUS1uZ6Tt7k5K
+ TdcR0C8Apd1x/Mxz5X8nUwJ0TvWkhuQbvwJjx27FhOrs7J1r1DHpw56xQ+n+8hSOTk5Wp9xZGR
+ EbtHmS4R0SsMw9iXr32u5POATiyj2xK9xcVFHDt2DAsLCzn1WyXTKOYKMfdumekPqbL2uRbWCi
+ HQ399vd0Hugmf323zucyXfB7VhGAumab6YiO6XvcY0TRw/fhwzMzP5Fs4WbO6dhcebNzl50WgU
+ 3d3dGB+3dSRsEMC1hmE8kO+drRTCiD506NCiz+d7JYB7ZK+5UOJ6eHjY9Su4NnLvOJz96xdiDx
+ F1WHlMueDlLS4u4ujRo5idnbVz2SSAFxiG8Ugh9LdSKAO7vb3dDAaDrwfw33ZCg6GhIfT29rp6
+ 4loy966v0HPvlsEyCXtiYsLVL77p6WkcO3YMS0tLdi4bIaJnGYbRVSgdrRTSqO7u7o4ZhvF2Iv
+ qcneumpqZw/Phxu4PJVeGsTOhWqORyTt6Fl3JPT4/dl/JxRVHadF0/Xkh9rRTg+Ba6rn+CiN4M
+ QDoLc2FhAUePHnVdFQ0bZ87eCeZpSeTk/cKhF0vGME0z2WmXRxRFaevs7DxdaH2tFOog13X9ew
+ BeivMTtlJEo1H09PTg9OnTrklSlnwIHzEMYwDMJSGi22Q8fbdMbQSDQRw9ejSZhbW7/H7/NZ2d
+ nQV5lqlSyIPcMIx9RHQVAFtiMDY2lsx8SVrCGckzZ7/PkrY8iZy8ZSvEuqFOnhACw8PDdks7IT
+ Gd8RnDMN6Qa+dQsOA56+kdURRFhY1dGRdC3CeeeMLu8r/jb3mZ3DshxM9Z0pYnkZPn6jp5F8o6
+ DQ0N2Q1h54noukRFoYKex1V4qJ8vF19dXf0iALfauS4Wi6G/vx8nT560+7bNWDhLRPcahrHAvS
+ yFZVibrZy88fFxPPHEEwgGg3YvPQWgTdf1n3D3suBdZN++fRHDMN6VWMywNaKnp6dx5MiRjE5q
+ 28i9u4N7V3qKoweAq3LywuEwuru70d/fb3v+kIgeisVimmEYh7l3WfAuFeJ+D8BVAHrsClBfXx
+ +6u7sz4gHYyL17lHvVlkhYenmZqJMnhMDo6CiOHDmSzMJEHMBnZ2dnrzl48OAk9yoLntWbvsvn
+ 87UQ0Y/tXjszM4MjR45gdHQ0rQ+FjZ0VnHtng0ROXmi5NktLSzKpQElzoVDnwMBAMqvCo0KIqw
+ 3D+GSunz/BgpdBOjo6grquXwfgnQBsLcfGYjEMDAwkO+diSSgUkvncuKIod3FP2u93AJaLPOko
+ /x6NRi+Om2SSnInoIQDP7Orqeoh7kgUvWW/vGx6PRwVwKNk3dU9Pj6NhrmzuXSEmljpERs+uFU
+ JgbGwMhw4dSjYyCBPRh30+39WGYYxy97HgpcT+/fuf8Pv9GoAvAbA9yqempnD48GEMDQ2l/JDY
+ yL27jXsuOfx+/+8ADFl58U7k5F1IID59+rSdisR/ySGPx9Oi6/q/JVJrGBa81GlvbzcNw/gogO
+ cA6LN7fTwex/DwMB5//HGMjo4mvVPDRu4d171Lvq/jkFjdTmW1dmFhASdOnMDx48eTrb0YA/Al
+ v9+v7d+//wnuNRa8dIW47QB2EdHXkcSCwIV5msOHDydVgUNy7ujHnHuX4oOhKJZh7dzcnO2piq
+ WlJfT29iZTxukv6QbwLMMwPlrIuyZY8DInegu6rv9zwtvrTuYzwuEwTp06dTF/T0b4otGoVLlx
+ Irqdeyk1Ojs7T8LBnLxQKIS+vj4cPnwYk5OTya7gRwB8QQjxTMMw/sy9xIKXaeF7NBaL7QLwWQ
+ BJvWn/8kEYHx9fNtSdmpri3LsMIvPisHpZXehfOy+2S6ADaDEM46aurq4Q9w4LXlY4ePBg2DCM
+ T3o8niusPAKrUKe/vx+HDx/G6Ojo0y5uSC5WFPyZsw6SdE7e3NwcTp486YTQzQN4r9/vv5J3TD
+ jwEkumI+rq6thyl7CnpmlvEEL8K4BVqXyQx+NBXV0dVq5cieLiYoRCIRw+bDne44qiNHM6inOo
+ qvoDAH+/XJv6+no0NTVdXEEfGxtzIjFZALhLCPGRrq6us7lks3TkKLLguZi2trZy0zQ/BeBGAP
+ 4UwyrU1NQgHA7LPEQPG4bxfO4B59A07RohhOXhNqtWrcLk5KQjRSSI6ACAd+m6vj8XbeZmweOQ
+ Ng20t7fPG4bxIUVRdkAia3/Z13zCa5D0GG5j6zuLz+d7EBY5eQBw9uxZJ8RumIje7PP5tFwVO7
+ fDgpdGOjs7ew3DeCURXQkbZ+MmyRyAn7LVHX95SeXkpcgMgI8IITbruv49TiBmwctpdF3fbxjG
+ c4joJQDSMvFMRD/h3Lv0QETpErwQgC/F4/EmwzC+zKuvLHj5Jny/9vv9zwTwKgCPO/zxHM6mr9
+ +6kcIK/NOwAODfPR5Pk2EYHz1w4MAMWzlDLy9etMie7TVNe5kQ4iYAWoqfdcowjE3gdJS0oarq
+ 2wB8M8WPCQL4mqIoN3d2dk7kq6140YJ5OoSu6/cZhtEK4BoAv01BsDj3Lv1h7d2wyMlbhhEiuk
+ kI0WQYxk35LHbs4TF2vIjtAN4L4AYAxZKXce5d5vrnhwCut3HJQSK62efz3VNIe145D4+xxd69
+ e2sjkcjbALwZQJOF5/H1xL5eJs20tLTsSOTIFS3TzMT5Q72/YRjGI4VoJxY8Jina2toU0zRfSE
+ RvFUK8HIDvKU0eIaKX67o+x9bKDJqmvVEI8XUApU/5px4i+rYQ4nbDMMbZUu4UVha8HGHPnj0r
+ Y7HY+wDUEdGSEOJRwzDuAc/dZSO03QzgeiK6TAhxGsCDhmE8zH2Rp4LHMAyTi/AqLcMwLHgMwz
+ AseAzDMCx4DMMwLHgMwzAseAzDMCx4DMMwLHgMwzAseAzDMCx4DMMwLHgMw7DgMQzDsOAxDMOw
+ 4DEMw7DgMQzDsOAxDMOw4DEMw7DgMQzDsOAxDMNI8/8HAE8wFAC1H96IAAAAAElFTkSuQmCC
+END:VEVENT
+
+BEGIN:VEVENT
+CREATED:20130718T073555Z
+UID:B968B885-08FB-40E5-B89E-6DA05F26AA79
+URL;VALUE=URI:http://en.wikipedia.org/wiki/Swiss_National_Day
+DTEND;VALUE=DATE:20130802
+TRANSP:TRANSPARENT
+SUMMARY:Swiss National Day
+DTSTART;VALUE=DATE:20130801
+DTSTAMP:20130718T074538Z
+SEQUENCE:2
+DESCRIPTION:German: Schweizer Bundesfeier\nFrench: Fête nationale Suisse
+ \nItalian: Festa nazionale svizzera\nRomansh: Fiasta naziunala Svizra
+END:VEVENT
+
+END:VCALENDAR


commit 406fd4d822699543eb550644aa6c3aabf4dbc59b
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Wed Oct 16 17:00:48 2013 +0200

    Update Sabre/VObject lib to version 2.1.3

diff --git a/plugins/libcalendaring/lib/Sabre/VObject/Parameter.php b/plugins/libcalendaring/lib/Sabre/VObject/Parameter.php
index 37c9fd0..72bf03b 100644
--- a/plugins/libcalendaring/lib/Sabre/VObject/Parameter.php
+++ b/plugins/libcalendaring/lib/Sabre/VObject/Parameter.php
@@ -82,7 +82,11 @@ class Parameter extends Node {
             '\,',
         );
 
-        return $this->name . '=' . str_replace($src, $out, $this->value);
+        $value = str_replace($src, $out, $this->value);
+        if (strpos($value,":")!==false) {
+            $value = '"' . $value . '"';
+        }
+        return $this->name . '=' . $value;
 
     }
 
diff --git a/plugins/libcalendaring/lib/Sabre/VObject/Property.php b/plugins/libcalendaring/lib/Sabre/VObject/Property.php
index ad54146..63ae645 100644
--- a/plugins/libcalendaring/lib/Sabre/VObject/Property.php
+++ b/plugins/libcalendaring/lib/Sabre/VObject/Property.php
@@ -188,10 +188,12 @@ class Property extends Node {
         $src = array(
             '\\',
             "\n",
+            "\r",
         );
         $out = array(
             '\\\\',
             '\n',
+            '',
         );
         $str.=':' . str_replace($src, $out, $this->value);
 
diff --git a/plugins/libcalendaring/lib/Sabre/VObject/RecurrenceIterator.php b/plugins/libcalendaring/lib/Sabre/VObject/RecurrenceIterator.php
index 0d5997e..8bd4ed2 100644
--- a/plugins/libcalendaring/lib/Sabre/VObject/RecurrenceIterator.php
+++ b/plugins/libcalendaring/lib/Sabre/VObject/RecurrenceIterator.php
@@ -105,7 +105,6 @@ class RecurrenceIterator implements \Iterator {
      */
     public $overriddenEvents = array();
 
-
     /**
      * Frequency is one of: secondly, minutely, hourly, daily, weekly, monthly,
      * yearly.
@@ -297,6 +296,13 @@ class RecurrenceIterator implements \Iterator {
     private $nextDate;
 
     /**
+     * This counts the number of overridden events we've handled so far
+     *
+     * @var int
+     */
+    private $handledOverridden = 0;
+
+    /**
      * Creates the iterator
      *
      * You should pass a VCALENDAR component, as well as the UID of the event
@@ -326,6 +332,9 @@ class RecurrenceIterator implements \Iterator {
                 }
             }
         }
+
+        ksort($this->overriddenEvents);
+
         if (!$this->baseEvent) {
             throw new \InvalidArgumentException('Could not find a base event with uid: ' . $uid);
         }
@@ -552,8 +561,18 @@ class RecurrenceIterator implements \Iterator {
         if (!is_null($this->count)) {
             return $this->counter < $this->count;
         }
-        if (!is_null($this->until)) {
-            return $this->currentDate <= $this->until;
+        if (!is_null($this->until) && $this->currentDate > $this->until) {
+
+            // Need to make sure there's no overridden events past the
+            // until date.
+            foreach($this->overriddenEvents as $overriddenEvent) {
+
+                if ($overriddenEvent->DTSTART->getDateTime() >= $this->currentDate) {
+
+                    return true;
+                }
+            }
+            return false;
         }
         return true;
 
@@ -608,93 +627,106 @@ class RecurrenceIterator implements \Iterator {
      */
     public function next() {
 
-        /*
-        if (!is_null($this->count) && $this->counter >= $this->count) {
-            $this->currentDate = null;
-        }*/
-
-
         $previousStamp = $this->currentDate->getTimeStamp();
 
-        while(true) {
-
-            $this->currentOverriddenEvent = null;
+        // Finding the next overridden event in line, and storing that for
+        // later use.
+        $overriddenEvent = null;
+        $overriddenDate = null;
+        $this->currentOverriddenEvent = null;
+
+        foreach($this->overriddenEvents as $index=>$event) {
+            if ($index > $previousStamp) {
+                $overriddenEvent = $event;
+                $overriddenDate = clone $event->DTSTART->getDateTime();
+                break;
+            }
+        }
 
-            // If we have a next date 'stored', we use that
-            if ($this->nextDate) {
+        // If we have a stored 'next date', we will use that.
+        if ($this->nextDate) {
+            if (!$overriddenDate || $this->nextDate < $overriddenDate) {
                 $this->currentDate = $this->nextDate;
                 $currentStamp = $this->currentDate->getTimeStamp();
                 $this->nextDate = null;
             } else {
+                $this->currentDate = clone $overriddenDate;
+                $this->currentOverriddenEvent = $overriddenEvent;
+            }
+            $this->counter++;
+            return;
+        }
 
-                // Otherwise, we calculate it
-                switch($this->frequency) {
-
-                    case 'hourly' :
-                        $this->nextHourly();
-                        break;
+        while(true) {
 
-                    case 'daily' :
-                        $this->nextDaily();
-                        break;
+            // Otherwise, we find the next event in the normal RRULE
+            // sequence.
+            switch($this->frequency) {
 
-                    case 'weekly' :
-                        $this->nextWeekly();
-                        break;
+                case 'hourly' :
+                    $this->nextHourly();
+                    break;
 
-                    case 'monthly' :
-                        $this->nextMonthly();
-                        break;
+                case 'daily' :
+                    $this->nextDaily();
+                    break;
 
-                    case 'yearly' :
-                        $this->nextYearly();
-                        break;
+                case 'weekly' :
+                    $this->nextWeekly();
+                    break;
 
-                }
-                $currentStamp = $this->currentDate->getTimeStamp();
+                case 'monthly' :
+                    $this->nextMonthly();
+                    break;
 
-                // Checking exception dates
-                foreach($this->exceptionDates as $exceptionDate) {
-                    if ($this->currentDate == $exceptionDate) {
-                        $this->counter++;
-                        continue 2;
-                    }
-                }
-                foreach($this->overriddenDates as $overriddenDate) {
-                    if ($this->currentDate == $overriddenDate) {
-                        continue 2;
-                    }
-                }
+                case 'yearly' :
+                    $this->nextYearly();
+                    break;
 
             }
+            $currentStamp = $this->currentDate->getTimeStamp();
 
-            // Checking overridden events
-            foreach($this->overriddenEvents as $index=>$event) {
-                if ($index > $previousStamp && $index <= $currentStamp) {
 
-                    // We're moving the 'next date' aside, for later use.
-                    $this->nextDate = clone $this->currentDate;
-
-                    $this->currentDate = $event->DTSTART->getDateTime();
-                    $this->currentOverriddenEvent = $event;
-
-                    break;
+            // Checking exception dates
+            foreach($this->exceptionDates as $exceptionDate) {
+                if ($this->currentDate == $exceptionDate) {
+                    $this->counter++;
+                    continue 2;
+                }
+            }
+            foreach($this->overriddenDates as $check) {
+                if ($this->currentDate == $check) {
+                    continue 2;
                 }
             }
-
             break;
 
         }
 
-        /*
-        if (!is_null($this->until)) {
-            if($this->currentDate > $this->until) {
-                $this->currentDate = null;
-            }
-        }*/
 
+
+        // Is the date we have actually higher than the next overiddenEvent?
+        if ($overriddenDate && $this->currentDate > $overriddenDate) {
+            $this->nextDate = clone $this->currentDate;
+            $this->currentDate = clone $overriddenDate;
+            $this->currentOverriddenEvent = $overriddenEvent;
+            $this->handledOverridden++;
+        }
         $this->counter++;
 
+
+        /*
+         * If we have overridden events left in the queue, but our counter is
+         * running out, we should grab one of those.
+         */
+        if (!is_null($overriddenEvent) && !is_null($this->count) && count($this->overriddenEvents) - $this->handledOverridden >= ($this->count - $this->counter)) {
+
+            $this->currentOverriddenEvent = $overriddenEvent;
+            $this->currentDate = clone $overriddenDate;
+            $this->handledOverridden++;
+
+        }
+
     }
 
     /**
diff --git a/plugins/libcalendaring/lib/Sabre/VObject/Version.php b/plugins/libcalendaring/lib/Sabre/VObject/Version.php
index 373980e..2a64140 100644
--- a/plugins/libcalendaring/lib/Sabre/VObject/Version.php
+++ b/plugins/libcalendaring/lib/Sabre/VObject/Version.php
@@ -14,7 +14,7 @@ class Version {
     /**
      * Full version number
      */
-    const VERSION = '2.1.0';
+    const VERSION = '2.1.3';
 
     /**
      * Stability : alpha, beta, stable




More information about the commits mailing list