Improve libs to support multiple VALARM items according to iCal standards, including action-specific properties
This commit is contained in:
parent
1ce6d461a6
commit
b5d6faee6a
5 changed files with 193 additions and 15 deletions
|
@ -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',
|
||||
|
|
|
@ -575,6 +575,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) {
|
||||
|
@ -582,22 +583,39 @@ 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 && ($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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -954,7 +972,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);
|
||||
|
|
|
@ -167,7 +167,7 @@ class libvcalendar_test extends PHPUnit_Framework_TestCase
|
|||
}
|
||||
|
||||
/**
|
||||
* @depends test_import_from_file
|
||||
*
|
||||
*/
|
||||
function test_alarms()
|
||||
{
|
||||
|
@ -181,6 +181,9 @@ class libvcalendar_test extends PHPUnit_Framework_TestCase
|
|||
$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');
|
||||
$event = $events[0];
|
||||
|
@ -190,6 +193,25 @@ class libvcalendar_test extends PHPUnit_Framework_TestCase
|
|||
$this->assertEquals('30', $alarm[0], "Alarm value");
|
||||
$this->assertEquals('-M', $alarm[1], "Alarm unit");
|
||||
$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@example.org', $ics, "Email alarm recipient");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -222,6 +244,10 @@ class libvcalendar_test extends PHPUnit_Framework_TestCase
|
|||
$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)");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -35,9 +35,17 @@ SUMMARY:Alarms test
|
|||
TRANSP:OPAQUE
|
||||
BEGIN:VALARM
|
||||
ACTION:DISPLAY
|
||||
DESCRIPTION:This is an event reminder
|
||||
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@example.org
|
||||
SUMMARY:This is the reminder message
|
||||
DESCRIPTION:This is the second event reminder
|
||||
END:VALARM
|
||||
END:VEVENT
|
||||
|
||||
END:VCALENDAR
|
||||
|
|
|
@ -217,27 +217,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 : '');
|
||||
}
|
||||
$object['alarms'] .= ':' . $type;
|
||||
break;
|
||||
|
||||
// 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; // legacy property
|
||||
$object['valarms'][] = array_filter($valarm);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -397,7 +429,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
|
||||
|
|
Loading…
Add table
Reference in a new issue