* CalDAV/Calendar/Resources: calendars of resources can now be accessed or subscribed via CalDAV, to subscribe use CalDAV preferences

This commit is contained in:
Ralf Becker 2012-09-27 15:46:08 +00:00
parent 9c8f7fe1ea
commit db4bdb7f74
8 changed files with 231 additions and 51 deletions

View File

@ -967,7 +967,7 @@ class addressbook_groupdav extends groupdav_handler
(in_array('A',$this->home_set_pref) || in_array((string)$id,$this->home_set_pref)) &&
is_numeric($id) && ($owner = $id ? $this->accounts->id2name($id) : 'accounts'))
{
$shared[$id] = $owner;
$shared[$id] = 'addressbook-'.$owner;
}
}
return $shared;

View File

@ -1970,7 +1970,7 @@ class calendar_bo
/**
* Query ctag for calendar
*
* @param int|array $user integer user-id or array of user-id's to use, defaults to the current user
* @param int|string|array $user integer user-id or array of user-id's to use, defaults to the current user
* @param string $filter='owner' all (not rejected), accepted, unknown, tentative, rejected or hideprivate
* @param boolean $master_only=false only check recurance master (egw_cal_user.recur_date=0)
* @return integer

View File

@ -1366,7 +1366,22 @@ class calendar_groupdav extends groupdav_handler
(in_array('A',$calendar_home_set) || in_array((string)$id,$calendar_home_set)) &&
is_numeric($id) && ($owner = $this->accounts->id2name($id)))
{
$shared[$id] = $owner;
$shared[$id] = 'calendar-'.$owner;
}
}
// shared locations and resources
if ($GLOBALS['egw_info']['user']['apps']['resources'])
{
foreach(array('locations','resources') as $res)
{
if (($pref = $GLOBALS['egw_info']['user']['preferences']['groupdav']['calendar-home-set-'.$res]))
{
foreach(explode(',', $pref) as $res_id)
{
$is_location = $res == 'locations';
$shared['r'.$res_id] = str_replace('s/', '-', groupdav_principals::resource2name($res_id, $is_location));
}
}
}
}
return $shared;
@ -1406,6 +1421,43 @@ class calendar_groupdav extends groupdav_handler
'xmlrpc' => True,
'admin' => False,
);
// allow to subscribe to resources
if ($GLOBALS['egw_info']['user']['apps']['resources'] && ($all_resources = groupdav_principals::get_resources()))
{
$resources = $locations = array();
foreach($all_resources as $resource)
{
if (groupdav_principals::resource_is_location($resource))
{
$locations[$resource['res_id']] = $resource['name'];
}
else
{
$resources[$resource['res_id']] = $resource['name'];
}
}
foreach(array(
'locations' => $locations,
'resources' => $resources,
) as $name => $options)
{
if ($options)
{
natcasesort($options);
$settings['calendar-home-set-'.$name] = array(
'type' => 'multiselect',
'label' => lang('%1 to sync', lang($name == 'locations' ? 'Location calendars' : 'Resource calendars')),
'no_lang'=> true,
'name' => 'calendar-home-set-'.$name,
'help' => lang('Only supported by a few fully conformant clients (eg. from Apple). If you have to enter a URL, it will most likly not be suppored!').'<br/>'.lang('They will be sub-folders in users home (%1 attribute).','CalDAV "calendar-home-set"'),
'values' => $options,
'xmlrpc' => True,
'admin' => False,
);
}
}
}
return $settings;
}
}

View File

@ -284,7 +284,7 @@ class calendar_so
*
* This includes ALL recurences of an event series
*
* @param int|array $users one or mulitple calendar users
* @param int|string|array $users one or mulitple calendar users
* @param booelan $owner_too=false if true return also events owned by given users
* @param boolean $master_only=false only check recurance master (egw_cal_user.recur_date=0)
* @return int maximum modification timestamp
@ -295,27 +295,44 @@ class calendar_so
$signature = serialize(func_get_args());
if (isset($ctags[$signature])) return $ctags[$signature];
$where = array(
'cal_user_type' => 'u',
'cal_user_id' => $users,
);
$types = array();
foreach((array)$users as $uid)
{
self::split_user($uid, $type, $id);
$types[$type][] = $id;
}
foreach($types as $type => $ids)
{
$where = array(
'cal_user_type' => $type,
'cal_user_id' => $ids,
);
if (count($types) > 1)
{
$types[$type] = $this->db->expression($this->user_table, $where);
}
}
if (count($types) > 1)
{
$where[] = '('.explode(' OR ', $types).')';
}
if ($master_only)
{
$where['cal_recur_date'] = 0;
}
if ($owner_too)
{
// owner can only by users, no groups
// owner can only by users, no groups or resources
foreach($users as $key => $user)
{
if ($user < 0) unset($users[$key]);
if (!($user > 0)) unset($users[$key]);
}
$where = $this->db->expression($this->user_table, '(', $where, ' OR ').
$this->db->expression($this->cal_table, array(
'cal_owner' => $users,
),')');
}
return $ctags[$signature] = $this->db->select($this->user_table,'MAX(cal_modified) AS max_modified',
return $ctags[$signature] = $this->db->select($this->user_table,'MAX(cal_modified)',
$where,__LINE__,__FILE__,false,'','calendar',0,'JOIN egw_cal ON egw_cal.cal_id=egw_cal_user.cal_id')->fetchColumn();
}

View File

@ -26,10 +26,10 @@ require_once('HTTP/WebDAV/Server.php');
* - /principals/groups/<groupname>/
* - /<username>/ users home-set with
* - /<username>/addressbook/ addressbook of user or group <username> given the user has rights to view it
* - /<username>/addressbook-<other-username>/ shared addressbooks from other user or group
* - /<username>/addressbook-accounts/ all accounts current user has rights to see
* - /<current-username>/addressbook-<other-username>/ shared addressbooks from other user or group
* - /<current-username>/addressbook-accounts/ all accounts current user has rights to see
* - /<username>/calendar/ calendar of user <username> given the user has rights to view it
* - /<username>/calendar-<other-username>/ shared calendar from other user or group
* - /<current-username>/calendar-<other-username>/ shared calendar from other user or group (only current <username>!)
* - /<username>/inbox/ scheduling inbox of user <username>
* - /<username>/outbox/ scheduling outbox of user <username>
* - /<username>/infolog/ InfoLog's of user <username> given the user has rights to view it
@ -37,6 +37,10 @@ require_once('HTTP/WebDAV/Server.php');
* - /addressbook-accounts/ all accounts current user has rights to see
* - /calendar/ calendar of current user
* - /infolog/ infologs of current user
* - /(resources|locations)/<resource-name>/calendar calendar of a resource/location, if user has rights to view
* - /<current-username>/(resource|location)-<resource-name> shared calendar from a resource/location
*
* Shared addressbooks or calendars are only shown in in users home-set, if he subscribed to it via his CalDAV preferences!
*
* Calling one of the above collections with a GET request / regular browser generates an automatic index
* from the data of a allprop PROPFIND, allow to browse CalDAV/CardDAV/GroupDAV tree with a regular browser.
@ -347,7 +351,7 @@ class groupdav extends HTTP_WebDAV_Server
if ($this->debug > 1) error_log(__CLASS__."::$method: user='$user', app='$app', id='$id': 404 not found!");
return '404 Not Found';
}
if ($this->debug > 1) error_log(__CLASS__."::$method: user='$user', app='$app', id='$id'");
if ($this->debug > 1) error_log(__CLASS__."::$method(path='$options[path]'): user='$user', user_prefix='$user_prefix', app='$app', id='$id'");
$files = array('files' => array());
$path = $user_prefix = $this->_slashify($user_prefix);
@ -364,7 +368,6 @@ class groupdav extends HTTP_WebDAV_Server
$files['files'][] = $this->add_collection('/principals/', array(
'displayname' => lang('Accounts'),
));
// todo: account_selection owngroups and none!!!
foreach($this->accounts->search(array('type' => 'both','order'=>'account_lid')) as $account)
{
$this->add_home($files, $path.$account['account_lid'].'/', $account['account_id'], $options['depth'] == 'infinity' ? 'infinity' : $options['depth']-1);
@ -372,6 +375,10 @@ class groupdav extends HTTP_WebDAV_Server
}
return true;
}
if ($path == '/' && ($app == 'resources' || $app == 'locations'))
{
return $this->add_resources_collection($files, '/'.$app.'/', $options['depth']);
}
if ($app != 'principals' && !isset($GLOBALS['egw_info']['user']['apps'][$this->root[$app]['app'] ? $this->root[$app]['app'] : $app]))
{
if ($this->debug) error_log(__CLASS__."::$method(path=$options[path]) 403 Forbidden: no app rights for '$app'");
@ -639,6 +646,49 @@ class groupdav extends HTTP_WebDAV_Server
// added shared calendars or addressbooks
$this->add_shared($files['files'], $path, $app, $user);
}
if ($path == '/' && $GLOBALS['egw_info']['user']['apps']['resources'])
{
$this->add_resources_collection($files, $path.'resources/');
$this->add_resources_collection($files, $path.'locations/');
}
}
return true;
}
/**
* Add collection with available resources or locations calendar-home-sets
*
* @param array &$files
* @param string $path / or /<username>/
* @param int $depth=0
* @return string|boolean http status or true|false
*/
protected function add_resources_collection(array &$files, $path, $depth=0)
{
if (!isset($GLOBALS['egw_info']['user']['apps']['resources']))
{
if ($this->debug) error_log(__CLASS__."::$method(path=$path) 403 Forbidden: no app rights for 'resources'");
return "403 Forbidden: no app rights for 'resources'"; // no rights for the given app
}
list(,$what) = explode('/', $path);
if (($is_location = ($what == 'locations')))
{
$files['files'][] = $this->add_collection('/locations/', array('displayname' => lang('Location calendars')));
}
else
{
$files['files'][] = $this->add_collection('/resources/', array('displayname' => lang('Resource calendars')));
}
if ($depth)
{
foreach(groupdav_principals::get_resources() as $res_id => $resource)
{
if ($is_location == groupdav_principals::resource_is_location($resource))
{
$files['files'][] = $this->add_app('calendar', false, 'r'.$resource['res_id'],
'/'.groupdav_principals::resource2name($resource, $is_location).'/');
}
}
}
return true;
}
@ -665,7 +715,7 @@ class groupdav extends HTTP_WebDAV_Server
{
foreach($shared as $id => $owner)
{
$file = $this->add_app($app,false,$id,$path.$app.'-'.$owner.'/');
$file = $this->add_app($app,false,$id,$path.$owner.'/');
// mark other users calendar as shared (iOS 5.0.1 AB does NOT display AB marked as shared!)
if ($app == 'calendar') $file['props']['resourcetype']['val'][] = self::mkprop(self::CALENDARSERVER,'shared','');
$files[] = $file;
@ -721,7 +771,12 @@ class groupdav extends HTTP_WebDAV_Server
{
if ($this->debug) error_log(__METHOD__."(app='$app', no_extra_types=$no_extra_types, user='$user', path='$path')");
$user_preferences = $GLOBALS['egw_info']['user']['preferences'];
if ($user)
if (is_string($user) && $user[0] == 'r' && ($resource = groupdav_principals::read_resource(substr($user, 1))))
{
$is_location = groupdav_principals::resource_is_location($resource);
list($principalType, $account_lid) = explode('/', groupdav_principals::resource2name($resource, $is_location, $displayname));
}
elseif ($user)
{
$account_lid = $this->accounts->id2name($user);
if ($user >= 0 && $GLOBALS['egw']->preferences->account_id != $user)
@ -730,22 +785,14 @@ class groupdav extends HTTP_WebDAV_Server
$user_preferences = $GLOBALS['egw']->preferences->read_repository();
$GLOBALS['egw']->preferences->__construct($GLOBALS['egw_info']['user']['account_lid']);
}
$principalType = $user < 0 ? 'groups' : 'users';
}
else
{
$account_lid = $GLOBALS['egw_info']['user']['account_lid'];
}
$account = $this->accounts->read($account_lid);
if ($user < 0)
{
$principalType = 'groups';
}
else
{
$principalType = 'users';
}
if (!isset($displayname)) $displayname = $this->account_name($user);
$props = array(
'owner' => array(self::mkprop('href',$this->base_uri.'/principals/'.$principalType.'/'.$account_lid.'/')),
@ -754,10 +801,10 @@ class groupdav extends HTTP_WebDAV_Server
switch ($app)
{
case 'inbox':
$props['displayname'] = lang('Scheduling inbox').' '.$this->account_name($user);
$props['displayname'] = lang('Scheduling inbox').' '.$displayname;
break;
case 'outbox':
$props['displayname'] = lang('Scheduling outbox').' '.$this->account_name($user);
$props['displayname'] = lang('Scheduling outbox').' '.$displayname;
break;
case 'addressbook':
if ($path == '/addressbook/')
@ -773,7 +820,7 @@ class groupdav extends HTTP_WebDAV_Server
}
// fall through
default:
$props['displayname'] = translation::convert(lang($app).' '.$this->account_name($user),$this->egw_charset,'utf-8');
$props['displayname'] = translation::convert(lang($app).' '.$displayname, $this->egw_charset, 'utf-8');
}
// rfc 5995 (Use POST to add members to WebDAV collections): we use collection path with add-member query param
@ -842,7 +889,7 @@ class groupdav extends HTTP_WebDAV_Server
{
if (method_exists($handler,'extra_properties'))
{
$props = $handler->extra_properties($props,$this->account_name($account),$this->base_uri,$user,$path);
$props = $handler->extra_properties($props, $displayname, $this->base_uri, $user, $path);
}
// add ctag if handler implements it
if (method_exists($handler,'getctag') && $this->prop_requested('getctag') === true)
@ -1433,14 +1480,30 @@ class groupdav extends HTTP_WebDAV_Server
}
$parts = explode('/', $this->_unslashify($path));
if (($account_id = $this->accounts->name2id($parts[0], 'account_lid')) ||
// /(resources|locations)/<resource-id>-<resource-name>/calendar
if ($parts[0] == 'resources' || $parts[0] == 'locations')
{
if (!empty($parts[1]))
{
$user = $parts[0].'/'.$parts[1];
array_shift($parts);
$res_id = (int)array_shift($parts);
if (!groupdav_principals::read_resource($res_id))
{
return false;
}
$account_id = 'r'.$res_id;
$app = 'calendar';
}
}
elseif (($account_id = $this->accounts->name2id($parts[0], 'account_lid')) ||
($account_id = $this->accounts->name2id($parts[0]=urldecode($parts[0]))))
{
// /$user/$app/...
$user = array_shift($parts);
}
$app = array_shift($parts);
if (!isset($app)) $app = array_shift($parts);
// /addressbook-accounts/
if (!$account_id && $app == 'addressbook-accounts')
@ -1449,21 +1512,30 @@ class groupdav extends HTTP_WebDAV_Server
$user = 0;
$user_prefix = '/';
}
// shared calendars/addressbooks at /<currentuser>/(calendar|addressbook|infolog)-<username>
// shared calendars/addressbooks at /<currentuser>/(calendar|addressbook|infolog|resource|location)-<username>
elseif ($account_id == $GLOBALS['egw_info']['user']['account_id'] && strpos($app, '-') !== false)
{
$user_prefix = '/'.$GLOBALS['egw_info']['user']['account_lid'].'/'.$app;
list($app, $username) = explode('-', $app, 2);
if ($username == 'accounts' && !$GLOBALS['egw_info']['user']['preferences']['addressbook']['hide_accounts'])
{
$account_id = 0;
}
elseif($app == 'resource' || $app == 'location')
{
if (!groupdav_principals::read_resource($res_id = (int)$username))
{
return false;
}
$account_id = 'r'.$res_id;
$app = 'calendar';
}
elseif (!($account_id = $this->accounts->name2id($username, 'account_lid')) &&
!($account_id = $this->accounts->name2id($username=urldecode($username))))
{
return false;
}
$user = $account_id;
$user_prefix = '/'.$GLOBALS['egw_info']['user']['account_lid'].'/'.$app.'-'.$username;
}
elseif ($user)
{

View File

@ -756,7 +756,7 @@ class groupdav_principals extends groupdav_handler
{
$files = array();
// add /pricipals/users/ entry
$files[] = $this->add_collection('/principals/users/');
$files[] = $this->add_collection('/principals/users/', array('displayname' => lang('Users')));
if ($options['depth'])
{
@ -822,7 +822,7 @@ class groupdav_principals extends groupdav_handler
{
$files = array();
// add /pricipals/users/ entry
$files[] = $this->add_collection('/principals/groups/');
$files[] = $this->add_collection('/principals/groups/', array('displayname' => lang('Groups')));
if ($options['depth'])
{
@ -1130,19 +1130,25 @@ class groupdav_principals extends groupdav_handler
'resource-id' => array(HTTP_WebDAV_Server::mkprop('href','urn:uuid:'.common::generate_uid('resources', $resource['res_id']))),
// Calendarserver also reports empty email-address-set, thought iCal still does not show resources (only locations)
'email-address-set' => HTTP_WebDAV_Server::mkprop(groupdav::CALENDARSERVER,'email-address-set',''),
'calendar-home-set' => HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'calendar-home-set',array(
HTTP_WebDAV_Server::mkprop('href',$this->base_uri.'/'.$name.'/'))),
));
}
/**
* Get path of a resource-principal (relative to principal collection)
*
* @param array $resource
* @param boolean $is_location=null
* @param array|int $resource
* @param boolean &$is_location=null
* @param string &$displayname=null on return displayname of resource
* @return string eg. "locations/123-some-room" or "resouces/345-some-device"
*/
protected function resource2name(array $resource, &$is_location=null, &$displayname=null)
public static function resource2name($resource, &$is_location=null, &$displayname=null)
{
if (!is_array($resource) && !($resource = self::read_resource($resource)))
{
return null;
}
if (is_null($is_location)) $is_location = self::resource_is_location($resource);
$displayname = translation::convert($resource['name'], translation::charset(), 'utf-8');
@ -1165,7 +1171,7 @@ class groupdav_principals extends groupdav_handler
$str = htmlentities($str,ENT_QUOTES,translation::charset());
$str = str_replace(array_keys($extra),array_values($extra),$str);
$str = preg_replace('/&([aAuUoO])uml;/','\\1e',$str); // replace german umlauts with the letter plus one 'e'
$str = preg_replace('/&([a-zA-Z])(grave|acute|circ|ring|cedil|tilde|slash|uml);/','\\1',$str); // remove all types of acents
$str = preg_replace('/&([a-zA-Z])(grave|acute|circ|ring|cedil|tilde|slash|uml);/','\\1',$str); // remove all types of accents
$str = preg_replace('/&([a-zA-Z]+|#[0-9]+|);/','',$str); // remove all other entities
return $str;
@ -1186,16 +1192,38 @@ class groupdav_principals extends groupdav_handler
$config = config::read('resources');
$location_cats = $config['location_cats'] ? explode(',', $config['location_cats']) : array();
}
if (!isset(self::$resources)) self::$resources = new resources_bo();
if (!is_array($resource))
if (!is_array($resource) && !($resource = self::read_resource($resource)))
{
if (!($resource = self::$resources->read($resource)))
return null;
}
return $resource['cat_id'] && in_array($resource['cat_id'], $location_cats);
}
/**
* Read a resource
*
* @param int $res_id resource-id
* @return array with values for res_id, cat_id and name
*/
public static function read_resource($res_id)
{
static $cache; // some per-request caching
if (isset(self::$all_resources) && isset(self::$all_resources[$res_id]))
{
return self::$all_resources[$res_id];
}
if (!isset($cache[$res_id]))
{
if (!isset(self::$resources)) self::$resources = new resources_bo();
if (!($cache[$res_id] = self::$resources->read($res_id)))
{
return null;
}
}
return $resource['cat_id'] && in_array($resource['cat_id'], $location_cats);
return $cache[$res_id];
}
/**
@ -1220,10 +1248,12 @@ class groupdav_principals extends groupdav_handler
*
* @return array of array with values for res_id, cat_id and name (no other values1)
*/
protected function get_resources()
public static function get_resources()
{
if (!isset(self::$all_resources))
{
if (!isset(self::$resources)) self::$resources = new resources_bo();
self::$all_resources = array();
$query = array(
'show_bookable' => true, // ignore non-bookable resources
@ -1331,7 +1361,7 @@ class groupdav_principals extends groupdav_handler
$app = 'resources';
if (!is_array($resource) || $resource['res_id'] == (int)$account)
{
$resource = self::$resources->read((int)$account);
$resource = self::read_resource((int)$account);
}
$location = 'L'.$resource['cat_id'];
$right = $what == 'write' ? EGW_ACL_DIRECT_BOOKING : EGW_ACL_CALREAD;
@ -1502,7 +1532,8 @@ class groupdav_principals extends groupdav_handler
{
$files = array();
// add /pricipals/users/ entry
$files[] = $this->add_collection('/principals/'.($do_locations ? 'locations/' : 'resources/'));
$files[] = $this->add_collection('/principals/'.($do_locations ? 'locations/' : 'resources/'),
array('displayname' => $do_locations ? lang('Locations') : lang('Resources')));
if ($options['depth'])
{
@ -1524,7 +1555,7 @@ class groupdav_principals extends groupdav_handler
}
else
{
if (!($resource = self::$resources->read((int)$name)) || ($is_location = self::resource_is_location($resource)) != $do_locations)
if (!($resource = self::read_resource((int)$name)) || ($is_location = self::resource_is_location($resource)) != $do_locations)
{
return '404 Not Found';
}

View File

@ -1,6 +1,7 @@
%1 email addresses inserted common de %1 E-Mail-Adressen eingefügt
%1 file common de %1 Datei
%1 is not executable by the webserver !!! common de %1 ist nicht ausführbar durch den Webserver !!!
%1 to sync groupdav de %1 zum synchronisieren
%1choose an other directory%2<br />or make %3 writeable by webserver common de %1Anderes Verzeichnis auswählen%2<br />oder %3 geben Sie dem Webserver Schreibrechte für dieses Verzeichnis.
%1egroupware%2 is a multi-user, web-based groupware suite written in %3php%4. common de %1EGroupware%2 ist eine, in %3PHP%4 programmierte, webbasierte, Mehrbenutzer Groupware Suite.
(session restored in %1 seconds) common de (Sitzung in %1 Sekunden wiederhergestellt)
@ -418,6 +419,8 @@ list common de Liste
list members common de Mitglieder anzeigen
lithuania common de LITAUEN
local common de lokal
location calendars groupdav de Kalender von Räumen
locations common de Räume
logging / debuging groupdav de Logging / Fehlersuche
login common de Anmelden
loginid common de Benutzerkennung
@ -596,6 +599,7 @@ replace common de Ersetzen
replace with common de Ersetzen durch
requests and full responses to files directory common de Anfragen und komplette Antworten in das Dateiverzeichnis
requests and truncated responses to apache error-log groupdav de Anfragen und gekürzte Antworten ins Apache error-log
resource calendars groupdav de Kalender von Ressourcen
resource type common de Ressource Typ
restore failed common de Wiederherstellung fehlgeschlagen
returns a full list of accounts on the system. warning: this is return can be quite large common de Liefert eine vollständige Lister der Benutzerkonten auf diesem System. Warnung: Die Rückgabe kann sehr gross sein

View File

@ -1,6 +1,7 @@
%1 email addresses inserted common en %1 email addresses inserted.
%1 file common en %1 file
%1 is not executable by the webserver !!! common en %1 is not executable by the web server!
%1 to sync groupdav en %1 to sync
%1choose an other directory%2<br />or make %3 writeable by webserver common en %1Choose an other directory%2<br />or make %3 writable by web server.
%1egroupware%2 is a multi-user, web-based groupware suite written in %3php%4. common en %1eGroupWare%2 is a multi-user, web-based groupware suite written in %3PHP%4.
(session restored in %1 seconds) common en Session restored in %1 seconds.
@ -419,6 +420,8 @@ list common en List
list members common en List members
lithuania common en LITHUANIA
local common en Local
location calendars groupdav en Location calendars
locations common en Locations
logging / debuging groupdav en Logging / debuging
login common en Login
loginid common en Login ID
@ -597,6 +600,7 @@ replace common en Replace
replace with common en Replace with
requests and full responses to files directory common en Requests and full responses to files directory
requests and truncated responses to apache error-log groupdav en Requests and truncated responses to Apache error-log
resource calendars groupdav en Resource calendars
resource type common en Resource type
restore failed common en Restore failed
returns a full list of accounts on the system. warning: this is return can be quite large common en Returns a full list of accounts on the system. Warning: This is return can be quite large.