Refactored resources directory to be an individual part and not dependant on a specific calendar backend driver

This commit is contained in:
Thomas Bruederli 2014-03-10 14:45:24 +01:00
parent 0b2e726857
commit 51fe7c26fb
9 changed files with 267 additions and 194 deletions

View file

@ -38,6 +38,7 @@ class calendar extends rcube_plugin
public $rc; public $rc;
public $lib; public $lib;
public $driver; public $driver;
public $resources_dir;
public $home; // declare public to be used in other classes public $home; // declare public to be used in other classes
public $urlbase; public $urlbase;
public $timezone; public $timezone;
@ -211,16 +212,10 @@ class calendar extends rcube_plugin
require_once($this->home . '/drivers/calendar_driver.php'); require_once($this->home . '/drivers/calendar_driver.php');
require_once($this->home . '/drivers/' . $driver_name . '/' . $driver_class . '.php'); require_once($this->home . '/drivers/' . $driver_name . '/' . $driver_class . '.php');
switch ($driver_name) { $this->driver = new $driver_class($this);
case "kolab":
$this->require_plugin('libkolab');
default:
$this->driver = new $driver_class($this);
break;
}
if ($this->driver->undelete) if ($this->driver->undelete)
$this->driver->undelete = $this->rc->config->get('undo_timeout', 0) > 0; $this->driver->undelete = $this->rc->config->get('undo_timeout', 0) > 0;
} }
/** /**
@ -297,7 +292,7 @@ class calendar extends rcube_plugin
$this->rc->output->set_env('timezone', $this->timezone->getName()); $this->rc->output->set_env('timezone', $this->timezone->getName());
$this->rc->output->set_env('calendar_driver', $this->rc->config->get('calendar_driver'), false); $this->rc->output->set_env('calendar_driver', $this->rc->config->get('calendar_driver'), false);
$this->rc->output->set_env('resources', (bool)$this->driver->resources); $this->rc->output->set_env('calendar_resources', (bool)$this->rc->config->get('calendar_resources_driver'));
$this->rc->output->set_env('mscolors', $this->driver->get_color_values()); $this->rc->output->set_env('mscolors', $this->driver->get_color_values());
$this->rc->output->set_env('identities-selector', $this->ui->identity_select(array('id' => 'edit-identities-list'))); $this->rc->output->set_env('identities-selector', $this->ui->identity_select(array('id' => 'edit-identities-list')));
@ -1936,6 +1931,30 @@ class calendar extends rcube_plugin
/**** Resource management functions ****/ /**** Resource management functions ****/
/**
* Getter for the configured implementation of the resource directory interface
*/
private function resources_directory()
{
if (is_object($this->resources_dir)) {
return $this->resources_dir;
}
if ($driver_name = $this->rc->config->get('calendar_resources_driver')) {
$driver_class = 'resources_driver_' . $driver_name;
require_once($this->home . '/drivers/resources_driver.php');
require_once($this->home . '/drivers/' . $driver_name . '/' . $driver_class . '.php');
$this->resources_dir = new $driver_class($this);
}
return $this->resources_dir;
}
/**
* Handler for resoruce autocompletion requests
*/
public function resources_autocomplete() public function resources_autocomplete()
{ {
$search = rcube_utils::get_input_value('_search', rcube_utils::INPUT_GPC, true); $search = rcube_utils::get_input_value('_search', rcube_utils::INPUT_GPC, true);
@ -1943,12 +1962,14 @@ class calendar extends rcube_plugin
$maxnum = (int)$this->rc->config->get('autocomplete_max', 15); $maxnum = (int)$this->rc->config->get('autocomplete_max', 15);
$results = array(); $results = array();
foreach ($this->driver->load_resources($search, $maxnum) as $rec) { if ($directory = $this->resources_directory()) {
$results[] = array( foreach ($directory->load_resources($search, $maxnum) as $rec) {
'name' => $rec['name'], $results[] = array(
'email' => $rec['email'], 'name' => $rec['name'],
'type' => $rec['_type'], 'email' => $rec['email'],
); 'type' => $rec['_type'],
);
}
} }
$this->rc->output->command('ksearch_query_results', $results, $search, $sid); $this->rc->output->command('ksearch_query_results', $results, $search, $sid);
@ -1961,9 +1982,11 @@ class calendar extends rcube_plugin
function resources_list() function resources_list()
{ {
$data = array(); $data = array();
foreach ($this->driver->load_resources() as $rec) {
$rec['dn'] = rcube_ldap::dn_decode($rec['ID']); if ($directory = $this->resources_directory()) {
$data[] = $rec; foreach ($directory->load_resources() as $rec) {
$data[] = $rec;
}
} }
$this->rc->output->command('plugin.resource_data', $data); $this->rc->output->command('plugin.resource_data', $data);
@ -1975,8 +1998,10 @@ class calendar extends rcube_plugin
*/ */
function resources_owner() function resources_owner()
{ {
$id = rcube_utils::get_input_value('_id', rcube_utils::INPUT_GPC); if ($directory = $this->resources_directory()) {
$data = $this->driver->get_resource_owner($id); $id = rcube_utils::get_input_value('_id', rcube_utils::INPUT_GPC);
$data = $directory->get_resource_owner($id);
}
$this->rc->output->command('plugin.resource_owner', $data); $this->rc->output->command('plugin.resource_owner', $data);
$this->rc->output->send(); $this->rc->output->send();

View file

@ -762,7 +762,7 @@ function rcube_calendar_ui(settings)
// show/hide tabs according to calendar's feature support // show/hide tabs according to calendar's feature support
$('#edit-tab-attendees')[(calendar.attendees?'show':'hide')](); $('#edit-tab-attendees')[(calendar.attendees?'show':'hide')]();
$('#edit-tab-resources')[(calendar.resources?'show':'hide')](); $('#edit-tab-resources')[(rcmail.env.calendar_resources?'show':'hide')]();
$('#edit-tab-attachments')[(calendar.attachments?'show':'hide')](); $('#edit-tab-attachments')[(calendar.attachments?'show':'hide')]();
// activate the first tab // activate the first tab
@ -1516,7 +1516,7 @@ function rcube_calendar_ui(settings)
'<td class="confirmstate"><span class="' + String(data.status).toLowerCase() + '" title="' + Q(data.status || '') + '">' + Q(data.status || '') + '</span></td>' + '<td class="confirmstate"><span class="' + String(data.status).toLowerCase() + '" title="' + Q(data.status || '') + '">' + Q(data.status || '') + '</span></td>' +
'<td class="options">' + (organizer || readonly ? '' : dellink) + '</td>'; '<td class="options">' + (organizer || readonly ? '' : dellink) + '</td>';
var table = calendar.resources && data.cutype == 'RESOURCE' ? resources_list : attendees_list; var table = rcmail.env.calendar_resources && data.cutype == 'RESOURCE' ? resources_list : attendees_list;
var tr = $('<tr>') var tr = $('<tr>')
.addClass(String(data.role).toLowerCase()) .addClass(String(data.role).toLowerCase())
.html(html) .html(html)
@ -1706,12 +1706,12 @@ function rcube_calendar_ui(settings)
// assign parent-relations // assign parent-relations
$.each(data, function(i, rec) { $.each(data, function(i, rec) {
resources_data[rec.dn] = rec; resources_data[rec.ID] = rec;
resources_index.push(rec.dn); resources_index.push(rec.ID);
if (rec.members) { if (rec.members) {
$.each(rec.members, function(j, m){ $.each(rec.members, function(j, m){
resources_data[m].parent_id = rec.dn; resources_data[m].parent_id = rec.ID;
}); });
} }
}); });
@ -1730,10 +1730,10 @@ function rcube_calendar_ui(settings)
$.each(index, function(i, dn) { $.each(index, function(i, dn) {
if (rec = resources_data[dn]) { if (rec = resources_data[dn]) {
link = $('<a>').attr('href', '#') link = $('<a>').attr('href', '#')
.attr('rel', rec.dn) .attr('rel', rec.ID)
.html(Q(rec.name)); .html(Q(rec.name));
resources_treelist.insert({ id:rec.dn, html:link, classes:[rec._type], collapsed:true }, rec.parent_id, false); resources_treelist.insert({ id:rec.ID, html:link, classes:[rec._type], collapsed:true }, rec.parent_id, false);
} }
}); });
}; };
@ -1772,7 +1772,7 @@ function rcube_calendar_ui(settings)
for (var dn in resources_data) { for (var dn in resources_data) {
rec = resources_data[dn]; rec = resources_data[dn];
if (String(rec.name).toLowerCase().indexOf(q) >= 0) { if (String(rec.name).toLowerCase().indexOf(q) >= 0) {
dataset.push(rec.dn); dataset.push(rec.ID);
} }
} }

View file

@ -135,4 +135,11 @@ $rcmail_config['calendar_itip_smtp_pass'] = '123456';
// %i - Calendar UUID // %i - Calendar UUID
// $rcmail_config['calendar_caldav_url'] = 'http://%h/iRony/calendars/%u/%i'; // $rcmail_config['calendar_caldav_url'] = 'http://%h/iRony/calendars/%u/%i';
// Driver to provide a resource directory ('ldap' is the only implementation yet).
// Leave empty or commented to disable resources support.
// $rcmail_config['calendar_resources_driver'] = 'ldap';
// LDAP directory configuration to find avilable resources for events
// $rcmail_config['calendar_resources_directory'] = array(/* ldap_public-like address book configuration */)
?> ?>

View file

@ -86,7 +86,6 @@ abstract class calendar_driver
// features supported by backend // features supported by backend
public $alarms = false; public $alarms = false;
public $attendees = false; public $attendees = false;
public $resources = false;
public $freebusy = false; public $freebusy = false;
public $attachments = false; public $attachments = false;
public $undelete = false; // event undelete action public $undelete = false; // event undelete action
@ -530,54 +529,4 @@ abstract class calendar_driver
return $events; return $events;
} }
/**
* Store alarm dismissal for birtual birthay events
*
* @param string Event identifier
* @param integer Suspend the alarm for this number of seconds
*/
public function dismiss_birthday_alarm($event_id, $snooze = 0)
{
$rcmail = rcmail::get_instance();
$cache = $rcmail->get_cache('calendar.birthdayalarms', 'db', 86400 * 30);
$cache->remove($event_id);
// compute new notification time or disable if not snoozed
$notifyat = $snooze > 0 ? time() + $snooze : null;
$cache->set($event_id, array('snooze' => $snooze, 'notifyat' => $notifyat));
return true;
}
/**
* Fetch resource objects to be displayed for booking
*
* @param string Search query (optional)
* @return array List of resource records available for booking
*/
public function load_resources($query = null)
{
return array();
}
/**
* Return properties of a single resource
*
* @param mixed UID string
* @return array Resource object as hash array
*/
public function get_resource($uid)
{
return null;
}
/**
*
*/
public function get_resource_owner($id)
{
return null;
}
} }

View file

@ -47,6 +47,8 @@ class kolab_driver extends calendar_driver
*/ */
public function __construct($cal) public function __construct($cal)
{ {
$cal->require_plugin('libkolab');
$this->cal = $cal; $this->cal = $cal;
$this->rc = $cal->rc; $this->rc = $cal->rc;
$this->_read_calendars(); $this->_read_calendars();
@ -60,10 +62,6 @@ class kolab_driver extends calendar_driver
$this->alarm_types = array('DISPLAY'); $this->alarm_types = array('DISPLAY');
$this->alarm_absolute = false; $this->alarm_absolute = false;
} }
if ($this->rc->config->get('calendar_resources_directory')) {
$this->resources = true;
}
} }
@ -1287,105 +1285,4 @@ class kolab_driver extends calendar_driver
'FFDEAD'); 'FFDEAD');
} }
private function resurces_ldap()
{
if (!isset($this->resources_dir)) {
$this->resources_dir = new rcube_ldap($this->rc->config->get('calendar_resources_directory'), true);
}
return $this->resources_dir->ready ? $this->resources_dir : null;
}
/**
* Fetch resource objects to be displayed for booking
*
* @param string Search query (optional)
* @return array List of resource records available for booking
*/
public function load_resources($query = null, $num = 5000)
{
if (!($ldap = $this->resurces_ldap())) {
return array();
}
// TODO: apply paging
$ldap->set_pagesize($num);
if (isset($query)) {
$results = $ldap->search('*', $query, 0, true, true);
}
else {
$results = $ldap->list_records();
}
if ($results instanceof ArrayAccess) {
foreach ($results as $i => $rec) {
$results[$i] = $this->decode_resource($rec);
}
}
return $results;
}
/**
* Return properties of a single resource
*
* @param mixed UID string
* @return array Resource object as hash array
*/
public function get_resource($uid)
{
$rec = null;
if ($ldap = $this->resurces_ldap()) {
$rec = $ldap->get_record($uid);
if (!empty($rec)) {
$rec = $this->decode_resource($rec);
}
}
return $rec;
}
/**
*
*/
public function get_resource_owner($dn)
{
$owner = null;
if ($ldap = $this->resurces_ldap()) {
$owner = $ldap->get_record(rcube_ldap::dn_encode($dn), true);
$owner['ID'] = rcube_ldap::dn_decode($owner['ID']);
unset($owner['_raw_attrib'], $owner['_type']);
}
return $owner;
}
/**
* Extract JSON-serialized attributes
*/
private function decode_resource($rec)
{
if (is_array($rec['attributes']) && $rec['attributes'][0]) {
$attributes = array();
foreach ($rec['attributes'] as $sattr) {
$attr = @json_decode($sattr, true);
$attributes += $attr;
}
$rec['attributes'] = $attributes;
}
// remove unused cruft
unset($rec['_raw_attrib']);
return $rec;
}
} }

View file

@ -0,0 +1,146 @@
<?php
/**
* LDAP-based resource directory class using rcube_ldap functionality
*
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2014, Kolab Systems AG <contact@kolabsys.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* LDAP-based resource directory implementation
*/
class resources_driver_ldap extends resources_driver
{
private $rc;
private $cal;
private $ldap;
/**
* Default constructor
*/
function __construct($cal)
{
$this->cal = $cal;
$this->rc = $cal->rc;
}
/**
* Fetch resource objects to be displayed for booking
*
* @param string Search query (optional)
* @return array List of resource records available for booking
*/
public function load_resources($query = null, $num = 5000)
{
if (!($ldap = $this->connect())) {
return array();
}
// TODO: apply paging
$ldap->set_pagesize($num);
if (isset($query)) {
$results = $ldap->search('*', $query, 0, true, true);
}
else {
$results = $ldap->list_records();
}
if ($results instanceof ArrayAccess) {
foreach ($results as $i => $rec) {
$results[$i] = $this->decode_resource($rec);
}
}
return $results;
}
/**
* Return properties of a single resource
*
* @param string Unique resource identifier
* @return array Resource object as hash array
*/
public function get_resource($dn)
{
$rec = null;
if ($ldap = $this->connect()) {
$rec = $ldap->get_record(rcube_ldap::dn_encode($dn));
if (!empty($rec)) {
$rec = $this->decode_resource($rec);
}
}
return $rec;
}
/**
* Return properties of a resource owner
*
* @param string Owner identifier
* @return array Resource object as hash array
*/
public function get_resource_owner($dn)
{
$owner = null;
if ($ldap = $this->connect()) {
$owner = $ldap->get_record(rcube_ldap::dn_encode($dn), true);
$owner['ID'] = rcube_ldap::dn_decode($owner['ID']);
unset($owner['_raw_attrib'], $owner['_type']);
}
return $owner;
}
/**
* Extract JSON-serialized attributes
*/
private function decode_resource($rec)
{
$rec['ID'] = rcube_ldap::dn_decode($rec['ID']);
if (is_array($rec['attributes']) && $rec['attributes'][0]) {
$attributes = array();
foreach ($rec['attributes'] as $sattr) {
$attr = @json_decode($sattr, true);
$attributes += $attr;
}
$rec['attributes'] = $attributes;
}
// remove unused cruft
unset($rec['_raw_attrib']);
return $rec;
}
private function connect()
{
if (!isset($this->ldap)) {
$this->ldap = new rcube_ldap($this->rc->config->get('calendar_resources_directory'), true);
}
return $this->ldap->ready ? $this->ldap : null;
}
}

View file

@ -0,0 +1,58 @@
<?php
/**
* Resources directory interface definition
*
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2014, Kolab Systems AG <contact@kolabsys.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Interface definition for a resources directory driver classe
*/
abstract class resources_driver
{
/**
* Fetch resource objects to be displayed for booking
*
* @param string Search query (optional)
* @return array List of resource records available for booking
*/
abstract public function load_resources($query = null);
/**
* Return properties of a single resource
*
* @param string Unique resource identifier
* @return array Resource object as hash array
*/
abstract public function get_resource($id);
/**
* Return properties of a resource owner
*
* @param string Owner identifier
* @return array Resource object as hash array
*/
public function get_resource_owner($id)
{
return null;
}
}

View file

@ -196,7 +196,6 @@ class calendar_ui
unset($prop['user_id']); unset($prop['user_id']);
$prop['alarms'] = $this->cal->driver->alarms; $prop['alarms'] = $this->cal->driver->alarms;
$prop['attendees'] = $this->cal->driver->attendees; $prop['attendees'] = $this->cal->driver->attendees;
$prop['resources'] = $this->cal->driver->resources;
$prop['freebusy'] = $this->cal->driver->freebusy; $prop['freebusy'] = $this->cal->driver->freebusy;
$prop['attachments'] = $this->cal->driver->attachments; $prop['attachments'] = $this->cal->driver->attachments;
$prop['undelete'] = $this->cal->driver->undelete; $prop['undelete'] = $this->cal->driver->undelete;

View file

@ -126,14 +126,6 @@
<div class="scroller"> <div class="scroller">
<roundcube:object name="plugin.resources_list" id="resources-list" class="listing treelist" /> <roundcube:object name="plugin.resources_list" id="resources-list" class="listing treelist" />
</div> </div>
<!--
<div class="boxpagenav">
<roundcube:button command="firstpage" type="link" class="icon firstpage disabled" classAct="icon firstpage" title="firstpage" content="|&amp;lt;" />
<roundcube:button command="previouspage" type="link" class="icon prevpage disabled" classAct="icon prevpage" title="previouspage" content="&amp;lt;" />
<roundcube:button command="nextpage" type="link" class="icon nextpage disabled" classAct="icon nextpage" title="nextpage" content="&amp;gt;" />
<roundcube:button command="lastpage" type="link" class="icon lastpage disabled" classAct="icon lastpage" title="lastpage" content="&amp;gt;|" />
</div>
-->
</div> </div>
</div> </div>