Merge branch 'master' of ssh://git.kolabsys.com/git/roundcube

This commit is contained in:
Thomas 2011-07-26 08:38:25 +02:00
commit 54367e737c
19 changed files with 544 additions and 137 deletions

View file

@ -29,6 +29,7 @@ class calendar extends rcube_plugin
const FREEBUSY_UNKNOWN = 0; const FREEBUSY_UNKNOWN = 0;
const FREEBUSY_FREE = 1; const FREEBUSY_FREE = 1;
const FREEBUSY_BUSY = 2; const FREEBUSY_BUSY = 2;
const FREEBUSY_TENTATIVE = 3;
const FREEBUSY_OOF = 4; const FREEBUSY_OOF = 4;
public $task = '?(?!login|logout).*'; public $task = '?(?!login|logout).*';
@ -51,6 +52,8 @@ class calendar extends rcube_plugin
'calendar_timeslots' => 2, 'calendar_timeslots' => 2,
'calendar_first_day' => 1, 'calendar_first_day' => 1,
'calendar_first_hour' => 6, 'calendar_first_hour' => 6,
'calendar_work_start' => 6,
'calendar_work_end' => 18,
); );
private $default_categories = array( 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-status', array($this, 'freebusy_status'));
$this->register_action('freebusy-times', array($this, 'freebusy_times')); $this->register_action('freebusy-times', array($this, 'freebusy_times'));
$this->register_action('randomdata', array($this, 'generate_randomdata')); $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... // remove undo information...
if ($undo = $_SESSION['calendar_event_undo']) { 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'))", . ' ' . html::a(array('onclick' => sprintf("%s.http_request('event', 'action=undo', %s.display_message('', 'loading'))",
JS_OBJECT_NAME, JS_OBJECT_NAME)), rcube_label('undo')); JS_OBJECT_NAME, JS_OBJECT_NAME)), rcube_label('undo'));
$this->rc->output->show_message($msg, 'confirmation', null, true, $undo_time); $this->rc->output->show_message($msg, 'confirmation', null, true, $undo_time);
$got_msg = true;
} }
else if ($success) { else if ($success) {
$this->rc->output->show_message('calendar.successremoval', 'confirmation'); $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); $end = get_input_value('end', RCUBE_INPUT_GET);
if (!$start) $start = mktime(0, 0, 0, 1, date('n'), date('Y')-1); 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); 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-Type: text/calendar");
header("Content-Disposition: inline; filename=calendar.ics"); header("Content-Disposition: inline; filename=".$calendar_name);
echo $this->ical->export($events); echo $this->ical->export($events);
exit; exit;
@ -624,6 +629,8 @@ class calendar extends rcube_plugin
$settings['timeslots'] = (int)$this->rc->config->get('calendar_timeslots', $this->defaults['calendar_timeslots']); $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_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['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; $settings['timezone'] = $this->timezone;
// localization // localization
@ -1204,6 +1211,7 @@ class calendar extends rcube_plugin
if (!$start) $start = time(); if (!$start) $start = time();
if (!$end) $end = $start + 3600; 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'; $status = 'UNKNOWN';
// if the backend has free-busy information // if the backend has free-busy information
@ -1212,9 +1220,9 @@ class calendar extends rcube_plugin
$status = 'FREE'; $status = 'FREE';
foreach ($fblist as $slot) { foreach ($fblist as $slot) {
list($from, $to) = $slot; list($from, $to, $type) = $slot;
if ($from <= $end && $to > $start) { if ($from <= $end && $to > $start) {
$status = 'BUSY'; $status = $type && $fbtypemap[$type] ? $fbtypemap[$type] : 'BUSY';
break; break;
} }
} }
@ -1254,9 +1262,9 @@ class calendar extends rcube_plugin
if (is_array($fblist)) { if (is_array($fblist)) {
$status = self::FREEBUSY_FREE; $status = self::FREEBUSY_FREE;
foreach ($fblist as $slot) { foreach ($fblist as $slot) {
list($from, $to) = $slot; list($from, $to, $type) = $slot;
if ($from <= $t_end && $to > $t) { if ($from <= $t_end && $to > $t) {
$status = self::FREEBUSY_BUSY; $status = isset($type) ? $type : self::FREEBUSY_BUSY;
break; break;
} }
} }

View file

@ -42,10 +42,12 @@ function rcube_calendar_ui(settings)
var gmt_offset = (new Date().getTimezoneOffset() / -60) - (settings.timezone || 0); var gmt_offset = (new Date().getTimezoneOffset() / -60) - (settings.timezone || 0);
var day_clicked = day_clicked_ts = 0; var day_clicked = day_clicked_ts = 0;
var ignore_click = false; var ignore_click = false;
var event_defaults = { free_busy:'busy' };
var event_attendees = null; var event_attendees = null;
var attendees_list; var attendees_list;
var freebusy_ui = {}; var freebusy_ui = { workinhoursonly:false };
var freebusy_data = {}; var freebusy_data = {};
var freebusy_needsupdate;
// general datepicker settings // general datepicker settings
var datepicker_settings = { var datepicker_settings = {
@ -329,7 +331,9 @@ function rcube_calendar_ui(settings)
var $dialog = $("#eventedit"); var $dialog = $("#eventedit");
var calendar = event.calendar && me.calendars[event.calendar] ? me.calendars[event.calendar] : { editable:action=='new' }; 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 // reset dialog first, enable/disable fields according to editable state
$('#eventtabs').get(0).reset(); $('#eventtabs').get(0).reset();
@ -614,27 +618,34 @@ function rcube_calendar_ui(settings)
return false; return false;
// set form elements // 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 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) { if (allday.checked) {
starttime.val("00:00").hide(); starttime.val("00:00").hide();
endtime.val("23:59").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 // render time slots
var now = new Date(), fb_start = new Date(), fb_end = new Date(); 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_start.setHours(0); fb_start.setMinutes(0); fb_start.setSeconds(0); fb_start.setMilliseconds(0);
fb_end.setTime(fb_start.getTime() + 86400000); fb_end.setTime(fb_start.getTime() + 86400000);
freebusy_data = {}; freebusy_data = {};
freebusy_ui.loading = 1; // prevent render_freebusy_grid() to load data freebusy_ui.loading = 1; // prevent render_freebusy_grid() to load data yet
freebusy_ui.numdays = allday.checked ? 7 : 1; freebusy_ui.numdays = allday.checked ? 7 : Math.ceil(duration * 2 / 86400);
freebusy_ui.interval = allday.checked ? 360 : 60; freebusy_ui.interval = allday.checked ? 360 : 60;
freebusy_ui.start = fb_start; freebusy_ui.start = fb_start;
freebusy_ui.end = new Date(freebusy_ui.start.getTime() + 86400000 * freebusy_ui.numdays); 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); $('#schedule-attendees-list').html(list_html);
// enable/disable buttons
$('#shedule-find-prev').button('option', 'disabled', (fb_start.getTime() < now.getTime()));
// dialog buttons // dialog buttons
var 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() { buttons[rcmail.gettext('cancel', 'calendar')] = function() {
$dialog.dialog("close"); $dialog.dialog("close");
}; };
@ -664,6 +689,8 @@ function rcube_calendar_ui(settings)
closeOnEscape: true, closeOnEscape: true,
title: rcmail.gettext('scheduletime', 'calendar'), title: rcmail.gettext('scheduletime', 'calendar'),
close: function() { close: function() {
if (bw.ie6)
$("#edit-attendees-table").css('visibility','visible');
$dialog.dialog("destroy").hide(); $dialog.dialog("destroy").hide();
}, },
buttons: buttons, buttons: buttons,
@ -671,6 +698,18 @@ function rcube_calendar_ui(settings)
width: 850 width: 850
}).show(); }).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 // fetch data from server
freebusy_ui.loading = 0; freebusy_ui.loading = 0;
load_freebusy_data(freebusy_ui.start, freebusy_ui.interval); load_freebusy_data(freebusy_ui.start, freebusy_ui.interval);
@ -694,8 +733,8 @@ function rcube_calendar_ui(settings)
lastdate = datestr; lastdate = datestr;
} }
// TODO: define working hours by config // set css class according to working hours
css = (freebusy_ui.numdays == 1 && (curdate.getHours() < 6 || curdate.getHours() > 18)) ? 'offhours' : 'workinghours'; css = (freebusy_ui.numdays == 1 && (curdate.getHours() < settings['work_start'] || curdate.getHours() > settings['work_end'])) ? 'offhours' : 'workinghours';
times_row += '<td class="' + css + '">' + Q($.fullCalendar.formatDate(curdate, settings['time_format'])) + '</td>'; times_row += '<td class="' + css + '">' + Q($.fullCalendar.formatDate(curdate, settings['time_format'])) + '</td>';
slots_row += '<td class="' + css + ' unknown">&nbsp;</td>'; slots_row += '<td class="' + css + ' unknown">&nbsp;</td>';
@ -712,23 +751,98 @@ function rcube_calendar_ui(settings)
times_html += '<tr id="fbrow' + domid + '">' + slots_row + '</tr>'; times_html += '<tr id="fbrow' + domid + '">' + slots_row + '</tr>';
} }
$('#schedule-freebusy-times > thead').html(dates_row + times_row); var table = $('#schedule-freebusy-times');
$('#schedule-freebusy-times > tbody').html(times_html); table.children('thead').html(dates_row + times_row);
table.children('tbody').html(times_html);
// if we have loaded free-busy data, show it // if we have loaded free-busy data, show it
if (!freebusy_ui.loading) { 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) { 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) load_freebusy_data(freebusy_ui.start, freebusy_ui.interval);
return;
} }
else {
for (var email, i=0; i < event_attendees.length; i++) { for (var email, i=0; i < event_attendees.length; i++) {
if ((email = event_attendees[i].email)) if ((email = event_attendees[i].email))
update_freebusy_display(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 // fetch free-busy information for each attendee from server
var load_freebusy_data = function(from, interval) 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 // update event properties and attendees availability if event times have changed
@ -797,10 +1014,11 @@ function rcube_calendar_ui(settings)
{ {
if (me.selected_event) { if (me.selected_event) {
var allday = $('#edit-allday').get(0); 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.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()); me.selected_event.end = parse_datetime(allday.checked ? '23:59' : $('#edit-endtime').val(), $('#edit-enddate').val());
if (me.selected_event.attendees) if (event_attendees)
update_freebusy_status(me.selected_event); freebusy_needsupdate = true;
$('#edit-startdate').data('duration', Math.round((me.selected_event.end.getTime() - me.selected_event.start.getTime()) / 1000)); $('#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 update_freebusy_status = function(event)
{ {
var icons = attendees_list.find('img.availabilityicon'); var icons = attendees_list.find('img.availabilityicon');
for (var i=0; i < event.attendees.length; i++) { for (var i=0; i < event_attendees.length; i++) {
if (icons.get(i) && event.attendees[i].email && event.attendees[i].status != 'ACCEPTED') if (icons.get(i) && event_attendees[i].email && event_attendees[i].status != 'ACCEPTED')
check_freebusy_status(icons.get(i), event.attendees[i].email, event); check_freebusy_status(icons.get(i), event_attendees[i].email, event);
} }
freebusy_needsupdate = false;
}; };
// load free-busy status from server and update icon accordingly // 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 // callback for clicks in all-day box
dayClick: function(date, allDay, e, view) { dayClick: function(date, allDay, e, view) {
var now = new Date().getTime(); var now = new Date().getTime();
if (now - day_clicked_ts < 400 && day_clicked == date.getTime()) // emulate double-click on day 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 }); 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) { if (!ignore_click) {
view.calendar.gotoDate(date); view.calendar.gotoDate(date);
@ -1563,14 +1785,17 @@ function rcube_calendar_ui(settings)
// init event dialog // init event dialog
$('#eventtabs').tabs({ $('#eventtabs').tabs({
show: function(event, ui) { show: function(event, ui) {
if (ui.panel.id == 'event-tab-3') if (ui.panel.id == 'event-tab-3') {
$('#edit-attendee-name').select(); $('#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-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-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-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 // configure drop-down menu on time input fields based on jquery UI autocomplete
$('#edit-starttime, #edit-endtime, input.edit-alarm-time') $('#edit-starttime, #edit-endtime, input.edit-alarm-time')
@ -1630,13 +1855,17 @@ function rcube_calendar_ui(settings)
event_freebusy_dialog(); event_freebusy_dialog();
}); });
$('#shedule-freebusy-prev').button().click(function(){ render_freebusy_grid(-1); }); $('#shedule-freebusy-prev').html(bw.ie6 ? '&lt;&lt;' : '&#9668;').button().click(function(){ render_freebusy_grid(-1); });
$('#shedule-freebusy-next').button().click(function(){ render_freebusy_grid(1); }).parent().buttonset(); $('#shedule-freebusy-next').html(bw.ie6 ? '&gt;&gt;' : '&#9658;').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(){ $('#schedule-freebusy-wokinghours').click(function(){
freebusy_ui.workinhoursonly = this.checked;
$('#workinghourscss').remove(); $('#workinghourscss').remove();
if (this.checked) if (this.checked)
$('<style type="text/css" id="workinghourscss"> td.offhours { display:none } </style>').appendTo('head'); $('<style type="text/css" id="workinghourscss"> td.offhours { opacity:0.3; filter:alpha(opacity=30) } </style>').appendTo('head');
}); });
// add proprietary css styles if not IE // add proprietary css styles if not IE

View file

@ -52,6 +52,12 @@ $rcmail_config['calendar_first_day'] = 1;
// first hour of the calendar (0-23) // first hour of the calendar (0-23)
$rcmail_config['calendar_first_hour'] = 6; $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 // event categories
$rcmail_config['calendar_categories'] = array( $rcmail_config['calendar_categories'] = array(
'Personal' => 'c0c0c0', 'Personal' => 'c0c0c0',

View file

@ -67,9 +67,11 @@ class kolab_calendar
} }
else { else {
$acl = $this->storage->_folder->getACL(); $acl = $this->storage->_folder->getACL();
$acl = $acl[$_SESSION['username']]; if (is_array($acl)) {
if (strpos($acl, 'i') !== false) $acl = $acl[$_SESSION['username']];
$this->readonly = false; if (strpos($acl, 'i') !== false)
$this->readonly = false;
}
} }
} }
} }

View file

@ -693,9 +693,16 @@ class kolab_driver extends calendar_driver
if (empty($email)/* || $end < time()*/) if (empty($email)/* || $end < time()*/)
return false; 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 // ask kolab server first
$fbdata = @file_get_contents(rcube_kolab::get_freebusy_url($email)); $fbdata = @file_get_contents(rcube_kolab::get_freebusy_url($email));
// get free-busy url from contacts // get free-busy url from contacts
if (!$fbdata) { if (!$fbdata) {
$fburl = null; $fburl = null;
@ -722,10 +729,12 @@ class kolab_driver extends calendar_driver
$fbcal->parsevCalendar($fbdata); $fbcal->parsevCalendar($fbdata);
if ($fb = $fbcal->findComponent('vfreebusy')) { if ($fb = $fbcal->findComponent('vfreebusy')) {
$result = array(); $result = array();
$params = $fb->getExtraParams();
foreach ($fb->getBusyPeriods() as $from => $to) { foreach ($fb->getBusyPeriods() as $from => $to) {
if ($to == null) // no information, assume free if ($to == null) // no information, assume free
break; break;
$result[] = array($from, $to); $type = $params[$from]['FBTYPE'];
$result[] = array($from, $to, isset($fbtypemap[$type]) ? $fbtypemap[$type] : calendar::FREEBUSY_BUSY);
} }
return $result; return $result;

View file

@ -67,7 +67,7 @@ class calendar_ical
$ical .= "DTSTART:" . gmdate('Ymd\THis\Z', $event['start']) . "\r\n"; $ical .= "DTSTART:" . gmdate('Ymd\THis\Z', $event['start']) . "\r\n";
$ical .= "DTEND:" . gmdate('Ymd\THis\Z', $event['end']) . "\r\n"; $ical .= "DTEND:" . gmdate('Ymd\THis\Z', $event['end']) . "\r\n";
$ical .= "SUMMARY:" . self::escpape($event['title']) . "\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'])){ if (!empty($event['attendees'])){

View file

@ -592,10 +592,13 @@ class calendar_ui
$table->add('attendees', $table->add('attendees',
html::tag('h3', 'boxtitle', $this->calendar->gettext('tabattendees')) . html::tag('h3', 'boxtitle', $this->calendar->gettext('tabattendees')) .
html::div('timesheader', '&nbsp;') . html::div('timesheader', '&nbsp;') .
html::div(array('id' => 'schedule-attendees-list'), '') html::div(array('id' => 'schedule-attendees-list', 'class' => 'attendees-list'), '')
); );
$table->add('times', $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'), '&nbsp;')
)
); );
return $table->show($attrib); return $table->show($attrib);

View file

@ -31,6 +31,7 @@ $labels['edit'] = 'Edit';
$labels['save'] = 'Save'; $labels['save'] = 'Save';
$labels['remove'] = 'Remove'; $labels['remove'] = 'Remove';
$labels['cancel'] = 'Cancel'; $labels['cancel'] = 'Cancel';
$labels['adobt'] = 'Adopt changes';
$labels['print'] = 'Print calendars'; $labels['print'] = 'Print calendars';
$labels['title'] = 'Summary'; $labels['title'] = 'Summary';
$labels['description'] = 'Description'; $labels['description'] = 'Description';
@ -95,10 +96,14 @@ $labels['roleresource'] = 'Resource';
$labels['availfree'] = 'Free'; $labels['availfree'] = 'Free';
$labels['availbusy'] = 'Busy'; $labels['availbusy'] = 'Busy';
$labels['availunknown'] = 'Unknown'; $labels['availunknown'] = 'Unknown';
$labels['availtentative'] = 'Tentative';
$labels['availoutofoffice'] = 'Out of Office'; $labels['availoutofoffice'] = 'Out of Office';
$labels['scheduletime'] = 'Available times'; $labels['scheduletime'] = 'Available times';
$labels['sendnotifications'] = 'Send notifications'; $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 // event dialog tabs
$labels['tabsummary'] = 'Summary'; $labels['tabsummary'] = 'Summary';

View file

@ -36,6 +36,7 @@ body.calendarmain {
#datepicker .ui-datepicker { #datepicker .ui-datepicker {
width: 97% !important; width: 97% !important;
box-shadow: none;
-moz-box-shadow: none; -moz-box-shadow: none;
-webkit-box-shadow: none; -webkit-box-shadow: none;
} }
@ -566,7 +567,7 @@ td.topalign {
} }
.availability img.availabilityicon.loading { .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, #schedule-freebusy-times td.unknown,
@ -584,6 +585,11 @@ td.topalign {
background: #c00; background: #c00;
} }
#schedule-freebusy-times td.tentative,
.availability img.availabilityicon.tentative {
background: #66d;
}
#schedule-freebusy-times td.out-of-office, #schedule-freebusy-times td.out-of-office,
.availability img.availabilityicon.out-of-office { .availability img.availabilityicon.out-of-office {
background: #f0b400; background: #f0b400;
@ -591,6 +597,7 @@ td.topalign {
#edit-attendees-legend { #edit-attendees-legend {
margin-top: 3em; margin-top: 3em;
margin-bottom: 0.5em;
} }
#edit-attendees-legend .legend { #edit-attendees-legend .legend {
@ -651,6 +658,7 @@ td.topalign {
} }
#attendees-freebusy-table div.scroll { #attendees-freebusy-table div.scroll {
position: relative;
overflow: auto; overflow: auto;
} }
@ -660,17 +668,33 @@ td.topalign {
border-color: #ccc; border-color: #ccc;
} }
#attendees-freebusy-table div.timesheader { .attendees-list .attendee {
padding: 0.3em; padding: 3px 4px 3px 20px;
background: url('images/attendee-status.gif') 2px -97px no-repeat;
} }
#schedule-attendees-list div.attendee { .attendees-list div.attendee {
padding: 3px 20px 3px 6px;
border-top: 1px solid #ccc; border-top: 1px solid #ccc;
} }
#schedule-attendees-list div.loading { .attendees-list span.attendee {
background: url('images/loading-small.gif') top right no-repeat; 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 { #schedule-freebusy-times {
@ -688,6 +712,7 @@ td.topalign {
border-width: 0 1px 0 1px; border-width: 0 1px 0 1px;
} }
#attendees-freebusy-table div.timesheader,
#schedule-freebusy-times tr.times td { #schedule-freebusy-times tr.times td {
min-width: 30px; min-width: 30px;
font-size: 9px; font-size: 9px;
@ -695,9 +720,19 @@ td.topalign {
text-align: center; 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 { #eventfreebusy .schedule-options {
position: relative; position: relative;
margin-bottom: 2em; margin-bottom: 1.5em;
} }
#eventfreebusy .schedule-buttons { #eventfreebusy .schedule-buttons {
@ -706,6 +741,15 @@ td.topalign {
right: 0; 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 { span.edit-alarm-set {
white-space: nowrap; white-space: nowrap;
} }

View file

@ -43,3 +43,12 @@ html #calendartoolbar a.buttonPas {
.fc-header-title h2 { .fc-header-title h2 {
font-size: 16px; font-size: 16px;
} }
#schedule-event-time {
filter: alpha(opacity=40);
}
#eventfreebusy .schedule-buttons,
#edit-attendees-form #edit-attendee-schedule {
right: 0.6em;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -188,25 +188,42 @@
<roundcube:object name="plugin.attendees_freebusy_table" id="attendees-freebusy-table" cellspacing="0" cellpadding="0" border="0" /> <roundcube:object name="plugin.attendees_freebusy_table" id="attendees-freebusy-table" cellspacing="0" cellpadding="0" border="0" />
<div class="schedule-options"> <div class="schedule-options">
<label><input type="checkbox" id="schedule-freebusy-wokinghours" value="1" /><roundcube:label name="calendar.onlyworkinghours" /></label> &nbsp;
<div class="schedule-buttons"> <div class="schedule-buttons">
<button id="shedule-freebusy-prev" title="<roundcube:label name='previouspage' />">&#9668;</button> <button id="shedule-freebusy-prev" title="<roundcube:label name='previouspage' />">&#9668;</button><button id="shedule-freebusy-next" title="<roundcube:label name='nextpage' />">&#9658;</button>
<button id="shedule-freebusy-next" title="<roundcube:label name='nextpage' />">&#9658;</button>
</div> </div>
</div> </div>
<div class="form-section"> <div style="float:left; width:28em">
<label for="schedule-startdate"><roundcube:label name="calendar.start" /></label> <div class="form-section">
<input type="text" name="startdate" size="10" id="schedule-startdate" disabled="true" /> &nbsp; <label for="schedule-startdate"><roundcube:label name="calendar.start" /></label>
<input type="text" name="starttime" size="6" id="schedule-starttime" disabled="true" /> <input type="text" name="startdate" size="10" id="schedule-startdate" disabled="true" /> &nbsp;
<input type="text" name="starttime" size="6" id="schedule-starttime" disabled="true" />
</div>
<div class="form-section">
<label for="schedule-enddate"><roundcube:label name="calendar.end" /></label>
<input type="text" name="enddate" size="10" id="schedule-enddate" disabled="true" /> &nbsp;
<input type="text" name="endtime" size="6" id="schedule-endtime" disabled="true" />
</div>
</div> </div>
<div class="form-section"> <div style="float:left">
<label for="schedule-enddate"><roundcube:label name="calendar.end" /></label> <div class="schedule-find-buttons">
<input type="text" name="enddate" size="10" id="schedule-enddate" disabled="true" /> &nbsp; <button id="shedule-find-prev">&#9668; <roundcube:label name="calendar.prevslot" /></button>
<input type="text" name="endtime" size="6" id="schedule-endtime" disabled="true" /> <button id="shedule-find-next"><roundcube:label name="calendar.nextslot" /> &#9658;</button>
</div>
<div class="schedule-options">
<label><input type="checkbox" id="schedule-freebusy-wokinghours" value="1" /><roundcube:label name="calendar.onlyworkinghours" /></label>
</div>
</div> </div>
<br style="clear:both;" />
<roundcube:include file="/templates/freebusylegend.html" /> <roundcube:include file="/templates/freebusylegend.html" />
<div class="attendees-list">
<span class="attendee organizer"><roundcube:label name="calendar.roleorganizer" /></span>
<span class="attendee req-participant"><roundcube:label name="calendar.rolerequired" /></span>
<span class="attendee opt-participant"><roundcube:label name="calendar.roleoptional" /></span>
<span class="attendee chair"><roundcube:label name="calendar.roleresource" /></span>
</div>
</div> </div>
<div id="calendarform" class="uidialog"> <div id="calendarform" class="uidialog">

View file

@ -1,6 +1,7 @@
<div id="edit-attendees-legend" class="availability"> <div id="edit-attendees-legend" class="availability">
<span class="legend"><img class="availabilityicon free" src="./program/blank.gif" /> <roundcube:label name="calendar.availfree" /></span> <span class="legend"><img class="availabilityicon free" src="./program/blank.gif" /> <roundcube:label name="calendar.availfree" /></span>
<span class="legend"><img class="availabilityicon busy" src="./program/blank.gif" /> <roundcube:label name="calendar.availbusy" /></span> <span class="legend"><img class="availabilityicon busy" src="./program/blank.gif" /> <roundcube:label name="calendar.availbusy" /></span>
<span class="legend"><img class="availabilityicon tentative" src="./program/blank.gif" /> <roundcube:label name="calendar.availtentative" /></span>
<span class="legend"><img class="availabilityicon out-of-office" src="./program/blank.gif" /> <roundcube:label name="calendar.availoutofoffice" /></span> <span class="legend"><img class="availabilityicon out-of-office" src="./program/blank.gif" /> <roundcube:label name="calendar.availoutofoffice" /></span>
<span class="legend"><img class="availabilityicon unknown" src="./program/blank.gif" /> <roundcube:label name="calendar.availunknown" /></span> <span class="legend"><img class="availabilityicon unknown" src="./program/blank.gif" /> <roundcube:label name="calendar.availunknown" /></span>
</div> </div>

View file

@ -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; level = olddata.realname.split(this.env.delimiter).length - data.realname.split(this.env.delimiter).length;
// update (realname and ID of) subfolders // update (realname and ID of) subfolders
for (n in sources) { for (n in sources) {
if (n.indexOf(old) == 0) { if (n != data.id && n.indexOf(old) == 0) {
// new ID // new ID
id = data.id + '-' + n.substr(old.length); id = data.id + '-' + n.substr(old.length);
name = sources[n].name; name = sources[n].name;

View file

@ -27,7 +27,7 @@
*/ */
class kolab_addressbook extends rcube_plugin class kolab_addressbook extends rcube_plugin
{ {
public $task = 'mail|settings|addressbook'; public $task = 'mail|settings|addressbook|calendar';
private $folders; private $folders;
private $sources; private $sources;
@ -54,6 +54,7 @@ class kolab_addressbook extends rcube_plugin
// register hooks // register hooks
$this->add_hook('addressbooks_list', array($this, 'address_sources')); $this->add_hook('addressbooks_list', array($this, 'address_sources'));
$this->add_hook('addressbook_get', array($this, 'get_address_book')); $this->add_hook('addressbook_get', array($this, 'get_address_book'));
$this->add_hook('config_get', array($this, 'config_get'));
if ($this->rc->task == 'addressbook') { if ($this->rc->task == 'addressbook') {
$this->add_texts('localization'); $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_list', array($this, 'prefs_list'));
$this->add_hook('preferences_save', array($this, 'prefs_save')); $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 * Sets autocomplete_addressbooks option according to
* kolab_addressbook_prio setting. * 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 // Load configuration
$this->load_config(); $this->load_config();
$abook_prio = (int) $this->rc->config->get('kolab_addressbook_prio'); $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 // Disable all global address books
// Assumes that all non-kolab_addressbook sources are global // Assumes that all non-kolab_addressbook sources are global
@ -166,6 +169,10 @@ class kolab_addressbook extends rcube_plugin
$sources = array(); $sources = array();
} }
if (!is_array($sources)) {
$sources = array();
}
$kolab_sources = array(); $kolab_sources = array();
foreach ($this->_list_sources() as $abook_id => $abook) { foreach ($this->_list_sources() as $abook_id => $abook) {
if (!in_array($abook_id, $sources)) if (!in_array($abook_id, $sources))
@ -180,9 +187,11 @@ class kolab_addressbook extends rcube_plugin
else { else {
$sources = array_merge($sources, $kolab_sources); $sources = array_merge($sources, $kolab_sources);
} }
$this->rc->config->set('autocomplete_addressbooks', $sources);
} }
$args['result'] = $sources;
return $args;
} }

View file

@ -128,11 +128,13 @@ class rcube_kolab_contacts extends rcube_addressbook
} }
else { else {
$acl = $this->storagefolder->getACL(); $acl = $this->storagefolder->getACL();
$acl = $acl[$_SESSION['username']]; if (is_array($acl)) {
if (strpos($acl, 'i') !== false) $acl = $acl[$_SESSION['username']];
$this->readonly = false; if (strpos($acl, 'i') !== false)
if (strpos($acl, 'a') !== false || strpos($acl, 'x') !== false) $this->readonly = false;
$this->editable = true; 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; $search = $value;
} }
$s_len = strlen($search);
foreach ((array)$contact[$col] as $val) { foreach ((array)$contact[$col] as $val) {
// composite field, e.g. address // composite field, e.g. address
if (is_array($val)) { if (is_array($val)) {
@ -370,7 +374,9 @@ class rcube_kolab_contacts extends rcube_addressbook
} }
$val = mb_strtolower($val); $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) { if (!$advanced) {
$this->filter['ids'][] = $id; $this->filter['ids'][] = $id;
break 2; break 2;
@ -892,6 +898,31 @@ class rcube_kolab_contacts extends rcube_addressbook
return false; 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 * 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 * 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 * 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 * Callback function for sorting contacts
*/ */
@ -950,7 +978,6 @@ class rcube_kolab_contacts extends rcube_addressbook
return strcasecmp($a['name'], $b['name']); return strcasecmp($a['name'], $b['name']);
} }
/** /**
* Read distribution-lists AKA groups from server * 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 * 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); return array_filter($out);
} }
/**
* Map fields from Roundcube format to internal Kolab_Format
*/
private function _from_rcube_contact($contact) private function _from_rcube_contact($contact)
{ {
$object = array(); $object = array();

View file

@ -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['bookdeleteconfirm'] = 'Do you really want to delete the selected address book and all contacts in it?';
$messages['bookdeleting'] = 'Deleting address book...'; $messages['bookdeleting'] = 'Deleting address book...';
$messages['booksaving'] = 'Saving address book...'; $messages['booksaving'] = 'Saving address book...';
$messages['bookdeleted'] = 'Address book deleted successfully'; $messages['bookdeleted'] = 'Address book deleted successfully.';
$messages['bookupdated'] = 'Address book updated successfully'; $messages['bookupdated'] = 'Address book updated successfully.';
$messages['bookcreated'] = 'Address book created successfully'; $messages['bookcreated'] = 'Address book created successfully.';
$messages['bookdeleteerror'] = 'An error occured while deleting address book'; $messages['bookdeleteerror'] = 'An error occured while deleting address book.';
$messages['bookupdateerror'] = 'An error occured while updating address book'; $messages['bookupdateerror'] = 'An error occured while updating address book.';
$messages['bookcreateerror'] = 'An error occured while creating address book'; $messages['bookcreateerror'] = 'An error occured while creating address book.';
$messages['nobooknamewarning'] = 'Please, enter address book name'; $messages['nobooknamewarning'] = 'Please, enter address book name.';
$messages['noemailnamewarning'] = 'Please, enter email address or contact name.';
?> ?>

View file

@ -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['bookdeleteconfirm'] = 'Czy na pewno chcesz usunąć wybraną książkę i wszystkie kontakty w niej zapisane?';
$messages['bookdeleting'] = 'Usuwanie książki adresowej...'; $messages['bookdeleting'] = 'Usuwanie książki adresowej...';
$messages['booksaving'] = 'Zapisywanie książki adresowej...'; $messages['booksaving'] = 'Zapisywanie książki adresowej...';
$messages['bookdeleted'] = 'Książka adresowa została usunięta'; $messages['bookdeleted'] = 'Książka adresowa została usunięta.';
$messages['bookupdated'] = 'Książka adresowa została zaktualizowana'; $messages['bookupdated'] = 'Książka adresowa została zaktualizowana.';
$messages['bookcreated'] = 'Książka adresowa została utworzona'; $messages['bookcreated'] = 'Książka adresowa została utworzona.';
$messages['bookdeleteerror'] = 'Wystąpił błąd podczas usuwania książki adresowej'; $messages['bookdeleteerror'] = 'Wystąpił błąd podczas usuwania książki adresowej.';
$messages['bookupdateerror'] = 'Wystąpił błąd podczas zmiany 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['bookcreateerror'] = 'Wystąpił błąd podczas tworzenia książki adresowej.';
$messages['nobooknamewarning'] = 'Proszę podać nazwę książki adresowej'; $messages['nobooknamewarning'] = 'Proszę podać nazwę książki adresowej.';
$messages['noemailnamewarning'] = 'Proszę podać adres email lub nazwę kontaktu.';
?> ?>

View file

@ -29,6 +29,8 @@ class rcube_kolab
private static $horde_auth; private static $horde_auth;
private static $config; private static $config;
private static $ready = false; private static $ready = false;
private static $list;
private static $cache;
/** /**
@ -72,9 +74,11 @@ class rcube_kolab
// Re-set LDAP/IMAP host config // Re-set LDAP/IMAP host config
$ldap = array('server' => 'ldap://' . $_SESSION['imap_host'] . ':389'); $ldap = array('server' => 'ldap://' . $_SESSION['imap_host'] . ':389');
$imap = array('server' => $_SESSION['imap_host'], 'port' => $_SESSION['imap_port']); $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']['ldap'] = array_merge($ldap, (array)$conf['kolab']['ldap']);
$conf['kolab']['imap'] = array_merge($imap, (array)$conf['kolab']['imap']); $conf['kolab']['imap'] = array_merge($imap, (array)$conf['kolab']['imap']);
$conf['kolab']['freebusy'] = array_merge($freebusy, (array)$conf['kolab']['freebusy']);
self::$config = &$conf; self::$config = &$conf;
// pass the current IMAP authentication credentials to the Horde auth system // pass the current IMAP authentication credentials to the Horde auth system
@ -88,19 +92,57 @@ class rcube_kolab
); );
Auth::setCredential('password', $pwd); Auth::setCredential('password', $pwd);
self::$ready = true; 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'); NLS::setCharset('UTF-8');
String::setDefaultCharset('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 * Get instance of a Kolab (XML) format object
* *
@ -123,8 +165,7 @@ class rcube_kolab
*/ */
public static function get_folders($type) public static function get_folders($type)
{ {
self::setup(); $kolab = self::get_folders_list();
$kolab = Kolab_List::singleton();
return self::$ready ? $kolab->getByType($type) : array(); return self::$ready ? $kolab->getByType($type) : array();
} }
@ -136,9 +177,8 @@ class rcube_kolab
*/ */
public static function get_folder($folder) public static function get_folder($folder)
{ {
self::setup(); $kolab = self::get_folders_list();
$kolab = Kolab_List::singleton(); return self::$ready ? $kolab->getFolder($folder) : null;
return self::$ready ? $kolab->getFolder($folder) : null;
} }
/** /**
@ -151,8 +191,7 @@ class rcube_kolab
*/ */
public static function get_storage($folder, $data_type = null) public static function get_storage($folder, $data_type = null)
{ {
self::setup(); $kolab = self::get_folders_list();
$kolab = Kolab_List::singleton();
return self::$ready ? $kolab->getFolder($folder)->getData($data_type) : null; return self::$ready ? $kolab->getFolder($folder)->getData($data_type) : null;
} }
@ -175,18 +214,6 @@ class rcube_kolab
unset($_SESSION['__auth']); 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 * Creates folder ID from folder name
* *
@ -208,8 +235,7 @@ class rcube_kolab
*/ */
public static function folder_delete($name) public static function folder_delete($name)
{ {
self::setup(); $kolab = self::get_folders_list();
$kolab = Kolab_List::singleton();
$folder = $kolab->getFolder($name); $folder = $kolab->getFolder($name);
$result = $folder->delete(); $result = $folder->delete();
@ -232,8 +258,7 @@ class rcube_kolab
*/ */
public static function folder_create($name, $type=null, $default=false) public static function folder_create($name, $type=null, $default=false)
{ {
self::setup(); $kolab = self::get_folders_list();
$kolab = Kolab_List::singleton();
$folder = new Kolab_Folder(); $folder = new Kolab_Folder();
$folder->setList($kolab); $folder->setList($kolab);
@ -261,17 +286,26 @@ class rcube_kolab
*/ */
public static function folder_rename($oldname, $newname) public static function folder_rename($oldname, $newname)
{ {
self::setup(); $kolab = self::get_folders_list();
$kolab = Kolab_List::singleton();
$folder = $kolab->getFolder($oldname); $folder = $kolab->getFolder($oldname);
$folder->setFolder($newname); $folder->setFolder($newname);
// We're not using $folder->save() because some caching issues
$result = $kolab->rename($folder); $result = $kolab->rename($folder);
if (is_a($result, 'PEAR_Error')) { if (is_a($result, 'PEAR_Error')) {
return false; 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; return true;
} }