Display resource's availability in a small calendar widget. Data is derived from the resource free/busy data

This commit is contained in:
Thomas Bruederli 2014-03-17 17:29:12 +01:00
parent 17013f732f
commit 0946cc37a4
9 changed files with 221 additions and 9 deletions

View file

@ -144,6 +144,7 @@ class calendar extends rcube_plugin
$this->register_action('check-recent', array($this, 'check_recent'));
$this->register_action('resources-list', array($this, 'resources_list'));
$this->register_action('resources-owner', array($this, 'resources_owner'));
$this->register_action('resources-calendar', array($this, 'resources_calendar'));
$this->register_action('resources-autocomplete', array($this, 'resources_autocomplete'));
$this->add_hook('refresh', array($this, 'refresh'));
@ -2007,6 +2008,24 @@ class calendar extends rcube_plugin
$this->rc->output->send();
}
/**
* Deliver event data for a resource's calendar
*/
function resources_calendar()
{
$events = array();
if ($directory = $this->resources_directory()) {
$events = $directory->get_resource_calendar(
rcube_utils::get_input_value('_id', rcube_utils::INPUT_GPC),
rcube_utils::get_input_value('start', RCUBE_INPUT_GET),
rcube_utils::get_input_value('end', RCUBE_INPUT_GET));
}
echo $this->encode($events);
exit;
}
/**** Event invitation plugin hooks ****/

View file

@ -52,6 +52,7 @@ function rcube_calendar_ui(settings)
var resources_data = {};
var resources_index = [];
var resource_owners = {};
var resources_events_source = { url:null, editable:false };
var freebusy_ui = { workinhoursonly:false, needsupdate:false };
var freebusy_data = {};
var current_view = null;
@ -1624,6 +1625,10 @@ function rcube_calendar_ui(settings)
close: function() {
$dialog.dialog('destroy').hide();
},
resize: function(e) {
var container = $(rcmail.gui_objects.resourceinfocalendar)
container.fullCalendar('option', 'height', container.height() + 4);
},
buttons: buttons,
width: 900,
height: 500
@ -1654,6 +1659,7 @@ function rcube_calendar_ui(settings)
rcmail.enable_command('add-resource', false);
$(rcmail.gui_objects.resourceinfo).hide();
$(rcmail.gui_objects.resourceownerinfo).hide();
$(rcmail.gui_objects.resourceinfocalendar).fullCalendar('removeEventSource', resources_events_source);
}
});
@ -1663,6 +1669,43 @@ function rcube_calendar_ui(settings)
// register button
rcmail.register_button('add-resource', 'rcmbtncalresadd', 'uibutton');
// initialize resource calendar display
var resource_cal = $(rcmail.gui_objects.resourceinfocalendar);
resource_cal.fullCalendar({
header: { left: '', center: '', right: '' },
height: resource_cal.height() + 4,
defaultView: 'agendaWeek',
ignoreTimezone: true,
eventSources: [],
monthNames: settings['months'],
monthNamesShort: settings['months_short'],
dayNames: settings['days'],
dayNamesShort : settings['days_short'],
firstDay: settings['first_day'],
firstHour: settings['first_hour'],
slotMinutes: 60,
allDaySlot: false,
timeFormat: { '': settings['time_format'] },
axisFormat: settings['time_format'],
columnFormat: { day: 'dddd ' + settings['date_short'] },
titleFormat: { day: 'dddd ' + settings['date_long'] },
currentTimeIndicator: settings.time_indicator,
eventRender: function(event, element, view) {
element.addClass('status-' + event.status);
element.find('.fc-event-head').hide();
element.find('.fc-event-title').text(rcmail.get_label(event.status, 'calendar'));
}
});
$('#resource-calendar-prev').click(function(){
resource_cal.fullCalendar('prev');
return false;
});
$('#resource-calendar-next').click(function(){
resource_cal.fullCalendar('next');
return false;
});
}
else if (search) {
resource_search();
@ -1670,6 +1713,10 @@ function rcube_calendar_ui(settings)
else {
resource_render_list(resources_index);
}
if (me.selected_event && me.selected_event.start) {
$(rcmail.gui_objects.resourceinfocalendar).fullCalendar('gotoDate', me.selected_event.start);
}
};
// render the resource details UI box
@ -1699,6 +1746,7 @@ function rcube_calendar_ui(settings)
}
$(rcmail.gui_objects.resourceownerinfo).hide();
$(rcmail.gui_objects.resourceinfocalendar).fullCalendar('removeEventSource', resources_events_source);
if (resource.owner) {
// display cached data
@ -1711,6 +1759,10 @@ function rcube_calendar_ui(settings)
rcmail.http_request('resources-owner', { _id: resource.owner }, me.loading_lock);
}
}
// load resource calendar
resources_events_source.url = "./?_task=calendar&_action=resources-calendar&_id="+escape(resource.ID);
$(rcmail.gui_objects.resourceinfocalendar).fullCalendar('addEventSource', resources_events_source);
}
};

View file

@ -27,7 +27,6 @@
class resources_driver_ldap extends resources_driver
{
private $rc;
private $cal;
private $ldap;
/**
@ -81,7 +80,7 @@ class resources_driver_ldap extends resources_driver
$rec = null;
if ($ldap = $this->connect()) {
$rec = $ldap->get_record(rcube_ldap::dn_encode($dn));
$rec = $ldap->get_record(rcube_ldap::dn_encode($dn), true);
if (!empty($rec)) {
$rec = $this->decode_resource($rec);

View file

@ -27,6 +27,15 @@
*/
abstract class resources_driver
{
protected$cal;
/**
* Default constructor
*/
function __construct($cal)
{
$this->cal = $cal;
}
/**
* Fetch resource objects to be displayed for booking
@ -52,7 +61,54 @@ abstract class resources_driver
*/
public function get_resource_owner($id)
{
return null;
return null;
}
/**
* Get event data to display a resource's calendar
*
* The default implementation extracts the resource's email address
* and fetches free-busy data using the calendar backend driver.
*
* @param integer Event's new start (unix timestamp)
* @param integer Event's new end (unix timestamp)
* @return array A list of event objects (see calendar_driver specification)
*/
public function get_resource_calendar($id, $start, $end)
{
$events = array();
$rec = $this->get_resource($id);
if ($rec && !empty($rec['email']) && $this->cal->driver) {
$fbtypemap = array(
calendar::FREEBUSY_BUSY => 'busy',
calendar::FREEBUSY_TENTATIVE => 'tentative',
calendar::FREEBUSY_OOF => 'outofoffice',
);
// if the backend has free-busy information
$fblist = $this->cal->driver->get_freebusy_list($rec['email'], $start, $end);
if (is_array($fblist)) {
foreach ($fblist as $slot) {
list($from, $to, $type) = $slot;
if ($type == calendar::FREEBUSY_FREE || $type == calendar::FREEBUSY_UNKNOWN) {
continue;
}
if ($from < $end && $to > $start) {
$event = array(
'id' => sha1($id . $from . $to),
'title' => $rec['name'],
'start' => new DateTime('@' . $from),
'end' => new DateTime('@' . $to),
'status' => $fbtypemap[$type],
'calendar' => '_resource',
);
$events[] = $event;
}
}
}
}
return $events;
}
}

View file

@ -88,6 +88,7 @@ class calendar_ui
$this->cal->register_handler('plugin.resources_list', array($this, 'resources_list'));
$this->cal->register_handler('plugin.resources_searchform', array($this, 'resources_search_form'));
$this->cal->register_handler('plugin.resource_info', array($this, 'resource_info'));
$this->cal->register_handler('plugin.resource_calendar', array($this, 'resource_calendar'));
$this->cal->register_handler('plugin.attendees_freebusy_table', array($this, 'attendees_freebusy_table'));
$this->cal->register_handler('plugin.edit_attendees_notify', array($this, 'edit_attendees_notify'));
$this->cal->register_handler('plugin.edit_recurring_warning', array($this, 'recurring_event_warning'));
@ -815,6 +816,18 @@ class calendar_ui
$table_attrib);
}
/**
*
*/
public function resource_calendar($attrib = array())
{
$attrib += array('id' => 'calendar-resources-calendar');
$this->rc->output->add_gui_object('resourceinfocalendar', $attrib['id']);
return html::div($attrib, '');
}
/**
* GUI object 'searchform' for the resource finder dialog
*

View file

@ -1086,7 +1086,7 @@ span.spacer {
top: 0;
left: 0;
right: 0;
height: 56%;
height: 48%;
border: 1px solid #999;
background-color: #F9F9F9;
overflow: auto;
@ -1095,7 +1095,8 @@ span.spacer {
#resource-availability {
top: auto;
bottom: 0;
height: 41%;
height: 49%;
overflow: hidden;
}
#resource-info .boxtitle,
@ -1103,6 +1104,40 @@ span.spacer {
margin-top: 0;
}
#resource-freebusy-calendar {
position: absolute;
top: 20px;
left: -1px;
right: -1px;
bottom: -1px;
}
#resource-freebusy-calendar .fc-content {
top: 0;
}
#resource-freebusy-calendar .fc-content .fc-event-bg {
background: 0;
}
#resource-freebusy-calendar .fc-event.status-busy,
#resource-freebusy-calendar .status-busy .fc-event-skin {
border-color: #e26569;
background-color: #e26569;
}
#resource-freebusy-calendar .fc-event.status-tentative,
#resource-freebusy-calendar .status-tentative .fc-event-skin {
border-color: #8383fc;
background: #8383fc;
}
#resource-freebusy-calendar .fc-event.status-outofoffice,
#resource-freebusy-calendar .status-outofoffice .fc-event-skin {
border-color: #fbaa68;
background: #fbaa68;
}
#resources-list div.treetoggle {
left: 3px !important;
top: -2px;

View file

@ -125,7 +125,7 @@
<div id="resource-availability">
<h2 class="boxtitle"><roundcube:label name="calendar.resourceavailability" /></h2>
<div id="resource-freebusy-calendar"></div>
<roundcube:object name="plugin.resource_calendar" id="resource-freebusy-calendar" />
</div>
</div>
</div>

View file

@ -1081,7 +1081,7 @@ a.dropdown-link:after {
top: 0;
left: 0;
right: 0;
height: 56%;
height: 48%;
}
#resource-info table {
@ -1100,7 +1100,41 @@ a.dropdown-link:after {
bottom: 0;
left: 0;
right: 0;
height: 40%;
height: 49%;
}
#resource-freebusy-calendar {
position: absolute;
top: 33px;
left: -1px;
right: -1px;
bottom: -1px;
}
#resource-freebusy-calendar .fc-content {
top: 0;
}
#resource-freebusy-calendar .fc-content .fc-event-bg {
background: 0;
}
#resource-freebusy-calendar .fc-event.status-busy,
#resource-freebusy-calendar .status-busy .fc-event-skin {
border-color: #e26569;
background-color: #e26569;
}
#resource-freebusy-calendar .fc-event.status-tentative,
#resource-freebusy-calendar .status-tentative .fc-event-skin {
border-color: #8383fc;
background: #8383fc;
}
#resource-freebusy-calendar .fc-event.status-outofoffice,
#resource-freebusy-calendar .status-outofoffice .fc-event-skin {
border-color: #fbaa68;
background: #fbaa68;
}
#resourcequicksearch {

View file

@ -139,7 +139,11 @@
<div id="resource-availability" class="uibox contentbox">
<h2 class="boxtitle"><roundcube:label name="calendar.resourceavailability" /></h2>
<div id="resource-freebusy-calendar"></div>
<roundcube:object name="plugin.resource_calendar" id="resource-freebusy-calendar" />
<div class="boxpagenav">
<roundcube:button name="resource-cal-prev" id="resource-calendar-prev" type="link" class="icon prevpage" title="calendar.prevslot" content="&amp;lt;" />
<roundcube:button name="resource-cal-next" id="resource-calendar-next" type="link" class="icon nextpage" title="calendar.nextslot" content="&amp;gt;" />
</div>
</div>
</div>
</div>