Branch 'roundcubemail-plugins-kolab-3.1' - 4 commits - plugins/calendar plugins/libcalendaring plugins/libkolab plugins/tasklist

Thomas Brüderli bruederli at kolabsys.com
Thu May 1 17:15:12 CEST 2014


 plugins/calendar/drivers/calendar_driver.php             |   13 +
 plugins/calendar/drivers/kolab/kolab_driver.php          |    2 
 plugins/libcalendaring/libcalendaring.php                |   18 +-
 plugins/libcalendaring/libvcalendar.php                  |   71 +++++++-
 plugins/libcalendaring/localization/en_US.inc            |    1 
 plugins/libcalendaring/tests/libvcalendar.php            |   63 ++++++-
 plugins/libcalendaring/tests/resources/alarms.ics        |   51 ++++++
 plugins/libkolab/lib/kolab_format_xcal.php               |  127 +++++++++++++--
 plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php |    2 
 9 files changed, 318 insertions(+), 30 deletions(-)

New commits:
commit a42e9947797b7a0da6e5bb2020cc6202944fc815
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Thu May 1 17:13:10 2014 +0200

    Fix legacy support for storing alarms: legacy option sets first entry, additional entries shall be preserved (#2972)

diff --git a/plugins/calendar/drivers/kolab/kolab_driver.php b/plugins/calendar/drivers/kolab/kolab_driver.php
index 611d9c5..f7612f5 100644
--- a/plugins/calendar/drivers/kolab/kolab_driver.php
+++ b/plugins/calendar/drivers/kolab/kolab_driver.php
@@ -33,7 +33,7 @@ class kolab_driver extends calendar_driver
   public $freebusy = true;
   public $attachments = true;
   public $undelete = true;
-  public $alarm_types = array('DISPLAY');
+  public $alarm_types = array('DISPLAY','AUDIO');
   public $categoriesimmutable = true;
 
   private $rc;
diff --git a/plugins/libcalendaring/localization/en_US.inc b/plugins/libcalendaring/localization/en_US.inc
index 702aac5..ea5a18e 100644
--- a/plugins/libcalendaring/localization/en_US.inc
+++ b/plugins/libcalendaring/localization/en_US.inc
@@ -6,6 +6,7 @@ $labels['alarmemail'] = 'Send Email';
 $labels['alarmdisplay'] = 'Show message';
 $labels['alarmdisplayoption'] = 'Message';
 $labels['alarmemailoption'] = 'Email';
+$labels['alarmaudiooption'] = 'Sound';
 $labels['alarmat'] = 'at $datetime';
 $labels['trigger@'] = 'on date';
 $labels['trigger-M'] = 'minutes before';
diff --git a/plugins/libkolab/lib/kolab_format_xcal.php b/plugins/libkolab/lib/kolab_format_xcal.php
index b3bf10e..76b7b3c 100644
--- a/plugins/libkolab/lib/kolab_format_xcal.php
+++ b/plugins/libkolab/lib/kolab_format_xcal.php
@@ -227,7 +227,7 @@ abstract class kolab_format_xcal extends kolab_format
                 }
 
                 if ($start = self::php_datetime($alarm->start())) {
-                    $object['alarms'] = '@' . $start->format('U');
+                    $trigger = '@' . $start->format('U');
                     $valarm['trigger'] = $start;
                 }
                 else if ($offset = $alarm->relativeStart()) {
@@ -245,7 +245,7 @@ abstract class kolab_format_xcal extends kolab_format
                         $time = '0S';
                     }
 
-                    $object['alarms'] = $prefix . $value . $time;
+                    $trigger = $prefix . $value . $time;
                     $valarm['trigger'] = $prefix . 'P' . $value . ($time ? 'T' . $time : '');
                 }
 
@@ -261,8 +261,11 @@ abstract class kolab_format_xcal extends kolab_format
                     $valarm['repeat'] = $alarm->numrepeat();
                 }
 
-                $object['alarms']  .= ':' . $type;  // legacy property
                 $object['valarms'][] = array_filter($valarm);
+
+                if (!$object['alarms']) {
+                    $object['alarms'] = $trigger . ':' . $type;  // legacy property
+                }
             }
         }
 
@@ -508,6 +511,19 @@ abstract class kolab_format_xcal extends kolab_format
             }
 
             $valarms->push($alarm);
+
+            // preserve additional alarm entries
+            $oldvalarms = $this->obj->alarms();
+            for ($i=1; $i < $oldvalarms->size(); $i++) {
+                $valarms->push($oldvalarms->get($i));
+            }
+
+            // HACK: set and read back alarms to store the correct 'valarms' value in cache
+            if ($i > 1) {
+                $this->obj->setAlarms($valarms);
+                $update = $this->to_array();
+                $object['valarms'] = $update['valarms'];
+            }
         }
         $this->obj->setAlarms($valarms);
 
diff --git a/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php b/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php
index 0cf31fb..92d0aff 100644
--- a/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php
+++ b/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php
@@ -28,7 +28,7 @@ class tasklist_kolab_driver extends tasklist_driver
     public $alarms = false;
     public $attachments = true;
     public $undelete = false; // task undelete action
-    public $alarm_types = array('DISPLAY');
+    public $alarm_types = array('DISPLAY','AUDIO');
 
     private $rc;
     private $plugin;


commit 7f887e0a4e003a77a2eb2b3277d2296957099f9a
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Thu May 1 16:47:57 2014 +0200

    Improve libcalendaring::parse_alaram_value() to parse iCal alarm values with zero values

diff --git a/plugins/libcalendaring/libcalendaring.php b/plugins/libcalendaring/libcalendaring.php
index 415a15c..c96807f 100644
--- a/plugins/libcalendaring/libcalendaring.php
+++ b/plugins/libcalendaring/libcalendaring.php
@@ -313,10 +313,22 @@ class libcalendaring extends rcube_plugin
      */
     public static function parse_alaram_value($val)
     {
-        if ($val[0] == '@')
+        if ($val[0] == '@') {
             return array(substr($val, 1));
-        else if (preg_match('/([+-])P?T?(\d+)([HMSDW])/', $val, $m))
-            return array($m[2], $m[1].$m[3]);
+        }
+        else if (preg_match('/([+-]?)P?(T?\d+[HMSDW])+/', $val, $m) && preg_match_all('/T?(\d+)([HMSDW])/', $val, $m2, PREG_SET_ORDER)) {
+            if ($m[1] == '')
+                $m[1] = '+';
+            foreach ($m2 as $seg) {
+                $prefix = $seg[2] == 'D' || $seg[2] == 'W' ? 'P' : 'PT';
+                if ($seg[1] > 0) {  // ignore zero values
+                    return array($seg[1], $m[1].$seg[2], $m[1].$seg[1].$seg[2], $m[1].$prefix.$seg[1].$seg[2]);
+                }
+            }
+
+            // return zero value nevertheless
+            return array($seg[1], $m[1].$seg[2], $m[1].$seg[1].$seg[2], $m[1].$prefix.$seg[1].$seg[2]);
+        }
 
         return false;
     }
diff --git a/plugins/libcalendaring/tests/libvcalendar.php b/plugins/libcalendaring/tests/libvcalendar.php
index b6b713f..f0fe704 100644
--- a/plugins/libcalendaring/tests/libvcalendar.php
+++ b/plugins/libcalendaring/tests/libvcalendar.php
@@ -186,6 +186,10 @@ class libvcalendar_test extends PHPUnit_Framework_TestCase
 
         // alarm trigger with 0 values
         $events = $ical->import_from_file(__DIR__ . '/resources/alarms.ics', 'UTF-8');
+        $event = $events[0];
+
+        $this->assertEquals('-30M:DISPLAY', $event['alarms'], "Stripped alarm string");
+        $alarm = libcalendaring::parse_alaram_value($event['alarms']);
         $this->assertEquals('-30M', $alarm[2], "Alarm string");
 
         $this->assertEquals('DISPLAY', $event['valarms'][0]['action'],  "First alarm action");


commit 32a3b432b1d97a06993d35a72994fd411744806a
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Thu Apr 3 18:38:26 2014 +0200

    Fix storing of (multiple) event alarms

diff --git a/plugins/libcalendaring/libvcalendar.php b/plugins/libcalendaring/libvcalendar.php
index aef62c2..87adbab 100644
--- a/plugins/libcalendaring/libvcalendar.php
+++ b/plugins/libcalendaring/libvcalendar.php
@@ -580,7 +580,13 @@ class libvcalendar implements Iterator
 
                     if (!$trigger && ($values = libcalendaring::parse_alaram_value($prop->value))) {
                         $trigger = $values[2];
+                    }
+
+                    if (!$alarm['trigger']) {
                         $alarm['trigger'] = rtrim(preg_replace('/([A-Z])0[WDHMS]/', '\\1', $prop->value), 'T');
+                        // if all 0-values have been stripped, assume 'at time'
+                        if ($alarm['trigger'] == 'P')
+                            $alarm['trigger'] = 'PT0S';
                     }
                     break;
 
@@ -604,10 +610,12 @@ class libvcalendar implements Iterator
                 }
             }
 
-            if ($trigger && strtoupper($action) != 'NONE') {
-                if (!$event['alarms']) // store first alarm in legacy property
+            if ($action != 'NONE') {
+                if ($trigger && !$event['alarms']) // store first alarm in legacy property
                     $event['alarms'] = $trigger . ':' . $action;
-                $event['valarms'][] = $alarm;
+
+                if ($alarm['trigger'])
+                    $event['valarms'][] = $alarm;
             }
         }
 
diff --git a/plugins/libkolab/lib/kolab_format_xcal.php b/plugins/libkolab/lib/kolab_format_xcal.php
index 9c5bed9..b3bf10e 100644
--- a/plugins/libkolab/lib/kolab_format_xcal.php
+++ b/plugins/libkolab/lib/kolab_format_xcal.php
@@ -238,7 +238,12 @@ abstract class kolab_format_xcal extends kolab_format
                     else if ($h = $offset->hours())     $time  .= $h . 'H';
                     else if ($m = $offset->minutes())   $time  .= $m . 'M';
                     else if ($s = $offset->seconds())   $time  .= $s . 'S';
-                    else continue;
+
+                    // assume 'at event time'
+                    if (empty($value) && empty($time)) {
+                        $prefix = '';
+                        $time = '0S';
+                    }
 
                     $object['alarms'] = $prefix . $value . $time;
                     $valarm['trigger'] = $prefix . 'P' . $value . ($time ? 'T' . $time : '');
@@ -423,11 +428,11 @@ abstract class kolab_format_xcal extends kolab_format
         $valarms = new vectoralarm;
         if ($object['valarms']) {
             foreach ($object['valarms'] as $valarm) {
-                if (!array_key_exists($valarm['type'], $this->alarm_type_map)) {
+                if (!array_key_exists($valarm['action'], $this->alarm_type_map)) {
                     continue;  // skip unknown alarm types
                 }
 
-                if ($valarm['type'] == 'EMAIL') {
+                if ($valarm['action'] == 'EMAIL') {
                     $recipients = new vectorcontactref;
                     foreach (($valarm['attendees'] ?: array($object['_owner'])) as $email) {
                         $recipients->push(new ContactReference(ContactReference::EmailReference, $email));
@@ -448,11 +453,12 @@ abstract class kolab_format_xcal extends kolab_format
                 else {
                     try {
                         $prefix = $valarm['trigger'][0];
-                        $period = new DateInterval(preg_replace('/[0-9PTWDHMS]/', '', $valarm['trigger']));
+                        $period = new DateInterval(preg_replace('/[^0-9PTWDHMS]/', '', $valarm['trigger']));
                         $duration = new Duration($period->d, $period->h, $period->i, $period->s, $prefix == '-');
                     }
                     catch (Exception $e) {
                         // skip alarm with invalid trigger values
+                        rcube::raise_error($e, true);
                         continue;
                     }
 


commit a595454bdddea0c5ebeec910553455588f63c91d
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date:   Thu Apr 3 17:39:26 2014 +0200

    Improve libs to support multiple VALARM items according to iCal standards, including action-specific properties
    
    Conflicts:
    	plugins/libcalendaring/libvcalendar.php
    	plugins/libcalendaring/tests/libvcalendar.php
    	plugins/libcalendaring/tests/resources/alarms.ics

diff --git a/plugins/calendar/drivers/calendar_driver.php b/plugins/calendar/drivers/calendar_driver.php
index c09d8b9..02e1e68 100644
--- a/plugins/calendar/drivers/calendar_driver.php
+++ b/plugins/calendar/drivers/calendar_driver.php
@@ -54,7 +54,18 @@
  *     'free_busy' => 'free|busy|outofoffice|tentative',  // Show time as
  *      'priority' => 0-9,     // Event priority (0=undefined, 1=highest, 9=lowest)
  *   'sensitivity' => 'public|private|confidential',   // Event sensitivity
- *        'alarms' => '-15M:DISPLAY',  // Reminder settings inspired by valarm definition (e.g. display alert 15 minutes before event)
+ *        'alarms' => '-15M:DISPLAY',  // DEPRECATED Reminder settings inspired by valarm definition (e.g. display alert 15 minutes before event)
+ *       'valarms' => array(           // List of reminders (new format), each represented as a hash array:
+ *                  array(
+ *                     'trigger' => '-PT90M',     // ISO 8601 period string prefixed with '+' or '-', or DateTime object
+ *                      'action' => 'DISPLAY|EMAIL|AUDIO',
+ *                    'duration' => 'PT15M',      // ISO 8601 period string
+ *                      'repeat' => 0,            // number of repetitions
+ *                 'description' => '',        // text to display for DISPLAY actions
+ *                     'summary' => '',        // message text for EMAIL actions
+ *                   'attendees' => array(),   // list of email addresses to receive alarm messages
+ *                  ),
+ *   ),
  *   'attachments' => array(   // List of attachments
  *            'name' => 'File name',
  *        'mimetype' => 'Content type',
diff --git a/plugins/libcalendaring/libvcalendar.php b/plugins/libcalendaring/libvcalendar.php
index 19ef12d..aef62c2 100644
--- a/plugins/libcalendaring/libvcalendar.php
+++ b/plugins/libcalendaring/libvcalendar.php
@@ -566,6 +566,7 @@ class libvcalendar implements Iterator
         foreach ($ve->select('VALARM') as $valarm) {
             $action = 'DISPLAY';
             $trigger = null;
+            $alarm = array();
 
             foreach ($valarm->children as $prop) {
                 switch ($prop->name) {
@@ -573,22 +574,40 @@ class libvcalendar implements Iterator
                     foreach ($prop->parameters as $param) {
                         if ($param->name == 'VALUE' && $param->value == 'DATE-TIME') {
                             $trigger = '@' . $prop->getDateTime()->format('U');
+                            $alarm['trigger'] = $prop->getDateTime();
                         }
                     }
-                    if (!$trigger) {
-                        $trigger = preg_replace('/PT?/', '', $prop->value);
+
+                    if (!$trigger && ($values = libcalendaring::parse_alaram_value($prop->value))) {
+                        $trigger = $values[2];
+                        $alarm['trigger'] = rtrim(preg_replace('/([A-Z])0[WDHMS]/', '\\1', $prop->value), 'T');
                     }
                     break;
 
                 case 'ACTION':
-                    $action = $prop->value;
+                    $action = $alarm['action'] = strtoupper($prop->value);
+                    break;
+
+                case 'SUMMARY':
+                case 'DESCRIPTION':
+                case 'DURATION':
+                    $alarm[strtolower($prop->name)] = self::convert_string($prop);
+                    break;
+
+                case 'REPEAT':
+                    $alarm['repeat'] = intval($prop->value);
+                    break;
+
+                case 'ATTENDEE':
+                    $alarm['attendees'][] = preg_replace('/^mailto:/i', '', $prop->value);
                     break;
                 }
             }
 
             if ($trigger && strtoupper($action) != 'NONE') {
-                $event['alarms'] = $trigger . ':' . $action;
-                break;
+                if (!$event['alarms']) // store first alarm in legacy property
+                    $event['alarms'] = $trigger . ':' . $action;
+                $event['valarms'][] = $alarm;
             }
         }
 
@@ -945,7 +964,37 @@ class libvcalendar implements Iterator
                 $ve->add(self::datetime_prop('COMPLETED', $event['changed'] ?: new DateTime('now - 1 hour'), true));
         }
 
-        if ($event['alarms']) {
+        if ($event['valarms']) {
+            foreach ($event['valarms'] as $alarm) {
+                $va = VObject\Component::create('VALARM');
+                $va->action = $alarm['action'];
+                if ($alarm['trigger'] instanceof DateTime) {
+                    $va->add(self::datetime_prop('TRIGGER', $alarm['trigger'], true));
+                }
+                else {
+                    $va->add('TRIGGER', $alarm['trigger']);
+                }
+
+                if ($alarm['action'] == 'EMAIL') {
+                    foreach ((array)$alarm['attendees'] as $attendee) {
+                        $va->add('ATTENDEE', 'mailto:' . $attendee);
+                    }
+                }
+                if ($alarm['description']) {
+                    $va->add('DESCRIPTION', $alarm['description'] ?: $event['title']);
+                }
+                if ($alarm['summary']) {
+                    $va->add('SUMMARY', $alarm['summary']);
+                }
+                if ($alarm['duration']) {
+                    $va->add('DURATION', $alarm['duration']);
+                    $va->add('REPEAT', intval($alarm['repeat']));
+                }
+                $ve->add($va);
+            }
+        }
+        // legacy support
+        else if ($event['alarms']) {
             $va = VObject\Component::create('VALARM');
             list($trigger, $va->action) = explode(':', $event['alarms']);
             $val = libcalendaring::parse_alaram_value($trigger);
diff --git a/plugins/libcalendaring/tests/libvcalendar.php b/plugins/libcalendaring/tests/libvcalendar.php
index 88a244f..b6b713f 100644
--- a/plugins/libcalendaring/tests/libvcalendar.php
+++ b/plugins/libcalendaring/tests/libvcalendar.php
@@ -168,19 +168,44 @@ class libvcalendar_test extends PHPUnit_Framework_TestCase
     }
 
     /**
-     * @depends test_import
+     * 
      */
-    function test_apple_alarms()
+    function test_alarms()
     {
         $ical = new libvcalendar();
-        $events = $ical->import_from_file(__DIR__ . '/resources/apple-alarms.ics', 'UTF-8');
+        $events = $ical->import_from_file(__DIR__ . '/resources/recurring.ics', 'UTF-8');
         $event = $events[0];
 
-        // alarms
-        $this->assertEquals('-45M:AUDIO', $event['alarms'], "Relative alarm string");
+        $this->assertEquals('-12H:DISPLAY', $event['alarms'], "Serialized alarms string");
         $alarm = libcalendaring::parse_alaram_value($event['alarms']);
-        $this->assertEquals('45', $alarm[0], "Alarm value");
-        $this->assertEquals('-M', $alarm[1], "Alarm unit");
+        $this->assertEquals('12', $alarm[0], "Alarm value");
+        $this->assertEquals('-H', $alarm[1], "Alarm unit");
+
+        $this->assertEquals('DISPLAY', $event['valarms'][0]['action'],  "Full alarm item (action)");
+        $this->assertEquals('-PT12H',   $event['valarms'][0]['trigger'], "Full alarm item (trigger)");
+
+        // alarm trigger with 0 values
+        $events = $ical->import_from_file(__DIR__ . '/resources/alarms.ics', 'UTF-8');
+        $this->assertEquals('-30M', $alarm[2], "Alarm string");
+
+        $this->assertEquals('DISPLAY', $event['valarms'][0]['action'],  "First alarm action");
+        $this->assertEquals('This is the first event reminder', $event['valarms'][0]['description'],  "First alarm text");
+
+        $this->assertEquals(2, count($event['valarms']), "List all VALARM blocks");
+
+        $valarm = $event['valarms'][1];
+        $this->assertEquals(1, count($valarm['attendees']), "Email alarm attendees");
+        $this->assertEquals('EMAIL', $valarm['action'],  "Second alarm item (action)");
+        $this->assertEquals('-P1D',  $valarm['trigger'], "Second alarm item (trigger)");
+        $this->assertEquals('This is the reminder message',  $valarm['summary'], "Email alarm text");
+
+        // test alarms export
+        $ics = $ical->export(array($event));
+        $this->assertContains('ACTION:DISPLAY',   $ics, "Display alarm block");
+        $this->assertContains('ACTION:EMAIL',     $ics, "Email alarm block");
+        $this->assertContains('DESCRIPTION:This is the first event reminder',    $ics, "Alarm description");
+        $this->assertContains('SUMMARY:This is the reminder message',            $ics, "Email alarm summary");
+        $this->assertContains('ATTENDEE:mailto:reminder-recipient at example.org',  $ics, "Email alarm recipient");
     }
 
     /**
@@ -200,6 +225,26 @@ class libvcalendar_test extends PHPUnit_Framework_TestCase
     }
 
     /**
+     * @depends test_import
+     */
+    function test_apple_alarms()
+    {
+        $ical = new libvcalendar();
+        $events = $ical->import_from_file(__DIR__ . '/resources/apple-alarms.ics', 'UTF-8');
+        $event = $events[0];
+
+        // alarms
+        $this->assertEquals('-45M:AUDIO', $event['alarms'], "Relative alarm string");
+        $alarm = libcalendaring::parse_alaram_value($event['alarms']);
+        $this->assertEquals('45', $alarm[0], "Alarm value");
+        $this->assertEquals('-M', $alarm[1], "Alarm unit");
+
+        $this->assertEquals(1, count($event['valarms']), "Ignore invalid alarm blocks");
+        $this->assertEquals('AUDIO', $event['valarms'][0]['action'],  "Full alarm item (action)");
+        $this->assertEquals('-PT45M', $event['valarms'][0]['trigger'], "Full alarm item (trigger)");
+    }
+
+    /**
      * 
      */
     function test_escaped_values()
diff --git a/plugins/libcalendaring/tests/resources/alarms.ics b/plugins/libcalendaring/tests/resources/alarms.ics
new file mode 100644
index 0000000..d6f9d54
--- /dev/null
+++ b/plugins/libcalendaring/tests/resources/alarms.ics
@@ -0,0 +1,51 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 5.0.3//EN
+CALSCALE:GREGORIAN
+BEGIN:VTIMEZONE
+TZID:Europe/Zurich
+BEGIN:DAYLIGHT
+TZOFFSETFROM:+0100
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
+DTSTART:19810329T020000
+TZNAME:CEST
+TZOFFSETTO:+0200
+END:DAYLIGHT
+BEGIN:STANDARD
+TZOFFSETFROM:+0200
+RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
+DTSTART:19961027T030000
+TZNAME:CET
+TZOFFSETTO:+0100
+END:STANDARD
+END:VTIMEZONE
+
+BEGIN:VEVENT
+UID:1dq52u617gkfqrr4uo1i2uh70
+CREATED:20130924T221822Z
+DESCRIPTION:
+DTSTART:20130818T230000Z
+DTEND:20130819T010000Z
+DTSTAMP:20130824T235608Z
+LAST-MODIFIED:20130924T222118Z
+LOCATION:
+SEQUENCE:2
+STATUS:CONFIRMED
+SUMMARY:Alarms test
+TRANSP:OPAQUE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:This is the first event reminder
+TRIGGER:-P0DT0H30M0S
+END:VALARM
+BEGIN:VALARM
+ACTION:EMAIL
+DESCRIPTION:This is an event reminder
+TRIGGER:-P1D
+ATTENDEE:mailto:reminder-recipient at example.org
+SUMMARY:This is the reminder message
+DESCRIPTION:This is the second event reminder
+END:VALARM
+END:VEVENT
+
+END:VCALENDAR
diff --git a/plugins/libkolab/lib/kolab_format_xcal.php b/plugins/libkolab/lib/kolab_format_xcal.php
index 5de82e0..9c5bed9 100644
--- a/plugins/libkolab/lib/kolab_format_xcal.php
+++ b/plugins/libkolab/lib/kolab_format_xcal.php
@@ -205,27 +205,59 @@ abstract class kolab_format_xcal extends kolab_format
         // read alarm
         $valarms = $this->obj->alarms();
         $alarm_types = array_flip($this->alarm_type_map);
+        $object['valarms'] = array();
         for ($i=0; $i < $valarms->size(); $i++) {
             $alarm = $valarms->get($i);
             $type = $alarm_types[$alarm->type()];
 
             if ($type == 'DISPLAY' || $type == 'EMAIL') {  // only DISPLAY and EMAIL alarms are supported
+                $valarm = array(
+                    'action' => $type,
+                    'summary' => $alarm->summary(),
+                    'description' => $alarm->description(),
+                );
+
+                if ($type == 'EMAIL') {
+                    $valarm['attendees'] = array();
+                    $attvec = $this->obj->attendees();
+                    for ($j=0; $j < $attvec->size(); $j++) {
+                        $cr = $attvec->get($j);
+                        $valarm['attendees'][] = $cr->email();
+                    }
+                }
+
                 if ($start = self::php_datetime($alarm->start())) {
                     $object['alarms'] = '@' . $start->format('U');
+                    $valarm['trigger'] = $start;
                 }
                 else if ($offset = $alarm->relativeStart()) {
-                    $value = $alarm->relativeTo() == kolabformat::End ? '+' : '-';
+                    $prefix = $alarm->relativeTo() == kolabformat::End ? '+' : '-';
+                    $value = $time = '';
                     if      ($w = $offset->weeks())     $value .= $w . 'W';
                     else if ($d = $offset->days())      $value .= $d . 'D';
-                    else if ($h = $offset->hours())     $value .= $h . 'H';
-                    else if ($m = $offset->minutes())   $value .= $m . 'M';
-                    else if ($s = $offset->seconds())   $value .= $s . 'S';
+                    else if ($h = $offset->hours())     $time  .= $h . 'H';
+                    else if ($m = $offset->minutes())   $time  .= $m . 'M';
+                    else if ($s = $offset->seconds())   $time  .= $s . 'S';
                     else continue;
 
-                    $object['alarms'] = $value;
+                    $object['alarms'] = $prefix . $value . $time;
+                    $valarm['trigger'] = $prefix . 'P' . $value . ($time ? 'T' . $time : '');
+                }
+
+                // read alarm duration and repeat properties
+                if (($duration = $alarm->duration()) && $duration->isValid()) {
+                    $value = $time = '';
+                    if      ($w = $duration->weeks())     $value .= $w . 'W';
+                    else if ($d = $duration->days())      $value .= $d . 'D';
+                    else if ($h = $duration->hours())     $time  .= $h . 'H';
+                    else if ($m = $duration->minutes())   $time  .= $m . 'M';
+                    else if ($s = $duration->seconds())   $time  .= $s . 'S';
+                    $valarm['duration'] = 'P' . $value . ($time ? 'T' . $time : '');
+                    $valarm['repeat'] = $alarm->numrepeat();
                 }
-                $object['alarms']  .= ':' . $type;
-                break;
+
+                $object['alarms']  .= ':' . $type;  // legacy property
+                $object['valarms'][] = array_filter($valarm);
             }
         }
 
@@ -389,7 +421,60 @@ abstract class kolab_format_xcal extends kolab_format
 
         // save alarm
         $valarms = new vectoralarm;
-        if ($object['alarms']) {
+        if ($object['valarms']) {
+            foreach ($object['valarms'] as $valarm) {
+                if (!array_key_exists($valarm['type'], $this->alarm_type_map)) {
+                    continue;  // skip unknown alarm types
+                }
+
+                if ($valarm['type'] == 'EMAIL') {
+                    $recipients = new vectorcontactref;
+                    foreach (($valarm['attendees'] ?: array($object['_owner'])) as $email) {
+                        $recipients->push(new ContactReference(ContactReference::EmailReference, $email));
+                    }
+                    $alarm = new Alarm(
+                        strval($valarm['summary'] ?: $object['title']),
+                        strval($valarm['description'] ?: $object['description']),
+                        $recipients
+                    );
+                }
+                else {
+                    $alarm = new Alarm(strval($valarm['summary'] ?: $object['title']));
+                }
+
+                if (is_object($valarm['trigger']) && $valarm['trigger'] instanceof DateTime) {
+                    $alarm->setStart(self::get_datetime($valarm['trigger'], new DateTimeZone('UTC')));
+                }
+                else {
+                    try {
+                        $prefix = $valarm['trigger'][0];
+                        $period = new DateInterval(preg_replace('/[0-9PTWDHMS]/', '', $valarm['trigger']));
+                        $duration = new Duration($period->d, $period->h, $period->i, $period->s, $prefix == '-');
+                    }
+                    catch (Exception $e) {
+                        // skip alarm with invalid trigger values
+                        continue;
+                    }
+
+                    $alarm->setRelativeStart($duration, $prefix == '-' ? kolabformat::Start : kolabformat::End);
+                }
+
+                if ($valarm['duration']) {
+                    try {
+                        $d = new DateInterval($valarm['duration']);
+                        $duration = new Duration($d->d, $d->h, $d->i, $d->s);
+                        $alarm->setDuration($duration, intval($valarm['repeat']));
+                    }
+                    catch (Exception $e) {
+                        // ignore
+                    }
+                }
+
+                $valarms->push($alarm);
+            }
+        }
+        // legacy support
+        else if ($object['alarms']) {
             list($offset, $type) = explode(":", $object['alarms']);
 
             if ($type == 'EMAIL' && !empty($object['_owner'])) {  // email alarms implicitly go to event owner




More information about the commits mailing list