diff --git a/addressbook/inc/class.addressbook_groupdav.inc.php b/addressbook/inc/class.addressbook_groupdav.inc.php index 94a2feac74..aa72a4d23a 100644 --- a/addressbook/inc/class.addressbook_groupdav.inc.php +++ b/addressbook/inc/class.addressbook_groupdav.inc.php @@ -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; diff --git a/calendar/inc/class.calendar_bo.inc.php b/calendar/inc/class.calendar_bo.inc.php index 535955024b..2bf179f527 100644 --- a/calendar/inc/class.calendar_bo.inc.php +++ b/calendar/inc/class.calendar_bo.inc.php @@ -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 diff --git a/calendar/inc/class.calendar_groupdav.inc.php b/calendar/inc/class.calendar_groupdav.inc.php index 3777718407..0b887a48d0 100644 --- a/calendar/inc/class.calendar_groupdav.inc.php +++ b/calendar/inc/class.calendar_groupdav.inc.php @@ -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!').'
'.lang('They will be sub-folders in users home (%1 attribute).','CalDAV "calendar-home-set"'), + 'values' => $options, + 'xmlrpc' => True, + 'admin' => False, + ); + } + } + } return $settings; } } diff --git a/calendar/inc/class.calendar_so.inc.php b/calendar/inc/class.calendar_so.inc.php index afc400920f..cab0331529 100644 --- a/calendar/inc/class.calendar_so.inc.php +++ b/calendar/inc/class.calendar_so.inc.php @@ -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(); } diff --git a/phpgwapi/inc/class.groupdav.inc.php b/phpgwapi/inc/class.groupdav.inc.php index 261502d714..3a82ab4943 100644 --- a/phpgwapi/inc/class.groupdav.inc.php +++ b/phpgwapi/inc/class.groupdav.inc.php @@ -26,10 +26,10 @@ require_once('HTTP/WebDAV/Server.php'); * - /principals/groups// * - // users home-set with * - //addressbook/ addressbook of user or group given the user has rights to view it - * - //addressbook-/ shared addressbooks from other user or group - * - //addressbook-accounts/ all accounts current user has rights to see + * - //addressbook-/ shared addressbooks from other user or group + * - //addressbook-accounts/ all accounts current user has rights to see * - //calendar/ calendar of user given the user has rights to view it - * - //calendar-/ shared calendar from other user or group + * - //calendar-/ shared calendar from other user or group (only current !) * - //inbox/ scheduling inbox of user * - //outbox/ scheduling outbox of user * - //infolog/ InfoLog's of user 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)//calendar calendar of a resource/location, if user has rights to view + * - //(resource|location)- 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 // + * @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)/-/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 //(calendar|addressbook|infolog)- + // shared calendars/addressbooks at //(calendar|addressbook|infolog|resource|location)- 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) { diff --git a/phpgwapi/inc/class.groupdav_principals.inc.php b/phpgwapi/inc/class.groupdav_principals.inc.php index 37120313a7..26c70341b5 100644 --- a/phpgwapi/inc/class.groupdav_principals.inc.php +++ b/phpgwapi/inc/class.groupdav_principals.inc.php @@ -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'; } diff --git a/phpgwapi/lang/egw_de.lang b/phpgwapi/lang/egw_de.lang index 2b9667cb6d..5b01227a00 100644 --- a/phpgwapi/lang/egw_de.lang +++ b/phpgwapi/lang/egw_de.lang @@ -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
or make %3 writeable by webserver common de %1Anderes Verzeichnis auswählen%2
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 diff --git a/phpgwapi/lang/egw_en.lang b/phpgwapi/lang/egw_en.lang index 5b1d1ce6a7..3836912747 100644 --- a/phpgwapi/lang/egw_en.lang +++ b/phpgwapi/lang/egw_en.lang @@ -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
or make %3 writeable by webserver common en %1Choose an other directory%2
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.