Refactoring: move common calendaring-related functionality into a new plugin 'libcalendaring'
This commit is contained in:
parent
0b82e6bac6
commit
9cc400f09f
30 changed files with 1274 additions and 1074 deletions
|
@ -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']) . "{ '—' " . $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
|
||||
|
|
|
@ -25,158 +25,16 @@
|
|||
// Basic setup for Roundcube calendar client class
|
||||
function rcube_calendar(settings)
|
||||
{
|
||||
// extend base class
|
||||
rcube_libcalendaring.call(this, settings);
|
||||
|
||||
// member vars
|
||||
this.ui;
|
||||
this.ui_loaded = false;
|
||||
this.settings = settings;
|
||||
this.alarm_ids = [];
|
||||
this.alarm_dialog = null;
|
||||
this.snooze_popup = null;
|
||||
this.dismiss_link = null;
|
||||
|
||||
// private vars
|
||||
var me = this;
|
||||
|
||||
// quote html entities
|
||||
var Q = this.quote_html = function(str)
|
||||
{
|
||||
return String(str).replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
};
|
||||
|
||||
// create a nice human-readable string for the date/time range
|
||||
this.event_date_text = function(event)
|
||||
{
|
||||
if (!event.start)
|
||||
return '';
|
||||
if (!event.end)
|
||||
event.end = event.start;
|
||||
|
||||
var fromto, duration = event.end.getTime() / 1000 - event.start.getTime() / 1000;
|
||||
if (event.allDay) {
|
||||
fromto = $.fullCalendar.formatDate(event.start, settings['date_format'])
|
||||
+ (duration > 86400 || event.start.getDay() != event.end.getDay() ? ' — ' + $.fullCalendar.formatDate(event.end, settings['date_format']) : '');
|
||||
}
|
||||
else if (duration < 86400 && event.start.getDay() == event.end.getDay()) {
|
||||
fromto = $.fullCalendar.formatDate(event.start, settings['date_format']) + ' ' + $.fullCalendar.formatDate(event.start, settings['time_format'])
|
||||
+ (duration > 0 ? ' — ' + $.fullCalendar.formatDate(event.end, settings['time_format']) : '');
|
||||
}
|
||||
else {
|
||||
fromto = $.fullCalendar.formatDate(event.start, settings['date_format']) + ' ' + $.fullCalendar.formatDate(event.start, settings['time_format'])
|
||||
+ (duration > 0 ? ' — ' + $.fullCalendar.formatDate(event.end, settings['date_format']) + ' ' + $.fullCalendar.formatDate(event.end, settings['time_format']) : '');
|
||||
}
|
||||
|
||||
return fromto;
|
||||
};
|
||||
|
||||
// display a notification for the given pending alarms
|
||||
this.display_alarms = function(alarms) {
|
||||
// clear old alert first
|
||||
if (this.alarm_dialog)
|
||||
this.alarm_dialog.dialog('destroy');
|
||||
|
||||
this.alarm_dialog = $('<div>').attr('id', 'alarm-display');
|
||||
|
||||
var actions, adismiss, asnooze, alarm, html, event_ids = [];
|
||||
for (var actions, html, alarm, i=0; i < alarms.length; i++) {
|
||||
alarm = alarms[i];
|
||||
alarm.start = $.fullCalendar.parseISO8601(alarm.start, true);
|
||||
alarm.end = $.fullCalendar.parseISO8601(alarm.end, true);
|
||||
event_ids.push(alarm.id);
|
||||
|
||||
html = '<h3 class="event-title">' + Q(alarm.title) + '</h3>';
|
||||
html += '<div class="event-section">' + Q(alarm.location || '') + '</div>';
|
||||
html += '<div class="event-section">' + Q(this.event_date_text(alarm)) + '</div>';
|
||||
|
||||
adismiss = $('<a href="#" class="alarm-action-dismiss"></a>').html(rcmail.gettext('dismiss','calendar')).click(function(){
|
||||
me.dismiss_link = $(this);
|
||||
me.dismiss_alarm(me.dismiss_link.data('id'), 0);
|
||||
});
|
||||
asnooze = $('<a href="#" class="alarm-action-snooze"></a>').html(rcmail.gettext('snooze','calendar')).click(function(e){
|
||||
me.snooze_dropdown($(this));
|
||||
e.stopPropagation();
|
||||
return false;
|
||||
});
|
||||
actions = $('<div>').addClass('alarm-actions').append(adismiss.data('id', alarm.id)).append(asnooze.data('id', alarm.id));
|
||||
|
||||
$('<div>').addClass('alarm-item').html(html).append(actions).appendTo(this.alarm_dialog);
|
||||
}
|
||||
|
||||
var buttons = {};
|
||||
buttons[rcmail.gettext('dismissall','calendar')] = function() {
|
||||
// submit dismissed event_ids to server
|
||||
me.dismiss_alarm(me.alarm_ids.join(','), 0);
|
||||
$(this).dialog('close');
|
||||
};
|
||||
|
||||
this.alarm_dialog.appendTo(document.body).dialog({
|
||||
modal: false,
|
||||
resizable: true,
|
||||
closeOnEscape: false,
|
||||
dialogClass: 'alarm',
|
||||
title: '<span class="ui-icon ui-icon-alert" style="float:left; margin:0 4px 0 0"></span>' + rcmail.gettext('alarmtitle', 'calendar'),
|
||||
buttons: buttons,
|
||||
close: function() {
|
||||
$('#alarm-snooze-dropdown').hide();
|
||||
$(this).dialog('destroy').remove();
|
||||
me.alarm_dialog = null;
|
||||
me.alarm_ids = null;
|
||||
},
|
||||
drag: function(event, ui) {
|
||||
$('#alarm-snooze-dropdown').hide();
|
||||
}
|
||||
});
|
||||
this.alarm_ids = event_ids;
|
||||
};
|
||||
|
||||
// show a drop-down menu with a selection of snooze times
|
||||
this.snooze_dropdown = function(link)
|
||||
{
|
||||
if (!this.snooze_popup) {
|
||||
this.snooze_popup = $('#alarm-snooze-dropdown');
|
||||
// create popup if not found
|
||||
if (!this.snooze_popup.length) {
|
||||
this.snooze_popup = $('<div>').attr('id', 'alarm-snooze-dropdown').addClass('popupmenu').appendTo(document.body);
|
||||
this.snooze_popup.html(rcmail.env.snooze_select)
|
||||
}
|
||||
$('#alarm-snooze-dropdown a').click(function(e){
|
||||
var time = String(this.href).replace(/.+#/, '');
|
||||
me.dismiss_alarm($('#alarm-snooze-dropdown').data('id'), time);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
// hide visible popup
|
||||
if (this.snooze_popup.is(':visible') && this.snooze_popup.data('id') == link.data('id')) {
|
||||
this.snooze_popup.hide();
|
||||
this.dismiss_link = null;
|
||||
}
|
||||
else { // open popup below the clicked link
|
||||
var pos = link.offset();
|
||||
pos.top += link.height() + 2;
|
||||
this.snooze_popup.data('id', link.data('id')).css({ top:Math.floor(pos.top)+'px', left:Math.floor(pos.left)+'px' }).show();
|
||||
this.dismiss_link = link;
|
||||
}
|
||||
};
|
||||
|
||||
// dismiss or snooze alarms for the given event
|
||||
this.dismiss_alarm = function(id, snooze)
|
||||
{
|
||||
$('#alarm-snooze-dropdown').hide();
|
||||
rcmail.http_post('calendar/event', { action:'dismiss', e:{ id:id, snooze:snooze } });
|
||||
|
||||
// remove dismissed alarm from list
|
||||
if (this.dismiss_link) {
|
||||
this.dismiss_link.closest('div.alarm-item').hide();
|
||||
var new_ids = jQuery.grep(this.alarm_ids, function(v){ return v != id; });
|
||||
if (new_ids.length)
|
||||
this.alarm_ids = new_ids;
|
||||
else
|
||||
this.alarm_dialog.dialog('close');
|
||||
}
|
||||
|
||||
this.dismiss_link = null;
|
||||
};
|
||||
|
||||
// create new event from current mail message
|
||||
this.create_from_mail = function()
|
||||
{
|
||||
|
@ -186,6 +44,7 @@ function rcube_calendar(settings)
|
|||
if (!this.ui_loaded) {
|
||||
$.when(
|
||||
$.getScript('./plugins/calendar/calendar_ui.js'),
|
||||
$.getScript('./plugins/calendar/lib/js/fullcalendar.js'),
|
||||
$.get(rcmail.url('calendar/inlineui'), function(html){ $(document.body).append(html); }, 'html')
|
||||
).then(function() {
|
||||
// register attachments form
|
||||
|
@ -261,24 +120,11 @@ rcube_calendar.fetch_event_rsvp_status = function(event)
|
|||
};
|
||||
|
||||
|
||||
// extend jQuery
|
||||
(function($){
|
||||
$.fn.serializeJSON = function(){
|
||||
var json = {};
|
||||
jQuery.map($(this).serializeArray(), function(n, i) {
|
||||
json[n['name']] = n['value'];
|
||||
});
|
||||
return json;
|
||||
};
|
||||
})(jQuery);
|
||||
|
||||
/* calendar plugin initialization (for non-calendar tasks) */
|
||||
window.rcmail && rcmail.addEventListener('init', function(evt) {
|
||||
if (rcmail.task != 'calendar') {
|
||||
var cal = new rcube_calendar(rcmail.env.calendar_settings);
|
||||
var cal = new rcube_calendar($.extend(rcmail.env.calendar_settings, rcmail.env.libcal_settings));
|
||||
|
||||
rcmail.addEventListener('plugin.display_alarms', function(alarms){ cal.display_alarms(alarms); });
|
||||
|
||||
rcmail.addEventListener('plugin.update_event_rsvp_status', function(p){
|
||||
if (p.html)
|
||||
$('#loading-'+p.id).hide().after(p.html);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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']);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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'],
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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']));
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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=" " />
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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">
|
||||
|
|
443
plugins/libcalendaring/libcalendaring.js
Normal file
443
plugins/libcalendaring/libcalendaring.js
Normal 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, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a nice human-readable string for the date/time range
|
||||
*/
|
||||
this.event_date_text = function(event)
|
||||
{
|
||||
if (!event.start)
|
||||
return '';
|
||||
if (!event.end)
|
||||
event.end = event.start;
|
||||
|
||||
var fromto, duration = event.end.getTime() / 1000 - event.start.getTime() / 1000;
|
||||
if (event.allDay) {
|
||||
fromto = this.format_datetime(event.start, 1)
|
||||
+ (duration > 86400 || event.start.getDay() != event.end.getDay() ? ' — ' + this.format_datetime(event.end, 1) : '');
|
||||
}
|
||||
else if (duration < 86400 && event.start.getDay() == event.end.getDay()) {
|
||||
fromto = this.format_datetime(event.start, 0)
|
||||
+ (duration > 0 ? ' — ' + this.format_datetime(event.end, 2) : '');
|
||||
}
|
||||
else {
|
||||
fromto = this.format_datetime(event.start, 0)
|
||||
+ (duration > 0 ? ' — ' + this.format_datetime(event.end, 0) : '');
|
||||
}
|
||||
|
||||
return fromto;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* From time and date strings to a real date object
|
||||
*/
|
||||
this.parse_datetime = function(time, date)
|
||||
{
|
||||
// we use the utility function from datepicker to parse dates
|
||||
var date = date ? $.datepicker.parseDate(datepicker_settings.dateFormat, date, datepicker_settings) : new Date();
|
||||
|
||||
var time_arr = time.replace(/\s*[ap][.m]*/i, '').replace(/0([0-9])/g, '$1').split(/[:.]/);
|
||||
if (!isNaN(time_arr[0])) {
|
||||
date.setHours(time_arr[0]);
|
||||
if (time.match(/p[.m]*/i) && date.getHours() < 12)
|
||||
date.setHours(parseInt(time_arr[0]) + 12);
|
||||
else if (time.match(/a[.m]*/i) && date.getHours() == 12)
|
||||
date.setHours(0);
|
||||
}
|
||||
if (!isNaN(time_arr[1]))
|
||||
date.setMinutes(time_arr[1]);
|
||||
|
||||
return date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an ISO 8601 formatted date string from the server into a Date object.
|
||||
* Timezone information will be ignored, the server already provides dates in user's timezone.
|
||||
*/
|
||||
function parseISO8601(s)
|
||||
{
|
||||
// force d to be on check's YMD, for daylight savings purposes
|
||||
var fixDate = function(d, check) {
|
||||
if (+d) { // prevent infinite looping on invalid dates
|
||||
while (d.getDate() != check.getDate()) {
|
||||
d.setTime(+d + (d < check ? 1 : -1) * 3600000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// derived from http://delete.me.uk/2005/03/iso8601.html
|
||||
var m = s.match(/^([0-9]{4})(-([0-9]{2})(-([0-9]{2})([T ]([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?(Z|(([-+])([0-9]{2})(:?([0-9]{2}))?))?)?)?)?$/);
|
||||
if (!m) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var date = new Date(m[1], 0, 1),
|
||||
check = new Date(m[1], 0, 1, 9, 0);
|
||||
if (m[3]) {
|
||||
date.setMonth(m[3] - 1);
|
||||
check.setMonth(m[3] - 1);
|
||||
}
|
||||
if (m[5]) {
|
||||
date.setDate(m[5]);
|
||||
check.setDate(m[5]);
|
||||
}
|
||||
fixDate(date, check);
|
||||
if (m[7]) {
|
||||
date.setHours(m[7]);
|
||||
}
|
||||
if (m[8]) {
|
||||
date.setMinutes(m[8]);
|
||||
}
|
||||
if (m[10]) {
|
||||
date.setSeconds(m[10]);
|
||||
}
|
||||
if (m[12]) {
|
||||
date.setMilliseconds(Number("0." + m[12]) * 1000);
|
||||
}
|
||||
fixDate(date, check);
|
||||
|
||||
return date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the given date object according to user's prefs
|
||||
*/
|
||||
this.format_datetime = function(date, mode)
|
||||
{
|
||||
var res = '';
|
||||
if (!mode || mode == 1)
|
||||
res += $.datepicker.formatDate(datepicker_settings.dateFormat, date, datepicker_settings);
|
||||
if (!mode)
|
||||
res += ' ';
|
||||
if (!mode || mode == 2)
|
||||
res += this.format_time(date);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone from fullcalendar.js
|
||||
*/
|
||||
this.format_time = function(date)
|
||||
{
|
||||
var zeroPad = function(n) { return (n < 10 ? '0' : '') + n; }
|
||||
var formatters = {
|
||||
s : function(d) { return d.getSeconds() },
|
||||
ss : function(d) { return zeroPad(d.getSeconds()) },
|
||||
m : function(d) { return d.getMinutes() },
|
||||
mm : function(d) { return zeroPad(d.getMinutes()) },
|
||||
h : function(d) { return d.getHours() % 12 || 12 },
|
||||
hh : function(d) { return zeroPad(d.getHours() % 12 || 12) },
|
||||
H : function(d) { return d.getHours() },
|
||||
HH : function(d) { return zeroPad(d.getHours()) },
|
||||
t : function(d) { return d.getHours() < 12 ? 'a' : 'p' },
|
||||
tt : function(d) { return d.getHours() < 12 ? 'am' : 'pm' },
|
||||
T : function(d) { return d.getHours() < 12 ? 'A' : 'P' },
|
||||
TT : function(d) { return d.getHours() < 12 ? 'AM' : 'PM' }
|
||||
};
|
||||
|
||||
var i, i2, c, formatter, res = '', format = settings['time_format'];
|
||||
for (i=0; i < format.length; i++) {
|
||||
c = format.charAt(i);
|
||||
for (i2=Math.min(i+2, format.length); i2 > i; i2--) {
|
||||
if (formatter = formatters[format.substring(i, i2)]) {
|
||||
res += formatter(date);
|
||||
i = i2 - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i2 == i) {
|
||||
res += c;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given Date object into a unix timestamp respecting browser's and user's timezone settings
|
||||
*/
|
||||
this.date2unixtime = function(date)
|
||||
{
|
||||
var dst_offset = (client_timezone - date.getTimezoneOffset()) * 60; // adjust DST offset
|
||||
return Math.round(date.getTime()/1000 + gmt_offset * 3600 + dst_offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn a unix timestamp value into a Date object
|
||||
*/
|
||||
this.fromunixtime = function(ts)
|
||||
{
|
||||
ts -= gmt_offset * 3600;
|
||||
var date = new Date(ts * 1000),
|
||||
dst_offset = (client_timezone - date.getTimezoneOffset()) * 60;
|
||||
if (dst_offset) // adjust DST offset
|
||||
date.setTime((ts + 3600) * 1000);
|
||||
return date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple plaintext to HTML converter, makig URLs clickable
|
||||
*/
|
||||
this.text2html = function(str, maxlen, maxlines)
|
||||
{
|
||||
var html = Q(String(str));
|
||||
|
||||
// limit visible text length
|
||||
if (maxlen) {
|
||||
var morelink = ' <a href="#more" onclick="$(this).hide().next().show();return false" class="morelink">'+rcmail.gettext('showmore','libcalendaring')+'</a><span style="display:none">',
|
||||
lines = html.split(/\r?\n/),
|
||||
words, out = '', len = 0;
|
||||
|
||||
for (var i=0; i < lines.length; i++) {
|
||||
len += lines[i].length;
|
||||
if (maxlines && i == maxlines - 1) {
|
||||
out += lines[i] + '\n' + morelink;
|
||||
maxlen = html.length * 2;
|
||||
}
|
||||
else if (len > maxlen) {
|
||||
len = out.length;
|
||||
words = lines[i].split(' ');
|
||||
for (var j=0; j < words.length; j++) {
|
||||
len += words[j].length + 1;
|
||||
out += words[j] + ' ';
|
||||
if (len > maxlen) {
|
||||
out += morelink;
|
||||
maxlen = html.length * 2;
|
||||
}
|
||||
}
|
||||
out += '\n';
|
||||
}
|
||||
else
|
||||
out += lines[i] + '\n';
|
||||
}
|
||||
|
||||
if (maxlen > str.length)
|
||||
out += '</span>';
|
||||
|
||||
html = out;
|
||||
}
|
||||
|
||||
// simple link parser (similar to rcube_string_replacer class in PHP)
|
||||
var utf_domain = '[^?&@"\'/\\(\\)\\s\\r\\t\\n]+\\.([^\x00-\x2f\x3b-\x40\x5b-\x60\x7b-\x7f]{2,}|xn--[a-z0-9]{2,})';
|
||||
var url1 = '.:;,', url2 = 'a-z0-9%=#@+?&/_~\\[\\]-';
|
||||
var link_pattern = new RegExp('([hf]t+ps?://)('+utf_domain+'(['+url1+']?['+url2+']+)*)?', 'ig');
|
||||
var mailto_pattern = new RegExp('([^\\s\\n\\(\\);]+@'+utf_domain+')', 'ig');
|
||||
|
||||
return html
|
||||
.replace(link_pattern, '<a href="$1$2" target="_blank">$1$2</a>')
|
||||
.replace(mailto_pattern, '<a href="mailto:$1">$1</a>')
|
||||
.replace(/(mailto:)([^"]+)"/g, '$1$2" onclick="rcmail.command(\'compose\', \'$2\');return false"')
|
||||
.replace(/\n/g, "<br/>");
|
||||
};
|
||||
|
||||
this.init_alarms_edit = function(prefix)
|
||||
{
|
||||
// register events on alarm fields
|
||||
$(prefix+' select.edit-alarm-type').change(function(){
|
||||
$(this).parent().find('span.edit-alarm-values')[(this.selectedIndex>0?'show':'hide')]();
|
||||
});
|
||||
$(prefix+' select.edit-alarm-offset').change(function(){
|
||||
var mode = $(this).val() == '@' ? 'show' : 'hide';
|
||||
$(this).parent().find('.edit-alarm-date, .edit-alarm-time')[mode]();
|
||||
$(this).parent().find('.edit-alarm-value').prop('disabled', mode == 'show');
|
||||
});
|
||||
|
||||
$(prefix+' .edit-alarm-date').datepicker(datepicker_settings);
|
||||
}
|
||||
|
||||
|
||||
/***** Alarms handling *****/
|
||||
|
||||
/**
|
||||
* Display a notification for the given pending alarms
|
||||
*/
|
||||
this.display_alarms = function(alarms)
|
||||
{
|
||||
// clear old alert first
|
||||
if (this.alarm_dialog)
|
||||
this.alarm_dialog.dialog('destroy');
|
||||
|
||||
this.alarm_dialog = $('<div>').attr('id', 'alarm-display');
|
||||
|
||||
var actions, adismiss, asnooze, alarm, html, event_ids = [];
|
||||
for (var actions, html, alarm, i=0; i < alarms.length; i++) {
|
||||
alarm = alarms[i];
|
||||
alarm.start = parseISO8601(alarm.start);
|
||||
alarm.end = parseISO8601(alarm.end);
|
||||
event_ids.push(alarm.id);
|
||||
|
||||
html = '<h3 class="event-title">' + Q(alarm.title) + '</h3>';
|
||||
html += '<div class="event-section">' + Q(alarm.location || '') + '</div>';
|
||||
html += '<div class="event-section">' + Q(this.event_date_text(alarm)) + '</div>';
|
||||
|
||||
adismiss = $('<a href="#" class="alarm-action-dismiss"></a>').html(rcmail.gettext('dismiss','libcalendaring')).click(function(){
|
||||
me.dismiss_link = $(this);
|
||||
me.dismiss_alarm(me.dismiss_link.data('id'), 0);
|
||||
});
|
||||
asnooze = $('<a href="#" class="alarm-action-snooze"></a>').html(rcmail.gettext('snooze','libcalendaring')).click(function(e){
|
||||
me.snooze_dropdown($(this));
|
||||
e.stopPropagation();
|
||||
return false;
|
||||
});
|
||||
actions = $('<div>').addClass('alarm-actions').append(adismiss.data('id', alarm.id)).append(asnooze.data('id', alarm.id));
|
||||
|
||||
$('<div>').addClass('alarm-item').html(html).append(actions).appendTo(this.alarm_dialog);
|
||||
}
|
||||
|
||||
var buttons = {};
|
||||
buttons[rcmail.gettext('dismissall','libcalendaring')] = function() {
|
||||
// submit dismissed event_ids to server
|
||||
me.dismiss_alarm(me.alarm_ids.join(','), 0);
|
||||
$(this).dialog('close');
|
||||
};
|
||||
|
||||
this.alarm_dialog.appendTo(document.body).dialog({
|
||||
modal: false,
|
||||
resizable: true,
|
||||
closeOnEscape: false,
|
||||
dialogClass: 'alarm',
|
||||
title: '<span class="ui-icon ui-icon-alarms" style="float:left; margin:0 4px 0 0"></span>' + rcmail.gettext('alarmtitle','libcalendaring'),
|
||||
buttons: buttons,
|
||||
close: function() {
|
||||
$('#alarm-snooze-dropdown').hide();
|
||||
$(this).dialog('destroy').remove();
|
||||
me.alarm_dialog = null;
|
||||
me.alarm_ids = null;
|
||||
},
|
||||
drag: function(event, ui) {
|
||||
$('#alarm-snooze-dropdown').hide();
|
||||
}
|
||||
});
|
||||
|
||||
this.alarm_ids = event_ids;
|
||||
};
|
||||
|
||||
/**
|
||||
* Show a drop-down menu with a selection of snooze times
|
||||
*/
|
||||
this.snooze_dropdown = function(link)
|
||||
{
|
||||
if (!this.snooze_popup) {
|
||||
this.snooze_popup = $('#alarm-snooze-dropdown');
|
||||
// create popup if not found
|
||||
if (!this.snooze_popup.length) {
|
||||
this.snooze_popup = $('<div>').attr('id', 'alarm-snooze-dropdown').addClass('popupmenu').appendTo(document.body);
|
||||
this.snooze_popup.html(rcmail.env.snooze_select)
|
||||
}
|
||||
$('#alarm-snooze-dropdown a').click(function(e){
|
||||
var time = String(this.href).replace(/.+#/, '');
|
||||
me.dismiss_alarm($('#alarm-snooze-dropdown').data('id'), time);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
// hide visible popup
|
||||
if (this.snooze_popup.is(':visible') && this.snooze_popup.data('id') == link.data('id')) {
|
||||
this.snooze_popup.hide();
|
||||
this.dismiss_link = null;
|
||||
}
|
||||
else { // open popup below the clicked link
|
||||
var pos = link.offset();
|
||||
pos.top += link.height() + 2;
|
||||
this.snooze_popup.data('id', link.data('id')).css({ top:Math.floor(pos.top)+'px', left:Math.floor(pos.left)+'px' }).show();
|
||||
this.dismiss_link = link;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Dismiss or snooze alarms for the given event
|
||||
*/
|
||||
this.dismiss_alarm = function(id, snooze)
|
||||
{
|
||||
$('#alarm-snooze-dropdown').hide();
|
||||
rcmail.http_post('utils/plugin.alarms', { action:'dismiss', data:{ id:id, snooze:snooze } });
|
||||
|
||||
// remove dismissed alarm from list
|
||||
if (this.dismiss_link) {
|
||||
this.dismiss_link.closest('div.alarm-item').hide();
|
||||
var new_ids = jQuery.grep(this.alarm_ids, function(v){ return v != id; });
|
||||
if (new_ids.length)
|
||||
this.alarm_ids = new_ids;
|
||||
else
|
||||
this.alarm_dialog.dialog('close');
|
||||
}
|
||||
|
||||
this.dismiss_link = null;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// extend jQuery
|
||||
(function($){
|
||||
$.fn.serializeJSON = function(){
|
||||
var json = {};
|
||||
jQuery.map($(this).serializeArray(), function(n, i) {
|
||||
json[n['name']] = n['value'];
|
||||
});
|
||||
return json;
|
||||
};
|
||||
})(jQuery);
|
||||
|
||||
|
||||
/* libcalendaring plugin initialization */
|
||||
window.rcmail && rcmail.addEventListener('init', function(evt) {
|
||||
var libcal = new rcube_libcalendaring(rcmail.env.libcal_settings);
|
||||
rcmail.addEventListener('plugin.display_alarms', function(alarms){ libcal.display_alarms(alarms); });
|
||||
});
|
547
plugins/libcalendaring/libcalendaring.php
Normal file
547
plugins/libcalendaring/libcalendaring.php
Normal 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']) . "{ '—' " . $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',
|
||||
));
|
||||
}
|
||||
|
||||
}
|
29
plugins/libcalendaring/localization/de_CH.inc
Normal file
29
plugins/libcalendaring/localization/de_CH.inc
Normal 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...';
|
29
plugins/libcalendaring/localization/de_DE.inc
Normal file
29
plugins/libcalendaring/localization/de_DE.inc
Normal 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...';
|
29
plugins/libcalendaring/localization/en_US.inc
Normal file
29
plugins/libcalendaring/localization/en_US.inc
Normal 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...';
|
||||
|
28
plugins/libcalendaring/localization/pl_PL.inc
Normal file
28
plugins/libcalendaring/localization/pl_PL.inc
Normal 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ń';
|
||||
|
54
plugins/libcalendaring/skins/larry/libcal.css
Normal file
54
plugins/libcalendaring/skins/larry/libcal.css
Normal 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;
|
||||
}
|
|
@ -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']);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
}
|
||||
|
||||
/**
|
||||
* Name says it all
|
||||
* (cloned from calendar plugin)
|
||||
*/
|
||||
function text2html(str, maxlen, maxlines)
|
||||
{
|
||||
var html = Q(String(str));
|
||||
|
||||
// limit visible text length
|
||||
if (maxlen) {
|
||||
var morelink = ' <a href="#more" onclick="$(this).hide().next().show();return false" class="morelink">'+rcmail.gettext('showmore','tasklist')+'</a><span style="display:none">',
|
||||
lines = html.split(/\r?\n/),
|
||||
words, out = '', len = 0;
|
||||
|
||||
for (var i=0; i < lines.length; i++) {
|
||||
len += lines[i].length;
|
||||
if (maxlines && i == maxlines - 1) {
|
||||
out += lines[i] + '\n' + morelink;
|
||||
maxlen = html.length * 2;
|
||||
}
|
||||
else if (len > maxlen) {
|
||||
len = out.length;
|
||||
words = lines[i].split(' ');
|
||||
for (var j=0; j < words.length; j++) {
|
||||
len += words[j].length + 1;
|
||||
out += words[j] + ' ';
|
||||
if (len > maxlen) {
|
||||
out += morelink;
|
||||
maxlen = html.length * 2;
|
||||
}
|
||||
}
|
||||
out += '\n';
|
||||
}
|
||||
else
|
||||
out += lines[i] + '\n';
|
||||
}
|
||||
|
||||
if (maxlen > str.length)
|
||||
out += '</span>';
|
||||
|
||||
html = out;
|
||||
}
|
||||
|
||||
// simple link parser (similar to rcube_string_replacer class in PHP)
|
||||
var utf_domain = '[^?&@"\'/\\(\\)\\s\\r\\t\\n]+\\.([^\x00-\x2f\x3b-\x40\x5b-\x60\x7b-\x7f]{2,}|xn--[a-z0-9]{2,})';
|
||||
var url1 = '.:;,', url2 = 'a-z0-9%=#@+?&/_~\\[\\]-';
|
||||
var link_pattern = new RegExp('([hf]t+ps?://)('+utf_domain+'(['+url1+']?['+url2+']+)*)?', 'ig');
|
||||
var mailto_pattern = new RegExp('([^\\s\\n\\(\\);]+@'+utf_domain+')', 'ig');
|
||||
|
||||
return html
|
||||
.replace(link_pattern, '<a href="$1$2" target="_blank">$1$2</a>')
|
||||
.replace(mailto_pattern, '<a href="mailto:$1">$1</a>')
|
||||
.replace(/(mailto:)([^"]+)"/g, '$1$2" onclick="rcmail.command(\'compose\', \'$2\');return false"')
|
||||
.replace(/\n/g, "<br/>");
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear any text selection
|
||||
* (text is probably selected when double-clicking somewhere)
|
||||
|
@ -1415,85 +1356,12 @@ function rcube_tasklist_ui(settings)
|
|||
}
|
||||
|
||||
|
||||
/**** calendaring utility functions *****/
|
||||
/* TO BE MOVED TO libcalendaring plugin */
|
||||
|
||||
var gmt_offset = (new Date().getTimezoneOffset() / -60) - (rcmail.env.calendar_settings.timezone || 0) - (rcmail.env.calendar_settings.dst || 0);
|
||||
var client_timezone = new Date().getTimezoneOffset();
|
||||
|
||||
/**
|
||||
* from time and date strings to a real date object
|
||||
*/
|
||||
function parse_datetime(time, date)
|
||||
{
|
||||
// we use the utility function from datepicker to parse dates
|
||||
var date = date ? $.datepicker.parseDate(datepicker_settings.dateFormat, date, datepicker_settings) : new Date();
|
||||
|
||||
var time_arr = time.replace(/\s*[ap][.m]*/i, '').replace(/0([0-9])/g, '$1').split(/[:.]/);
|
||||
if (!isNaN(time_arr[0])) {
|
||||
date.setHours(time_arr[0]);
|
||||
if (time.match(/p[.m]*/i) && date.getHours() < 12)
|
||||
date.setHours(parseInt(time_arr[0]) + 12);
|
||||
else if (time.match(/a[.m]*/i) && date.getHours() == 12)
|
||||
date.setHours(0);
|
||||
}
|
||||
if (!isNaN(time_arr[1]))
|
||||
date.setMinutes(time_arr[1]);
|
||||
|
||||
return date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the given date object according to user's prefs
|
||||
*/
|
||||
function format_datetime(date, mode)
|
||||
{
|
||||
var format =
|
||||
mode == 2 ? rcmail.env.calendar_settings['time_format'] :
|
||||
(mode == 1 ? rcmail.env.calendar_settings['date_format'] :
|
||||
rcmail.env.calendar_settings['date_format'] + ' '+ rcmail.env.calendar_settings['time_format']);
|
||||
|
||||
return $.fullCalendar.formatDate(date, format);
|
||||
}
|
||||
|
||||
/**
|
||||
* convert the given Date object into a unix timestamp respecting browser's and user's timezone settings
|
||||
*/
|
||||
function date2unixtime(date)
|
||||
{
|
||||
var dst_offset = (client_timezone - date.getTimezoneOffset()) * 60; // adjust DST offset
|
||||
return Math.round(date.getTime()/1000 + gmt_offset * 3600 + dst_offset);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function fromunixtime(ts)
|
||||
{
|
||||
ts -= gmt_offset * 3600;
|
||||
var date = new Date(ts * 1000),
|
||||
dst_offset = (client_timezone - date.getTimezoneOffset()) * 60;
|
||||
if (dst_offset) // adjust DST offset
|
||||
date.setTime((ts + 3600) * 1000);
|
||||
return date;
|
||||
}
|
||||
|
||||
// init dialog by default
|
||||
init_taskedit();
|
||||
}
|
||||
|
||||
|
||||
// extend jQuery
|
||||
(function($){
|
||||
$.fn.serializeJSON = function(){
|
||||
var json = {};
|
||||
jQuery.map($(this).serializeArray(), function(n, i) {
|
||||
json[n['name']] = n['value'];
|
||||
});
|
||||
return json;
|
||||
};
|
||||
})(jQuery);
|
||||
|
||||
// from http://james.padolsey.com/javascript/sorting-elements-with-jquery/
|
||||
jQuery.fn.sortElements = (function(){
|
||||
var sort = [].sort;
|
||||
|
@ -1518,7 +1386,7 @@ jQuery.fn.sortElements = (function(){
|
|||
var rctasks;
|
||||
window.rcmail && rcmail.addEventListener('init', function(evt) {
|
||||
|
||||
rctasks = new rcube_tasklist_ui(rcmail.env.tasklist_settings);
|
||||
rctasks = new rcube_tasklist_ui(rcmail.env.libcal_settings);
|
||||
|
||||
// register button commands
|
||||
rcmail.register_command('newtask', function(){ rctasks.edit_task(null, 'new', {}); }, true);
|
||||
|
|
|
@ -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']);
|
||||
|
|
|
@ -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) });
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Add table
Reference in a new issue