Updated calendar to version 3.1.13 from git.kolab.org
This commit is contained in:
parent
4636da34f9
commit
2eef94389e
26 changed files with 953 additions and 199 deletions
258
calendar.php
258
calendar.php
|
@ -88,15 +88,6 @@ class calendar extends rcube_plugin
|
|||
require($this->home . '/lib/calendar_ui.php');
|
||||
$this->ui = new calendar_ui($this);
|
||||
|
||||
// load Calendar user interface which includes jquery-ui
|
||||
if (!$this->rc->output->ajax_call && !$this->rc->output->env['framed']) {
|
||||
$this->ui->init();
|
||||
|
||||
// settings are required in (almost) every GUI step
|
||||
if ($this->rc->action != 'attend')
|
||||
$this->rc->output->set_env('calendar_settings', $this->load_settings());
|
||||
}
|
||||
|
||||
// catch iTIP confirmation requests that don're require a valid session
|
||||
if ($this->rc->action == 'attend' && !empty($_REQUEST['_t'])) {
|
||||
$this->add_hook('startup', array($this, 'itip_attend_response'));
|
||||
|
@ -104,8 +95,32 @@ class calendar extends rcube_plugin
|
|||
else if ($this->rc->action == 'feed' && !empty($_REQUEST['_cal'])) {
|
||||
$this->add_hook('startup', array($this, 'ical_feed_export'));
|
||||
}
|
||||
else if ($this->rc->task == 'calendar' && $this->rc->action != 'save-pref') {
|
||||
if ($this->rc->action != 'upload') {
|
||||
else {
|
||||
// default startup routine
|
||||
$this->add_hook('startup', array($this, 'startup'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Startup hook
|
||||
*/
|
||||
public function startup($args)
|
||||
{
|
||||
// the calendar module can be enabled/disabled by the kolab_auth plugin
|
||||
if ($this->rc->config->get('calendar_disabled', false) || !$this->rc->config->get('calendar_enabled', true))
|
||||
return;
|
||||
|
||||
// load Calendar user interface
|
||||
if (!$this->rc->output->ajax_call && !$this->rc->output->env['framed']) {
|
||||
$this->ui->init();
|
||||
|
||||
// settings are required in (almost) every GUI step
|
||||
if ($args['action'] != 'attend')
|
||||
$this->rc->output->set_env('calendar_settings', $this->load_settings());
|
||||
}
|
||||
|
||||
if ($args['task'] == 'calendar' && $args['action'] != 'save-pref') {
|
||||
if ($args['action'] != 'upload') {
|
||||
$this->load_driver();
|
||||
}
|
||||
|
||||
|
@ -126,6 +141,7 @@ class calendar extends rcube_plugin
|
|||
$this->register_action('mailtoevent', array($this, 'mail_message2event'));
|
||||
$this->register_action('inlineui', array($this, 'get_inline_ui'));
|
||||
$this->register_action('check-recent', array($this, 'check_recent'));
|
||||
$this->add_hook('refresh', array($this, 'refresh'));
|
||||
|
||||
// remove undo information...
|
||||
if ($undo = $_SESSION['calendar_event_undo']) {
|
||||
|
@ -137,19 +153,19 @@ class calendar extends rcube_plugin
|
|||
}
|
||||
}
|
||||
}
|
||||
else if ($this->rc->task == 'settings') {
|
||||
else if ($args['task'] == 'settings') {
|
||||
// add hooks for Calendar settings
|
||||
$this->add_hook('preferences_sections_list', array($this, 'preferences_sections_list'));
|
||||
$this->add_hook('preferences_list', array($this, 'preferences_list'));
|
||||
$this->add_hook('preferences_save', array($this, 'preferences_save'));
|
||||
}
|
||||
else if ($this->rc->task == 'mail') {
|
||||
else if ($args['task'] == 'mail') {
|
||||
// hooks to catch event invitations on incoming mails
|
||||
if ($this->rc->action == 'show' || $this->rc->action == 'preview') {
|
||||
if ($args['action'] == 'show' || $args['action'] == 'preview') {
|
||||
$this->add_hook('message_load', array($this, 'mail_message_load'));
|
||||
$this->add_hook('template_object_messagebody', array($this, 'mail_messagebody_html'));
|
||||
}
|
||||
|
||||
|
||||
// add 'Create event' item to message menu
|
||||
if ($this->api->output->type == 'html') {
|
||||
$this->api->add_content(html::tag('li', null,
|
||||
|
@ -162,9 +178,11 @@ class calendar extends rcube_plugin
|
|||
'innerclass' => 'icon calendar',
|
||||
))),
|
||||
'messagemenu');
|
||||
|
||||
$this->api->output->add_label('calendar.createfrommail');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// add hooks to display alarms
|
||||
$this->add_hook('pending_alarms', array($this, 'pending_alarms'));
|
||||
$this->add_hook('dismiss_alarms', array($this, 'dismiss_alarms'));
|
||||
|
@ -918,6 +936,35 @@ class calendar extends rcube_plugin
|
|||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for keep-alive requests
|
||||
* This will check for updated data in active calendars and sync them to the client
|
||||
*/
|
||||
public function refresh($attr)
|
||||
{
|
||||
// refresh the entire calendar every 10th time to also sync deleted events
|
||||
if (rand(0,10) == 10) {
|
||||
$this->rc->output->command('plugin.refresh_calendar', array('refetch' => true));
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->driver->list_calendars(true) as $cal) {
|
||||
$events = $this->driver->load_events(
|
||||
get_input_value('start', RCUBE_INPUT_GPC),
|
||||
get_input_value('end', RCUBE_INPUT_GPC),
|
||||
get_input_value('q', RCUBE_INPUT_GPC),
|
||||
$cal['id'],
|
||||
1,
|
||||
$attr['last']
|
||||
);
|
||||
|
||||
foreach ($events as $event) {
|
||||
$this->rc->output->command('plugin.refresh_calendar',
|
||||
array('source' => $cal['id'], 'update' => $this->_client_event($event)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for pending_alarms plugin hook triggered by the calendar module on keep-alive requests.
|
||||
* This will check for pending notifications and pass them to the client
|
||||
|
@ -968,22 +1015,31 @@ class calendar extends rcube_plugin
|
|||
rcube_upload_progress();
|
||||
}
|
||||
|
||||
$calendar = get_input_value('calendar', RCUBE_INPUT_GPC);
|
||||
@set_time_limit(0);
|
||||
|
||||
// process uploaded file if there is no error
|
||||
$err = $_FILES['_data']['error'];
|
||||
|
||||
if (!$err && $_FILES['_data']['tmp_name']) {
|
||||
$calendar = get_input_value('calendar', RCUBE_INPUT_GPC);
|
||||
$events = $this->get_ical()->import_from_file($_FILES['_data']['tmp_name']);
|
||||
|
||||
$count = $errors = 0;
|
||||
$rangestart = $_REQUEST['_range'] ? date_create("now -" . intval($_REQUEST['_range']) . " months") : 0;
|
||||
foreach ($events as $event) {
|
||||
$user_email = $this->rc->user->get_username();
|
||||
|
||||
$ical = $this->get_ical();
|
||||
$errors = !$ical->fopen($_FILES['_data']['tmp_name']);
|
||||
$count = $i = 0;
|
||||
foreach ($ical as $event) {
|
||||
// keep the browser connection alive on long import jobs
|
||||
if (++$i > 100 && $i % 100 == 0) {
|
||||
echo "<!-- -->";
|
||||
ob_flush();
|
||||
}
|
||||
|
||||
// TODO: correctly handle recurring events which start before $rangestart
|
||||
if ($event['end'] < $rangestart && (!$event['recurrence'] || ($event['recurrence']['until'] && $event['recurrence']['until'] < $rangestart)))
|
||||
continue;
|
||||
|
||||
$event['_owner'] = $user_email;
|
||||
$event['calendar'] = $calendar;
|
||||
if ($this->driver->new_event($event)) {
|
||||
$count++;
|
||||
|
@ -1000,8 +1056,9 @@ class calendar extends rcube_plugin
|
|||
$this->rc->output->command('display_message', $this->gettext('importnone'), 'notice');
|
||||
$this->rc->output->command('plugin.import_success', array('source' => $calendar));
|
||||
}
|
||||
else
|
||||
$this->rc->output->command('display_message', $this->gettext('importerror'), 'error');
|
||||
else {
|
||||
$this->rc->output->command('plugin.import_error', array('message' => $this->gettext('importerror') . ($msg ? ': ' . $msg : '')));
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ($err == UPLOAD_ERR_INI_SIZE || $err == UPLOAD_ERR_FORM_SIZE) {
|
||||
|
@ -1012,7 +1069,7 @@ class calendar extends rcube_plugin
|
|||
$msg = rcube_label('fileuploaderror');
|
||||
}
|
||||
|
||||
$this->rc->output->command('display_message', $msg, 'error');
|
||||
$this->rc->output->command('plugin.import_error', array('message' => $msg));
|
||||
$this->rc->output->command('plugin.unlock_saving', false);
|
||||
}
|
||||
|
||||
|
@ -1026,11 +1083,20 @@ class calendar extends rcube_plugin
|
|||
{
|
||||
$start = get_input_value('start', RCUBE_INPUT_GET);
|
||||
$end = get_input_value('end', RCUBE_INPUT_GET);
|
||||
if (!$start) $start = mktime(0, 0, 0, 1, date('n'), date('Y')-1);
|
||||
if (!$end) $end = mktime(0, 0, 0, 31, 12, date('Y')+10);
|
||||
if (!isset($start))
|
||||
$start = 'today -1 year';
|
||||
if (!is_numeric($start))
|
||||
$start = strtotime($start . ' 00:00:00');
|
||||
if (!$end)
|
||||
$end = 'today +10 years';
|
||||
if (!is_numeric($end))
|
||||
$end = strtotime($end . ' 23:59:59');
|
||||
|
||||
$attachments = get_input_value('attachments', RCUBE_INPUT_GET);
|
||||
$calid = $calname = get_input_value('source', RCUBE_INPUT_GET);
|
||||
$calendars = $this->driver->list_calendars(true);
|
||||
|
||||
|
||||
$calendars = $this->driver->list_calendars();
|
||||
|
||||
if ($calendars[$calid]) {
|
||||
$calname = $calendars[$calid]['name'] ? $calendars[$calid]['name'] : $calid;
|
||||
$calname = preg_replace('/[^a-z0-9_.-]/i', '', html_entity_decode($calname)); // to 7bit ascii
|
||||
|
@ -1043,7 +1109,7 @@ class calendar extends rcube_plugin
|
|||
header("Content-Type: text/calendar");
|
||||
header("Content-Disposition: inline; filename=".$calname.'.ics');
|
||||
|
||||
$this->get_ical()->export($events, '', true, array($this->driver, 'get_attachment_body'));
|
||||
$this->get_ical()->export($events, '', true, $attachments ? array($this->driver, 'get_attachment_body') : null);
|
||||
|
||||
if ($terminate)
|
||||
exit;
|
||||
|
@ -1172,8 +1238,16 @@ class calendar extends rcube_plugin
|
|||
if ($event['recurrence']) {
|
||||
$event['recurrence_text'] = $this->_recurrence_text($event['recurrence']);
|
||||
if ($event['recurrence']['UNTIL'])
|
||||
$event['recurrence']['UNTIL'] = $this->lib->adjust_timezone($event['recurrence']['UNTIL'])->format('c');
|
||||
$event['recurrence']['UNTIL'] = $this->lib->adjust_timezone($event['recurrence']['UNTIL'], $event['allday'])->format('c');
|
||||
unset($event['recurrence']['EXCEPTIONS']);
|
||||
|
||||
// format RDATE values
|
||||
if (is_array($event['recurrence']['RDATE'])) {
|
||||
$libcal = $this->lib;
|
||||
$event['recurrence']['RDATE'] = array_map(function($rdate) use ($libcal) {
|
||||
return $libcal->adjust_timezone($rdate, true)->format('c');
|
||||
}, $event['recurrence']['RDATE']);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ((array)$event['attachments'] as $k => $attachment) {
|
||||
|
@ -1203,9 +1277,11 @@ class calendar extends rcube_plugin
|
|||
|
||||
return array(
|
||||
'_id' => $event['calendar'] . ':' . $event['id'], // unique identifier for fullcalendar
|
||||
'start' => $this->lib->adjust_timezone($event['start'])->format('c'),
|
||||
'end' => $this->lib->adjust_timezone($event['end'])->format('c'),
|
||||
'changed' => $this->lib->adjust_timezone($event['changed'])->format('c'),
|
||||
'start' => $this->lib->adjust_timezone($event['start'], $event['allday'])->format('c'),
|
||||
'end' => $this->lib->adjust_timezone($event['end'], $event['allday'])->format('c'),
|
||||
// 'changed' might be empty for event recurrences (Bug #2185)
|
||||
'changed' => $event['changed'] ? $this->lib->adjust_timezone($event['changed'])->format('c') : null,
|
||||
'created' => $event['created'] ? $this->lib->adjust_timezone($event['created'])->format('c') : null,
|
||||
'title' => strval($event['title']),
|
||||
'description' => strval($event['description']),
|
||||
'location' => strval($event['location']),
|
||||
|
@ -1220,6 +1296,34 @@ class calendar extends rcube_plugin
|
|||
*/
|
||||
private function _recurrence_text($rrule)
|
||||
{
|
||||
// derive missing FREQ and INTERVAL from RDATE list
|
||||
if (empty($rrule['FREQ']) && !empty($rrule['RDATE'])) {
|
||||
$first = $rrule['RDATE'][0];
|
||||
$second = $rrule['RDATE'][1];
|
||||
$third = $rrule['RDATE'][2];
|
||||
if (is_a($first, 'DateTime') && is_a($second, 'DateTime')) {
|
||||
$diff = $first->diff($second);
|
||||
foreach (array('y' => 'YEARLY', 'm' => 'MONTHLY', 'd' => 'DAILY') as $k => $freq) {
|
||||
if ($diff->$k != 0) {
|
||||
$rrule['FREQ'] = $freq;
|
||||
$rrule['INTERVAL'] = $diff->$k;
|
||||
|
||||
// verify interval with next item
|
||||
if (is_a($third, 'DateTime')) {
|
||||
$diff2 = $second->diff($third);
|
||||
if ($diff2->$k != $diff->$k) {
|
||||
unset($rrule['INTERVAL']);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$rrule['INTERVAL'])
|
||||
$rrule['FREQ'] = 'RDATE';
|
||||
$rrule['UNTIL'] = end($rrule['RDATE']);
|
||||
}
|
||||
|
||||
// TODO: finish this
|
||||
$freq = sprintf('%s %d ', $this->gettext('every'), $rrule['INTERVAL']);
|
||||
$details = '';
|
||||
|
@ -1368,9 +1472,24 @@ class calendar extends rcube_plugin
|
|||
return;
|
||||
}
|
||||
|
||||
if ($event['recurrence']['UNTIL'])
|
||||
if (is_array($event['recurrence']) && !empty($event['recurrence']['UNTIL']))
|
||||
$event['recurrence']['UNTIL'] = new DateTime($event['recurrence']['UNTIL'], $this->timezone);
|
||||
|
||||
if (is_array($event['recurrence']) && is_array($event['recurrence']['RDATE'])) {
|
||||
$tz = $this->timezone;
|
||||
$start = $event['start'];
|
||||
$event['recurrence']['RDATE'] = array_map(function($rdate) use ($tz, $start) {
|
||||
try {
|
||||
$dt = new DateTime($rdate, $tz);
|
||||
$dt->setTime($start->format('G'), $start->format('i'));
|
||||
return $dt;
|
||||
}
|
||||
catch (Exception $e) {
|
||||
return null;
|
||||
}
|
||||
}, $event['recurrence']['RDATE']);
|
||||
}
|
||||
|
||||
$attachments = array();
|
||||
$eventid = 'cal:'.$event['id'];
|
||||
if (is_array($_SESSION[self::SESSION_KEY]) && $_SESSION[self::SESSION_KEY]['id'] == $eventid) {
|
||||
|
@ -1418,8 +1537,10 @@ class calendar extends rcube_plugin
|
|||
}
|
||||
|
||||
// mapping url => vurl because of the fullcalendar client script
|
||||
$event['url'] = $event['vurl'];
|
||||
unset($event['vurl']);
|
||||
if (array_key_exists('vurl', $event)) {
|
||||
$event['url'] = $event['vurl'];
|
||||
unset($event['vurl']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1700,6 +1821,8 @@ class calendar extends rcube_plugin
|
|||
public function itip_attend_response($p)
|
||||
{
|
||||
if ($p['action'] == 'attend') {
|
||||
$this->ui->init();
|
||||
|
||||
$this->rc->output->set_env('task', 'calendar'); // override some env vars
|
||||
$this->rc->output->set_env('refresh_interval', 0);
|
||||
$this->rc->output->set_pagetitle($this->gettext('calendar'));
|
||||
|
@ -1806,17 +1929,21 @@ class calendar extends rcube_plugin
|
|||
|
||||
$html = '';
|
||||
foreach ($this->ics_parts as $mime_id) {
|
||||
$part = $this->message->mime_parts[$mime_id];
|
||||
$part = $this->message->mime_parts[$mime_id];
|
||||
$charset = $part->ctype_parameters['charset'] ? $part->ctype_parameters['charset'] : RCMAIL_CHARSET;
|
||||
$events = $this->ical->import($this->message->get_part_content($mime_id), $charset);
|
||||
$title = $this->gettext('title');
|
||||
|
||||
$events = $this->ical->import($this->message->get_part_content($mime_id), $charset);
|
||||
$title = $this->gettext('title');
|
||||
$date = rcube_utils::anytodatetime($this->message->headers->date);
|
||||
|
||||
// successfully parsed events?
|
||||
if (empty($events))
|
||||
continue;
|
||||
|
||||
// show a box for every event in the file
|
||||
foreach ($events as $idx => $event) {
|
||||
if ($event['_type'] != 'event') // skip non-event objects (#2928)
|
||||
continue;
|
||||
|
||||
// define buttons according to method
|
||||
if ($this->ical->method == 'REPLY') {
|
||||
$title = $this->gettext('itipreply');
|
||||
|
@ -1851,17 +1978,25 @@ class calendar extends rcube_plugin
|
|||
$status = 'unknown';
|
||||
foreach ($event['attendees'] as $attendee) {
|
||||
if ($attendee['email'] && in_array(strtolower($attendee['email']), $emails)) {
|
||||
$status = strtoupper($attendee['status']);
|
||||
$status = !empty($attendee['status']) ? strtoupper($attendee['status']) : 'NEEDS-ACTION';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$dom_id = asciiwords($event['uid'], true);
|
||||
$buttons = html::div(array('id' => 'rsvp-'.$dom_id, 'style' => 'display:none'), $rsvp_buttons);
|
||||
$buttons .= html::div(array('id' => 'import-'.$dom_id, 'style' => 'display:none'), $import_button);
|
||||
|
||||
$dom_id = asciiwords($event['uid'], true);
|
||||
$buttons = html::div(array('id' => 'rsvp-'.$dom_id, 'style' => 'display:none'), $rsvp_buttons);
|
||||
$buttons .= html::div(array('id' => 'import-'.$dom_id, 'style' => 'display:none'), $import_button);
|
||||
$buttons_pre = html::div(array('id' => 'loading-'.$dom_id, 'class' => 'rsvp-status loading'), $this->gettext('loading'));
|
||||
|
||||
$this->rc->output->add_script('rcube_calendar.fetch_event_rsvp_status(' . json_serialize(array('uid' => $event['uid'], 'changed' => $event['changed']->format('U'), 'sequence' => intval($event['sequence']), 'fallback' => $status)) . ')', 'docready');
|
||||
$changed = is_object($event['changed']) ? $event['changed'] : $date;
|
||||
|
||||
$script = json_serialize(array(
|
||||
'uid' => $event['uid'],
|
||||
'changed' => $changed ? $changed->format('U') : 0,
|
||||
'sequence' => intval($event['sequence']),
|
||||
'fallback' => $status,
|
||||
));
|
||||
|
||||
$this->rc->output->add_script("rcube_calendar.fetch_event_rsvp_status($script)", 'docready');
|
||||
}
|
||||
else if ($this->ical->method == 'CANCEL') {
|
||||
$title = $this->gettext('itipcancellation');
|
||||
|
@ -1879,13 +2014,21 @@ class calendar extends rcube_plugin
|
|||
'onclick' => "rcube_calendar.remove_event_from_mail('" . JQ($event['uid']) . "', '" . JQ($event['title']) . "')",
|
||||
'value' => $this->gettext('removefromcalendar'),
|
||||
));
|
||||
|
||||
$dom_id = asciiwords($event['uid'], true);
|
||||
$buttons = html::div(array('id' => 'rsvp-'.$dom_id, 'style' => 'display:none'), $button_remove);
|
||||
$buttons .= html::div(array('id' => 'import-'.$dom_id, 'style' => 'display:none'), $button_import);
|
||||
|
||||
$dom_id = asciiwords($event['uid'], true);
|
||||
$buttons = html::div(array('id' => 'rsvp-'.$dom_id, 'style' => 'display:none'), $button_remove);
|
||||
$buttons .= html::div(array('id' => 'import-'.$dom_id, 'style' => 'display:none'), $button_import);
|
||||
$buttons_pre = html::div(array('id' => 'loading-'.$dom_id, 'class' => 'rsvp-status loading'), $this->gettext('loading'));
|
||||
|
||||
$this->rc->output->add_script('rcube_calendar.fetch_event_rsvp_status(' . json_serialize(array('uid' => $event['uid'], 'changed' => $event['changed']->format('U'), 'sequence' => intval($event['sequence']), 'fallback' => 'CANCELLED')) . ')', 'docready');
|
||||
$changed = is_object($event['changed']) ? $event['changed'] : $date;
|
||||
|
||||
$script = json_serialize(array(
|
||||
'uid' => $event['uid'],
|
||||
'changed' => $changed ? $changed->format('U') : 0,
|
||||
'sequence' => intval($event['sequence']),
|
||||
'fallback' => 'CANCELLED',
|
||||
));
|
||||
|
||||
$this->rc->output->add_script("rcube_calendar.fetch_event_rsvp_status($script)", 'docready');
|
||||
}
|
||||
else {
|
||||
$buttons = html::tag('input', array(
|
||||
|
@ -2047,7 +2190,12 @@ class calendar extends rcube_plugin
|
|||
if ($success) {
|
||||
$message = $this->ical->method == 'REPLY' ? 'attendeupdateesuccess' : ($deleted ? 'successremoval' : 'importedsuccessfully');
|
||||
$this->rc->output->command('display_message', $this->gettext(array('name' => $message, 'vars' => array('calendar' => $calendar['name']))), 'confirmation');
|
||||
$this->rc->output->command('plugin.fetch_event_rsvp_status', array('uid' => $event['uid'], 'changed' => $event['changed']->format('U'), 'sequence' => intval($event['sequence']), 'fallback' => strtoupper($status)));
|
||||
$this->rc->output->command('plugin.fetch_event_rsvp_status', array(
|
||||
'uid' => $event['uid'],
|
||||
'changed' => is_object($event['changed']) ? $event['changed']->format('U') : 0,
|
||||
'sequence' => intval($event['sequence']),
|
||||
'fallback' => strtoupper($status),
|
||||
));
|
||||
$error_msg = null;
|
||||
}
|
||||
else if ($error_msg)
|
||||
|
@ -2180,7 +2328,7 @@ class calendar extends rcube_plugin
|
|||
$schema = 'https';
|
||||
$default_port = 443;
|
||||
}
|
||||
$url = $schema . '://' . $_SERVER['HTTP_HOST'];
|
||||
$url = $schema . '://' . preg_replace('/:\d+$/', '', $_SERVER['HTTP_HOST']);
|
||||
if ($_SERVER['SERVER_PORT'] != $default_port)
|
||||
$url .= ':' . $_SERVER['SERVER_PORT'];
|
||||
if (dirname($_SERVER['SCRIPT_NAME']) != '/')
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* @author Thomas Bruederli <bruederli@kolabsys.com>
|
||||
*
|
||||
* Copyright (C) 2010, Lazlo Westerhof <hello@lazlo.me>
|
||||
* Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
|
||||
* Copyright (C) 2013, Kolab Systems AG <contact@kolabsys.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
|
@ -36,10 +36,9 @@ function rcube_calendar(settings)
|
|||
var me = this;
|
||||
|
||||
// create new event from current mail message
|
||||
this.create_from_mail = function()
|
||||
this.create_from_mail = function(uid)
|
||||
{
|
||||
var uid;
|
||||
if ((uid = rcmail.get_single_uid())) {
|
||||
if (uid || (uid = rcmail.get_single_uid())) {
|
||||
// load calendar UI (scripts and edit dialog template)
|
||||
if (!this.ui_loaded) {
|
||||
$.when(
|
||||
|
@ -53,7 +52,7 @@ function rcube_calendar(settings)
|
|||
|
||||
me.ui_loaded = true;
|
||||
me.ui = new rcube_calendar_ui(me.settings);
|
||||
me.create_from_mail(); // start over
|
||||
me.create_from_mail(uid); // start over
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
@ -156,9 +155,6 @@ window.rcmail && rcmail.addEventListener('init', function(evt) {
|
|||
|
||||
// register create-from-mail command to message_commands array
|
||||
if (rcmail.env.task == 'mail') {
|
||||
// place link above 'view source'
|
||||
$('#messagemenu a.calendarlink').parent().insertBefore($('#messagemenu a.sourcelink').parent());
|
||||
|
||||
rcmail.register_command('calendar-create-from-mail', function() { cal.create_from_mail() });
|
||||
rcmail.addEventListener('plugin.mail2event_dialog', function(p){ cal.mail2event_dialog(p) });
|
||||
rcmail.addEventListener('plugin.unlock_saving', function(p){ cal.ui && cal.ui.unlock_saving(); });
|
||||
|
@ -169,6 +165,15 @@ window.rcmail && rcmail.addEventListener('init', function(evt) {
|
|||
}
|
||||
else
|
||||
rcmail.enable_command('calendar-create-from-mail', true);
|
||||
|
||||
// add contextmenu item
|
||||
if (window.rcm_contextmenu_register_command) {
|
||||
rcm_contextmenu_register_command(
|
||||
'calendar-create-from-mail',
|
||||
function(cmd,el){ cal.create_from_mail() },
|
||||
'calendar.createfrommail',
|
||||
'moveto');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
236
calendar_ui.js
236
calendar_ui.js
|
@ -332,7 +332,7 @@ function rcube_calendar_ui(settings)
|
|||
|
||||
// list event attendees
|
||||
if (calendar.attendees && event.attendees) {
|
||||
var data, dispname, organizer = false, rsvp = false, html = '';
|
||||
var data, dispname, organizer = false, rsvp = false, line, morelink, html = '',overflow = '';
|
||||
for (var j=0; j < event.attendees.length; j++) {
|
||||
data = event.attendees[j];
|
||||
dispname = Q(data.name || data.email);
|
||||
|
@ -343,12 +343,16 @@ function rcube_calendar_ui(settings)
|
|||
else if ((data.status == 'NEEDS-ACTION' || data.status == 'TENTATIVE') && settings.identity.emails.indexOf(';'+data.email) >= 0)
|
||||
rsvp = data.status.toLowerCase();
|
||||
}
|
||||
html += '<span class="attendee ' + String(data.role == 'ORGANIZER' ? 'organizer' : data.status).toLowerCase() + '">' + dispname + '</span> ';
|
||||
|
||||
line = '<span class="attendee ' + String(data.role == 'ORGANIZER' ? 'organizer' : data.status).toLowerCase() + '">' + dispname + '</span> ';
|
||||
if (morelink)
|
||||
overflow += line;
|
||||
else
|
||||
html += line;
|
||||
|
||||
// stop listing attendees
|
||||
if (j == 7 && event.attendees.length >= 7) {
|
||||
html += ' <em>' + rcmail.gettext('andnmore', 'calendar').replace('$nr', event.attendees.length - j - 1) + '</em>';
|
||||
break;
|
||||
morelink = $('<a href="#more" class="morelink"></a>').html(rcmail.gettext('andnmore', 'calendar').replace('$nr', event.attendees.length - j - 1));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -357,6 +361,20 @@ function rcube_calendar_ui(settings)
|
|||
.children('.event-text')
|
||||
.html(html)
|
||||
.find('a.mailtolink').click(function(e) { rcmail.redirect(rcmail.url('mail/compose', { _to:this.href.substr(7) })); return false; });
|
||||
|
||||
// display all attendees in a popup when clicking the "more" link
|
||||
if (morelink) {
|
||||
$('#event-attendees .event-text').append(morelink);
|
||||
morelink.click(function(e){
|
||||
rcmail.show_popup_dialog(
|
||||
'<div id="all-event-attendees" class="event-attendees">' + html + overflow + '</div>',
|
||||
rcmail.gettext('tabattendees','calendar'),
|
||||
null,
|
||||
{ width:450, modal:false });
|
||||
$('#all-event-attendees a.mailtolink').click(function(e) { rcmail.redirect(rcmail.url('mail/compose', { _to:this.href.substr(7) })); return false; });
|
||||
return false;
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
$('#event-rsvp')[(rsvp&&!organizer?'show':'hide')]();
|
||||
|
@ -496,11 +514,12 @@ function rcube_calendar_ui(settings)
|
|||
var recurrence, interval, rrtimes, rrenddate;
|
||||
var load_recurrence_tab = function()
|
||||
{
|
||||
recurrence = $('#edit-recurrence-frequency').val(event.recurrence ? event.recurrence.FREQ : '').change();
|
||||
recurrence = $('#edit-recurrence-frequency').val(event.recurrence ? event.recurrence.FREQ || (event.recurrence.RDATE ? 'RDATE' : '') : '').change();
|
||||
interval = $('#eventedit select.edit-recurrence-interval').val(event.recurrence ? event.recurrence.INTERVAL : 1);
|
||||
rrtimes = $('#edit-recurrence-repeat-times').val(event.recurrence ? event.recurrence.COUNT : 1);
|
||||
rrenddate = $('#edit-recurrence-enddate').val(event.recurrence && event.recurrence.UNTIL ? $.fullCalendar.formatDate(parseISO8601(event.recurrence.UNTIL), settings['date_format']) : '');
|
||||
$('#eventedit input.edit-recurrence-until:checked').prop('checked', false);
|
||||
$('#edit-recurrence-rdates').html('');
|
||||
|
||||
var weekdays = ['SU','MO','TU','WE','TH','FR','SA'];
|
||||
var rrepeat_id = '#edit-recurrence-repeat-forever';
|
||||
|
@ -533,6 +552,11 @@ function rcube_calendar_ui(settings)
|
|||
else if (event.start) {
|
||||
$('input.edit-recurrence-yearly-bymonth').val([String(event.start.getMonth()+1)]);
|
||||
}
|
||||
if (event.recurrence && event.recurrence.RDATE) {
|
||||
$.each(event.recurrence.RDATE, function(i,rdate){
|
||||
add_rdate(parseISO8601(rdate));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// show warning if editing a recurring event
|
||||
|
@ -697,6 +721,16 @@ function rcube_calendar_ui(settings)
|
|||
if ((byday = $('#edit-recurrence-yearly-byday').val()))
|
||||
data.recurrence.BYDAY = $('#edit-recurrence-yearly-prefix').val() + byday;
|
||||
}
|
||||
else if (freq == 'RDATE') {
|
||||
data.recurrence = { RDATE:[] };
|
||||
// take selected but not yet added date into account
|
||||
if ($('#edit-recurrence-rdate-input').val() != '') {
|
||||
$('#recurrence-form-rdate input.button.add').click();
|
||||
}
|
||||
$('#edit-recurrence-rdates li').each(function(i, li){
|
||||
data.recurrence.RDATE.push($(li).attr('data-value'));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
data.calendar = calendars.val();
|
||||
|
@ -1547,6 +1581,34 @@ function rcube_calendar_ui(settings)
|
|||
me.saving_lock = rcmail.set_busy(true, 'calendar.savingdata');
|
||||
rcmail.http_post('event', { action:'rsvp', e:me.selected_event, status:response });
|
||||
}
|
||||
};
|
||||
|
||||
// add the given date to the RDATE list
|
||||
var add_rdate = function(date)
|
||||
{
|
||||
var li = $('<li>')
|
||||
.attr('data-value', date2servertime(date))
|
||||
.html('<span>' + Q($.fullCalendar.formatDate(date, settings['date_format'])) + '</span>')
|
||||
.appendTo('#edit-recurrence-rdates');
|
||||
|
||||
$('<a>').attr('href', '#del')
|
||||
.addClass('iconbutton delete')
|
||||
.html(rcmail.get_label('delete', 'calendar'))
|
||||
.attr('title', rcmail.get_label('delete', 'calendar'))
|
||||
.appendTo(li);
|
||||
};
|
||||
|
||||
// re-sort the list items by their 'data-value' attribute
|
||||
var sort_rdates = function()
|
||||
{
|
||||
var mylist = $('#edit-recurrence-rdates'),
|
||||
listitems = mylist.children('li').get();
|
||||
listitems.sort(function(a, b) {
|
||||
var compA = $(a).attr('data-value');
|
||||
var compB = $(b).attr('data-value');
|
||||
return (compA < compB) ? -1 : (compA > compB) ? 1 : 0;
|
||||
})
|
||||
$.each(listitems, function(idx, item) { mylist.append(item); });
|
||||
}
|
||||
|
||||
// post the given event data to server
|
||||
|
@ -1911,7 +1973,7 @@ function rcube_calendar_ui(settings)
|
|||
|
||||
this.calendar_remove = function(calendar)
|
||||
{
|
||||
if (confirm(rcmail.gettext('deletecalendarconfirm', 'calendar'))) {
|
||||
if (confirm(rcmail.gettext(calendar.children ? 'deletecalendarconfirmrecursive' : 'deletecalendarconfirm', 'calendar'))) {
|
||||
rcmail.http_post('calendar', { action:'remove', c:{ id:calendar.id } });
|
||||
return true;
|
||||
}
|
||||
|
@ -1920,7 +1982,24 @@ function rcube_calendar_ui(settings)
|
|||
|
||||
this.calendar_destroy_source = function(id)
|
||||
{
|
||||
var delete_ids = [];
|
||||
|
||||
if (this.calendars[id]) {
|
||||
// find sub-calendars
|
||||
if (this.calendars[id].children) {
|
||||
for (var child_id in this.calendars) {
|
||||
if (String(child_id).indexOf(id) == 0)
|
||||
delete_ids.push(child_id);
|
||||
}
|
||||
}
|
||||
else {
|
||||
delete_ids.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
// delete all calendars in the list
|
||||
for (var i=0; i < delete_ids.length; i++) {
|
||||
id = delete_ids[i];
|
||||
fc.fullCalendar('removeEventSource', this.calendars[id]);
|
||||
$(rcmail.get_folder_li(id, 'rcmlical')).remove();
|
||||
$('#edit-calendar option[value="'+id+'"]').remove();
|
||||
|
@ -1938,17 +2017,30 @@ function rcube_calendar_ui(settings)
|
|||
if ($dialog.is(':ui-dialog'))
|
||||
$dialog.dialog('close');
|
||||
|
||||
$('#event-import-calendar').val(calendar.id);
|
||||
if (calendar)
|
||||
$('#event-import-calendar').val(calendar.id);
|
||||
|
||||
var buttons = {};
|
||||
buttons[rcmail.gettext('import', 'calendar')] = function() {
|
||||
if (form && form.elements._data.value) {
|
||||
rcmail.async_upload_form(form, 'import_events', function(e) {
|
||||
rcmail.set_busy(false, null, me.saving_lock);
|
||||
$('.ui-dialog-buttonpane button', $dialog.parent()).button('enable');
|
||||
|
||||
// display error message if no sophisticated response from server arrived (e.g. iframe load error)
|
||||
if (me.import_succeeded === null)
|
||||
rcmail.display_message(rcmail.get_label('importerror', 'calendar'), 'error');
|
||||
});
|
||||
|
||||
// display upload indicator
|
||||
// display upload indicator (with extended timeout)
|
||||
var timeout = rcmail.env.request_timeout;
|
||||
rcmail.env.request_timeout = 600;
|
||||
me.import_succeeded = null;
|
||||
me.saving_lock = rcmail.set_busy(true, 'uploading');
|
||||
$('.ui-dialog-buttonpane button', $dialog.parent()).button('disable');
|
||||
|
||||
// restore settings
|
||||
rcmail.env.request_timeout = timeout;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1963,6 +2055,7 @@ function rcube_calendar_ui(settings)
|
|||
closeOnEscape: false,
|
||||
title: rcmail.gettext('importevents', 'calendar'),
|
||||
close: function() {
|
||||
$('.ui-dialog-buttonpane button', $dialog.parent()).button('enable');
|
||||
$dialog.dialog("destroy").hide();
|
||||
},
|
||||
buttons: buttons,
|
||||
|
@ -1974,6 +2067,7 @@ function rcube_calendar_ui(settings)
|
|||
// callback from server if import succeeded
|
||||
this.import_success = function(p)
|
||||
{
|
||||
this.import_succeeded = true;
|
||||
$("#eventsimport:ui-dialog").dialog('close');
|
||||
rcmail.set_busy(false, null, me.saving_lock);
|
||||
rcmail.gui_objects.importform.reset();
|
||||
|
@ -1982,6 +2076,70 @@ function rcube_calendar_ui(settings)
|
|||
this.refresh(p);
|
||||
};
|
||||
|
||||
// callback from server to report errors on import
|
||||
this.import_error = function(p)
|
||||
{
|
||||
this.import_succeeded = false;
|
||||
rcmail.display_message(p.message || rcmail.get_label('importerror', 'calendar'), 'error');
|
||||
}
|
||||
|
||||
// open a dialog to select calendars for export
|
||||
this.export_events = function(calendar)
|
||||
{
|
||||
// close show dialog first
|
||||
var $dialog = $("#eventsexport"),
|
||||
form = rcmail.gui_objects.exportform;
|
||||
|
||||
if ($dialog.is(':ui-dialog'))
|
||||
$dialog.dialog('close');
|
||||
|
||||
if (calendar)
|
||||
$('#event-export-calendar').val(calendar.id);
|
||||
|
||||
$('#event-export-range').change(function(e){
|
||||
var custom = $('option:selected', this).val() == 'custom',
|
||||
input = $('#event-export-startdate')
|
||||
input.parent()[(custom?'show':'hide')]();
|
||||
if (custom)
|
||||
input.select();
|
||||
})
|
||||
|
||||
var buttons = {};
|
||||
buttons[rcmail.gettext('export', 'calendar')] = function() {
|
||||
if (form) {
|
||||
var start = 0, range = $('#event-export-range option:selected', this).val(),
|
||||
source = $('#event-export-calendar option:selected').val(),
|
||||
attachmt = $('#event-export-attachments').get(0).checked;
|
||||
|
||||
if (range == 'custom')
|
||||
start = date2unixtime(parse_datetime('00:00', $('#event-export-startdate').val()));
|
||||
else if (range > 0)
|
||||
start = 'today -' + range + '^months';
|
||||
|
||||
rcmail.goto_url('export_events', { source:source, start:start, attachments:attachmt?1:0 });
|
||||
}
|
||||
};
|
||||
|
||||
buttons[rcmail.gettext('cancel', 'calendar')] = function() {
|
||||
$dialog.dialog("close");
|
||||
};
|
||||
|
||||
// open jquery UI dialog
|
||||
$dialog.dialog({
|
||||
modal: true,
|
||||
resizable: false,
|
||||
closeOnEscape: false,
|
||||
title: rcmail.gettext('exporttitle', 'calendar'),
|
||||
close: function() {
|
||||
$('.ui-dialog-buttonpane button', $dialog.parent()).button('enable');
|
||||
$dialog.dialog("destroy").hide();
|
||||
},
|
||||
buttons: buttons,
|
||||
width: 520
|
||||
}).show();
|
||||
|
||||
};
|
||||
|
||||
// show URL of the given calendar in a dialog box
|
||||
this.showurl = function(calendar)
|
||||
{
|
||||
|
@ -1991,6 +2149,14 @@ function rcube_calendar_ui(settings)
|
|||
$dialog.dialog('close');
|
||||
|
||||
if (calendar.feedurl) {
|
||||
if (calendar.caldavurl) {
|
||||
$('#caldavurl').val(calendar.caldavurl);
|
||||
$('#calendarcaldavurl').show();
|
||||
}
|
||||
else {
|
||||
$('#calendarcaldavurl').hide();
|
||||
}
|
||||
|
||||
$dialog.dialog({
|
||||
resizable: true,
|
||||
closeOnEscape: true,
|
||||
|
@ -2038,11 +2204,29 @@ function rcube_calendar_ui(settings)
|
|||
if (me.fisheye_date)
|
||||
me.fisheye_view(me.fisheye_date);
|
||||
}
|
||||
// refetch all calendars
|
||||
else if (p.refetch) {
|
||||
fc.fullCalendar('refetchEvents');
|
||||
}
|
||||
|
||||
// remove temp events
|
||||
fc.fullCalendar('removeEvents', function(e){ return e.temp; });
|
||||
};
|
||||
|
||||
// modify query parameters for refresh requests
|
||||
this.before_refresh = function(query)
|
||||
{
|
||||
var view = fc.fullCalendar('getView');
|
||||
|
||||
query.start = date2unixtime(view.visStart);
|
||||
query.end = date2unixtime(view.visEnd);
|
||||
|
||||
if (this.search_query)
|
||||
query.q = this.search_query;
|
||||
|
||||
return query;
|
||||
};
|
||||
|
||||
|
||||
/*** event searching ***/
|
||||
|
||||
|
@ -2235,7 +2419,7 @@ function rcube_calendar_ui(settings)
|
|||
var id = $(this).data('id');
|
||||
rcmail.select_folder(id, 'rcmlical');
|
||||
rcmail.enable_command('calendar-edit', true);
|
||||
rcmail.enable_command('calendar-remove', 'events-import', 'calendar-showurl', true);
|
||||
rcmail.enable_command('calendar-remove', 'calendar-showurl', true);
|
||||
me.selected_calendar = id;
|
||||
})
|
||||
.dblclick(function(){ me.calendar_edit_dialog(me.calendars[me.selected_calendar]); })
|
||||
|
@ -2533,8 +2717,8 @@ function rcube_calendar_ui(settings)
|
|||
minical = $('#datepicker').datepicker($.extend(datepicker_settings, {
|
||||
inline: true,
|
||||
showWeek: true,
|
||||
changeMonth: false, // maybe enable?
|
||||
changeYear: false, // maybe enable?
|
||||
changeMonth: true,
|
||||
changeYear: true,
|
||||
onSelect: function(dateText, inst) {
|
||||
ignore_click = true;
|
||||
var d = minical.datepicker('getDate'); //parse_datetime('0:0', dateText);
|
||||
|
@ -2623,12 +2807,32 @@ function rcube_calendar_ui(settings)
|
|||
$('#edit-recurrence-frequency').change(function(e){
|
||||
var freq = $(this).val().toLowerCase();
|
||||
$('.recurrence-form').hide();
|
||||
if (freq)
|
||||
$('#recurrence-form-'+freq+', #recurrence-form-until').show();
|
||||
if (freq) {
|
||||
$('#recurrence-form-'+freq).show();
|
||||
if (freq != 'rdate')
|
||||
$('#recurrence-form-until').show();
|
||||
}
|
||||
});
|
||||
$('#recurrence-form-rdate input.button.add').click(function(e){
|
||||
var dt, dv = $('#edit-recurrence-rdate-input').val();
|
||||
if (dv && (dt = parse_datetime('12:00', dv))) {
|
||||
add_rdate(dt);
|
||||
sort_rdates();
|
||||
$('#edit-recurrence-rdate-input').val('')
|
||||
}
|
||||
else {
|
||||
$('#edit-recurrence-rdate-input').select();
|
||||
}
|
||||
});
|
||||
$('#edit-recurrence-rdates').on('click', 'a.delete', function(e){
|
||||
$(this).closest('li').remove();
|
||||
return false;
|
||||
});
|
||||
$('#edit-recurrence-enddate').datepicker(datepicker_settings).click(function(){ $("#edit-recurrence-repeat-until").prop('checked', true) });
|
||||
$('#edit-recurrence-repeat-times').change(function(e){ $('#edit-recurrence-repeat-count').prop('checked', true); });
|
||||
|
||||
$('#edit-recurrence-rdate-input, #event-export-startdate').datepicker(datepicker_settings);
|
||||
|
||||
// init attendees autocompletion
|
||||
var ac_props;
|
||||
// parallel autocompletion
|
||||
|
@ -2723,11 +2927,11 @@ window.rcmail && rcmail.addEventListener('init', function(evt) {
|
|||
rcmail.register_command('calendar-create', function(){ cal.calendar_edit_dialog(null); }, true);
|
||||
rcmail.register_command('calendar-edit', function(){ cal.calendar_edit_dialog(cal.calendars[cal.selected_calendar]); }, false);
|
||||
rcmail.register_command('calendar-remove', function(){ cal.calendar_remove(cal.calendars[cal.selected_calendar]); }, false);
|
||||
rcmail.register_command('events-import', function(){ cal.import_events(cal.calendars[cal.selected_calendar]); }, false);
|
||||
rcmail.register_command('events-import', function(){ cal.import_events(cal.calendars[cal.selected_calendar]); }, true);
|
||||
rcmail.register_command('calendar-showurl', function(){ cal.showurl(cal.calendars[cal.selected_calendar]); }, false);
|
||||
|
||||
// search and export events
|
||||
rcmail.register_command('export', function(){ rcmail.goto_url('export_events', { source:cal.selected_calendar }); }, true);
|
||||
rcmail.register_command('export', function(){ cal.export_events(cal.calendars[cal.selected_calendar]); }, true);
|
||||
rcmail.register_command('search', function(){ cal.quicksearch(); }, true);
|
||||
rcmail.register_command('reset-search', function(){ cal.reset_quicksearch(); }, true);
|
||||
|
||||
|
@ -2736,6 +2940,8 @@ window.rcmail && rcmail.addEventListener('init', function(evt) {
|
|||
rcmail.addEventListener('plugin.unlock_saving', function(p){ cal.unlock_saving(); });
|
||||
rcmail.addEventListener('plugin.refresh_calendar', function(p){ cal.refresh(p); });
|
||||
rcmail.addEventListener('plugin.import_success', function(p){ cal.import_success(p); });
|
||||
rcmail.addEventListener('plugin.import_error', function(p){ cal.import_error(p); });
|
||||
rcmail.addEventListener('requestrefresh', function(q){ return cal.before_refresh(q); });
|
||||
|
||||
// let's go
|
||||
var cal = new rcube_calendar_ui($.extend(rcmail.env.calendar_settings, rcmail.env.libcal_settings));
|
||||
|
|
|
@ -1,31 +1,30 @@
|
|||
{
|
||||
"name": "kolab/calendar",
|
||||
"type": "roundcube-plugin",
|
||||
"description": "Calendar module for Roundcube",
|
||||
"keywords": ["apps","calendar"],
|
||||
"description": "Calendar plugin",
|
||||
"homepage": "http://git.kolab.org/roundcubemail-plugins-kolab/",
|
||||
"license": "GPL-3.0+",
|
||||
"license": "AGPLv3",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Thomas Bruederli",
|
||||
"email": "bruederli@kolabsys.com",
|
||||
"role": "Lead"
|
||||
},
|
||||
{
|
||||
"name": "Alensader Machniak",
|
||||
"email": "machniak@kolabsys.com",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"repositories": [
|
||||
{
|
||||
"type": "composer",
|
||||
"url": "http://33.33.108.2"
|
||||
"url": "http://plugins.roundcube.net"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.3.0",
|
||||
"roundcube/plugin-installer": "dev-master"
|
||||
},
|
||||
"extra": {
|
||||
"roundcube": {
|
||||
"min-version": "0.9.2",
|
||||
"sql-dir": "drivers/database/SQL"
|
||||
}
|
||||
"roundcube/plugin-installer": ">=0.1.3",
|
||||
"kolab/libcalendaring": ">=1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -110,7 +110,7 @@ $rcmail_config['calendar_allow_invite_shared'] = false;
|
|||
// enable asynchronous free-busy triggering after data changed
|
||||
$rcmail_config['calendar_freebusy_trigger'] = false;
|
||||
|
||||
// SMTP username used to send (anonymous) itip messages
|
||||
// SMTP server host used to send (anonymous) itip messages
|
||||
$rcmail_config['calendar_itip_smtp_server'] = null;
|
||||
|
||||
// SMTP username used to send (anonymous) itip messages
|
||||
|
@ -119,5 +119,12 @@ $rcmail_config['calendar_itip_smtp_user'] = 'smtpauth';
|
|||
// SMTP password used to send (anonymous) itip messages
|
||||
$rcmail_config['calendar_itip_smtp_pass'] = '123456';
|
||||
|
||||
// Base URL to build fully qualified URIs to access calendars via CALDAV
|
||||
// The following replacement variables are supported:
|
||||
// %h - Current HTTP host
|
||||
// %u - Current webmail user name
|
||||
// %n - Calendar name
|
||||
// %i - Calendar UUID
|
||||
// $rcmail_config['calendar_caldav_url'] = 'http://%h/iRony/calendars/%u/%i';
|
||||
|
||||
?>
|
||||
?>
|
||||
|
|
|
@ -54,7 +54,18 @@
|
|||
* 'free_busy' => 'free|busy|outofoffice|tentative', // Show time as
|
||||
* 'priority' => 0-9, // Event priority (0=undefined, 1=highest, 9=lowest)
|
||||
* 'sensitivity' => 'public|private|confidential', // Event sensitivity
|
||||
* 'alarms' => '-15M:DISPLAY', // Reminder settings inspired by valarm definition (e.g. display alert 15 minutes before event)
|
||||
* 'alarms' => '-15M:DISPLAY', // DEPRECATED Reminder settings inspired by valarm definition (e.g. display alert 15 minutes before event)
|
||||
* 'valarms' => array( // List of reminders (new format), each represented as a hash array:
|
||||
* array(
|
||||
* 'trigger' => '-PT90M', // ISO 8601 period string prefixed with '+' or '-', or DateTime object
|
||||
* 'action' => 'DISPLAY|EMAIL|AUDIO',
|
||||
* 'duration' => 'PT15M', // ISO 8601 period string
|
||||
* 'repeat' => 0, // number of repetitions
|
||||
* 'description' => '', // text to display for DISPLAY actions
|
||||
* 'summary' => '', // message text for EMAIL actions
|
||||
* 'attendees' => array(), // list of email addresses to receive alarm messages
|
||||
* ),
|
||||
* ),
|
||||
* 'attachments' => array( // List of attachments
|
||||
* 'name' => 'File name',
|
||||
* 'mimetype' => 'Content type',
|
||||
|
@ -236,9 +247,11 @@ abstract class calendar_driver
|
|||
* @param integer Event's new end (unix timestamp)
|
||||
* @param string Search query (optional)
|
||||
* @param mixed List of calendar IDs to load events from (either as array or comma-separated string)
|
||||
* @param boolean Include virtual/recurring events (optional)
|
||||
* @param integer Only list events modified since this time (unix timestamp)
|
||||
* @return array A list of event objects (see header of this file for struct of an event)
|
||||
*/
|
||||
abstract function load_events($start, $end, $query = null, $calendars = null);
|
||||
abstract function load_events($start, $end, $query = null, $calendars = null, $virtual = 1, $modifiedsince = null);
|
||||
|
||||
/**
|
||||
* Get a list of pending alarms to be displayed to the user
|
||||
|
|
|
@ -90,6 +90,7 @@ class database_driver extends calendar_driver
|
|||
$arr['showalarms'] = intval($arr['showalarms']);
|
||||
$arr['active'] = !in_array($arr['id'], $hidden);
|
||||
$arr['name'] = html::quote($arr['name']);
|
||||
$arr['listname'] = html::quote($arr['name']);
|
||||
$this->calendars[$arr['calendar_id']] = $arr;
|
||||
$calendar_ids[] = $this->rc->db->quote($arr['calendar_id']);
|
||||
}
|
||||
|
@ -723,7 +724,7 @@ class database_driver extends calendar_driver
|
|||
*
|
||||
* @see calendar_driver::load_events()
|
||||
*/
|
||||
public function load_events($start, $end, $query = null, $calendars = null)
|
||||
public function load_events($start, $end, $query = null, $calendars = null, $virtual = 1, $modifiedsince = null)
|
||||
{
|
||||
if (empty($calendars))
|
||||
$calendars = array_keys($this->calendars);
|
||||
|
@ -741,6 +742,12 @@ class database_driver extends calendar_driver
|
|||
$sql_add = 'AND (' . join(' OR ', $sql_query) . ')';
|
||||
}
|
||||
|
||||
if (!$virtual)
|
||||
$sql_arr .= ' AND e.recurrence_id = 0';
|
||||
|
||||
if ($modifiedsince)
|
||||
$sql_add .= ' AND e.changed >= ' . $this->rc->db->quote(date('Y-m-d H:i:s', $modifiedsince));
|
||||
|
||||
$events = array();
|
||||
if (!empty($calendar_ids)) {
|
||||
$result = $this->rc->db->query(sprintf(
|
||||
|
@ -792,6 +799,8 @@ class database_driver extends calendar_driver
|
|||
$rr[2] = intval($rr[2]);
|
||||
else if ($rr[1] == 'UNTIL')
|
||||
$rr[2] = date_create($rr[2]);
|
||||
else if ($rr[1] == 'RDATE')
|
||||
$rr[2] = array_map('date_create', explode(',', $rr[2]));
|
||||
else if ($rr[1] == 'EXDATE')
|
||||
$rr[2] = array_map('date_create', explode(',', $rr[2]));
|
||||
$event['recurrence'][$rr[1]] = $rr[2];
|
||||
|
|
|
@ -11,7 +11,7 @@ CREATE TABLE IF NOT EXISTS `kolab_alarms` (
|
|||
`user_id` int(10) UNSIGNED NOT NULL,
|
||||
`notifyat` DATETIME DEFAULT NULL,
|
||||
`dismissed` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY(`event_id`),
|
||||
PRIMARY KEY(`event_id`,`user_id`),
|
||||
CONSTRAINT `fk_kolab_alarms_user_id` FOREIGN KEY (`user_id`)
|
||||
REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE
|
||||
) /*!40000 ENGINE=INNODB */;
|
||||
|
|
2
drivers/kolab/SQL/mysql/2014082600.sql
Normal file
2
drivers/kolab/SQL/mysql/2014082600.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE `kolab_alarms` DROP PRIMARY KEY;
|
||||
ALTER TABLE `kolab_alarms` ADD PRIMARY KEY (`alarm_id`, `user_id`);
|
|
@ -33,6 +33,7 @@ class kolab_calendar
|
|||
public $alarms = false;
|
||||
public $categories = array();
|
||||
public $storage;
|
||||
public $name;
|
||||
|
||||
private $cal;
|
||||
private $events = array();
|
||||
|
@ -48,7 +49,7 @@ class kolab_calendar
|
|||
$this->cal = $calendar;
|
||||
|
||||
if (strlen($imap_folder))
|
||||
$this->imap_folder = $imap_folder;
|
||||
$this->imap_folder = $this->name = $imap_folder;
|
||||
|
||||
// ID is derrived from folder name
|
||||
$this->id = kolab_storage::folder_id($this->imap_folder);
|
||||
|
@ -155,6 +156,24 @@ class kolab_calendar
|
|||
return 'cc0000';
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose an URL for CalDAV access to this calendar (if configured)
|
||||
*/
|
||||
public function get_caldav_url()
|
||||
{
|
||||
$url = null;
|
||||
if ($template = $this->cal->rc->config->get('calendar_caldav_url', null)) {
|
||||
return strtr($template, array(
|
||||
'%h' => $_SERVER['HTTP_HOST'],
|
||||
'%u' => urlencode($this->cal->rc->get_user_name()),
|
||||
'%i' => urlencode($this->storage->get_uid()),
|
||||
'%n' => urlencode($this->imap_folder),
|
||||
));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the corresponding kolab_storage_folder instance
|
||||
*/
|
||||
|
@ -214,7 +233,7 @@ class kolab_calendar
|
|||
}
|
||||
|
||||
$events = array();
|
||||
foreach ((array)$this->storage->select($query) as $record) {
|
||||
foreach ($this->storage->select($query) as $record) {
|
||||
$event = $this->_to_rcube_event($record);
|
||||
$this->events[$event['id']] = $event;
|
||||
|
||||
|
@ -248,9 +267,18 @@ class kolab_calendar
|
|||
$add = true;
|
||||
|
||||
// skip the first instance of a recurring event if listed in exdate
|
||||
if ($virtual && !empty($event['recurrence']['EXDATE'])) {
|
||||
if ($virtual && (!empty($event['recurrence']['EXDATE']) || !empty($event['recurrence']['EXCEPTIONS']))) {
|
||||
$event_date = $event['start']->format('Ymd');
|
||||
foreach ($event['recurrence']['EXDATE'] as $exdate) {
|
||||
$exdates = (array)$event['recurrence']['EXDATE'];
|
||||
|
||||
// add dates from exceptions to list
|
||||
if (is_array($event['recurrence']['EXCEPTIONS'])) {
|
||||
foreach ($event['recurrence']['EXCEPTIONS'] as $exception) {
|
||||
$exdates[] = clone $exception['start'];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($exdates as $exdate) {
|
||||
if ($exdate->format('Ymd') == $event_date) {
|
||||
$add = false;
|
||||
break;
|
||||
|
@ -298,7 +326,7 @@ class kolab_calendar
|
|||
}
|
||||
else {
|
||||
$event['id'] = $event['uid'];
|
||||
$this->events[$event['uid']] = $this->_to_rcube_event($object);
|
||||
$this->events = array($event['uid'] => $this->_to_rcube_event($object));
|
||||
}
|
||||
|
||||
return $saved;
|
||||
|
@ -318,7 +346,6 @@ class kolab_calendar
|
|||
if (!$old || PEAR::isError($old))
|
||||
return false;
|
||||
|
||||
$old['recurrence'] = ''; # clear old field, could have been removed in new, too
|
||||
$object = $this->_from_rcube_event($event, $old);
|
||||
$saved = $this->storage->save($object, 'event', $event['id']);
|
||||
|
||||
|
@ -560,12 +587,16 @@ class kolab_calendar
|
|||
if (is_array($record['categories']))
|
||||
$record['categories'] = $record['categories'][0];
|
||||
|
||||
// The web client only supports DISPLAY type of alarms
|
||||
if (!empty($record['alarms']))
|
||||
$record['alarms'] = preg_replace('/:[A-Z]+$/', ':DISPLAY', $record['alarms']);
|
||||
|
||||
// remove empty recurrence array
|
||||
if (empty($record['recurrence']))
|
||||
unset($record['recurrence']);
|
||||
|
||||
// remove internals
|
||||
unset($record['_mailbox'], $record['_msguid'], $record['_formatobj'], $record['_attachments']);
|
||||
unset($record['_mailbox'], $record['_msguid'], $record['_formatobj'], $record['_attachments'], $record['x-custom']);
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
@ -616,6 +647,11 @@ class kolab_calendar
|
|||
|
||||
$event['_owner'] = $identity['email'];
|
||||
|
||||
# remove EXDATE values if RDATE is given
|
||||
if (!empty($event['recurrence']['RDATE'])) {
|
||||
$event['recurrence']['EXDATE'] = array();
|
||||
}
|
||||
|
||||
// remove some internal properties which should not be saved
|
||||
unset($event['_savemode'], $event['_fromcalendar'], $event['_identity']);
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ class kolab_driver extends calendar_driver
|
|||
public $freebusy = true;
|
||||
public $attachments = true;
|
||||
public $undelete = true;
|
||||
public $alarm_types = array('DISPLAY');
|
||||
public $alarm_types = array('DISPLAY','AUDIO');
|
||||
public $categoriesimmutable = true;
|
||||
|
||||
private $rc;
|
||||
|
@ -105,30 +105,48 @@ class kolab_driver extends calendar_driver
|
|||
}
|
||||
}
|
||||
|
||||
$calendars = $this->filter_calendars(false, $active, $personal);
|
||||
$names = array();
|
||||
$folders = $this->filter_calendars(false, $active, $personal);
|
||||
$calendars = $names = array();
|
||||
|
||||
foreach ($calendars as $id => $cal) {
|
||||
$name = kolab_storage::folder_displayname($cal->get_name(), $names);
|
||||
// include virtual folders for a full folder tree
|
||||
if (!$active && !$personal && !$this->rc->output->ajax_call && in_array($this->rc->action, array('index','')))
|
||||
$folders = kolab_storage::folder_hierarchy($folders);
|
||||
|
||||
$calendars[$id] = array(
|
||||
'id' => $cal->id,
|
||||
'name' => $name,
|
||||
'editname' => $cal->get_foldername(),
|
||||
'color' => $cal->get_color(),
|
||||
'readonly' => $cal->readonly,
|
||||
'showalarms' => $cal->alarms,
|
||||
'class_name' => $cal->get_namespace(),
|
||||
'default' => $cal->storage->default,
|
||||
'active' => $cal->storage->is_active(),
|
||||
'owner' => $cal->get_owner(),
|
||||
);
|
||||
foreach ($folders as $id => $cal) {
|
||||
$fullname = $cal->get_name();
|
||||
$listname = kolab_storage::folder_displayname($fullname, $names);
|
||||
|
||||
// special handling for virtual folders
|
||||
if ($cal->virtual) {
|
||||
$calendars[$cal->id] = array(
|
||||
'id' => $cal->id,
|
||||
'name' => $fullname,
|
||||
'listname' => $listname,
|
||||
'virtual' => true,
|
||||
);
|
||||
}
|
||||
else {
|
||||
$calendars[$cal->id] = array(
|
||||
'id' => $cal->id,
|
||||
'name' => $fullname,
|
||||
'listname' => $listname,
|
||||
'editname' => $cal->get_foldername(),
|
||||
'color' => $cal->get_color(),
|
||||
'readonly' => $cal->readonly,
|
||||
'showalarms' => $cal->alarms,
|
||||
'class_name' => $cal->get_namespace(),
|
||||
'default' => $cal->storage->default,
|
||||
'active' => $cal->storage->is_active(),
|
||||
'owner' => $cal->get_owner(),
|
||||
'children' => true, // TODO: determine if that folder indeed has child folders
|
||||
'caldavurl' => $cal->get_caldav_url(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $calendars;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get list of calendars according to specified filters
|
||||
*
|
||||
|
@ -448,6 +466,15 @@ class kolab_driver extends calendar_driver
|
|||
if ($master['recurrence']['COUNT'])
|
||||
$master['recurrence']['COUNT']--;
|
||||
}
|
||||
// remove the matching RDATE entry
|
||||
else if ($master['recurrence']['RDATE']) {
|
||||
foreach ($master['recurrence']['RDATE'] as $j => $rdate) {
|
||||
if ($rdate->format('Ymd') == $event['start']->format('Ymd')) {
|
||||
unset($master['recurrence']['RDATE'][$j]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else { // add exception to master event
|
||||
$master['recurrence']['EXDATE'][] = $event['start'];
|
||||
}
|
||||
|
@ -464,8 +491,18 @@ class kolab_driver extends calendar_driver
|
|||
unset($master['recurrence']['COUNT']);
|
||||
|
||||
// if all future instances are deleted, remove recurrence rule entirely (bug #1677)
|
||||
if ($master['recurrence']['UNTIL']->format('Ymd') == $master['start']->format('Ymd'))
|
||||
if ($master['recurrence']['UNTIL']->format('Ymd') == $master['start']->format('Ymd')) {
|
||||
$master['recurrence'] = array();
|
||||
}
|
||||
// remove matching RDATE entries
|
||||
else if ($master['recurrence']['RDATE']) {
|
||||
foreach ($master['recurrence']['RDATE'] as $j => $rdate) {
|
||||
if ($rdate->format('Ymd') == $event['start']->format('Ymd')) {
|
||||
$master['recurrence']['RDATE'] = array_slice($master['recurrence']['RDATE'], 0, $j);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$success = $storage->update_event($master);
|
||||
break;
|
||||
|
@ -622,8 +659,24 @@ class kolab_driver extends calendar_driver
|
|||
}
|
||||
}
|
||||
|
||||
$add_exception = true;
|
||||
|
||||
// adjust matching RDATE entry if dates changed
|
||||
if ($savemode == 'current' && $master['recurrence']['RDATE'] && ($old_date = $old['start']->format('Ymd')) != $event['start']->format('Ymd')) {
|
||||
foreach ($master['recurrence']['RDATE'] as $j => $rdate) {
|
||||
if ($rdate->format('Ymd') == $old_date) {
|
||||
$master['recurrence']['RDATE'][$j] = $event['start'];
|
||||
sort($master['recurrence']['RDATE']);
|
||||
$add_exception = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// save as new exception to master event
|
||||
$master['recurrence']['EXCEPTIONS'][] = $event;
|
||||
if ($add_exception) {
|
||||
$master['recurrence']['EXCEPTIONS'][] = $event;
|
||||
}
|
||||
$success = $storage->update_event($master);
|
||||
break;
|
||||
|
||||
|
@ -662,6 +715,9 @@ class kolab_driver extends calendar_driver
|
|||
$event['end'] = $master['end'];
|
||||
}
|
||||
|
||||
// unset _dateonly flags in (cached) date objects
|
||||
unset($event['start']->_dateonly, $event['end']->_dateonly);
|
||||
|
||||
$success = $storage->update_event($event);
|
||||
break;
|
||||
}
|
||||
|
@ -679,26 +735,31 @@ class kolab_driver extends calendar_driver
|
|||
* @param integer Event's new end (unix timestamp)
|
||||
* @param string Search query (optional)
|
||||
* @param mixed List of calendar IDs to load events from (either as array or comma-separated string)
|
||||
* @param boolean Strip virtual events (optional)
|
||||
* @param boolean Include virtual events (optional)
|
||||
* @param integer Only list events modified since this time (unix timestamp)
|
||||
* @return array A list of event records
|
||||
*/
|
||||
public function load_events($start, $end, $search = null, $calendars = null, $virtual = 1)
|
||||
public function load_events($start, $end, $search = null, $calendars = null, $virtual = 1, $modifiedsince = null)
|
||||
{
|
||||
if ($calendars && is_string($calendars))
|
||||
$calendars = explode(',', $calendars);
|
||||
|
||||
$query = array();
|
||||
if ($modifiedsince)
|
||||
$query[] = array('changed', '>=', $modifiedsince);
|
||||
|
||||
$events = $categories = array();
|
||||
foreach (array_keys($this->calendars) as $cid) {
|
||||
if ($calendars && !in_array($cid, $calendars))
|
||||
continue;
|
||||
|
||||
$events = array_merge($events, $this->calendars[$cid]->list_events($start, $end, $search, $virtual));
|
||||
$events = array_merge($events, $this->calendars[$cid]->list_events($start, $end, $search, $virtual, $query));
|
||||
$categories += $this->calendars[$cid]->categories;
|
||||
}
|
||||
|
||||
// add new categories to user prefs
|
||||
$old_categories = $this->rc->config->get('calendar_categories', $this->default_categories);
|
||||
if ($newcats = array_diff(array_map('strtolower', array_keys($categories)), array_map('strtolower', array_keys($old_categories)))) {
|
||||
if ($newcats = array_udiff(array_keys($categories), array_keys($old_categories), function($a, $b){ return strcasecmp($a, $b); })) {
|
||||
foreach ($newcats as $category)
|
||||
$old_categories[$category] = ''; // no color set yet
|
||||
$this->rc->user->save_prefs(array('calendar_categories' => $old_categories));
|
||||
|
@ -874,8 +935,6 @@ class kolab_driver extends calendar_driver
|
|||
*/
|
||||
public function get_freebusy_list($email, $start, $end)
|
||||
{
|
||||
require_once('HTTP/Request2.php');
|
||||
|
||||
if (empty($email)/* || $end < time()*/)
|
||||
return false;
|
||||
|
||||
|
@ -888,14 +947,11 @@ class kolab_driver extends calendar_driver
|
|||
|
||||
// ask kolab server first
|
||||
try {
|
||||
$rcmail = rcube::get_instance();
|
||||
$request = new HTTP_Request2(kolab_storage::get_freebusy_url($email));
|
||||
$request->setConfig(array(
|
||||
$request_config = array(
|
||||
'store_body' => true,
|
||||
'follow_redirects' => true,
|
||||
'ssl_verify_peer' => $rcmail->config->get('kolab_ssl_verify_peer', true),
|
||||
));
|
||||
|
||||
);
|
||||
$request = libkolab::http_request(kolab_storage::get_freebusy_url($email), 'GET', $request_config);
|
||||
$response = $request->send();
|
||||
|
||||
// authentication required
|
||||
|
@ -935,28 +991,25 @@ class kolab_driver extends calendar_driver
|
|||
|
||||
// parse free-busy information using Horde classes
|
||||
if ($fbdata) {
|
||||
$fbcal = $this->cal->get_ical()->get_parser();
|
||||
$fbcal->parsevCalendar($fbdata);
|
||||
if ($fb = $fbcal->findComponent('vfreebusy')) {
|
||||
$ical = $this->cal->get_ical();
|
||||
$ical->import($fbdata);
|
||||
if ($fb = $ical->freebusy) {
|
||||
$result = array();
|
||||
$params = $fb->getExtraParams();
|
||||
foreach ($fb->getBusyPeriods() as $from => $to) {
|
||||
if ($to == null) // no information, assume free
|
||||
break;
|
||||
$type = $params[$from]['FBTYPE'];
|
||||
$result[] = array($from, $to, isset($fbtypemap[$type]) ? $fbtypemap[$type] : calendar::FREEBUSY_BUSY);
|
||||
foreach ($fb['periods'] as $tuple) {
|
||||
list($from, $to, $type) = $tuple;
|
||||
$result[] = array($from->format('U'), $to->format('U'), isset($fbtypemap[$type]) ? $fbtypemap[$type] : calendar::FREEBUSY_BUSY);
|
||||
}
|
||||
|
||||
// we take 'dummy' free-busy lists as "unknown"
|
||||
if (empty($result) && ($comment = $fb->getAttribute('COMMENT')) && stripos($comment, 'dummy'))
|
||||
if (empty($result) && !empty($fb['comment']) && stripos($fb['comment'], 'dummy'))
|
||||
return false;
|
||||
|
||||
// set period from $start till the begin of the free-busy information as 'unknown'
|
||||
if (($fbstart = $fb->getStart()) && $start < $fbstart) {
|
||||
if ($fb['start'] && ($fbstart = $fb['start']->format('U')) && $start < $fbstart) {
|
||||
array_unshift($result, array($start, $fbstart, calendar::FREEBUSY_UNKNOWN));
|
||||
}
|
||||
// pad period till $end with status 'unknown'
|
||||
if (($fbend = $fb->getEnd()) && $fbend < $end) {
|
||||
if ($fb['end'] && ($fbend = $fb['end']->format('U')) && $fbend < $end) {
|
||||
$result[] = array($fbend, $end, calendar::FREEBUSY_UNKNOWN);
|
||||
}
|
||||
|
||||
|
@ -1039,7 +1092,7 @@ class kolab_driver extends calendar_driver
|
|||
// Disable folder name input
|
||||
if (!empty($options) && ($options['norename'] || $options['protected'])) {
|
||||
$input_name = new html_hiddenfield(array('name' => 'name', 'id' => 'calendar-name'));
|
||||
$formfields['name']['value'] = Q(str_replace($delim, ' » ', kolab_storage::object_name($folder)))
|
||||
$formfields['name']['value'] = kolab_storage::object_name($folder)
|
||||
. $input_name->show($folder);
|
||||
}
|
||||
|
||||
|
|
|
@ -126,6 +126,13 @@ class Horde_Date_Recurrence
|
|||
*/
|
||||
public $recurMonths = array();
|
||||
|
||||
/**
|
||||
* RDATE recurrence values
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $rdates = array();
|
||||
|
||||
/**
|
||||
* All the exceptions from recurrence for this event.
|
||||
*
|
||||
|
@ -427,7 +434,7 @@ class Horde_Date_Recurrence
|
|||
return clone $this->start;
|
||||
}
|
||||
|
||||
if ($this->recurInterval == 0) {
|
||||
if ($this->recurInterval == 0 && empty($this->rdates)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -779,6 +786,19 @@ class Horde_Date_Recurrence
|
|||
return $next;
|
||||
}
|
||||
|
||||
// fall-back to RDATE properties
|
||||
if (!empty($this->rdates)) {
|
||||
$next = clone $this->start;
|
||||
foreach ($this->rdates as $rdate) {
|
||||
$next->year = $rdate->year;
|
||||
$next->month = $rdate->month;
|
||||
$next->mday = $rdate->mday;
|
||||
if ($next->compareDateTime($after) > 0) {
|
||||
return $next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We didn't find anything, the recurType was bad, or something else
|
||||
// went wrong - return false.
|
||||
return false;
|
||||
|
@ -834,6 +854,18 @@ class Horde_Date_Recurrence
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an absolute recurrence date.
|
||||
*
|
||||
* @param integer $year The year of the instance.
|
||||
* @param integer $month The month of the instance.
|
||||
* @param integer $mday The day of the month of the instance.
|
||||
*/
|
||||
public function addRDate($year, $month, $mday)
|
||||
{
|
||||
$this->rdates[] = new Horde_Date($year, $month, $mday);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an exception to a recurring event.
|
||||
*
|
||||
|
|
|
@ -57,8 +57,18 @@ class calendar_recurrence
|
|||
$this->engine->fromRRule20(libcalendaring::to_rrule($event['recurrence']));
|
||||
|
||||
if (is_array($event['recurrence']['EXDATE'])) {
|
||||
foreach ($event['recurrence']['EXDATE'] as $exdate)
|
||||
$this->engine->addException($exdate->format('Y'), $exdate->format('n'), $exdate->format('j'));
|
||||
foreach ($event['recurrence']['EXDATE'] as $exdate) {
|
||||
if (is_a($exdate, 'DateTime')) {
|
||||
$this->engine->addException($exdate->format('Y'), $exdate->format('n'), $exdate->format('j'));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (is_array($event['recurrence']['RDATE'])) {
|
||||
foreach ($event['recurrence']['RDATE'] as $rdate) {
|
||||
if (is_a($rdate, 'DateTime')) {
|
||||
$this->engine->addRDate($rdate->format('Y'), $rdate->format('n'), $rdate->format('j'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -90,6 +90,7 @@ class calendar_ui
|
|||
$this->cal->register_handler('plugin.event_rsvp_buttons', array($this, 'event_rsvp_buttons'));
|
||||
$this->cal->register_handler('plugin.angenda_options', array($this, 'angenda_options'));
|
||||
$this->cal->register_handler('plugin.events_import_form', array($this, 'events_import_form'));
|
||||
$this->cal->register_handler('plugin.events_export_form', array($this, 'events_export_form'));
|
||||
$this->cal->register_handler('plugin.searchform', array($this->rc->output, 'search_form')); // use generic method from rcube_template
|
||||
}
|
||||
|
||||
|
@ -194,20 +195,25 @@ class calendar_ui
|
|||
$prop['attachments'] = $this->cal->driver->attachments;
|
||||
$prop['undelete'] = $this->cal->driver->undelete;
|
||||
$prop['feedurl'] = $this->cal->get_url(array('_cal' => $this->cal->ical_feed_hash($id) . '.ics', 'action' => 'feed'));
|
||||
$jsenv[$id] = $prop;
|
||||
|
||||
if (!$prop['virtual'])
|
||||
$jsenv[$id] = $prop;
|
||||
|
||||
$html_id = html_identifier($id);
|
||||
$class = 'cal-' . asciiwords($id, true);
|
||||
$title = $prop['name'] != $prop['listname'] ? html_entity_decode($prop['name'], ENT_COMPAT, RCMAIL_CHARSET) : '';
|
||||
|
||||
if ($prop['readonly'])
|
||||
if ($prop['virtual'])
|
||||
$class .= ' virtual';
|
||||
else if ($prop['readonly'])
|
||||
$class .= ' readonly';
|
||||
if ($prop['class_name'])
|
||||
$class .= ' '.$prop['class_name'];
|
||||
|
||||
$li .= html::tag('li', array('id' => 'rcmlical' . $html_id, 'class' => $class),
|
||||
html::tag('input', array('type' => 'checkbox', 'name' => '_cal[]', 'value' => $id, 'checked' => $prop['active']), '') .
|
||||
html::span('handle', ' ') .
|
||||
html::span('calname', $prop['name']));
|
||||
($prop['virtual'] ? '' : html::tag('input', array('type' => 'checkbox', 'name' => '_cal[]', 'value' => $id, 'checked' => $prop['active']), '') .
|
||||
html::span('handle', ' ')) .
|
||||
html::span(array('class' => 'calname', 'title' => $title), $prop['listname']));
|
||||
}
|
||||
|
||||
$this->rc->output->set_env('calendars', $jsenv);
|
||||
|
@ -386,6 +392,7 @@ class calendar_ui
|
|||
$select->add($this->cal->gettext('weekly'), 'WEEKLY');
|
||||
$select->add($this->cal->gettext('monthly'), 'MONTHLY');
|
||||
$select->add($this->cal->gettext('yearly'), 'YEARLY');
|
||||
$select->add($this->cal->gettext('rdate'), 'RDATE');
|
||||
$html = html::label('edit-frequency', $this->cal->gettext('frequency')) . $select->show('');
|
||||
break;
|
||||
|
||||
|
@ -474,6 +481,13 @@ class calendar_ui
|
|||
$this->cal->gettext('untildate') . ' ' . $input->show(''));
|
||||
$html = $table->show();
|
||||
break;
|
||||
|
||||
case 'rdate':
|
||||
$ul = html::tag('ul', array('id' => 'edit-recurrence-rdates'), '');
|
||||
$input = new html_inputfield(array('name' => 'rdate', 'id' => 'edit-recurrence-rdate-input', 'size' => "10"));
|
||||
$button = new html_inputfield(array('type' => 'button', 'class' => 'button add', 'value' => $this->cal->gettext('addrdate')));
|
||||
$html .= html::div($attrib, $ul . html::div('inputform', $input->show() . $button->show()));
|
||||
break;
|
||||
}
|
||||
|
||||
return $html;
|
||||
|
@ -537,11 +551,12 @@ class calendar_ui
|
|||
$select->add(array(
|
||||
$this->cal->gettext('onemonthback'),
|
||||
$this->cal->gettext(array('name' => 'nmonthsback', 'vars' => array('nr'=>2))),
|
||||
$this->cal->gettext(array('name' => 'nmonthsback', 'vars' => array('nr'=>3))),
|
||||
$this->cal->gettext(array('name' => 'nmonthsback', 'vars' => array('nr'=>6))),
|
||||
$this->cal->gettext(array('name' => 'nmonthsback', 'vars' => array('nr'=>12))),
|
||||
$this->cal->gettext('all'),
|
||||
),
|
||||
array('1','2','6','12',0));
|
||||
array('1','2','3','6','12',0));
|
||||
|
||||
$html .= html::div('form-section',
|
||||
html::div(null, $input->show()) .
|
||||
|
@ -567,6 +582,53 @@ class calendar_ui
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Form to select options for exporting events
|
||||
*/
|
||||
function events_export_form($attrib = array())
|
||||
{
|
||||
if (!$attrib['id'])
|
||||
$attrib['id'] = 'rcmExportForm';
|
||||
|
||||
$html .= html::div('form-section',
|
||||
html::label('event-export-calendar', $this->cal->gettext('calendar')) .
|
||||
$this->calendar_select(array('name' => 'calendar', 'id' => 'event-export-calendar'))
|
||||
);
|
||||
|
||||
$select = new html_select(array('name' => 'range', 'id' => 'event-export-range'));
|
||||
$select->add(array(
|
||||
$this->cal->gettext('all'),
|
||||
$this->cal->gettext('onemonthback'),
|
||||
$this->cal->gettext(array('name' => 'nmonthsback', 'vars' => array('nr'=>2))),
|
||||
$this->cal->gettext(array('name' => 'nmonthsback', 'vars' => array('nr'=>3))),
|
||||
$this->cal->gettext(array('name' => 'nmonthsback', 'vars' => array('nr'=>6))),
|
||||
$this->cal->gettext(array('name' => 'nmonthsback', 'vars' => array('nr'=>12))),
|
||||
$this->cal->gettext('customdate'),
|
||||
),
|
||||
array(0,'1','2','3','6','12','custom'));
|
||||
|
||||
$startdate = new html_inputfield(array('name' => 'start', 'size' => 11, 'id' => 'event-export-startdate'));
|
||||
|
||||
$html .= html::div('form-section',
|
||||
html::label('event-export-range', $this->cal->gettext('exportrange')) .
|
||||
$select->show(0) .
|
||||
html::span(array('style'=>'display:none'), $startdate->show())
|
||||
);
|
||||
|
||||
$checkbox = new html_checkbox(array('name' => 'attachments', 'id' => 'event-export-attachments', 'value' => 1));
|
||||
$html .= html::div('form-section',
|
||||
html::label('event-export-range', $this->cal->gettext('exportattachments')) .
|
||||
$checkbox->show(1)
|
||||
);
|
||||
|
||||
$this->rc->output->add_gui_object('exportform', $attrib['id']);
|
||||
|
||||
return html::tag('form', array('action' => $this->rc->url(array('task' => 'calendar', 'action' => 'export_events')),
|
||||
'method' => "post", 'id' => $attrib['id']),
|
||||
$html
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the form for event attachments upload
|
||||
*/
|
||||
|
|
|
@ -5913,5 +5913,5 @@ function TableView(element, calendar) {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
})(jQuery);
|
||||
|
|
|
@ -46,6 +46,9 @@ $labels['description'] = 'Beschrieb';
|
|||
$labels['all-day'] = 'ganztägig';
|
||||
$labels['export'] = 'Exportieren';
|
||||
$labels['exporttitle'] = 'Kalender als iCalendar exportieren';
|
||||
$labels['exportrange'] = 'Termine ab';
|
||||
$labels['exportattachments'] = 'Mit Anhängen';
|
||||
$labels['customdate'] = 'Eigenes Datum';
|
||||
$labels['location'] = 'Ort';
|
||||
$labels['date'] = 'Datum';
|
||||
$labels['start'] = 'Beginn';
|
||||
|
@ -76,6 +79,7 @@ $labels['onemonthback'] = '1 Monat zurück';
|
|||
$labels['nmonthsback'] = '$nr Monate zurück';
|
||||
$labels['showurl'] = 'URL anzeigen';
|
||||
$labels['showurldescription'] = 'Über die folgende Adresse können Sie mit einem beliebigen Kalenderprogramm Ihren Kalender abrufen (nur lesend), sofern dieses das iCal-Format unterstützt.';
|
||||
$labels['caldavurldescription'] = 'Benutzen Sie folgende Addresse in einer <a href="http://de.wikipedia.org/wiki/CalDAV" target="_blank">CalDAV</a>-Anwendung (wie z.B. Evolution oder Mozilla Thunderbird) um diesen spezifischen Kalender mit dem Computer oder Mobiltelefon zu synchronisieren.';
|
||||
|
||||
// agenda view
|
||||
$labels['listrange'] = 'Angezeigter Bereich:';
|
||||
|
@ -167,6 +171,7 @@ $labels['tabsharing'] = 'Freigabe';
|
|||
// messages
|
||||
$labels['deleteventconfirm'] = 'Möchten Sie diesen Termin wirklich löschen?';
|
||||
$labels['deletecalendarconfirm'] = 'Möchten Sie diesen Kalender mit allen Terminen wirklich löschen?';
|
||||
$labels['deletecalendarconfirmrecursive'] = 'Möchten Sie diesen Kalender mit allen Terminen und Unter-Kalendern wirklich löschen?';
|
||||
$labels['savingdata'] = 'Speichere Daten...';
|
||||
$labels['errorsaving'] = 'Fehler beim Speichern.';
|
||||
$labels['operationfailed'] = 'Die Aktion ist fehlgeschlagen.';
|
||||
|
@ -197,6 +202,7 @@ $labels['daily'] = 'täglich';
|
|||
$labels['weekly'] = 'wöchentlich';
|
||||
$labels['monthly'] = 'monatlich';
|
||||
$labels['yearly'] = 'jährlich';
|
||||
$labels['rdate'] = 'per Datum';
|
||||
$labels['every'] = 'Alle';
|
||||
$labels['days'] = 'Tag(e)';
|
||||
$labels['weeks'] = 'Woche(n)';
|
||||
|
@ -216,7 +222,7 @@ $labels['third'] = 'dritter';
|
|||
$labels['fourth'] = 'vierter';
|
||||
$labels['last'] = 'letzter';
|
||||
$labels['dayofmonth'] = 'Tag des Montats';
|
||||
|
||||
$labels['addrdate'] = 'Datum hinzufügen';
|
||||
$labels['changeeventconfirm'] = 'Termin ändern';
|
||||
$labels['removeeventconfirm'] = 'Termin löschen';
|
||||
$labels['changerecurringeventwarning'] = 'Dies ist eine Terminreihe. Möchten Sie nur den aktuellen, diesen und alle zukünftigen oder alle Termine bearbeiten oder die Änderungen als neuen Termin speichern?';
|
||||
|
|
|
@ -46,6 +46,9 @@ $labels['description'] = 'Beschreibung';
|
|||
$labels['all-day'] = 'ganztägig';
|
||||
$labels['export'] = 'Exportieren';
|
||||
$labels['exporttitle'] = 'Kalender als iCalendar exportieren';
|
||||
$labels['exportrange'] = 'Termine ab';
|
||||
$labels['exportattachments'] = 'Mit Anhängen';
|
||||
$labels['customdate'] = 'Eigenes Datum';
|
||||
$labels['location'] = 'Ort';
|
||||
$labels['date'] = 'Datum';
|
||||
$labels['start'] = 'Beginn';
|
||||
|
@ -76,6 +79,7 @@ $labels['onemonthback'] = '1 Monat zurück';
|
|||
$labels['nmonthsback'] = '$nr Monate zurück';
|
||||
$labels['showurl'] = 'URL anzeigen';
|
||||
$labels['showurldescription'] = 'Über die folgende Adresse können Sie mit einem beliebigen Kalenderprogramm Ihren Kalender abrufen (nur lesend), sofern dieses das iCal-Format unterstützt.';
|
||||
$labels['caldavurldescription'] = 'Benutzen Sie folgende Addresse in einer <a href="http://de.wikipedia.org/wiki/CalDAV" target="_blank">CalDAV</a>-Anwendung (wie z.B. Evolution oder Mozilla Thunderbird) um diesen spezifischen Kalender mit dem Computer oder Mobiltelefon zu synchronisieren.';
|
||||
|
||||
// agenda view
|
||||
$labels['listrange'] = 'Angezeigter Bereich:';
|
||||
|
@ -167,6 +171,7 @@ $labels['tabsharing'] = 'Freigabe';
|
|||
// messages
|
||||
$labels['deleteventconfirm'] = 'Möchten Sie diesen Termin wirklich löschen?';
|
||||
$labels['deletecalendarconfirm'] = 'Möchten Sie diesen Kalender mit allen Terminen wirklich löschen?';
|
||||
$labels['deletecalendarconfirmrecursive'] = 'Möchten Sie diesen Kalender mit allen Terminen und Unter-Kalendern wirklich löschen?';
|
||||
$labels['savingdata'] = 'Speichere Daten...';
|
||||
$labels['errorsaving'] = 'Fehler beim Speichern.';
|
||||
$labels['operationfailed'] = 'Die Aktion ist fehlgeschlagen.';
|
||||
|
@ -197,6 +202,7 @@ $labels['daily'] = 'täglich';
|
|||
$labels['weekly'] = 'wöchentlich';
|
||||
$labels['monthly'] = 'monatlich';
|
||||
$labels['yearly'] = 'jährlich';
|
||||
$labels['rdate'] = 'per Datum';
|
||||
$labels['every'] = 'Alle';
|
||||
$labels['days'] = 'Tag(e)';
|
||||
$labels['weeks'] = 'Woche(n)';
|
||||
|
@ -216,7 +222,7 @@ $labels['third'] = 'dritter';
|
|||
$labels['fourth'] = 'vierter';
|
||||
$labels['last'] = 'letzter';
|
||||
$labels['dayofmonth'] = 'Tag des Montats';
|
||||
|
||||
$labels['addrdate'] = 'Datum hinzufügen';
|
||||
$labels['changeeventconfirm'] = 'Termin ändern';
|
||||
$labels['removeeventconfirm'] = 'Termin löschen';
|
||||
$labels['changerecurringeventwarning'] = 'Dies ist eine Terminreihe. Möchten Sie nur den aktuellen, diesen und alle zukünftigen oder alle Termine bearbeiten oder die Änderungen als neuen Termin speichern?';
|
||||
|
|
|
@ -46,6 +46,9 @@ $labels['description'] = 'Description';
|
|||
$labels['all-day'] = 'all-day';
|
||||
$labels['export'] = 'Export';
|
||||
$labels['exporttitle'] = 'Export to iCalendar';
|
||||
$labels['exportrange'] = 'Events from';
|
||||
$labels['exportattachments'] = 'With attachments';
|
||||
$labels['customdate'] = 'Custom date';
|
||||
$labels['location'] = 'Location';
|
||||
$labels['url'] = 'URL';
|
||||
$labels['date'] = 'Date';
|
||||
|
@ -77,6 +80,7 @@ $labels['onemonthback'] = '1 month back';
|
|||
$labels['nmonthsback'] = '$nr months back';
|
||||
$labels['showurl'] = 'Show calendar URL';
|
||||
$labels['showurldescription'] = 'Use the following address to access (read only) your calendar from other applications. You can copy and paste this into any calendar software that supports the iCal format.';
|
||||
$labels['caldavurldescription'] = 'Copy this address to a <a href="http://en.wikipedia.org/wiki/CalDAV" target="_blank">CalDAV</a> client application (e.g. Evolution or Mozilla Thunderbird) to fully synchronize this specific calendar with your computer or mobile device.';
|
||||
|
||||
// agenda view
|
||||
$labels['listrange'] = 'Range to display:';
|
||||
|
@ -172,6 +176,7 @@ $labels['tabsharing'] = 'Sharing';
|
|||
// messages
|
||||
$labels['deleteventconfirm'] = 'Do you really want to delete this event?';
|
||||
$labels['deletecalendarconfirm'] = 'Do you really want to delete this calendar with all its events?';
|
||||
$labels['deletecalendarconfirmrecursive'] = 'Do you really want to delete this calendar with all its events and sub-calendars?';
|
||||
$labels['savingdata'] = 'Saving data...';
|
||||
$labels['errorsaving'] = 'Failed to save changes.';
|
||||
$labels['operationfailed'] = 'The requested operation failed.';
|
||||
|
@ -204,6 +209,7 @@ $labels['daily'] = 'daily';
|
|||
$labels['weekly'] = 'weekly';
|
||||
$labels['monthly'] = 'monthly';
|
||||
$labels['yearly'] = 'annually';
|
||||
$labels['rdate'] = 'on dates';
|
||||
$labels['every'] = 'Every';
|
||||
$labels['days'] = 'day(s)';
|
||||
$labels['weeks'] = 'week(s)';
|
||||
|
@ -223,6 +229,7 @@ $labels['third'] = 'third';
|
|||
$labels['fourth'] = 'fourth';
|
||||
$labels['last'] = 'last';
|
||||
$labels['dayofmonth'] = 'Day of month';
|
||||
$labels['addrdate'] = 'Add repeat date';
|
||||
|
||||
$labels['changeeventconfirm'] = 'Change event';
|
||||
$labels['removeeventconfirm'] = 'Remove event';
|
||||
|
|
|
@ -164,7 +164,12 @@ pre {
|
|||
background-position: 0 -92px;
|
||||
}
|
||||
|
||||
#calfeedurl {
|
||||
#calendarslist li.virtual span.calname {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
#calfeedurl,
|
||||
#caldavurl {
|
||||
width: 98%;
|
||||
background: #fbfbfb;
|
||||
padding: 4px;
|
||||
|
@ -248,6 +253,14 @@ pre {
|
|||
background-position: -64px -32px;
|
||||
}
|
||||
|
||||
#calendartoolbar a.import {
|
||||
background-position: -168px 0;
|
||||
}
|
||||
|
||||
#calendartoolbar a.importSel {
|
||||
background-position: -168px -32px;
|
||||
}
|
||||
|
||||
#calendartoolbar a.export {
|
||||
background-position: -128px 0;
|
||||
}
|
||||
|
@ -369,37 +382,44 @@ a.miniColors-trigger {
|
|||
margin: 0.5em 0;
|
||||
}
|
||||
|
||||
#event-attendees span.attendee {
|
||||
.event-attendees span.attendee {
|
||||
padding-right: 18px;
|
||||
margin-right: 0.5em;
|
||||
background: url(images/attendee-status.gif) right 0 no-repeat;
|
||||
}
|
||||
|
||||
#event-attendees span.attendee a.mailtolink {
|
||||
.event-attendees span.attendee a.mailtolink {
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#event-attendees span.attendee a.mailtolink:hover {
|
||||
.event-attendees span.attendee a.mailtolink:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#event-attendees span.accepted {
|
||||
.event-attendees span.accepted {
|
||||
background-position: right -20px;
|
||||
}
|
||||
|
||||
#event-attendees span.declined {
|
||||
.event-attendees span.declined {
|
||||
background-position: right -40px;
|
||||
}
|
||||
|
||||
#event-attendees span.tentative {
|
||||
.event-attendees span.tentative {
|
||||
background-position: right -60px;
|
||||
}
|
||||
|
||||
#event-attendees span.organizer {
|
||||
.event-attendees span.organizer {
|
||||
background-position: right -80px;
|
||||
}
|
||||
|
||||
#all-event-attendees span.attendee {
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
padding-bottom: 3px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
/* jQuery UI overrides */
|
||||
|
||||
#eventshow h1 {
|
||||
|
@ -440,6 +460,12 @@ a.miniColors-trigger {
|
|||
margin-bottom: 0.3em;
|
||||
}
|
||||
|
||||
#eventshow #event-url .event-text {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
#eventedit {
|
||||
position: relative;
|
||||
padding: 0.5em 0.1em;
|
||||
|
@ -517,6 +543,28 @@ td.topalign {
|
|||
margin-left: 7.5em;
|
||||
}
|
||||
|
||||
#edit-recurrence-rdates {
|
||||
display: block;
|
||||
list-style: none;
|
||||
margin: 0 0 0.8em 0;
|
||||
padding: 0;
|
||||
max-height: 300px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#edit-recurrence-rdates li {
|
||||
display: block;
|
||||
position: relative;
|
||||
width: 14em;
|
||||
padding: 1px;
|
||||
}
|
||||
|
||||
#edit-recurrence-rdates li a.delete {
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
#eventedit .recurrence-form {
|
||||
display: none;
|
||||
}
|
||||
|
@ -1139,8 +1187,8 @@ div.fc-event-location {
|
|||
color: #333;
|
||||
}
|
||||
|
||||
.fc-view-table col.fc-event-location {
|
||||
width: 20%;
|
||||
.fc-view-table table.fc-list-smart {
|
||||
table-layout: auto;
|
||||
}
|
||||
|
||||
.fc-listappend {
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 15 KiB |
|
@ -35,7 +35,6 @@
|
|||
<ul>
|
||||
<li><roundcube:button command="calendar-edit" label="calendar.edit" classAct="active" /></li>
|
||||
<li><roundcube:button command="calendar-remove" label="calendar.remove" classAct="active" /></li>
|
||||
<li><roundcube:button command="events-import" label="calendar.importevents" classAct="active" /></li>
|
||||
<li><roundcube:button command="calendar-showurl" label="calendar.showurl" classAct="active" /></li>
|
||||
<roundcube:if condition="env:calendar_driver == 'kolab'" />
|
||||
<li class="separator_above"><roundcube:button command="folders" task="settings" type="link" label="managefolders" classAct="active" /></li>
|
||||
|
@ -63,7 +62,7 @@
|
|||
<h5 class="label"><roundcube:label name="calendar.alarms" /></h5>
|
||||
<div class="event-text"></div>
|
||||
</div>
|
||||
<div class="event-section" id="event-attendees">
|
||||
<div class="event-section event-attendees" id="event-attendees">
|
||||
<h5 class="label"><roundcube:label name="calendar.tabattendees" /></h5>
|
||||
<div class="event-text"></div>
|
||||
</div>
|
||||
|
@ -147,14 +146,23 @@
|
|||
<roundcube:object name="plugin.events_import_form" id="events-import-form" uploadFieldSize="30" />
|
||||
</div>
|
||||
|
||||
<div id="eventsexport" class="uidialog">
|
||||
<roundcube:object name="plugin.events_export_form" id="events-export-form" />
|
||||
</div>
|
||||
|
||||
<div id="calendarurlbox" class="uidialog">
|
||||
<p><roundcube:label name="calendar.showurldescription" /></p>
|
||||
<textarea id="calfeedurl" rows="2" readonly="readonly"></textarea>
|
||||
<div id="calendarcaldavurl" style="display:none">
|
||||
<p><roundcube:label name="calendar.caldavurldescription" html="yes" /></p>
|
||||
<textarea id="caldavurl" rows="2" readonly="readonly"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="calendartoolbar">
|
||||
<roundcube:button command="addevent" type="link" class="buttonPas addevent" classAct="button addevent" classSel="button addeventSel" title="calendar.new_event" content=" " />
|
||||
<roundcube:button command="print" type="link" class="buttonPas print" classAct="button print" classSel="button printSel" title="calendar.print" content=" " />
|
||||
<roundcube:button command="events-import" type="link" class="buttonPas import" classAct="button import" classSel="button importSel" title="calendar.importevents" content=" " />
|
||||
<roundcube:button command="export" type="link" class="buttonPas export" classAct="button export" classSel="button exportSel" title="calendar.export" content=" " />
|
||||
<roundcube:container name="toolbar" id="calendartoolbar" />
|
||||
</div>
|
||||
|
|
|
@ -84,6 +84,9 @@
|
|||
<div class="recurrence-form" id="recurrence-form-until">
|
||||
<roundcube:object name="plugin.recurrence_form" part="until" class="event-section" />
|
||||
</div>
|
||||
<div class="recurrence-form" id="recurrence-form-rdate">
|
||||
<roundcube:object name="plugin.recurrence_form" part="rdate" class="event-section" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- attendees list -->
|
||||
<div id="event-tab-3">
|
||||
|
|
|
@ -18,6 +18,22 @@ body.calendarmain #mainscreen {
|
|||
left: 0;
|
||||
}
|
||||
|
||||
/* overrides for tablets and mobile phones */
|
||||
@media screen and (max-device-width: 1024px){
|
||||
body.calendarmain {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
body.calendarmain #mainscreen {
|
||||
min-width: 1000px !important;
|
||||
min-height: 520px !important;
|
||||
}
|
||||
|
||||
body.calendarmain #header {
|
||||
min-width: 1020px !important;
|
||||
}
|
||||
}
|
||||
|
||||
body.attachmentwin #mainscreen {
|
||||
top: 60px;
|
||||
}
|
||||
|
@ -117,7 +133,6 @@ div.sidebarclosed {
|
|||
left: 266px;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
padding-bottom: 28px;
|
||||
}
|
||||
|
||||
.calendarmain #message.statusbar {
|
||||
|
@ -125,10 +140,10 @@ div.sidebarclosed {
|
|||
border-bottom-color: #ababab;
|
||||
}
|
||||
|
||||
#calendar .timezonedisplay {
|
||||
#timezonedisplay {
|
||||
position: absolute;
|
||||
bottom: 9px;
|
||||
right: 8px;
|
||||
bottom: 5px;
|
||||
right: 12px;
|
||||
font-size: 0.85em;
|
||||
color: #666;
|
||||
}
|
||||
|
@ -157,6 +172,10 @@ pre {
|
|||
position: relative;
|
||||
}
|
||||
|
||||
#calendarslist li.virtual {
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
#calendarslist li label {
|
||||
display: block;
|
||||
}
|
||||
|
@ -225,7 +244,13 @@ pre {
|
|||
background-position: right -92px;
|
||||
}
|
||||
|
||||
#calfeedurl {
|
||||
#calendarslist li.virtual span.calname {
|
||||
color: #aaa;
|
||||
top: 2px;
|
||||
}
|
||||
|
||||
#calfeedurl,
|
||||
#caldavurl {
|
||||
width: 98%;
|
||||
background: #fbfbfb;
|
||||
padding: 4px;
|
||||
|
@ -266,6 +291,7 @@ pre {
|
|||
top: -6px;
|
||||
left: 0;
|
||||
height: 40px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#calendartoolbar a.button {
|
||||
|
@ -277,9 +303,17 @@ pre {
|
|||
}
|
||||
|
||||
#calendartoolbar a.button.export {
|
||||
min-width: 50px;
|
||||
max-width: 55px;
|
||||
background-position: center -40px;
|
||||
}
|
||||
|
||||
#calendartoolbar a.button.import {
|
||||
min-width: 50px;
|
||||
max-width: 55px;
|
||||
background-position: center -440px;
|
||||
}
|
||||
|
||||
#calendartoolbar a.button.print {
|
||||
background-position: center -80px;
|
||||
}
|
||||
|
@ -367,7 +401,7 @@ a.miniColors-trigger {
|
|||
display: block;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
padding: 8px 4px 3px 30px;
|
||||
padding: 4px 4px 3px 30px;
|
||||
text-shadow: 0px 1px 1px #fff;
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
|
@ -419,37 +453,45 @@ a.miniColors-trigger {
|
|||
outline: none;
|
||||
}
|
||||
|
||||
#event-attendees span.attendee {
|
||||
.event-attendees span.attendee {
|
||||
padding-right: 18px;
|
||||
margin-right: 0.5em;
|
||||
background: url(images/attendee-status.gif) right 0 no-repeat;
|
||||
}
|
||||
|
||||
#event-attendees span.attendee a.mailtolink {
|
||||
.event-attendees span.attendee a.mailtolink {
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#event-attendees span.attendee a.mailtolink:hover {
|
||||
.event-attendees span.attendee a.mailtolink:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#event-attendees span.accepted {
|
||||
.event-attendees span.accepted {
|
||||
background-position: right -20px;
|
||||
}
|
||||
|
||||
#event-attendees span.declined {
|
||||
.event-attendees span.declined {
|
||||
background-position: right -40px;
|
||||
}
|
||||
|
||||
#event-attendees span.tentative {
|
||||
.event-attendees span.tentative {
|
||||
background-position: right -60px;
|
||||
}
|
||||
|
||||
#event-attendees span.organizer {
|
||||
.event-attendees span.organizer {
|
||||
background-position: right -80px;
|
||||
}
|
||||
|
||||
#all-event-attendees span.attendee {
|
||||
display: block;
|
||||
margin-bottom: 0.4em;
|
||||
padding-bottom: 0.3em;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.calendarmain .fc-view-table td.fc-list-header,
|
||||
#attendees-freebusy-table h3.boxtitle,
|
||||
#schedule-freebusy-times thead th,
|
||||
|
@ -550,6 +592,12 @@ div.form-section,
|
|||
padding-right: 0.5em;
|
||||
}
|
||||
|
||||
.calendarmain .eventdialog #event-url .event-text {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
#eventedit .formtable td.label {
|
||||
min-width: 6em;
|
||||
}
|
||||
|
@ -575,6 +623,31 @@ td.topalign {
|
|||
display: none;
|
||||
}
|
||||
|
||||
#edit-recurrence-rdates {
|
||||
display: block;
|
||||
list-style: none;
|
||||
margin: 0 0 0.8em 0;
|
||||
padding: 0;
|
||||
max-height: 300px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#edit-recurrence-rdates li {
|
||||
display: block;
|
||||
position: relative;
|
||||
width: 12em;
|
||||
padding: 4px 0 4px 0;
|
||||
}
|
||||
|
||||
#edit-recurrence-rdates li a.delete {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 0;
|
||||
width: 20px;
|
||||
height: 18px;
|
||||
background-position: -7px -337px;
|
||||
}
|
||||
|
||||
#eventedit .formtable td {
|
||||
padding: 0.2em 0;
|
||||
}
|
||||
|
@ -963,7 +1036,7 @@ a.dropdown-link:after {
|
|||
|
||||
#agendaoptions {
|
||||
position: absolute;
|
||||
bottom: 28px;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: auto;
|
||||
|
@ -972,6 +1045,7 @@ a.dropdown-link:after {
|
|||
border: 1px solid #c3c3c3;
|
||||
border-top-color: #ddd;
|
||||
border-bottom-color: #bbb;
|
||||
border-radius: 0 0 4px 4px;
|
||||
background: #ebebeb;
|
||||
background: -moz-linear-gradient(top, #ebebeb 0%, #c6c6c6 100%);
|
||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ebebeb), color-stop(100%,#c6c6c6));
|
||||
|
@ -1011,7 +1085,7 @@ a.dropdown-link:after {
|
|||
.rcube-fc-content {
|
||||
overflow: hidden;
|
||||
border: 0;
|
||||
border-radius: 4px 4px 0 0;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 0 2px #999;
|
||||
-o-box-shadow: 0 0 2px #999;
|
||||
-webkit-box-shadow: 0 0 2px #999;
|
||||
|
@ -1023,7 +1097,7 @@ a.dropdown-link:after {
|
|||
top: 40px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 28px;
|
||||
bottom: 0;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
|
@ -1281,14 +1355,13 @@ div.fc-event-location {
|
|||
.calendarmain .fc-view-table tr.fc-event td {
|
||||
border-color: #ddd;
|
||||
padding: 4px 7px;
|
||||
}
|
||||
|
||||
.calendarmain .fc-view-table col.fc-event-location {
|
||||
width: 20%;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.calendarmain .fc-view-table tr.fc-event td.fc-event-handle {
|
||||
padding: 5px 10px 2px 7px;
|
||||
padding: 5px 0 2px 7px;
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
|
@ -1315,6 +1388,13 @@ div.fc-event-location {
|
|||
box-shadow: inset 0px 0 1px 1px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.calendarmain .fc-view-table col.fc-event-location {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
.fc-view-table table.fc-list-smart {
|
||||
/* table-layout: auto; */
|
||||
}
|
||||
|
||||
.fc-listappend {
|
||||
text-align: center;
|
||||
|
@ -1347,7 +1427,7 @@ fieldset #calendarcategories div {
|
|||
/* Invitation UI in mail */
|
||||
|
||||
#messagemenu li a.calendarlink span.calendar {
|
||||
background-position: 0px -1948px;
|
||||
background-position: 0px -2197px;
|
||||
}
|
||||
|
||||
div.calendar-invitebox {
|
||||
|
@ -1410,6 +1490,7 @@ div.calendar-invitebox .rsvp-status.tentative {
|
|||
|
||||
.calendaritipattend .centerbox {
|
||||
width: 40em;
|
||||
min-height: 7em;
|
||||
margin: 80px auto 0 auto;
|
||||
padding: 10px 10px 10px 90px;
|
||||
background: url(images/invitation.png) 10px 10px no-repeat #fff;
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 5 KiB After Width: | Height: | Size: 8.6 KiB |
|
@ -5,7 +5,7 @@
|
|||
<roundcube:include file="/includes/links.html" />
|
||||
<!--[if lte IE 7]><link rel="stylesheet" type="text/css" href="/this/iehacks.css" /><![endif]-->
|
||||
</head>
|
||||
<body class="calendarmain noscroll">
|
||||
<body class="calendarmain">
|
||||
|
||||
<roundcube:include file="/includes/header.html" />
|
||||
|
||||
|
@ -14,6 +14,7 @@
|
|||
<div id="calendartoolbar" class="toolbar">
|
||||
<roundcube:button command="addevent" type="link" class="button addevent disabled" classAct="button addevent" classSel="button addevent pressed" label="calendar.new_event" title="calendar.new_event" />
|
||||
<roundcube:button command="print" type="link" class="button print disabled" classAct="button print" classSel="button print pressed" label="calendar.print" title="calendar.printtitle" />
|
||||
<roundcube:button command="events-import" type="link" class="button import disabled" classAct="button import" classSel="button import pressed" label="import" title="calendar.importevents" />
|
||||
<roundcube:button command="export" type="link" class="button export disabled" classAct="button export" classSel="button export pressed" label="calendar.export" title="calendar.exporttitle" />
|
||||
<roundcube:container name="toolbar" id="calendartoolbar" />
|
||||
</div>
|
||||
|
@ -40,16 +41,17 @@
|
|||
|
||||
<div id="calendar">
|
||||
<roundcube:object name="plugin.angenda_options" class="boxfooter" id="agendaoptions" />
|
||||
<roundcube:object name="message" id="message" class="statusbar" />
|
||||
<div class="timezonedisplay"><roundcube:var name="env:timezone" /></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="timezonedisplay"><roundcube:var name="env:timezone" /></div>
|
||||
|
||||
<roundcube:object name="message" id="messagestack" />
|
||||
|
||||
<div id="calendaroptionsmenu" class="popupmenu">
|
||||
<ul class="toolbarmenu">
|
||||
<li><roundcube:button command="calendar-edit" label="calendar.edit" classAct="active" /></li>
|
||||
<li><roundcube:button command="calendar-remove" label="calendar.remove" classAct="active" /></li>
|
||||
<li><roundcube:button command="events-import" label="calendar.importevents" classAct="active" /></li>
|
||||
<li><roundcube:button command="calendar-showurl" label="calendar.showurl" classAct="active" /></li>
|
||||
<roundcube:if condition="env:calendar_driver == 'kolab'" />
|
||||
<li class="separator_above"><roundcube:button command="folders" task="settings" type="link" label="managefolders" classAct="active" /></li>
|
||||
|
@ -77,7 +79,7 @@
|
|||
<h5 class="label"><roundcube:label name="calendar.alarms" /></h5>
|
||||
<div class="event-text"></div>
|
||||
</div>
|
||||
<div class="event-section" id="event-attendees">
|
||||
<div class="event-section event-attendees" id="event-attendees">
|
||||
<h5 class="label"><roundcube:label name="calendar.tabattendees" /></h5>
|
||||
<div class="event-text"></div>
|
||||
</div>
|
||||
|
@ -161,9 +163,17 @@
|
|||
<roundcube:object name="plugin.events_import_form" id="events-import-form" uploadFieldSize="30" />
|
||||
</div>
|
||||
|
||||
<div id="eventsexport" class="uidialog">
|
||||
<roundcube:object name="plugin.events_export_form" id="events-export-form" />
|
||||
</div>
|
||||
|
||||
<div id="calendarurlbox" class="uidialog">
|
||||
<p><roundcube:label name="calendar.showurldescription" /></p>
|
||||
<textarea id="calfeedurl" rows="2" readonly="readonly"></textarea>
|
||||
<div id="calendarcaldavurl" style="display:none">
|
||||
<p><roundcube:label name="calendar.caldavurldescription" html="yes" /></p>
|
||||
<textarea id="caldavurl" rows="2" readonly="readonly"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<roundcube:object name="plugin.calendar_css" />
|
||||
|
|
|
@ -81,6 +81,9 @@
|
|||
<div class="recurrence-form" id="recurrence-form-until">
|
||||
<roundcube:object name="plugin.recurrence_form" part="until" class="event-section" />
|
||||
</div>
|
||||
<div class="recurrence-form" id="recurrence-form-rdate">
|
||||
<roundcube:object name="plugin.recurrence_form" part="rdate" class="event-section" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- attendees list -->
|
||||
<div id="event-tab-3">
|
||||
|
|
Loading…
Add table
Reference in a new issue