6 commits - plugins/calendar plugins/libcalendaring plugins/tasklist
Thomas Brüderli
bruederli at kolabsys.com
Thu Aug 16 21:26:27 CEST 2012
plugins/calendar/calendar.php | 650 --------
plugins/calendar/calendar_base.js | 164 --
plugins/calendar/calendar_ui.js | 112 -
plugins/calendar/drivers/database/database_driver.php | 4
plugins/calendar/drivers/kolab/kolab_driver.php | 2
plugins/calendar/lib/calendar_ical.php | 6
plugins/calendar/lib/calendar_itip.php | 2
plugins/calendar/lib/calendar_recurrence.php | 2
plugins/calendar/lib/calendar_ui.php | 83 -
plugins/calendar/localization/de_CH.inc | 23
plugins/calendar/localization/de_DE.inc | 23
plugins/calendar/localization/en_US.inc | 23
plugins/calendar/localization/pl_PL.inc | 22
plugins/calendar/localization/ru_RU.inc | 22
plugins/calendar/package.xml | 9
plugins/calendar/skins/classic/templates/calendar.html | 4
plugins/calendar/skins/larry/calendar.css | 49
plugins/calendar/skins/larry/templates/attachment.html | 9
plugins/calendar/skins/larry/templates/calendar.html | 4
plugins/libcalendaring/libcalendaring.js | 443 +++++
plugins/libcalendaring/libcalendaring.php | 788 ++++++++++
plugins/libcalendaring/localization/de_CH.inc | 29
plugins/libcalendaring/localization/de_DE.inc | 29
plugins/libcalendaring/localization/en_US.inc | 30
plugins/libcalendaring/localization/pl_PL.inc | 28
plugins/libcalendaring/localization/ru_RU.inc | 29
plugins/libcalendaring/skins/larry/libcal.css | 54
plugins/tasklist/drivers/database/tasklist_database_driver.php | 19
plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php | 16
plugins/tasklist/localization/de_CH.inc | 1
plugins/tasklist/localization/en_US.inc | 1
plugins/tasklist/skins/larry/tasklist.css | 14
plugins/tasklist/skins/larry/templates/attachment.html | 9
plugins/tasklist/skins/larry/templates/mainview.html | 2
plugins/tasklist/skins/larry/templates/taskedit.html | 2
plugins/tasklist/tasklist.js | 166 --
plugins/tasklist/tasklist.php | 252 ---
plugins/tasklist/tasklist_base.js | 2
plugins/tasklist/tasklist_ui.php | 66
39 files changed, 1627 insertions(+), 1566 deletions(-)
New commits:
commit 47857456ebf4d4bf4d9979028d14c570305af346
Author: Thomas Bruederli <thomas at roundcube.net>
Date: Thu Aug 16 21:26:37 2012 +0200
Move alarm translations to libcalendaring
diff --git a/plugins/calendar/localization/ru_RU.inc b/plugins/calendar/localization/ru_RU.inc
index 862c4ee..978462e 100644
--- a/plugins/calendar/localization/ru_RU.inc
+++ b/plugins/calendar/localization/ru_RU.inc
@@ -107,30 +107,8 @@ $labels['futureevents'] = 'ÐÑдÑÑÑее';
// alarm/reminder settings
$labels['showalarms'] = 'ÐоказаÑÑ Ñведомление';
-$labels['alarmemail'] = 'ÐоÑлаÑÑ e-mail';
-$labels['alarmdisplay'] = 'ÐоказаÑÑ ÑообÑение';
-$labels['alarmdisplayoption'] = 'СообÑение';
-$labels['alarmemailoption'] = 'Email';
-$labels['alarmat'] = 'на $datetime';
-$labels['trigger@'] = 'на даÑÑ';
-$labels['trigger-M'] = 'минÑÑ Ð´Ð¾';
-$labels['trigger-H'] = 'ÑаÑов до';
-$labels['trigger-D'] = 'дней до';
-$labels['trigger+M'] = 'минÑÑ Ð¿Ð¾Ñле';
-$labels['trigger+H'] = 'ÑаÑов поÑле';
-$labels['trigger+D'] = 'дней поÑле';
-$labels['addalarm'] = 'добавиÑÑ Ñведомление';
$labels['defaultalarmtype'] = 'ÐаÑÑÑойки Ð½Ð°Ð¿Ð¾Ð¼Ð¸Ð½Ð°Ð½Ð¸Ñ Ð¿Ð¾ ÑмолÑаниÑ';
$labels['defaultalarmoffset'] = 'ÐÑÐµÐ¼Ñ Ð½Ð°Ð¿Ð¾Ð¼Ð¸Ð½Ð°Ð½Ð¸Ñ Ð¿Ð¾ ÑмолÑаниÑ';
-$labels['dismissall'] = 'ÐÑмениÑÑ Ð²Ñе';
-$labels['dismiss'] = 'ÐÑмениÑÑ';
-$labels['snooze'] = 'ÐÑложиÑÑ';
-$labels['repeatinmin'] = 'ÐовÑоÑиÑÑ ÑеÑез $min minutes';
-$labels['repeatinhr'] = 'ÐовÑоÑиÑÑ ÑеÑез 1 ÑаÑ';
-$labels['repeatinhrs'] = 'ÐовÑоÑиÑÑ ÑеÑез $hrs ÑаÑов';
-$labels['repeattomorrow'] = 'ÐовÑоÑиÑÑ Ð·Ð°Ð²ÑÑа';
-$labels['repeatinweek'] = 'ÐовÑоÑиÑÑ ÑеÑез неделÑ';
-$labels['alarmtitle'] = 'ÐÑедÑÑоÑÑие ÑобÑÑиÑ';
// attendees
$labels['attendee'] = 'УÑаÑÑник';
diff --git a/plugins/libcalendaring/localization/en_US.inc b/plugins/libcalendaring/localization/en_US.inc
index 4a4eee2..702aac5 100644
--- a/plugins/libcalendaring/localization/en_US.inc
+++ b/plugins/libcalendaring/localization/en_US.inc
@@ -15,6 +15,7 @@ $labels['trigger+M'] = 'minutes after';
$labels['trigger+H'] = 'hours after';
$labels['trigger+D'] = 'days after';
$labels['addalarm'] = 'add alarm';
+
$labels['alarmtitle'] = 'Upcoming events';
$labels['dismissall'] = 'Dismiss all';
$labels['dismiss'] = 'Dismiss';
diff --git a/plugins/libcalendaring/localization/ru_RU.inc b/plugins/libcalendaring/localization/ru_RU.inc
new file mode 100644
index 0000000..0057ebb
--- /dev/null
+++ b/plugins/libcalendaring/localization/ru_RU.inc
@@ -0,0 +1,29 @@
+<?php
+
+$labels = array();
+
+$labels['alarmemail'] = 'ÐоÑлаÑÑ e-mail';
+$labels['alarmdisplay'] = 'ÐоказаÑÑ ÑообÑение';
+$labels['alarmdisplayoption'] = 'СообÑение';
+$labels['alarmemailoption'] = 'Email';
+$labels['alarmat'] = 'на $datetime';
+$labels['trigger@'] = 'на даÑÑ';
+$labels['trigger-M'] = 'минÑÑ Ð´Ð¾';
+$labels['trigger-H'] = 'ÑаÑов до';
+$labels['trigger-D'] = 'дней до';
+$labels['trigger+M'] = 'минÑÑ Ð¿Ð¾Ñле';
+$labels['trigger+H'] = 'ÑаÑов поÑле';
+$labels['trigger+D'] = 'дней поÑле';
+$labels['addalarm'] = 'добавиÑÑ Ñведомление';
+
+$labels['alarmtitle'] = 'ÐÑедÑÑоÑÑие ÑобÑÑиÑ';
+$labels['dismissall'] = 'ÐÑмениÑÑ Ð²Ñе';
+$labels['dismiss'] = 'ÐÑмениÑÑ';
+$labels['snooze'] = 'ÐÑложиÑÑ';
+$labels['repeatinmin'] = 'ÐовÑоÑиÑÑ ÑеÑез $min minutes';
+$labels['repeatinhr'] = 'ÐовÑоÑиÑÑ ÑеÑез 1 ÑаÑ';
+$labels['repeatinhrs'] = 'ÐовÑоÑиÑÑ ÑеÑез $hrs ÑаÑов';
+$labels['repeattomorrow'] = 'ÐовÑоÑиÑÑ Ð·Ð°Ð²ÑÑа';
+$labels['repeatinweek'] = 'ÐовÑоÑиÑÑ ÑеÑез неделÑ';
+
+$labels['showmore'] = 'ÐоказаÑÑ Ð±Ð¾Ð»ÑÑе...';
commit d3a3885e6dabb88a02a9fb890e3f182d2c0131f4
Merge: 758f7a1 4a8a320
Author: Thomas Bruederli <thomas at roundcube.net>
Date: Thu Aug 16 21:22:28 2012 +0200
Merge branch 'master' of ssh://git.kolabsys.com/git/roundcube
commit 758f7a120f8ca402f95c4c1afc72f8168540005f
Author: Thomas Bruederli <thomas at roundcube.net>
Date: Thu Aug 16 19:25:06 2012 +0200
Register handler for plugin.display_alarms only once; small code cleanup
diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js
index ce993e5..f835f01 100644
--- a/plugins/calendar/calendar_ui.js
+++ b/plugins/calendar/calendar_ui.js
@@ -2625,7 +2625,6 @@ window.rcmail && rcmail.addEventListener('init', function(evt) {
rcmail.register_command('reset-search', function(){ cal.reset_quicksearch(); }, true);
// register callback commands
- rcmail.addEventListener('plugin.display_alarms', function(alarms){ cal.display_alarms(alarms); });
rcmail.addEventListener('plugin.destroy_source', function(p){ cal.calendar_destroy_source(p.id); });
rcmail.addEventListener('plugin.unlock_saving', function(p){ cal.unlock_saving(); });
rcmail.addEventListener('plugin.refresh_calendar', function(p){ cal.refresh(p); });
diff --git a/plugins/libcalendaring/libcalendaring.js b/plugins/libcalendaring/libcalendaring.js
index 0d43384..35897d7 100644
--- a/plugins/libcalendaring/libcalendaring.js
+++ b/plugins/libcalendaring/libcalendaring.js
@@ -122,7 +122,7 @@ function rcube_libcalendaring(settings)
}
// derived from http://delete.me.uk/2005/03/iso8601.html
- var m = s.match(/^([0-9]{4})(-([0-9]{2})(-([0-9]{2})([T ]([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?(Z|(([-+])([0-9]{2})(:?([0-9]{2}))?))?)?)?)?$/);
+ var m = s && s.match(/^([0-9]{4})(-([0-9]{2})(-([0-9]{2})([T ]([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?(Z|(([-+])([0-9]{2})(:?([0-9]{2}))?))?)?)?)?$/);
if (!m) {
return null;
}
@@ -317,7 +317,7 @@ function rcube_libcalendaring(settings)
this.alarm_dialog = $('<div>').attr('id', 'alarm-display');
var actions, adismiss, asnooze, alarm, html, event_ids = [];
- for (var actions, html, alarm, i=0; i < alarms.length; i++) {
+ for (var i=0; i < alarms.length; i++) {
alarm = alarms[i];
alarm.start = parseISO8601(alarm.start);
alarm.end = parseISO8601(alarm.end);
commit d919e2b4bdd5709953fbe575f35de266f81a840f
Author: Thomas Bruederli <thomas at roundcube.net>
Date: Thu Aug 16 19:03:17 2012 +0200
- Move attachment handling functions to libcalendaring module.
- Removed redundant code in calendar and tasklist
- Add more vertical space in attachment view window
- Minor bugfixes
diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php
index b7ad656..9d46086 100644
--- a/plugins/calendar/calendar.php
+++ b/plugins/calendar/calendar.php
@@ -31,7 +31,9 @@ class calendar extends rcube_plugin
const FREEBUSY_BUSY = 2;
const FREEBUSY_TENTATIVE = 3;
const FREEBUSY_OOF = 4;
-
+
+ const SESSION_KEY = 'calendar_temp';
+
public $task = '?(?!logout).*';
public $rc;
public $lib;
@@ -1193,106 +1195,7 @@ class calendar extends rcube_plugin
*/
public function attachment_upload()
{
- // Upload progress update
- if (!empty($_GET['_progress'])) {
- rcube_upload_progress();
- }
-
- $event = get_input_value('_id', RCUBE_INPUT_GPC);
- $uploadid = get_input_value('_uploadid', RCUBE_INPUT_GPC);
-
- $eventid = 'cal:'.$event;
-
- if (!is_array($_SESSION['event_session']) || $_SESSION['event_session']['id'] != $eventid) {
- $_SESSION['event_session'] = array();
- $_SESSION['event_session']['id'] = $eventid;
- $_SESSION['event_session']['attachments'] = array();
- }
-
- // clear all stored output properties (like scripts and env vars)
- $this->rc->output->reset();
-
- if (is_array($_FILES['_attachments']['tmp_name'])) {
- foreach ($_FILES['_attachments']['tmp_name'] as $i => $filepath) {
- // Process uploaded attachment if there is no error
- $err = $_FILES['_attachments']['error'][$i];
-
- if (!$err) {
- $attachment = array(
- 'path' => $filepath,
- 'size' => $_FILES['_attachments']['size'][$i],
- 'name' => $_FILES['_attachments']['name'][$i],
- 'mimetype' => rc_mime_content_type($filepath, $_FILES['_attachments']['name'][$i], $_FILES['_attachments']['type'][$i]),
- 'group' => $eventid,
- );
-
- $attachment = $this->rc->plugins->exec_hook('attachment_upload', $attachment);
- }
-
- if (!$err && $attachment['status'] && !$attachment['abort']) {
- $id = $attachment['id'];
-
- // store new attachment in session
- unset($attachment['status'], $attachment['abort']);
- $_SESSION['event_session']['attachments'][$id] = $attachment;
-
- if (($icon = $_SESSION['calendar_deleteicon']) && is_file($icon)) {
- $button = html::img(array(
- 'src' => $icon,
- 'alt' => rcube_label('delete')
- ));
- }
- else {
- $button = Q(rcube_label('delete'));
- }
-
- $content = html::a(array(
- 'href' => "#delete",
- 'class' => 'delete',
- 'onclick' => sprintf("return %s.remove_from_attachment_list('rcmfile%s')", JS_OBJECT_NAME, $id),
- 'title' => rcube_label('delete'),
- ), $button);
-
- $content .= Q($attachment['name']);
-
- $this->rc->output->command('add2attachment_list', "rcmfile$id", array(
- 'html' => $content,
- 'name' => $attachment['name'],
- 'mimetype' => $attachment['mimetype'],
- 'classname' => rcmail_filetype2classname($attachment['mimetype'], $attachment['name']),
- 'complete' => true), $uploadid);
- }
- else { // upload failed
- if ($err == UPLOAD_ERR_INI_SIZE || $err == UPLOAD_ERR_FORM_SIZE) {
- $msg = rcube_label(array('name' => 'filesizeerror', 'vars' => array(
- 'size' => show_bytes(parse_bytes(ini_get('upload_max_filesize'))))));
- }
- else if ($attachment['error']) {
- $msg = $attachment['error'];
- }
- else {
- $msg = rcube_label('fileuploaderror');
- }
-
- $this->rc->output->command('display_message', $msg, 'error');
- $this->rc->output->command('remove_from_attachment_list', $uploadid);
- }
- }
- }
- else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
- // if filesize exceeds post_max_size then $_FILES array is empty,
- // show filesizeerror instead of fileuploaderror
- if ($maxsize = ini_get('post_max_size'))
- $msg = rcube_label(array('name' => 'filesizeerror', 'vars' => array(
- 'size' => show_bytes(parse_bytes($maxsize)))));
- else
- $msg = rcube_label('fileuploaderror');
-
- $this->rc->output->command('display_message', $msg, 'error');
- $this->rc->output->command('remove_from_attachment_list', $uploadid);
- }
-
- $this->rc->output->send('iframe');
+ $this->lib->attachment_upload(self::SESSION_KEY, 'cal:');
}
/**
@@ -1300,102 +1203,29 @@ class calendar extends rcube_plugin
*/
public function attachment_get()
{
- $event = get_input_value('_event', RCUBE_INPUT_GPC);
- $calendar = get_input_value('_cal', RCUBE_INPUT_GPC);
- $id = get_input_value('_id', RCUBE_INPUT_GPC);
-
- $event = array('id' => $event, 'calendar' => $calendar);
-
// show loading page
if (!empty($_GET['_preload'])) {
- $url = str_replace('&_preload=1', '', $_SERVER['REQUEST_URI']);
- $message = rcube_label('loadingdata');
-
- header('Content-Type: text/html; charset=' . RCMAIL_CHARSET);
- print "<html>\n<head>\n"
- . '<meta http-equiv="refresh" content="0; url='.Q($url).'">' . "\n"
- . '<meta http-equiv="content-type" content="text/html; charset='.RCMAIL_CHARSET.'">' . "\n"
- . "</head>\n<body>\n$message\n</body>\n</html>";
- exit;
+ return $this->lib->attachment_loading_page();
}
- ob_end_clean();
+ $event_id = get_input_value('_event', RCUBE_INPUT_GPC);
+ $calendar = get_input_value('_cal', RCUBE_INPUT_GPC);
+ $id = get_input_value('_id', RCUBE_INPUT_GPC);
- $attachment = $GLOBALS['calendar_attachment'] = $this->driver->get_attachment($id, $event);
+ $event = array('id' => $event_id, 'calendar' => $calendar);
+ $attachment = $this->driver->get_attachment($id, $event);
// show part page
if (!empty($_GET['_frame'])) {
- $this->attachment = $attachment;
- $this->register_handler('plugin.attachmentframe', array($this, 'attachment_frame'));
- $this->register_handler('plugin.attachmentcontrols', array($this->ui, 'attachment_controls'));
- $this->rc->output->send('calendar.attachment');
- exit;
+ $this->lib->attachment = $attachment;
+ $this->register_handler('plugin.attachmentframe', array($this->lib, 'attachment_frame'));
+ $this->register_handler('plugin.attachmentcontrols', array($this->lib, 'attachment_header'));
+ $this->rc->output->send('calendar.attachment');
}
-
- if ($attachment) {
- // allow post-processing of the attachment body
- $part = new rcube_message_part;
- $part->filename = $attachment['name'];
- $part->size = $attachment['size'];
- $part->mimetype = $attachment['mimetype'];
-
- $plugin = $this->rc->plugins->exec_hook('message_part_get', array(
- 'body' => $this->driver->get_attachment_body($id, $event),
- 'mimetype' => strtolower($attachment['mimetype']),
- 'download' => !empty($_GET['_download']),
- 'part' => $part,
- ));
-
- if ($plugin['abort'])
- exit;
-
- $mimetype = $plugin['mimetype'];
- list($ctype_primary, $ctype_secondary) = explode('/', $mimetype);
-
- $browser = $this->rc->output->browser;
-
- // send download headers
- if ($plugin['download']) {
- header("Content-Type: application/octet-stream");
- if ($browser->ie)
- header("Content-Type: application/force-download");
- }
- else if ($ctype_primary == 'text') {
- header("Content-Type: text/$ctype_secondary");
- }
- else {
-// $mimetype = rcmail_fix_mimetype($mimetype);
- header("Content-Type: $mimetype");
- header("Content-Transfer-Encoding: binary");
- }
-
- // display page, @TODO: support text/plain (and maybe some other text formats)
- if ($mimetype == 'text/html' && empty($_GET['_download'])) {
- $OUTPUT = new rcube_html_page();
- // @TODO: use washtml on $body
- $OUTPUT->write($plugin['body']);
- }
- else {
- // don't kill the connection if download takes more than 30 sec.
- @set_time_limit(0);
-
- $filename = $attachment['name'];
- $filename = preg_replace('[\r\n]', '', $filename);
-
- if ($browser->ie && $browser->ver < 7)
- $filename = rawurlencode(abbreviate_string($filename, 55));
- else if ($browser->ie)
- $filename = rawurlencode($filename);
- else
- $filename = addcslashes($filename, '"');
-
- $disposition = !empty($_GET['_download']) ? 'attachment' : 'inline';
- header("Content-Disposition: $disposition; filename=\"$filename\"");
-
- echo $plugin['body'];
- }
-
- exit;
+ // deliver attachment content
+ else if ($attachment) {
+ $attachment['body'] = $this->driver->get_attachment_body($id, $event);
+ $this->lib->attachment_get($attachment);
}
// if we arrive here, the requested part was not found
@@ -1403,20 +1233,6 @@ class calendar extends rcube_plugin
exit;
}
- /**
- * Template object for attachment display frame
- */
- public function attachment_frame($attrib)
- {
- $attachment = $GLOBALS['calendar_attachment'];
-
- $mimetype = strtolower($attachment['mimetype']);
- list($ctype_primary, $ctype_secondary) = explode('/', $mimetype);
-
- $attrib['src'] = './?' . str_replace('_frame=', ($ctype_primary == 'text' ? '_show=' : '_preload='), $_SERVER['QUERY_STRING']);
-
- return html::iframe($attrib);
- }
/**
* Prepares new/edited event properties before save
@@ -1432,9 +1248,9 @@ class calendar extends rcube_plugin
$attachments = array();
$eventid = 'cal:'.$event['id'];
- if (is_array($_SESSION['event_session']) && $_SESSION['event_session']['id'] == $eventid) {
- if (!empty($_SESSION['event_session']['attachments'])) {
- foreach ($_SESSION['event_session']['attachments'] as $id => $attachment) {
+ if (is_array($_SESSION[self::SESSION_KEY]) && $_SESSION[self::SESSION_KEY]['id'] == $eventid) {
+ if (!empty($_SESSION[self::SESSION_KEY]['attachments'])) {
+ foreach ($_SESSION[self::SESSION_KEY]['attachments'] as $id => $attachment) {
if (is_array($event['attachments']) && in_array($id, $event['attachments'])) {
$attachments[$id] = $this->rc->plugins->exec_hook('attachment_get', $attachment);
}
@@ -1474,9 +1290,9 @@ class calendar extends rcube_plugin
private function cleanup_event(&$event)
{
// remove temp. attachment files
- if (!empty($_SESSION['event_session']) && ($eventid = $_SESSION['event_session']['id'])) {
+ if (!empty($_SESSION[self::SESSION_KEY]) && ($eventid = $_SESSION[self::SESSION_KEY]['id'])) {
$this->rc->plugins->exec_hook('attachments_cleanup', array('group' => $eventid));
- $this->rc->session->remove('event_session');
+ $this->rc->session->remove(self::SESSION_KEY);
}
}
@@ -2089,10 +1905,10 @@ class calendar extends rcube_plugin
// copy mail attachments to event
if ($message->attachments) {
$eventid = 'cal:';
- if (!is_array($_SESSION['event_session']) || $_SESSION['event_session']['id'] != $eventid) {
- $_SESSION['event_session'] = array();
- $_SESSION['event_session']['id'] = $eventid;
- $_SESSION['event_session']['attachments'] = array();
+ if (!is_array($_SESSION[self::SESSION_KEY]) || $_SESSION[self::SESSION_KEY]['id'] != $eventid) {
+ $_SESSION[self::SESSION_KEY] = array();
+ $_SESSION[self::SESSION_KEY]['id'] = $eventid;
+ $_SESSION[self::SESSION_KEY]['attachments'] = array();
}
foreach ((array)$message->attachments as $part) {
@@ -2112,7 +1928,7 @@ class calendar extends rcube_plugin
// store new attachment in session
unset($attachment['status'], $attachment['abort'], $attachment['data']);
- $_SESSION['event_session']['attachments'][$id] = $attachment;
+ $_SESSION[self::SESSION_KEY]['attachments'][$id] = $attachment;
$attachment['id'] = 'rcmfile' . $attachment['id']; # add prefix to consider it 'new'
$event['attachments'][] = $attachment;
diff --git a/plugins/calendar/drivers/kolab/kolab_driver.php b/plugins/calendar/drivers/kolab/kolab_driver.php
index bdf2250..21ea4ca 100644
--- a/plugins/calendar/drivers/kolab/kolab_driver.php
+++ b/plugins/calendar/drivers/kolab/kolab_driver.php
@@ -680,7 +680,7 @@ class kolab_driver extends calendar_driver
foreach ($calendar->list_events($time, $time + 86400 * 365, null, 1, $query) as $e) {
// add to list if alarm is set
- $alarm = calendarlibcalendaring::get_next_alarm($e);
+ $alarm = libcalendaring::get_next_alarm($e);
if ($alarm && $alarm['time'] && $alarm['time'] <= $time && $alarm['action'] == 'DISPLAY') {
$id = $e['id'];
$events[$id] = $e;
diff --git a/plugins/calendar/lib/calendar_ui.php b/plugins/calendar/lib/calendar_ui.php
index 34e0dd3..cfa6684 100644
--- a/plugins/calendar/lib/calendar_ui.php
+++ b/plugins/calendar/lib/calendar_ui.php
@@ -595,7 +595,7 @@ class calendar_ui
$skin_path = $this->cal->local_skin_path();
if ($attrib['deleteicon']) {
- $_SESSION['calendar_deleteicon'] = $skin_path . $attrib['deleteicon'];
+ $_SESSION[calendar::SESSION_KEY . '_deleteicon'] = $skin_path . $attrib['deleteicon'];
$this->rc->output->set_env('deleteicon', $skin_path . $attrib['deleteicon']);
}
if ($attrib['cancelicon'])
@@ -608,24 +608,6 @@ class calendar_ui
return html::tag('ul', $attrib, '', html::$common_attrib);
}
- function attachment_controls($attrib = array())
- {
- $table = new html_table(array('cols' => 3));
-
- if (!empty($this->cal->attachment['name'])) {
- $table->add('title', Q(rcube_label('filename')));
- $table->add('header', Q($this->cal->attachment['name']));
- $table->add('download-link', html::a('?'.str_replace('_frame=', '_download=', $_SERVER['QUERY_STRING']), Q(rcube_label('download'))));
- }
-
- if (!empty($this->cal->attachment['size'])) {
- $table->add('title', Q(rcube_label('filesize')));
- $table->add('header', Q(show_bytes($this->cal->attachment['size'])));
- }
-
- return $table->show($attrib);
- }
-
/**
* Handler for calendar form template.
* The form content could be overriden by the driver
diff --git a/plugins/calendar/package.xml b/plugins/calendar/package.xml
index 1dc6625..c422d2a 100644
--- a/plugins/calendar/package.xml
+++ b/plugins/calendar/package.xml
@@ -19,10 +19,10 @@
<email>machniak at kolabsys.com</email>
<active>yes</active>
</developer>
- <date>2011-11-01</date>
+ <date>2012-12-16</date>
<version>
- <release>0.8</release>
- <api>0.8</api>
+ <release>0.8-alpha</release>
+ <api>0.9-alpha</api>
</version>
<stability>
<release>stable</release>
@@ -112,7 +112,6 @@
<tasks:replace from="@package_version@" to="version" type="package-info"/>
</file>
-
<file name="skins/classic/calendar.css" role="data"></file>
<file name="skins/classic/fullcalendar.css" role="data"></file>
<file name="skins/classic/fullcalendar.print.css" role="data"></file>
@@ -178,7 +177,7 @@
<dependencies>
<required>
<php>
- <min>5.2.1</min>
+ <min>5.3.1</min>
</php>
<pearinstaller>
<min>1.7.0</min>
diff --git a/plugins/calendar/skins/larry/calendar.css b/plugins/calendar/skins/larry/calendar.css
index 1a7325e..76ccecb 100644
--- a/plugins/calendar/skins/larry/calendar.css
+++ b/plugins/calendar/skins/larry/calendar.css
@@ -18,6 +18,14 @@ body.calendarmain #mainscreen {
left: 0;
}
+body.attachmentwin #mainscreen {
+ top: 60px;
+}
+
+body.attachmentwin #topnav .topright {
+ margin-top: 20px;
+}
+
#calendarsidebar {
position: absolute;
top: 0;
diff --git a/plugins/calendar/skins/larry/templates/attachment.html b/plugins/calendar/skins/larry/templates/attachment.html
index 4d4789d..9879a10 100644
--- a/plugins/calendar/skins/larry/templates/attachment.html
+++ b/plugins/calendar/skins/larry/templates/attachment.html
@@ -4,19 +4,16 @@
<title><roundcube:object name="pagetitle" /></title>
<roundcube:include file="/includes/links.html" />
</head>
-<body class="extwin">
+<body class="extwin attachmentwin">
<div id="header">
- <div id="topline">
+ <div id="topnav">
+ <roundcube:object name="logo" src="/images/roundcube_logo.png" id="toplogo" border="0" alt="Logo" />
<div class="topright">
<a href="#close" class="closelink" onclick="self.close()"><roundcube:label name="close" /></a>
</div>
</div>
- <div id="topnav">
- <roundcube:object name="logo" src="/images/roundcube_logo.png" id="toplogo" border="0" alt="Logo" />
- </div>
-
<br style="clear:both" />
</div>
diff --git a/plugins/libcalendaring/libcalendaring.php b/plugins/libcalendaring/libcalendaring.php
index c7cf6e8..2d530a3 100644
--- a/plugins/libcalendaring/libcalendaring.php
+++ b/plugins/libcalendaring/libcalendaring.php
@@ -5,8 +5,9 @@
*
* Provides utility functions for calendar-related modules such as
* - alarms display and dismissal
- * - recurrence computation and UI elements
- * - ical parsing and exporting
+ * - attachment handling
+ * - recurrence computation and UI elements (TODO)
+ * - ical parsing and exporting (TODO)
*
* @version @package_version@
* @author Thomas Bruederli <bruederli at kolabsys.com>
@@ -456,6 +457,246 @@ class libcalendaring extends rcube_plugin
return html::tag('ul', $attrib + array('class' => 'toolbarmenu'), join("\n", $items), html::$common_attrib);
}
+
+ /********* Attachments handling *********/
+
+ /**
+ * Handler for attachment uploads
+ */
+ public function attachment_upload($session_key, $id_prefix = '')
+ {
+ // Upload progress update
+ if (!empty($_GET['_progress'])) {
+ rcube_upload_progress();
+ }
+
+ $recid = $id_prefix . get_input_value('_id', RCUBE_INPUT_GPC);
+ $uploadid = get_input_value('_uploadid', RCUBE_INPUT_GPC);
+
+ if (!is_array($_SESSION[$session_key]) || $_SESSION[$session_key]['id'] != $recid) {
+ $_SESSION[$session_key] = array();
+ $_SESSION[$session_key]['id'] = $recid;
+ $_SESSION[$session_key]['attachments'] = array();
+ }
+
+ // clear all stored output properties (like scripts and env vars)
+ $this->rc->output->reset();
+
+ if (is_array($_FILES['_attachments']['tmp_name'])) {
+ foreach ($_FILES['_attachments']['tmp_name'] as $i => $filepath) {
+ // Process uploaded attachment if there is no error
+ $err = $_FILES['_attachments']['error'][$i];
+
+ if (!$err) {
+ $attachment = array(
+ 'path' => $filepath,
+ 'size' => $_FILES['_attachments']['size'][$i],
+ 'name' => $_FILES['_attachments']['name'][$i],
+ 'mimetype' => rc_mime_content_type($filepath, $_FILES['_attachments']['name'][$i], $_FILES['_attachments']['type'][$i]),
+ 'group' => $recid,
+ );
+
+ $attachment = $this->rc->plugins->exec_hook('attachment_upload', $attachment);
+ }
+
+ if (!$err && $attachment['status'] && !$attachment['abort']) {
+ $id = $attachment['id'];
+
+ // store new attachment in session
+ unset($attachment['status'], $attachment['abort']);
+ $_SESSION[$session_key]['attachments'][$id] = $attachment;
+
+ if (($icon = $_SESSION[$session_key . '_deleteicon']) && is_file($icon)) {
+ $button = html::img(array(
+ 'src' => $icon,
+ 'alt' => rcube_label('delete')
+ ));
+ }
+ else {
+ $button = Q(rcube_label('delete'));
+ }
+
+ $content = html::a(array(
+ 'href' => "#delete",
+ 'class' => 'delete',
+ 'onclick' => sprintf("return %s.remove_from_attachment_list('rcmfile%s')", JS_OBJECT_NAME, $id),
+ 'title' => rcube_label('delete'),
+ ), $button);
+
+ $content .= Q($attachment['name']);
+
+ $this->rc->output->command('add2attachment_list', "rcmfile$id", array(
+ 'html' => $content,
+ 'name' => $attachment['name'],
+ 'mimetype' => $attachment['mimetype'],
+ 'classname' => rcmail_filetype2classname($attachment['mimetype'], $attachment['name']),
+ 'complete' => true), $uploadid);
+ }
+ else { // upload failed
+ if ($err == UPLOAD_ERR_INI_SIZE || $err == UPLOAD_ERR_FORM_SIZE) {
+ $msg = rcube_label(array('name' => 'filesizeerror', 'vars' => array(
+ 'size' => show_bytes(parse_bytes(ini_get('upload_max_filesize'))))));
+ }
+ else if ($attachment['error']) {
+ $msg = $attachment['error'];
+ }
+ else {
+ $msg = rcube_label('fileuploaderror');
+ }
+
+ $this->rc->output->command('display_message', $msg, 'error');
+ $this->rc->output->command('remove_from_attachment_list', $uploadid);
+ }
+ }
+ }
+ else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
+ // if filesize exceeds post_max_size then $_FILES array is empty,
+ // show filesizeerror instead of fileuploaderror
+ if ($maxsize = ini_get('post_max_size'))
+ $msg = rcube_label(array('name' => 'filesizeerror', 'vars' => array(
+ 'size' => show_bytes(parse_bytes($maxsize)))));
+ else
+ $msg = rcube_label('fileuploaderror');
+
+ $this->rc->output->command('display_message', $msg, 'error');
+ $this->rc->output->command('remove_from_attachment_list', $uploadid);
+ }
+
+ $this->rc->output->send('iframe');
+ }
+
+
+ /**
+ * Deliver an event/task attachment to the client
+ * (similar as in Roundcube core program/steps/mail/get.inc)
+ */
+ public function attachment_get($attachment)
+ {
+ ob_end_clean();
+
+ if ($attachment && $attachment['body']) {
+ // allow post-processing of the attachment body
+ $part = new rcube_message_part;
+ $part->filename = $attachment['name'];
+ $part->size = $attachment['size'];
+ $part->mimetype = $attachment['mimetype'];
+
+ $plugin = $this->rc->plugins->exec_hook('message_part_get', array(
+ 'body' => $attachment['body'],
+ 'mimetype' => strtolower($attachment['mimetype']),
+ 'download' => !empty($_GET['_download']),
+ 'part' => $part,
+ ));
+
+ if ($plugin['abort'])
+ exit;
+
+ $mimetype = $plugin['mimetype'];
+ list($ctype_primary, $ctype_secondary) = explode('/', $mimetype);
+
+ $browser = $this->rc->output->browser;
+
+ // send download headers
+ if ($plugin['download']) {
+ header("Content-Type: application/octet-stream");
+ if ($browser->ie)
+ header("Content-Type: application/force-download");
+ }
+ else if ($ctype_primary == 'text') {
+ header("Content-Type: text/$ctype_secondary");
+ }
+ else {
+ header("Content-Type: $mimetype");
+ header("Content-Transfer-Encoding: binary");
+ }
+
+ // display page, @TODO: support text/plain (and maybe some other text formats)
+ if ($mimetype == 'text/html' && empty($_GET['_download'])) {
+ $OUTPUT = new rcube_html_page();
+ // @TODO: use washtml on $body
+ $OUTPUT->write($plugin['body']);
+ }
+ else {
+ // don't kill the connection if download takes more than 30 sec.
+ @set_time_limit(0);
+
+ $filename = $attachment['name'];
+ $filename = preg_replace('[\r\n]', '', $filename);
+
+ if ($browser->ie && $browser->ver < 7)
+ $filename = rawurlencode(abbreviate_string($filename, 55));
+ else if ($browser->ie)
+ $filename = rawurlencode($filename);
+ else
+ $filename = addcslashes($filename, '"');
+
+ $disposition = !empty($_GET['_download']) ? 'attachment' : 'inline';
+ header("Content-Disposition: $disposition; filename=\"$filename\"");
+
+ echo $plugin['body'];
+ }
+
+ exit;
+ }
+
+ // if we arrive here, the requested part was not found
+ header('HTTP/1.1 404 Not Found');
+ exit;
+ }
+
+ /**
+ * Show "loading..." page in attachment iframe
+ */
+ public function attachment_loading_page()
+ {
+ $url = str_replace('&_preload=1', '', $_SERVER['REQUEST_URI']);
+ $message = rcube_label('loadingdata');
+
+ header('Content-Type: text/html; charset=' . RCMAIL_CHARSET);
+ print "<html>\n<head>\n"
+ . '<meta http-equiv="refresh" content="0; url='.Q($url).'">' . "\n"
+ . '<meta http-equiv="content-type" content="text/html; charset='.RCMAIL_CHARSET.'">' . "\n"
+ . "</head>\n<body>\n$message\n</body>\n</html>";
+ exit;
+ }
+
+ /**
+ * Template object for attachment display frame
+ */
+ public function attachment_frame($attrib = array())
+ {
+ $mimetype = strtolower($this->attachment['mimetype']);
+ list($ctype_primary, $ctype_secondary) = explode('/', $mimetype);
+
+ $attrib['src'] = './?' . str_replace('_frame=', ($ctype_primary == 'text' ? '_show=' : '_preload='), $_SERVER['QUERY_STRING']);
+
+ return html::iframe($attrib);
+ }
+
+ /**
+ *
+ */
+ public function attachment_header($attrib = array())
+ {
+ $table = new html_table(array('cols' => 3));
+
+ if (!empty($this->attachment['name'])) {
+ $table->add('title', Q(rcube_label('filename')));
+ $table->add('header', Q($this->attachment['name']));
+ $table->add('download-link', html::a('?'.str_replace('_frame=', '_download=', $_SERVER['QUERY_STRING']), Q(rcube_label('download'))));
+ }
+
+ if (!empty($this->attachment['size'])) {
+ $table->add('title', Q(rcube_label('filesize')));
+ $table->add('header', Q(show_bytes($this->attachment['size'])));
+ }
+
+ return $table->show($attrib);
+ }
+
+
+ /********* Static utility functions *********/
+
/**
* Convert the internal structured data into a vcalendar rrule 2.0 string
*/
diff --git a/plugins/tasklist/localization/de_CH.inc b/plugins/tasklist/localization/de_CH.inc
index 038c05b..a20a6f5 100644
--- a/plugins/tasklist/localization/de_CH.inc
+++ b/plugins/tasklist/localization/de_CH.inc
@@ -17,6 +17,7 @@ $labels['title'] = 'Titel';
$labels['description'] = 'Beschreibung';
$labels['datetime'] = 'Datum/Zeit';
$labels['start'] = 'Beginn';
+$labels['alarms'] = 'Erinnerung';
$labels['all'] = 'Alle';
$labels['flagged'] = 'Markiert';
diff --git a/plugins/tasklist/localization/en_US.inc b/plugins/tasklist/localization/en_US.inc
index 35bae10..98a8076 100644
--- a/plugins/tasklist/localization/en_US.inc
+++ b/plugins/tasklist/localization/en_US.inc
@@ -17,6 +17,7 @@ $labels['title'] = 'Title';
$labels['description'] = 'Description';
$labels['datetime'] = 'Date/Time';
$labels['start'] = 'Start';
+$labels['alarms'] = 'Reminder';
$labels['all'] = 'All';
$labels['flagged'] = 'Flagged';
diff --git a/plugins/tasklist/skins/larry/tasklist.css b/plugins/tasklist/skins/larry/tasklist.css
index b502361..8a9bc50 100644
--- a/plugins/tasklist/skins/larry/tasklist.css
+++ b/plugins/tasklist/skins/larry/tasklist.css
@@ -29,6 +29,14 @@ div.uidialog {
display: none;
}
+body.attachmentwin #mainscreen {
+ top: 60px;
+}
+
+body.attachmentwin #topnav .topright {
+ margin-top: 20px;
+}
+
#sidebar {
position: absolute;
top: 0;
diff --git a/plugins/tasklist/skins/larry/templates/attachment.html b/plugins/tasklist/skins/larry/templates/attachment.html
index 4d4789d..9879a10 100644
--- a/plugins/tasklist/skins/larry/templates/attachment.html
+++ b/plugins/tasklist/skins/larry/templates/attachment.html
@@ -4,19 +4,16 @@
<title><roundcube:object name="pagetitle" /></title>
<roundcube:include file="/includes/links.html" />
</head>
-<body class="extwin">
+<body class="extwin attachmentwin">
<div id="header">
- <div id="topline">
+ <div id="topnav">
+ <roundcube:object name="logo" src="/images/roundcube_logo.png" id="toplogo" border="0" alt="Logo" />
<div class="topright">
<a href="#close" class="closelink" onclick="self.close()"><roundcube:label name="close" /></a>
</div>
</div>
- <div id="topnav">
- <roundcube:object name="logo" src="/images/roundcube_logo.png" id="toplogo" border="0" alt="Logo" />
- </div>
-
<br style="clear:both" />
</div>
diff --git a/plugins/tasklist/skins/larry/templates/mainview.html b/plugins/tasklist/skins/larry/templates/mainview.html
index b347a0c..eb98e98 100644
--- a/plugins/tasklist/skins/larry/templates/mainview.html
+++ b/plugins/tasklist/skins/larry/templates/mainview.html
@@ -113,7 +113,7 @@
<span id="task-starttime"></span>
</div>
<div id="task-alarm" class="form-section">
- <label><roundcube:label name="calendar.alarms" /></label>
+ <label><roundcube:label name="tasklist.alarms" /></label>
<span class="task-text"></span>
</div>
<div id="task-list" class="form-section">
diff --git a/plugins/tasklist/skins/larry/templates/taskedit.html b/plugins/tasklist/skins/larry/templates/taskedit.html
index 1773fea..f67d20a 100644
--- a/plugins/tasklist/skins/larry/templates/taskedit.html
+++ b/plugins/tasklist/skins/larry/templates/taskedit.html
@@ -32,7 +32,7 @@
<a href="#nodate" style="margin-left:1em" class="edit-nodate" rel="#taskedit-startdate,#taskedit-starttime"><roundcube:label name="tasklist.nodate" /></a>
</div>
<div class="form-section" id="taskedit-alarms">
- <label for="taskedit-alarm"><roundcube:label name="calendar.alarms" /></label>
+ <label for="taskedit-alarm"><roundcube:label name="tasklist.alarms" /></label>
<roundcube:object name="plugin.alarm_select" />
</div>
<div class="form-section">
diff --git a/plugins/tasklist/tasklist.php b/plugins/tasklist/tasklist.php
index a2647fb..f01e238 100644
--- a/plugins/tasklist/tasklist.php
+++ b/plugins/tasklist/tasklist.php
@@ -33,6 +33,8 @@ class tasklist extends rcube_plugin
const FILTER_MASK_FLAGGED = 64;
const FILTER_MASK_COMPLETE = 128;
+ const SESSION_KEY = 'tasklist_temp';
+
public static $filter_masks = array(
'today' => self::FILTER_MASK_TODAY,
'tomorrow' => self::FILTER_MASK_TOMORROW,
@@ -308,9 +310,9 @@ class tasklist extends rcube_plugin
$attachments = array();
$taskid = $rec['id'];
- if (is_array($_SESSION['tasklist_session']) && $_SESSION['tasklist_session']['id'] == $taskid) {
- if (!empty($_SESSION['tasklist_session']['attachments'])) {
- foreach ($_SESSION['tasklist_session']['attachments'] as $id => $attachment) {
+ if (is_array($_SESSION[self::SESSION_KEY]) && $_SESSION[self::SESSION_KEY]['id'] == $taskid) {
+ if (!empty($_SESSION[self::SESSION_KEY]['attachments'])) {
+ foreach ($_SESSION[self::SESSION_KEY]['attachments'] as $id => $attachment) {
if (is_array($rec['attachments']) && in_array($id, $rec['attachments'])) {
$attachments[$id] = $this->rc->plugins->exec_hook('attachment_get', $attachment);
unset($attachments[$id]['abort'], $attachments[$id]['group']);
@@ -334,9 +336,9 @@ class tasklist extends rcube_plugin
private function cleanup_task(&$rec)
{
// remove temp. attachment files
- if (!empty($_SESSION['tasklist_session']) && ($taskid = $_SESSION['tasklist_session']['id'])) {
+ if (!empty($_SESSION[self::SESSION_KEY]) && ($taskid = $_SESSION[self::SESSION_KEY]['id'])) {
$this->rc->plugins->exec_hook('attachments_cleanup', array('group' => $taskid));
- $this->rc->session->remove('tasklist_session');
+ $this->rc->session->remove(self::SESSION_KEY);
}
}
@@ -656,104 +658,13 @@ class tasklist extends rcube_plugin
/******* Attachment handling *******/
- /*** pretty much the same as in plugins/calendar/calendar.php ***/
/**
* Handler for attachments upload
*/
public function attachment_upload()
{
- // Upload progress update
- if (!empty($_GET['_progress'])) {
- rcube_upload_progress();
- }
-
- $taskid = get_input_value('_id', RCUBE_INPUT_GPC);
- $uploadid = get_input_value('_uploadid', RCUBE_INPUT_GPC);
-
- // prepare session storage
- if (!is_array($_SESSION['tasklist_session']) || $_SESSION['tasklist_session']['id'] != $taskid) {
- $_SESSION['tasklist_session'] = array();
- $_SESSION['tasklist_session']['id'] = $taskid;
- $_SESSION['tasklist_session']['attachments'] = array();
- }
-
- // clear all stored output properties (like scripts and env vars)
- $this->rc->output->reset();
-
- if (is_array($_FILES['_attachments']['tmp_name'])) {
- foreach ($_FILES['_attachments']['tmp_name'] as $i => $filepath) {
- // Process uploaded attachment if there is no error
- $err = $_FILES['_attachments']['error'][$i];
-
- if (!$err) {
- $attachment = array(
- 'path' => $filepath,
- 'size' => $_FILES['_attachments']['size'][$i],
- 'name' => $_FILES['_attachments']['name'][$i],
- 'mimetype' => rc_mime_content_type($filepath, $_FILES['_attachments']['name'][$i], $_FILES['_attachments']['type'][$i]),
- 'group' => $taskid,
- );
-
- $attachment = $this->rc->plugins->exec_hook('attachment_upload', $attachment);
- }
-
- if (!$err && $attachment['status'] && !$attachment['abort']) {
- $id = $attachment['id'];
-
- // store new attachment in session
- unset($attachment['status'], $attachment['abort']);
- $_SESSION['tasklist_session']['attachments'][$id] = $attachment;
-
- $content = html::a(array(
- 'href' => "#delete",
- 'class' => 'delete',
- 'onclick' => sprintf("return %s.remove_from_attachment_list('rcmfile%s')", JS_OBJECT_NAME, $id),
- 'title' => rcube_label('delete'),
- ), Q(rcube_label('delete')));
-
- $content .= Q($attachment['name']);
-
- $this->rc->output->command('add2attachment_list', "rcmfile$id", array(
- 'html' => $content,
- 'name' => $attachment['name'],
- 'mimetype' => $attachment['mimetype'],
- 'classname' => rcmail_filetype2classname($attachment['mimetype'], $attachment['name']),
- 'complete' => true), $uploadid);
- }
- else { // upload failed
- if ($err == UPLOAD_ERR_INI_SIZE || $err == UPLOAD_ERR_FORM_SIZE) {
- $msg = rcube_label(array('name' => 'filesizeerror', 'vars' => array(
- 'size' => show_bytes(parse_bytes(ini_get('upload_max_filesize'))))));
- }
- else if ($attachment['error']) {
- $msg = $attachment['error'];
- }
- else {
- $msg = rcube_label('fileuploaderror');
- }
-
- $this->rc->output->command('display_message', $msg, 'error');
- $this->rc->output->command('remove_from_attachment_list', $uploadid);
- }
- }
- }
- else if ($_SERVER['REQUEST_METHOD'] == 'POST') {
- // if filesize exceeds post_max_size then $_FILES array is empty,
- // show filesizeerror instead of fileuploaderror
- if ($maxsize = ini_get('post_max_size')) {
- $msg = rcube_label(array('name' => 'filesizeerror', 'vars' => array(
- 'size' => show_bytes(parse_bytes($maxsize)))));
- }
- else {
- $msg = rcube_label('fileuploaderror');
- }
-
- $this->rc->output->command('display_message', $msg, 'error');
- $this->rc->output->command('remove_from_attachment_list', $uploadid);
- }
-
- $this->rc->output->send('iframe');
+ $this->lib->attachment_upload(self::SESSION_KEY);
}
/**
@@ -761,100 +672,29 @@ class tasklist extends rcube_plugin
*/
public function attachment_get()
{
- $task = get_input_value('_t', RCUBE_INPUT_GPC);
- $list = get_input_value('_list', RCUBE_INPUT_GPC);
- $id = get_input_value('_id', RCUBE_INPUT_GPC);
-
- $task = array('id' => $task, 'list' => $list);
-
// show loading page
if (!empty($_GET['_preload'])) {
- $url = str_replace('&_preload=1', '', $_SERVER['REQUEST_URI']);
- $message = rcube_label('loadingdata');
-
- header('Content-Type: text/html; charset=' . RCMAIL_CHARSET);
- print "<html>\n<head>\n"
- . '<meta http-equiv="refresh" content="0; url='.Q($url).'">' . "\n"
- . '<meta http-equiv="content-type" content="text/html; charset='.RCMAIL_CHARSET.'">' . "\n"
- . "</head>\n<body>\n$message\n</body>\n</html>";
- exit;
+ return $this->lib->attachment_loading_page();
}
- ob_end_clean();
+ $task = get_input_value('_t', RCUBE_INPUT_GPC);
+ $list = get_input_value('_list', RCUBE_INPUT_GPC);
+ $id = get_input_value('_id', RCUBE_INPUT_GPC);
- $attachment = $this->attachment = $this->driver->get_attachment($id, $task);
+ $task = array('id' => $task, 'list' => $list);
+ $attachment = $this->driver->get_attachment($id, $task);
// show part page
if (!empty($_GET['_frame'])) {
- $this->register_handler('plugin.attachmentframe', array($this, 'attachment_frame'));
- $this->register_handler('plugin.attachmentcontrols', array($this->ui, 'attachment_controls'));
+ $this->lib->attachment = $attachment;
+ $this->register_handler('plugin.attachmentframe', array($this->lib, 'attachment_frame'));
+ $this->register_handler('plugin.attachmentcontrols', array($this->lib, 'attachment_header'));
$this->rc->output->send('tasklist.attachment');
- exit;
}
-
- if ($attachment) {
- // allow post-processing of the attachment body
- $part = new rcube_message_part;
- $part->filename = $attachment['name'];
- $part->size = $attachment['size'];
- $part->mimetype = $attachment['mimetype'];
-
- $plugin = $this->rc->plugins->exec_hook('message_part_get', array(
- 'body' => $this->driver->get_attachment_body($id, $task),
- 'mimetype' => strtolower($attachment['mimetype']),
- 'download' => !empty($_GET['_download']),
- 'part' => $part,
- ));
-
- if ($plugin['abort'])
- exit;
-
- $mimetype = $plugin['mimetype'];
- list($ctype_primary, $ctype_secondary) = explode('/', $mimetype);
-
- $browser = $this->rc->output->browser;
-
- // send download headers
- if ($plugin['download']) {
- header("Content-Type: application/octet-stream");
- if ($browser->ie)
- header("Content-Type: application/force-download");
- }
- else if ($ctype_primary == 'text') {
- header("Content-Type: text/$ctype_secondary");
- }
- else {
- header("Content-Type: $mimetype");
- header("Content-Transfer-Encoding: binary");
- }
-
- // display page, @TODO: support text/plain (and maybe some other text formats)
- if ($mimetype == 'text/html' && empty($_GET['_download'])) {
- $OUTPUT = new rcube_html_page();
- // @TODO: use washtml on $body
- $OUTPUT->write($plugin['body']);
- }
- else {
- // don't kill the connection if download takes more than 30 sec.
- @set_time_limit(0);
-
- $filename = $attachment['name'];
- $filename = preg_replace('[\r\n]', '', $filename);
-
- if ($browser->ie && $browser->ver < 7)
- $filename = rawurlencode(abbreviate_string($filename, 55));
- else if ($browser->ie)
- $filename = rawurlencode($filename);
- else
- $filename = addcslashes($filename, '"');
-
- $disposition = !empty($_GET['_download']) ? 'attachment' : 'inline';
- header("Content-Disposition: $disposition; filename=\"$filename\"");
-
- echo $plugin['body'];
- }
-
- exit;
+ // deliver attachment content
+ else if ($attachment) {
+ $attachment['body'] = $this->driver->get_attachment_body($id, $task);
+ $this->lib->attachment_get($attachment);
}
// if we arrive here, the requested part was not found
@@ -862,21 +702,6 @@ class tasklist extends rcube_plugin
exit;
}
- /**
- * Template object for attachment display frame
- */
- public function attachment_frame($attrib)
- {
- $attachment = $this->attachment;
-
- $mimetype = strtolower($attachment['mimetype']);
- list($ctype_primary, $ctype_secondary) = explode('/', $mimetype);
-
- $attrib['src'] = './?' . str_replace('_frame=', ($ctype_primary == 'text' ? '_show=' : '_preload='), $_SERVER['QUERY_STRING']);
-
- return html::iframe($attrib);
- }
-
/******* Email related function *******/
@@ -900,10 +725,10 @@ class tasklist extends rcube_plugin
// copy mail attachments to task
if ($message->attachments && $this->driver->attachments) {
- if (!is_array($_SESSION['tasklist_session']) || $_SESSION['tasklist_session']['id'] != $task['id']) {
- $_SESSION['tasklist_session'] = array();
- $_SESSION['tasklist_session']['id'] = $task['id'];
- $_SESSION['tasklist_session']['attachments'] = array();
+ if (!is_array($_SESSION[self::SESSION_KEY]) || $_SESSION[self::SESSION_KEY]['id'] != $task['id']) {
+ $_SESSION[self::SESSION_KEY] = array();
+ $_SESSION[self::SESSION_KEY]['id'] = $task['id'];
+ $_SESSION[self::SESSION_KEY]['attachments'] = array();
}
foreach ((array)$message->attachments as $part) {
@@ -923,7 +748,7 @@ class tasklist extends rcube_plugin
// store new attachment in session
unset($attachment['status'], $attachment['abort'], $attachment['data']);
- $_SESSION['tasklist_session']['attachments'][$id] = $attachment;
+ $_SESSION[self::SESSION_KEY]['attachments'][$id] = $attachment;
$attachment['id'] = 'rcmfile' . $attachment['id']; # add prefix to consider it 'new'
$task['attachments'][] = $attachment;
diff --git a/plugins/tasklist/tasklist_ui.php b/plugins/tasklist/tasklist_ui.php
index 5ee6c64..a740670 100644
--- a/plugins/tasklist/tasklist_ui.php
+++ b/plugins/tasklist/tasklist_ui.php
@@ -277,25 +277,4 @@ class tasklist_ui
}
}
- /**
- *
- */
- function attachment_controls($attrib = array())
- {
- $table = new html_table(array('cols' => 3));
-
- if (!empty($this->plugin->attachment['name'])) {
- $table->add('title', Q(rcube_label('filename')));
- $table->add('header', Q($this->plugin->attachment['name']));
- $table->add('download-link', html::a('?'.str_replace('_frame=', '_download=', $_SERVER['QUERY_STRING']), Q(rcube_label('download'))));
- }
-
- if (!empty($this->plugin->attachment['size'])) {
- $table->add('title', Q(rcube_label('filesize')));
- $table->add('header', Q(show_bytes($this->plugin->attachment['size'])));
- }
-
- return $table->show($attrib);
- }
-
}
commit 9b2c7953915dd7c722a74aba947a356fd37baa67
Merge: 9cc400f 87d096e
Author: Thomas Bruederli <thomas at roundcube.net>
Date: Thu Aug 16 09:25:56 2012 +0200
Merge branch 'master' of ssh://git.kolabsys.com/git/roundcube
commit 9cc400f09f928353d6d9125c2edd640232a59d80
Author: Thomas Bruederli <thomas at roundcube.net>
Date: Thu Aug 16 08:57:25 2012 +0200
Refactoring: move common calendaring-related functionality into a new plugin 'libcalendaring'
diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php
index 722e1cb..3906774 100644
--- a/plugins/calendar/calendar.php
+++ b/plugins/calendar/calendar.php
@@ -34,6 +34,7 @@ class calendar extends rcube_plugin
public $task = '?(?!logout).*';
public $rc;
+ public $lib;
public $driver;
public $home; // declare public to be used in other classes
public $urlbase;
@@ -46,29 +47,13 @@ class calendar extends rcube_plugin
public $defaults = array(
'calendar_default_view' => "agendaWeek",
- 'calendar_date_format' => "yyyy-MM-dd",
- 'calendar_date_short' => "M-d",
- 'calendar_date_long' => "MMM d yyyy",
- 'calendar_date_agenda' => "ddd MM-dd",
- 'calendar_time_format' => "HH:mm",
'calendar_timeslots' => 2,
- 'calendar_first_day' => 1,
- 'calendar_first_hour' => 6,
'calendar_work_start' => 6,
'calendar_work_end' => 18,
'calendar_agenda_range' => 60,
'calendar_agenda_sections' => 'smart',
'calendar_event_coloring' => 0,
'calendar_time_indicator' => true,
- 'calendar_date_format_sets' => array(
- 'yyyy-MM-dd' => array('MMM d yyyy', 'M-d', 'ddd MM-dd'),
- 'dd-MM-yyyy' => array('d MMM yyyy', 'd-M', 'ddd dd-MM'),
- 'yyyy/MM/dd' => array('MMM d yyyy', 'M/d', 'ddd MM/dd'),
- 'MM/dd/yyyy' => array('MMM d yyyy', 'M/d', 'ddd MM/dd'),
- 'dd/MM/yyyy' => array('d MMM yyyy', 'd/M', 'ddd dd/MM'),
- 'dd.MM.yyyy' => array('dd. MMM yyyy', 'd.M', 'ddd dd.MM.'),
- 'd.M.yyyy' => array('d. MMM yyyy', 'd.M', 'ddd d.MM.'),
- ),
);
private $default_categories = array(
@@ -86,7 +71,10 @@ class calendar extends rcube_plugin
*/
function init()
{
+ $this->require_plugin('libcalendaring');
+
$this->rc = rcmail::get_instance();
+ $this->lib = libcalendaring::get_instance();
$this->register_task('calendar', 'calendar');
@@ -96,11 +84,9 @@ class calendar extends rcube_plugin
// load localizations
$this->add_texts('localization/', $this->rc->task == 'calendar' && (!$this->rc->action || $this->rc->action == 'print'));
- // set user's timezone
- $this->timezone = new DateTimeZone($this->rc->config->get('timezone', 'GMT'));
- $now = new DateTime('now', $this->timezone);
- $this->gmt_offset = $now->getOffset();
- $this->dst_active = $now->format('I');
+ $this->timezone = $this->lib->timezone;
+ $this->gmt_offset = $this->lib->gmt_offset;
+ $this->dst_active = $this->lib->dst_active;
$this->timezone_offset = $this->gmt_offset / 3600 - $this->dst_active;
require($this->home . '/lib/calendar_ui.php');
@@ -108,8 +94,6 @@ class calendar extends rcube_plugin
// load Calendar user interface which includes jquery-ui
if (!$this->rc->output->ajax_call && !$this->rc->output->env['framed']) {
- $this->require_plugin('jqueryui');
-
$this->ui->init();
// settings are required in (almost) every GUI step
@@ -185,8 +169,9 @@ class calendar extends rcube_plugin
}
}
- // add hook to display alarms
- $this->add_hook('keep_alive', array($this, 'keep_alive'));
+ // add hooks to display alarms
+ $this->add_hook('pending_alarms', array($this, 'pending_alarms'));
+ $this->add_hook('dismiss_alarms', array($this, 'dismiss_alarms'));
}
/**
@@ -358,7 +343,7 @@ class calendar extends rcube_plugin
'content' => $select->show(strval($this->rc->config->get('calendar_first_day', $this->defaults['calendar_first_day']))),
);
- $time_format = $this->rc->config->get('time_format', self::to_php_date_format($this->rc->config->get('calendar_time_format', $this->defaults['calendar_time_format'])));
+ $time_format = $this->rc->config->get('time_format', libcalendaring::to_php_date_format($this->rc->config->get('calendar_time_format', $this->defaults['calendar_time_format'])));
$select_hours = new html_select();
for ($h = 0; $h < 24; $h++)
$select_hours->add(date($time_format, mktime($h, 0, 0)), $h);
@@ -403,7 +388,7 @@ class calendar extends rcube_plugin
'title' => html::label($field_id, Q($this->gettext('defaultalarmtype'))),
'content' => $select_type->show($this->rc->config->get('calendar_default_alarm_type', '')),
);
- $preset = self::parse_alaram_value($this->rc->config->get('calendar_default_alarm_offset', '-15M'));
+ $preset = libcalendaring::parse_alaram_value($this->rc->config->get('calendar_default_alarm_offset', '-15M'));
$p['blocks']['view']['options']['alarmoffset'] = array(
'title' => html::label($field_id . 'value', Q($this->gettext('defaultalarmoffset'))),
'content' => $input_value->show($preset[0]) . ' ' . $select_offset->show($preset[1]),
@@ -831,35 +816,38 @@ class calendar extends rcube_plugin
echo $this->encode($events, !empty($query));
exit;
}
-
+
/**
- * Handler for keep-alive requests
+ * 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
*/
- function keep_alive($attr)
+ public function pending_alarms($p)
{
- $timestamp = time();
$this->load_driver();
- $alarms = (array)$this->driver->pending_alarms($timestamp);
- foreach ($alarms as $i => $alarm) {
- $alarms[$i]['id'] = 'cal:' . $alarm['id']; // prefix ID with cal:
+ if ($alarms = $this->driver->pending_alarms($p['time'] ?: time())) {
+ foreach ($alarms as $i => $alarm) {
+ $alarm['id'] = 'cal:' . $alarm['id']; // prefix ID with cal:
+ $p['alarms'][] = $alarm;
+ }
}
- $plugin = $this->rc->plugins->exec_hook('pending_alarms', array(
- 'time' => $timestamp,
- 'alarms' => $alarms,
- ));
+ return $p;
+ }
- if (!$plugin['abort'] && $plugin['alarms']) {
- // make sure texts and env vars are available on client
- if ($this->rc->task != 'calendar') {
- $this->add_texts('localization/', true);
- $this->rc->output->set_env('snooze_select', $this->ui->snooze_select());
+ /**
+ * Handler for alarm dismiss hook triggered by libcalendaring
+ */
+ public function dismiss_alarms($p)
+ {
+ $this->load_driver();
+ foreach ((array)$p['ids'] as $id) {
+ if (strpos($id, 'cal:') === 0)
+ $p['success'] |= $this->driver->dismiss_alarm(substr($id, 4), $p['snooze']);
}
- $this->rc->output->command('plugin.display_alarms', $this->_alarms_output($plugin['alarms']));
- }
+
+ return $p;
}
-
+
/**
* Handler for check-recent requests which are accidentally sent to calendar taks
*/
@@ -1021,20 +1009,16 @@ class calendar extends rcube_plugin
*/
function load_settings()
{
- $this->date_format_defaults();
+ $this->lib->load_settings();
+ $this->defaults += $this->lib->defaults;
+
$settings = array();
-
+
// configuration
$settings['default_calendar'] = $this->rc->config->get('calendar_default_calendar');
$settings['default_view'] = (string)$this->rc->config->get('calendar_default_view', $this->defaults['calendar_default_view']);
-
- $settings['date_format'] = (string)$this->rc->config->get('calendar_date_format', $this->defaults['calendar_date_format']);
- $settings['time_format'] = (string)$this->rc->config->get('calendar_time_format', $this->defaults['calendar_time_format']);
- $settings['date_short'] = (string)$this->rc->config->get('calendar_date_short', $this->defaults['calendar_date_short']);
- $settings['date_long'] = (string)$this->rc->config->get('calendar_date_long', $this->defaults['calendar_date_long']);
- $settings['dates_long'] = str_replace(' yyyy', '[ yyyy]', $settings['date_long']) . "{ '—' " . $settings['date_long'] . '}';
$settings['date_agenda'] = (string)$this->rc->config->get('calendar_date_agenda', $this->defaults['calendar_date_agenda']);
-
+
$settings['timeslots'] = (int)$this->rc->config->get('calendar_timeslots', $this->defaults['calendar_timeslots']);
$settings['first_day'] = (int)$this->rc->config->get('calendar_first_day', $this->defaults['calendar_first_day']);
$settings['first_hour'] = (int)$this->rc->config->get('calendar_first_hour', $this->defaults['calendar_first_hour']);
@@ -1044,39 +1028,6 @@ 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_offset;
- $settings['dst'] = $this->dst_active;
-
- // localization
- $settings['days'] = array(
- rcube_label('sunday'), rcube_label('monday'),
- rcube_label('tuesday'), rcube_label('wednesday'),
- rcube_label('thursday'), rcube_label('friday'),
- rcube_label('saturday')
- );
- $settings['days_short'] = array(
- rcube_label('sun'), rcube_label('mon'),
- rcube_label('tue'), rcube_label('wed'),
- rcube_label('thu'), rcube_label('fri'),
- rcube_label('sat')
- );
- $settings['months'] = array(
- $this->rc->gettext('longjan'), $this->rc->gettext('longfeb'),
- $this->rc->gettext('longmar'), $this->rc->gettext('longapr'),
- $this->rc->gettext('longmay'), $this->rc->gettext('longjun'),
- $this->rc->gettext('longjul'), $this->rc->gettext('longaug'),
- $this->rc->gettext('longsep'), $this->rc->gettext('longoct'),
- $this->rc->gettext('longnov'), $this->rc->gettext('longdec')
- );
- $settings['months_short'] = array(
- $this->rc->gettext('jan'), $this->rc->gettext('feb'),
- $this->rc->gettext('mar'), $this->rc->gettext('apr'),
- $this->rc->gettext('may'), $this->rc->gettext('jun'),
- $this->rc->gettext('jul'), $this->rc->gettext('aug'),
- $this->rc->gettext('sep'), $this->rc->gettext('oct'),
- $this->rc->gettext('nov'), $this->rc->gettext('dec')
- );
- $settings['today'] = $this->rc->gettext('today');
// get user identity to create default attendee
if ($this->ui->screen == 'calendar') {
@@ -1089,56 +1040,8 @@ class calendar extends rcube_plugin
$settings['identity'] = array('name' => $identity['name'], 'email' => $identity['email'], 'emails' => ';' . join(';', $identity['emails']));
}
- // define list of file types which can be displayed inline
- // same as in program/steps/mail/show.inc
- $mimetypes = $this->rc->config->get('client_mimetypes', 'text/plain,text/html,text/xml,image/jpeg,image/gif,image/png,application/x-javascript,application/pdf,application/x-shockwave-flash');
- $settings['mimetypes'] = is_string($mimetypes) ? explode(',', $mimetypes) : (array)$mimetypes;
-
return $settings;
}
-
- /**
- * Helper function to set date/time format according to config and user preferences
- */
- private function date_format_defaults()
- {
- static $defaults = array();
-
- // nothing to be done
- if (isset($defaults['date_format']))
- return;
-
- $defaults['date_format'] = $this->rc->config->get('calendar_date_format', self::from_php_date_format($this->rc->config->get('date_format')));
- $defaults['time_format'] = $this->rc->config->get('calendar_time_format', self::from_php_date_format($this->rc->config->get('time_format')));
-
- // override defaults
- if ($defaults['date_format'])
- $this->defaults['calendar_date_format'] = $defaults['date_format'];
- if ($defaults['time_format'])
- $this->defaults['calendar_time_format'] = $defaults['time_format'];
-
- // derive format variants from basic date format
- $format_sets = $this->rc->config->get('calendar_date_format_sets', $this->defaults['calendar_date_format_sets']);
- if ($format_set = $format_sets[$this->defaults['calendar_date_format']]) {
- $this->defaults['calendar_date_long'] = $format_set[0];
- $this->defaults['calendar_date_short'] = $format_set[1];
- $this->defaults['calendar_date_agenda'] = $format_set[2];
- }
- }
-
- /**
- * Shift dates into user's current timezone
- */
- private function adjust_timezone($dt)
- {
- if (is_numeric($dt))
- $dt = new DateTime('@'.$td);
- else if (is_string($dt))
- $dt = new DateTime($dt);
-
- $dt->setTimezone($this->timezone);
- return $dt;
- }
/**
* Encode events as JSON
@@ -1163,11 +1066,11 @@ class calendar extends rcube_plugin
{
// compose a human readable strings for alarms_text and recurrence_text
if ($event['alarms'])
- $event['alarms_text'] = self::alarms_text($event['alarms']);
+ $event['alarms_text'] = libcalendaring::alarms_text($event['alarms']);
if ($event['recurrence']) {
$event['recurrence_text'] = $this->_recurrence_text($event['recurrence']);
if ($event['recurrence']['UNTIL'])
- $event['recurrence']['UNTIL'] = $this->adjust_timezone($event['recurrence']['UNTIL'])->format('c');
+ $event['recurrence']['UNTIL'] = $this->lib->adjust_timezone($event['recurrence']['UNTIL'])->format('c');
}
foreach ((array)$event['attachments'] as $k => $attachment) {
@@ -1176,8 +1079,8 @@ class calendar extends rcube_plugin
return array(
'_id' => $event['calendar'] . ':' . $event['id'], // unique identifier for fullcalendar
- 'start' => $this->adjust_timezone($event['start'])->format('c'),
- 'end' => $this->adjust_timezone($event['end'])->format('c'),
+ 'start' => $this->lib->adjust_timezone($event['start'])->format('c'),
+ 'end' => $this->lib->adjust_timezone($event['end'])->format('c'),
'title' => strval($event['title']),
'description' => strval($event['description']),
'location' => strval($event['location']),
@@ -1188,56 +1091,6 @@ class calendar extends rcube_plugin
/**
- * Generate reduced and streamlined output for pending alarms
- */
- private function _alarms_output($alarms)
- {
- $out = array();
- foreach ($alarms as $alarm) {
- $out[] = array(
- 'id' => $alarm['id'],
- 'start' => $alarm['start'] ? $this->adjust_timezone($alarm['start'])->format('c') : '',
- 'end' => $alarm['end'] ? $this->adjust_timezone($alarm['end'])->format('c') : '',
- 'allDay' => ($alarm['allday'] == 1)?true:false,
- 'title' => $alarm['title'],
- 'location' => $alarm['location'],
- 'calendar' => $alarm['calendar'],
- );
- }
-
- return $out;
- }
-
- /**
- * Render localized text for alarm settings
- */
- public static function alarms_text($alarm)
- {
- list($trigger, $action) = explode(':', $alarm);
-
- $text = '';
- switch ($action) {
- case 'EMAIL':
- $text = rcube_label('calendar.alarmemail');
- break;
- case 'DISPLAY':
- $text = rcube_label('calendar.alarmdisplay');
- break;
- }
-
- if (preg_match('/@(\d+)/', $trigger, $m)) {
- $text .= ' ' . rcube_label(array('name' => 'calendar.alarmat', 'vars' => array('datetime' => format_date($m[1]))));
- }
- else if ($val = self::parse_alaram_value($trigger)) {
- $text .= ' ' . intval($val[0]) . ' ' . rcube_label('calendar.trigger' . $val[1]);
- }
- else
- return false;
-
- return $text;
- }
-
- /**
* Render localized text describing the recurrence rule of an event
*/
private function _recurrence_text($rrule)
@@ -1266,7 +1119,7 @@ class calendar extends rcube_plugin
if ($rrule['COUNT'])
$until = $this->gettext(array('name' => 'forntimes', 'vars' => array('nr' => $rrule['COUNT'])));
else if ($rrule['UNTIL'])
- $until = $this->gettext('recurrencend') . ' ' . format_date($rrule['UNTIL'], self::to_php_date_format($this->rc->config->get('calendar_date_format', $this->defaults['calendar_date_format'])));
+ $until = $this->gettext('recurrencend') . ' ' . format_date($rrule['UNTIL'], libcalendaring::to_php_date_format($this->rc->config->get('calendar_date_format', $this->defaults['calendar_date_format'])));
else
$until = $this->gettext('forever');
@@ -1281,149 +1134,8 @@ class calendar extends rcube_plugin
return strtoupper(md5(time() . uniqid(rand())) . '-' . substr(md5($this->rc->user->get_username()), 0, 16));
}
- /**
- * Helper function to convert alarm trigger strings
- * into two-field values (e.g. "-45M" => 45, "-M")
- */
- public static function parse_alaram_value($val)
- {
- if ($val[0] == '@')
- return array(substr($val, 1));
- else if (preg_match('/([+-])(\d+)([HMD])/', $val, $m))
- return array($m[2], $m[1].$m[3]);
-
- return false;
- }
/**
- * Get the next alarm (time & action) for the given event
- *
- * @param array Event data
- * @return array Hash array with alarm time/type or null if no alarms are configured
- */
- public static function get_next_alarm($event)
- {
- if (!$event['alarms'])
- return null;
-
- // TODO: handle multiple alarms (currently not supported)
- list($trigger, $action) = explode(':', $event['alarms'], 2);
-
- $notify = self::parse_alaram_value($trigger);
- if (!empty($notify[1])){ // offset
- $mult = 1;
- switch ($notify[1]) {
- case '-S': $mult = -1; break;
- case '+S': $mult = 1; break;
- case '-M': $mult = -60; break;
- case '+M': $mult = 60; break;
- case '-H': $mult = -3600; break;
- case '+H': $mult = 3600; break;
- case '-D': $mult = -86400; break;
- case '+D': $mult = 86400; break;
- case '-W': $mult = -604800; break;
- case '+W': $mult = 604800; break;
- }
- $offset = $notify[0] * $mult;
- $refdate = $mult > 0 ? $event['end'] : $event['start'];
- $notify_at = $refdate->format('U') + $offset;
- }
- else { // absolute timestamp
- $notify_at = $notify[0];
- }
-
- return array('time' => $notify_at, 'action' => $action ? strtoupper($action) : 'DISPLAY');
- }
-
- /**
- * Convert the internal structured data into a vcalendar rrule 2.0 string
- */
- public static function to_rrule($recurrence)
- {
- if (is_string($recurrence))
- return $recurrence;
-
- $rrule = '';
- foreach ((array)$recurrence as $k => $val) {
- $k = strtoupper($k);
- switch ($k) {
- case 'UNTIL':
- $val = $val->format('Ymd\THis');
- break;
- case 'EXDATE':
- foreach ((array)$val as $i => $ex)
- $val[$i] = $ex->format('Ymd\THis');
- $val = join(',', (array)$val);
- break;
- }
- $rrule .= $k . '=' . $val . ';';
- }
-
- return rtrim($rrule, ';');
- }
-
- /**
- * Convert from fullcalendar date format to PHP date() format string
- */
- private static function to_php_date_format($from)
- {
- // "dd.MM.yyyy HH:mm:ss" => "d.m.Y H:i:s"
- return strtr(strtr($from, array(
- 'yyyy' => 'Y',
- 'yy' => 'y',
- 'MMMM' => 'F',
- 'MMM' => 'M',
- 'MM' => 'm',
- 'M' => 'n',
- 'dddd' => 'l',
- 'ddd' => 'D',
- 'dd' => 'd',
- 'HH' => '**',
- 'hh' => '%%',
- 'H' => 'G',
- 'h' => 'g',
- 'mm' => 'i',
- 'ss' => 's',
- 'TT' => 'A',
- 'tt' => 'a',
- 'T' => 'A',
- 't' => 'a',
- 'u' => 'c',
- )), array(
- '**' => 'H',
- '%%' => 'h',
- ));
- }
-
- /**
- * Convert from PHP date() format to fullcalendar format string
- */
- private static function from_php_date_format($from)
- {
- // "d.m.Y H:i:s" => "dd.MM.yyyy HH:mm:ss"
- return strtr($from, array(
- 'y' => 'yy',
- 'Y' => 'yyyy',
- 'M' => 'MMM',
- 'F' => 'MMMM',
- 'm' => 'MM',
- 'n' => 'M',
- 'd' => 'dd',
- 'D' => 'ddd',
- 'l' => 'dddd',
- 'H' => 'HH',
- 'h' => 'hh',
- 'G' => 'H',
- 'g' => 'h',
- 'i' => 'mm',
- 's' => 'ss',
- 'A' => 'TT',
- 'a' => 'tt',
- 'c' => 'u',
- ));
- }
-
- /**
* TEMPORARY: generate random event data for testing
* Create events by opening http://<roundcubeurl>/?_task=calendar&_action=randomdata&_num=500
*/
@@ -1812,40 +1524,6 @@ class calendar extends rcube_plugin
return $sent;
}
-
- /**
- * Compose a date string for the given event
- */
- public function event_date_text($event, $tzinfo = false)
- {
- $fromto = '';
- $duration = $event['start']->diff($event['end'])->format('s');
-
- $this->date_format_defaults();
- $date_format = self::to_php_date_format($this->rc->config->get('calendar_date_format', $this->defaults['calendar_date_format']));
- $time_format = self::to_php_date_format($this->rc->config->get('calendar_time_format', $this->defaults['calendar_time_format']));
-
- if ($event['allday']) {
- $fromto = format_date($event['start'], $date_format);
- if (($todate = format_date($event['end'], $date_format)) != $fromto)
- $fromto .= ' - ' . $todate;
- }
- else if ($duration < 86400 && $event['start']->format('d') == $event['end']->format('d')) {
- $fromto = format_date($event['start'], $date_format) . ' ' . format_date($event['start'], $time_format) .
- ' - ' . format_date($event['end'], $time_format);
- }
- else {
- $fromto = format_date($event['start'], $date_format) . ' ' . format_date($event['start'], $time_format) .
- ' - ' . format_date($event['end'], $date_format) . ' ' . format_date($event['end'], $time_format);
- }
-
- // add timezone information
- if ($tzinfo && ($tzname = $this->timezone->getName())) {
- $fromto .= ' (' . strtr($tzname, '_', ' ') . ')';
- }
-
- return $fromto;
- }
/**
* Echo simple free/busy status text for the given user and time range
diff --git a/plugins/calendar/calendar_base.js b/plugins/calendar/calendar_base.js
index 48c43b6..0b8c904 100644
--- a/plugins/calendar/calendar_base.js
+++ b/plugins/calendar/calendar_base.js
@@ -25,158 +25,16 @@
// Basic setup for Roundcube calendar client class
function rcube_calendar(settings)
{
+ // extend base class
+ rcube_libcalendaring.call(this, settings);
+
// member vars
this.ui;
this.ui_loaded = false;
- this.settings = settings;
- this.alarm_ids = [];
- this.alarm_dialog = null;
- this.snooze_popup = null;
- this.dismiss_link = null;
// private vars
var me = this;
- // quote html entities
- var Q = this.quote_html = function(str)
- {
- return String(str).replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
- };
-
- // create a nice human-readable string for the date/time range
- this.event_date_text = function(event)
- {
- if (!event.start)
- return '';
- if (!event.end)
- event.end = event.start;
-
- var fromto, duration = event.end.getTime() / 1000 - event.start.getTime() / 1000;
- if (event.allDay) {
- fromto = $.fullCalendar.formatDate(event.start, settings['date_format'])
- + (duration > 86400 || event.start.getDay() != event.end.getDay() ? ' — ' + $.fullCalendar.formatDate(event.end, settings['date_format']) : '');
- }
- else if (duration < 86400 && event.start.getDay() == event.end.getDay()) {
- fromto = $.fullCalendar.formatDate(event.start, settings['date_format']) + ' ' + $.fullCalendar.formatDate(event.start, settings['time_format'])
- + (duration > 0 ? ' — ' + $.fullCalendar.formatDate(event.end, settings['time_format']) : '');
- }
- else {
- fromto = $.fullCalendar.formatDate(event.start, settings['date_format']) + ' ' + $.fullCalendar.formatDate(event.start, settings['time_format'])
- + (duration > 0 ? ' — ' + $.fullCalendar.formatDate(event.end, settings['date_format']) + ' ' + $.fullCalendar.formatDate(event.end, settings['time_format']) : '');
- }
-
- return fromto;
- };
-
- // display a notification for the given pending alarms
- this.display_alarms = function(alarms) {
- // clear old alert first
- if (this.alarm_dialog)
- this.alarm_dialog.dialog('destroy');
-
- this.alarm_dialog = $('<div>').attr('id', 'alarm-display');
-
- var actions, adismiss, asnooze, alarm, html, event_ids = [];
- for (var actions, html, alarm, i=0; i < alarms.length; i++) {
- alarm = alarms[i];
- alarm.start = $.fullCalendar.parseISO8601(alarm.start, true);
- alarm.end = $.fullCalendar.parseISO8601(alarm.end, true);
- event_ids.push(alarm.id);
-
- html = '<h3 class="event-title">' + Q(alarm.title) + '</h3>';
- html += '<div class="event-section">' + Q(alarm.location || '') + '</div>';
- html += '<div class="event-section">' + Q(this.event_date_text(alarm)) + '</div>';
-
- adismiss = $('<a href="#" class="alarm-action-dismiss"></a>').html(rcmail.gettext('dismiss','calendar')).click(function(){
- me.dismiss_link = $(this);
- me.dismiss_alarm(me.dismiss_link.data('id'), 0);
- });
- asnooze = $('<a href="#" class="alarm-action-snooze"></a>').html(rcmail.gettext('snooze','calendar')).click(function(e){
- me.snooze_dropdown($(this));
- e.stopPropagation();
- return false;
- });
- actions = $('<div>').addClass('alarm-actions').append(adismiss.data('id', alarm.id)).append(asnooze.data('id', alarm.id));
-
- $('<div>').addClass('alarm-item').html(html).append(actions).appendTo(this.alarm_dialog);
- }
-
- var buttons = {};
- buttons[rcmail.gettext('dismissall','calendar')] = function() {
- // submit dismissed event_ids to server
- me.dismiss_alarm(me.alarm_ids.join(','), 0);
- $(this).dialog('close');
- };
-
- this.alarm_dialog.appendTo(document.body).dialog({
- modal: false,
- resizable: true,
- closeOnEscape: false,
- dialogClass: 'alarm',
- title: '<span class="ui-icon ui-icon-alert" style="float:left; margin:0 4px 0 0"></span>' + rcmail.gettext('alarmtitle', 'calendar'),
- buttons: buttons,
- close: function() {
- $('#alarm-snooze-dropdown').hide();
- $(this).dialog('destroy').remove();
- me.alarm_dialog = null;
- me.alarm_ids = null;
- },
- drag: function(event, ui) {
- $('#alarm-snooze-dropdown').hide();
- }
- });
- this.alarm_ids = event_ids;
- };
-
- // show a drop-down menu with a selection of snooze times
- this.snooze_dropdown = function(link)
- {
- if (!this.snooze_popup) {
- this.snooze_popup = $('#alarm-snooze-dropdown');
- // create popup if not found
- if (!this.snooze_popup.length) {
- this.snooze_popup = $('<div>').attr('id', 'alarm-snooze-dropdown').addClass('popupmenu').appendTo(document.body);
- this.snooze_popup.html(rcmail.env.snooze_select)
- }
- $('#alarm-snooze-dropdown a').click(function(e){
- var time = String(this.href).replace(/.+#/, '');
- me.dismiss_alarm($('#alarm-snooze-dropdown').data('id'), time);
- return false;
- });
- }
-
- // hide visible popup
- if (this.snooze_popup.is(':visible') && this.snooze_popup.data('id') == link.data('id')) {
- this.snooze_popup.hide();
- this.dismiss_link = null;
- }
- else { // open popup below the clicked link
- var pos = link.offset();
- pos.top += link.height() + 2;
- this.snooze_popup.data('id', link.data('id')).css({ top:Math.floor(pos.top)+'px', left:Math.floor(pos.left)+'px' }).show();
- this.dismiss_link = link;
- }
- };
-
- // dismiss or snooze alarms for the given event
- this.dismiss_alarm = function(id, snooze)
- {
- $('#alarm-snooze-dropdown').hide();
- rcmail.http_post('calendar/event', { action:'dismiss', e:{ id:id, snooze:snooze } });
-
- // remove dismissed alarm from list
- if (this.dismiss_link) {
- this.dismiss_link.closest('div.alarm-item').hide();
- var new_ids = jQuery.grep(this.alarm_ids, function(v){ return v != id; });
- if (new_ids.length)
- this.alarm_ids = new_ids;
- else
- this.alarm_dialog.dialog('close');
- }
-
- this.dismiss_link = null;
- };
-
// create new event from current mail message
this.create_from_mail = function()
{
@@ -186,6 +44,7 @@ function rcube_calendar(settings)
if (!this.ui_loaded) {
$.when(
$.getScript('./plugins/calendar/calendar_ui.js'),
+ $.getScript('./plugins/calendar/lib/js/fullcalendar.js'),
$.get(rcmail.url('calendar/inlineui'), function(html){ $(document.body).append(html); }, 'html')
).then(function() {
// register attachments form
@@ -261,24 +120,11 @@ rcube_calendar.fetch_event_rsvp_status = function(event)
};
-// extend jQuery
-(function($){
- $.fn.serializeJSON = function(){
- var json = {};
- jQuery.map($(this).serializeArray(), function(n, i) {
- json[n['name']] = n['value'];
- });
- return json;
- };
-})(jQuery);
-
/* calendar plugin initialization (for non-calendar tasks) */
window.rcmail && rcmail.addEventListener('init', function(evt) {
if (rcmail.task != 'calendar') {
- var cal = new rcube_calendar(rcmail.env.calendar_settings);
+ var cal = new rcube_calendar($.extend(rcmail.env.calendar_settings, rcmail.env.libcal_settings));
- rcmail.addEventListener('plugin.display_alarms', function(alarms){ cal.display_alarms(alarms); });
-
rcmail.addEventListener('plugin.update_event_rsvp_status', function(p){
if (p.html)
$('#loading-'+p.id).hide().after(p.html);
diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js
index 1f9d448..ce993e5 100644
--- a/plugins/calendar/calendar_ui.js
+++ b/plugins/calendar/calendar_ui.js
@@ -67,64 +67,18 @@ function rcube_calendar_ui(settings)
selectOtherMonths: true
};
-
- /*** private methods ***/
-
+ /*** imports ***/
var Q = this.quote_html;
+ var text2html = this.text2html;
var event_date_text = this.event_date_text;
+ var parse_datetime = this.parse_datetime;
+ var date2unixtime = this.date2unixtime;
+ var fromunixtime = this.fromunixtime;
+ var init_alarms_edit = this.init_alarms_edit;
- var text2html = function(str, maxlen, maxlines)
- {
- var html = Q(String(str));
-
- // limit visible text length
- if (maxlen) {
- var morelink = ' <a href="#more" onclick="$(this).hide().next().show();return false" class="morelink">'+rcmail.gettext('showmore','calendar')+'</a><span style="display:none">',
- lines = html.split(/\r?\n/),
- words, out = '', len = 0;
-
- for (var i=0; i < lines.length; i++) {
- len += lines[i].length;
- if (maxlines && i == maxlines - 1) {
- out += lines[i] + '\n' + morelink;
- maxlen = html.length * 2;
- }
- else if (len > maxlen) {
- len = out.length;
- words = lines[i].split(' ');
- for (var j=0; j < words.length; j++) {
- len += words[j].length + 1;
- out += words[j] + ' ';
- if (len > maxlen) {
- out += morelink;
- maxlen = html.length * 2;
- }
- }
- out += '\n';
- }
- else
- out += lines[i] + '\n';
- }
-
- if (maxlen > str.length)
- out += '</span>';
-
- html = out;
- }
-
- // simple link parser (similar to rcube_string_replacer class in PHP)
- var utf_domain = '[^?&@"\'/\\(\\)\\s\\r\\t\\n]+\\.([^\x00-\x2f\x3b-\x40\x5b-\x60\x7b-\x7f]{2,}|xn--[a-z0-9]{2,})';
- var url1 = '.:;,', url2 = 'a-z0-9%=#@+?&/_~\\[\\]-';
- var link_pattern = new RegExp('([hf]t+ps?://)('+utf_domain+'(['+url1+']?['+url2+']+)*)?', 'ig');
- var mailto_pattern = new RegExp('([^\\s\\n\\(\\);]+@'+utf_domain+')', 'ig');
- return html
- .replace(link_pattern, '<a href="$1$2" target="_blank">$1$2</a>')
- .replace(mailto_pattern, '<a href="mailto:$1">$1</a>')
- .replace(/(mailto:)([^"]+)"/g, '$1$2" onclick="rcmail.command(\'compose\', \'$2\');return false"')
- .replace(/\n/g, "<br/>");
- };
-
+ /*** private methods ***/
+
// same as str.split(delimiter) but it ignores delimiters within quoted strings
var explode_quoted_string = function(str, delimiter)
{
@@ -148,25 +102,6 @@ function rcube_calendar_ui(settings)
return result;
};
- // from time and date strings to a real date object
- var parse_datetime = function(time, date)
- {
- // we use the utility function from datepicker to parse dates
- var date = date ? $.datepicker.parseDate(datepicker_settings.dateFormat, date, datepicker_settings) : new Date();
-
- var time_arr = time.replace(/\s*[ap][.m]*/i, '').replace(/0([0-9])/g, '$1').split(/[:.]/);
- if (!isNaN(time_arr[0])) {
- date.setHours(time_arr[0]);
- if (time.match(/p[.m]*/i) && date.getHours() < 12)
- date.setHours(parseInt(time_arr[0]) + 12);
- else if (time.match(/a[.m]*/i) && date.getHours() == 12)
- date.setHours(0);
- }
- if (!isNaN(time_arr[1]))
- date.setMinutes(time_arr[1]);
-
- return date;
- };
// clone the given date object and optionally adjust time
var clone_date = function(date, adjust)
@@ -194,23 +129,6 @@ function rcube_calendar_ui(settings)
+ 'T'+date.getHours()+':'+date.getMinutes()+':'+date.getSeconds();
}
- // convert the given Date object into a unix timestamp respecting browser's and user's timezone settings
- var date2unixtime = function(date)
- {
- var dst_offset = (client_timezone - date.getTimezoneOffset()) * 60; // adjust DST offset
- return Math.round(date.getTime()/1000 + gmt_offset * 3600 + dst_offset);
- };
-
- var fromunixtime = function(ts)
- {
- ts -= gmt_offset * 3600;
- var date = new Date(ts * 1000),
- dst_offset = (client_timezone - date.getTimezoneOffset()) * 60;
- if (dst_offset) // adjust DST offset
- date.setTime((ts + 3600) * 1000);
- return date;
- };
-
// determine whether the given date is on a weekend
var is_weekend = function(date)
{
@@ -2561,7 +2479,7 @@ function rcube_calendar_ui(settings)
}
}
});
- $('#edit-enddate, input.edit-alarm-date').datepicker(datepicker_settings);
+ $('#edit-enddate').datepicker(datepicker_settings);
$('#edit-startdate').datepicker(datepicker_settings).datepicker('option', 'onSelect', shift_enddate).change(function(){ shift_enddate(this.value); });
$('#edit-enddate').datepicker('option', 'onSelect', event_times_changed).change(event_times_changed);
$('#edit-allday').click(function(){ $('#edit-starttime, #edit-endtime')[(this.checked?'hide':'show')](); event_times_changed(); });
@@ -2592,14 +2510,7 @@ function rcube_calendar_ui(settings)
});
// register events on alarm fields
- $('#eventedit select.edit-alarm-type').change(function(){
- $(this).parent().find('span.edit-alarm-values')[(this.selectedIndex>0?'show':'hide')]();
- });
- $('#eventedit select.edit-alarm-offset').change(function(){
- var mode = $(this).val() == '@' ? 'show' : 'hide';
- $(this).parent().find('.edit-alarm-date, .edit-alarm-time')[mode]();
- $(this).parent().find('.edit-alarm-value').prop('disabled', mode == 'show');
- });
+ init_alarms_edit('#eventedit');
// toggle recurrence frequency forms
$('#edit-recurrence-frequency').change(function(e){
@@ -2721,7 +2632,7 @@ window.rcmail && rcmail.addEventListener('init', function(evt) {
rcmail.addEventListener('plugin.import_success', function(p){ cal.import_success(p); });
// let's go
- var cal = new rcube_calendar_ui(rcmail.env.calendar_settings);
+ var cal = new rcube_calendar_ui($.extend(rcmail.env.calendar_settings, rcmail.env.libcal_settings));
$(window).resize(function(e) {
// check target due to bugs in jquery
diff --git a/plugins/calendar/drivers/database/database_driver.php b/plugins/calendar/drivers/database/database_driver.php
index 039276e..6d3a5a8 100644
--- a/plugins/calendar/drivers/database/database_driver.php
+++ b/plugins/calendar/drivers/database/database_driver.php
@@ -378,7 +378,7 @@ class database_driver extends calendar_driver
$event['end']->setTimezone($this->server_timezone);
// compose vcalendar-style recurrencue rule from structured data
- $rrule = $event['recurrence'] ? calendar::to_rrule($event['recurrence']) : '';
+ $rrule = $event['recurrence'] ? libcalendaring::to_rrule($event['recurrence']) : '';
$event['_recurrence'] = rtrim($rrule, ';');
$event['free_busy'] = intval($this->free_busy_map[strtolower($event['free_busy'])]);
@@ -411,7 +411,7 @@ class database_driver extends calendar_driver
private function _get_notification($event)
{
if ($event['alarms'] && $event['start'] > new DateTime()) {
- $alarm = calendar::get_next_alarm($event);
+ $alarm = libcalendaring::get_next_alarm($event);
if ($alarm['time'] && $alarm['action'] == 'DISPLAY')
return date('Y-m-d H:i:s', $alarm['time']);
diff --git a/plugins/calendar/drivers/kolab/kolab_driver.php b/plugins/calendar/drivers/kolab/kolab_driver.php
index 9346f82..bdf2250 100644
--- a/plugins/calendar/drivers/kolab/kolab_driver.php
+++ b/plugins/calendar/drivers/kolab/kolab_driver.php
@@ -680,7 +680,7 @@ class kolab_driver extends calendar_driver
foreach ($calendar->list_events($time, $time + 86400 * 365, null, 1, $query) as $e) {
// add to list if alarm is set
- $alarm = calendar::get_next_alarm($e);
+ $alarm = calendarlibcalendaring::get_next_alarm($e);
if ($alarm && $alarm['time'] && $alarm['time'] <= $time && $alarm['action'] == 'DISPLAY') {
$id = $e['id'];
$events[$id] = $e;
diff --git a/plugins/calendar/lib/calendar_ical.php b/plugins/calendar/lib/calendar_ical.php
index 83ff1be..719c1dc 100644
--- a/plugins/calendar/lib/calendar_ical.php
+++ b/plugins/calendar/lib/calendar_ical.php
@@ -124,7 +124,7 @@ class calendar_ical
public function get_parser()
{
// use Horde:iCalendar to parse vcalendar file format
- @include_once('Horde/iCalendar.php');
+ // @include_once('Horde/iCalendar.php');
if (!class_exists('Horde_iCalendar'))
require_once($this->cal->home . '/lib/Horde_iCalendar.php');
@@ -377,7 +377,7 @@ class calendar_ical
$vevent .= "LOCATION:" . self::escpape($event['location']) . self::EOL;
}
if ($event['recurrence']) {
- $vevent .= "RRULE:" . calendar::to_rrule($event['recurrence'], self::EOL) . self::EOL;
+ $vevent .= "RRULE:" . libcalendaring::to_rrule($event['recurrence'], self::EOL) . self::EOL;
}
if(!empty($event['categories'])) {
$vevent .= "CATEGORIES:" . self::escpape(strtoupper($event['categories'])) . self::EOL;
@@ -387,7 +387,7 @@ class calendar_ical
}
if ($event['alarms']) {
list($trigger, $action) = explode(':', $event['alarms']);
- $val = calendar::parse_alaram_value($trigger);
+ $val = libcalendaring::parse_alaram_value($trigger);
$vevent .= "BEGIN:VALARM\n";
if ($val[1]) $vevent .= "TRIGGER:" . preg_replace('/^([-+])(.+)/', '\\1PT\\2', $trigger) . self::EOL;
diff --git a/plugins/calendar/lib/calendar_itip.php b/plugins/calendar/lib/calendar_itip.php
index 8008aae..b27c17c 100644
--- a/plugins/calendar/lib/calendar_itip.php
+++ b/plugins/calendar/lib/calendar_itip.php
@@ -77,7 +77,7 @@ class calendar_itip
'name' => $bodytext,
'vars' => array(
'title' => $event['title'],
- 'date' => $this->cal->event_date_text($event, true),
+ 'date' => $this->cal->lib->event_date_text($event, true),
'attendees' => join(', ', $attendees_list),
'sender' => $this->sender['name'],
'organizer' => $this->sender['name'],
diff --git a/plugins/calendar/lib/calendar_recurrence.php b/plugins/calendar/lib/calendar_recurrence.php
index 7326717..03831c1 100644
--- a/plugins/calendar/lib/calendar_recurrence.php
+++ b/plugins/calendar/lib/calendar_recurrence.php
@@ -54,7 +54,7 @@ class calendar_recurrence
require_once($this->cal->home . '/lib/Horde_Date_Recurrence.php');
$this->engine = new Horde_Date_Recurrence($event['start']);
- $this->engine->fromRRule20(calendar::to_rrule($event['recurrence']));
+ $this->engine->fromRRule20(libcalendaring::to_rrule($event['recurrence']));
if (is_array($event['recurrence']['EXDATE'])) {
foreach ($event['recurrence']['EXDATE'] as $exdate)
diff --git a/plugins/calendar/lib/calendar_ui.php b/plugins/calendar/lib/calendar_ui.php
index 1cbcf0a..34e0dd3 100644
--- a/plugins/calendar/lib/calendar_ui.php
+++ b/plugins/calendar/lib/calendar_ui.php
@@ -55,8 +55,7 @@ class calendar_ui
'label' => 'calendar.calendar',
), 'taskbar');
- // load basic client script (which - unfortunately - requires fullcalendar)
- $this->cal->include_script('lib/js/fullcalendar.js');
+ // load basic client script
$this->cal->include_script('calendar_base.js');
$skin_path = $this->cal->local_skin_path();
@@ -78,7 +77,6 @@ class calendar_ui
$this->cal->register_handler('plugin.priority_select', array($this, 'priority_select'));
$this->cal->register_handler('plugin.sensitivity_select', array($this, 'sensitivity_select'));
$this->cal->register_handler('plugin.alarm_select', array($this, 'alarm_select'));
- $this->cal->register_handler('plugin.snooze_select', array($this, 'snooze_select'));
$this->cal->register_handler('plugin.recurrence_form', array($this, 'recurrence_form'));
$this->cal->register_handler('plugin.attachments_form', array($this, 'attachments_form'));
$this->cal->register_handler('plugin.attachments_list', array($this, 'attachments_list'));
@@ -110,6 +108,7 @@ class calendar_ui
public function addJS()
{
$this->cal->include_script('calendar_ui.js');
+ $this->cal->include_script('lib/js/fullcalendar.js');
$this->cal->include_script('lib/js/jquery.miniColors.min.js');
}
@@ -326,63 +325,9 @@ class calendar_ui
*/
function alarm_select($attrib = array())
{
- unset($attrib['name']);
- $select_type = new html_select(array('name' => 'alarmtype[]', 'class' => 'edit-alarm-type'));
- $select_type->add($this->cal->gettext('none'), '');
- foreach ($this->cal->driver->alarm_types as $type)
- $select_type->add($this->cal->gettext(strtolower("alarm{$type}option")), $type);
-
- $input_value = new html_inputfield(array('name' => 'alarmvalue[]', 'class' => 'edit-alarm-value', 'size' => 3));
- $input_date = new html_inputfield(array('name' => 'alarmdate[]', 'class' => 'edit-alarm-date', 'size' => 10));
- $input_time = new html_inputfield(array('name' => 'alarmtime[]', 'class' => 'edit-alarm-time', 'size' => 6));
-
- $select_offset = new html_select(array('name' => 'alarmoffset[]', 'class' => 'edit-alarm-offset'));
- foreach (array('-M','-H','-D','+M','+H','+D','@') as $trigger)
- $select_offset->add($this->cal->gettext('trigger' . $trigger), $trigger);
-
- // pre-set with default values from user settings
- $preset = calendar::parse_alaram_value($this->rc->config->get('calendar_default_alarm_offset', '-15M'));
- $hidden = array('style' => 'display:none');
- $html = html::span('edit-alarm-set',
- $select_type->show($this->rc->config->get('calendar_default_alarm_type', '')) . ' ' .
- html::span(array('class' => 'edit-alarm-values', 'style' => 'display:none'),
- $input_value->show($preset[0]) . ' ' .
- $select_offset->show($preset[1]) . ' ' .
- $input_date->show('', $hidden) . ' ' .
- $input_time->show('', $hidden)
- )
- );
-
- // TODO: support adding more alarms
- #$html .= html::a(array('href' => '#', 'id' => 'edit-alam-add', 'title' => $this->cal->gettext('addalarm')),
- # $attrib['addicon'] ? html::img(array('src' => $attrib['addicon'], 'alt' => 'add')) : '(+)');
-
- return $html;
+ return $this->cal->lib->alarm_select($attrib, $this->cal->driver->alarm_types);
}
- function snooze_select($attrib = array())
- {
- $steps = array(
- 5 => 'repeatinmin',
- 10 => 'repeatinmin',
- 15 => 'repeatinmin',
- 20 => 'repeatinmin',
- 30 => 'repeatinmin',
- 60 => 'repeatinhr',
- 120 => 'repeatinhrs',
- 1440 => 'repeattomorrow',
- 10080 => 'repeatinweek',
- );
-
- $items = array();
- foreach ($steps as $n => $label) {
- $items[] = html::tag('li', null, html::a(array('href' => "#" . ($n * 60), 'class' => 'active'),
- $this->cal->gettext(array('name' => $label, 'vars' => array('min' => $n % 60, 'hrs' => intval($n / 60))))));
- }
-
- return html::tag('ul', $attrib + array('class' => 'toolbarmenu'), join("\n", $items), html::$common_attrib);
- }
-
/**
*
*/
@@ -780,7 +725,7 @@ class calendar_ui
$table->add('ititle', $title);
$table->add('title', Q($event['title']));
$table->add('label', $this->cal->gettext('date'));
- $table->add('location', Q($this->cal->event_date_text($event)));
+ $table->add('location', Q($this->cal->lib->event_date_text($event)));
if ($event['location']) {
$table->add('label', $this->cal->gettext('location'));
$table->add('location', Q($event['location']));
diff --git a/plugins/calendar/localization/de_CH.inc b/plugins/calendar/localization/de_CH.inc
index 37ace18..12614dd 100644
--- a/plugins/calendar/localization/de_CH.inc
+++ b/plugins/calendar/localization/de_CH.inc
@@ -68,7 +68,6 @@ $labels['parentcalendar'] = 'Erstellen in';
$labels['searchearlierdates'] = '« Frühere Termine suchen';
$labels['searchlaterdates'] = 'Spätere Termine suchen »';
$labels['andnmore'] = '$nr weitere...';
-$labels['showmore'] = 'Mehr anzeigen...';
$labels['togglerole'] = 'Zum Ãndern der Rolle klicken';
$labels['createfrommail'] = 'Als Termin speichern';
$labels['importevents'] = 'Termine importieren';
@@ -95,30 +94,8 @@ $labels['futureevents'] = 'Zukünftige';
// alarm/reminder settings
$labels['showalarms'] = 'Erinnerungen anzeigen';
-$labels['alarmemail'] = 'E-Mail senden';
-$labels['alarmdisplay'] = 'Nachricht anzeigen';
-$labels['alarmdisplayoption'] = 'Nachricht';
-$labels['alarmemailoption'] = 'E-Mail';
-$labels['alarmat'] = 'um $datetime';
-$labels['trigger@'] = 'genau um';
-$labels['trigger-M'] = 'Minuten davor';
-$labels['trigger-H'] = 'Stunden davor';
-$labels['trigger-D'] = 'Tage davor';
-$labels['trigger+M'] = 'Minuten danach';
-$labels['trigger+H'] = 'Stunden danach';
-$labels['trigger+D'] = 'Tage danach';
-$labels['addalarm'] = 'Erinnerung hinzufügen';
$labels['defaultalarmtype'] = 'Standard-Erinnerungseinstellung';
$labels['defaultalarmoffset'] = 'Standard-Erinnerungszeit';
-$labels['dismissall'] = 'Alle ignorieren';
-$labels['dismiss'] = 'Ignorieren';
-$labels['snooze'] = 'Später erinnern';
-$labels['repeatinmin'] = 'Wiederholung in $min Minuten';
-$labels['repeatinhr'] = 'Wiederholung in 1 Stunde';
-$labels['repeatinhrs'] = 'Wiederholung in $hrs Stunden';
-$labels['repeattomorrow'] = 'Wiederholung morgen';
-$labels['repeatinweek'] = 'Wiederholung in einer Woche';
-$labels['alarmtitle'] = 'Anstehende Termine';
// attendees
$labels['attendee'] = 'Teilnehmer';
diff --git a/plugins/calendar/localization/de_DE.inc b/plugins/calendar/localization/de_DE.inc
index 0cadf51..7c0d00f 100644
--- a/plugins/calendar/localization/de_DE.inc
+++ b/plugins/calendar/localization/de_DE.inc
@@ -68,7 +68,6 @@ $labels['parentcalendar'] = 'Erstellen in';
$labels['searchearlierdates'] = '« Frühere Termine suchen';
$labels['searchlaterdates'] = 'Spätere Termine suchen »';
$labels['andnmore'] = '$nr weitere...';
-$labels['showmore'] = 'Mehr anzeigen...';
$labels['togglerole'] = 'Zum Ãndern der Rolle klicken';
$labels['createfrommail'] = 'Als Termin speichern';
$labels['importevents'] = 'Termine importieren';
@@ -95,30 +94,8 @@ $labels['futureevents'] = 'Zukünftige';
// alarm/reminder settings
$labels['showalarms'] = 'Erinnerungen anzeigen';
-$labels['alarmemail'] = 'E-Mail senden';
-$labels['alarmdisplay'] = 'Nachricht anzeigen';
-$labels['alarmdisplayoption'] = 'Nachricht';
-$labels['alarmemailoption'] = 'E-Mail';
-$labels['alarmat'] = 'um $datetime';
-$labels['trigger@'] = 'genau um';
-$labels['trigger-M'] = 'Minuten davor';
-$labels['trigger-H'] = 'Stunden davor';
-$labels['trigger-D'] = 'Tage davor';
-$labels['trigger+M'] = 'Minuten danach';
-$labels['trigger+H'] = 'Stunden danach';
-$labels['trigger+D'] = 'Tage danach';
-$labels['addalarm'] = 'Erinnerung hinzufügen';
$labels['defaultalarmtype'] = 'Standard-Erinnerungseinstellung';
$labels['defaultalarmoffset'] = 'Standard-Erinnerungszeit';
-$labels['dismissall'] = 'Alle ignorieren';
-$labels['dismiss'] = 'Ignorieren';
-$labels['snooze'] = 'Später erinnern';
-$labels['repeatinmin'] = 'Wiederholung in $min Minuten';
-$labels['repeatinhr'] = 'Wiederholung in 1 Stunde';
-$labels['repeatinhrs'] = 'Wiederholung in $hrs Stunden';
-$labels['repeattomorrow'] = 'Wiederholung morgen';
-$labels['repeatinweek'] = 'Wiederholung in einer Woche';
-$labels['alarmtitle'] = 'Anstehende Termine';
// attendees
$labels['attendee'] = 'Teilnehmer';
diff --git a/plugins/calendar/localization/en_US.inc b/plugins/calendar/localization/en_US.inc
index e13cd88..2137b39 100644
--- a/plugins/calendar/localization/en_US.inc
+++ b/plugins/calendar/localization/en_US.inc
@@ -68,7 +68,6 @@ $labels['parentcalendar'] = 'Insert inside';
$labels['searchearlierdates'] = '« Search for earlier events';
$labels['searchlaterdates'] = 'Search for later events »';
$labels['andnmore'] = '$nr more...';
-$labels['showmore'] = 'Show more...';
$labels['togglerole'] = 'Click to toggle role';
$labels['createfrommail'] = 'Save as event';
$labels['importevents'] = 'Import events';
@@ -95,30 +94,8 @@ $labels['futureevents'] = 'Future';
// alarm/reminder settings
$labels['showalarms'] = 'Show alarms';
-$labels['alarmemail'] = 'Send Email';
-$labels['alarmdisplay'] = 'Show message';
-$labels['alarmdisplayoption'] = 'Message';
-$labels['alarmemailoption'] = 'Email';
-$labels['alarmat'] = 'at $datetime';
-$labels['trigger@'] = 'on date';
-$labels['trigger-M'] = 'minutes before';
-$labels['trigger-H'] = 'hours before';
-$labels['trigger-D'] = 'days before';
-$labels['trigger+M'] = 'minutes after';
-$labels['trigger+H'] = 'hours after';
-$labels['trigger+D'] = 'days after';
-$labels['addalarm'] = 'add alarm';
$labels['defaultalarmtype'] = 'Default reminder setting';
$labels['defaultalarmoffset'] = 'Default reminder time';
-$labels['dismissall'] = 'Dismiss all';
-$labels['dismiss'] = 'Dismiss';
-$labels['snooze'] = 'Snooze';
-$labels['repeatinmin'] = 'Repeat in $min minutes';
-$labels['repeatinhr'] = 'Repeat in 1 hour';
-$labels['repeatinhrs'] = 'Repeat in $hrs hours';
-$labels['repeattomorrow'] = 'Repeat tomorrow';
-$labels['repeatinweek'] = 'Repeat in a week';
-$labels['alarmtitle'] = 'Upcoming events';
// attendees
$labels['attendee'] = 'Participant';
diff --git a/plugins/calendar/localization/pl_PL.inc b/plugins/calendar/localization/pl_PL.inc
index 706e999..3403ea5 100644
--- a/plugins/calendar/localization/pl_PL.inc
+++ b/plugins/calendar/localization/pl_PL.inc
@@ -90,30 +90,8 @@ $labels['futureevents'] = 'PrzyszÅoÅÄ';
// alarm/reminder settings
$labels['showalarms'] = 'WyÅwietlaj alarmy';
-$labels['alarmemail'] = 'WyÅlij pocztÄ';
-$labels['alarmdisplay'] = 'Pokaż wiadomoÅÄ';
-$labels['alarmdisplayoption'] = 'WiadomoÅÄ';
-$labels['alarmemailoption'] = 'Poczta';
-$labels['alarmat'] = 'o $datetime';
-$labels['trigger@'] = 'w dniu';
-$labels['trigger-M'] = 'minuty przed';
-$labels['trigger-H'] = 'godziny przed';
-$labels['trigger-D'] = 'dni przed';
-$labels['trigger+M'] = 'minut po';
-$labels['trigger+H'] = 'godziny po';
-$labels['trigger+D'] = 'dni po';
-$labels['addalarm'] = 'dodaj alarm';
$labels['defaultalarmtype'] = 'DomyÅlne ustawienia przypomnienia';
$labels['defaultalarmoffset'] = 'DomyÅlny czas przypomnienia';
-$labels['dismissall'] = 'OdrzuÄ wszystkie';
-$labels['dismiss'] = 'OdrzuÄ';
-$labels['snooze'] = 'OdÅóż';
-$labels['repeatinmin'] = 'Powtórz po $min minutach';
-$labels['repeatinhr'] = 'Powtórz po godzinie';
-$labels['repeatinhrs'] = 'Powtórz po $hrs godzinach';
-$labels['repeattomorrow'] = 'Powtórz jutro';
-$labels['repeatinweek'] = 'Powtórz za tydzieÅ';
-$labels['alarmtitle'] = 'NadchodzÄ
ce zdarzenia';
// attendees
$labels['attendee'] = 'Uczestnik';
diff --git a/plugins/calendar/skins/classic/templates/calendar.html b/plugins/calendar/skins/classic/templates/calendar.html
index a22056d..59b880d 100644
--- a/plugins/calendar/skins/classic/templates/calendar.html
+++ b/plugins/calendar/skins/classic/templates/calendar.html
@@ -148,10 +148,6 @@
<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>
-
<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=" " />
diff --git a/plugins/calendar/skins/larry/calendar.css b/plugins/calendar/skins/larry/calendar.css
index 6765211..1a7325e 100644
--- a/plugins/calendar/skins/larry/calendar.css
+++ b/plugins/calendar/skins/larry/calendar.css
@@ -1,15 +1,13 @@
/**
* Roundcube Calendar plugin styles for skin "Larry"
*
- * Copyright (c) 2012, The Roundcube Dev Team
+ * Copyright (c) 2012, Kolab Systems AG <contact at kolabsys.com>
* Screendesign by FLINT / Büro für Gestaltung, bueroflint.com
*
* The contents are subject to the Creative Commons Attribution-ShareAlike
* License. It is allowed to copy, distribute, transmit and to adapt the work
* by keeping credits to the original autors in the README file.
* See http://creativecommons.org/licenses/by-sa/3.0/ for details.
- *
- * $Id$
*/
body.calendarmain {
@@ -887,10 +885,6 @@ td.topalign {
text-align: center;
}
-span.edit-alarm-set {
- white-space: nowrap;
-}
-
a.dropdown-link {
font-size: 12px;
text-decoration: none;
@@ -906,39 +900,6 @@ a.dropdown-link:after {
min-height: 24em;
}
-.alarm-item {
- margin: 0.4em 0 1em 0;
-}
-
-.alarm-item .event-title {
- font-size: 14px;
- margin: 0.1em 0 0.3em 0;
-}
-
-.alarm-item div.event-section {
- margin-top: 0.1em;
- margin-bottom: 0.3em;
-}
-
-.alarm-item .alarm-actions {
- margin-top: 0.4em;
-}
-
-.alarm-item div.alarm-actions a {
- margin-right: 0.8em;
- text-decoration: none;
-}
-
-a.alarm-action-snooze:after {
- content: ' â¼';
- font-size: 10px;
- color: #666;
-}
-
-#alarm-snooze-dropdown {
- z-index: 5000;
-}
-
.ui-dialog-buttonset a.dropdown-link {
margin-right: 1em;
}
diff --git a/plugins/calendar/skins/larry/templates/calendar.html b/plugins/calendar/skins/larry/templates/calendar.html
index ecc6e5f..e4df042 100644
--- a/plugins/calendar/skins/larry/templates/calendar.html
+++ b/plugins/calendar/skins/larry/templates/calendar.html
@@ -161,10 +161,6 @@
<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>
-
<roundcube:object name="plugin.calendar_css" />
<script type="text/javascript">
diff --git a/plugins/libcalendaring/libcalendaring.js b/plugins/libcalendaring/libcalendaring.js
new file mode 100644
index 0000000..0d43384
--- /dev/null
+++ b/plugins/libcalendaring/libcalendaring.js
@@ -0,0 +1,443 @@
+/**
+ * Basic Javascript utilities for calendar-related plugins
+ *
+ * @version @package_version@
+ * @author Thomas Bruederli <bruederli at kolabsys.com>
+ *
+ * Copyright (C) 2012, Kolab Systems AG <contact at 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
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+function rcube_libcalendaring(settings)
+{
+ // member vars
+ this.settings = settings;
+ this.alarm_ids = [];
+ this.alarm_dialog = null;
+ this.snooze_popup = null;
+ this.dismiss_link = null;
+
+ // private vars
+ var me = this;
+ var gmt_offset = (new Date().getTimezoneOffset() / -60) - (settings.timezone || 0) - (settings.dst || 0);
+ var client_timezone = new Date().getTimezoneOffset();
+
+ // general datepicker settings
+ var datepicker_settings = {
+ // translate from fullcalendar format to datepicker format
+ dateFormat: settings.date_format.replace(/M/g, 'm').replace(/mmmmm/, 'MM').replace(/mmm/, 'M').replace(/dddd/, 'DD').replace(/ddd/, 'D').replace(/yy/g, 'y'),
+ firstDay : settings.first_day,
+ dayNamesMin: settings.days_short,
+ monthNames: settings.months,
+ monthNamesShort: settings.months,
+ changeMonth: false,
+ showOtherMonths: true,
+ selectOtherMonths: true
+ };
+
+
+ /**
+ * Quote html entities
+ */
+ var Q = this.quote_html = function(str)
+ {
+ return String(str).replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
+ };
+
+ /**
+ * Create a nice human-readable string for the date/time range
+ */
+ this.event_date_text = function(event)
+ {
+ if (!event.start)
+ return '';
+ if (!event.end)
+ event.end = event.start;
+
+ var fromto, duration = event.end.getTime() / 1000 - event.start.getTime() / 1000;
+ if (event.allDay) {
+ fromto = this.format_datetime(event.start, 1)
+ + (duration > 86400 || event.start.getDay() != event.end.getDay() ? ' — ' + this.format_datetime(event.end, 1) : '');
+ }
+ else if (duration < 86400 && event.start.getDay() == event.end.getDay()) {
+ fromto = this.format_datetime(event.start, 0)
+ + (duration > 0 ? ' — ' + this.format_datetime(event.end, 2) : '');
+ }
+ else {
+ fromto = this.format_datetime(event.start, 0)
+ + (duration > 0 ? ' — ' + this.format_datetime(event.end, 0) : '');
+ }
+
+ return fromto;
+ };
+
+
+ /**
+ * From time and date strings to a real date object
+ */
+ this.parse_datetime = function(time, date)
+ {
+ // we use the utility function from datepicker to parse dates
+ var date = date ? $.datepicker.parseDate(datepicker_settings.dateFormat, date, datepicker_settings) : new Date();
+
+ var time_arr = time.replace(/\s*[ap][.m]*/i, '').replace(/0([0-9])/g, '$1').split(/[:.]/);
+ if (!isNaN(time_arr[0])) {
+ date.setHours(time_arr[0]);
+ if (time.match(/p[.m]*/i) && date.getHours() < 12)
+ date.setHours(parseInt(time_arr[0]) + 12);
+ else if (time.match(/a[.m]*/i) && date.getHours() == 12)
+ date.setHours(0);
+ }
+ if (!isNaN(time_arr[1]))
+ date.setMinutes(time_arr[1]);
+
+ return date;
+ }
+
+ /**
+ * Convert an ISO 8601 formatted date string from the server into a Date object.
+ * Timezone information will be ignored, the server already provides dates in user's timezone.
+ */
+ function parseISO8601(s)
+ {
+ // force d to be on check's YMD, for daylight savings purposes
+ var fixDate = function(d, check) {
+ if (+d) { // prevent infinite looping on invalid dates
+ while (d.getDate() != check.getDate()) {
+ d.setTime(+d + (d < check ? 1 : -1) * 3600000);
+ }
+ }
+ }
+
+ // derived from http://delete.me.uk/2005/03/iso8601.html
+ var m = s.match(/^([0-9]{4})(-([0-9]{2})(-([0-9]{2})([T ]([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?(Z|(([-+])([0-9]{2})(:?([0-9]{2}))?))?)?)?)?$/);
+ if (!m) {
+ return null;
+ }
+
+ var date = new Date(m[1], 0, 1),
+ check = new Date(m[1], 0, 1, 9, 0);
+ if (m[3]) {
+ date.setMonth(m[3] - 1);
+ check.setMonth(m[3] - 1);
+ }
+ if (m[5]) {
+ date.setDate(m[5]);
+ check.setDate(m[5]);
+ }
+ fixDate(date, check);
+ if (m[7]) {
+ date.setHours(m[7]);
+ }
+ if (m[8]) {
+ date.setMinutes(m[8]);
+ }
+ if (m[10]) {
+ date.setSeconds(m[10]);
+ }
+ if (m[12]) {
+ date.setMilliseconds(Number("0." + m[12]) * 1000);
+ }
+ fixDate(date, check);
+
+ return date;
+ }
+
+ /**
+ * Format the given date object according to user's prefs
+ */
+ this.format_datetime = function(date, mode)
+ {
+ var res = '';
+ if (!mode || mode == 1)
+ res += $.datepicker.formatDate(datepicker_settings.dateFormat, date, datepicker_settings);
+ if (!mode)
+ res += ' ';
+ if (!mode || mode == 2)
+ res += this.format_time(date);
+
+ return res;
+ }
+
+ /**
+ * Clone from fullcalendar.js
+ */
+ this.format_time = function(date)
+ {
+ var zeroPad = function(n) { return (n < 10 ? '0' : '') + n; }
+ var formatters = {
+ s : function(d) { return d.getSeconds() },
+ ss : function(d) { return zeroPad(d.getSeconds()) },
+ m : function(d) { return d.getMinutes() },
+ mm : function(d) { return zeroPad(d.getMinutes()) },
+ h : function(d) { return d.getHours() % 12 || 12 },
+ hh : function(d) { return zeroPad(d.getHours() % 12 || 12) },
+ H : function(d) { return d.getHours() },
+ HH : function(d) { return zeroPad(d.getHours()) },
+ t : function(d) { return d.getHours() < 12 ? 'a' : 'p' },
+ tt : function(d) { return d.getHours() < 12 ? 'am' : 'pm' },
+ T : function(d) { return d.getHours() < 12 ? 'A' : 'P' },
+ TT : function(d) { return d.getHours() < 12 ? 'AM' : 'PM' }
+ };
+
+ var i, i2, c, formatter, res = '', format = settings['time_format'];
+ for (i=0; i < format.length; i++) {
+ c = format.charAt(i);
+ for (i2=Math.min(i+2, format.length); i2 > i; i2--) {
+ if (formatter = formatters[format.substring(i, i2)]) {
+ res += formatter(date);
+ i = i2 - 1;
+ break;
+ }
+ }
+ if (i2 == i) {
+ res += c;
+ }
+ }
+
+ return res;
+ }
+
+ /**
+ * Convert the given Date object into a unix timestamp respecting browser's and user's timezone settings
+ */
+ this.date2unixtime = function(date)
+ {
+ var dst_offset = (client_timezone - date.getTimezoneOffset()) * 60; // adjust DST offset
+ return Math.round(date.getTime()/1000 + gmt_offset * 3600 + dst_offset);
+ }
+
+ /**
+ * Turn a unix timestamp value into a Date object
+ */
+ this.fromunixtime = function(ts)
+ {
+ ts -= gmt_offset * 3600;
+ var date = new Date(ts * 1000),
+ dst_offset = (client_timezone - date.getTimezoneOffset()) * 60;
+ if (dst_offset) // adjust DST offset
+ date.setTime((ts + 3600) * 1000);
+ return date;
+ }
+
+ /**
+ * Simple plaintext to HTML converter, makig URLs clickable
+ */
+ this.text2html = function(str, maxlen, maxlines)
+ {
+ var html = Q(String(str));
+
+ // limit visible text length
+ if (maxlen) {
+ var morelink = ' <a href="#more" onclick="$(this).hide().next().show();return false" class="morelink">'+rcmail.gettext('showmore','libcalendaring')+'</a><span style="display:none">',
+ lines = html.split(/\r?\n/),
+ words, out = '', len = 0;
+
+ for (var i=0; i < lines.length; i++) {
+ len += lines[i].length;
+ if (maxlines && i == maxlines - 1) {
+ out += lines[i] + '\n' + morelink;
+ maxlen = html.length * 2;
+ }
+ else if (len > maxlen) {
+ len = out.length;
+ words = lines[i].split(' ');
+ for (var j=0; j < words.length; j++) {
+ len += words[j].length + 1;
+ out += words[j] + ' ';
+ if (len > maxlen) {
+ out += morelink;
+ maxlen = html.length * 2;
+ }
+ }
+ out += '\n';
+ }
+ else
+ out += lines[i] + '\n';
+ }
+
+ if (maxlen > str.length)
+ out += '</span>';
+
+ html = out;
+ }
+
+ // simple link parser (similar to rcube_string_replacer class in PHP)
+ var utf_domain = '[^?&@"\'/\\(\\)\\s\\r\\t\\n]+\\.([^\x00-\x2f\x3b-\x40\x5b-\x60\x7b-\x7f]{2,}|xn--[a-z0-9]{2,})';
+ var url1 = '.:;,', url2 = 'a-z0-9%=#@+?&/_~\\[\\]-';
+ var link_pattern = new RegExp('([hf]t+ps?://)('+utf_domain+'(['+url1+']?['+url2+']+)*)?', 'ig');
+ var mailto_pattern = new RegExp('([^\\s\\n\\(\\);]+@'+utf_domain+')', 'ig');
+
+ return html
+ .replace(link_pattern, '<a href="$1$2" target="_blank">$1$2</a>')
+ .replace(mailto_pattern, '<a href="mailto:$1">$1</a>')
+ .replace(/(mailto:)([^"]+)"/g, '$1$2" onclick="rcmail.command(\'compose\', \'$2\');return false"')
+ .replace(/\n/g, "<br/>");
+ };
+
+ this.init_alarms_edit = function(prefix)
+ {
+ // register events on alarm fields
+ $(prefix+' select.edit-alarm-type').change(function(){
+ $(this).parent().find('span.edit-alarm-values')[(this.selectedIndex>0?'show':'hide')]();
+ });
+ $(prefix+' select.edit-alarm-offset').change(function(){
+ var mode = $(this).val() == '@' ? 'show' : 'hide';
+ $(this).parent().find('.edit-alarm-date, .edit-alarm-time')[mode]();
+ $(this).parent().find('.edit-alarm-value').prop('disabled', mode == 'show');
+ });
+
+ $(prefix+' .edit-alarm-date').datepicker(datepicker_settings);
+ }
+
+
+ /***** Alarms handling *****/
+
+ /**
+ * Display a notification for the given pending alarms
+ */
+ this.display_alarms = function(alarms)
+ {
+ // clear old alert first
+ if (this.alarm_dialog)
+ this.alarm_dialog.dialog('destroy');
+
+ this.alarm_dialog = $('<div>').attr('id', 'alarm-display');
+
+ var actions, adismiss, asnooze, alarm, html, event_ids = [];
+ for (var actions, html, alarm, i=0; i < alarms.length; i++) {
+ alarm = alarms[i];
+ alarm.start = parseISO8601(alarm.start);
+ alarm.end = parseISO8601(alarm.end);
+ event_ids.push(alarm.id);
+
+ html = '<h3 class="event-title">' + Q(alarm.title) + '</h3>';
+ html += '<div class="event-section">' + Q(alarm.location || '') + '</div>';
+ html += '<div class="event-section">' + Q(this.event_date_text(alarm)) + '</div>';
+
+ adismiss = $('<a href="#" class="alarm-action-dismiss"></a>').html(rcmail.gettext('dismiss','libcalendaring')).click(function(){
+ me.dismiss_link = $(this);
+ me.dismiss_alarm(me.dismiss_link.data('id'), 0);
+ });
+ asnooze = $('<a href="#" class="alarm-action-snooze"></a>').html(rcmail.gettext('snooze','libcalendaring')).click(function(e){
+ me.snooze_dropdown($(this));
+ e.stopPropagation();
+ return false;
+ });
+ actions = $('<div>').addClass('alarm-actions').append(adismiss.data('id', alarm.id)).append(asnooze.data('id', alarm.id));
+
+ $('<div>').addClass('alarm-item').html(html).append(actions).appendTo(this.alarm_dialog);
+ }
+
+ var buttons = {};
+ buttons[rcmail.gettext('dismissall','libcalendaring')] = function() {
+ // submit dismissed event_ids to server
+ me.dismiss_alarm(me.alarm_ids.join(','), 0);
+ $(this).dialog('close');
+ };
+
+ this.alarm_dialog.appendTo(document.body).dialog({
+ modal: false,
+ resizable: true,
+ closeOnEscape: false,
+ dialogClass: 'alarm',
+ title: '<span class="ui-icon ui-icon-alarms" style="float:left; margin:0 4px 0 0"></span>' + rcmail.gettext('alarmtitle','libcalendaring'),
+ buttons: buttons,
+ close: function() {
+ $('#alarm-snooze-dropdown').hide();
+ $(this).dialog('destroy').remove();
+ me.alarm_dialog = null;
+ me.alarm_ids = null;
+ },
+ drag: function(event, ui) {
+ $('#alarm-snooze-dropdown').hide();
+ }
+ });
+
+ this.alarm_ids = event_ids;
+ };
+
+ /**
+ * Show a drop-down menu with a selection of snooze times
+ */
+ this.snooze_dropdown = function(link)
+ {
+ if (!this.snooze_popup) {
+ this.snooze_popup = $('#alarm-snooze-dropdown');
+ // create popup if not found
+ if (!this.snooze_popup.length) {
+ this.snooze_popup = $('<div>').attr('id', 'alarm-snooze-dropdown').addClass('popupmenu').appendTo(document.body);
+ this.snooze_popup.html(rcmail.env.snooze_select)
+ }
+ $('#alarm-snooze-dropdown a').click(function(e){
+ var time = String(this.href).replace(/.+#/, '');
+ me.dismiss_alarm($('#alarm-snooze-dropdown').data('id'), time);
+ return false;
+ });
+ }
+
+ // hide visible popup
+ if (this.snooze_popup.is(':visible') && this.snooze_popup.data('id') == link.data('id')) {
+ this.snooze_popup.hide();
+ this.dismiss_link = null;
+ }
+ else { // open popup below the clicked link
+ var pos = link.offset();
+ pos.top += link.height() + 2;
+ this.snooze_popup.data('id', link.data('id')).css({ top:Math.floor(pos.top)+'px', left:Math.floor(pos.left)+'px' }).show();
+ this.dismiss_link = link;
+ }
+ };
+
+ /**
+ * Dismiss or snooze alarms for the given event
+ */
+ this.dismiss_alarm = function(id, snooze)
+ {
+ $('#alarm-snooze-dropdown').hide();
+ rcmail.http_post('utils/plugin.alarms', { action:'dismiss', data:{ id:id, snooze:snooze } });
+
+ // remove dismissed alarm from list
+ if (this.dismiss_link) {
+ this.dismiss_link.closest('div.alarm-item').hide();
+ var new_ids = jQuery.grep(this.alarm_ids, function(v){ return v != id; });
+ if (new_ids.length)
+ this.alarm_ids = new_ids;
+ else
+ this.alarm_dialog.dialog('close');
+ }
+
+ this.dismiss_link = null;
+ };
+}
+
+
+// extend jQuery
+(function($){
+ $.fn.serializeJSON = function(){
+ var json = {};
+ jQuery.map($(this).serializeArray(), function(n, i) {
+ json[n['name']] = n['value'];
+ });
+ return json;
+ };
+})(jQuery);
+
+
+/* libcalendaring plugin initialization */
+window.rcmail && rcmail.addEventListener('init', function(evt) {
+ var libcal = new rcube_libcalendaring(rcmail.env.libcal_settings);
+ rcmail.addEventListener('plugin.display_alarms', function(alarms){ libcal.display_alarms(alarms); });
+});
diff --git a/plugins/libcalendaring/libcalendaring.php b/plugins/libcalendaring/libcalendaring.php
new file mode 100644
index 0000000..c7cf6e8
--- /dev/null
+++ b/plugins/libcalendaring/libcalendaring.php
@@ -0,0 +1,547 @@
+<?php
+
+/**
+ * Library providing common functions for calendaring plugins
+ *
+ * Provides utility functions for calendar-related modules such as
+ * - alarms display and dismissal
+ * - recurrence computation and UI elements
+ * - ical parsing and exporting
+ *
+ * @version @package_version@
+ * @author Thomas Bruederli <bruederli at kolabsys.com>
+ *
+ * Copyright (C) 2012, Kolab Systems AG <contact at 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
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+class libcalendaring extends rcube_plugin
+{
+ public $rc;
+ public $timezone;
+ public $gmt_offset;
+ public $dst_active;
+ public $timezone_offset;
+
+ public $defaults = array(
+ 'calendar_date_format' => "yyyy-MM-dd",
+ 'calendar_date_short' => "M-d",
+ 'calendar_date_long' => "MMM d yyyy",
+ 'calendar_date_agenda' => "ddd MM-dd",
+ 'calendar_time_format' => "HH:mm",
+ 'calendar_first_day' => 1,
+ 'calendar_first_hour' => 6,
+ 'calendar_date_format_sets' => array(
+ 'yyyy-MM-dd' => array('MMM d yyyy', 'M-d', 'ddd MM-dd'),
+ 'dd-MM-yyyy' => array('d MMM yyyy', 'd-M', 'ddd dd-MM'),
+ 'yyyy/MM/dd' => array('MMM d yyyy', 'M/d', 'ddd MM/dd'),
+ 'MM/dd/yyyy' => array('MMM d yyyy', 'M/d', 'ddd MM/dd'),
+ 'dd/MM/yyyy' => array('d MMM yyyy', 'd/M', 'ddd dd/MM'),
+ 'dd.MM.yyyy' => array('dd. MMM yyyy', 'd.M', 'ddd dd.MM.'),
+ 'd.M.yyyy' => array('d. MMM yyyy', 'd.M', 'ddd d.MM.'),
+ ),
+ );
+
+ private static $instance;
+
+ /**
+ * Singleton getter to allow direct access from other plugins
+ */
+ public static function get_instance()
+ {
+ return self::$instance;
+ }
+
+ /**
+ * Required plugin startup method
+ */
+ public function init()
+ {
+ self::$instance = $this;
+
+ $this->rc = rcmail::get_instance();
+
+ // set user's timezone
+ $this->timezone = new DateTimeZone($this->rc->config->get('timezone', 'GMT'));
+ $now = new DateTime('now', $this->timezone);
+ $this->gmt_offset = $now->getOffset();
+ $this->dst_active = $now->format('I');
+ $this->timezone_offset = $this->gmt_offset / 3600 - $this->dst_active;
+
+ $this->add_texts('localization/', false);
+
+ // include client scripts and styles
+ $this->include_script('libcalendaring.js');
+ $this->rc->output->set_env('libcal_settings', $this->load_settings());
+
+ $this->include_stylesheet($this->local_skin_path() . '/libcal.css');
+
+ // add hook to display alarms
+ $this->add_hook('keep_alive', array($this, 'keep_alive'));
+ $this->register_action('plugin.alarms', array($this, 'alarms_action'));
+ }
+
+
+ /**
+ * Shift dates into user's current timezone
+ *
+ * @param mixed Any kind of a date representation (DateTime object, string or unix timestamp)
+ * @return object DateTime object in user's timezone
+ */
+ public function adjust_timezone($dt)
+ {
+ if (is_numeric($dt))
+ $dt = new DateTime('@'.$td);
+ else if (is_string($dt))
+ $dt = new DateTime($dt);
+
+ $dt->setTimezone($this->timezone);
+ return $dt;
+ }
+
+
+ /**
+ *
+ */
+ public function load_settings()
+ {
+ $this->date_format_defaults();
+ $settings = array();
+
+ // configuration
+ $settings['date_format'] = (string)$this->rc->config->get('calendar_date_format', $this->defaults['calendar_date_format']);
+ $settings['time_format'] = (string)$this->rc->config->get('calendar_time_format', $this->defaults['calendar_time_format']);
+ $settings['date_short'] = (string)$this->rc->config->get('calendar_date_short', $this->defaults['calendar_date_short']);
+ $settings['date_long'] = (string)$this->rc->config->get('calendar_date_long', $this->defaults['calendar_date_long']);
+ $settings['dates_long'] = str_replace(' yyyy', '[ yyyy]', $settings['date_long']) . "{ '—' " . $settings['date_long'] . '}';
+ $settings['first_day'] = (int)$this->rc->config->get('calendar_first_day', $this->defaults['calendar_first_day']);
+
+ $settings['timezone'] = $this->timezone_offset;
+ $settings['dst'] = $this->dst_active;
+
+ // localization
+ $settings['days'] = array(
+ rcube_label('sunday'), rcube_label('monday'),
+ rcube_label('tuesday'), rcube_label('wednesday'),
+ rcube_label('thursday'), rcube_label('friday'),
+ rcube_label('saturday')
+ );
+ $settings['days_short'] = array(
+ rcube_label('sun'), rcube_label('mon'),
+ rcube_label('tue'), rcube_label('wed'),
+ rcube_label('thu'), rcube_label('fri'),
+ rcube_label('sat')
+ );
+ $settings['months'] = array(
+ $this->rc->gettext('longjan'), $this->rc->gettext('longfeb'),
+ $this->rc->gettext('longmar'), $this->rc->gettext('longapr'),
+ $this->rc->gettext('longmay'), $this->rc->gettext('longjun'),
+ $this->rc->gettext('longjul'), $this->rc->gettext('longaug'),
+ $this->rc->gettext('longsep'), $this->rc->gettext('longoct'),
+ $this->rc->gettext('longnov'), $this->rc->gettext('longdec')
+ );
+ $settings['months_short'] = array(
+ $this->rc->gettext('jan'), $this->rc->gettext('feb'),
+ $this->rc->gettext('mar'), $this->rc->gettext('apr'),
+ $this->rc->gettext('may'), $this->rc->gettext('jun'),
+ $this->rc->gettext('jul'), $this->rc->gettext('aug'),
+ $this->rc->gettext('sep'), $this->rc->gettext('oct'),
+ $this->rc->gettext('nov'), $this->rc->gettext('dec')
+ );
+ $settings['today'] = $this->rc->gettext('today');
+
+ // define list of file types which can be displayed inline
+ // same as in program/steps/mail/show.inc
+ $mimetypes = $this->rc->config->get('client_mimetypes', 'text/plain,text/html,text/xml,image/jpeg,image/gif,image/png,application/x-javascript,application/pdf,application/x-shockwave-flash');
+ $settings['mimetypes'] = is_string($mimetypes) ? explode(',', $mimetypes) : (array)$mimetypes;
+
+ return $settings;
+ }
+
+
+ /**
+ * Helper function to set date/time format according to config and user preferences
+ */
+ private function date_format_defaults()
+ {
+ static $defaults = array();
+
+ // nothing to be done
+ if (isset($defaults['date_format']))
+ return;
+
+ $defaults['date_format'] = $this->rc->config->get('calendar_date_format', self::from_php_date_format($this->rc->config->get('date_format')));
+ $defaults['time_format'] = $this->rc->config->get('calendar_time_format', self::from_php_date_format($this->rc->config->get('time_format')));
+
+ // override defaults
+ if ($defaults['date_format'])
+ $this->defaults['calendar_date_format'] = $defaults['date_format'];
+ if ($defaults['time_format'])
+ $this->defaults['calendar_time_format'] = $defaults['time_format'];
+
+ // derive format variants from basic date format
+ $format_sets = $this->rc->config->get('calendar_date_format_sets', $this->defaults['calendar_date_format_sets']);
+ if ($format_set = $format_sets[$this->defaults['calendar_date_format']]) {
+ $this->defaults['calendar_date_long'] = $format_set[0];
+ $this->defaults['calendar_date_short'] = $format_set[1];
+ $this->defaults['calendar_date_agenda'] = $format_set[2];
+ }
+ }
+
+ /**
+ * Compose a date string for the given event
+ */
+ public function event_date_text($event, $tzinfo = false)
+ {
+ $fromto = '';
+ $duration = $event['start']->diff($event['end'])->format('s');
+
+ $this->date_format_defaults();
+ $date_format = self::to_php_date_format($this->rc->config->get('calendar_date_format', $this->defaults['calendar_date_format']));
+ $time_format = self::to_php_date_format($this->rc->config->get('calendar_time_format', $this->defaults['calendar_time_format']));
+
+ if ($event['allday']) {
+ $fromto = format_date($event['start'], $date_format);
+ if (($todate = format_date($event['end'], $date_format)) != $fromto)
+ $fromto .= ' - ' . $todate;
+ }
+ else if ($duration < 86400 && $event['start']->format('d') == $event['end']->format('d')) {
+ $fromto = format_date($event['start'], $date_format) . ' ' . format_date($event['start'], $time_format) .
+ ' - ' . format_date($event['end'], $time_format);
+ }
+ else {
+ $fromto = format_date($event['start'], $date_format) . ' ' . format_date($event['start'], $time_format) .
+ ' - ' . format_date($event['end'], $date_format) . ' ' . format_date($event['end'], $time_format);
+ }
+
+ // add timezone information
+ if ($tzinfo && ($tzname = $this->timezone->getName())) {
+ $fromto .= ' (' . strtr($tzname, '_', ' ') . ')';
+ }
+
+ return $fromto;
+ }
+
+
+ /**
+ * Render HTML form for alarm configuration
+ */
+ public function alarm_select($attrib, $alarm_types)
+ {
+ unset($attrib['name']);
+ $select_type = new html_select(array('name' => 'alarmtype[]', 'class' => 'edit-alarm-type'));
+ $select_type->add($this->gettext('none'), '');
+ foreach ($alarm_types as $type)
+ $select_type->add($this->gettext(strtolower("alarm{$type}option")), $type);
+
+ $input_value = new html_inputfield(array('name' => 'alarmvalue[]', 'class' => 'edit-alarm-value', 'size' => 3));
+ $input_date = new html_inputfield(array('name' => 'alarmdate[]', 'class' => 'edit-alarm-date', 'size' => 10));
+ $input_time = new html_inputfield(array('name' => 'alarmtime[]', 'class' => 'edit-alarm-time', 'size' => 6));
+
+ $select_offset = new html_select(array('name' => 'alarmoffset[]', 'class' => 'edit-alarm-offset'));
+ foreach (array('-M','-H','-D','+M','+H','+D','@') as $trigger)
+ $select_offset->add($this->gettext('trigger' . $trigger), $trigger);
+
+ // pre-set with default values from user settings
+ $preset = self::parse_alaram_value($this->rc->config->get('calendar_default_alarm_offset', '-15M'));
+ $hidden = array('style' => 'display:none');
+ $html = html::span('edit-alarm-set',
+ $select_type->show($this->rc->config->get('calendar_default_alarm_type', '')) . ' ' .
+ html::span(array('class' => 'edit-alarm-values', 'style' => 'display:none'),
+ $input_value->show($preset[0]) . ' ' .
+ $select_offset->show($preset[1]) . ' ' .
+ $input_date->show('', $hidden) . ' ' .
+ $input_time->show('', $hidden)
+ )
+ );
+
+ // TODO: support adding more alarms
+ #$html .= html::a(array('href' => '#', 'id' => 'edit-alam-add', 'title' => $this->gettext('addalarm')),
+ # $attrib['addicon'] ? html::img(array('src' => $attrib['addicon'], 'alt' => 'add')) : '(+)');
+
+ return $html;
+ }
+
+
+ /********* Alarms handling *********/
+
+ /**
+ * Helper function to convert alarm trigger strings
+ * into two-field values (e.g. "-45M" => 45, "-M")
+ */
+ public static function parse_alaram_value($val)
+ {
+ if ($val[0] == '@')
+ return array(substr($val, 1));
+ else if (preg_match('/([+-])(\d+)([HMD])/', $val, $m))
+ return array($m[2], $m[1].$m[3]);
+
+ return false;
+ }
+
+ /**
+ * Render localized text for alarm settings
+ */
+ public static function alarms_text($alarm)
+ {
+ list($trigger, $action) = explode(':', $alarm);
+
+ $text = '';
+ switch ($action) {
+ case 'EMAIL':
+ $text = rcube_label('libcalendaring.alarmemail');
+ break;
+ case 'DISPLAY':
+ $text = rcube_label('libcalendaring.alarmdisplay');
+ break;
+ }
+
+ if (preg_match('/@(\d+)/', $trigger, $m)) {
+ $text .= ' ' . rcube_label(array('name' => 'libcalendaring.alarmat', 'vars' => array('datetime' => format_date($m[1]))));
+ }
+ else if ($val = self::parse_alaram_value($trigger)) {
+ $text .= ' ' . intval($val[0]) . ' ' . rcube_label('libcalendaring.trigger' . $val[1]);
+ }
+ else
+ return false;
+
+ return $text;
+ }
+
+ /**
+ * Get the next alarm (time & action) for the given event
+ *
+ * @param array Record data
+ * @return array Hash array with alarm time/type or null if no alarms are configured
+ */
+ public static function get_next_alarm($rec, $type = 'event')
+ {
+ if (!$rec['alarms'])
+ return null;
+
+ if ($type == 'task') {
+ $timezone = self::get_instance()->timezone;
+ if ($rec['date'])
+ $rec['start'] = new DateTime($rec['date'] . ' ' . ($rec['time'] ?: '12:00'), $timezone);
+ if ($rec['startdate'])
+ $rec['end'] = new DateTime($rec['startdate'] . ' ' . ($rec['starttime'] ?: '12:00'), $timezone);
+ }
+
+ if (!$rec['end'])
+ $rec['end'] = $rec['start'];
+
+
+ // TODO: handle multiple alarms (currently not supported)
+ list($trigger, $action) = explode(':', $rec['alarms'], 2);
+
+ $notify = self::parse_alaram_value($trigger);
+ if (!empty($notify[1])){ // offset
+ $mult = 1;
+ switch ($notify[1]) {
+ case '-S': $mult = -1; break;
+ case '+S': $mult = 1; break;
+ case '-M': $mult = -60; break;
+ case '+M': $mult = 60; break;
+ case '-H': $mult = -3600; break;
+ case '+H': $mult = 3600; break;
+ case '-D': $mult = -86400; break;
+ case '+D': $mult = 86400; break;
+ case '-W': $mult = -604800; break;
+ case '+W': $mult = 604800; break;
+ }
+ $offset = $notify[0] * $mult;
+ $refdate = $mult > 0 ? $rec['end'] : $rec['start'];
+ $notify_at = $refdate->format('U') + $offset;
+ }
+ else { // absolute timestamp
+ $notify_at = $notify[0];
+ }
+
+ return array('time' => $notify_at, 'action' => $action ? strtoupper($action) : 'DISPLAY');
+ }
+
+ /**
+ * Handler for keep-alive requests
+ * This will check for pending notifications and pass them to the client
+ */
+ public function keep_alive($attr)
+ {
+ // collect pending alarms from all providers (e.g. calendar, tasks)
+ $plugin = $this->rc->plugins->exec_hook('pending_alarms', array(
+ 'time' => time(),
+ 'alarms' => $alarms,
+ ));
+
+ if (!$plugin['abort'] && $plugin['alarms']) {
+ // make sure texts and env vars are available on client
+ $this->add_texts('localization/', true);
+ $this->rc->output->set_env('snooze_select', $this->snooze_select());
+ $this->rc->output->command('plugin.display_alarms', $this->_alarms_output($plugin['alarms']));
+ }
+ }
+
+ /**
+ * Handler for alarm dismiss/snooze requests
+ */
+ public function alarms_action()
+ {
+ $action = get_input_value('action', RCUBE_INPUT_GPC);
+ $data = get_input_value('data', RCUBE_INPUT_POST, true);
+
+ $data['ids'] = explode(',', $data['id']);
+ $plugin = $this->rc->plugins->exec_hook('dismiss_alarms', $data);
+
+ if ($plugin['success'])
+ $this->rc->output->show_message('successfullysaved', 'confirmation');
+ else
+ $this->rc->output->show_message('calendar.errorsaving', 'error');
+ }
+
+ /**
+ * Generate reduced and streamlined output for pending alarms
+ */
+ private function _alarms_output($alarms)
+ {
+ $out = array();
+ foreach ($alarms as $alarm) {
+ $out[] = array(
+ 'id' => $alarm['id'],
+ 'start' => $alarm['start'] ? $this->adjust_timezone($alarm['start'])->format('c') : '',
+ 'end' => $alarm['end'] ? $this->adjust_timezone($alarm['end'])->format('c') : '',
+ 'allDay' => ($alarm['allday'] == 1)?true:false,
+ 'title' => $alarm['title'],
+ 'location' => $alarm['location'],
+ 'calendar' => $alarm['calendar'],
+ );
+ }
+
+ return $out;
+ }
+
+ /**
+ * Render a dropdown menu to choose snooze time
+ */
+ private function snooze_select($attrib = array())
+ {
+ $steps = array(
+ 5 => 'repeatinmin',
+ 10 => 'repeatinmin',
+ 15 => 'repeatinmin',
+ 20 => 'repeatinmin',
+ 30 => 'repeatinmin',
+ 60 => 'repeatinhr',
+ 120 => 'repeatinhrs',
+ 1440 => 'repeattomorrow',
+ 10080 => 'repeatinweek',
+ );
+
+ $items = array();
+ foreach ($steps as $n => $label) {
+ $items[] = html::tag('li', null, html::a(array('href' => "#" . ($n * 60), 'class' => 'active'),
+ $this->gettext(array('name' => $label, 'vars' => array('min' => $n % 60, 'hrs' => intval($n / 60))))));
+ }
+
+ return html::tag('ul', $attrib + array('class' => 'toolbarmenu'), join("\n", $items), html::$common_attrib);
+ }
+
+ /**
+ * Convert the internal structured data into a vcalendar rrule 2.0 string
+ */
+ public static function to_rrule($recurrence)
+ {
+ if (is_string($recurrence))
+ return $recurrence;
+
+ $rrule = '';
+ foreach ((array)$recurrence as $k => $val) {
+ $k = strtoupper($k);
+ switch ($k) {
+ case 'UNTIL':
+ $val = $val->format('Ymd\THis');
+ break;
+ case 'EXDATE':
+ foreach ((array)$val as $i => $ex)
+ $val[$i] = $ex->format('Ymd\THis');
+ $val = join(',', (array)$val);
+ break;
+ }
+ $rrule .= $k . '=' . $val . ';';
+ }
+
+ return rtrim($rrule, ';');
+ }
+
+ /**
+ * Convert from fullcalendar date format to PHP date() format string
+ */
+ public static function to_php_date_format($from)
+ {
+ // "dd.MM.yyyy HH:mm:ss" => "d.m.Y H:i:s"
+ return strtr(strtr($from, array(
+ 'yyyy' => 'Y',
+ 'yy' => 'y',
+ 'MMMM' => 'F',
+ 'MMM' => 'M',
+ 'MM' => 'm',
+ 'M' => 'n',
+ 'dddd' => 'l',
+ 'ddd' => 'D',
+ 'dd' => 'd',
+ 'HH' => '**',
+ 'hh' => '%%',
+ 'H' => 'G',
+ 'h' => 'g',
+ 'mm' => 'i',
+ 'ss' => 's',
+ 'TT' => 'A',
+ 'tt' => 'a',
+ 'T' => 'A',
+ 't' => 'a',
+ 'u' => 'c',
+ )), array(
+ '**' => 'H',
+ '%%' => 'h',
+ ));
+ }
+
+ /**
+ * Convert from PHP date() format to fullcalendar format string
+ */
+ public static function from_php_date_format($from)
+ {
+ // "d.m.Y H:i:s" => "dd.MM.yyyy HH:mm:ss"
+ return strtr($from, array(
+ 'y' => 'yy',
+ 'Y' => 'yyyy',
+ 'M' => 'MMM',
+ 'F' => 'MMMM',
+ 'm' => 'MM',
+ 'n' => 'M',
+ 'd' => 'dd',
+ 'D' => 'ddd',
+ 'l' => 'dddd',
+ 'H' => 'HH',
+ 'h' => 'hh',
+ 'G' => 'H',
+ 'g' => 'h',
+ 'i' => 'mm',
+ 's' => 'ss',
+ 'A' => 'TT',
+ 'a' => 'tt',
+ 'c' => 'u',
+ ));
+ }
+
+}
diff --git a/plugins/libcalendaring/localization/de_CH.inc b/plugins/libcalendaring/localization/de_CH.inc
new file mode 100644
index 0000000..3760931
--- /dev/null
+++ b/plugins/libcalendaring/localization/de_CH.inc
@@ -0,0 +1,29 @@
+<?php
+
+$labels = array();
+
+$labels['alarmemail'] = 'E-Mail senden';
+$labels['alarmdisplay'] = 'Nachricht anzeigen';
+$labels['alarmdisplayoption'] = 'Nachricht';
+$labels['alarmemailoption'] = 'E-Mail';
+$labels['alarmat'] = 'um $datetime';
+$labels['trigger@'] = 'genau um';
+$labels['trigger-M'] = 'Minuten davor';
+$labels['trigger-H'] = 'Stunden davor';
+$labels['trigger-D'] = 'Tage davor';
+$labels['trigger+M'] = 'Minuten danach';
+$labels['trigger+H'] = 'Stunden danach';
+$labels['trigger+D'] = 'Tage danach';
+$labels['addalarm'] = 'Erinnerung hinzufügen';
+
+$labels['alarmtitle'] = 'Anstehende Termine';
+$labels['dismissall'] = 'Alle ignorieren';
+$labels['dismiss'] = 'Ignorieren';
+$labels['snooze'] = 'Später erinnern';
+$labels['repeatinmin'] = 'Wiederholung in $min Minuten';
+$labels['repeatinhr'] = 'Wiederholung in 1 Stunde';
+$labels['repeatinhrs'] = 'Wiederholung in $hrs Stunden';
+$labels['repeattomorrow'] = 'Wiederholung morgen';
+$labels['repeatinweek'] = 'Wiederholung in einer Woche';
+
+$labels['showmore'] = 'Mehr anzeigen...';
diff --git a/plugins/libcalendaring/localization/de_DE.inc b/plugins/libcalendaring/localization/de_DE.inc
new file mode 100644
index 0000000..3760931
--- /dev/null
+++ b/plugins/libcalendaring/localization/de_DE.inc
@@ -0,0 +1,29 @@
+<?php
+
+$labels = array();
+
+$labels['alarmemail'] = 'E-Mail senden';
+$labels['alarmdisplay'] = 'Nachricht anzeigen';
+$labels['alarmdisplayoption'] = 'Nachricht';
+$labels['alarmemailoption'] = 'E-Mail';
+$labels['alarmat'] = 'um $datetime';
+$labels['trigger@'] = 'genau um';
+$labels['trigger-M'] = 'Minuten davor';
+$labels['trigger-H'] = 'Stunden davor';
+$labels['trigger-D'] = 'Tage davor';
+$labels['trigger+M'] = 'Minuten danach';
+$labels['trigger+H'] = 'Stunden danach';
+$labels['trigger+D'] = 'Tage danach';
+$labels['addalarm'] = 'Erinnerung hinzufügen';
+
+$labels['alarmtitle'] = 'Anstehende Termine';
+$labels['dismissall'] = 'Alle ignorieren';
+$labels['dismiss'] = 'Ignorieren';
+$labels['snooze'] = 'Später erinnern';
+$labels['repeatinmin'] = 'Wiederholung in $min Minuten';
+$labels['repeatinhr'] = 'Wiederholung in 1 Stunde';
+$labels['repeatinhrs'] = 'Wiederholung in $hrs Stunden';
+$labels['repeattomorrow'] = 'Wiederholung morgen';
+$labels['repeatinweek'] = 'Wiederholung in einer Woche';
+
+$labels['showmore'] = 'Mehr anzeigen...';
diff --git a/plugins/libcalendaring/localization/en_US.inc b/plugins/libcalendaring/localization/en_US.inc
new file mode 100644
index 0000000..4a4eee2
--- /dev/null
+++ b/plugins/libcalendaring/localization/en_US.inc
@@ -0,0 +1,29 @@
+<?php
+
+$labels = array();
+
+$labels['alarmemail'] = 'Send Email';
+$labels['alarmdisplay'] = 'Show message';
+$labels['alarmdisplayoption'] = 'Message';
+$labels['alarmemailoption'] = 'Email';
+$labels['alarmat'] = 'at $datetime';
+$labels['trigger@'] = 'on date';
+$labels['trigger-M'] = 'minutes before';
+$labels['trigger-H'] = 'hours before';
+$labels['trigger-D'] = 'days before';
+$labels['trigger+M'] = 'minutes after';
+$labels['trigger+H'] = 'hours after';
+$labels['trigger+D'] = 'days after';
+$labels['addalarm'] = 'add alarm';
+$labels['alarmtitle'] = 'Upcoming events';
+$labels['dismissall'] = 'Dismiss all';
+$labels['dismiss'] = 'Dismiss';
+$labels['snooze'] = 'Snooze';
+$labels['repeatinmin'] = 'Repeat in $min minutes';
+$labels['repeatinhr'] = 'Repeat in 1 hour';
+$labels['repeatinhrs'] = 'Repeat in $hrs hours';
+$labels['repeattomorrow'] = 'Repeat tomorrow';
+$labels['repeatinweek'] = 'Repeat in a week';
+
+$labels['showmore'] = 'Show more...';
+
diff --git a/plugins/libcalendaring/localization/pl_PL.inc b/plugins/libcalendaring/localization/pl_PL.inc
new file mode 100644
index 0000000..4b9c11a
--- /dev/null
+++ b/plugins/libcalendaring/localization/pl_PL.inc
@@ -0,0 +1,28 @@
+<?php
+
+$labels = array();
+
+$labels['alarmemail'] = 'WyÅlij pocztÄ';
+$labels['alarmdisplay'] = 'Pokaż wiadomoÅÄ';
+$labels['alarmdisplayoption'] = 'WiadomoÅÄ';
+$labels['alarmemailoption'] = 'Poczta';
+$labels['alarmat'] = 'o $datetime';
+$labels['trigger@'] = 'w dniu';
+$labels['trigger-M'] = 'minuty przed';
+$labels['trigger-H'] = 'godziny przed';
+$labels['trigger-D'] = 'dni przed';
+$labels['trigger+M'] = 'minut po';
+$labels['trigger+H'] = 'godziny po';
+$labels['trigger+D'] = 'dni po';
+$labels['addalarm'] = 'dodaj alarm';
+
+$labels['alarmtitle'] = 'NadchodzÄ
ce zdarzenia';
+$labels['dismissall'] = 'OdrzuÄ wszystkie';
+$labels['dismiss'] = 'OdrzuÄ';
+$labels['snooze'] = 'OdÅóż';
+$labels['repeatinmin'] = 'Powtórz po $min minutach';
+$labels['repeatinhr'] = 'Powtórz po godzinie';
+$labels['repeatinhrs'] = 'Powtórz po $hrs godzinach';
+$labels['repeattomorrow'] = 'Powtórz jutro';
+$labels['repeatinweek'] = 'Powtórz za tydzieÅ';
+
diff --git a/plugins/libcalendaring/skins/larry/libcal.css b/plugins/libcalendaring/skins/larry/libcal.css
new file mode 100644
index 0000000..1e3a4c8
--- /dev/null
+++ b/plugins/libcalendaring/skins/larry/libcal.css
@@ -0,0 +1,54 @@
+/**
+ * Roundcube libcalendaring plugin styles for skin "Larry"
+ *
+ * Copyright (c) 2012, Kolab Systems AG <contact at kolabsys.com>
+ *
+ * The contents are subject to the Creative Commons Attribution-ShareAlike
+ * License. It is allowed to copy, distribute, transmit and to adapt the work
+ * by keeping credits to the original autors in the README file.
+ * See http://creativecommons.org/licenses/by-sa/3.0/ for details.
+ */
+
+.alarm-item {
+ margin: 0.4em 0 1em 0;
+}
+
+.alarm-item .event-title {
+ font-size: 14px;
+ margin: 0.1em 0 0.3em 0;
+}
+
+.alarm-item div.event-section {
+ margin-top: 0.1em;
+ margin-bottom: 0.3em;
+}
+
+.alarm-item .alarm-actions {
+ margin-top: 0.4em;
+}
+
+.alarm-item div.alarm-actions a {
+ margin-right: 0.8em;
+ text-decoration: none;
+}
+
+a.alarm-action-snooze:after {
+ content: ' â¼';
+ font-size: 10px;
+ color: #666;
+}
+
+#alarm-snooze-dropdown {
+ z-index: 5000;
+}
+
+span.edit-alarm-set {
+ white-space: nowrap;
+}
+
+.ui-widget-header .ui-dialog-title .ui-icon-alarms {
+ background-image: url(../../../../skins/larry/images/messages.png);
+ background-position: 0 -91px;
+ width: 20px;
+ height: 16px;
+}
diff --git a/plugins/tasklist/drivers/database/tasklist_database_driver.php b/plugins/tasklist/drivers/database/tasklist_database_driver.php
index e2c9687..3cbddb3 100644
--- a/plugins/tasklist/drivers/database/tasklist_database_driver.php
+++ b/plugins/tasklist/drivers/database/tasklist_database_driver.php
@@ -365,9 +365,8 @@ class tasklist_database_driver extends tasklist_driver
$result = $this->rc->db->query(sprintf(
"SELECT * FROM " . $this->db_tasks . "
WHERE tasklist_id IN (%s)
- AND notify <= %s AND date > %s",
+ AND notify <= %s AND complete < 1",
join(',', $list_ids),
- $this->rc->db->fromunixtime($time),
$this->rc->db->fromunixtime($time)
));
@@ -575,20 +574,8 @@ class tasklist_database_driver extends tasklist_driver
*/
private function _get_notification($task)
{
- // fake object properties to suit the expectations of calendar::get_next_alarm()
- // TODO: move all that to libcalendaring plugin
- if ($task['date'])
- $task['start'] = new DateTime($task['date'] . ' ' . ($task['time'] ?: '12:00'), $this->plugin->timezone);
- if ($task['startdate'])
- $task['end'] = new DateTime($task['startdate'] . ' ' . ($task['starttime'] ?: '12:00'), $this->plugin->timezone);
- else
- $task['end'] = $tast['start'];
-
- if (!$task['start'])
- $task['end'] = $task['start'];
-
- if ($task['alarms'] && $task['start'] > new DateTime() || strpos($task['alarms'], '@') !== false) {
- $alarm = calendar::get_next_alarm($task);
+ if ($task['alarms'] && $task['complete'] < 1 || strpos($task['alarms'], '@') !== false) {
+ $alarm = calendarlibcalendaring::get_next_alarm($task);
if ($alarm['time'] && $alarm['action'] == 'DISPLAY')
return date('Y-m-d H:i:s', $alarm['time']);
diff --git a/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php b/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php
index ec104d0..74442bd 100644
--- a/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php
+++ b/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php
@@ -370,7 +370,7 @@ class tasklist_kolab_driver extends tasklist_driver
$time = $slot + $interval;
$tasks = array();
- $query = array(array('tags', '=', 'x-has-alarms'));
+ $query = array(array('tags', '=', 'x-has-alarms'), array('tags', '!=', 'x-complete'));
foreach ($this->lists as $lid => $list) {
// skip lists with alarms disabled
if (!$list['showalarms'] || ($lists && !in_array($lid, $lists)))
@@ -383,20 +383,8 @@ class tasklist_kolab_driver extends tasklist_driver
$task = $this->_to_rcube_task($record);
- // fake object properties to suit the expectations of calendar::get_next_alarm()
- // TODO: move all that to libcalendaring plugin
- if ($task['date'])
- $task['start'] = new DateTime($task['date'] . ' ' . ($task['time'] ?: '12:00'), $this->plugin->timezone);
- if ($task['startdate'])
- $task['end'] = new DateTime($task['startdate'] . ' ' . ($task['starttime'] ?: '12:00'), $this->plugin->timezone);
- else
- $task['end'] = $tast['start'];
-
- if (!$task['start'])
- $task['end'] = $task['start'];
-
// add to list if alarm is set
- $alarm = calendar::get_next_alarm($task);
+ $alarm = libcalendaring::get_next_alarm($task, 'task');
if ($alarm && $alarm['time'] && $alarm['time'] <= $time && $alarm['action'] == 'DISPLAY') {
$id = $task['id'];
$tasks[$id] = $task;
diff --git a/plugins/tasklist/skins/larry/tasklist.css b/plugins/tasklist/skins/larry/tasklist.css
index df82cc7..b502361 100644
--- a/plugins/tasklist/skins/larry/tasklist.css
+++ b/plugins/tasklist/skins/larry/tasklist.css
@@ -8,8 +8,6 @@
* License. It is allowed to copy, distribute, transmit and to adapt the work
* by keeping credits to the original autors in the README file.
* See http://creativecommons.org/licenses/by-sa/3.0/ for details.
- *
- * $Id$
*/
#taskbar a.button-tasklist span.button-inner {
@@ -27,6 +25,10 @@ ul.toolbarmenu li span.icon.taskadd {
background-position: -4px -90px;
}
+div.uidialog {
+ display: none;
+}
+
#sidebar {
position: absolute;
top: 0;
diff --git a/plugins/tasklist/tasklist.js b/plugins/tasklist/tasklist.js
index 3727d08..6842d8b 100644
--- a/plugins/tasklist/tasklist.js
+++ b/plugins/tasklist/tasklist.js
@@ -22,6 +22,9 @@
function rcube_tasklist_ui(settings)
{
+ // extend base class
+ rcube_libcalendaring.call(this, settings);
+
/* constants */
var FILTER_MASK_ALL = 0;
var FILTER_MASK_TODAY = 1;
@@ -93,6 +96,15 @@ function rcube_tasklist_ui(settings)
this.list_edit_dialog = list_edit_dialog;
this.unlock_saving = unlock_saving;
+ /* imports */
+ var Q = this.quote_html;
+ var text2html = this.text2html;
+ var event_date_text = this.event_date_text;
+ var format_datetime = this.format_datetime;
+ var parse_datetime = this.parse_datetime;
+ var date2unixtime = this.date2unixtime;
+ var fromunixtime = this.fromunixtime;
+ var init_alarms_edit = this.init_alarms_edit;
/**
* initialize the tasks UI
@@ -318,16 +330,9 @@ function rcube_tasklist_ui(settings)
});
// register events on alarm fields
- $('#taskedit select.edit-alarm-type').change(function(){
- $(this).parent().find('span.edit-alarm-values')[(this.selectedIndex>0?'show':'hide')]();
- });
- $('#taskedit select.edit-alarm-offset').change(function(){
- var mode = $(this).val() == '@' ? 'show' : 'hide';
- $(this).parent().find('.edit-alarm-date, .edit-alarm-time')[mode]();
- $(this).parent().find('.edit-alarm-value').prop('disabled', mode == 'show');
- });
+ init_alarms_edit('#taskedit');
- $('#taskedit-date, #taskedit-startdate, #taskedit .edit-alarm-date').datepicker(datepicker_settings);
+ $('#taskedit-date, #taskedit-startdate').datepicker(datepicker_settings);
$('a.edit-nodate').click(function(){
var sel = $(this).attr('rel');
@@ -370,7 +375,7 @@ function rcube_tasklist_ui(settings)
rcmail.http_request('fetch', { filter:basefilter, lists:active.join(','), q:search_query }, true);
}
else if (reload)
- data_ready([]);
+ data_ready({ data:[], lists:'', filter:basefilter, search:search_query });
else
render_tasklist();
@@ -688,7 +693,7 @@ function rcube_tasklist_ui(settings)
{
var drag_id = draggable.data('id'),
parent_id = $(this).data('id'),
- drag_rec = listdata[drag_id],
+ drag_rec = listdata[drag_id] || {},
drop_rec = listdata[parent_id];
if (drop_rec && drop_rec.list != drag_rec.list)
@@ -1263,70 +1268,6 @@ function rcube_tasklist_ui(settings)
/**** Utility functions ****/
/**
- * quote html entities
- */
- function Q(str)
- {
- return String(str).replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
- }
-
- /**
- * Name says it all
- * (cloned from calendar plugin)
- */
- function text2html(str, maxlen, maxlines)
- {
- var html = Q(String(str));
-
- // limit visible text length
- if (maxlen) {
- var morelink = ' <a href="#more" onclick="$(this).hide().next().show();return false" class="morelink">'+rcmail.gettext('showmore','tasklist')+'</a><span style="display:none">',
- lines = html.split(/\r?\n/),
- words, out = '', len = 0;
-
- for (var i=0; i < lines.length; i++) {
- len += lines[i].length;
- if (maxlines && i == maxlines - 1) {
- out += lines[i] + '\n' + morelink;
- maxlen = html.length * 2;
- }
- else if (len > maxlen) {
- len = out.length;
- words = lines[i].split(' ');
- for (var j=0; j < words.length; j++) {
- len += words[j].length + 1;
- out += words[j] + ' ';
- if (len > maxlen) {
- out += morelink;
- maxlen = html.length * 2;
- }
- }
- out += '\n';
- }
- else
- out += lines[i] + '\n';
- }
-
- if (maxlen > str.length)
- out += '</span>';
-
- html = out;
- }
-
- // simple link parser (similar to rcube_string_replacer class in PHP)
- var utf_domain = '[^?&@"\'/\\(\\)\\s\\r\\t\\n]+\\.([^\x00-\x2f\x3b-\x40\x5b-\x60\x7b-\x7f]{2,}|xn--[a-z0-9]{2,})';
- var url1 = '.:;,', url2 = 'a-z0-9%=#@+?&/_~\\[\\]-';
- var link_pattern = new RegExp('([hf]t+ps?://)('+utf_domain+'(['+url1+']?['+url2+']+)*)?', 'ig');
- var mailto_pattern = new RegExp('([^\\s\\n\\(\\);]+@'+utf_domain+')', 'ig');
-
- return html
- .replace(link_pattern, '<a href="$1$2" target="_blank">$1$2</a>')
- .replace(mailto_pattern, '<a href="mailto:$1">$1</a>')
- .replace(/(mailto:)([^"]+)"/g, '$1$2" onclick="rcmail.command(\'compose\', \'$2\');return false"')
- .replace(/\n/g, "<br/>");
- }
-
- /**
* Clear any text selection
* (text is probably selected when double-clicking somewhere)
*/
@@ -1415,85 +1356,12 @@ function rcube_tasklist_ui(settings)
}
- /**** calendaring utility functions *****/
- /* TO BE MOVED TO libcalendaring plugin */
-
- var gmt_offset = (new Date().getTimezoneOffset() / -60) - (rcmail.env.calendar_settings.timezone || 0) - (rcmail.env.calendar_settings.dst || 0);
- var client_timezone = new Date().getTimezoneOffset();
-
- /**
- * from time and date strings to a real date object
- */
- function parse_datetime(time, date)
- {
- // we use the utility function from datepicker to parse dates
- var date = date ? $.datepicker.parseDate(datepicker_settings.dateFormat, date, datepicker_settings) : new Date();
-
- var time_arr = time.replace(/\s*[ap][.m]*/i, '').replace(/0([0-9])/g, '$1').split(/[:.]/);
- if (!isNaN(time_arr[0])) {
- date.setHours(time_arr[0]);
- if (time.match(/p[.m]*/i) && date.getHours() < 12)
- date.setHours(parseInt(time_arr[0]) + 12);
- else if (time.match(/a[.m]*/i) && date.getHours() == 12)
- date.setHours(0);
- }
- if (!isNaN(time_arr[1]))
- date.setMinutes(time_arr[1]);
-
- return date;
- }
-
- /**
- * Format the given date object according to user's prefs
- */
- function format_datetime(date, mode)
- {
- var format =
- mode == 2 ? rcmail.env.calendar_settings['time_format'] :
- (mode == 1 ? rcmail.env.calendar_settings['date_format'] :
- rcmail.env.calendar_settings['date_format'] + ' '+ rcmail.env.calendar_settings['time_format']);
-
- return $.fullCalendar.formatDate(date, format);
- }
-
- /**
- * convert the given Date object into a unix timestamp respecting browser's and user's timezone settings
- */
- function date2unixtime(date)
- {
- var dst_offset = (client_timezone - date.getTimezoneOffset()) * 60; // adjust DST offset
- return Math.round(date.getTime()/1000 + gmt_offset * 3600 + dst_offset);
- }
-
- /**
- *
- */
- function fromunixtime(ts)
- {
- ts -= gmt_offset * 3600;
- var date = new Date(ts * 1000),
- dst_offset = (client_timezone - date.getTimezoneOffset()) * 60;
- if (dst_offset) // adjust DST offset
- date.setTime((ts + 3600) * 1000);
- return date;
- }
-
// init dialog by default
init_taskedit();
}
// extend jQuery
-(function($){
- $.fn.serializeJSON = function(){
- var json = {};
- jQuery.map($(this).serializeArray(), function(n, i) {
- json[n['name']] = n['value'];
- });
- return json;
- };
-})(jQuery);
-
// from http://james.padolsey.com/javascript/sorting-elements-with-jquery/
jQuery.fn.sortElements = (function(){
var sort = [].sort;
@@ -1518,7 +1386,7 @@ jQuery.fn.sortElements = (function(){
var rctasks;
window.rcmail && rcmail.addEventListener('init', function(evt) {
- rctasks = new rcube_tasklist_ui(rcmail.env.tasklist_settings);
+ rctasks = new rcube_tasklist_ui(rcmail.env.libcal_settings);
// register button commands
rcmail.register_command('newtask', function(){ rctasks.edit_task(null, 'new', {}); }, true);
diff --git a/plugins/tasklist/tasklist.php b/plugins/tasklist/tasklist.php
index 9589354..a2647fb 100644
--- a/plugins/tasklist/tasklist.php
+++ b/plugins/tasklist/tasklist.php
@@ -46,23 +46,21 @@ class tasklist extends rcube_plugin
public $task = '?(?!login|logout).*';
public $rc;
+ public $lib;
public $driver;
public $timezone;
public $ui;
- public $defaults = array(
- 'date_format' => "Y-m-d",
- 'time_format' => "H:i",
- 'first_day' => 1,
- );
-
/**
* Plugin initialization.
*/
function init()
{
+ $this->require_plugin('libcalendaring');
+
$this->rc = rcmail::get_instance();
+ $this->lib = libcalendaring::get_instance();
$this->register_task('tasks', 'tasklist');
@@ -72,6 +70,8 @@ class tasklist extends rcube_plugin
// load localizations
$this->add_texts('localization/', $this->rc->task == 'tasks' && (!$this->rc->action || $this->rc->action == 'print'));
+ $this->timezone = $this->lib->timezone;
+
if ($this->rc->task == 'tasks' && $this->rc->action != 'save-pref') {
$this->load_driver();
@@ -141,14 +141,11 @@ class tasklist extends rcube_plugin
$this->driver = new $driver_class($this);
break;
}
-
- // get user's timezone
- $this->timezone = new DateTimeZone($this->rc->config->get('timezone', 'GMT'));
}
/**
- *
+ * Dispatcher for task-related actions initiated by the client
*/
public function task_action()
{
@@ -345,7 +342,7 @@ class tasklist extends rcube_plugin
/**
- *
+ * Dispatcher for tasklist actions initiated by the client
*/
public function tasklist_action()
{
@@ -389,7 +386,7 @@ class tasklist extends rcube_plugin
}
/**
- *
+ * Get counts for active tasks divided into different selectors
*/
public function fetch_counts()
{
@@ -400,8 +397,6 @@ class tasklist extends rcube_plugin
/**
* Adjust the cached counts after changing a task
- *
- *
*/
public function update_counts($oldrec, $newrec)
{
@@ -510,7 +505,7 @@ class tasklist extends rcube_plugin
}
if ($rec['alarms'])
- $rec['alarms_text'] = calendar::alarms_text($rec['alarms']);
+ $rec['alarms_text'] = libcalendaring::alarms_text($rec['alarms']);
foreach ((array)$rec['attachments'] as $k => $attachment) {
$rec['attachments'][$k]['classname'] = rcmail_filetype2classname($attachment['mimetype'], $attachment['name']);
diff --git a/plugins/tasklist/tasklist_base.js b/plugins/tasklist/tasklist_base.js
index 3b1673b..e3a889c 100644
--- a/plugins/tasklist/tasklist_base.js
+++ b/plugins/tasklist/tasklist_base.js
@@ -80,7 +80,7 @@ function rcube_tasklist(settings)
/* tasklist plugin initialization (for email task) */
window.rcmail && rcmail.env.task == 'mail' && rcmail.addEventListener('init', function(evt) {
- var tasks = new rcube_tasklist(rcmail.env.tasklist_settings);
+ var tasks = new rcube_tasklist(rcmail.env.libcal_settings);
rcmail.register_command('tasklist-create-from-mail', function() { tasks.create_from_mail() });
rcmail.addEventListener('plugin.mail2taskdialog', function(p){ tasks.mail2taskdialog(p) });
diff --git a/plugins/tasklist/tasklist_ui.php b/plugins/tasklist/tasklist_ui.php
index 330e7a6..5ee6c64 100644
--- a/plugins/tasklist/tasklist_ui.php
+++ b/plugins/tasklist/tasklist_ui.php
@@ -55,14 +55,7 @@ class tasklist_ui
$this->plugin->include_script('tasklist_base.js');
// copy config to client
- $defaults = $this->plugin->defaults;
- $settings = array(
- 'date_format' => $this->rc->config->get('date_format', $defaults['date_format']),
- 'time_format' => $this->rc->config->get('time_format', $defaults['time_format']),
- 'first_day' => $this->rc->config->get('calendar_first_day', $defaults['first_day']),
- );
-
- $this->rc->output->set_env('tasklist_settings', $settings);
+ // $this->rc->output->set_env('tasklist_settings', $settings);
$this->ready = true;
}
@@ -86,13 +79,6 @@ class tasklist_ui
$this->plugin->register_handler('plugin.attachments_list', array($this, 'attachments_list'));
$this->plugin->register_handler('plugin.filedroparea', array($this, 'file_drop_area'));
- // define list of file types which can be displayed inline
- // same as in program/steps/mail/show.inc
- $mimetypes = $this->rc->config->get('client_mimetypes', 'text/plain,text/html,text/xml,image/jpeg,image/gif,image/png,application/x-javascript,application/pdf,application/x-shockwave-flash');
- $settings = $this->rc->output->get_env('tasklist_settings');
- $settings['mimetypes'] = is_string($mimetypes) ? explode(',', $mimetypes) : (array)$mimetypes;
- $this->rc->output->set_env('tasklist_settings', $settings);
-
$this->plugin->include_script('jquery.tagedit.js');
$this->plugin->include_script('tasklist.js');
}
@@ -185,34 +171,7 @@ class tasklist_ui
*/
function alarm_select($attrib = array())
{
- unset($attrib['name']);
- $select_type = new html_select(array('name' => 'alarmtype[]', 'class' => 'edit-alarm-type'));
- $select_type->add(rcube_label('none'), '');
- foreach ($this->plugin->driver->alarm_types as $type)
- $select_type->add(rcube_label(strtolower("calendar.alarm{$type}option")), $type);
-
- $input_value = new html_inputfield(array('name' => 'alarmvalue[]', 'class' => 'edit-alarm-value', 'size' => 3));
- $input_date = new html_inputfield(array('name' => 'alarmdate[]', 'class' => 'edit-alarm-date', 'size' => 10));
- $input_time = new html_inputfield(array('name' => 'alarmtime[]', 'class' => 'edit-alarm-time', 'size' => 6));
-
- $select_offset = new html_select(array('name' => 'alarmoffset[]', 'class' => 'edit-alarm-offset'));
- foreach (array('-M','-H','-D','+M','+H','+D','@') as $trigger)
- $select_offset->add(rcube_label('calendar.trigger' . $trigger), $trigger);
-
- // pre-set with default values from user settings
- $preset = calendar::parse_alaram_value($this->rc->config->get('calendar_default_alarm_offset', '-15M'));
- $hidden = array('style' => 'display:none');
- $html = html::span('edit-alarm-set',
- $select_type->show($this->rc->config->get('calendar_default_alarm_type', '')) . ' ' .
- html::span(array('class' => 'edit-alarm-values', 'style' => 'display:none'),
- $input_value->show($preset[0]) . ' ' .
- $select_offset->show($preset[1]) . ' ' .
- $input_date->show('', $hidden) . ' ' .
- $input_time->show('', $hidden)
- )
- );
-
- return $html;
+ return $this->plugin->lib->alarm_select($attrib, $this->plugin->driver->alarm_types);
}
/**
More information about the commits
mailing list