2011-05-20 19:04:25 +02:00
|
|
|
<?php
|
2011-08-21 12:48:33 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* iCalendar functions for the Calendar plugin
|
|
|
|
*
|
2011-11-21 11:20:48 +01:00
|
|
|
* @version @package_version@
|
2011-08-21 12:48:33 +02:00
|
|
|
* @author Lazlo Westerhof <hello@lazlo.me>
|
2011-12-07 12:51:23 +01:00
|
|
|
* @author Thomas Bruederli <bruederli@kolabsys.com>
|
2011-08-21 12:48:33 +02:00
|
|
|
* @author Bogomil "Bogo" Shopov <shopov@kolabsys.com>
|
|
|
|
*
|
2011-10-27 10:20:46 +02:00
|
|
|
* Copyright (C) 2010, Lazlo Westerhof <hello@lazlo.me>
|
2013-02-21 17:41:41 +01:00
|
|
|
* Copyright (C) 2013, Kolab Systems AG <contact@kolabsys.com>
|
2011-08-21 12:48:33 +02:00
|
|
|
*
|
2011-10-27 10:20:46 +02:00
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU Affero General Public License as
|
|
|
|
* published by the Free Software Foundation, either version 3 of the
|
|
|
|
* License, or (at your option) any later version.
|
2011-08-21 12:48:33 +02:00
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
2011-10-27 10:20:46 +02:00
|
|
|
* GNU Affero General Public License for more details.
|
2011-08-21 12:48:33 +02:00
|
|
|
*
|
2011-10-27 10:20:46 +02:00
|
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
2011-08-21 12:48:33 +02:00
|
|
|
*/
|
2011-05-20 19:04:25 +02:00
|
|
|
|
2011-07-29 09:19:18 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Class to parse and build vCalendar (iCalendar) files
|
|
|
|
*
|
|
|
|
* Uses the Horde:iCalendar class for parsing. To install:
|
|
|
|
* > pear channel-discover pear.horde.org
|
|
|
|
* > pear install horde/Horde_Icalendar
|
|
|
|
*
|
|
|
|
*/
|
2011-05-20 19:04:25 +02:00
|
|
|
class calendar_ical
|
|
|
|
{
|
2011-07-27 17:42:45 +02:00
|
|
|
const EOL = "\r\n";
|
|
|
|
|
2011-05-20 19:04:25 +02:00
|
|
|
private $rc;
|
2011-07-29 09:19:18 +02:00
|
|
|
private $cal;
|
2012-01-04 23:05:34 +01:00
|
|
|
|
2011-08-14 15:35:22 +02:00
|
|
|
public $method;
|
|
|
|
public $events = array();
|
2011-05-20 19:04:25 +02:00
|
|
|
|
2011-07-29 09:19:18 +02:00
|
|
|
function __construct($cal)
|
2011-07-06 22:42:23 +02:00
|
|
|
{
|
2011-07-29 09:19:18 +02:00
|
|
|
$this->cal = $cal;
|
|
|
|
$this->rc = $cal->rc;
|
2011-05-20 19:04:25 +02:00
|
|
|
}
|
2011-07-29 09:19:18 +02:00
|
|
|
|
2011-05-20 19:04:25 +02:00
|
|
|
/**
|
|
|
|
* Import events from iCalendar format
|
|
|
|
*
|
2011-07-29 09:19:18 +02:00
|
|
|
* @param string vCalendar input
|
2011-08-04 23:54:29 +02:00
|
|
|
* @param string Input charset (from envelope)
|
2011-07-29 09:19:18 +02:00
|
|
|
* @return array List of events extracted from the input
|
2011-05-20 19:04:25 +02:00
|
|
|
*/
|
2011-08-04 23:54:29 +02:00
|
|
|
public function import($vcal, $charset = RCMAIL_CHARSET)
|
2011-07-06 22:42:23 +02:00
|
|
|
{
|
2011-10-12 19:44:38 +02:00
|
|
|
$parser = $this->get_parser();
|
2011-08-04 23:54:29 +02:00
|
|
|
$parser->parsevCalendar($vcal, 'VCALENDAR', $charset);
|
2011-08-14 15:35:22 +02:00
|
|
|
$this->method = $parser->getAttributeDefault('METHOD', '');
|
2011-11-11 13:22:40 +01:00
|
|
|
$this->events = $seen = array();
|
2011-07-29 09:19:18 +02:00
|
|
|
if ($data = $parser->getComponents()) {
|
|
|
|
foreach ($data as $comp) {
|
2011-11-11 13:22:40 +01:00
|
|
|
if ($comp->getType() == 'vEvent') {
|
|
|
|
$event = $this->_to_rcube_format($comp);
|
|
|
|
if (!$seen[$event['uid']]++)
|
|
|
|
$this->events[] = $event;
|
|
|
|
}
|
2011-07-29 09:19:18 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-08-14 15:35:22 +02:00
|
|
|
return $this->events;
|
2011-05-20 19:04:25 +02:00
|
|
|
}
|
2011-07-29 09:19:18 +02:00
|
|
|
|
2011-10-12 19:44:38 +02:00
|
|
|
/**
|
|
|
|
* Read iCalendar events from a file
|
|
|
|
*
|
|
|
|
* @param string File path to read from
|
|
|
|
* @return array List of events extracted from the file
|
|
|
|
*/
|
|
|
|
public function import_from_file($filepath)
|
|
|
|
{
|
2011-11-11 13:22:40 +01:00
|
|
|
$this->events = $seen = array();
|
2011-10-12 19:44:38 +02:00
|
|
|
$fp = fopen($filepath, 'r');
|
|
|
|
|
|
|
|
// check file content first
|
|
|
|
$begin = fread($fp, 1024);
|
|
|
|
if (!preg_match('/BEGIN:VCALENDAR/i', $begin))
|
|
|
|
return $this->events;
|
|
|
|
|
|
|
|
$parser = $this->get_parser();
|
|
|
|
$buffer = '';
|
|
|
|
|
|
|
|
fseek($fp, 0);
|
|
|
|
while (($line = fgets($fp, 2048)) !== false) {
|
|
|
|
$buffer .= $line;
|
|
|
|
if (preg_match('/END:VEVENT/i', $line)) {
|
2013-07-04 17:07:58 +02:00
|
|
|
if (preg_match('/BEGIN:VCALENDAR/i', $buffer))
|
|
|
|
$buffer .= self::EOL ."END:VCALENDAR";
|
2011-10-12 19:44:38 +02:00
|
|
|
$parser->parsevCalendar($buffer, 'VCALENDAR', RCMAIL_CHARSET, false);
|
|
|
|
$buffer = '';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fclose($fp);
|
|
|
|
|
|
|
|
if ($data = $parser->getComponents()) {
|
|
|
|
foreach ($data as $comp) {
|
2011-11-11 13:22:40 +01:00
|
|
|
if ($comp->getType() == 'vEvent') {
|
|
|
|
$event = $this->_to_rcube_format($comp);
|
|
|
|
if (!$seen[$event['uid']]++)
|
|
|
|
$this->events[] = $event;
|
|
|
|
}
|
2011-10-12 19:44:38 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->events;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Load iCal parser from the Horde lib
|
|
|
|
*/
|
2012-07-02 17:43:19 +02:00
|
|
|
public function get_parser()
|
2011-10-12 19:44:38 +02:00
|
|
|
{
|
2012-05-02 17:10:53 +02:00
|
|
|
if (!class_exists('Horde_iCalendar'))
|
|
|
|
require_once($this->cal->home . '/lib/Horde_iCalendar.php');
|
2011-10-12 19:44:38 +02:00
|
|
|
|
|
|
|
// set target charset for parsed events
|
|
|
|
$GLOBALS['_HORDE_STRING_CHARSET'] = RCMAIL_CHARSET;
|
|
|
|
|
|
|
|
return new Horde_iCalendar;
|
|
|
|
}
|
|
|
|
|
2011-07-29 09:19:18 +02:00
|
|
|
/**
|
|
|
|
* Convert the given File_IMC_Parse_Vcalendar_Event object to the internal event format
|
|
|
|
*/
|
|
|
|
private function _to_rcube_format($ve)
|
|
|
|
{
|
|
|
|
$event = array(
|
|
|
|
'uid' => $ve->getAttributeDefault('UID'),
|
2011-07-29 17:51:04 +02:00
|
|
|
'changed' => $ve->getAttributeDefault('DTSTAMP', 0),
|
2011-07-29 09:19:18 +02:00
|
|
|
'title' => $ve->getAttributeDefault('SUMMARY'),
|
2012-07-06 17:15:45 +02:00
|
|
|
'start' => $this->_date2time($ve->getAttribute('DTSTART')),
|
|
|
|
'end' => $this->_date2time($ve->getAttribute('DTEND')),
|
2011-07-29 17:51:04 +02:00
|
|
|
// set defaults
|
|
|
|
'free_busy' => 'busy',
|
2011-10-05 12:16:43 +02:00
|
|
|
'priority' => 0,
|
2012-10-15 12:36:23 +02:00
|
|
|
'attendees' => array(),
|
2011-07-29 09:19:18 +02:00
|
|
|
);
|
2012-07-06 17:15:45 +02:00
|
|
|
|
2011-07-29 09:19:18 +02:00
|
|
|
// check for all-day dates
|
2012-07-06 17:15:45 +02:00
|
|
|
if (is_array($ve->getAttribute('DTSTART')))
|
2011-07-29 09:19:18 +02:00
|
|
|
$event['allday'] = true;
|
2012-07-06 17:15:45 +02:00
|
|
|
|
|
|
|
if ($event['allday'])
|
|
|
|
$event['end']->sub(new DateInterval('PT23H'));
|
|
|
|
|
|
|
|
// assign current timezone to event start/end
|
2012-11-21 10:33:02 +01:00
|
|
|
if (is_a($event['start'], 'DateTime'))
|
|
|
|
$event['start']->setTimezone($this->cal->timezone);
|
|
|
|
else
|
|
|
|
unset($event['start']);
|
|
|
|
|
|
|
|
if (is_a($event['end'], 'DateTime'))
|
|
|
|
$event['end']->setTimezone($this->cal->timezone);
|
|
|
|
else
|
|
|
|
unset($event['end']);
|
2011-08-06 16:12:34 +02:00
|
|
|
|
2011-07-29 17:51:04 +02:00
|
|
|
// map other attributes to internal fields
|
|
|
|
$_attendees = array();
|
|
|
|
foreach ($ve->getAllAttributes() as $attr) {
|
|
|
|
switch ($attr['name']) {
|
|
|
|
case 'ORGANIZER':
|
|
|
|
$organizer = array(
|
|
|
|
'name' => $attr['params']['CN'],
|
2011-11-09 15:37:21 +01:00
|
|
|
'email' => preg_replace('/^mailto:/i', '', $attr['value']),
|
2011-07-29 17:51:04 +02:00
|
|
|
'role' => 'ORGANIZER',
|
|
|
|
'status' => 'ACCEPTED',
|
|
|
|
);
|
|
|
|
if (isset($_attendees[$organizer['email']])) {
|
|
|
|
$i = $_attendees[$organizer['email']];
|
|
|
|
$event['attendees'][$i]['role'] = $organizer['role'];
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'ATTENDEE':
|
|
|
|
$attendee = array(
|
|
|
|
'name' => $attr['params']['CN'],
|
2011-11-09 15:37:21 +01:00
|
|
|
'email' => preg_replace('/^mailto:/i', '', $attr['value']),
|
2011-07-29 17:51:04 +02:00
|
|
|
'role' => $attr['params']['ROLE'] ? $attr['params']['ROLE'] : 'REQ-PARTICIPANT',
|
|
|
|
'status' => $attr['params']['PARTSTAT'],
|
|
|
|
'rsvp' => $attr['params']['RSVP'] == 'TRUE',
|
|
|
|
);
|
|
|
|
if ($organizer && $organizer['email'] == $attendee['email'])
|
|
|
|
$attendee['role'] = 'ORGANIZER';
|
|
|
|
|
|
|
|
$event['attendees'][] = $attendee;
|
|
|
|
$_attendees[$attendee['email']] = count($event['attendees']) - 1;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'TRANSP':
|
|
|
|
$event['free_busy'] = $attr['value'] == 'TRANSPARENT' ? 'free' : 'busy';
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'STATUS':
|
|
|
|
if ($attr['value'] == 'TENTATIVE')
|
2013-02-21 17:33:17 +01:00
|
|
|
$event['free_busy'] = 'tentative';
|
|
|
|
else if ($attr['value'] == 'CANCELLED')
|
|
|
|
$event['cancelled'] = true;
|
2011-07-29 17:51:04 +02:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 'PRIORITY':
|
|
|
|
if (is_numeric($attr['value'])) {
|
2011-10-05 12:16:43 +02:00
|
|
|
$event['priority'] = $attr['value'];
|
2011-07-29 17:51:04 +02:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'RRULE':
|
|
|
|
// parse recurrence rule attributes
|
|
|
|
foreach (explode(';', $attr['value']) as $par) {
|
|
|
|
list($k, $v) = explode('=', $par);
|
|
|
|
$params[$k] = $v;
|
|
|
|
}
|
|
|
|
if ($params['UNTIL'])
|
2012-07-06 17:15:45 +02:00
|
|
|
$params['UNTIL'] = date_create($params['UNTIL']);
|
2011-07-29 17:51:04 +02:00
|
|
|
if (!$params['INTERVAL'])
|
|
|
|
$params['INTERVAL'] = 1;
|
|
|
|
|
|
|
|
$event['recurrence'] = $params;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'EXDATE':
|
|
|
|
break;
|
2011-08-14 15:35:22 +02:00
|
|
|
|
|
|
|
case 'RECURRENCE-ID':
|
|
|
|
$event['recurrence_id'] = $this->_date2time($attr['value']);
|
|
|
|
break;
|
2011-07-29 17:51:04 +02:00
|
|
|
|
2011-08-22 19:20:46 +02:00
|
|
|
case 'SEQUENCE':
|
|
|
|
$event['sequence'] = intval($attr['value']);
|
|
|
|
break;
|
|
|
|
|
2011-07-29 17:51:04 +02:00
|
|
|
case 'DESCRIPTION':
|
|
|
|
case 'LOCATION':
|
2013-05-16 13:32:01 +02:00
|
|
|
case 'URL':
|
2011-07-29 17:51:04 +02:00
|
|
|
$event[strtolower($attr['name'])] = $attr['value'];
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'CLASS':
|
|
|
|
case 'X-CALENDARSERVER-ACCESS':
|
|
|
|
$sensitivity_map = array('PUBLIC' => 0, 'PRIVATE' => 1, 'CONFIDENTIAL' => 2);
|
|
|
|
$event['sensitivity'] = $sensitivity_map[$attr['value']];
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'X-MICROSOFT-CDO-BUSYSTATUS':
|
|
|
|
if ($attr['value'] == 'OOF')
|
|
|
|
$event['free_busy'] == 'outofoffice';
|
|
|
|
else if (in_array($attr['value'], array('FREE', 'BUSY', 'TENTATIVE')))
|
|
|
|
$event['free_busy'] = strtolower($attr['value']);
|
|
|
|
break;
|
2013-02-21 17:41:41 +01:00
|
|
|
|
2013-07-04 17:07:58 +02:00
|
|
|
case 'ATTACH':
|
|
|
|
// decode inline attachment
|
|
|
|
if (strtoupper($attr['params']['VALUE']) == 'BINARY' && !empty($attr['value'])) {
|
|
|
|
$data = !strcasecmp($attr['params']['ENCODING'], 'BASE64') ? base64_decode($attr['value']) : $attr['value'];
|
|
|
|
$mimetype = $attr['params']['FMTTYPE'] ? $attr['params']['FMTTYPE'] : rcube_mime::file_content_type($data, $attr['params']['X-LABEL'], 'application/octet-stream', true);
|
|
|
|
$extensions = rcube_mime::get_mime_extensions($mimetype);
|
|
|
|
$filename = $attr['params']['X-LABEL'] ? $attr['params']['X-LABEL'] : 'attachment' . count($event['attachments']) . '.' . $extensions[0];
|
|
|
|
$event['attachments'][] = array(
|
|
|
|
'mimetype' => $mimetype,
|
|
|
|
'name' => $filename,
|
|
|
|
'data' => $data,
|
|
|
|
'size' => strlen($data),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
else if (!empty($attr['value']) && preg_match('!^[hftps]+://!', $attr['value'])) {
|
|
|
|
// TODO: add support for displaying/managing link attachments in UI
|
|
|
|
$event['links'][] = $attr['value'];
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2013-02-21 17:41:41 +01:00
|
|
|
default:
|
|
|
|
if (substr($attr['name'], 0, 2) == 'X-')
|
|
|
|
$event['x-custom'][] = array($attr['name'], $attr['value']);
|
2011-07-29 17:51:04 +02:00
|
|
|
}
|
|
|
|
}
|
2011-10-12 22:39:14 +02:00
|
|
|
|
|
|
|
// find alarms
|
|
|
|
if ($valarm = $ve->findComponent('valarm')) {
|
|
|
|
$action = 'DISPLAY';
|
|
|
|
$trigger = null;
|
|
|
|
|
|
|
|
foreach ($valarm->getAllAttributes() as $attr) {
|
|
|
|
switch ($attr['name']) {
|
|
|
|
case 'TRIGGER':
|
|
|
|
if ($attr['params']['VALUE'] == 'DATE-TIME') {
|
|
|
|
$trigger = '@' . $attr['value'];
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$trigger = $attr['value'];
|
|
|
|
$offset = abs($trigger);
|
|
|
|
$unit = 'S';
|
|
|
|
if ($offset % 86400 == 0) {
|
|
|
|
$unit = 'D';
|
|
|
|
$trigger = intval($trigger / 86400);
|
|
|
|
}
|
|
|
|
else if ($offset % 3600 == 0) {
|
|
|
|
$unit = 'H';
|
|
|
|
$trigger = intval($trigger / 3600);
|
|
|
|
}
|
|
|
|
else if ($offset % 60 == 0) {
|
|
|
|
$unit = 'M';
|
|
|
|
$trigger = intval($trigger / 60);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'ACTION':
|
|
|
|
$action = $attr['value'];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ($trigger)
|
|
|
|
$event['alarms'] = $trigger . $unit . ':' . $action;
|
|
|
|
}
|
|
|
|
|
2011-07-29 17:51:04 +02:00
|
|
|
// add organizer to attendees list if not already present
|
|
|
|
if ($organizer && !isset($_attendees[$organizer['email']]))
|
|
|
|
array_unshift($event['attendees'], $organizer);
|
|
|
|
|
|
|
|
// make sure the event has an UID
|
2011-07-29 09:19:18 +02:00
|
|
|
if (!$event['uid'])
|
2012-10-15 12:36:23 +02:00
|
|
|
$event['uid'] = $this->cal->generate_uid();
|
2011-07-29 09:19:18 +02:00
|
|
|
|
|
|
|
return $event;
|
|
|
|
}
|
2011-08-14 15:35:22 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper method to correctly interpret an all-day date value
|
|
|
|
*/
|
|
|
|
private function _date2time($prop)
|
|
|
|
{
|
2011-09-28 16:47:18 +02:00
|
|
|
// create timestamp at 12:00 in user's timezone
|
2012-07-06 17:15:45 +02:00
|
|
|
if (is_array($prop))
|
|
|
|
return date_create(sprintf('%04d%02d%02dT120000', $prop['year'], $prop['month'], $prop['mday']), $this->cal->timezone);
|
|
|
|
else if (is_numeric($prop))
|
|
|
|
return date_create('@'.$prop);
|
|
|
|
|
2012-01-04 23:05:34 +01:00
|
|
|
return $prop;
|
2011-08-14 15:35:22 +02:00
|
|
|
}
|
|
|
|
|
2011-07-29 09:19:18 +02:00
|
|
|
|
2011-08-14 15:35:22 +02:00
|
|
|
/**
|
|
|
|
* Free resources by clearing member vars
|
|
|
|
*/
|
|
|
|
public function reset()
|
|
|
|
{
|
|
|
|
$this->method = '';
|
|
|
|
$this->events = array();
|
|
|
|
}
|
2011-07-29 09:19:18 +02:00
|
|
|
|
2011-05-20 19:04:25 +02:00
|
|
|
/**
|
|
|
|
* Export events to iCalendar format
|
|
|
|
*
|
2011-07-29 17:51:04 +02:00
|
|
|
* @param array Events as array
|
|
|
|
* @param string VCalendar method to advertise
|
|
|
|
* @param boolean Directly send data to stdout instead of returning
|
2013-07-04 17:07:58 +02:00
|
|
|
* @param callable Callback function to fetch attachment contents, false if no attachment export
|
2011-05-20 19:04:25 +02:00
|
|
|
* @return string Events in iCalendar format (http://tools.ietf.org/html/rfc5545)
|
|
|
|
*/
|
2013-07-04 17:07:58 +02:00
|
|
|
public function export($events, $method = null, $write = false, $get_attachment = false, $recurrence_id = null)
|
2011-07-06 22:42:23 +02:00
|
|
|
{
|
2013-07-04 17:07:58 +02:00
|
|
|
$memory_limit = parse_bytes(ini_get('memory_limit'));
|
|
|
|
|
2013-02-28 10:44:15 +01:00
|
|
|
if (!$recurrence_id) {
|
|
|
|
$ical = "BEGIN:VCALENDAR" . self::EOL;
|
|
|
|
$ical .= "VERSION:2.0" . self::EOL;
|
|
|
|
$ical .= "PRODID:-//Roundcube Webmail " . RCMAIL_VERSION . "//NONSGML Calendar//EN" . self::EOL;
|
|
|
|
$ical .= "CALSCALE:GREGORIAN" . self::EOL;
|
|
|
|
|
|
|
|
if ($method)
|
|
|
|
$ical .= "METHOD:" . strtoupper($method) . self::EOL;
|
|
|
|
|
|
|
|
if ($write) {
|
|
|
|
echo $ical;
|
|
|
|
$ical = '';
|
|
|
|
}
|
2011-07-29 17:51:04 +02:00
|
|
|
}
|
2013-02-28 10:44:15 +01:00
|
|
|
|
2011-05-20 19:04:25 +02:00
|
|
|
foreach ($events as $event) {
|
2011-07-29 17:51:04 +02:00
|
|
|
$vevent = "BEGIN:VEVENT" . self::EOL;
|
2013-03-14 09:30:12 +01:00
|
|
|
$vevent .= "UID:" . self::escape($event['uid']) . self::EOL;
|
2012-07-06 17:15:45 +02:00
|
|
|
$vevent .= $this->format_datetime("DTSTAMP", $event['changed'] ?: new DateTime(), false, true) . self::EOL;
|
2012-10-18 19:45:01 +02:00
|
|
|
if ($event['sequence'])
|
2013-02-28 10:44:15 +01:00
|
|
|
$vevent .= "SEQUENCE:" . intval($event['sequence']) . self::EOL;
|
|
|
|
if ($recurrence_id)
|
|
|
|
$vevent .= $recurrence_id . self::EOL;
|
|
|
|
|
2011-07-29 09:19:18 +02:00
|
|
|
// correctly set all-day dates
|
|
|
|
if ($event['allday']) {
|
2012-07-06 17:15:45 +02:00
|
|
|
$event['end'] = clone $event['end'];
|
|
|
|
$event['end']->add(new DateInterval('P1D')); // ends the next day
|
|
|
|
$vevent .= $this->format_datetime("DTSTART", $event['start'], true) . self::EOL;
|
|
|
|
$vevent .= $this->format_datetime("DTEND", $event['end'], true) . self::EOL;
|
2011-07-29 09:19:18 +02:00
|
|
|
}
|
|
|
|
else {
|
2012-07-06 17:15:45 +02:00
|
|
|
$vevent .= $this->format_datetime("DTSTART", $event['start'], false) . self::EOL;
|
|
|
|
$vevent .= $this->format_datetime("DTEND", $event['end'], false) . self::EOL;
|
2011-07-29 09:19:18 +02:00
|
|
|
}
|
2013-03-14 09:30:12 +01:00
|
|
|
$vevent .= "SUMMARY:" . self::escape($event['title']) . self::EOL;
|
|
|
|
$vevent .= "DESCRIPTION:" . self::escape($event['description']) . self::EOL;
|
2011-07-27 17:42:45 +02:00
|
|
|
|
|
|
|
if (!empty($event['attendees'])){
|
2011-07-29 17:51:04 +02:00
|
|
|
$vevent .= $this->_get_attendees($event['attendees']);
|
2011-07-27 17:42:45 +02:00
|
|
|
}
|
|
|
|
|
2011-05-20 19:04:25 +02:00
|
|
|
if (!empty($event['location'])) {
|
2013-03-14 09:30:12 +01:00
|
|
|
$vevent .= "LOCATION:" . self::escape($event['location']) . self::EOL;
|
2011-05-20 19:04:25 +02:00
|
|
|
}
|
2013-05-16 13:32:01 +02:00
|
|
|
if (!empty($event['url'])) {
|
|
|
|
$vevent .= "URL:" . self::escape($event['url']) . self::EOL;
|
|
|
|
}
|
2013-02-28 10:44:15 +01:00
|
|
|
if ($event['recurrence'] && !$recurrence_id) {
|
2012-08-16 08:57:25 +02:00
|
|
|
$vevent .= "RRULE:" . libcalendaring::to_rrule($event['recurrence'], self::EOL) . self::EOL;
|
2011-07-06 22:42:23 +02:00
|
|
|
}
|
2011-05-20 19:04:25 +02:00
|
|
|
if(!empty($event['categories'])) {
|
2013-03-14 09:30:12 +01:00
|
|
|
$vevent .= "CATEGORIES:" . self::escape(strtoupper($event['categories'])) . self::EOL;
|
2011-05-20 19:04:25 +02:00
|
|
|
}
|
2011-07-06 22:42:23 +02:00
|
|
|
if ($event['sensitivity'] > 0) {
|
2011-07-29 17:51:04 +02:00
|
|
|
$vevent .= "CLASS:" . ($event['sensitivity'] == 2 ? 'CONFIDENTIAL' : 'PRIVATE') . self::EOL;
|
2011-07-06 22:42:23 +02:00
|
|
|
}
|
|
|
|
if ($event['alarms']) {
|
|
|
|
list($trigger, $action) = explode(':', $event['alarms']);
|
2012-08-16 08:57:25 +02:00
|
|
|
$val = libcalendaring::parse_alaram_value($trigger);
|
2011-07-06 22:42:23 +02:00
|
|
|
|
2011-07-29 17:51:04 +02:00
|
|
|
$vevent .= "BEGIN:VALARM\n";
|
|
|
|
if ($val[1]) $vevent .= "TRIGGER:" . preg_replace('/^([-+])(.+)/', '\\1PT\\2', $trigger) . self::EOL;
|
|
|
|
else $vevent .= "TRIGGER;VALUE=DATE-TIME:" . gmdate('Ymd\THis\Z', $val[0]) . self::EOL;
|
2013-03-14 09:30:12 +01:00
|
|
|
if ($action) $vevent .= "ACTION:" . self::escape(strtoupper($action)) . self::EOL;
|
2011-07-29 17:51:04 +02:00
|
|
|
$vevent .= "END:VALARM\n";
|
2011-05-20 19:04:25 +02:00
|
|
|
}
|
2011-07-29 18:41:43 +02:00
|
|
|
|
2011-07-29 17:51:04 +02:00
|
|
|
$vevent .= "TRANSP:" . ($event['free_busy'] == 'free' ? 'TRANSPARENT' : 'OPAQUE') . self::EOL;
|
2011-07-06 22:42:23 +02:00
|
|
|
|
2011-10-05 12:16:43 +02:00
|
|
|
if ($event['priority']) {
|
|
|
|
$vevent .= "PRIORITY:" . $event['priority'] . self::EOL;
|
2011-08-04 23:54:29 +02:00
|
|
|
}
|
|
|
|
|
2011-07-29 18:41:43 +02:00
|
|
|
if ($event['cancelled'])
|
|
|
|
$vevent .= "STATUS:CANCELLED" . self::EOL;
|
|
|
|
else if ($event['free_busy'] == 'tentative')
|
|
|
|
$vevent .= "STATUS:TENTATIVE" . self::EOL;
|
|
|
|
|
2013-02-21 17:41:41 +01:00
|
|
|
foreach ((array)$event['x-custom'] as $prop)
|
2013-03-14 09:30:12 +01:00
|
|
|
$vevent .= $prop[0] . ':' . self::escape($prop[1]) . self::EOL;
|
2013-02-21 17:41:41 +01:00
|
|
|
|
2013-07-04 17:07:58 +02:00
|
|
|
// export attachments using the given callback function
|
|
|
|
if (is_callable($get_attachment) && !empty($event['attachments'])) {
|
|
|
|
foreach ((array)$event['attachments'] as $attach) {
|
|
|
|
// check available memory and skip attachment export if we can't buffer it
|
|
|
|
if ($memory_limit > 0 && ($memory_used = function_exists('memory_get_usage') ? memory_get_usage() : 16*1024*1024)
|
|
|
|
&& $attach['size'] && $memory_used + $attach['size'] * 3 > $memory_limit) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// TODO: let the callback print the data directly to stdout (with b64 encoding)
|
|
|
|
if ($data = call_user_func($get_attachment, $attach['id'], $event)) {
|
|
|
|
$vevent .= sprintf('ATTACH;VALUE=BINARY;ENCODING=BASE64;FMTTYPE=%s;X-LABEL=%s:',
|
|
|
|
self::escape($attach['mimetype']), self::escape($attach['name']));
|
|
|
|
$vevent .= base64_encode($data) . self::EOL;
|
|
|
|
}
|
|
|
|
unset($data); // attempt to free memory
|
|
|
|
}
|
|
|
|
}
|
2011-07-06 22:42:23 +02:00
|
|
|
|
2011-07-29 17:51:04 +02:00
|
|
|
$vevent .= "END:VEVENT" . self::EOL;
|
2013-02-28 10:44:15 +01:00
|
|
|
|
|
|
|
// append recurrence exceptions
|
|
|
|
if ($event['recurrence']['EXCEPTIONS'] && !$recurrence_id) {
|
|
|
|
foreach ($event['recurrence']['EXCEPTIONS'] as $ex) {
|
|
|
|
$exdate = clone $event['start'];
|
|
|
|
$exdate->setDate($ex['start']->format('Y'), $ex['start']->format('n'), $ex['start']->format('j'));
|
2013-07-04 17:07:58 +02:00
|
|
|
$vevent .= $this->export(array($ex), null, false, $get_attachment,
|
2013-02-28 10:44:15 +01:00
|
|
|
$this->format_datetime('RECURRENCE-ID', $exdate, $event['allday']));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-07-29 17:51:04 +02:00
|
|
|
if ($write)
|
|
|
|
echo rcube_vcard::rfc2425_fold($vevent);
|
|
|
|
else
|
|
|
|
$ical .= $vevent;
|
2011-05-20 19:04:25 +02:00
|
|
|
}
|
2011-07-06 22:42:23 +02:00
|
|
|
|
2013-02-28 10:44:15 +01:00
|
|
|
if (!$recurrence_id) {
|
|
|
|
$ical .= "END:VCALENDAR" . self::EOL;
|
2011-07-29 17:51:04 +02:00
|
|
|
|
2013-02-28 10:44:15 +01:00
|
|
|
if ($write) {
|
|
|
|
echo $ical;
|
|
|
|
return true;
|
|
|
|
}
|
2011-07-29 17:51:04 +02:00
|
|
|
}
|
2011-05-20 19:04:25 +02:00
|
|
|
|
2011-07-27 17:42:45 +02:00
|
|
|
// fold lines to 75 chars
|
|
|
|
return rcube_vcard::rfc2425_fold($ical);
|
2011-05-20 19:04:25 +02:00
|
|
|
}
|
2012-07-11 09:59:53 +02:00
|
|
|
|
2012-07-06 17:15:45 +02:00
|
|
|
private function format_datetime($attr, $dt, $dateonly = false, $utc = false)
|
|
|
|
{
|
2012-07-11 09:59:53 +02:00
|
|
|
if (is_numeric($dt))
|
|
|
|
$dt = new DateTime('@'.$dt);
|
|
|
|
|
2012-07-06 17:15:45 +02:00
|
|
|
if ($utc)
|
|
|
|
$dt->setTimezone(new DateTimeZone('UTC'));
|
|
|
|
|
|
|
|
if ($dateonly) {
|
|
|
|
return $attr . ';VALUE=DATE:' . $dt->format('Ymd');
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// <ATTR>;TZID=Europe/Zurich:20120706T210000
|
|
|
|
$tz = $dt->getTimezone();
|
2013-01-23 10:06:44 +01:00
|
|
|
$tzname = $tz ? $tz->getName() : null;
|
2013-03-14 09:30:12 +01:00
|
|
|
$tzid = $tzname && $tzname != 'UTC' && $tzname != '+00:00' ? ';TZID=' . self::escape($tzname) : '';
|
2012-10-18 19:45:01 +02:00
|
|
|
return $attr . $tzid . ':' . $dt->format('Ymd\THis' . ($tzid ? '' : '\Z'));
|
2012-07-06 17:15:45 +02:00
|
|
|
}
|
|
|
|
}
|
2012-07-11 09:59:53 +02:00
|
|
|
|
2012-11-21 22:17:20 +01:00
|
|
|
/**
|
2013-03-14 09:43:29 +01:00
|
|
|
* Escape values according to RFC 2445 4.3.11
|
2012-11-21 22:17:20 +01:00
|
|
|
*/
|
2013-03-14 09:30:12 +01:00
|
|
|
private function escape($str)
|
2011-07-06 22:47:36 +02:00
|
|
|
{
|
2013-03-14 09:30:12 +01:00
|
|
|
return strtr($str, array('\\' => '\\\\', "\n" => '\n', ';' => '\;', ',' => '\,'));
|
2011-07-06 22:47:36 +02:00
|
|
|
}
|
2011-07-19 17:11:33 +03:00
|
|
|
|
2011-07-27 17:42:45 +02:00
|
|
|
/**
|
2011-07-19 17:11:33 +03:00
|
|
|
* Construct the orginizer of the event.
|
|
|
|
* @param Array Attendees and roles
|
|
|
|
*
|
|
|
|
*/
|
2011-07-27 17:42:45 +02:00
|
|
|
private function _get_attendees($ats)
|
|
|
|
{
|
|
|
|
$organizer = "";
|
|
|
|
$attendees = "";
|
|
|
|
foreach ($ats as $at) {
|
2012-11-21 22:17:20 +01:00
|
|
|
if ($at['role'] == "ORGANIZER") {
|
|
|
|
if ($at['email']) {
|
|
|
|
$organizer .= "ORGANIZER;";
|
|
|
|
if (!empty($at['name']))
|
|
|
|
$organizer .= 'CN="' . $at['name'] . '"';
|
|
|
|
$organizer .= ":mailto:". $at['email'] . self::EOL;
|
|
|
|
}
|
2011-07-27 17:42:45 +02:00
|
|
|
}
|
2012-11-21 22:17:20 +01:00
|
|
|
else if ($at['email']) {
|
2011-07-27 17:42:45 +02:00
|
|
|
//I am an attendee
|
|
|
|
$attendees .= "ATTENDEE;ROLE=" . $at['role'] . ";PARTSTAT=" . $at['status'];
|
2011-08-14 15:35:22 +02:00
|
|
|
if ($at['rsvp'])
|
|
|
|
$attendees .= ";RSVP=TRUE";
|
2011-07-27 17:42:45 +02:00
|
|
|
if (!empty($at['name']))
|
2011-07-27 18:55:51 +02:00
|
|
|
$attendees .= ';CN="' . $at['name'] . '"';
|
|
|
|
$attendees .= ":mailto:" . $at['email'] . self::EOL;
|
2011-07-27 17:42:45 +02:00
|
|
|
}
|
2011-07-19 17:11:33 +03:00
|
|
|
}
|
2011-07-27 17:42:45 +02:00
|
|
|
|
|
|
|
return $organizer . $attendees;
|
2011-07-19 17:11:33 +03:00
|
|
|
}
|
2011-07-27 17:42:45 +02:00
|
|
|
|
2011-10-27 10:20:46 +02:00
|
|
|
}
|