Refactoring: move common calendaring-related functionality into a new plugin 'libcalendaring'

This commit is contained in:
Thomas Bruederli 2012-08-16 08:57:25 +02:00
parent 0b82e6bac6
commit 9cc400f09f
30 changed files with 1274 additions and 1074 deletions

View file

@ -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:
}
$plugin = $this->rc->plugins->exec_hook('pending_alarms', array(
'time' => $timestamp,
'alarms' => $alarms,
));
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());
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;
}
$this->rc->output->command('plugin.display_alarms', $this->_alarms_output($plugin['alarms']));
}
return $p;
}
/**
* 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']);
}
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']) . "{ '&mdash;' " . $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']),
@ -1187,56 +1090,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
*/
@ -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,148 +1134,7 @@ 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

View file

@ -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, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
};
// 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() ? ' &mdash; ' + $.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 ? ' &mdash; ' + $.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 ? ' &mdash; ' + $.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);

View file

@ -67,64 +67,18 @@ function rcube_calendar_ui(settings)
selectOtherMonths: true
};
/*** 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;
/*** private methods ***/
var Q = this.quote_html;
var event_date_text = this.event_date_text;
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/>");
};
// 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

View file

@ -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']);

View file

@ -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;

View file

@ -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;

View file

@ -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'],

View file

@ -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)

View file

@ -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']));

View file

@ -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';

View file

@ -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';

View file

@ -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';

View file

@ -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';

View file

@ -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=" " />

View file

@ -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@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;
}

View file

@ -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">

View file

@ -0,0 +1,443 @@
/**
* Basic Javascript utilities for calendar-related plugins
*
* @version @package_version@
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* 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, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
};
/**
* 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() ? ' &mdash; ' + 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 ? ' &mdash; ' + this.format_datetime(event.end, 2) : '');
}
else {
fromto = this.format_datetime(event.start, 0)
+ (duration > 0 ? ' &mdash; ' + 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); });
});

View file

@ -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@kolabsys.com>
*
* Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* 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']) . "{ '&mdash;' " . $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',
));
}
}

View file

@ -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...';

View file

@ -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...';

View file

@ -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...';

View file

@ -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ń';

View file

@ -0,0 +1,54 @@
/**
* Roundcube libcalendaring plugin styles for skin "Larry"
*
* Copyright (c) 2012, Kolab Systems AG <contact@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;
}

View file

@ -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']);

View file

@ -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;

View file

@ -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;

View file

@ -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)
@ -1262,70 +1267,6 @@ function rcube_tasklist_ui(settings)
/**** Utility functions ****/
/**
* quote html entities
*/
function Q(str)
{
return String(str).replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}
/**
* 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);

View file

@ -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']);

View file

@ -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) });

View file

@ -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);
}
/**