From 306c9455c50edcb290d9e0a6f1b8fbe7bfb39150 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Lehrke?= Date: Mon, 1 Mar 2010 21:18:52 +0000 Subject: [PATCH] Various GroupDAV fixes and extensions --- .../inc/class.addressbook_groupdav.inc.php | 16 +- calendar/inc/class.calendar_groupdav.inc.php | 95 ++++- calendar/inc/class.calendar_ical.inc.php | 338 ++++++++++++------ egw-pear/HTTP/WebDAV/Server.php | 162 +++++++-- infolog/inc/class.infolog_bo.inc.php | 15 +- infolog/inc/class.infolog_groupdav.inc.php | 103 +++++- infolog/inc/class.infolog_ical.inc.php | 13 +- phpgwapi/inc/class.groupdav.inc.php | 109 +++++- phpgwapi/inc/class.groupdav_groups.inc.php | 9 +- phpgwapi/inc/class.groupdav_handler.inc.php | 33 +- .../inc/class.groupdav_principals.inc.php | 52 ++- 11 files changed, 736 insertions(+), 209 deletions(-) diff --git a/addressbook/inc/class.addressbook_groupdav.inc.php b/addressbook/inc/class.addressbook_groupdav.inc.php index 39f93fc189..66b054cef8 100644 --- a/addressbook/inc/class.addressbook_groupdav.inc.php +++ b/addressbook/inc/class.addressbook_groupdav.inc.php @@ -51,10 +51,11 @@ class addressbook_groupdav extends groupdav_handler * @param string $app 'calendar', 'addressbook' or 'infolog' * @param int $debug=null debug-level to set * @param string $base_uri=null base url of handler + * @param string $principalURL=null pricipal url of handler */ - function __construct($app,$debug=null,$base_uri=null) + function __construct($app,$debug=null,$base_uri=null,$principalURL=null) { - parent::__construct($app,$debug,$base_uri); + parent::__construct($app,$debug,$base_uri,$principalURL); $this->bo = new addressbook_bo(); } @@ -309,7 +310,7 @@ class addressbook_groupdav extends groupdav_handler /** * Query ctag for addressbook - * + * * @return string */ public function getctag($path,$user) @@ -319,9 +320,9 @@ class addressbook_groupdav extends groupdav_handler if ($user && $path != '/addressbook/') $filter['contact_owner'] = $user; // should we hide the accounts addressbook if ($GLOBALS['egw_info']['user']['preferences']['addressbook']['hide_accounts']) $filter['account_id'] = null; - + $result = $this->bo->search(array(),'MAX(contact_modified) AS contact_modified','','','','','',$filter); - + return '"'.$result[0]['modified'].'"'; } @@ -342,11 +343,12 @@ class addressbook_groupdav extends groupdav_handler * * * @link http://www.mail-archive.com/calendarserver-users@lists.macosforge.org/msg01156.html - * + * * @param array $props=array() regular props by the groupdav handler + * @param string $base_uri=null base url of handler * @return array */ - static function extra_properties(array $props=array()) + static function extra_properties(array $props=array(), $base_uri=null) { // supported reports (required property for CardDAV) $props[] = HTTP_WebDAV_Server::mkprop('supported-report-set',array( diff --git a/calendar/inc/class.calendar_groupdav.inc.php b/calendar/inc/class.calendar_groupdav.inc.php index 098ed1da08..faf5dfa574 100644 --- a/calendar/inc/class.calendar_groupdav.inc.php +++ b/calendar/inc/class.calendar_groupdav.inc.php @@ -23,6 +23,8 @@ class calendar_groupdav extends groupdav_handler */ var $bo; + const DAV = 'DAV:'; + var $filter_prop2cal = array( 'SUMMARY' => 'cal_title', 'UID' => 'cal_uid', @@ -58,10 +60,11 @@ class calendar_groupdav extends groupdav_handler * @param string $app 'calendar', 'addressbook' or 'infolog' * @param int $debug=null debug-level to set * @param string $base_uri=null base url of handler + * @param string $principalURL=null pricipal url of handler */ - function __construct($app,$debug=null, $base_uri=null) + function __construct($app,$debug=null, $base_uri=null,$principalURL=null) { - parent::__construct($app,$debug,$base_uri); + parent::__construct($app,$debug,$base_uri,$principalURL); $this->bo = new calendar_boupdate(); } @@ -172,8 +175,10 @@ class calendar_groupdav extends groupdav_handler 'text/calendar; charset=utf-8; component=VEVENT' : 'text/calendar'), // getlastmodified and getcontentlength are required by WebDAV and Cadaver eg. reports 404 Not found if not set HTTP_WebDAV_Server::mkprop('getlastmodified', $event['modified']), - HTTP_WebDAV_Server::mkprop('resourcetype',''), // iPhone requires that attribute! + HTTP_WebDAV_Server::mkprop('resourcetype',''), // DAVKit requires that attribute! ); + //$props[] = HTTP_WebDAV_Server::mkprop('current-user-principal',array(self::mkprop('href','MAILTO:'.$GLOBALS['egw_info']['user']['email']))); + //$props = self::current_user_privilege_set($props); //error_log(__FILE__ . __METHOD__ . "Calendar Data : $calendar_data"); if ($calendar_data) { @@ -229,7 +234,7 @@ class calendar_groupdav extends groupdav_handler return false; // return nothing for now, todo: check if we can pass it on to the infolog handler // todos are handled by the infolog handler //$infolog_handler = new groupdav_infolog(); - //return $infolog_handler->propfind($path,$options,$files,$user,$method); + //return $infolog_handler->propfind($options['path'],$options,$options['files'],$user,$method); case 'VCALENDAR': case 'VEVENT': break; // that's our default anyway @@ -357,7 +362,7 @@ class calendar_groupdav extends groupdav_handler { $events[0]['uid'] .= '-'.$event['id']; // force a different uid } - return $handler->exportVCal($events,'2.0','PUBLISH'); + return $handler->exportVCal($events,'2.0','PUBLISH',0,$this->principalURL); } /** @@ -444,13 +449,10 @@ class calendar_groupdav extends groupdav_handler } $handler = $this->_get_handler(); - if (!is_numeric($id) && ($foundEntries = $handler->find_event($options['content'], 'exact'))) - { - $id = array_shift($foundEntries); - } + $vCalendar = htmlspecialchars_decode($options['content']); - if (!($cal_id = $handler->importVCal($options['content'],is_numeric($id) ? $id : -1, - self::etag2value($this->http_if_match)))) + if (!($cal_id = $handler->importVCal($vCalendar, is_numeric($id) ? $id : -1, + self::etag2value($this->http_if_match), false, 0, $this->principalURL))) { if ($this->debug) error_log(__METHOD__."(,$id) importVCal($options[content]) returned false"); return '403 Forbidden'; @@ -463,6 +465,11 @@ class calendar_groupdav extends groupdav_handler header('Location: '.$this->base_uri.self::get_path($cal_id)); return '201 Created'; } + if (strpos($_SERVER[HTTP_USER_AGENT], 'Mac OS X') !== false) + { + //return '205 Reset Content'; // would be nicer + return '400 Event updated, please reload'; // Enforce a reload by iCal + } return true; } @@ -636,23 +643,75 @@ class calendar_groupdav extends groupdav_handler } /** - * Add extra properties for calendar collections + * Add the privileges of the current user * * @param array $props=array() regular props by the groupdav handler * @return array */ - static function extra_properties(array $props=array()) + static function current_user_privilege_set(array $props=array()) { - // calendaring URL of the current user - $props[] = HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'calendar-home-set',$_SERVER['SCRIPT_NAME'].'/'); + $props[] = HTTP_WebDAV_Server::mkprop(self::DAV,'current-user-privilege-set', + array(HTTP_WebDAV_Server::mkprop(self::DAV,'privilege', + array(//HTTP_WebDAV_Server::mkprop(self::DAV,'all',''), + HTTP_WebDAV_Server::mkprop(self::DAV,'read',''), + HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'read-free-busy',''), + //HTTP_WebDAV_Server::mkprop(self::DAV,'read-current-user-privilege-set',''), + HTTP_WebDAV_Server::mkprop(self::DAV,'bind',''), + HTTP_WebDAV_Server::mkprop(self::DAV,'unbind',''), + HTTP_WebDAV_Server::mkprop(self::DAV,'schedule-post',''), + HTTP_WebDAV_Server::mkprop(self::DAV,'schedule-post-vevent',''), + HTTP_WebDAV_Server::mkprop(self::DAV,'schedule-respond',''), + HTTP_WebDAV_Server::mkprop(self::DAV,'schedule-respond-vevent',''), + HTTP_WebDAV_Server::mkprop(self::DAV,'schedule-deliver',''), + HTTP_WebDAV_Server::mkprop(self::DAV,'schedule-deliver-vevent',''), + HTTP_WebDAV_Server::mkprop(self::DAV,'write',''), + HTTP_WebDAV_Server::mkprop(self::DAV,'write-properties',''), + HTTP_WebDAV_Server::mkprop(self::DAV,'write-content',''), + )))); + return $props; + } + + /** + * Add extra properties for calendar collections + * + * @param array $props=array() regular props by the groupdav handler + * @param string $base_uri=null base url of handler + * @return array + */ + static function extra_properties(array $props=array(), $base_uri=null) + { + // calendar description + $displayname = $GLOBALS['egw']->translation->convert(lang('Calendar of'). ' ' . + $GLOBALS['egw_info']['user']['account_fullname'], + $GLOBALS['egw']->translation->charset(),'utf-8'); + $props[] = HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'calendar-description',$displayname); + // BOX URLs of the current user + /* + $props[] = HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'schedule-inbox-URL', + array(HTTP_WebDAV_Server::mkprop(self::DAV,'href',$base_uri.'/calendar/'))); + $props[] = HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'schedule-outbox-URL', + array(HTTP_WebDAV_Server::mkprop(self::DAV,'href',$base_uri.'/calendar/'))); + $props[] = HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'schedule-default-calendar-URL', + array(HTTP_WebDAV_Server::mkprop(self::DAV,'href',$base_uri.'/calendar/'))); + $props[] = HTTP_WebDAV_Server::mkprop(groupdav::CALENDARSERVER,'dropbox-home-URL', + array(HTTP_WebDAV_Server::mkprop(self::DAV,'href',$base_uri.'/calendar/'))); + $props[] = HTTP_WebDAV_Server::mkprop(groupdav::CALENDARSERVER,'notifications-URL', + array(HTTP_WebDAV_Server::mkprop(self::DAV,'href',$base_uri.'/calendar/'))); + */ // email of the current user, see caldav-sheduling draft $props[] = HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'calendar-user-address-set','MAILTO:'.$GLOBALS['egw_info']['user']['email']); // supported components, currently only VEVENT - $props[] = $sc = HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'supported-calendar-component-set',array( + $props[] = HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'supported-calendar-component-set',array( HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'comp',array('name' => 'VEVENT')), -// HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'comp',array('name' => 'VTODO')), // not yet supported + // HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'comp',array('name' => 'VTODO')), // not yet supported )); - + /* + $props[] = HTTP_WebDAV_Server::mkprop('supported-report-set',array( + HTTP_WebDAV_Server::mkprop('supported-report',array( + HTTP_WebDAV_Server::mkprop('report', + HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'calendar-multiget')))))); + */ + //$props = self::current_user_privilege_set($props); return $props; } diff --git a/calendar/inc/class.calendar_ical.inc.php b/calendar/inc/class.calendar_ical.inc.php index 9952667bea..1fd3e00601 100644 --- a/calendar/inc/class.calendar_ical.inc.php +++ b/calendar/inc/class.calendar_ical.inc.php @@ -54,6 +54,7 @@ class calendar_ical extends calendar_boupdate 'DECLINED' => 'R', 'TENTATIVE' => 'T', 'DELEGATED' => 'D', + 'X-UNINVITED' => 'G', // removed ); /** @@ -188,21 +189,29 @@ class calendar_ical extends calendar_boupdate if ($this->log) $this->logfile = $GLOBALS['egw_info']['server']['temp_dir']."/log-vcal"; $this->clientProperties = $_clientProperties; $this->vCalendar = new Horde_iCalendar; + $this->addressbook = new addressbook_bo; } /** * Exports one calendar event to an iCalendar item * - * @param int/array $events (array of) cal_id or array of the events + * @param int|array $events (array of) cal_id or array of the events * @param string $version='1.0' could be '2.0' too * @param string $method='PUBLISH' * @param int $recur_date=0 if set export the next recurrence at or after the timestamp, * default 0 => export whole series (or events, if not recurring) - * @return string/boolean string with iCal or false on error (eg. no permission to read the event) + * @param string $principalURL='' Used for CalDAV exports + * @return string|boolean string with iCal or false on error (eg. no permission to read the event) */ - function &exportVCal($events, $version='1.0', $method='PUBLISH', $recur_date=0) + function &exportVCal($events, $version='1.0', $method='PUBLISH', $recur_date=0, $principalURL='') { + if ($this->log) + { + error_log(__FILE__.'['.__LINE__.'] '.__METHOD__. + "($version, $method, $recur_date, $principalURL)\n", + 3, $this->logfile); + } $egwSupportedFields = array( 'CLASS' => 'public', 'SUMMARY' => 'title', @@ -242,7 +251,7 @@ class calendar_ical extends calendar_boupdate foreach ($events as $event) { - $mailtoOrganizer = false; + $organizerURL = ''; $organizerCN = false; $recurrence = $this->date2usertime($recur_date); if (!is_array($event) @@ -436,11 +445,19 @@ class calendar_ical extends calendar_boupdate foreach ((array)$event['participants'] as $uid => $status) { if (!($info = $this->resource_info($uid))) continue; - $mailtoParticipant = $info['email'] ? 'MAILTO:'.$info['email'] : ''; + $participantURL = $info['email'] ? 'MAILTO:'.$info['email'] : ''; $participantCN = '"' . ($info['cn'] ? $info['cn'] : $info['name']) . '"'; calendar_so::split_status($status, $quantity, $role); + if ($uid == $event['owner']) + { + $role = 'CHAIR'; + } + else + { + $role = 'REQ-PARTICIPANT'; + } // RB: MAILTO href contains only the email-address, NO cn! - $attributes['ATTENDEE'][] = $mailtoParticipant; + $attributes['ATTENDEE'][] = $participantURL; // RSVP={TRUE|FALSE} // resonse expected, not set in eGW => status=U $rsvp = $status == 'U' ? 'TRUE' : 'FALSE'; // PARTSTAT={NEEDS-ACTION|ACCEPTED|DECLINED|TENTATIVE|DELEGATED|COMPLETED|IN-PROGRESS} everything from delegated is NOT used by eGW atm. @@ -487,21 +504,28 @@ class calendar_ical extends calendar_boupdate break; case 'ORGANIZER': - // according to iCalendar standard, ORGANIZER not used for events in the own calendar - if (!$organizerCN && - ($event['owner'] != $this->user - || $this->productManufacturer != 'groupdav' - || $this->productName == 'kde')) + $organizerURL = $GLOBALS['egw']->accounts->id2name($event['owner'],'account_email'); + $organizerURL = $organizerURL ? 'MAILTO:'.$organizerURL : ''; + $organizerCN = '"' . trim($GLOBALS['egw']->accounts->id2name($event['owner'],'account_firstname') + . ' ' . $GLOBALS['egw']->accounts->id2name($event['owner'],'account_lastname')) . '"'; + if (!isset($event['participants'][$event['owner']])) { - $mailtoOrganizer = $GLOBALS['egw']->accounts->id2name($event['owner'],'account_email'); - $mailtoOrganizer = $mailtoOrganizer ? 'MAILTO:'.$mailtoOrganizer : ''; - $organizerCN = '"' . trim($GLOBALS['egw']->accounts->id2name($event['owner'],'account_firstname') - . ' ' . $GLOBALS['egw']->accounts->id2name($event['owner'],'account_lastname')) . '"'; + $attributes['ATTENDEE'][] = $organizerURL; + $parameters['ATTENDEE'][] = array( + 'CN' => $organizerCN, + 'ROLE' => 'CHAIR', + 'PARTSTAT' => 'DELEGATED', + 'CUTYPE' => 'INDIVIDUAL', + 'RSVP' => 'FALSE', + 'X-EGROUPWARE-UID' => $event['owner'], + ); } - if ($organizerCN) + if ($this->productManufacturer != 'groupdav' + || !$this->check_perms(EGW_ACL_EDIT,$event['id'])) { - $attributes['ORGANIZER'] = $mailtoOrganizer; + $attributes['ORGANIZER'] = $organizerURL; $parameters['ORGANIZER']['CN'] = $organizerCN; + $parameters['ORGANIZER']['X-EGROUPWARE-UID'] = $event['owner']; } break; @@ -1004,13 +1028,14 @@ class calendar_ical extends calendar_boupdate * @param boolean $merge=false merge data with existing entry * @param int $recur_date=0 if set, import the recurrence at this timestamp, * default 0 => import whole series (or events, if not recurring) + * @param string $principalURL='' Used for CalDAV imports * @return int|boolean cal_id > 0 on success, false on failure or 0 for a failed etag */ - function importVCal($_vcalData, $cal_id=-1, $etag=null, $merge=false, $recur_date=0) + function importVCal($_vcalData, $cal_id=-1, $etag=null, $merge=false, $recur_date=0, $principalURL='') { if (!is_array($this->supportedFields)) $this->setSupportedFields(); - if (!($events = $this->icaltoegw($_vcalData))) + if (!($events = $this->icaltoegw($_vcalData, $principalURL))) { return false; } @@ -1055,8 +1080,9 @@ class calendar_ical extends calendar_boupdate } if ($this->log) { - error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."()\n" . - array2string($event)."\n",3,$this->logfile); + error_log(__FILE__.'['.__LINE__.'] '.__METHOD__ + ."($cal_id, $etag, $recur_date, $principalURL)\n" + . array2string($event)."\n",3,$this->logfile); } $updated_id = false; $event_info = $this->get_event_info($event); @@ -1108,7 +1134,7 @@ class calendar_ical extends calendar_boupdate else { // no merge - if(!isset($this->supportedFields['category'])) + if(!isset($this->supportedFields['category']) || !isset($event['category'])) { $event['category'] = $event_info['stored_event']['category']; } @@ -1830,11 +1856,18 @@ class calendar_ical extends calendar_boupdate // __FILE__, __LINE__, PEAR_LOG_DEBUG); } - function icaltoegw($_vcalData) + /** + * Convert vCalendar data in EGw events + * + * @param string $_vcalData + * @param string $principalURL='' Used for CalDAV imports + * @return array|boolean events on success, false on failure + */ + function icaltoegw($_vcalData, $principalURL='') { if ($this->log) { - error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."()\n" . + error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."($principalURL)\n" . array2string($_vcalData)."\n",3,$this->logfile); } @@ -1848,10 +1881,6 @@ class calendar_ical extends calendar_boupdate error_log(__FILE__.'['.__LINE__.'] '.__METHOD__. "(): No vCalendar Container found!\n",3,$this->logfile); } - if ($this->tzid) - { - date_default_timezone_set($GLOBALS['egw_info']['server']['server_timezone']); - } return false; } $version = $vcal->getAttribute('VERSION'); @@ -1861,7 +1890,12 @@ class calendar_ical extends calendar_boupdate { if (is_a($component, 'Horde_iCalendar_vevent')) { - if ($event = $this->vevent2egw($component, $version, $this->supportedFields)) + if ($this->log) + { + error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.'()' . + get_class($component)." found\n",3,$this->logfile); + } + if ($event = $this->vevent2egw($component, $version, $this->supportedFields, $principalURL)) { if ($this->isWholeDay($event)) $event['whole_day'] = true; //common adjustments @@ -1958,6 +1992,14 @@ class calendar_ical extends calendar_boupdate $events[] = $event; } } + else + { + if ($this->log) + { + error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.'()' . + get_class($component)." found\n",3,$this->logfile); + } + } } return $events; @@ -1969,12 +2011,21 @@ class calendar_ical extends calendar_boupdate * @param array $component VEVENT * @param string $version vCal version (1.0/2.0) * @param array $supportedFields supported fields of the device + * @param string $principalURL='' Used for CalDAV imports * * @return array|boolean event on success, false on failure */ - function vevent2egw(&$component, $version, $supportedFields) + function vevent2egw(&$component, $version, $supportedFields, $principalURL='') { - if (!is_a($component, 'Horde_iCalendar_vevent')) return false; + if (!is_a($component, 'Horde_iCalendar_vevent')) + { + if ($this->log) + { + error_log(__FILE__.'['.__LINE__.'] '.__METHOD__.'()' . + get_class($component)." found\n",3,$this->logfile); + } + return false; + } if (isset($GLOBALS['egw_info']['user']['preferences']['syncml']['minimum_uid_length'])) { $minimum_uid_length = $GLOBALS['egw_info']['user']['preferences']['syncml']['minimum_uid_length']; @@ -2014,11 +2065,24 @@ class calendar_ical extends calendar_boupdate $vcardData['end'] = $dtend_ts; } } - if (!isset($vcardData['start'])) return false; // not a valid entry - + if (!isset($vcardData['start'])) + { + if ($this->log) + { + error_log(__FILE__.'['.__LINE__.'] '.__METHOD__ + . "() DTSTART missing!\n",3,$this->logfile); + } + return false; // not a valid entry + } // lets see what we can get from the vcard foreach ($component->_attributes as $attributes) { + if ($this->log) + { + error_log(__FILE__.'['.__LINE__.'] '.__METHOD__ + . '(): ' . $attributes['name'] . ' => ' + . $attributes['value'] . "\n",3,$this->logfile); + } switch ($attributes['name']) { case 'AALARM': @@ -2358,14 +2422,64 @@ class calendar_ical extends calendar_boupdate { $status = 'X'; // client did not return the status } - $cn = ''; - if (preg_match('/MAILTO:([@.a-z0-9_-]+)|MAILTO:"?([.a-z0-9_ -]*)"?[ ]*<([@.a-z0-9_-]*)>/i', + $uid = $email = $cn = ''; + $quantity = 1; + $role = 'REQ-PARTICIPANT'; + if (!empty($attributes['params']['ROLE'])) + { + $role = $attributes['params']['ROLE']; + } + // try pricipal url from CalDAV + if (strpos($attributes['value'], 'http') === 0) + { + if (!empty($principalURL) && strstr($attributes['value'], $principalURL) !== false) + { + $uid = $this->user; + if ($this->log) + { + error_log(__FILE__.'['.__LINE__.'] '.__METHOD__ + . "(): Found myself: '$uid'\n",3,$this->logfile); + } + } + else + { + if ($this->log) + { + error_log(__FILE__.'['.__LINE__.'] '.__METHOD__ + . '(): Unknown URI: ' . $attributes['value'] + . "\n",3,$this->logfile); + } + $attributes['value'] = ''; + } + } + // try X-EGROUPWARE-UID + if (!$uid && !empty($attributes['params']['X-EGROUPWARE-UID'])) + { + $uid = $attributes['params']['X-EGROUPWARE-UID']; + if (!empty($attributes['params']['X-EGROUPWARE-QUANTITY'])) + { + $quantity = $attributes['params']['X-EGROUPWARE-QUANTITY']; + } + if ($this->log) + { + error_log(__FILE__.'['.__LINE__.'] '.__METHOD__ + . "(): Found X-EGROUPWARE-UID: '$uid'\n",3,$this->logfile); + } + } + elseif ($attributes['value'] == 'Unknown') + { + // we use the current user + $uid = $this->user; + } + // try to find an email address + elseif (preg_match('/MAILTO:([@.a-z0-9_-]+)|MAILTO:"?([.a-z0-9_ -]*)"?[ ]*<([@.a-z0-9_-]*)>/i', $attributes['value'],$matches)) { $email = $matches[1] ? $matches[1] : $matches[3]; $cn = isset($matches[2]) ? $matches[2]: ''; } - elseif (preg_match('/"?([.a-z0-9_ -]*)"?[ ]*<([@.a-z0-9_-]*)>/i', + elseif (!empty($attributes['value']) && + preg_match('/"?([.a-z0-9_ -]*)"?[ ]*<([@.a-z0-9_-]*)>/i', $attributes['value'],$matches)) { $cn = $matches[1]; @@ -2375,82 +2489,92 @@ class calendar_ical extends calendar_boupdate { $email = $attributes['value']; } - else + if (!$uid && $email && ($uid = $GLOBALS['egw']->accounts->name2id($email, 'account_email'))) { - $email = false; // no email given - } - $searcharray = array(); - if ($email) - { - $searcharray = array('email' => $email, 'email_home' => $email); - } - if (isset($attributes['params']['CN']) && $attributes['params']['CN']) - { - if ($attributes['params']['CN'][0] == '"' - && substr($attributes['params']['CN'],-1) == '"') + // we use the account we found + if ($this->log) { - $attributes['params']['CN'] = substr($attributes['params']['CN'],1,-1); + error_log(__FILE__.'['.__LINE__.'] '.__METHOD__ + . "() Found account: '$uid', '$cn', '$email'\n",3,$this->logfile); } - $searcharray['n_fn'] = $attributes['params']['CN']; } - elseif ($cn) + if (!$uid) { - $searcharray['n_fn'] = $cn; - } - if (($uid = $attributes['params']['X-EGROUPWARE-UID']) - && ($info = $this->resource_info($uid)) - && (!$email || $info['email'] == $email)) - { - // we use the (checked) X-EGROUPWARE-UID - } - - //elseif (//$attributes['params']['CUTYPE'] == 'GROUP' - elseif (preg_match('/(.*) Group/', $searcharray['n_fn'], $matches)) - { - if (($uid = $GLOBALS['egw']->accounts->name2id($matches[1], 'account_lid', 'g'))) + $searcharray = array(); + // search for provided email address ... + if ($email) { - //Horde::logMessage("vevent2egw: group participant $uid", - // __FILE__, __LINE__, PEAR_LOG_DEBUG); - if ($status != 'X' && $status != 'U') + $searcharray = array('email' => $email, 'email_home' => $email); + } + // ... and for provided CN + if (!empty($attributes['params']['CN'])) + { + if ($attributes['params']['CN'][0] == '"' + && substr($attributes['params']['CN'],-1) == '"') { - // User tries to reply to the group invitiation - $members = $GLOBALS['egw']->accounts->members($uid, true); - if (in_array($this->user, $members)) + $cn = substr($attributes['params']['CN'],1,-1); + } + $searcharray['n_fn'] = $cn; + } + elseif ($cn) + { + $searcharray['n_fn'] = $cn; + } + + //elseif (//$attributes['params']['CUTYPE'] == 'GROUP' + if (preg_match('/(.*) Group/', $cn, $matches)) + { + if (($uid = $GLOBALS['egw']->accounts->name2id($matches[1], 'account_lid', 'g'))) + { + //Horde::logMessage("vevent2egw: group participant $uid", + // __FILE__, __LINE__, PEAR_LOG_DEBUG); + if (!isset($vcardData['participants'][$this->user]) && + $status != 'X' && $status != 'U') { - //Horde::logMessage("vevent2egw: set status to " . $status, - // __FILE__, __LINE__, PEAR_LOG_DEBUG); - $vcardData['participants'][$this->user] = - calendar_so::combine_status($status); + // User tries to reply to the group invitiation + $members = $GLOBALS['egw']->accounts->members($uid, true); + if (in_array($this->user, $members)) + { + //Horde::logMessage("vevent2egw: set status to " . $status, + // __FILE__, __LINE__, PEAR_LOG_DEBUG); + $vcardData['participants'][$this->user] = + calendar_so::combine_status($status,$quantity,$role); + } } $status = 'U'; // keep the group } + else continue; // can't find this group } - else continue; // can't find this group - } - elseif ($attributes['value'] == 'Unknown') - { - $uid = $this->user; - } - elseif ($email && ($uid = $GLOBALS['egw']->accounts->name2id($email,'account_email'))) - { - // we use the account we found - } - elseif (!$searcharray) - { - continue; // participants without email AND CN --> ignore it - } - elseif ((list($data) = ExecMethod2('addressbook.addressbook_bo.search',$searcharray, - array('id','egw_addressbook.account_id as account_id','n_fn'),'egw_addressbook.account_id IS NOT NULL DESC, n_fn IS NOT NULL DESC','','',false,'OR'))) - { - $uid = $data['account_id'] ? (int)$data['account_id'] : 'c'.$data['id']; - } - else - { - if (!$email) + elseif (empty($searcharray)) { - $email = 'no-email@egroupware.org'; // set dummy email to store the CN + continue; // participants without email AND CN --> ignore it + } + elseif ((list($data) = $this->addressbook->search($searcharray, + array('id','egw_addressbook.account_id as account_id','n_fn'), + 'egw_addressbook.account_id IS NOT NULL DESC, n_fn IS NOT NULL DESC', + '','',false,'OR'))) + { + // found an addressbook entry + $uid = $data['account_id'] ? (int)$data['account_id'] : 'c'.$data['id']; + if ($this->log) + { + error_log(__FILE__.'['.__LINE__.'] '.__METHOD__ + . "() Found addressbook entry: '$uid', '$cn', '$email'\n",3,$this->logfile); + } + } + else + { + if (!$email) + { + $email = 'no-email@egroupware.org'; // set dummy email to store the CN + } + $uid = 'e'. ($cn ? '"' . $cn . '" <' . $email . '>' : $email); + if ($this->log) + { + error_log(__FILE__.'['.__LINE__.'] '.__METHOD__ + . "() Not Found, create dummy: '$uid', '$cn', '$email'\n",3,$this->logfile); + } } - $uid = 'e'.($attributes['params']['CN'] ? $attributes['params']['CN'].' <'.$email.'>' : $email); } switch($attributes['name']) { @@ -2461,9 +2585,15 @@ class calendar_ical extends calendar_boupdate // for multiple entries the ACCEPT wins // add quantity and role $vcardData['participants'][$uid] = - calendar_so::combine_status($status, - $attributes['params']['X-EGROUPWARE-QUANTITY'], - $attributes['params']['ROLE']); + calendar_so::combine_status($status, $quantity, $role); + + if (!$this->calendarOwner && is_numeric($uid) && + $role == 'CHAIR' && + is_a($component->getAttribute('ORGANIZER'), 'PEAR_Error')) + { + // we can store the ORGANIZER as event owner + $event['owner'] = $uid; + } } break; @@ -2475,7 +2605,7 @@ class calendar_ical extends calendar_boupdate $vcardData['participants'][$uid] = calendar_so::combine_status($status, $quantity, 'CHAIR'); } - if (is_numeric($uid) && ($uid == $this->calendarOwner || !$this->calendarOwner)) + if (!$this->calendarOwner && is_numeric($uid)) { // we can store the ORGANIZER as event owner $event['owner'] = $uid; @@ -2488,7 +2618,7 @@ class calendar_ical extends calendar_boupdate { // save the ORGANIZER as event CHAIR $vcardData['participants'][$uid] = - calendar_so::combine_status('U', 1, 'CHAIR'); + calendar_so::combine_status('D', 1, 'CHAIR'); } } } @@ -2563,7 +2693,7 @@ class calendar_ical extends calendar_boupdate if ($this->log) { - error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."()\n" . + error_log(__FILE__.'['.__LINE__.'] '.__METHOD__."($principalURL)\n" . array2string($event)."\n",3,$this->logfile); } //Horde::logMessage("vevent2egw:\n" . print_r($event, true), diff --git a/egw-pear/HTTP/WebDAV/Server.php b/egw-pear/HTTP/WebDAV/Server.php index 94dd2170a6..0a98a2bc01 100644 --- a/egw-pear/HTTP/WebDAV/Server.php +++ b/egw-pear/HTTP/WebDAV/Server.php @@ -43,7 +43,6 @@ class HTTP_WebDAV_Server */ var $uri; - /** * base URI for this request * @@ -431,6 +430,26 @@ class HTTP_WebDAV_Server // dummy entry for PHPDoc } */ + // }}} + + // {{{ LOCK() + + /** + * ACL implementation + * + * ACL implementation + * + * @abstract + * @param array &$params + * @returns int HTTP-Statuscode + */ + + /* abstract + function ACL() + { + // dummy entry for PHPDoc + } + */ // }}} // }}} @@ -592,13 +611,27 @@ class HTTP_WebDAV_Server } } - // now we generate the reply header ... - $this->http_status("207 Multi-Status"); + // now we generate the reply header ... + if ($propinfo->root['name'] == 'principal-search-property-set') + { + $this->http_status('200 OK'); + } + else + { + $this->http_status('207 Multi-Status'); + } header('Content-Type: text/xml; charset="utf-8"'); // ... and payload echo "\n"; - echo "\n"; + if ($propinfo->root['name'] == 'principal-search-property-set') + { + echo "\n"; + } + else + { + echo "\n"; + } // using an ArrayIterator to prevent foreach from copying the array, // as we cant loop by reference, when an iterator is given in $files['files'] @@ -715,7 +748,7 @@ class HTTP_WebDAV_Server $path = $file['path']; if (!is_string($path) || $path==="") continue; - echo " \n"; + if ($propinfo->root['name'] != 'principal-search-property-set') echo " \n"; /* TODO right now the user implementation has to make sure collections end in a slash, this should be done in here @@ -723,13 +756,15 @@ class HTTP_WebDAV_Server // path needs to be urlencoded (only basic version of this class!) $href = $this->_urlencode($this->_mergePathes($this->base_uri, $path)); - echo " $href\n"; + if ($propinfo->root['name'] != 'principal-search-property-set') echo " $href\n"; // report all found properties and their values (if any) if (isset($file["props"]) && is_array($file["props"])) { - echo " \n"; - echo " \n"; - + if ($propinfo->root['name'] != 'principal-search-property-set') + { + echo " \n"; + echo " \n"; + } foreach ($file["props"] as &$prop) { if (!is_array($prop)) continue; @@ -757,8 +792,40 @@ class HTTP_WebDAV_Server . gmdate("D, d M Y H:i:s ", $prop['val']) . "GMT\n"; break; + /* @Todo: breaks CalDAV - 2010/03/01 jlehrke + case "resourcetype": + if (!is_array($prop['val'])) { + echo ' \n"; + } else { // multiple resourcetypes from different namespaces as required by GroupDAV + $vals = $extra_ns = ''; + foreach($prop['val'] as $subprop) + { + if ($subprop['ns'] && $subprop['ns'] != 'DAV:') { + // register property namespace if not known yet + if (!isset($ns_hash[$subprop['ns']])) { + $ns_name = "ns".(count($ns_hash) + 1); + $ns_hash[$subprop['ns']] = $ns_name; + } else { + $ns_name = $ns_hash[$subprop['ns']]; + } + if (strchr($extra_ns,$extra=' xmlns:'.$ns_name.'="'.$subprop['ns'].'"') === false) { + $extra_ns .= $extra; + } + $ns_name .= ':'; + } elseif ($subprop['ns'] == 'DAV:') { + $ns_name = 'D:'; + } else { + $ns_name = ''; + } + $vals .= "<$ns_name$subprop[val]/>"; + } + echo " $vals\n"; + //error_log("resourcetype: $vals"); + } + break; + */ case "supportedlock": - echo " $prop[val]\n"; + echo " $prop[val]\n"; break; case "lockdiscovery": echo " \n"; @@ -768,7 +835,7 @@ class HTTP_WebDAV_Server default: echo " ". (is_array($prop['val']) ? - $this->_hierarchical_prop_encode($prop['val']) : + $this->_hierarchical_prop_encode($prop['val']) : $this->_prop_encode(htmlspecialchars($prop['val']))). "\n"; break; @@ -829,10 +896,12 @@ class HTTP_WebDAV_Server } } } - - echo " \n"; - echo " HTTP/1.1 200 OK\n"; - echo " \n"; + if ($propinfo->root['name'] != 'principal-search-property-set') + { + echo " \n"; + echo " HTTP/1.1 200 OK\n"; + echo " \n"; + } } // now report all properties requested but not found @@ -855,10 +924,18 @@ class HTTP_WebDAV_Server echo " \n"; } - echo " \n"; + if ($propinfo->root['name'] != 'principal-search-property-set') echo " \n"; + } + + if ($propinfo->root['name'] == 'principal-search-property-set') + { + echo "\n"; + } + else + { + echo "\n"; } - echo "\n"; } @@ -1550,6 +1627,49 @@ class HTTP_WebDAV_Server // }}} + // {{{ http_UNLOCK() + + /** + * ACL method handler + * + * @param void + * @return void + */ + function http_ACL() + { + $options = Array(); + $options['path'] = $this->path; + $options['errors'] = array(); + + if (isset($this->_SERVER['HTTP_DEPTH'])) { + $options['depth'] = $this->_SERVER['HTTP_DEPTH']; + } else { + $options['depth'] = 'infinity'; + } + + // call user method + $status = $this->ACL($options); + + // now we generate the reply header ... + $this->http_status($status); + $content = ''; + + if (is_array($options['errors']) && count($options['errors'])) { + header('Content-Type: text/xml; charset="utf-8"'); + // ... and payload + $content .= "\n"; + $content .= " \n"; + foreach ($options['errors'] as $violation) { + $content .= "\n"; + } + $content .= "\n"; + } + header("Content-length: ".$this->bytes($content)); + if ($content) echo $options['content']; + } + + // }}} + // }}} // {{{ _copymove() @@ -2087,7 +2207,7 @@ class HTTP_WebDAV_Server /** * Encode a hierarchical properties like: - * + * * * * @@ -2100,7 +2220,7 @@ class HTTP_WebDAV_Server * * * - * + * * @param array $props * @return string */ @@ -2108,14 +2228,14 @@ class HTTP_WebDAV_Server { //error_log(__METHOD__.'('.array2string($props).')'); if (isset($props['name'])) $props = array($props); - + $ret = ''; foreach($props as $prop) { $ret .= '<'.$prop['name']. ($prop['ns'] != 'DAV:' ? ' xmlns="'.$prop['ns'].'"' : ''). (empty($prop['val']) ? ' />' : '>'. - (is_array($prop['val']) ? + (is_array($prop['val']) ? $this->_hierarchical_prop_encode($prop['val']) : $this->_prop_encode($prop['val'])). ''); diff --git a/infolog/inc/class.infolog_bo.inc.php b/infolog/inc/class.infolog_bo.inc.php index de4f9e931c..097ca0d434 100644 --- a/infolog/inc/class.infolog_bo.inc.php +++ b/infolog/inc/class.infolog_bo.inc.php @@ -837,7 +837,8 @@ class infolog_bo function &search(&$query) { //echo "

boinfolog::search(".print_r($query,True).")

\n"; - if (!empty($query['start'])) + if (!empty($query['start']) && + (!isset($query['date_format']) || $query['date_format'] != 'server')) { $query['start'] -= $this->tz_offset_s; } @@ -864,7 +865,8 @@ class infolog_bo date('Y', $data['info_enddate'])); } - if ($this->tz_offset_s) + if ($this->tz_offset_s && + (!isset($query['date_format']) || $query['date_format'] != 'server')) { // convert system- to user-time foreach ($this->timestamps as $time) @@ -875,9 +877,14 @@ class infolog_bo $data[$time] += $this->tz_offset_s; } } + // pre-cache title and file access + self::set_link_cache($data); + } + elseif (!$this->tz_offset_s) + { + // pre-cache title and file access + self::set_link_cache($data); } - // pre-cache title and file access - self::set_link_cache($data); } } //echo "

boinfolog::search(".print_r($query,True).")=

".print_r($ret,True)."
\n"; diff --git a/infolog/inc/class.infolog_groupdav.inc.php b/infolog/inc/class.infolog_groupdav.inc.php index 528e7bbba2..206bd0acef 100644 --- a/infolog/inc/class.infolog_groupdav.inc.php +++ b/infolog/inc/class.infolog_groupdav.inc.php @@ -29,10 +29,11 @@ class infolog_groupdav extends groupdav_handler * @param string $app 'calendar', 'addressbook' or 'infolog' * @param int $debug=null debug-level to set * @param string $base_uri=null base url of handler + * @param string $principalURL=null pricipal url of handler */ - function __construct($app,$debug=null,$base_uri=null) + function __construct($app,$debug=null,$base_uri=null,$principalURL=null) { - parent::__construct($app,$debug,$base_uri); + parent::__construct($app,$debug,$base_uri,$principalURL); $this->bo = new infolog_bo(); } @@ -72,6 +73,42 @@ class infolog_groupdav extends groupdav_handler { $starttime = microtime(true); + if ($options['filters']) + { + + foreach($options['filters'] as $filter) + { + switch($filter['name']) + { + case 'comp-filter': + if ($this->debug > 1) error_log(__METHOD__."($options[path],...) comp-filter='{$filter['attrs']['name']}'"); + + switch($filter['attrs']['name']) + { + case 'VCALENDAR': + continue; + case 'VTODO': + break 3; + default: // We don't handle this + return false; + } + } + } + } + + // check if we have to return the full calendar data or just the etag's + if (!($calendar_data = $options['props'] == 'all' && $options['root']['ns'] == groupdav::CALDAV) && is_array($options['props'])) + { + foreach($options['props'] as $prop) + { + if ($prop['name'] == 'calendar-data') + { + $calendar_data = true; + break; + } + } + } + // todo add a filter to limit how far back entries from the past get synced $filter = array( 'info_type' => 'task', @@ -84,21 +121,35 @@ class infolog_groupdav extends groupdav_handler 'sort' => 'DESC', 'filter' => 'own', // filter my: entries user is responsible for, // filter own: entries the user own or is responsible for + 'date_format' => 'server', 'col_filter' => $filter, )))) { foreach($tasks as &$task) { + $props = array( + HTTP_WebDAV_Server::mkprop('getetag',$this->get_etag($task)), + HTTP_WebDAV_Server::mkprop('getcontenttype',$this->agent != 'kde' ? + 'text/calendar; charset=utf-8; component=VTODO' : 'text/calendar'), // Konqueror (3.5) dont understand it otherwise + // getlastmodified and getcontentlength are required by WebDAV and Cadaver eg. reports 404 Not found if not set + HTTP_WebDAV_Server::mkprop('getlastmodified', $task['info_datemodified']), + HTTP_WebDAV_Server::mkprop('resourcetype',''), // DAVKit requires that attribute! + HTTP_WebDAV_Server::mkprop('getcontentlength',''), + ); + if ($calendar_data) + { + $handler = $this->_get_handler(); + $content = $handler->exportVTODO($task,'2.0','PUBLISH'); + $props[] = HTTP_WebDAV_Server::mkprop('getcontentlength',bytes($content)); + $props[] = HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'calendar-data',$content); + } + else + { + $props[] = HTTP_WebDAV_Server::mkprop('getcontentlength', ''); // expensive to calculate and no CalDAV client uses it + } $files['files'][] = array( 'path' => self::get_path($task), - 'props' => array( - HTTP_WebDAV_Server::mkprop('getetag',$this->get_etag($task)), - HTTP_WebDAV_Server::mkprop('getcontenttype',$this->agent != 'kde' ? - 'text/calendar; charset=utf-8; component=VTODO' : 'text/calendar'), // Konqueror (3.5) dont understand it otherwise - // getlastmodified and getcontentlength are required by WebDAV and Cadaver eg. reports 404 Not found if not set - HTTP_WebDAV_Server::mkprop('getlastmodified', $task['info_datemodified']), - HTTP_WebDAV_Server::mkprop('getcontentlength',''), - ), + 'props' => $props, ); } } @@ -120,7 +171,7 @@ class infolog_groupdav extends groupdav_handler return $task; } $handler = $this->_get_handler(); - $options['data'] = $handler->exportVTODO($id,'2.0',false,false); // keep UID the client set and no extra charset attributes + $options['data'] = $handler->exportVTODO($id,'2.0','PUBLISH'); $options['mimetype'] = 'text/calendar; charset=utf-8'; header('Content-Encoding: identity'); header('ETag: '.$this->get_etag($task)); @@ -215,6 +266,36 @@ class infolog_groupdav extends groupdav_handler return '"'.$info['info_id'].':'.$info['info_datemodified'].'"'; } + /** + * Add extra properties for calendar collections + * + * @param array $props=array() regular props by the groupdav handler + * @param string $base_uri=null base url of handler + * @return array + */ + static function extra_properties(array $props=array(), $base_uri=null) + { + // calendar description + $displayname = $GLOBALS['egw']->translation->convert(lang('Tasks of') . ' ' . + $GLOBALS['egw_info']['user']['account_fullname'], + $GLOBALS['egw']->translation->charset(),'utf-8'); + $props[] = HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'calendar-description',$displayname); + // email of the current user, see caldav-sheduling draft + $props[] = HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'calendar-user-address-set','MAILTO:'.$GLOBALS['egw_info']['user']['email']); + // supported components, currently only VEVENT + $props[] = HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'supported-calendar-component-set',array( + // HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'comp',array('name' => 'VEVENT')), + HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'comp',array('name' => 'VTODO')), + )); + /* + $props[] = HTTP_WebDAV_Server::mkprop('supported-report-set',array( + HTTP_WebDAV_Server::mkprop('supported-report',array( + HTTP_WebDAV_Server::mkprop('report', + HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'calendar-multiget')))))); + */ + return $props; + } + /** * Get the handler and set the supported fields * diff --git a/infolog/inc/class.infolog_ical.inc.php b/infolog/inc/class.infolog_ical.inc.php index be2486e5e7..b169b996d9 100644 --- a/infolog/inc/class.infolog_ical.inc.php +++ b/infolog/inc/class.infolog_ical.inc.php @@ -111,12 +111,12 @@ class infolog_ical extends infolog_bo /** * Exports one InfoLog tast to an iCalendar VTODO * - * @param int $_taskID info_id + * @param int|array $task infolog_id or infolog-tasks data * @param string $_version='2.0' could be '1.0' too * @param string $_method='PUBLISH' * @return string/boolean string with vCal or false on error (eg. no permission to read the event) */ - function exportVTODO($_taskID, $_version='2.0',$_method='PUBLISH') + function exportVTODO($task, $_version='2.0',$_method='PUBLISH') { if ($this->useServerTZ) { @@ -126,7 +126,14 @@ class infolog_ical extends infolog_bo { $date_format = 'ts'; } - if (!($taskData = $this->read($_taskID, true, $date_format))) return false; + if (is_array($task)) + { + $taskData = $task; + } + else + { + if (!($taskData = $this->read($task, true, $date_format))) return false; + } if ($taskData['info_id_parent']) { diff --git a/phpgwapi/inc/class.groupdav.inc.php b/phpgwapi/inc/class.groupdav.inc.php index 20a9896cb7..1d0b6e989d 100644 --- a/phpgwapi/inc/class.groupdav.inc.php +++ b/phpgwapi/inc/class.groupdav.inc.php @@ -70,7 +70,7 @@ class groupdav extends HTTP_WebDAV_Server 'component-set' => array(self::GROUPDAV => 'VCARD'), ), 'infolog' => array( - 'resourcetype' => array(self::GROUPDAV => 'vtodo-collection'), + 'resourcetype' => array(self::GROUPDAV => 'vtodo-collection', self::CALDAV => 'calendar'), 'component-set' => array(self::GROUPDAV => 'VTODO'), ), ); @@ -101,6 +101,13 @@ class groupdav extends HTTP_WebDAV_Server * @var groupdav_handler */ var $handler; + /** + * principal URL + * + * @var string + */ + var $principalURL; + function __construct() { @@ -120,6 +127,15 @@ class groupdav extends HTTP_WebDAV_Server $this->translation =& $GLOBALS['egw']->translation; $this->egw_charset = $this->translation->charset(); + if (strpos($this->base_uri, 'http') === 0) + { + $this->principalURL = $this->_slashify($this->base_uri); + } + else + { + $this->principalURL = (@$_SERVER["HTTPS"] === "on" ? "https:" : "http:") . + '//' . $_SERVER['HTTP_HOST'] . $_SERVER['SCRIPT_NAME'] . '/'; + } } /** @@ -130,7 +146,7 @@ class groupdav extends HTTP_WebDAV_Server */ function app_handler($app) { - return groupdav_handler::app_handler($app,$this->debug,$this->base_uri); + return groupdav_handler::app_handler($app,$this->debug,$this->base_uri,$this->principalURL); } /** @@ -146,6 +162,7 @@ class groupdav extends HTTP_WebDAV_Server switch($app) { case 'calendar': + case 'infolog': $dav[] = 'calendar-access'; break; case 'addressbook': @@ -178,15 +195,26 @@ class groupdav extends HTTP_WebDAV_Server if (!$app) // root folder containing apps { + if (empty($user_prefix)) + { + $displayname = 'EGroupware (Cal|Card|Group)DAV server'; + } + else + { + $displayname = $this->translation->convert($GLOBALS['egw_info']['user']['account_fullname'],$this->egw_charset,'utf-8'); + } // self url $files['files'][] = array( 'path' => $user_prefix.'/', 'props' => array( - self::mkprop('displayname','EGroupware (Cal|Card|Group)DAV server'), + self::mkprop('displayname',$displayname), self::mkprop('resourcetype','collection'), // adding the calendar extra property (calendar-home-set, etc.) here, allows apple iCal to "autodetect" the URL - self::mkprop(groupdav::CALDAV,'calendar-home-set',$this->base_uri.'/calendar/'), - self::mkprop('current-user-principal',array(self::mkprop('href',$this->base_uri.'/principals/'.$GLOBALS['egw_info']['user']['account_lid'].'/'))), + self::mkprop(groupdav::CALDAV,'calendar-home-set',array(self::mkprop('href',$this->base_uri.'/calendar/'))), + self::mkprop('current-user-principal',array(self::mkprop('href',$this->principalURL))), + self::mkprop(groupdav::CALDAV,'calendar-user-address-set','MAILTO:'.$GLOBALS['egw_info']['user']['email']), + //self::mkprop('principal-URL',array(self::mkprop('href',$this->principalURL))), + //self::mkprop('principal-collection-set',array(self::mkprop('href',$this->base_uri.'/principals/'))), ), ); if ($options['depth']) @@ -197,18 +225,22 @@ class groupdav extends HTTP_WebDAV_Server $files['files'][] = array( 'path' => '/principals/', 'props' => array( - self::mkprop('displayname',lang('Accounts')), - self::mkprop('resourcetype','collection'), - self::mkprop('current-user-principal',array(self::mkprop('href',$this->base_uri.'/principals/'.$GLOBALS['egw_info']['user']['account_lid'].'/'))), - ), - ); + self::mkprop('displayname',lang('Accounts')), + self::mkprop('resourcetype','collection'), + self::mkprop('current-user-principal',array(self::mkprop('href',$this->principalURL))), + self::mkprop(groupdav::CALDAV,'calendar-home-set',array(self::mkprop('href',$this->base_uri.'/calendar/'))), + //self::mkprop('principal-URL',array(self::mkprop('href',$this->principalURL))), + ), + ); // groups collection $files['files'][] = array( 'path' => '/groups/', 'props' => array( self::mkprop('displayname',lang('Groups')), self::mkprop('resourcetype','collection'), - self::mkprop('current-user-principal',array(self::mkprop('href',$this->base_uri.'/principals/'.$GLOBALS['egw_info']['user']['account_lid'].'/'))), + self::mkprop('current-user-principal',array(self::mkprop('href',$this->principalURL))), + self::mkprop(groupdav::CALDAV,'calendar-home-set',array(self::mkprop('href',$this->base_uri.'/calendar/'))), + //self::mkprop('principal-URL',array(self::mkprop('href',$this->principalURL))), ), ); } @@ -239,7 +271,7 @@ class groupdav extends HTTP_WebDAV_Server 'props' => $this->_properties($app,$app=='addressbook'&&strpos($_SERVER['HTTP_USER_AGENT'],'KHTML') !== false), ); } - if (!$options['depth'] && !$id) + if (isset($options['depth']) && !$options['depth'] && !$id) { // add ctag if handler implements it (only for depth 0) if (method_exists($handler,'getctag')) @@ -265,11 +297,26 @@ class groupdav extends HTTP_WebDAV_Server function _properties($app,$no_extra_types=false,$user=null) { if (!$user) $user = $GLOBALS['egw_info']['user']['account_fullname']; - + $displayname = $this->translation->convert($GLOBALS['egw_info']['user']['account_fullname'],$this->egw_charset,'utf-8'); $props = array( - self::mkprop('displayname',$this->translation->convert(lang($app).' '.common::grab_owner_name($user),$this->egw_charset,'utf-8')), - self::mkprop('current-user-principal',array(self::mkprop('href',$this->base_uri.'/principals/'.$GLOBALS['egw_info']['user']['account_lid'].'/'))), - ); + self::mkprop('current-user-principal',array(self::mkprop('href',$this->principalURL))), + self::mkprop('owner',$displayname), + //self::mkprop('principal-URL',array(self::mkprop('href',$this->principalURL))), + //self::mkprop('principal-collection-set',array(self::mkprop('href',$this->base_uri.'/principals/'))), + ); + + switch ($app) + { + case 'calendar': + $props[] = self::mkprop(groupdav::CALDAV,'calendar-home-set',array(self::mkprop('href',$this->base_uri.'/calendar/'))); + break; + case 'infolog': + $props[] = self::mkprop(groupdav::CALDAV,'calendar-home-set',array(self::mkprop('href',$this->base_uri.'/infolog/'))); + default: + $displayname = $this->translation->convert(lang($app).' '.common::grab_owner_name($user),$this->egw_charset,'utf-8'); + } + $props[] = self::mkprop('displayname',$displayname); + foreach((array)$this->root[$app] as $prop => $values) { if ($prop == 'resourcetype') @@ -296,7 +343,7 @@ class groupdav extends HTTP_WebDAV_Server } if (method_exists($app.'_groupdav','extra_properties')) { - $props = ExecMethod($app.'_groupdav::extra_properties',$props); + $props = ExecMethod2($app.'_groupdav::extra_properties',$props,$this->base_uri); } return $props; } @@ -664,6 +711,34 @@ class groupdav extends HTTP_WebDAV_Server return egw_vfs::checkLock($path); } + /** + * ACL method handler + * + * @param array general parameter passing array + * @return string HTTP status + */ + function ACL(&$options) + { + self::_parse_path($options['path'],$id,$app,$user); + + if ($this->debug) error_log(__METHOD__.'('.array2string($options).") path=$path"); + + $options['errors'] = array(); + switch ($app) + { + case 'calendar': + case 'addressbook': + case 'infolog': + $status = '200 OK'; // grant all + break; + default: + $options['errors'][] = 'no-inherited-ace-conflict'; + $status = '403 Forbidden'; + } + + return $status; + } + /** * Parse a path into it's id, app and user parts * diff --git a/phpgwapi/inc/class.groupdav_groups.inc.php b/phpgwapi/inc/class.groupdav_groups.inc.php index ae628587b4..648f428126 100644 --- a/phpgwapi/inc/class.groupdav_groups.inc.php +++ b/phpgwapi/inc/class.groupdav_groups.inc.php @@ -29,10 +29,11 @@ class groupdav_groups extends groupdav_handler * @param string $app 'calendar', 'addressbook' or 'infolog' * @param int $debug=null debug-level to set * @param string $base_uri=null base url of handler + * @param string $principalURL=null pricipal url of handler */ - function __construct($app,$debug=null,$base_uri=null) + function __construct($app,$debug=null,$base_uri=null,$principalURL=null) { - parent::__construct($app,$debug,$base_uri); + parent::__construct($app,$debug,$base_uri,$principalURL); $this->accounts = $GLOBALS['egw']->accounts; } @@ -56,8 +57,8 @@ class groupdav_groups extends groupdav_handler HTTP_WebDAV_Server::mkprop('getetag',$this->get_etag($account)), HTTP_WebDAV_Server::mkprop('resourcetype','principal'), HTTP_WebDAV_Server::mkprop('alternate-URI-set',''), - HTTP_WebDAV_Server::mkprop('principal-URL',$this->base_uri.'/groups/'.$account['account_lid']), - HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'calendar-home-set',$this->base_uri.'/calendar/'), + HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'calendar-home-set',$this->base_uri.'/'.$account['account_lid'].'/calendar/'), + //HTTP_WebDAV_Server::mkprop('principal-URL',array(self::mkprop('href',$this->principalURL))), ); foreach($this->accounts->members($account['account_id']) as $uid => $user) { diff --git a/phpgwapi/inc/class.groupdav_handler.inc.php b/phpgwapi/inc/class.groupdav_handler.inc.php index 1bbadffd79..da40122531 100644 --- a/phpgwapi/inc/class.groupdav_handler.inc.php +++ b/phpgwapi/inc/class.groupdav_handler.inc.php @@ -59,6 +59,12 @@ abstract class groupdav_handler * @var string */ var $base_uri; + /** + * principal URL + * + * @var string + */ + var $principalURL; /** * HTTP_IF_MATCH / etag of current request / last call to _common_get_put_delete() method * @@ -78,13 +84,24 @@ abstract class groupdav_handler * @param string $app 'calendar', 'addressbook' or 'infolog' * @param int $debug=null debug-level to set * @param string $base_uri=null base url of handler + * @param string $principalURL=null pricipal url of handler */ - function __construct($app,$debug=null,$base_uri=null) + function __construct($app,$debug=null,$base_uri=null,$principalURL=null) { //error_log(__METHOD__." called"); $this->app = $app; - #if (!is_null($debug)) $this->debug = $debug = 3; + if (!is_null($debug)) $this->debug = $debug; $this->base_uri = is_null($base_uri) ? $base_uri : $_SERVER['SCRIPT_NAME']; + if (is_null($principalURL)) + { + $this->principalURL = (@$_SERVER["HTTPS"] === "on" ? "https:" : "http:") . + '//'.$_SERVER['HTTP_HOST'] . $_SERVER['SCRIPT_NAME'] . '/'; + } + else + { + $this->principalURL = $principalURL; + } + $this->agent = self::get_agent(); $this->translation =& $GLOBALS['egw']->translation; @@ -161,9 +178,10 @@ abstract class groupdav_handler * Add extra properties for collections * * @param array $props=array() regular props by the groupdav handler + * @param string $base_uri=null base url of handler * @return array */ - static function extra_properties(array $props=array()) + static function extra_properties(array $props=array(), $base_uri=null) { return $props; } @@ -262,9 +280,10 @@ abstract class groupdav_handler * @param string $app 'calendar', 'addressbook' or 'infolog' * @param int $debug=null debug-level to set * @param string $base_uri=null base url of handler + * @param string $principalURL=null pricipal url of handler * @return groupdav_handler */ - static function &app_handler($app,$debug=null,$base_uri=null) + static function &app_handler($app,$debug=null,$base_uri=null,$principalURL=null) { static $handler_cache = array(); @@ -273,8 +292,12 @@ abstract class groupdav_handler $class = $app.'_groupdav'; if (!class_exists($class) && !class_exists($class = 'groupdav_'.$app)) return null; - $handler_cache[$app] = new $class($app,$debug,$base_uri); + $handler_cache[$app] = new $class($app,$debug,$base_uri,$principalURL); } + $handler_cache[$app]->$debug = $debug; + $handler_cache[$app]->$base_uri = $base_uri; + $handler_cache[$app]->$principalURL = $principalURL; + if ($debug) error_log(__METHOD__."($app, $base_uri, $principalURL)"); return $handler_cache[$app]; } diff --git a/phpgwapi/inc/class.groupdav_principals.inc.php b/phpgwapi/inc/class.groupdav_principals.inc.php index a11e7c3de8..61d49e426b 100644 --- a/phpgwapi/inc/class.groupdav_principals.inc.php +++ b/phpgwapi/inc/class.groupdav_principals.inc.php @@ -29,10 +29,11 @@ class groupdav_principals extends groupdav_handler * @param string $app 'calendar', 'addressbook' or 'infolog' * @param int $debug=null debug-level to set * @param string $base_uri=null base url of handler + * @param string $principalURL=null pricipal url of handler */ - function __construct($app,$debug=null,$base_uri=null) + function __construct($app,$debug=null,$base_uri=null,$principalURL=null) { - parent::__construct($app,$debug,$base_uri); + parent::__construct($app,$debug,$base_uri,$principalURL); $this->accounts = $GLOBALS['egw']->accounts; } @@ -48,25 +49,43 @@ class groupdav_principals extends groupdav_handler */ function propfind($path,$options,&$files,$user) { + if ($this->debug) error_log(__METHOD__."($path,".array2string($options).",,$user,$id)"); + list(,,$id) = explode('/',$path); + if ($id && !($id = $this->accounts->id2name($id))) { return false; } foreach($id ? array($this->accounts->read($id)) : $this->accounts->search(array('type' => 'accounts')) as $account) { - $props = array( - HTTP_WebDAV_Server::mkprop('displayname',trim($account['account_firstname'].' '.$account['account_lastname'])), - HTTP_WebDAV_Server::mkprop('getetag',$this->get_etag($account)), - HTTP_WebDAV_Server::mkprop('resourcetype','principal'), - HTTP_WebDAV_Server::mkprop('alternate-URI-set',''), - HTTP_WebDAV_Server::mkprop('principal-URL',$_SERVER['SCRIPT_NAME'].'/principals/'.$account['account_lid']), - HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'calendar-home-set',$_SERVER['SCRIPT_NAME'].'/'), - HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'calendar-user-address-set','MAILTO:'.$account['account_email']), - ); - foreach($this->accounts->memberships($account['account_id']) as $gid => $group) + $displayname = $GLOBALS['egw']->translation->convert($account['account_fullname'], + $GLOBALS['egw']->translation->charset(),'utf-8'); + if ($options['root']['name'] == 'principal-search-property-set') { - $props[] = HTTP_WebDAV_Server::mkprop('group-membership',$_SERVER['SCRIPT_NAME'].'/groups/'.$group); + $props = array(HTTP_WebDAV_Server::mkprop('principal-search-property', + array(HTTP_WebDAV_Server::mkprop('prop', + array(HTTP_WebDAV_Server::mkprop('displayname',$displayname)) + ), + HTTP_WebDAV_Server::mkprop('description', 'Full name'))) + ); + } + else + { + $props = array( + HTTP_WebDAV_Server::mkprop('displayname',$displayname), + HTTP_WebDAV_Server::mkprop('getetag',$this->get_etag($account)), + HTTP_WebDAV_Server::mkprop('resourcetype','principal'), + HTTP_WebDAV_Server::mkprop('alternate-URI-set',''), + HTTP_WebDAV_Server::mkprop('current-user-principal',array(HTTP_WebDAV_Server::mkprop('href',$this->principalURL))), + HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'calendar-home-set',array(HTTP_WebDAV_Server::mkprop('href',$this->base_uri.'/'.$account['account_lid'].'/calendar/'))), + HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'calendar-user-address-set','MAILTO:'.$account['account_email']), + //HTTP_WebDAV_Server::mkprop('principal-URL',array(HTTP_WebDAV_Server::mkprop('href',$this->principalURL))), + ); + foreach($this->accounts->memberships($account['account_id']) as $gid => $group) + { + $props[] = HTTP_WebDAV_Server::mkprop('group-membership',$this->base_uri.'/groups/'.$group); + } } $files['files'][] = array( 'path' => '/principals/'.$account['account_lid'], @@ -90,9 +109,12 @@ class groupdav_principals extends groupdav_handler { return $account; } + $displayname = $GLOBALS['egw']->translation->convert( + trim($account['account_firstname'].' '.$account['account_lastname']), + $GLOBALS['egw']->translation->charset(),'utf-8'); $options['data'] = 'Principal: '.$account['account_lid']. - "\nURL: ".$_SERVER['SCRIPT_NAME'].$options['path']. - "\nName: ".$account['account_firstname'].' '.$account['account_lastname']. + "\nURL: ".$this->base_uri.$options['path']. + "\nName: ".$displayname. "\nEmail: ".$account['account_email']. "\nMemberships: ".implode(', ',$this->accounts->memberships($id))."\n"; $options['mimetype'] = 'text/plain; charset=utf-8';