diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php
index 98dc5ce1..f472743d 100644
--- a/plugins/calendar/calendar.php
+++ b/plugins/calendar/calendar.php
@@ -1235,11 +1235,15 @@ class calendar extends rcube_plugin
{
$email = get_input_value('email', RCUBE_INPUT_GPC);
$start = get_input_value('start', RCUBE_INPUT_GET);
- $end = get_input_value('end', RCUBE_INPUT_GET);
- $interval = 60; // in minutes
+# $end = get_input_value('end', RCUBE_INPUT_GET);
+ $interval = 3600; // in seconds
if (!$start) $start = time();
- if (!$end) $end = $start + 86400 * 7;
+ if (!$end) $end = $start + 86400 * 30;
+
+ // reset $start/$end to midnight
+ #$start = gmmktime(0, 0, 0, gmdate('n', $start), gmdate('j'), gmdate('Y'));
+ #$end = gmmktime(23, 59, 59, gmdate('n', $end), gmdate('j'), gmdate('Y'));
$fblist = $this->driver->get_freebusy_list($email, $start, $end);
$slots = array();
@@ -1247,7 +1251,7 @@ class calendar extends rcube_plugin
// build a list from $start till $end with blocks representing the fb-status
for ($s = 0, $t = $start; $t <= $end; $s++) {
$status = self::FREEBUSY_UNKNOWN;
- $t_end = $t + $interval*60;
+ $t_end = $t + $interval;
// determine attendee's status
if (is_array($fblist)) {
@@ -1260,12 +1264,12 @@ class calendar extends rcube_plugin
}
}
}
-
+
$slots[$s] = $status;
$t = $t_end;
}
- echo json_encode(array('email' => $email, 'slots' => $slots, 'interval' => $interval));
+ echo json_encode(array('email' => $email, 'start' => intval($start), 'interval' => $interval, 'slots' => $slots));
exit;
}
diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js
index 0b5f6001..02564d3c 100644
--- a/plugins/calendar/calendar_ui.js
+++ b/plugins/calendar/calendar_ui.js
@@ -44,6 +44,8 @@ function rcube_calendar_ui(settings)
var ignore_click = false;
var event_attendees = null;
var attendees_list;
+ var freebusy_ui = {};
+ var freebusy_data = {};
// general datepicker settings
var datepicker_settings = {
@@ -601,7 +603,7 @@ function rcube_calendar_ui(settings)
title.select();
};
-
+
var event_freebusy_dialog = function()
{
var $dialog = $('#eventfreebusy').dialog('close');
@@ -615,52 +617,39 @@ function rcube_calendar_ui(settings)
var endtime = $('#schedule-endtime').val($.fullCalendar.formatDate(event.end, settings['time_format'])).show();
var allday = $('#edit-allday').get(0);
- if (event.allDay) {
+ if (allday.checked) {
starttime.val("00:00").hide();
endtime.val("23:59").hide();
}
// render time slots
- var now = new Date(), range_start = new Date(), range_end = new Date();
- range_start.setTime(Math.max(now, event.start.getTime() - 86400000 * 3)); // start 3 days before event
- range_start.setHours(0);
- range_start.setMinutes(0);
- range_start.setSeconds(0);
- range_end.setTime(range_start.getTime() + 86400000 * 10); // show 10 days
+ var interval = 60;
+ var now = new Date(), fb_start = new Date(), fb_end = new Date();
+ fb_start.setTime(Math.max(now, 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);
- var interval = 60; // minutes
- var dayslots = 24;
- var lastdate, datestr, curdate = new Date(), dates_row = '
', times_row = '
', slots_row = '';
- for (var s = 0, t = range_start.getTime(); t < range_end.getTime(); s++) {
- curdate.setTime(t);
- datestr = $.fullCalendar.formatDate(curdate, settings['date_format']);
- if (datestr != lastdate) {
- dates_row += '' + Q(datestr) + ' | ';
- lastdate = datestr;
- }
-
- times_row += '' + Q($.fullCalendar.formatDate(curdate, settings['time_format'])) + ' | ';
- slots_row += ' | ';
-
- t += interval * 60000;
- }
- dates_row += '
';
- times_row += '';
+ freebusy_data = {};
+ freebusy_ui.loading = 1;
+ freebusy_ui.numdays = 1;
+ freebusy_ui.interval = interval;
+ freebusy_ui.start = fb_start;
+ freebusy_ui.end = new Date(freebusy_ui.start.getTime() + 86400000 * freebusy_ui.numdays);
+ render_freebusy_grid(0);
// render list of attendees
- var domid, dispname, data, list_html = '', times_html = '';
+ var domid, dispname, data, list_html = '';
for (var i=0; i < event_attendees.length; i++) {
data = event_attendees[i];
dispname = Q(data.name || data.email);
domid = String(data.email).replace(rcmail.identifier_expr, '');
-
list_html += '' + dispname + '
';
- times_html += '' + slots_row + '
';
}
$('#schedule-attendees-list').html(list_html);
- $('#schedule-freebusy-times > thead').html(dates_row + times_row);
- $('#schedule-freebusy-times > tbody').html(times_html);
// dialog buttons
var buttons = {};
@@ -680,9 +669,71 @@ function rcube_calendar_ui(settings)
buttons: buttons,
minWidth: 640,
width: 850
-
}).show();
+ // fetch data from server
+ freebusy_ui.loading = 0;
+ load_freebusy_data(fb_start, interval);
+ };
+
+ // render an HTML table showing free-busy status for all the event attendees
+ var render_freebusy_grid = function(delta)
+ {
+ if (delta) {
+ freebusy_ui.start.setTime(freebusy_ui.start.getTime() + 86400000 * delta);
+ freebusy_ui.end = new Date(freebusy_ui.start.getTime() + 86400000 * freebusy_ui.numdays);
+ }
+
+ var dayslots = Math.floor(1440 / freebusy_ui.interval);
+ var lastdate, datestr, css, curdate = new Date(), dates_row = '', times_row = '
', slots_row = '';
+ for (var s = 0, t = freebusy_ui.start.getTime(); t < freebusy_ui.end.getTime(); s++) {
+ curdate.setTime(t);
+ datestr = $.fullCalendar.formatDate(curdate, settings['date_format']);
+ if (datestr != lastdate) {
+ dates_row += '' + Q(datestr) + ' | ';
+ lastdate = datestr;
+ }
+
+ // TODO: define working hours by config
+ css = !(curdate.getHours() >= 6 && curdate.getHours() <= 18) ? 'offhours' : 'workinghours';
+ times_row += '' + Q($.fullCalendar.formatDate(curdate, settings['time_format'])) + ' | ';
+ slots_row += ' | ';
+
+ t += freebusy_ui.interval * 60000;
+ }
+ dates_row += '
';
+ times_row += '';
+
+ // render list of attendees
+ var domid, data, list_html = '', times_html = '';
+ for (var i=0; i < event_attendees.length; i++) {
+ data = event_attendees[i];
+ domid = String(data.email).replace(rcmail.identifier_expr, '');
+ times_html += '' + slots_row + '
';
+ }
+
+ $('#schedule-freebusy-times > thead').html(dates_row + times_row);
+ $('#schedule-freebusy-times > 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) {
+ load_freebusy_data(freebusy_ui.start, freebusy_ui.interval)
+ return;
+ }
+
+ for (var email, i=0; i < event_attendees.length; i++) {
+ if ((email = event_attendees[i].email))
+ update_freebusy_display(email);
+ }
+ }
+ };
+
+ // fetch free-busy information for each attendee from server
+ var load_freebusy_data = function(from, interval)
+ {
+ var start = new Date(from.getTime() - 86400000 * 2); // start 1 days before event
+ var end = new Date(start.getTime() + 86400000 * 14); // load 14 days
// load free-busy information for every attendee
var domid, email
@@ -690,35 +741,56 @@ function rcube_calendar_ui(settings)
if ((email = event_attendees[i].email)) {
domid = String(email).replace(rcmail.identifier_expr, '');
$('#rcmli' + domid).addClass('loading');
+ freebusy_ui.loading++;
$.ajax({
type: 'GET',
dataType: 'json',
url: rcmail.url('freebusy-times'),
- data: { email:email, start:date2unixtime(range_start), end:date2unixtime(range_end), _remote: 1 },
+ data: { email:email, start:date2unixtime(start), end:date2unixtime(end), _remote: 1 },
success: function(data){
- var status_classes = ['unknown','free','busy','tentative','out-of-office'];
+ freebusy_ui.loading--;
+
+ // copy data to member var
+ var ts = data.start - 0;
+ freebusy_data.start = ts;
+ freebusy_data[data.email] = {};
+ for (var i=0; i < data.slots.length; i++) {
+ freebusy_data[data.email][ts] = data.slots[i];
+ ts += data.interval;
+ }
+ freebusy_data.end = ts;
+
+ // hide loading indicator
var domid = String(data.email).replace(rcmail.identifier_expr, '');
$('#rcmli' + domid).removeClass('loading');
- var row = $('#fbrow' + domid);
- if (data.slots && row.length) {
- row.children().each(function(i, cell){
- cell.className = data.slots[i] ? status_classes[data.slots[i]] : 'unknown';
- });
- }
+ // update display
+ update_freebusy_display(data.email);
}
});
}
}
-
- // scroll to event date
- var pos = $('#schedule-freebusy-times th.date' + $.fullCalendar.formatDate(event.start, 'ddMMyyyy')).position();
- if (pos && pos.left)
- $('#schedule-freebusy-times').parent().scrollLeft(pos.left);
-
};
-
+
+ // update free-busy grid with status loaded from server
+ var update_freebusy_display = function(email)
+ {
+ var status_classes = ['unknown','free','busy','tentative','out-of-office'];
+ var domid = String(email).replace(rcmail.identifier_expr, '');
+ var row = $('#fbrow' + domid);
+ var ts = date2unixtime(freebusy_ui.start);
+ var fbdata = freebusy_data[email];
+
+ if (fbdata && fbdata[ts] && row.length) {
+ row.children().each(function(i, cell){
+ cell.className = cell.className.replace('unknown', fbdata[ts] ? status_classes[fbdata[ts]] : 'unknown');
+ ts += freebusy_ui.interval * 60;
+ });
+ }
+ };
+
+
// update event properties and attendees availability if event times have changed
var event_times_changed = function()
{
@@ -1523,6 +1595,15 @@ function rcube_calendar_ui(settings)
$('#edit-attendee-schedule').click(function(){
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();
+
+ $('#schedule-freebusy-wokinghours').click(function(){
+ $('#workinghourscss').remove();
+ if (this.checked)
+ $('').appendTo('head');
+ });
// add proprietary css styles if not IE
if (!bw.ie)
diff --git a/plugins/calendar/drivers/kolab/kolab_calendar.php b/plugins/calendar/drivers/kolab/kolab_calendar.php
index d6b89f97..816a9444 100644
--- a/plugins/calendar/drivers/kolab/kolab_calendar.php
+++ b/plugins/calendar/drivers/kolab/kolab_calendar.php
@@ -198,6 +198,9 @@ class kolab_calendar
{
$this->_fetch_events();
+ if (!empty($search))
+ $search = mb_strtolower($search);
+
$events = array();
foreach ($this->events as $id => $event) {
// filter events by search query
diff --git a/plugins/calendar/localization/en_US.inc b/plugins/calendar/localization/en_US.inc
index fecda11c..a4a8a509 100644
--- a/plugins/calendar/localization/en_US.inc
+++ b/plugins/calendar/localization/en_US.inc
@@ -98,6 +98,7 @@ $labels['availunknown'] = 'Unknown';
$labels['availoutofoffice'] = 'Out of Office';
$labels['scheduletime'] = 'Available times';
$labels['sendnotifications'] = 'Send notifications';
+$labels['onlyworkinghours'] = 'Show only working hours';
// event dialog tabs
$labels['tabsummary'] = 'Summary';
diff --git a/plugins/calendar/skins/default/calendar.css b/plugins/calendar/skins/default/calendar.css
index 14fd8472..163bdd73 100644
--- a/plugins/calendar/skins/default/calendar.css
+++ b/plugins/calendar/skins/default/calendar.css
@@ -634,7 +634,7 @@ td.topalign {
width: 100%;
table-layout: fixed;
border-collapse: collapse;
- margin-bottom: 2em;
+ margin: 0.5em 0;
}
#attendees-freebusy-table td.attendees {
@@ -683,12 +683,27 @@ td.topalign {
}
#schedule-freebusy-times tr.dates th {
- border-bottom: 0;
+ border-color: #aaa;
+ border-style: solid;
+ border-width: 0 1px 0 1px;
}
#schedule-freebusy-times tr.times td {
+ min-width: 30px;
font-size: 9px;
padding: 5px 2px 6px 2px;
+ text-align: center;
+}
+
+#eventfreebusy .schedule-options {
+ position: relative;
+ margin-bottom: 2em;
+}
+
+#eventfreebusy .schedule-buttons {
+ position: absolute;
+ top: 0;
+ right: 0;
}
span.edit-alarm-set {
diff --git a/plugins/calendar/skins/default/templates/calendar.html b/plugins/calendar/skins/default/templates/calendar.html
index 173ba7c3..bf83f030 100644
--- a/plugins/calendar/skins/default/templates/calendar.html
+++ b/plugins/calendar/skins/default/templates/calendar.html
@@ -186,7 +186,15 @@