From ee77665dd507fdcc444b474b6a719ed54d20a08d Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Wed, 4 Jan 2012 23:05:34 +0100 Subject: [PATCH] Adapt to new timezone handling in Roundcube core; provide direct url to subscribe to calendars (using http auth) --- plugins/calendar/calendar.php | 88 +++++++++++++++++-- plugins/calendar/calendar_ui.js | 23 ++++- plugins/calendar/lib/calendar_ical.php | 16 ++-- plugins/calendar/lib/calendar_ui.php | 7 +- plugins/calendar/localization/de_CH.inc | 2 + plugins/calendar/localization/de_DE.inc | 2 + plugins/calendar/localization/en_US.inc | 2 + plugins/calendar/skins/default/calendar.css | 12 ++- .../skins/default/templates/calendar.html | 6 ++ 9 files changed, 135 insertions(+), 23 deletions(-) diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php index 39496ca6..56247e6c 100644 --- a/plugins/calendar/calendar.php +++ b/plugins/calendar/calendar.php @@ -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); + } + } diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js index 31481d32..cd1a5d95 100644 --- a/plugins/calendar/calendar_ui.js +++ b/plugins/calendar/calendar_ui.js @@ -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); diff --git a/plugins/calendar/lib/calendar_ical.php b/plugins/calendar/lib/calendar_ical.php index 9a536722..c99fab35 100644 --- a/plugins/calendar/lib/calendar_ical.php +++ b/plugins/calendar/lib/calendar_ical.php @@ -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; } diff --git a/plugins/calendar/lib/calendar_ui.php b/plugins/calendar/lib/calendar_ui.php index a8c89800..d009ef2b 100644 --- a/plugins/calendar/lib/calendar_ui.php +++ b/plugins/calendar/lib/calendar_ui.php @@ -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); diff --git a/plugins/calendar/localization/de_CH.inc b/plugins/calendar/localization/de_CH.inc index 34f89099..266b37bf 100644 --- a/plugins/calendar/localization/de_CH.inc +++ b/plugins/calendar/localization/de_CH.inc @@ -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:'; diff --git a/plugins/calendar/localization/de_DE.inc b/plugins/calendar/localization/de_DE.inc index 9a16e87d..91b7ef5b 100644 --- a/plugins/calendar/localization/de_DE.inc +++ b/plugins/calendar/localization/de_DE.inc @@ -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:'; diff --git a/plugins/calendar/localization/en_US.inc b/plugins/calendar/localization/en_US.inc index 8050bb8b..664eecfb 100644 --- a/plugins/calendar/localization/en_US.inc +++ b/plugins/calendar/localization/en_US.inc @@ -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:'; diff --git a/plugins/calendar/skins/default/calendar.css b/plugins/calendar/skins/default/calendar.css index aa1d5507..51984834 100644 --- a/plugins/calendar/skins/default/calendar.css +++ b/plugins/calendar/skins/default/calendar.css @@ -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 { diff --git a/plugins/calendar/skins/default/templates/calendar.html b/plugins/calendar/skins/default/templates/calendar.html index 6253ab7c..e6e12711 100644 --- a/plugins/calendar/skins/default/templates/calendar.html +++ b/plugins/calendar/skins/default/templates/calendar.html @@ -36,6 +36,7 @@
  • +
  • @@ -142,6 +143,11 @@ +
    +

    + +
    +