Ask about notifying attendess when moving event; finish import from ical mail attachments
This commit is contained in:
parent
0f266481dd
commit
593a3d3faa
10 changed files with 347 additions and 100 deletions
|
@ -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,11 +138,14 @@ 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'])
|
||||||
|
@ -1328,7 +1332,7 @@ class calendar extends rcube_plugin
|
||||||
|
|
||||||
$message->headers($headers);
|
$message->headers($headers);
|
||||||
$message->setTXTBody(rcube_message::format_flowed($body, 79));
|
$message->setTXTBody(rcube_message::format_flowed($body, 79));
|
||||||
|
|
||||||
// finally send the message
|
// finally send the message
|
||||||
if (rcmail_deliver_message($message, $from, $mailto, $smtp_error))
|
if (rcmail_deliver_message($message, $from, $mailto, $smtp_error))
|
||||||
$sent++;
|
$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)
|
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'),
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
|
@ -1568,6 +1573,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
|
||||||
|
|
|
@ -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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -139,6 +139,12 @@ function rcube_calendar_ui(settings)
|
||||||
else
|
else
|
||||||
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;
|
||||||
rcmail.gettext((action == 'remove' ? 'removerecurringeventwarning' : 'changerecurringeventwarning'), 'calendar') + '</div>' +
|
var html = '';
|
||||||
'<div class="savemode">' +
|
|
||||||
'<a href="#current" class="button">' + rcmail.gettext('currentevent', 'calendar') + '</a>' +
|
|
||||||
'<a href="#future" class="button">' + rcmail.gettext('futurevents', 'calendar') + '</a>' +
|
|
||||||
'<a href="#all" class="button">' + rcmail.gettext('allevents', 'calendar') + '</a>' +
|
|
||||||
(action != 'remove' ? '<a href="#new" class="button">' + rcmail.gettext('saveasnew', 'calendar') + '</a>' : '') +
|
|
||||||
'</div>');
|
|
||||||
|
|
||||||
$dialog.find('a.button').button().click(function(e){
|
// event has attendees, ask whether to notify them
|
||||||
event.savemode = String(this.href).replace(/.+#/, '');
|
if (has_attendees(event)) {
|
||||||
update_event(action, event);
|
html += '<div class="message">' +
|
||||||
$dialog.dialog("destroy").hide();
|
'<label><input class="confirm-attendees-donotify" type="checkbox" checked="checked" value="1" name="notify" /> ' +
|
||||||
return false;
|
rcmail.gettext('sendnotifications', 'calendar') +
|
||||||
});
|
'</label></div>';
|
||||||
|
}
|
||||||
|
|
||||||
$dialog.dialog({
|
// recurring event: user needs to select the savemode
|
||||||
modal: true,
|
if (event.recurrence) {
|
||||||
width: 420,
|
html += '<div class="message"><span class="ui-icon ui-icon-alert"></span>' +
|
||||||
dialogClass: 'warning',
|
rcmail.gettext((action == 'remove' ? 'removerecurringeventwarning' : 'changerecurringeventwarning'), 'calendar') + '</div>' +
|
||||||
title: rcmail.gettext((action == 'remove' ? 'removerecurringevent' : 'changerecurringevent'), 'calendar'),
|
'<div class="savemode">' +
|
||||||
buttons: [
|
'<a href="#current" class="button">' + rcmail.gettext('currentevent', 'calendar') + '</a>' +
|
||||||
{
|
'<a href="#future" class="button">' + rcmail.gettext('futurevents', 'calendar') + '</a>' +
|
||||||
text: rcmail.gettext('cancel', 'calendar'),
|
'<a href="#all" class="button">' + rcmail.gettext('allevents', 'calendar') + '</a>' +
|
||||||
|
(action != 'remove' ? '<a href="#new" class="button">' + rcmail.gettext('saveasnew', 'calendar') + '</a>' : '') +
|
||||||
|
'</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// show dialog
|
||||||
|
if (html) {
|
||||||
|
var $dialog = $('<div>').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() {
|
click: function() {
|
||||||
|
data._notify = $dialog.find('input.confirm-attendees-donotify').get(0).checked ? 1 : 0;
|
||||||
|
update_event(action, data);
|
||||||
$(this).dialog("close");
|
$(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;
|
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);
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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']);
|
||||||
|
|
|
@ -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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,13 +78,15 @@ 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
|
||||||
if (is_array($event['start'])) {
|
if (is_array($event['start'])) {
|
||||||
$event['start'] = gmmktime(0, 0, 0, $event['start']['month'], $event['start']['mday'], $event['start']['year']) + $this->cal->gmt_offset;
|
$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'])) {
|
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;
|
$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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add organizer to attendees list if not already present
|
||||||
// make sure event has an UID
|
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();
|
||||||
|
|
||||||
|
@ -108,10 +196,12 @@ 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;
|
||||||
|
@ -121,56 +211,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);
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue