Ask about notifying attendess when moving event; finish import from ical mail attachments

This commit is contained in:
Thomas Bruederli 2011-07-29 17:51:04 +02:00
parent 0f266481dd
commit 593a3d3faa
10 changed files with 347 additions and 100 deletions

View file

@ -120,6 +120,7 @@ class calendar extends rcube_plugin
$this->register_action('freebusy-times', array($this, 'freebusy_times')); $this->register_action('freebusy-times', array($this, 'freebusy_times'));
$this->register_action('randomdata', array($this, 'generate_randomdata')); $this->register_action('randomdata', array($this, 'generate_randomdata'));
$this->register_action('print', array($this,'print_view')); $this->register_action('print', array($this,'print_view'));
$this->register_action('mailimportevent', array($this, 'mail_import_event'));
// remove undo information... // remove undo information...
if ($undo = $_SESSION['calendar_event_undo']) { if ($undo = $_SESSION['calendar_event_undo']) {
@ -137,10 +138,13 @@ class calendar extends rcube_plugin
$this->add_hook('preferences_list', array($this, 'preferences_list')); $this->add_hook('preferences_list', array($this, 'preferences_list'));
$this->add_hook('preferences_save', array($this, 'preferences_save')); $this->add_hook('preferences_save', array($this, 'preferences_save'));
} }
else if ($this->rc->task == 'mail' && ($this->rc->action == 'show' || $this->rc->action == 'preview')) { else if ($this->rc->task == 'mail') {
// hooks to catch event invitations on incoming mails
if ($this->rc->action == 'show' || $this->rc->action == 'preview') {
$this->add_hook('message_load', array($this, 'mail_message_load')); $this->add_hook('message_load', array($this, 'mail_message_load'));
$this->add_hook('template_object_messagebody', array($this, 'mail_messagebody_html')); $this->add_hook('template_object_messagebody', array($this, 'mail_messagebody_html'));
} }
}
// add hook to display alarms // add hook to display alarms
$this->add_hook('keep_alive', array($this, 'keep_alive')); $this->add_hook('keep_alive', array($this, 'keep_alive'));
@ -583,7 +587,7 @@ class calendar extends rcube_plugin
} }
// send out notifications // send out notifications
if ($success && $event['_notify'] && $event['attendees']) { if ($success && $event['_notify'] && ($event['attendees'] || $old['attendees'])) {
// make sure we have the complete record // make sure we have the complete record
$event = $this->driver->get_event($event); $event = $this->driver->get_event($event);
@ -657,10 +661,10 @@ class calendar extends rcube_plugin
$events = $this->driver->load_events($start, $end, null, $calendar_name, 0); $events = $this->driver->load_events($start, $end, null, $calendar_name, 0);
header("Content-Type: text/calendar"); header("Content-Type: text/calendar");
header("Content-Disposition: inline; filename=".$calendar_name); header("Content-Disposition: inline; filename=".$calendar_name.'.ics');
$this->load_ical(); $this->load_ical();
echo $this->ical->export($events); $this->ical->export($events, '', true);
exit; exit;
} }
@ -841,7 +845,7 @@ class calendar extends rcube_plugin
break; break;
} }
if ($rrule['INTERVAL'] == 1) if ($rrule['INTERVAL'] <= 1)
$freq = $this->gettext(strtolower($rrule['FREQ'])); $freq = $this->gettext(strtolower($rrule['FREQ']));
if ($rrule['COUNT']) if ($rrule['COUNT'])
@ -1526,7 +1530,7 @@ class calendar extends rcube_plugin
} }
/** /**
* Add UI elements to copy event invitations or updates to the calendar * Add UI element to copy event invitations or updates to the calendar
*/ */
public function mail_messagebody_html($p) public function mail_messagebody_html($p)
{ {
@ -1545,6 +1549,7 @@ class calendar extends rcube_plugin
if (empty($events)) if (empty($events))
continue; continue;
// TODO: show more iTip options like (accept, deny, etc.)
foreach ($events as $idx => $event) { foreach ($events as $idx => $event) {
// add box below messsage body // add box below messsage body
$html .= html::p('calendar-invitebox', $html .= html::p('calendar-invitebox',
@ -1553,7 +1558,7 @@ class calendar extends rcube_plugin
html::tag('input', array( html::tag('input', array(
'type' => 'button', 'type' => 'button',
'class' => 'button', 'class' => 'button',
# 'onclick' => "rcube_calendar.add_event_from_mail('" . JQ($part.':'.$idx) . "', '" . JQ($event['title']) . "')", 'onclick' => "rcube_calendar.add_event_from_mail('" . JQ($part.':'.$idx) . "', '" . JQ($event['title']) . "')",
'value' => $this->gettext('importtocalendar'), 'value' => $this->gettext('importtocalendar'),
)) ))
); );
@ -1569,6 +1574,75 @@ class calendar extends rcube_plugin
return $p; return $p;
} }
/**
* Handler for POST request to import an event attached to a mail message
*/
public function mail_import_event()
{
$uid = get_input_value('_uid', RCUBE_INPUT_POST);
$mbox = get_input_value('_mbox', RCUBE_INPUT_POST);
$mime_id = get_input_value('_part', RCUBE_INPUT_POST);
// establish imap connection
$this->rc->imap_connect();
$this->rc->imap->set_mailbox($mbox);
if ($uid && $mime_id) {
list($mime_id, $index) = explode(':', $mime_id);
$part = $this->rc->imap->get_message_part($uid, $mime_id);
}
$this->load_ical();
$events = $this->ical->import($part);
$error_msg = $this->gettext('errorimportingevent');
$success = false;
// successfully parsed events?
if (!empty($events) && ($event = $events[$index])) {
// find writeable calendar to store event
$cal_id = $this->rc->config->get('calendar_default_calendar');
$calendars = $this->driver->list_calendars();
$calendar = $calendars[$cal_id] ? $calendars[$calname] : null;
if (!$calendar || $calendar['readonly']) {
foreach ($calendars as $cal) {
if (!$cal['readonly']) {
$calendar = $cal;
break;
}
}
}
if ($calendar && !$calendar['readonly']) {
$event['id'] = $event['uid'];
$event['calendar'] = $calendar['id'];
// check for existing event with the same UID
$existing = $this->driver->get_event($event);
if ($existing) {
if ($event['changed'] >= $existing['changed'])
$success = $this->driver->edit_event($event);
else
$error_msg = $this->gettext('newerversionexists');
}
else if (!$existing) {
$success = $this->driver->new_event($event);
}
}
else
$error_msg = $this->gettext('nowritecalendarfound');
}
if ($success)
$this->rc->output->command('display_message', $this->gettext(array('name' => 'importedsuccessfully', 'vars' => array('calendar' => $calendar['name']))), 'confirmation');
else
$this->rc->output->command('display_message', $error_msg, 'error');
$this->rc->output->send();
}
/** /**
* Checks if specified message part is a vcalendar data * Checks if specified message part is a vcalendar data
* *

View file

@ -166,6 +166,15 @@ function rcube_calendar(settings)
} }
// static methods
rcube_calendar.add_event_from_mail = function(mime_id, title)
{
var lock = rcmail.set_busy(true, 'loading');
rcmail.http_post('calendar/mailimportevent', '_uid='+rcmail.env.uid+'&_mbox='+urlencode(rcmail.env.mailbox)+'&_part='+urlencode(mime_id), lock);
return false;
};
// extend jQuery // extend jQuery
(function($){ (function($){
$.fn.serializeJSON = function(){ $.fn.serializeJSON = function(){
@ -183,5 +192,10 @@ window.rcmail && rcmail.addEventListener('init', function(evt) {
var cal = new rcube_calendar(rcmail.env.calendar_settings); var cal = new rcube_calendar(rcmail.env.calendar_settings);
rcmail.addEventListener('plugin.display_alarms', function(alarms){ cal.display_alarms(alarms); }); rcmail.addEventListener('plugin.display_alarms', function(alarms){ cal.display_alarms(alarms); });
} }
rcmail.addEventListener('plugin.ping_url', function(p){
var action = p.action;
p.action = p.event = null;
new Image().src = rcmail.url(action, p);
});
}); });

View file

@ -140,6 +140,12 @@ function rcube_calendar_ui(settings)
return date.getHours() >= settings['work_start'] && date.getHours() < settings['work_end']; return date.getHours() >= settings['work_start'] && date.getHours() < settings['work_end'];
}; };
// check if the event has 'real' attendees, excluding the current user
var has_attendees = function(event)
{
return (event.attendees && (event.attendees.length > 1 || event.attendees[0].email != settings.event_owner.email));
};
// create a nice human-readable string for the date/time range // create a nice human-readable string for the date/time range
var event_date_text = function(event) var event_date_text = function(event)
{ {
@ -465,7 +471,7 @@ function rcube_calendar_ui(settings)
for (var j=0; j < event.attendees.length; j++) for (var j=0; j < event.attendees.length; j++)
add_attendee(event.attendees[j], true); add_attendee(event.attendees[j], true);
if (event.attendees.length > 1 || event.attendees[0].email != settings.event_owner.email) { if (has_attendees(event)) {
notify.checked = invite.checked = true; // enable notification by default notify.checked = invite.checked = true; // enable notification by default
} }
} }
@ -1292,43 +1298,86 @@ function rcube_calendar_ui(settings)
return true; return true;
}; };
// display confirm dialog when modifying/deleting a recurring event where the user needs to select the savemode // display confirm dialog when modifying/deleting an event
var recurring_edit_confirm = function(event, action) { var update_event_confirm = function(action, event, data)
var $dialog = $('<div>').addClass('edit-recurring-warning'); {
$dialog.html('<div class="message"><span class="ui-icon ui-icon-alert"></span>' + if (!data) data = event;
var html = '';
// event has attendees, ask whether to notify them
if (has_attendees(event)) {
html += '<div class="message">' +
'<label><input class="confirm-attendees-donotify" type="checkbox" checked="checked" value="1" name="notify" />&nbsp;' +
rcmail.gettext('sendnotifications', 'calendar') +
'</label></div>';
}
// recurring event: user needs to select the savemode
if (event.recurrence) {
html += '<div class="message"><span class="ui-icon ui-icon-alert"></span>' +
rcmail.gettext((action == 'remove' ? 'removerecurringeventwarning' : 'changerecurringeventwarning'), 'calendar') + '</div>' + rcmail.gettext((action == 'remove' ? 'removerecurringeventwarning' : 'changerecurringeventwarning'), 'calendar') + '</div>' +
'<div class="savemode">' + '<div class="savemode">' +
'<a href="#current" class="button">' + rcmail.gettext('currentevent', 'calendar') + '</a>' + '<a href="#current" class="button">' + rcmail.gettext('currentevent', 'calendar') + '</a>' +
'<a href="#future" class="button">' + rcmail.gettext('futurevents', 'calendar') + '</a>' + '<a href="#future" class="button">' + rcmail.gettext('futurevents', 'calendar') + '</a>' +
'<a href="#all" class="button">' + rcmail.gettext('allevents', 'calendar') + '</a>' + '<a href="#all" class="button">' + rcmail.gettext('allevents', 'calendar') + '</a>' +
(action != 'remove' ? '<a href="#new" class="button">' + rcmail.gettext('saveasnew', 'calendar') + '</a>' : '') + (action != 'remove' ? '<a href="#new" class="button">' + rcmail.gettext('saveasnew', 'calendar') + '</a>' : '') +
'</div>'); '</div>';
}
// show dialog
if (html) {
var $dialog = $('<div>').html(html);
$dialog.find('a.button').button().click(function(e){ $dialog.find('a.button').button().click(function(e){
event.savemode = String(this.href).replace(/.+#/, ''); data.savemode = String(this.href).replace(/.+#/, '');
update_event(action, event); if ($dialog.find('input.confirm-attendees-donotify').get(0))
data._notify = $dialog.find('input.confirm-attendees-donotify').get(0).checked ? 1 : 0;
update_event(action, data);
$dialog.dialog("destroy").hide(); $dialog.dialog("destroy").hide();
return false; return false;
}); });
var buttons = [{
text: rcmail.gettext('cancel', 'calendar'),
click: function() {
$(this).dialog("close");
}
}];
if (!event.recurrence) {
buttons.push({
text: rcmail.gettext((action == 'remove' ? 'remove' : 'save'), 'calendar'),
click: function() {
data._notify = $dialog.find('input.confirm-attendees-donotify').get(0).checked ? 1 : 0;
update_event(action, data);
$(this).dialog("close");
}
});
}
$dialog.dialog({ $dialog.dialog({
modal: true, modal: true,
width: 420, width: 420,
dialogClass: 'warning', dialogClass: 'warning',
title: rcmail.gettext((action == 'remove' ? 'removerecurringevent' : 'changerecurringevent'), 'calendar'), title: rcmail.gettext((action == 'remove' ? 'removeeventconfirm' : 'changeeventconfirm'), 'calendar'),
buttons: [ buttons: buttons,
{
text: rcmail.gettext('cancel', 'calendar'),
click: function() {
$(this).dialog("close");
}
}
],
close: function(){ close: function(){
$dialog.dialog("destroy").hide(); $dialog.dialog("destroy").hide();
if (!rcmail.busy)
fc.fullCalendar('refetchEvents'); fc.fullCalendar('refetchEvents');
} }
}).show(); }).addClass('event-update-confirm').show();
return false;
}
// show regular confirm box when deleting
else if (action == 'remove') {
if (!confirm(rcmail.gettext('deleteventconfirm', 'calendar')))
return false;
}
// do update
update_event(action, data);
return true; return true;
}; };
@ -1361,17 +1410,8 @@ function rcube_calendar_ui(settings)
// delete the given event after showing a confirmation dialog // delete the given event after showing a confirmation dialog
this.delete_event = function(event) { this.delete_event = function(event) {
// show extended confirm dialog for recurring events, use jquery UI dialog // show confirm dialog for recurring events, use jquery UI dialog
if (event.recurrence) return update_event_confirm('remove', event, { id:event.id, calendar:event.calendar });
return recurring_edit_confirm({ id:event.id, calendar:event.calendar }, 'remove');
// send remove request to plugin
if (confirm(rcmail.gettext('deleteventconfirm', 'calendar'))) {
update_event('remove', { id:event.id, calendar:event.calendar });
return true;
}
return false;
}; };
// opens a jquery UI dialog with event properties (or empty for creating a new calendar) // opens a jquery UI dialog with event properties (or empty for creating a new calendar)
@ -1752,10 +1792,7 @@ function rcube_calendar_ui(settings)
end: date2unixtime(event.end), end: date2unixtime(event.end),
allday: allDay?1:0 allday: allDay?1:0
}; };
if (event.recurrence) update_event_confirm('move', event, data);
recurring_edit_confirm(data, 'move');
else
update_event('move', data);
}, },
// callback for event resizing // callback for event resizing
eventResize: function(event, delta) { eventResize: function(event, delta) {
@ -1766,10 +1803,7 @@ function rcube_calendar_ui(settings)
start: date2unixtime(event.start), start: date2unixtime(event.start),
end: date2unixtime(event.end) end: date2unixtime(event.end)
}; };
if (event.recurrence) update_event_confirm('resize', event, data);
recurring_edit_confirm(data, 'resize');
else
update_event('resize', data);
}, },
viewDisplay: function(view) { viewDisplay: function(view) {
me.eventcount = []; me.eventcount = [];
@ -2046,7 +2080,6 @@ window.rcmail && rcmail.addEventListener('init', function(evt) {
rcmail.addEventListener('plugin.reload_calendar', function(p){ $('#calendar').fullCalendar('refetchEvents', cal.calendars[p.source]); }); rcmail.addEventListener('plugin.reload_calendar', function(p){ $('#calendar').fullCalendar('refetchEvents', cal.calendars[p.source]); });
rcmail.addEventListener('plugin.destroy_source', function(p){ cal.calendar_destroy_source(p.id); }); rcmail.addEventListener('plugin.destroy_source', function(p){ cal.calendar_destroy_source(p.id); });
rcmail.addEventListener('plugin.unlock_saving', function(p){ rcmail.set_busy(false, null, cal.saving_lock); }); rcmail.addEventListener('plugin.unlock_saving', function(p){ rcmail.set_busy(false, null, cal.saving_lock); });
rcmail.addEventListener('plugin.ping_url', function(p){ p.event = null; new Image().src = rcmail.url(p.action, p); });
// let's go // let's go
var cal = new rcube_calendar_ui(rcmail.env.calendar_settings); var cal = new rcube_calendar_ui(rcmail.env.calendar_settings);

View file

@ -33,6 +33,7 @@
* 'start' => <unixtime>, // Event start date/time as unix timestamp * 'start' => <unixtime>, // Event start date/time as unix timestamp
* 'end' => <unixtime>, // Event end date/time as unix timestamp * 'end' => <unixtime>, // Event end date/time as unix timestamp
* 'allday' => true|false, // Boolean flag if this is an all-day event * 'allday' => true|false, // Boolean flag if this is an all-day event
* 'changed' => <unixtime>, // Last modification date of event
* 'title' => 'Event title/summary', * 'title' => 'Event title/summary',
* 'location' => 'Location string', * 'location' => 'Location string',
* 'description' => 'Event description', * 'description' => 'Event description',

View file

@ -686,6 +686,7 @@ class database_driver extends calendar_driver
$event['start'] = strtotime($event['start']); $event['start'] = strtotime($event['start']);
$event['end'] = strtotime($event['end']); $event['end'] = strtotime($event['end']);
$event['allday'] = intval($event['all_day']); $event['allday'] = intval($event['all_day']);
$event['changed'] = strtotime($event['changed']);
$event['free_busy'] = $free_busy_map[$event['free_busy']]; $event['free_busy'] = $free_busy_map[$event['free_busy']];
$event['calendar'] = $event['calendar_id']; $event['calendar'] = $event['calendar_id'];
$event['recurrence_id'] = intval($event['recurrence_id']); $event['recurrence_id'] = intval($event['recurrence_id']);

View file

@ -561,6 +561,7 @@ class kolab_calendar
'free_busy' => $rec['show-time-as'], 'free_busy' => $rec['show-time-as'],
'priority' => isset($priority_map[$rec['priority']]) ? $priority_map[$rec['priority']] : 1, 'priority' => isset($priority_map[$rec['priority']]) ? $priority_map[$rec['priority']] : 1,
'sensitivity' => $sensitivity_map[$rec['sensitivity']], 'sensitivity' => $sensitivity_map[$rec['sensitivity']],
'changed' => $rec['last-modification-date'],
'calendar' => $this->id, 'calendar' => $this->id,
); );
} }

View file

@ -310,7 +310,7 @@ class kolab_driver extends calendar_driver
$success = $storage->insert_event($event); $success = $storage->insert_event($event);
if ($success) if ($success)
$this->rc->output->command('plugin.ping_url', array('action' => 'push-freebusy', 'source' => $storage->id)); $this->rc->output->command('plugin.ping_url', array('action' => 'calendar/push-freebusy', 'source' => $storage->id));
return $success; return $success;
} }
@ -405,7 +405,7 @@ class kolab_driver extends calendar_driver
} }
if ($success) if ($success)
$this->rc->output->command('plugin.ping_url', array('action' => 'push-freebusy', 'source' => $storage->id)); $this->rc->output->command('plugin.ping_url', array('action' => 'calendar/push-freebusy', 'source' => $storage->id));
return $success; return $success;
} }
@ -558,7 +558,7 @@ class kolab_driver extends calendar_driver
} }
if ($success) if ($success)
$this->rc->output->command('plugin.ping_url', array('action' => 'push-freebusy', 'source' => $storage->id)); $this->rc->output->command('plugin.ping_url', array('action' => 'calendar/push-freebusy', 'source' => $storage->id));
return $success; return $success;
} }

View file

@ -78,11 +78,13 @@ class calendar_ical
{ {
$event = array( $event = array(
'uid' => $ve->getAttributeDefault('UID'), 'uid' => $ve->getAttributeDefault('UID'),
'changed' => $ve->getAttributeDefault('DTSTAMP', 0),
'title' => $ve->getAttributeDefault('SUMMARY'), 'title' => $ve->getAttributeDefault('SUMMARY'),
'description' => $ve->getAttributeDefault('DESCRIPTION'),
'location' => $ve->getAttributeDefault('LOCATION'),
'start' => $ve->getAttribute('DTSTART'), 'start' => $ve->getAttribute('DTSTART'),
'end' => $ve->getAttribute('DTEND'), 'end' => $ve->getAttribute('DTEND'),
// set defaults
'free_busy' => 'busy',
'priority' => 1,
); );
// check for all-day dates // check for all-day dates
@ -94,10 +96,96 @@ class calendar_ical
$event['end'] = gmmktime(0, 0, 0, $event['end']['month'], $event['end']['mday'], $event['end']['year']) + $this->cal->gmt_offset - 60; $event['end'] = gmmktime(0, 0, 0, $event['end']['month'], $event['end']['mday'], $event['end']['year']) + $this->cal->gmt_offset - 60;
} }
// TODO: complete this // map other attributes to internal fields
$_attendees = array();
foreach ($ve->getAllAttributes() as $attr) {
switch ($attr['name']) {
case 'ORGANIZER':
$organizer = array(
'name' => $attr['params']['CN'],
'email' => preg_replace('/^mailto:/', '', $attr['value']),
'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'],
'email' => preg_replace('/^mailto:/', '', $attr['value']),
'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';
// make sure event has an UID $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')
$event['free_busy'] == 'tentative';
break;
case 'PRIORITY':
if (is_numeric($attr['value'])) {
$event['priority'] = $attr['value'] <= 4 ? 2 /* high */ :
($attr['value'] == 5 ? 1 /* normal */ : 0 /* low */);
}
break;
case 'RRULE':
// parse recurrence rule attributes
foreach (explode(';', $attr['value']) as $par) {
list($k, $v) = explode('=', $par);
$params[$k] = $v;
}
if ($params['UNTIL'])
$params['UNTIL'] = $ve->_parseDateTime($params['UNTIL']);
if (!$params['INTERVAL'])
$params['INTERVAL'] = 1;
$event['recurrence'] = $params;
break;
case 'EXDATE':
break;
case 'DESCRIPTION':
case 'LOCATION':
$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;
}
}
// 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
if (!$event['uid']) if (!$event['uid'])
$event['uid'] = $this->cal->$this->generate_uid(); $event['uid'] = $this->cal->$this->generate_uid();
@ -109,9 +197,11 @@ class calendar_ical
* Export events to iCalendar format * Export events to iCalendar format
* *
* @param array Events as array * @param array Events as array
* @param string VCalendar method to advertise
* @param boolean Directly send data to stdout instead of returning
* @return string Events in iCalendar format (http://tools.ietf.org/html/rfc5545) * @return string Events in iCalendar format (http://tools.ietf.org/html/rfc5545)
*/ */
public function export($events, $method = null) public function export($events, $method = null, $write = false)
{ {
if (!empty($this->rc->user->ID)) { if (!empty($this->rc->user->ID)) {
$ical = "BEGIN:VCALENDAR" . self::EOL; $ical = "BEGIN:VCALENDAR" . self::EOL;
@ -122,56 +212,72 @@ class calendar_ical
if ($method) if ($method)
$ical .= "METHOD:" . strtoupper($method) . self::EOL; $ical .= "METHOD:" . strtoupper($method) . self::EOL;
if ($write) {
echo $ical;
$ical = '';
}
foreach ($events as $event) { foreach ($events as $event) {
$ical .= "BEGIN:VEVENT" . self::EOL; $vevent = "BEGIN:VEVENT" . self::EOL;
$ical .= "UID:" . self::escpape($event['uid']) . self::EOL; $vevent .= "UID:" . self::escpape($event['uid']) . self::EOL;
$vevent .= "DTSTAMP:" . gmdate('Ymd\THis\Z', $event['changed'] ? $event['changed'] : time()) . self::EOL;
// correctly set all-day dates // correctly set all-day dates
if ($event['allday']) { if ($event['allday']) {
$ical .= "DTSTART;VALUE=DATE:" . gmdate('Ymd', $event['start'] + $this->cal->gmt_offset) . self::EOL; $vevent .= "DTSTART;VALUE=DATE:" . gmdate('Ymd', $event['start'] + $this->cal->gmt_offset) . self::EOL;
$ical .= "DTEND;VALUE=DATE:" . gmdate('Ymd', $event['end'] + $this->cal->gmt_offset + 60) . self::EOL; // ends the next day $vevent .= "DTEND;VALUE=DATE:" . gmdate('Ymd', $event['end'] + $this->cal->gmt_offset + 60) . self::EOL; // ends the next day
} }
else { else {
$ical .= "DTSTART:" . gmdate('Ymd\THis\Z', $event['start']) . self::EOL; $vevent .= "DTSTART:" . gmdate('Ymd\THis\Z', $event['start']) . self::EOL;
$ical .= "DTEND:" . gmdate('Ymd\THis\Z', $event['end']) . self::EOL; $vevent .= "DTEND:" . gmdate('Ymd\THis\Z', $event['end']) . self::EOL;
} }
$ical .= "SUMMARY:" . self::escpape($event['title']) . self::EOL; $vevent .= "SUMMARY:" . self::escpape($event['title']) . self::EOL;
$ical .= "DESCRIPTION:" . self::escpape($event['description']) . self::EOL; $vevent .= "DESCRIPTION:" . self::escpape($event['description']) . self::EOL;
if (!empty($event['attendees'])){ if (!empty($event['attendees'])){
$ical .= $this->_get_attendees($event['attendees']); $vevent .= $this->_get_attendees($event['attendees']);
} }
if (!empty($event['location'])) { if (!empty($event['location'])) {
$ical .= "LOCATION:" . self::escpape($event['location']) . self::EOL; $vevent .= "LOCATION:" . self::escpape($event['location']) . self::EOL;
} }
if ($event['recurrence']) { if ($event['recurrence']) {
$ical .= "RRULE:" . calendar::to_rrule($event['recurrence']) . self::EOL; $vevent .= "RRULE:" . calendar::to_rrule($event['recurrence'], self::EOL) . self::EOL;
} }
if(!empty($event['categories'])) { if(!empty($event['categories'])) {
$ical .= "CATEGORIES:" . self::escpape(strtoupper($event['categories'])) . self::EOL; $vevent .= "CATEGORIES:" . self::escpape(strtoupper($event['categories'])) . self::EOL;
} }
if ($event['sensitivity'] > 0) { if ($event['sensitivity'] > 0) {
$ical .= "X-CALENDARSERVER-ACCESS:CONFIDENTIAL"; $vevent .= "CLASS:" . ($event['sensitivity'] == 2 ? 'CONFIDENTIAL' : 'PRIVATE') . self::EOL;
} }
if ($event['alarms']) { if ($event['alarms']) {
list($trigger, $action) = explode(':', $event['alarms']); list($trigger, $action) = explode(':', $event['alarms']);
$val = calendar::parse_alaram_value($trigger); $val = calendar::parse_alaram_value($trigger);
$ical .= "BEGIN:VALARM\n"; $vevent .= "BEGIN:VALARM\n";
if ($val[1]) $ical .= "TRIGGER:" . preg_replace('/^([-+])(.+)/', '\\1PT\\2', $trigger) . self::EOL; if ($val[1]) $vevent .= "TRIGGER:" . preg_replace('/^([-+])(.+)/', '\\1PT\\2', $trigger) . self::EOL;
else $ical .= "TRIGGER;VALUE=DATE-TIME:" . gmdate('Ymd\THis\Z', $val[0]) . self::EOL; else $vevent .= "TRIGGER;VALUE=DATE-TIME:" . gmdate('Ymd\THis\Z', $val[0]) . self::EOL;
if ($action) $ical .= "ACTION:" . self::escpape(strtoupper($action)) . self::EOL; if ($action) $vevent .= "ACTION:" . self::escpape(strtoupper($action)) . self::EOL;
$ical .= "END:VALARM\n"; $vevent .= "END:VALARM\n";
} }
$ical .= "TRANSP:" . ($event['free_busy'] == 'free' ? 'TRANSPARENT' : 'OPAQUE') . self::EOL; $vevent .= "TRANSP:" . ($event['free_busy'] == 'free' ? 'TRANSPARENT' : 'OPAQUE') . self::EOL;
// TODO: export attachments // TODO: export attachments
$ical .= "END:VEVENT" . self::EOL; $vevent .= "END:VEVENT" . self::EOL;
if ($write)
echo rcube_vcard::rfc2425_fold($vevent);
else
$ical .= $vevent;
} }
$ical .= "END:VCALENDAR" . self::EOL; $ical .= "END:VCALENDAR" . self::EOL;
if ($write) {
echo $ical;
return true;
}
// fold lines to 75 chars // fold lines to 75 chars
return rcube_vcard::rfc2425_fold($ical); return rcube_vcard::rfc2425_fold($ical);
} }

View file

@ -60,6 +60,7 @@ $labels['generated'] = 'generated at';
$labels['selectdate'] = 'Select date'; $labels['selectdate'] = 'Select date';
$labels['printdescriptions'] = 'Print descriptions'; $labels['printdescriptions'] = 'Print descriptions';
$labels['parentcalendar'] = 'Superior calendar'; $labels['parentcalendar'] = 'Superior calendar';
$labels['importtocalendar'] = 'Save to my calendar';
// alarm/reminder settings // alarm/reminder settings
$labels['alarmemail'] = 'Send Email'; $labels['alarmemail'] = 'Send Email';
@ -104,7 +105,7 @@ $labels['availtentative'] = 'Tentative';
$labels['availoutofoffice'] = 'Out of Office'; $labels['availoutofoffice'] = 'Out of Office';
$labels['scheduletime'] = 'Find availability'; $labels['scheduletime'] = 'Find availability';
$labels['sendinvitations'] = 'Send invitations'; $labels['sendinvitations'] = 'Send invitations';
$labels['sendnotifications'] = 'Notify attendees about modifications'; $labels['sendnotifications'] = 'Notify participants about modifications';
$labels['onlyworkinghours'] = 'Find availability within my working hours'; $labels['onlyworkinghours'] = 'Find availability within my working hours';
$labels['reqallattendees'] = 'Required/all participants'; $labels['reqallattendees'] = 'Required/all participants';
$labels['prevslot'] = 'Previous Slot'; $labels['prevslot'] = 'Previous Slot';
@ -133,6 +134,10 @@ $labels['searchnoresults'] = 'No events found in the selected calendars.';
$labels['successremoval'] = 'The event has been deleted successfully.'; $labels['successremoval'] = 'The event has been deleted successfully.';
$labels['successrestore'] = 'The event has been restored successfully.'; $labels['successrestore'] = 'The event has been restored successfully.';
$labels['errornotifying'] = 'Failed to send notifications to event participants'; $labels['errornotifying'] = 'Failed to send notifications to event participants';
$labels['errorimportingevent'] = 'Failed to import the event';
$labels['newerversionexists'] = 'A newer version of this event already exists! Aborted.';
$labels['nowritecalendarfound'] = 'No calendar found to save the event';
$labels['importedsuccessfully'] = 'The event was successfully added to \'$calendar\'';
// recurrence form // recurrence form
$labels['repeat'] = 'Repeat'; $labels['repeat'] = 'Repeat';
@ -162,8 +167,8 @@ $labels['fourth'] = 'fourth';
$labels['last'] = 'last'; $labels['last'] = 'last';
$labels['dayofmonth'] = 'Day of month'; $labels['dayofmonth'] = 'Day of month';
$labels['changerecurringevent'] = 'Change recurring event'; $labels['changeeventconfirm'] = 'Change event';
$labels['removerecurringevent'] = 'Remove recurring event'; $labels['removeeventconfirm'] = 'Remove event';
$labels['changerecurringeventwarning'] = 'This is a recurring event. Would you like to edit the current event only, this and all future occurences, all occurences or save it as a new event?'; $labels['changerecurringeventwarning'] = 'This is a recurring event. Would you like to edit the current event only, this and all future occurences, all occurences or save it as a new event?';
$labels['removerecurringeventwarning'] = 'This is a recurring event. Would you like to remove the current event only, this and all future occurences or all occurences of this event?'; $labels['removerecurringeventwarning'] = 'This is a recurring event. Would you like to remove the current event only, this and all future occurences or all occurences of this event?';
$labels['currentevent'] = 'Current'; $labels['currentevent'] = 'Current';

View file

@ -472,14 +472,20 @@ td.topalign {
padding: 0.2em 0; padding: 0.2em 0;
} }
.edit-recurring-warning { .ui-dialog .event-update-confirm {
padding: 0 0.5em 0.5em 0.5em;
}
.edit-recurring-warning,
.event-update-confirm .message {
margin-top: 0.5em; margin-top: 0.5em;
padding: 0.8em; padding: 0.8em;
background-color: #F7FDCB; background-color: #F7FDCB;
border: 1px solid #C2D071; border: 1px solid #C2D071;
} }
.edit-recurring-warning .message { .edit-recurring-warning .message,
.event-update-confirm .message {
margin-bottom: 0.5em; margin-bottom: 0.5em;
} }
@ -487,17 +493,23 @@ td.topalign {
padding-left: 20px; padding-left: 20px;
} }
.edit-recurring-warning span.ui-icon { .event-update-confirm .savemode {
padding-left: 30px;
}
.edit-recurring-warning span.ui-icon,
.event-update-confirm span.ui-icon {
float: left; float: left;
margin: 0 7px 20px 0; margin: 0 7px 20px 0;
} }
.edit-recurring-warning label { .edit-recurring-warning label,
.event-update-confirm label {
min-width: 3em; min-width: 3em;
padding-right: 1em; padding-right: 1em;
} }
.edit-recurring-warning a.button { .event-update-confirm a.button {
margin: 0 0.5em 0 0.2em; margin: 0 0.5em 0 0.2em;
min-width: 5em; min-width: 5em;
} }