diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php
index 4d506ffc..ae7b2048 100644
--- a/plugins/calendar/calendar.php
+++ b/plugins/calendar/calendar.php
@@ -1395,8 +1395,8 @@ class calendar extends rcube_plugin
}
}
- // let this information be cached for 15min
- send_future_expire_header(900);
+ // let this information be cached for 5min
+ send_future_expire_header(300);
echo $status;
exit;
@@ -1441,6 +1441,9 @@ class calendar extends rcube_plugin
$t = $t_end;
}
+ // let this information be cached for 5min
+ send_future_expire_header(300);
+
echo json_encode(array('email' => $email, 'start' => intval($start), 'end' => intval($t_end), 'interval' => $interval, 'slots' => $slots));
exit;
}
diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js
index 0b2b7049..2baeb49d 100644
--- a/plugins/calendar/calendar_ui.js
+++ b/plugins/calendar/calendar_ui.js
@@ -673,7 +673,7 @@ function rcube_calendar_ui(settings)
fb_start.setHours(0); fb_start.setMinutes(0); fb_start.setSeconds(0); fb_start.setMilliseconds(0);
fb_end.setTime(fb_start.getTime() + DAY_MS);
- freebusy_data = {};
+ freebusy_data = { required:{}, all:{} };
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;
@@ -690,6 +690,10 @@ function rcube_calendar_ui(settings)
list_html += '
' + dispname + '
';
}
+ // add total row
+ list_html += '
';
+ list_html += '' + rcmail.gettext('reqallattendees','calendar') + '
';
+
$('#schedule-attendees-list').html(list_html);
// enable/disable buttons
@@ -781,6 +785,10 @@ function rcube_calendar_ui(settings)
times_html += '' + slots_row + '
';
}
+ // add line for all/required attendees
+ times_html += ' | ';
+ times_html += '
' + slots_row + '
';
+
var table = $('#schedule-freebusy-times');
table.children('thead').html(dates_row + times_row);
table.children('tbody').html(times_html);
@@ -905,9 +913,10 @@ function rcube_calendar_ui(settings)
{
var start = new Date(from.getTime() - DAY_MS * 2); // start 1 days before event
var end = new Date(start.getTime() + DAY_MS * 14); // load 14 days
+ freebusy_ui.numrequired = 0;
// load free-busy information for every attendee
- var domid, email
+ var domid, email;
for (var i=0; i < event_attendees.length; i++) {
if ((email = event_attendees[i].email)) {
domid = String(email).replace(rcmail.identifier_expr, '');
@@ -918,16 +927,37 @@ function rcube_calendar_ui(settings)
type: 'GET',
dataType: 'json',
url: rcmail.url('freebusy-times'),
- data: { email:email, start:date2unixtime(start), end:date2unixtime(end), interval:interval, _remote: 1 },
- success: function(data){
+ data: { email:email, start:date2unixtime(start), end:date2unixtime(end), interval:interval, _remote:1 },
+ success: function(data) {
freebusy_ui.loading--;
+ // find attendee
+ var attendee = null;
+ for (var i=0; i < event_attendees.length; i++) {
+ if (event_attendees[i].email == data.email) {
+ attendee = event_attendees[i];
+ break;
+ }
+ }
+
// copy data to member var
+ var req = attendee.role != 'OPT-PARTICIPANT';
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];
+
+ // set totals
+ if (!freebusy_data.required[ts])
+ freebusy_data.required[ts] = [0,0,0,0];
+ if (req)
+ freebusy_data.required[ts][data.slots[i]]++;
+
+ if (!freebusy_data.all[ts])
+ freebusy_data.all[ts] = [0,0,0,0];
+ freebusy_data.all[ts][data.slots[i]]++;
+
ts += data.interval * 60;
}
freebusy_data.end = ts;
@@ -941,6 +971,10 @@ function rcube_calendar_ui(settings)
update_freebusy_display(data.email);
}
});
+
+ // count required attendees
+ if (event_attendees[i].role != 'OPT-PARTICIPANT')
+ freebusy_ui.numrequired++;
}
}
};
@@ -951,12 +985,28 @@ function rcube_calendar_ui(settings)
var status_classes = ['unknown','free','busy','tentative','out-of-office'];
var domid = String(email).replace(rcmail.identifier_expr, '');
var row = $('#fbrow' + domid);
+ var rowall = $('#fbrowall').children();
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');
+
+ // also update total row if all data was loaded
+ if (freebusy_ui.loading == 0 && freebusy_data.all[ts] && (cell = rowall.get(i))) {
+ var all_status = freebusy_data.all[ts][2] ? 'busy' : 'unknown';
+ req_status = freebusy_data.required[ts][2] ? 'busy' : 'free';
+ for (var j=0; j < status_classes.length; j++) {
+ if (freebusy_ui.numrequired && freebusy_data.required[ts][j] >= freebusy_ui.numrequired)
+ req_status = status_classes[j];
+ if (freebusy_data.all[ts][j] == event_attendees.length)
+ all_status = status_classes[j];
+ }
+
+ cell.className = cell.className.replace('unknown', req_status + ' all-' + all_status);
+ }
+
ts += freebusy_ui.interval * 60;
});
}
diff --git a/plugins/calendar/drivers/kolab/kolab_driver.php b/plugins/calendar/drivers/kolab/kolab_driver.php
index 62a55e03..a5002bab 100644
--- a/plugins/calendar/drivers/kolab/kolab_driver.php
+++ b/plugins/calendar/drivers/kolab/kolab_driver.php
@@ -806,6 +806,10 @@ class kolab_driver extends calendar_driver
if (($fbstart = $fb->getStart()) && $start < $fbstart) {
array_unshift($result, array($start, $fbstart, calendar::FREEBUSY_UNKNOWN));
}
+ // pad period till $end with status 'unknown'
+ if (($fbend = $fb->getEnd()) && $fbend < $end) {
+ $result[] = array($fbend, $end, calendar::FREEBUSY_UNKNOWN);
+ }
return $result;
}
}
diff --git a/plugins/calendar/localization/en_US.inc b/plugins/calendar/localization/en_US.inc
index 7599c010..d24252a7 100644
--- a/plugins/calendar/localization/en_US.inc
+++ b/plugins/calendar/localization/en_US.inc
@@ -106,6 +106,7 @@ $labels['scheduletime'] = 'Find availability';
$labels['sendinvitations'] = 'Send invitations';
$labels['sendnotifications'] = 'Notify attendees about modifications';
$labels['onlyworkinghours'] = 'Find availability within my working hours';
+$labels['reqallattendees'] = 'Required/all participants';
$labels['prevslot'] = 'Previous Slot';
$labels['nextslot'] = 'Next Slot';
$labels['noslotfound'] = 'Unable to find a free time slot';
diff --git a/plugins/calendar/skins/default/calendar.css b/plugins/calendar/skins/default/calendar.css
index 6c2dae6f..95a8f322 100644
--- a/plugins/calendar/skins/default/calendar.css
+++ b/plugins/calendar/skins/default/calendar.css
@@ -602,6 +602,22 @@ td.topalign {
background: #f0b400;
}
+#schedule-freebusy-times td.all-busy,
+#schedule-freebusy-times td.all-tentative,
+#schedule-freebusy-times td.all-out-of-office {
+ background-image: url('images/freebusy-colors.png');
+ background-position: top right;
+ background-repeat: no-repeat;
+}
+
+#schedule-freebusy-times td.all-tentative {
+ background-position: right -40px;
+}
+
+#schedule-freebusy-times td.all-out-of-office {
+ background-position: right -80px;
+}
+
#edit-attendees-legend {
margin-top: 3em;
margin-bottom: 0.5em;
@@ -704,6 +720,18 @@ td.topalign {
background: url('images/loading-small.gif') 1px 50% no-repeat;
}
+.attendees-list .total {
+ background: none;
+ padding-left: 4px;
+ font-weight: bold;
+}
+
+.attendees-list .spacer,
+#schedule-freebusy-times tr.spacer td {
+ background: 0;
+ font-size: 50%;
+}
+
#schedule-freebusy-times {
border-collapse: collapse;
}
diff --git a/plugins/calendar/skins/default/iehacks.css b/plugins/calendar/skins/default/iehacks.css
index 31f82d78..59469034 100644
--- a/plugins/calendar/skins/default/iehacks.css
+++ b/plugins/calendar/skins/default/iehacks.css
@@ -51,4 +51,10 @@ html #calendartoolbar a.buttonPas {
#eventfreebusy .schedule-buttons,
#edit-attendees-form #edit-attendee-schedule {
right: 0.6em;
-}
\ No newline at end of file
+}
+
+#schedule-freebusy-times td.all-busy,
+#schedule-freebusy-times td.all-tentative,
+#schedule-freebusy-times td.all-out-of-office {
+ background-image: url('images/freebusy-colors.gif');
+}
diff --git a/plugins/calendar/skins/default/images/freebusy-colors.gif b/plugins/calendar/skins/default/images/freebusy-colors.gif
new file mode 100644
index 00000000..699f4b9c
Binary files /dev/null and b/plugins/calendar/skins/default/images/freebusy-colors.gif differ
diff --git a/plugins/calendar/skins/default/images/freebusy-colors.png b/plugins/calendar/skins/default/images/freebusy-colors.png
new file mode 100644
index 00000000..ca37cd08
Binary files /dev/null and b/plugins/calendar/skins/default/images/freebusy-colors.png differ