diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php
index ae7b2048..775fb56b 100644
--- a/plugins/calendar/calendar.php
+++ b/plugins/calendar/calendar.php
@@ -120,6 +120,7 @@ class calendar extends rcube_plugin
$this->register_action('freebusy-times', array($this, 'freebusy_times'));
$this->register_action('randomdata', array($this, 'generate_randomdata'));
$this->register_action('print', array($this,'print_view'));
+ $this->register_action('mailimportevent', array($this, 'mail_import_event'));
// remove undo information...
if ($undo = $_SESSION['calendar_event_undo']) {
@@ -137,11 +138,14 @@ class calendar extends rcube_plugin
$this->add_hook('preferences_list', array($this, 'preferences_list'));
$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('template_object_messagebody', array($this, 'mail_messagebody_html'));
+ }
}
-
+
// add hook to display alarms
$this->add_hook('keep_alive', array($this, 'keep_alive'));
}
@@ -583,7 +587,7 @@ class calendar extends rcube_plugin
}
// send out notifications
- if ($success && $event['_notify'] && $event['attendees']) {
+ if ($success && $event['_notify'] && ($event['attendees'] || $old['attendees'])) {
// make sure we have the complete record
$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);
header("Content-Type: text/calendar");
- header("Content-Disposition: inline; filename=".$calendar_name);
+ header("Content-Disposition: inline; filename=".$calendar_name.'.ics');
$this->load_ical();
- echo $this->ical->export($events);
+ $this->ical->export($events, '', true);
exit;
}
@@ -841,7 +845,7 @@ class calendar extends rcube_plugin
break;
}
- if ($rrule['INTERVAL'] == 1)
+ if ($rrule['INTERVAL'] <= 1)
$freq = $this->gettext(strtolower($rrule['FREQ']));
if ($rrule['COUNT'])
@@ -1328,7 +1332,7 @@ class calendar extends rcube_plugin
$message->headers($headers);
$message->setTXTBody(rcube_message::format_flowed($body, 79));
-
+
// finally send the message
if (rcmail_deliver_message($message, $from, $mailto, $smtp_error))
$sent++;
@@ -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)
{
@@ -1545,6 +1549,7 @@ class calendar extends rcube_plugin
if (empty($events))
continue;
+ // TODO: show more iTip options like (accept, deny, etc.)
foreach ($events as $idx => $event) {
// add box below messsage body
$html .= html::p('calendar-invitebox',
@@ -1553,7 +1558,7 @@ class calendar extends rcube_plugin
html::tag('input', array(
'type' => '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'),
))
);
@@ -1568,6 +1573,75 @@ class calendar extends rcube_plugin
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
diff --git a/plugins/calendar/calendar_base.js b/plugins/calendar/calendar_base.js
index 638e8604..aa482b7c 100644
--- a/plugins/calendar/calendar_base.js
+++ b/plugins/calendar/calendar_base.js
@@ -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
(function($){
$.fn.serializeJSON = function(){
@@ -183,5 +192,10 @@ window.rcmail && rcmail.addEventListener('init', function(evt) {
var cal = new rcube_calendar(rcmail.env.calendar_settings);
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);
+ });
});
diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js
index 2baeb49d..bc1ccd63 100644
--- a/plugins/calendar/calendar_ui.js
+++ b/plugins/calendar/calendar_ui.js
@@ -139,6 +139,12 @@ function rcube_calendar_ui(settings)
else
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
var event_date_text = function(event)
@@ -465,7 +471,7 @@ function rcube_calendar_ui(settings)
for (var j=0; j < event.attendees.length; j++)
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
}
}
@@ -1292,43 +1298,86 @@ function rcube_calendar_ui(settings)
return true;
};
- // display confirm dialog when modifying/deleting a recurring event where the user needs to select the savemode
- var recurring_edit_confirm = function(event, action) {
- var $dialog = $('
').addClass('edit-recurring-warning');
- $dialog.html('
' +
- rcmail.gettext((action == 'remove' ? 'removerecurringeventwarning' : 'changerecurringeventwarning'), 'calendar') + '
' +
- '
');
+ // display confirm dialog when modifying/deleting an event
+ var update_event_confirm = function(action, event, data)
+ {
+ if (!data) data = event;
+ var html = '';
- $dialog.find('a.button').button().click(function(e){
- event.savemode = String(this.href).replace(/.+#/, '');
- update_event(action, event);
- $dialog.dialog("destroy").hide();
- return false;
- });
+ // event has attendees, ask whether to notify them
+ if (has_attendees(event)) {
+ html += '
' +
+ '
';
+ }
- $dialog.dialog({
- modal: true,
- width: 420,
- dialogClass: 'warning',
- title: rcmail.gettext((action == 'remove' ? 'removerecurringevent' : 'changerecurringevent'), 'calendar'),
- buttons: [
- {
- text: rcmail.gettext('cancel', 'calendar'),
+ // recurring event: user needs to select the savemode
+ if (event.recurrence) {
+ html += '
' +
+ rcmail.gettext((action == 'remove' ? 'removerecurringeventwarning' : 'changerecurringeventwarning'), 'calendar') + '
' +
+ '
';
+ }
+
+ // show dialog
+ if (html) {
+ var $dialog = $('
').html(html);
+
+ $dialog.find('a.button').button().click(function(e){
+ data.savemode = String(this.href).replace(/.+#/, '');
+ 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();
+ 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");
}
- }
- ],
- close: function(){
- $dialog.dialog("destroy").hide();
- fc.fullCalendar('refetchEvents');
+ });
}
- }).show();
+
+ $dialog.dialog({
+ modal: true,
+ width: 420,
+ dialogClass: 'warning',
+ title: rcmail.gettext((action == 'remove' ? 'removeeventconfirm' : 'changeeventconfirm'), 'calendar'),
+ buttons: buttons,
+ close: function(){
+ $dialog.dialog("destroy").hide();
+ if (!rcmail.busy)
+ fc.fullCalendar('refetchEvents');
+ }
+ }).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;
};
@@ -1361,17 +1410,8 @@ function rcube_calendar_ui(settings)
// delete the given event after showing a confirmation dialog
this.delete_event = function(event) {
- // show extended confirm dialog for recurring events, use jquery UI dialog
- if (event.recurrence)
- 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;
+ // show confirm dialog for recurring events, use jquery UI dialog
+ return update_event_confirm('remove', event, { id:event.id, calendar:event.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),
allday: allDay?1:0
};
- if (event.recurrence)
- recurring_edit_confirm(data, 'move');
- else
- update_event('move', data);
+ update_event_confirm('move', event, data);
},
// callback for event resizing
eventResize: function(event, delta) {
@@ -1766,10 +1803,7 @@ function rcube_calendar_ui(settings)
start: date2unixtime(event.start),
end: date2unixtime(event.end)
};
- if (event.recurrence)
- recurring_edit_confirm(data, 'resize');
- else
- update_event('resize', data);
+ update_event_confirm('resize', event, data);
},
viewDisplay: function(view) {
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.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.ping_url', function(p){ p.event = null; new Image().src = rcmail.url(p.action, p); });
// let's go
var cal = new rcube_calendar_ui(rcmail.env.calendar_settings);
diff --git a/plugins/calendar/drivers/calendar_driver.php b/plugins/calendar/drivers/calendar_driver.php
index 0940e861..67deae92 100644
--- a/plugins/calendar/drivers/calendar_driver.php
+++ b/plugins/calendar/drivers/calendar_driver.php
@@ -33,6 +33,7 @@
* 'start' => , // Event start date/time as unix timestamp
* 'end' => , // Event end date/time as unix timestamp
* 'allday' => true|false, // Boolean flag if this is an all-day event
+ * 'changed' => , // Last modification date of event
* 'title' => 'Event title/summary',
* 'location' => 'Location string',
* 'description' => 'Event description',
diff --git a/plugins/calendar/drivers/database/database_driver.php b/plugins/calendar/drivers/database/database_driver.php
index 69898a8a..53dcb8c4 100644
--- a/plugins/calendar/drivers/database/database_driver.php
+++ b/plugins/calendar/drivers/database/database_driver.php
@@ -686,6 +686,7 @@ class database_driver extends calendar_driver
$event['start'] = strtotime($event['start']);
$event['end'] = strtotime($event['end']);
$event['allday'] = intval($event['all_day']);
+ $event['changed'] = strtotime($event['changed']);
$event['free_busy'] = $free_busy_map[$event['free_busy']];
$event['calendar'] = $event['calendar_id'];
$event['recurrence_id'] = intval($event['recurrence_id']);
diff --git a/plugins/calendar/drivers/kolab/kolab_calendar.php b/plugins/calendar/drivers/kolab/kolab_calendar.php
index 59ebb609..ce05a50e 100644
--- a/plugins/calendar/drivers/kolab/kolab_calendar.php
+++ b/plugins/calendar/drivers/kolab/kolab_calendar.php
@@ -561,6 +561,7 @@ class kolab_calendar
'free_busy' => $rec['show-time-as'],
'priority' => isset($priority_map[$rec['priority']]) ? $priority_map[$rec['priority']] : 1,
'sensitivity' => $sensitivity_map[$rec['sensitivity']],
+ 'changed' => $rec['last-modification-date'],
'calendar' => $this->id,
);
}
diff --git a/plugins/calendar/drivers/kolab/kolab_driver.php b/plugins/calendar/drivers/kolab/kolab_driver.php
index a5002bab..872fecab 100644
--- a/plugins/calendar/drivers/kolab/kolab_driver.php
+++ b/plugins/calendar/drivers/kolab/kolab_driver.php
@@ -310,7 +310,7 @@ class kolab_driver extends calendar_driver
$success = $storage->insert_event($event);
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;
}
@@ -405,7 +405,7 @@ class kolab_driver extends calendar_driver
}
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;
}
@@ -558,7 +558,7 @@ class kolab_driver extends calendar_driver
}
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;
}
diff --git a/plugins/calendar/lib/calendar_ical.php b/plugins/calendar/lib/calendar_ical.php
index b5d6cb0e..f0410505 100644
--- a/plugins/calendar/lib/calendar_ical.php
+++ b/plugins/calendar/lib/calendar_ical.php
@@ -78,13 +78,15 @@ class calendar_ical
{
$event = array(
'uid' => $ve->getAttributeDefault('UID'),
+ 'changed' => $ve->getAttributeDefault('DTSTAMP', 0),
'title' => $ve->getAttributeDefault('SUMMARY'),
- 'description' => $ve->getAttributeDefault('DESCRIPTION'),
- 'location' => $ve->getAttributeDefault('LOCATION'),
'start' => $ve->getAttribute('DTSTART'),
'end' => $ve->getAttribute('DTEND'),
+ // set defaults
+ 'free_busy' => 'busy',
+ 'priority' => 1,
);
-
+
// check for all-day dates
if (is_array($event['start'])) {
$event['start'] = gmmktime(0, 0, 0, $event['start']['month'], $event['start']['mday'], $event['start']['year']) + $this->cal->gmt_offset;
@@ -93,11 +95,97 @@ class calendar_ical
if (is_array($event['end'])) {
$event['end'] = gmmktime(0, 0, 0, $event['end']['month'], $event['end']['mday'], $event['end']['year']) + $this->cal->gmt_offset - 60;
}
+
+ // 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';
+
+ $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;
- // TODO: complete this
+ 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;
+ }
+ }
-
- // make sure event has an UID
+ // 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'])
$event['uid'] = $this->cal->$this->generate_uid();
@@ -108,10 +196,12 @@ class calendar_ical
/**
* 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)
*/
- public function export($events, $method = null)
+ public function export($events, $method = null, $write = false)
{
if (!empty($this->rc->user->ID)) {
$ical = "BEGIN:VCALENDAR" . self::EOL;
@@ -121,56 +211,72 @@ class calendar_ical
if ($method)
$ical .= "METHOD:" . strtoupper($method) . self::EOL;
+
+ if ($write) {
+ echo $ical;
+ $ical = '';
+ }
foreach ($events as $event) {
- $ical .= "BEGIN:VEVENT" . self::EOL;
- $ical .= "UID:" . self::escpape($event['uid']) . self::EOL;
+ $vevent = "BEGIN:VEVENT" . 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
if ($event['allday']) {
- $ical .= "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 .= "DTSTART;VALUE=DATE:" . gmdate('Ymd', $event['start'] + $this->cal->gmt_offset) . self::EOL;
+ $vevent .= "DTEND;VALUE=DATE:" . gmdate('Ymd', $event['end'] + $this->cal->gmt_offset + 60) . self::EOL; // ends the next day
}
else {
- $ical .= "DTSTART:" . gmdate('Ymd\THis\Z', $event['start']) . self::EOL;
- $ical .= "DTEND:" . gmdate('Ymd\THis\Z', $event['end']) . self::EOL;
+ $vevent .= "DTSTART:" . gmdate('Ymd\THis\Z', $event['start']) . self::EOL;
+ $vevent .= "DTEND:" . gmdate('Ymd\THis\Z', $event['end']) . self::EOL;
}
- $ical .= "SUMMARY:" . self::escpape($event['title']) . self::EOL;
- $ical .= "DESCRIPTION:" . self::escpape($event['description']) . self::EOL;
+ $vevent .= "SUMMARY:" . self::escpape($event['title']) . self::EOL;
+ $vevent .= "DESCRIPTION:" . self::escpape($event['description']) . self::EOL;
if (!empty($event['attendees'])){
- $ical .= $this->_get_attendees($event['attendees']);
+ $vevent .= $this->_get_attendees($event['attendees']);
}
if (!empty($event['location'])) {
- $ical .= "LOCATION:" . self::escpape($event['location']) . self::EOL;
+ $vevent .= "LOCATION:" . self::escpape($event['location']) . self::EOL;
}
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'])) {
- $ical .= "CATEGORIES:" . self::escpape(strtoupper($event['categories'])) . self::EOL;
+ $vevent .= "CATEGORIES:" . self::escpape(strtoupper($event['categories'])) . self::EOL;
}
if ($event['sensitivity'] > 0) {
- $ical .= "X-CALENDARSERVER-ACCESS:CONFIDENTIAL";
+ $vevent .= "CLASS:" . ($event['sensitivity'] == 2 ? 'CONFIDENTIAL' : 'PRIVATE') . self::EOL;
}
if ($event['alarms']) {
list($trigger, $action) = explode(':', $event['alarms']);
$val = calendar::parse_alaram_value($trigger);
- $ical .= "BEGIN:VALARM\n";
- if ($val[1]) $ical .= "TRIGGER:" . preg_replace('/^([-+])(.+)/', '\\1PT\\2', $trigger) . self::EOL;
- else $ical .= "TRIGGER;VALUE=DATE-TIME:" . gmdate('Ymd\THis\Z', $val[0]) . self::EOL;
- if ($action) $ical .= "ACTION:" . self::escpape(strtoupper($action)) . self::EOL;
- $ical .= "END:VALARM\n";
+ $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;
+ if ($action) $vevent .= "ACTION:" . self::escpape(strtoupper($action)) . self::EOL;
+ $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
- $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;
+
+ if ($write) {
+ echo $ical;
+ return true;
+ }
// fold lines to 75 chars
return rcube_vcard::rfc2425_fold($ical);
diff --git a/plugins/calendar/localization/en_US.inc b/plugins/calendar/localization/en_US.inc
index d24252a7..e3dd99b1 100644
--- a/plugins/calendar/localization/en_US.inc
+++ b/plugins/calendar/localization/en_US.inc
@@ -60,6 +60,7 @@ $labels['generated'] = 'generated at';
$labels['selectdate'] = 'Select date';
$labels['printdescriptions'] = 'Print descriptions';
$labels['parentcalendar'] = 'Superior calendar';
+$labels['importtocalendar'] = 'Save to my calendar';
// alarm/reminder settings
$labels['alarmemail'] = 'Send Email';
@@ -104,7 +105,7 @@ $labels['availtentative'] = 'Tentative';
$labels['availoutofoffice'] = 'Out of Office';
$labels['scheduletime'] = 'Find availability';
$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['reqallattendees'] = 'Required/all participants';
$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['successrestore'] = 'The event has been restored successfully.';
$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
$labels['repeat'] = 'Repeat';
@@ -162,8 +167,8 @@ $labels['fourth'] = 'fourth';
$labels['last'] = 'last';
$labels['dayofmonth'] = 'Day of month';
-$labels['changerecurringevent'] = 'Change recurring event';
-$labels['removerecurringevent'] = 'Remove recurring event';
+$labels['changeeventconfirm'] = 'Change 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['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';
diff --git a/plugins/calendar/skins/default/calendar.css b/plugins/calendar/skins/default/calendar.css
index 95a8f322..3354810c 100644
--- a/plugins/calendar/skins/default/calendar.css
+++ b/plugins/calendar/skins/default/calendar.css
@@ -472,14 +472,20 @@ td.topalign {
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;
padding: 0.8em;
background-color: #F7FDCB;
border: 1px solid #C2D071;
}
-.edit-recurring-warning .message {
+.edit-recurring-warning .message,
+.event-update-confirm .message {
margin-bottom: 0.5em;
}
@@ -487,17 +493,23 @@ td.topalign {
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;
margin: 0 7px 20px 0;
}
-.edit-recurring-warning label {
+.edit-recurring-warning label,
+.event-update-confirm label {
min-width: 3em;
padding-right: 1em;
}
-.edit-recurring-warning a.button {
+.event-update-confirm a.button {
margin: 0 0.5em 0 0.2em;
min-width: 5em;
}