diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php
index 0623d08d..1c5801a1 100644
--- a/plugins/calendar/calendar.php
+++ b/plugins/calendar/calendar.php
@@ -29,6 +29,7 @@ class calendar extends rcube_plugin
const FREEBUSY_UNKNOWN = 0;
const FREEBUSY_FREE = 1;
const FREEBUSY_BUSY = 2;
+ const FREEBUSY_TENTATIVE = 3;
const FREEBUSY_OOF = 4;
public $task = '?(?!login|logout).*';
@@ -51,6 +52,8 @@ class calendar extends rcube_plugin
'calendar_timeslots' => 2,
'calendar_first_day' => 1,
'calendar_first_hour' => 6,
+ 'calendar_work_start' => 6,
+ 'calendar_work_end' => 18,
);
private $default_categories = array(
@@ -116,7 +119,7 @@ class calendar extends rcube_plugin
$this->register_action('freebusy-status', array($this, 'freebusy_status'));
$this->register_action('freebusy-times', array($this, 'freebusy_times'));
$this->register_action('randomdata', array($this, 'generate_randomdata'));
- $this->register_action('print',array($this,'print_view'));
+ $this->register_action('print', array($this,'print_view'));
// remove undo information...
if ($undo = $_SESSION['calendar_event_undo']) {
@@ -508,6 +511,7 @@ class calendar extends rcube_plugin
. ' ' . html::a(array('onclick' => sprintf("%s.http_request('event', 'action=undo', %s.display_message('', 'loading'))",
JS_OBJECT_NAME, JS_OBJECT_NAME)), rcube_label('undo'));
$this->rc->output->show_message($msg, 'confirmation', null, true, $undo_time);
+ $got_msg = true;
}
else if ($success) {
$this->rc->output->show_message('calendar.successremoval', 'confirmation');
@@ -597,10 +601,11 @@ class calendar extends rcube_plugin
$end = get_input_value('end', RCUBE_INPUT_GET);
if (!$start) $start = mktime(0, 0, 0, 1, date('n'), date('Y')-1);
if (!$end) $end = mktime(0, 0, 0, 31, 12, date('Y')+10);
- $events = $this->driver->load_events($start, $end, null, get_input_value('source', RCUBE_INPUT_GET), 0);
+ $calendar_name = get_input_value('source', RCUBE_INPUT_GET);
+ $events = $this->driver->load_events($start, $end, null, $calendar_name, 0);
header("Content-Type: text/calendar");
- header("Content-Disposition: inline; filename=calendar.ics");
+ header("Content-Disposition: inline; filename=".$calendar_name);
echo $this->ical->export($events);
exit;
@@ -624,6 +629,8 @@ class calendar extends rcube_plugin
$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']);
+ $settings['work_start'] = (int)$this->rc->config->get('calendar_work_start', $this->defaults['calendar_work_start']);
+ $settings['work_end'] = (int)$this->rc->config->get('calendar_work_end', $this->defaults['calendar_work_end']);
$settings['timezone'] = $this->timezone;
// localization
@@ -1204,6 +1211,7 @@ class calendar extends rcube_plugin
if (!$start) $start = time();
if (!$end) $end = $start + 3600;
+ $fbtypemap = array(calendar::FREEBUSY_FREE => 'FREE', calendar::FREEBUSY_BUSY => 'BUSY', calendar::FREEBUSY_TENTATIVE => 'TENTATIVE', calendar::FREEBUSY_OOF => 'OUT-OF-OFFICE');
$status = 'UNKNOWN';
// if the backend has free-busy information
@@ -1212,9 +1220,9 @@ class calendar extends rcube_plugin
$status = 'FREE';
foreach ($fblist as $slot) {
- list($from, $to) = $slot;
+ list($from, $to, $type) = $slot;
if ($from <= $end && $to > $start) {
- $status = 'BUSY';
+ $status = $type && $fbtypemap[$type] ? $fbtypemap[$type] : 'BUSY';
break;
}
}
@@ -1254,9 +1262,9 @@ class calendar extends rcube_plugin
if (is_array($fblist)) {
$status = self::FREEBUSY_FREE;
foreach ($fblist as $slot) {
- list($from, $to) = $slot;
+ list($from, $to, $type) = $slot;
if ($from <= $t_end && $to > $t) {
- $status = self::FREEBUSY_BUSY;
+ $status = isset($type) ? $type : self::FREEBUSY_BUSY;
break;
}
}
diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js
index 6530a012..bfa2614d 100644
--- a/plugins/calendar/calendar_ui.js
+++ b/plugins/calendar/calendar_ui.js
@@ -42,10 +42,12 @@ function rcube_calendar_ui(settings)
var gmt_offset = (new Date().getTimezoneOffset() / -60) - (settings.timezone || 0);
var day_clicked = day_clicked_ts = 0;
var ignore_click = false;
+ var event_defaults = { free_busy:'busy' };
var event_attendees = null;
var attendees_list;
- var freebusy_ui = {};
+ var freebusy_ui = { workinhoursonly:false };
var freebusy_data = {};
+ var freebusy_needsupdate;
// general datepicker settings
var datepicker_settings = {
@@ -329,7 +331,9 @@ function rcube_calendar_ui(settings)
var $dialog = $("#eventedit");
var calendar = event.calendar && me.calendars[event.calendar] ? me.calendars[event.calendar] : { editable:action=='new' };
- me.selected_event = event;
+ me.selected_event = $.extend(event_defaults, event); // clone event object (with defaults)
+ event = me.selected_event; // change reference to clone
+ freebusy_needsupdate = false;
// reset dialog first, enable/disable fields according to editable state
$('#eventtabs').get(0).reset();
@@ -614,27 +618,34 @@ function rcube_calendar_ui(settings)
return false;
// set form elements
- var duration = Math.round((event.end.getTime() - event.start.getTime()) / 1000);
- var startdate = $('#schedule-startdate').val($.fullCalendar.formatDate(event.start, settings['date_format'])).data('duration', duration);
- var starttime = $('#schedule-starttime').val($.fullCalendar.formatDate(event.start, settings['time_format'])).show();
- var enddate = $('#schedule-enddate').val($.fullCalendar.formatDate(event.end, settings['date_format']));
- var endtime = $('#schedule-endtime').val($.fullCalendar.formatDate(event.end, settings['time_format'])).show();
var allday = $('#edit-allday').get(0);
+ var duration = Math.round((event.end.getTime() - event.start.getTime()) / 1000);
+ freebusy_ui.startdate = $('#schedule-startdate').val($.fullCalendar.formatDate(event.start, settings['date_format'])).data('duration', duration);
+ freebusy_ui.starttime = $('#schedule-starttime').val($.fullCalendar.formatDate(event.start, settings['time_format'])).show();
+ freebusy_ui.enddate = $('#schedule-enddate').val($.fullCalendar.formatDate(event.end, settings['date_format']));
+ freebusy_ui.endtime = $('#schedule-endtime').val($.fullCalendar.formatDate(event.end, settings['time_format'])).show();
if (allday.checked) {
starttime.val("00:00").hide();
endtime.val("23:59").hide();
+ event.allDay = true;
}
+ // read attendee roles from drop-downs
+ $('select.edit-attendee-role').each(function(i, elem){
+ if (event_attendees[i])
+ event_attendees[i].role = $(elem).val();
+ });
+
// render time slots
var now = new Date(), fb_start = new Date(), fb_end = new Date();
- fb_start.setTime(Math.max(now, event.start));
+ fb_start.setTime(event.start);
fb_start.setHours(0); fb_start.setMinutes(0); fb_start.setSeconds(0); fb_start.setMilliseconds(0);
fb_end.setTime(fb_start.getTime() + 86400000);
freebusy_data = {};
- freebusy_ui.loading = 1; // prevent render_freebusy_grid() to load data
- freebusy_ui.numdays = allday.checked ? 7 : 1;
+ freebusy_ui.loading = 1; // prevent render_freebusy_grid() to load data yet
+ freebusy_ui.numdays = allday.checked ? 7 : Math.ceil(duration * 2 / 86400);
freebusy_ui.interval = allday.checked ? 360 : 60;
freebusy_ui.start = fb_start;
freebusy_ui.end = new Date(freebusy_ui.start.getTime() + 86400000 * freebusy_ui.numdays);
@@ -651,9 +662,23 @@ function rcube_calendar_ui(settings)
$('#schedule-attendees-list').html(list_html);
+ // enable/disable buttons
+ $('#shedule-find-prev').button('option', 'disabled', (fb_start.getTime() < now.getTime()));
+
// dialog buttons
var buttons = {};
+ buttons[rcmail.gettext('adobt', 'calendar')] = function() {
+ $('#edit-startdate').val(freebusy_ui.startdate.val());
+ $('#edit-starttime').val(freebusy_ui.starttime.val());
+ $('#edit-enddate').val(freebusy_ui.enddate.val());
+ $('#edit-endtime').val(freebusy_ui.endtime.val());
+ if (freebusy_needsupdate)
+ update_freebusy_status(me.selected_event);
+ freebusy_needsupdate = false;
+ $dialog.dialog("close");
+ };
+
buttons[rcmail.gettext('cancel', 'calendar')] = function() {
$dialog.dialog("close");
};
@@ -664,6 +689,8 @@ function rcube_calendar_ui(settings)
closeOnEscape: true,
title: rcmail.gettext('scheduletime', 'calendar'),
close: function() {
+ if (bw.ie6)
+ $("#edit-attendees-table").css('visibility','visible');
$dialog.dialog("destroy").hide();
},
buttons: buttons,
@@ -671,6 +698,18 @@ function rcube_calendar_ui(settings)
width: 850
}).show();
+ // hide edit dialog on IE6 because of drop-down elements
+ if (bw.ie6)
+ $("#edit-attendees-table").css('visibility','hidden');
+
+ // adjust dialog size to fit grid without scrolling
+ var gridw = $('#schedule-freebusy-times').width();
+ var overflow = gridw - $('#attendees-freebusy-table td.times').width() + 1;
+ if (overflow > 0) {
+ $dialog.dialog('option', 'width', Math.min((window.innerWidth || document.documentElement.clientWidth) - 40, 850 + overflow));
+ $dialog.dialog('option', 'position', ['center', 'center']);
+ }
+
// fetch data from server
freebusy_ui.loading = 0;
load_freebusy_data(freebusy_ui.start, freebusy_ui.interval);
@@ -694,8 +733,8 @@ function rcube_calendar_ui(settings)
lastdate = datestr;
}
- // TODO: define working hours by config
- css = (freebusy_ui.numdays == 1 && (curdate.getHours() < 6 || curdate.getHours() > 18)) ? 'offhours' : 'workinghours';
+ // set css class according to working hours
+ css = (freebusy_ui.numdays == 1 && (curdate.getHours() < settings['work_start'] || curdate.getHours() > settings['work_end'])) ? 'offhours' : 'workinghours';
times_row += '
' + Q($.fullCalendar.formatDate(curdate, settings['time_format'])) + ' | ';
slots_row += ' | ';
@@ -712,23 +751,98 @@ function rcube_calendar_ui(settings)
times_html += '' + slots_row + '
';
}
- $('#schedule-freebusy-times > thead').html(dates_row + times_row);
- $('#schedule-freebusy-times > tbody').html(times_html);
+ var table = $('#schedule-freebusy-times');
+ table.children('thead').html(dates_row + times_row);
+ table.children('tbody').html(times_html);
// if we have loaded free-busy data, show it
if (!freebusy_ui.loading) {
if (date2unixtime(freebusy_ui.start) < freebusy_data.start || date2unixtime(freebusy_ui.end) > freebusy_data.end || freebusy_ui.interval != freebusy_data.interval) {
- load_freebusy_data(freebusy_ui.start, freebusy_ui.interval)
- return;
+ load_freebusy_data(freebusy_ui.start, freebusy_ui.interval);
}
-
- for (var email, i=0; i < event_attendees.length; i++) {
- if ((email = event_attendees[i].email))
- update_freebusy_display(email);
+ else {
+ for (var email, i=0; i < event_attendees.length; i++) {
+ if ((email = event_attendees[i].email))
+ update_freebusy_display(email);
+ }
}
}
+
+ // render current event date/time selection over grid table
+ // use timeout to let the dom attributes (width/height/offset) be set first
+ window.setTimeout(function(){ render_freebusy_overlay(); }, 10);
};
+ // render overlay element over the grid to visiualize the current event date/time
+ var render_freebusy_overlay = function()
+ {
+ var overlay = $('#schedule-event-time');
+ if (me.selected_event.end.getTime() < freebusy_ui.start.getTime() || me.selected_event.start.getTime() > freebusy_ui.end.getTime()) {
+ overlay.draggable('disable').hide();
+ }
+ else {
+ var table = $('#schedule-freebusy-times'),
+ width = 0,
+ pos = { top:table.children('thead').height(), left:0 },
+ eventstart = date2unixtime(me.selected_event.start),
+ eventend = date2unixtime(me.selected_event.end),
+ slotstart = date2unixtime(freebusy_ui.start),
+ slotsize = freebusy_ui.interval * 60,
+ slotend, fraction, $cell;
+
+ // iterate through slots to determine position and size of the overlay
+ table.children('thead').find('td').each(function(i, cell){
+ slotend = slotstart + slotsize - 60;
+ // event starts in this slot: compute left
+ if (eventstart >= slotstart && eventstart <= slotend) {
+ fraction = 1 - (slotend - eventstart) / slotsize;
+ pos.left = Math.round(cell.offsetLeft + cell.offsetWidth * fraction);
+ }
+ // event ends in this slot: compute width
+ else if (eventend >= slotstart && eventend <= slotend) {
+ fraction = 1 - (slotend - eventend) / slotsize;
+ width = Math.round(cell.offsetLeft + cell.offsetWidth * fraction) - pos.left;
+ }
+
+ slotstart = slotstart + slotsize;
+ });
+
+ if (!width)
+ width = table.width() - pos.left;
+
+ // overlay is visible
+ if (width > 0) {
+ overlay.css({ width: (width-5)+'px', height:(table.children('tbody').height() - 4)+'px', left:pos.left+'px', top:pos.top+'px' }).draggable('enable').show();
+
+ // configure draggable
+ if (!overlay.data('isdraggable')) {
+ overlay.draggable({
+ axis: 'x',
+ scroll: true,
+ stop: function(e, ui){
+ // convert pixels to time
+ var px = ui.position.left;
+ var range_p = $('#schedule-freebusy-times').width();
+ var range_t = freebusy_ui.end.getTime() - freebusy_ui.start.getTime();
+ var newstart = new Date(freebusy_ui.start.getTime() + px * (range_t / range_p));
+ newstart.setSeconds(0); newstart.setMilliseconds(0);
+ // round to 5 minutes
+ var round = newstart.getMinutes() % 5;
+ if (round > 2.5) newstart.setTime(newstart.getTime() + (5 - round) * 60000);
+ else if (round > 0) newstart.setTime(newstart.getTime() - round * 60000);
+ // update event times
+ update_freebusy_dates(newstart, new Date(newstart.getTime() + freebusy_ui.startdate.data('duration') * 1000));
+ }
+ }).data('isdraggable', true);
+ }
+ }
+ else
+ overlay.draggable('disable').hide();
+ }
+
+ };
+
+
// fetch free-busy information for each attendee from server
var load_freebusy_data = function(from, interval)
{
@@ -790,6 +904,109 @@ function rcube_calendar_ui(settings)
});
}
};
+
+ // write changed event date/times back to form fields
+ var update_freebusy_dates = function(start, end)
+ {
+ me.selected_event.start = start;
+ me.selected_event.end = end;
+ freebusy_ui.startdate.val($.fullCalendar.formatDate(start, settings['date_format']));
+ freebusy_ui.starttime.val($.fullCalendar.formatDate(start, settings['time_format']));
+ freebusy_ui.enddate.val($.fullCalendar.formatDate(end, settings['date_format']));
+ freebusy_ui.endtime.val($.fullCalendar.formatDate(end, settings['time_format']));
+ freebusy_needsupdate = true;
+ };
+
+ // attempt to find a time slot where all attemdees are available
+ var freebusy_find_slot = function(dir)
+ {
+ var event = me.selected_event,
+ eventstart = date2unixtime(event.start), // calculate with unitimes
+ eventend = date2unixtime(event.end),
+ duration = eventend - eventstart,
+ sinterval = freebusy_data.interval * 60,
+ intvlslots = event.allDay ? 4 : 1,
+ numslots = Math.ceil(duration / sinterval),
+ checkdate, slotend, email, curdate;
+
+ // shift event times to next possible slot
+ eventstart += sinterval * intvlslots * dir;
+ eventend += sinterval * intvlslots * dir;
+
+ // iterate through free-busy slots and find candidates
+ var candidatecount = 0, candidatestart = candidateend = success = false;
+ for (var slot = dir > 0 ? freebusy_data.start : freebusy_data.end - sinterval; (dir > 0 && slot < freebusy_data.end) || (dir < 0 && slot >= freebusy_data.start); slot += sinterval * dir) {
+ slotend = slot + sinterval;
+ if ((dir > 0 && slotend <= eventstart) || (dir < 0 && slot >= eventend)) // skip
+ continue;
+
+ // respect workingours setting
+ if (freebusy_ui.workinhoursonly && freebusy_data.interval <= 60) {
+ curdate = fromunixtime(dir > 0 || !candidateend ? slot : (candidateend - duration));
+ if (curdate.getHours() < settings['work_start'] || curdate.getHours() > settings['work_end']) { // skip off-hours
+ candidatestart = candidateend = false;
+ candidatecount = 0;
+ continue;
+ }
+ }
+
+ if (!candidatestart)
+ candidatestart = slot;
+
+ // check freebusy data for all attendees
+ for (var i=0; i < event_attendees.length; i++) {
+ if ((email = event_attendees[i].email) && freebusy_data[email][slot] > 1) {
+ candidatestart = candidateend = false;
+ break;
+ }
+ }
+
+ // occupied slot
+ if (!candidatestart) {
+ slot += Math.max(0, intvlslots - candidatecount - 1) * sinterval * dir;
+ candidatecount = 0;
+ continue;
+ }
+
+ // set candidate end to slot end time
+ candidatecount++;
+ if (dir < 0 && !candidateend)
+ candidateend = slotend;
+
+ // if candidate is big enough, this is it!
+ if (candidatecount == numslots) {
+ if (dir > 0) {
+ event.start = fromunixtime(candidatestart);
+ event.end = fromunixtime(candidatestart + duration);
+ }
+ else {
+ event.end = fromunixtime(candidateend);
+ event.start = fromunixtime(candidateend - duration);
+ }
+ success = true;
+ break;
+ }
+ }
+
+ // update event date/time display
+ if (success) {
+ update_freebusy_dates(event.start, event.end);
+
+ // move freebusy grid if necessary
+ if (event.start.getTime() >= freebusy_ui.end.getTime())
+ render_freebusy_grid(1);
+ else if (event.end.getTime() <= freebusy_ui.start.getTime())
+ render_freebusy_grid(-1);
+ else
+ render_freebusy_overlay();
+
+ var now = new Date();
+ $('#shedule-find-prev').button('option', 'disabled', (event.start.getTime() < now.getTime()));
+ }
+ else {
+ alert(rcmail.gettext('noslotfound','calendar'));
+ }
+ };
// update event properties and attendees availability if event times have changed
@@ -797,10 +1014,11 @@ function rcube_calendar_ui(settings)
{
if (me.selected_event) {
var allday = $('#edit-allday').get(0);
+ me.selected_event.allDay = allday.checked;
me.selected_event.start = parse_datetime(allday.checked ? '00:00' : $('#edit-starttime').val(), $('#edit-startdate').val());
me.selected_event.end = parse_datetime(allday.checked ? '23:59' : $('#edit-endtime').val(), $('#edit-enddate').val());
- if (me.selected_event.attendees)
- update_freebusy_status(me.selected_event);
+ if (event_attendees)
+ freebusy_needsupdate = true;
$('#edit-startdate').data('duration', Math.round((me.selected_event.end.getTime() - me.selected_event.start.getTime()) / 1000));
}
};
@@ -902,10 +1120,12 @@ function rcube_calendar_ui(settings)
var update_freebusy_status = function(event)
{
var icons = attendees_list.find('img.availabilityicon');
- for (var i=0; i < event.attendees.length; i++) {
- if (icons.get(i) && event.attendees[i].email && event.attendees[i].status != 'ACCEPTED')
- check_freebusy_status(icons.get(i), event.attendees[i].email, event);
+ for (var i=0; i < event_attendees.length; i++) {
+ if (icons.get(i) && event_attendees[i].email && event_attendees[i].status != 'ACCEPTED')
+ check_freebusy_status(icons.get(i), event_attendees[i].email, event);
}
+
+ freebusy_needsupdate = false;
};
// load free-busy status from server and update icon accordingly
@@ -1372,8 +1592,10 @@ function rcube_calendar_ui(settings)
// callback for clicks in all-day box
dayClick: function(date, allDay, e, view) {
var now = new Date().getTime();
- if (now - day_clicked_ts < 400 && day_clicked == date.getTime()) // emulate double-click on day
- return event_edit_dialog('new', { start:date, end:date, allDay:allDay, calendar:me.selected_calendar });
+ if (now - day_clicked_ts < 400 && day_clicked == date.getTime()) { // emulate double-click on day
+ var enddate = new Date(); enddate.setTime(date.getTime() + 86400000 - 60000);
+ return event_edit_dialog('new', { start:date, end:enddate, allDay:allDay, calendar:me.selected_calendar });
+ }
if (!ignore_click) {
view.calendar.gotoDate(date);
@@ -1563,14 +1785,17 @@ function rcube_calendar_ui(settings)
// init event dialog
$('#eventtabs').tabs({
show: function(event, ui) {
- if (ui.panel.id == 'event-tab-3')
+ if (ui.panel.id == 'event-tab-3') {
$('#edit-attendee-name').select();
+ if (freebusy_needsupdate && me.selected_event)
+ update_freebusy_status(me.selected_event);
+ }
}
});
$('#edit-enddate, input.edit-alarm-date').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')](); });
+ $('#edit-allday').click(function(){ $('#edit-starttime, #edit-endtime')[(this.checked?'hide':'show')](); event_times_changed(); });
// configure drop-down menu on time input fields based on jquery UI autocomplete
$('#edit-starttime, #edit-endtime, input.edit-alarm-time')
@@ -1630,13 +1855,17 @@ function rcube_calendar_ui(settings)
event_freebusy_dialog();
});
- $('#shedule-freebusy-prev').button().click(function(){ render_freebusy_grid(-1); });
- $('#shedule-freebusy-next').button().click(function(){ render_freebusy_grid(1); }).parent().buttonset();
-
+ $('#shedule-freebusy-prev').html(bw.ie6 ? '<<' : '◄').button().click(function(){ render_freebusy_grid(-1); });
+ $('#shedule-freebusy-next').html(bw.ie6 ? '>>' : '►').button().click(function(){ render_freebusy_grid(1); }).parent().buttonset();
+
+ $('#shedule-find-prev').button().click(function(){ freebusy_find_slot(-1); });
+ $('#shedule-find-next').button().click(function(){ freebusy_find_slot(1); });
+
$('#schedule-freebusy-wokinghours').click(function(){
+ freebusy_ui.workinhoursonly = this.checked;
$('#workinghourscss').remove();
if (this.checked)
- $('').appendTo('head');
+ $('').appendTo('head');
});
// add proprietary css styles if not IE
diff --git a/plugins/calendar/config.inc.php.dist b/plugins/calendar/config.inc.php.dist
index aa580071..a6b23415 100644
--- a/plugins/calendar/config.inc.php.dist
+++ b/plugins/calendar/config.inc.php.dist
@@ -52,6 +52,12 @@ $rcmail_config['calendar_first_day'] = 1;
// first hour of the calendar (0-23)
$rcmail_config['calendar_first_hour'] = 6;
+// working hours begin
+$rcmail_config['calendar_work_start'] = 6;
+
+// working hours end
+$rcmail_config['calendar_work_end'] = 18;
+
// event categories
$rcmail_config['calendar_categories'] = array(
'Personal' => 'c0c0c0',
diff --git a/plugins/calendar/drivers/kolab/kolab_calendar.php b/plugins/calendar/drivers/kolab/kolab_calendar.php
index 816a9444..50b06ed0 100644
--- a/plugins/calendar/drivers/kolab/kolab_calendar.php
+++ b/plugins/calendar/drivers/kolab/kolab_calendar.php
@@ -67,9 +67,11 @@ class kolab_calendar
}
else {
$acl = $this->storage->_folder->getACL();
- $acl = $acl[$_SESSION['username']];
- if (strpos($acl, 'i') !== false)
- $this->readonly = false;
+ if (is_array($acl)) {
+ $acl = $acl[$_SESSION['username']];
+ if (strpos($acl, 'i') !== false)
+ $this->readonly = false;
+ }
}
}
}
diff --git a/plugins/calendar/drivers/kolab/kolab_driver.php b/plugins/calendar/drivers/kolab/kolab_driver.php
index 2b22320e..0430bb0d 100644
--- a/plugins/calendar/drivers/kolab/kolab_driver.php
+++ b/plugins/calendar/drivers/kolab/kolab_driver.php
@@ -693,9 +693,16 @@ class kolab_driver extends calendar_driver
if (empty($email)/* || $end < time()*/)
return false;
+ // map vcalendar fbtypes to internal values
+ $fbtypemap = array(
+ 'FREE' => calendar::FREEBUSY_FREE,
+ 'BUSY-TENTATIVE' => calendar::FREEBUSY_TENTATIVE,
+ 'X-OUT-OF-OFFICE' => calendar::FREEBUSY_OOF,
+ 'OOF' => calendar::FREEBUSY_OOF);
+
// ask kolab server first
$fbdata = @file_get_contents(rcube_kolab::get_freebusy_url($email));
-
+
// get free-busy url from contacts
if (!$fbdata) {
$fburl = null;
@@ -722,10 +729,12 @@ class kolab_driver extends calendar_driver
$fbcal->parsevCalendar($fbdata);
if ($fb = $fbcal->findComponent('vfreebusy')) {
$result = array();
+ $params = $fb->getExtraParams();
foreach ($fb->getBusyPeriods() as $from => $to) {
if ($to == null) // no information, assume free
break;
- $result[] = array($from, $to);
+ $type = $params[$from]['FBTYPE'];
+ $result[] = array($from, $to, isset($fbtypemap[$type]) ? $fbtypemap[$type] : calendar::FREEBUSY_BUSY);
}
return $result;
diff --git a/plugins/calendar/lib/calendar_ical.php b/plugins/calendar/lib/calendar_ical.php
index 66cd9ee9..d2e06cb5 100644
--- a/plugins/calendar/lib/calendar_ical.php
+++ b/plugins/calendar/lib/calendar_ical.php
@@ -67,7 +67,7 @@ class calendar_ical
$ical .= "DTSTART:" . gmdate('Ymd\THis\Z', $event['start']) . "\r\n";
$ical .= "DTEND:" . gmdate('Ymd\THis\Z', $event['end']) . "\r\n";
$ical .= "SUMMARY:" . self::escpape($event['title']) . "\r\n";
- $ical .= "DESCRIPTION:" . wordwrap(self::escpape($event['description']),75,'\r\n ') . "\r\n";
+ $ical .= "DESCRIPTION:" . wordwrap(self::escpape($event['description']),75,"\r\n ") . "\r\n";
if (!empty($event['attendees'])){
diff --git a/plugins/calendar/lib/calendar_ui.php b/plugins/calendar/lib/calendar_ui.php
index 4d90826a..30d44ea5 100644
--- a/plugins/calendar/lib/calendar_ui.php
+++ b/plugins/calendar/lib/calendar_ui.php
@@ -592,10 +592,13 @@ class calendar_ui
$table->add('attendees',
html::tag('h3', 'boxtitle', $this->calendar->gettext('tabattendees')) .
html::div('timesheader', ' ') .
- html::div(array('id' => 'schedule-attendees-list'), '')
+ html::div(array('id' => 'schedule-attendees-list', 'class' => 'attendees-list'), '')
);
$table->add('times',
- html::div('scroll', html::tag('table', array('id' => 'schedule-freebusy-times', 'border' => 0, 'cellspacing' => 0), html::tag('thead') . html::tag('tbody')))
+ html::div('scroll',
+ html::tag('table', array('id' => 'schedule-freebusy-times', 'border' => 0, 'cellspacing' => 0), html::tag('thead') . html::tag('tbody')) .
+ html::div(array('id' => 'schedule-event-time', 'style' => 'display:none'), ' ')
+ )
);
return $table->show($attrib);
diff --git a/plugins/calendar/localization/en_US.inc b/plugins/calendar/localization/en_US.inc
index a4a8a509..4b405ee5 100644
--- a/plugins/calendar/localization/en_US.inc
+++ b/plugins/calendar/localization/en_US.inc
@@ -31,6 +31,7 @@ $labels['edit'] = 'Edit';
$labels['save'] = 'Save';
$labels['remove'] = 'Remove';
$labels['cancel'] = 'Cancel';
+$labels['adobt'] = 'Adopt changes';
$labels['print'] = 'Print calendars';
$labels['title'] = 'Summary';
$labels['description'] = 'Description';
@@ -95,10 +96,14 @@ $labels['roleresource'] = 'Resource';
$labels['availfree'] = 'Free';
$labels['availbusy'] = 'Busy';
$labels['availunknown'] = 'Unknown';
+$labels['availtentative'] = 'Tentative';
$labels['availoutofoffice'] = 'Out of Office';
$labels['scheduletime'] = 'Available times';
$labels['sendnotifications'] = 'Send notifications';
-$labels['onlyworkinghours'] = 'Show only working hours';
+$labels['onlyworkinghours'] = 'Only working hours';
+$labels['prevslot'] = 'Previous Slot';
+$labels['nextslot'] = 'Next Slot';
+$labels['noslotfound'] = 'Unable to find a free time slot';
// event dialog tabs
$labels['tabsummary'] = 'Summary';
diff --git a/plugins/calendar/skins/default/calendar.css b/plugins/calendar/skins/default/calendar.css
index 47e47e8c..64669a92 100644
--- a/plugins/calendar/skins/default/calendar.css
+++ b/plugins/calendar/skins/default/calendar.css
@@ -36,6 +36,7 @@ body.calendarmain {
#datepicker .ui-datepicker {
width: 97% !important;
+ box-shadow: none;
-moz-box-shadow: none;
-webkit-box-shadow: none;
}
@@ -566,7 +567,7 @@ td.topalign {
}
.availability img.availabilityicon.loading {
- background: url('images/loading-small.gif') top left no-repeat;
+ background: url('images/loading-small.gif') middle middle no-repeat;
}
#schedule-freebusy-times td.unknown,
@@ -584,6 +585,11 @@ td.topalign {
background: #c00;
}
+#schedule-freebusy-times td.tentative,
+.availability img.availabilityicon.tentative {
+ background: #66d;
+}
+
#schedule-freebusy-times td.out-of-office,
.availability img.availabilityicon.out-of-office {
background: #f0b400;
@@ -591,6 +597,7 @@ td.topalign {
#edit-attendees-legend {
margin-top: 3em;
+ margin-bottom: 0.5em;
}
#edit-attendees-legend .legend {
@@ -651,6 +658,7 @@ td.topalign {
}
#attendees-freebusy-table div.scroll {
+ position: relative;
overflow: auto;
}
@@ -660,17 +668,33 @@ td.topalign {
border-color: #ccc;
}
-#attendees-freebusy-table div.timesheader {
- padding: 0.3em;
+.attendees-list .attendee {
+ padding: 3px 4px 3px 20px;
+ background: url('images/attendee-status.gif') 2px -97px no-repeat;
}
-#schedule-attendees-list div.attendee {
- padding: 3px 20px 3px 6px;
+.attendees-list div.attendee {
border-top: 1px solid #ccc;
}
-#schedule-attendees-list div.loading {
- background: url('images/loading-small.gif') top right no-repeat;
+.attendees-list span.attendee {
+ margin-right: 2em;
+}
+
+.attendees-list .organizer {
+ background-position: 3px -77px;
+}
+
+.attendees-list .opt-participant {
+ background-position: 2px -117px;
+}
+
+.attendees-list .chair {
+ background-position: 2px -137px;
+}
+
+.attendees-list .loading {
+ background: url('images/loading-small.gif') 1px 50% no-repeat;
}
#schedule-freebusy-times {
@@ -688,6 +712,7 @@ td.topalign {
border-width: 0 1px 0 1px;
}
+#attendees-freebusy-table div.timesheader,
#schedule-freebusy-times tr.times td {
min-width: 30px;
font-size: 9px;
@@ -695,9 +720,19 @@ td.topalign {
text-align: center;
}
+#schedule-event-time {
+ position: absolute;
+ border: 2px solid #333;
+ background: #777;
+ background: rgba(60, 60, 60, 0.6);
+ opacity: 0.5;
+ border-radius: 4px;
+ cursor: move;
+}
+
#eventfreebusy .schedule-options {
position: relative;
- margin-bottom: 2em;
+ margin-bottom: 1.5em;
}
#eventfreebusy .schedule-buttons {
@@ -706,6 +741,15 @@ td.topalign {
right: 0;
}
+#eventfreebusy .schedule-find-buttons {
+ padding-bottom:0.5em;
+}
+
+#eventfreebusy .schedule-find-buttons button {
+ min-width: 9em;
+ text-align: center;
+}
+
span.edit-alarm-set {
white-space: nowrap;
}
diff --git a/plugins/calendar/skins/default/iehacks.css b/plugins/calendar/skins/default/iehacks.css
index fa282176..31f82d78 100644
--- a/plugins/calendar/skins/default/iehacks.css
+++ b/plugins/calendar/skins/default/iehacks.css
@@ -43,3 +43,12 @@ html #calendartoolbar a.buttonPas {
.fc-header-title h2 {
font-size: 16px;
}
+
+#schedule-event-time {
+ filter: alpha(opacity=40);
+}
+
+#eventfreebusy .schedule-buttons,
+#edit-attendees-form #edit-attendee-schedule {
+ right: 0.6em;
+}
\ No newline at end of file
diff --git a/plugins/calendar/skins/default/images/attendee-status.gif b/plugins/calendar/skins/default/images/attendee-status.gif
index a5f65a02..5c08aae3 100644
Binary files a/plugins/calendar/skins/default/images/attendee-status.gif and b/plugins/calendar/skins/default/images/attendee-status.gif differ
diff --git a/plugins/calendar/skins/default/templates/calendar.html b/plugins/calendar/skins/default/templates/calendar.html
index bf83f030..de0419cc 100644
--- a/plugins/calendar/skins/default/templates/calendar.html
+++ b/plugins/calendar/skins/default/templates/calendar.html
@@ -188,25 +188,42 @@
-
-
-
-
+
-
diff --git a/plugins/calendar/skins/default/templates/freebusylegend.html b/plugins/calendar/skins/default/templates/freebusylegend.html
index c3d0a86e..5cc01a18 100644
--- a/plugins/calendar/skins/default/templates/freebusylegend.html
+++ b/plugins/calendar/skins/default/templates/freebusylegend.html
@@ -1,6 +1,7 @@
diff --git a/plugins/kolab_addressbook/kolab_addressbook.js b/plugins/kolab_addressbook/kolab_addressbook.js
index cdb525d1..9cb1756e 100644
--- a/plugins/kolab_addressbook/kolab_addressbook.js
+++ b/plugins/kolab_addressbook/kolab_addressbook.js
@@ -191,7 +191,7 @@ rcube_webmail.prototype.book_update = function(data, old)
level = olddata.realname.split(this.env.delimiter).length - data.realname.split(this.env.delimiter).length;
// update (realname and ID of) subfolders
for (n in sources) {
- if (n.indexOf(old) == 0) {
+ if (n != data.id && n.indexOf(old) == 0) {
// new ID
id = data.id + '-' + n.substr(old.length);
name = sources[n].name;
diff --git a/plugins/kolab_addressbook/kolab_addressbook.php b/plugins/kolab_addressbook/kolab_addressbook.php
index 5432ffac..c6178920 100644
--- a/plugins/kolab_addressbook/kolab_addressbook.php
+++ b/plugins/kolab_addressbook/kolab_addressbook.php
@@ -27,7 +27,7 @@
*/
class kolab_addressbook extends rcube_plugin
{
- public $task = 'mail|settings|addressbook';
+ public $task = 'mail|settings|addressbook|calendar';
private $folders;
private $sources;
@@ -54,6 +54,7 @@ class kolab_addressbook extends rcube_plugin
// register hooks
$this->add_hook('addressbooks_list', array($this, 'address_sources'));
$this->add_hook('addressbook_get', array($this, 'get_address_book'));
+ $this->add_hook('config_get', array($this, 'config_get'));
if ($this->rc->task == 'addressbook') {
$this->add_texts('localization');
@@ -74,10 +75,6 @@ class kolab_addressbook extends rcube_plugin
$this->add_hook('preferences_list', array($this, 'prefs_list'));
$this->add_hook('preferences_save', array($this, 'prefs_save'));
}
- // extend list of address sources to be used for autocompletion
- else if ($this->rc->task == 'mail' && $this->rc->action == 'autocomplete') {
- $this->autocomplete_sources();
- }
}
@@ -149,16 +146,22 @@ class kolab_addressbook extends rcube_plugin
/**
- * Setts autocomplete_addressbooks option according to
- * kolab_addressbook_prio setting.
+ * Sets autocomplete_addressbooks option according to
+ * kolab_addressbook_prio setting extending list of address sources
+ * to be used for autocompletion.
*/
- public function autocomplete_sources()
+ public function config_get($args)
{
+ if ($args['name'] != 'autocomplete_addressbooks') {
+ return $args;
+ }
+
// Load configuration
$this->load_config();
$abook_prio = (int) $this->rc->config->get('kolab_addressbook_prio');
- $sources = (array) $this->rc->config->get('autocomplete_addressbooks', array());
+ // here we cannot use rc->config->get()
+ $sources = $GLOBALS['CONFIG']['autocomplete_addressbooks'];
// Disable all global address books
// Assumes that all non-kolab_addressbook sources are global
@@ -166,6 +169,10 @@ class kolab_addressbook extends rcube_plugin
$sources = array();
}
+ if (!is_array($sources)) {
+ $sources = array();
+ }
+
$kolab_sources = array();
foreach ($this->_list_sources() as $abook_id => $abook) {
if (!in_array($abook_id, $sources))
@@ -180,9 +187,11 @@ class kolab_addressbook extends rcube_plugin
else {
$sources = array_merge($sources, $kolab_sources);
}
-
- $this->rc->config->set('autocomplete_addressbooks', $sources);
}
+
+ $args['result'] = $sources;
+
+ return $args;
}
diff --git a/plugins/kolab_addressbook/lib/rcube_kolab_contacts.php b/plugins/kolab_addressbook/lib/rcube_kolab_contacts.php
index b4c72a4f..6cc0289b 100644
--- a/plugins/kolab_addressbook/lib/rcube_kolab_contacts.php
+++ b/plugins/kolab_addressbook/lib/rcube_kolab_contacts.php
@@ -128,11 +128,13 @@ class rcube_kolab_contacts extends rcube_addressbook
}
else {
$acl = $this->storagefolder->getACL();
- $acl = $acl[$_SESSION['username']];
- if (strpos($acl, 'i') !== false)
- $this->readonly = false;
- if (strpos($acl, 'a') !== false || strpos($acl, 'x') !== false)
- $this->editable = true;
+ if (is_array($acl)) {
+ $acl = $acl[$_SESSION['username']];
+ if (strpos($acl, 'i') !== false)
+ $this->readonly = false;
+ if (strpos($acl, 'a') !== false || strpos($acl, 'x') !== false)
+ $this->editable = true;
+ }
}
}
}
@@ -363,6 +365,8 @@ class rcube_kolab_contacts extends rcube_addressbook
$search = $value;
}
+ $s_len = strlen($search);
+
foreach ((array)$contact[$col] as $val) {
// composite field, e.g. address
if (is_array($val)) {
@@ -370,7 +374,9 @@ class rcube_kolab_contacts extends rcube_addressbook
}
$val = mb_strtolower($val);
- if (($strict && $val == $search) || (!$strict && strpos($val, $search) !== false)) {
+ if (($strict && $val == $search)
+ || (!$strict && $s_len && strpos($val, $search) !== false)
+ ) {
if (!$advanced) {
$this->filter['ids'][] = $id;
break 2;
@@ -892,6 +898,31 @@ class rcube_kolab_contacts extends rcube_addressbook
return false;
}
+ /**
+ * Check the given data before saving.
+ * If input not valid, the message to display can be fetched using get_error()
+ *
+ * @param array Associative array with contact data to save
+ *
+ * @return boolean True if input is valid, False if not.
+ */
+ public function validate($save_data)
+ {
+ // validate e-mail addresses
+ $valid = parent::validate($save_data);
+
+ // require at least one e-mail address (syntax check is already done)
+ if ($valid) {
+ if (!strlen($save_data['name'])
+ && !array_filter($this->get_col_values('email', $save_data, true))
+ ) {
+ $this->set_error('warning', 'kolab_addressbook.noemailnamewarning');
+ $valid = false;
+ }
+ }
+
+ return $valid;
+ }
/**
* Establishes a connection to the Kolab_Data object for accessing contact data
@@ -903,7 +934,6 @@ class rcube_kolab_contacts extends rcube_addressbook
}
}
-
/**
* Establishes a connection to the Kolab_Data object for accessing groups data
*/
@@ -914,7 +944,6 @@ class rcube_kolab_contacts extends rcube_addressbook
}
}
-
/**
* Simply fetch all records and store them in private member vars
*/
@@ -941,7 +970,6 @@ class rcube_kolab_contacts extends rcube_addressbook
}
}
-
/**
* Callback function for sorting contacts
*/
@@ -950,7 +978,6 @@ class rcube_kolab_contacts extends rcube_addressbook
return strcasecmp($a['name'], $b['name']);
}
-
/**
* Read distribution-lists AKA groups from server
*/
@@ -975,7 +1002,6 @@ class rcube_kolab_contacts extends rcube_addressbook
}
}
-
/**
* Map fields from internal Kolab_Format to Roundcube contact format
*/
@@ -1026,6 +1052,9 @@ class rcube_kolab_contacts extends rcube_addressbook
return array_filter($out);
}
+ /**
+ * Map fields from Roundcube format to internal Kolab_Format
+ */
private function _from_rcube_contact($contact)
{
$object = array();
diff --git a/plugins/kolab_addressbook/localization/en_US.inc b/plugins/kolab_addressbook/localization/en_US.inc
index ce90f60a..f3cb17ea 100644
--- a/plugins/kolab_addressbook/localization/en_US.inc
+++ b/plugins/kolab_addressbook/localization/en_US.inc
@@ -25,12 +25,13 @@ $labels['globalonly'] = 'Global address book(s) only';
$messages['bookdeleteconfirm'] = 'Do you really want to delete the selected address book and all contacts in it?';
$messages['bookdeleting'] = 'Deleting address book...';
$messages['booksaving'] = 'Saving address book...';
-$messages['bookdeleted'] = 'Address book deleted successfully';
-$messages['bookupdated'] = 'Address book updated successfully';
-$messages['bookcreated'] = 'Address book created successfully';
-$messages['bookdeleteerror'] = 'An error occured while deleting address book';
-$messages['bookupdateerror'] = 'An error occured while updating address book';
-$messages['bookcreateerror'] = 'An error occured while creating address book';
-$messages['nobooknamewarning'] = 'Please, enter address book name';
+$messages['bookdeleted'] = 'Address book deleted successfully.';
+$messages['bookupdated'] = 'Address book updated successfully.';
+$messages['bookcreated'] = 'Address book created successfully.';
+$messages['bookdeleteerror'] = 'An error occured while deleting address book.';
+$messages['bookupdateerror'] = 'An error occured while updating address book.';
+$messages['bookcreateerror'] = 'An error occured while creating address book.';
+$messages['nobooknamewarning'] = 'Please, enter address book name.';
+$messages['noemailnamewarning'] = 'Please, enter email address or contact name.';
?>
diff --git a/plugins/kolab_addressbook/localization/pl_PL.inc b/plugins/kolab_addressbook/localization/pl_PL.inc
index 61887231..e2735783 100644
--- a/plugins/kolab_addressbook/localization/pl_PL.inc
+++ b/plugins/kolab_addressbook/localization/pl_PL.inc
@@ -25,12 +25,13 @@ $labels['parentbook'] = 'Książka nadrzędna';
$messages['bookdeleteconfirm'] = 'Czy na pewno chcesz usunąć wybraną książkę i wszystkie kontakty w niej zapisane?';
$messages['bookdeleting'] = 'Usuwanie książki adresowej...';
$messages['booksaving'] = 'Zapisywanie książki adresowej...';
-$messages['bookdeleted'] = 'Książka adresowa została usunięta';
-$messages['bookupdated'] = 'Książka adresowa została zaktualizowana';
-$messages['bookcreated'] = 'Książka adresowa została utworzona';
-$messages['bookdeleteerror'] = 'Wystąpił błąd podczas usuwania książki adresowej';
-$messages['bookupdateerror'] = 'Wystąpił błąd podczas zmiany książki adresowej';
-$messages['bookcreateerror'] = 'Wystąpił błąd podczas tworzenia książki adresowej';
-$messages['nobooknamewarning'] = 'Proszę podać nazwę książki adresowej';
+$messages['bookdeleted'] = 'Książka adresowa została usunięta.';
+$messages['bookupdated'] = 'Książka adresowa została zaktualizowana.';
+$messages['bookcreated'] = 'Książka adresowa została utworzona.';
+$messages['bookdeleteerror'] = 'Wystąpił błąd podczas usuwania książki adresowej.';
+$messages['bookupdateerror'] = 'Wystąpił błąd podczas zmiany książki adresowej.';
+$messages['bookcreateerror'] = 'Wystąpił błąd podczas tworzenia książki adresowej.';
+$messages['nobooknamewarning'] = 'Proszę podać nazwę książki adresowej.';
+$messages['noemailnamewarning'] = 'Proszę podać adres email lub nazwę kontaktu.';
?>
diff --git a/plugins/kolab_core/rcube_kolab.php b/plugins/kolab_core/rcube_kolab.php
index 74b46a86..10cdb36a 100644
--- a/plugins/kolab_core/rcube_kolab.php
+++ b/plugins/kolab_core/rcube_kolab.php
@@ -29,6 +29,8 @@ class rcube_kolab
private static $horde_auth;
private static $config;
private static $ready = false;
+ private static $list;
+ private static $cache;
/**
@@ -72,9 +74,11 @@ class rcube_kolab
// Re-set LDAP/IMAP host config
$ldap = array('server' => 'ldap://' . $_SESSION['imap_host'] . ':389');
$imap = array('server' => $_SESSION['imap_host'], 'port' => $_SESSION['imap_port']);
+ $freebusy = array('server' => $_SESSION['imap_host']);
$conf['kolab']['ldap'] = array_merge($ldap, (array)$conf['kolab']['ldap']);
$conf['kolab']['imap'] = array_merge($imap, (array)$conf['kolab']['imap']);
+ $conf['kolab']['freebusy'] = array_merge($freebusy, (array)$conf['kolab']['freebusy']);
self::$config = &$conf;
// pass the current IMAP authentication credentials to the Horde auth system
@@ -88,19 +92,57 @@ class rcube_kolab
);
Auth::setCredential('password', $pwd);
self::$ready = true;
-
- // Register shutdown function for storing folders cache in session
- // This is already required, because Roundcube session handler
- // saves data into DB before Horde's shutdown function is called
- if (!empty($conf['kolab']['imap']['cache_folders'])) {
- $rcmail->add_shutdown_function(array('rcube_kolab', 'save_folders_cache'));
- }
}
NLS::setCharset('UTF-8');
String::setDefaultCharset('UTF-8');
}
+ /**
+ * Get instance of Kolab_List object
+ *
+ * @return object Kolab_List Folders list object
+ */
+ public static function get_folders_list()
+ {
+ self::setup();
+
+ if (self::$list)
+ return self::$list;
+
+ if (!self::$ready)
+ return null;
+
+ $rcmail = rcmail::get_instance();
+ $imap_cache = $rcmail->config->get('imap_cache');
+
+ if ($imap_cache) {
+ self::$cache = $rcmail->get_cache('IMAP', $imap_cache);
+ self::$list = self::$cache->get('mailboxes.kolab');
+
+ // Disable Horde folders caching, we're using our own cache
+ self::$config['kolab']['imap']['cache_folders'] = false;
+ // Register shutdown function for saving folders list cache
+ $rcmail->add_shutdown_function(array('rcube_kolab', 'save_folders_list'));
+ }
+
+ if (empty(self::$list)) {
+ self::$list = Kolab_List::singleton();
+ }
+
+ return self::$list;
+ }
+
+ /**
+ * Store Kolab_List instance in Roundcube cache
+ */
+ public static function save_folders_list()
+ {
+ if (self::$cache && self::$list) {
+ self::$cache->set('mailboxes.kolab', self::$list);
+ }
+ }
+
/**
* Get instance of a Kolab (XML) format object
*
@@ -123,8 +165,7 @@ class rcube_kolab
*/
public static function get_folders($type)
{
- self::setup();
- $kolab = Kolab_List::singleton();
+ $kolab = self::get_folders_list();
return self::$ready ? $kolab->getByType($type) : array();
}
@@ -136,9 +177,8 @@ class rcube_kolab
*/
public static function get_folder($folder)
{
- self::setup();
- $kolab = Kolab_List::singleton();
- return self::$ready ? $kolab->getFolder($folder) : null;
+ $kolab = self::get_folders_list();
+ return self::$ready ? $kolab->getFolder($folder) : null;
}
/**
@@ -151,8 +191,7 @@ class rcube_kolab
*/
public static function get_storage($folder, $data_type = null)
{
- self::setup();
- $kolab = Kolab_List::singleton();
+ $kolab = self::get_folders_list();
return self::$ready ? $kolab->getFolder($folder)->getData($data_type) : null;
}
@@ -175,18 +214,6 @@ class rcube_kolab
unset($_SESSION['__auth']);
}
- /**
- * Save Horde's folders cache in session (workaround shoutdown function issue)
- */
- public static function save_folders_cache()
- {
- require_once 'Horde/SessionObjects.php';
-
- $kolab = Kolab_List::singleton();
- $session = Horde_SessionObjects::singleton();
- $session->overwrite('kolab_folderlist', $kolab, false);
- }
-
/**
* Creates folder ID from folder name
*
@@ -208,8 +235,7 @@ class rcube_kolab
*/
public static function folder_delete($name)
{
- self::setup();
- $kolab = Kolab_List::singleton();
+ $kolab = self::get_folders_list();
$folder = $kolab->getFolder($name);
$result = $folder->delete();
@@ -232,8 +258,7 @@ class rcube_kolab
*/
public static function folder_create($name, $type=null, $default=false)
{
- self::setup();
- $kolab = Kolab_List::singleton();
+ $kolab = self::get_folders_list();
$folder = new Kolab_Folder();
$folder->setList($kolab);
@@ -261,17 +286,26 @@ class rcube_kolab
*/
public static function folder_rename($oldname, $newname)
{
- self::setup();
- $kolab = Kolab_List::singleton();
+ $kolab = self::get_folders_list();
$folder = $kolab->getFolder($oldname);
$folder->setFolder($newname);
+
+ // We're not using $folder->save() because some caching issues
$result = $kolab->rename($folder);
if (is_a($result, 'PEAR_Error')) {
return false;
}
+ // need to re-set some properties
+ $folder->name = $folder->new_name;
+ $folder->new_name = null;
+ $folder->_title = null;
+ $folder->_owner = null;
+ // resetting _data prevents from some wierd cache unserialization issue
+ $folder->_data = null;
+
return true;
}