Merge branch 'master' of ssh://git.kolabsys.com/git/roundcube
This commit is contained in:
commit
54367e737c
19 changed files with 544 additions and 137 deletions
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 += '<td class="' + css + '">' + Q($.fullCalendar.formatDate(curdate, settings['time_format'])) + '</td>';
|
||||
slots_row += '<td class="' + css + ' unknown"> </td>';
|
||||
|
||||
|
@ -712,23 +751,98 @@ function rcube_calendar_ui(settings)
|
|||
times_html += '<tr id="fbrow' + domid + '">' + slots_row + '</tr>';
|
||||
}
|
||||
|
||||
$('#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)
|
||||
$('<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
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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'])){
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 2 KiB |
|
@ -188,25 +188,42 @@
|
|||
<roundcube:object name="plugin.attendees_freebusy_table" id="attendees-freebusy-table" cellspacing="0" cellpadding="0" border="0" />
|
||||
|
||||
<div class="schedule-options">
|
||||
<label><input type="checkbox" id="schedule-freebusy-wokinghours" value="1" /><roundcube:label name="calendar.onlyworkinghours" /></label>
|
||||
|
||||
<div class="schedule-buttons">
|
||||
<button id="shedule-freebusy-prev" title="<roundcube:label name='previouspage' />">◄</button>
|
||||
<button id="shedule-freebusy-next" title="<roundcube:label name='nextpage' />">►</button>
|
||||
<button id="shedule-freebusy-prev" title="<roundcube:label name='previouspage' />">◄</button><button id="shedule-freebusy-next" title="<roundcube:label name='nextpage' />">►</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-section">
|
||||
<label for="schedule-startdate"><roundcube:label name="calendar.start" /></label>
|
||||
<input type="text" name="startdate" size="10" id="schedule-startdate" disabled="true" />
|
||||
<input type="text" name="starttime" size="6" id="schedule-starttime" disabled="true" />
|
||||
<div style="float:left; width:28em">
|
||||
<div class="form-section">
|
||||
<label for="schedule-startdate"><roundcube:label name="calendar.start" /></label>
|
||||
<input type="text" name="startdate" size="10" id="schedule-startdate" disabled="true" />
|
||||
<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" />
|
||||
<input type="text" name="endtime" size="6" id="schedule-endtime" disabled="true" />
|
||||
</div>
|
||||
</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" />
|
||||
<input type="text" name="endtime" size="6" id="schedule-endtime" disabled="true" />
|
||||
<div style="float:left">
|
||||
<div class="schedule-find-buttons">
|
||||
<button id="shedule-find-prev">◄ <roundcube:label name="calendar.prevslot" /></button>
|
||||
<button id="shedule-find-next"><roundcube:label name="calendar.nextslot" /> ►</button>
|
||||
</div>
|
||||
<div class="schedule-options">
|
||||
<label><input type="checkbox" id="schedule-freebusy-wokinghours" value="1" /><roundcube:label name="calendar.onlyworkinghours" /></label>
|
||||
</div>
|
||||
</div>
|
||||
<br style="clear:both;" />
|
||||
|
||||
<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 id="calendarform" class="uidialog">
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<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 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 unknown" src="./program/blank.gif" /> <roundcube:label name="calendar.availunknown" /></span>
|
||||
</div>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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.';
|
||||
|
||||
?>
|
||||
|
|
|
@ -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.';
|
||||
|
||||
?>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue