Adapt to new timezone handling in Roundcube core; provide direct url to subscribe to calendars (using http auth)

This commit is contained in:
Thomas Bruederli 2012-01-04 23:05:34 +01:00
parent 210edef7dc
commit ee77665dd5
9 changed files with 135 additions and 23 deletions

View file

@ -38,6 +38,7 @@ class calendar extends rcube_plugin
public $home; // declare public to be used in other classes
public $urlbase;
public $timezone;
public $timezone_offset;
public $gmt_offset;
public $ical;
@ -96,10 +97,11 @@ class calendar extends rcube_plugin
$this->add_texts('localization/', $this->rc->task == 'calendar' && (!$this->rc->action || $this->rc->action == 'print'));
// set user's timezone
$this->timezone = $this->rc->config->get('timezone');
$this->dst_active = $this->rc->config->get('dst_active');
$this->gmt_offset = ($this->timezone + $this->dst_active) * 3600;
$this->user_timezone = new DateTimeZone($this->timezone ? timezone_name_from_abbr("", $this->gmt_offset, $this->dst_active) : 'GMT');
$this->timezone = new DateTimeZone($this->rc->config->get('timezone', 'GMT'));
$now = new DateTime('now', $this->timezone);
$this->timezone_offset = $now->format('Z') / 3600;
$this->dst_active = $now->format('I');
$this->gmt_offset = $now->getOffset();
require($this->home . '/lib/calendar_ui.php');
$this->ui = new calendar_ui($this);
@ -119,6 +121,9 @@ class calendar extends rcube_plugin
if ($this->rc->action == 'attend' && !empty($_REQUEST['_t'])) {
$this->add_hook('startup', array($this, 'itip_attend_response'));
}
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') {
$this->load_driver();
@ -890,7 +895,7 @@ class calendar extends rcube_plugin
/**
* Construct the ics file for exporting events to iCalendar format;
*/
function export_events()
function export_events($terminate = true)
{
$start = get_input_value('start', RCUBE_INPUT_GET);
$end = get_input_value('end', RCUBE_INPUT_GET);
@ -903,14 +908,73 @@ class calendar extends rcube_plugin
$calname = $calendars[$calid]['name'] ? $calendars[$calid]['name'] : $calid;
$events = $this->driver->load_events($start, $end, null, $calid, 0);
}
else
$events = array();
header("Content-Type: text/calendar");
header("Content-Disposition: inline; filename=".$calname.'.ics');
$this->get_ical()->export($events, '', true);
if ($terminate)
exit;
}
/**
* Handler for iCal feed requests
*/
function ical_feed_export()
{
// process HTTP auth info
if (!empty($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])) {
$_POST['_user'] = $_SERVER['PHP_AUTH_USER']; // used for rcmail::autoselect_host()
$auth = $this->rc->plugins->exec_hook('authenticate', array(
'host' => $this->rc->autoselect_host(),
'user' => trim($_SERVER['PHP_AUTH_USER']),
'pass' => $_SERVER['PHP_AUTH_PW'],
'cookiecheck' => true,
'valid' => true,
));
if ($auth['valid'] && !$auth['abort'])
$this->rc->login($auth['user'], $auth['pass'], $auth['host']);
}
// require HTTP auth
if (empty($_SESSION['user_id'])) {
header('WWW-Authenticate: Basic realm="Roundcube Calendar"');
header('HTTP/1.0 401 Unauthorized');
exit;
}
// decode calendar feed hash
$format = 'ics';
$calhash = get_input_value('_cal', RCUBE_INPUT_GET);
if (preg_match(($suff_regex = '/\.([a-z0-9]{3,5})$/i'), $calhash, $m)) {
$format = strtolower($m[1]);
$calhash = preg_replace($suff_regex, '', $calhash);
}
if (!strpos($calhash, ':'))
$calhash = base64_decode($calhash);
list($user, $_GET['source']) = explode(':', $calhash, 2);
// sanity check user
if ($this->rc->user->get_username() == $user) {
$this->load_driver();
$this->export_events(false);
}
else {
header('HTTP/1.0 404 Not Found');
}
// don't save session data
session_destroy();
exit;
}
/**
*
*/
@ -939,7 +1003,7 @@ class calendar extends rcube_plugin
$settings['agenda_sections'] = $this->rc->config->get('calendar_agenda_sections', $this->defaults['calendar_agenda_sections']);
$settings['event_coloring'] = (int)$this->rc->config->get('calendar_event_coloring', $this->defaults['calendar_event_coloring']);
$settings['time_indicator'] = (int)$this->rc->config->get('calendar_time_indicator', $this->defaults['calendar_time_indicator']);
$settings['timezone'] = $this->timezone;
$settings['timezone'] = $this->timezone_offset;
$settings['dst'] = $this->dst_active;
// localization
@ -1661,8 +1725,8 @@ class calendar extends rcube_plugin
}
// add timezone information
if ($tzinfo && ($tzname = $this->user_timezone->getName())) {
$fromto .= ' (' . $tzname . ')';
if ($tzinfo && ($tzname = $this->timezone->getName())) {
$fromto .= ' (' . strtr($tzname, '_', ' ') . ')';
}
return $fromto;
@ -2311,4 +2375,10 @@ class calendar extends rcube_plugin
return $url;
}
public function ical_feed_hash($source)
{
return base64_encode($this->rc->user->get_username() . ':' . $source);
}
}

View file

@ -1953,6 +1953,26 @@ function rcube_calendar_ui(settings)
this.refresh(p);
};
// show URL of the given calendar in a dialog box
this.showurl = function(calendar)
{
var $dialog = $('#calendarurlbox').dialog('close');
if (calendar.feedurl) {
$dialog.dialog({
resizable: true,
closeOnEscape: true,
title: rcmail.gettext('showurl', 'calendar'),
close: function() {
$dialog.dialog("destroy").hide();
},
width: 520
}).show();
$('#calfeedurl').val(calendar.feedurl).select();
}
};
// refresh the calendar view after saving event data
this.refresh = function(p)
{
@ -2183,7 +2203,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', !me.calendars[id].readonly);
rcmail.enable_command('calendar-remove', 'events-import', 'calendar-showurl', !me.calendars[id].readonly);
me.selected_calendar = id;
})
.dblclick(function(){ me.calendar_edit_dialog(me.calendars[me.selected_calendar]); })
@ -2673,6 +2693,7 @@ window.rcmail && rcmail.addEventListener('init', function(evt) {
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('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);

View file

@ -40,8 +40,7 @@ class calendar_ical
private $rc;
private $cal;
private $timezone = 'Z';
public $method;
public $events = array();
@ -49,12 +48,6 @@ class calendar_ical
{
$this->cal = $cal;
$this->rc = $cal->rc;
// compose timezone string
if ($cal->timezone) {
$hours = floor($cal->timezone + $cal->dst_active);
$this->timezone = sprintf('%s%02d:%02d', ($hours >= 0 ? '+' : ''), $hours, ($cal->timezone - $hours) * 60);
}
}
/**
@ -313,7 +306,12 @@ class calendar_ical
private function _date2time($prop)
{
// create timestamp at 12:00 in user's timezone
return is_array($prop) ? strtotime(sprintf('%04d%02d%02dT120000%s', $prop['year'], $prop['month'], $prop['mday'], $this->timezone)) : $prop;
if (is_array($prop)) {
$date = new DateTime(sprintf('%04d%02d%02dT120000', $prop['year'], $prop['month'], $prop['mday']), $this->cal->timezone);
console($prop, $date->format('r'));
return $date->getTimestamp();
}
return $prop;
}

View file

@ -48,11 +48,11 @@ class calendar_ui
// add taskbar button
$this->cal->add_button(array(
'name' => 'calendar',
'command' => 'calendar',
'class' => 'button-calendar',
'classsel' => 'button-calendar button-selected',
'innerclass' => 'button-inner',
'label' => 'calendar.calendar',
'href' => './?_task=calendar',
'onclick' => sprintf("%s.command('plugin.calendar');return false", JS_OBJECT_NAME),
), 'taskbar');
// load basic client script (which - unfortunately - requires fullcalendar)
@ -191,6 +191,7 @@ class calendar_ui
$prop['freebusy'] = $this->cal->driver->freebusy;
$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;
$html_id = html_identifier($id);

View file

@ -72,6 +72,8 @@ $labels['importevents'] = 'Termine importieren';
$labels['importrange'] = 'Termine ab';
$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 auf Ihren Kalender zugreifen, sofern dieses das iCal-Format unterstützt.';
// agenda view
$labels['listrange'] = 'Angezeigter Bereich:';

View file

@ -72,6 +72,8 @@ $labels['importevents'] = 'Termine importieren';
$labels['importrange'] = 'Termine ab';
$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 auf Ihren Kalender zugreifen, sofern dieses das iCal-Format unterstützt.';
// agenda view
$labels['listrange'] = 'Angezeigter Bereich:';

View file

@ -72,6 +72,8 @@ $labels['importevents'] = 'Import events';
$labels['importrange'] = 'Events from';
$labels['onemonthback'] = '1 month back';
$labels['nmonthsback'] = '$nr months back';
$labels['showurl'] = 'Show calendar URL';
$labels['showurldescription'] = 'Use the following address to access your calendar from other applications. You can copy and paste this into any calendar software that supports the iCal format.';
// agenda view
$labels['listrange'] = 'Range to display:';

View file

@ -162,6 +162,14 @@ pre {
background-position: 0 -92px;
}
#calfeedurl {
width: 98%;
background: #fbfbfb;
padding: 4px;
margin-bottom: 1em;
resize: none;
}
#agendalist {
width: 100%;
margin: 0 auto;
@ -1164,7 +1172,9 @@ fieldset #calendarcategories div {
/* Invitation UI in mail */
#messagemenu li a.calendarlink {
background: url(images/calendars.png) 7px -109px no-repeat;
background-image: url(images/calendars.png);
background-position: 7px -109px;
background-repeat: no-repeat;
}
div.calendar-invitebox {

View file

@ -36,6 +36,7 @@
<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>
<roundcube:endif />
@ -142,6 +143,11 @@
<roundcube:object name="plugin.events_import_form" id="events-import-form" uploadFieldSize="30" />
</div>
<div id="calendarurlbox" class="uidialog">
<p><roundcube:label name="calendar.showurldescription" /></p>
<textarea id="calfeedurl" rows="2" readonly="readonly"></textarea>
</div>
<div id="alarm-snooze-dropdown" class="popupmenu">
<roundcube:object name="plugin.snooze_select" type="ul" />
</div>