From f187b1e31f74fb0e913a1ceaae30e9fef259417d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Lehrke?= Date: Sun, 7 Mar 2010 12:23:19 +0000 Subject: [PATCH] New GroupDAV base classes --- egw-pear/HTTP/WebDAV/Server.php | 428 +++++++++++++----- phpgwapi/inc/class.groupdav.inc.php | 308 ++++++++++--- phpgwapi/inc/class.groupdav_groups.inc.php | 169 ------- phpgwapi/inc/class.groupdav_handler.inc.php | 93 +++- .../inc/class.groupdav_principals.inc.php | 319 ++++++++++++- phpgwapi/inc/class.vfs_webdav_server.inc.php | 5 +- 6 files changed, 935 insertions(+), 387 deletions(-) delete mode 100644 phpgwapi/inc/class.groupdav_groups.inc.php diff --git a/egw-pear/HTTP/WebDAV/Server.php b/egw-pear/HTTP/WebDAV/Server.php index 94dd2170a6..c5beebe4ac 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 * @@ -60,6 +59,18 @@ class HTTP_WebDAV_Server */ var $client_require_href_as_url; + /** + * Set if client requires does not allow namespace redundacy. + * The XML Namespace specification does allow both + * But some clients can NOT deal with one or the other! + * + * @var boolean (client_refuses_redundand_namespace_declarations) + */ + var $crrnd = false; + + /** + + /** * URI path for this request * @@ -230,11 +241,18 @@ class HTTP_WebDAV_Server $this->$wrapper(); // call method by name } else { // method not found/implemented if ($this->_SERVER["REQUEST_METHOD"] == "LOCK") { - $this->http_status("412 Precondition failed"); + $error = '412 Precondition failed'; + ; } else { - $this->http_status("405 Method not allowed"); + $error = '405 Method not allowed'; header("Allow: ".join(", ", $this->_allow())); // tell client what's allowed } + $this->http_status($error); + echo "Error $error\n"; + echo "

$error

\n"; + echo "The requested could not by handled by this server.\n"; + echo '(URI ' . $this->_SERVER['REQUEST_URI'] . ")
\n
\n"; + echo "\n"; } } @@ -433,6 +451,26 @@ class HTTP_WebDAV_Server */ // }}} + // {{{ ACL() + + /** + * ACL implementation + * + * ACL implementation + * + * @abstract + * @param array &$params + * @returns int HTTP-Statuscode + */ + + /* abstract + function ACL() + { + // dummy entry for PHPDoc + } + */ + // }}} + // }}} // {{{ other abstract methods @@ -566,7 +604,7 @@ class HTTP_WebDAV_Server $options['other'] = $propinfo->other; // call user handler - if (!$this->$handler($options, $files)) { + if (!($retval =$this->$handler($options, $files))) { $files = array("files" => array()); if (method_exists($this, "checkLock")) { // is locked? @@ -593,12 +631,42 @@ class HTTP_WebDAV_Server } // now we generate the reply header ... - $this->http_status("207 Multi-Status"); + if ($retval === true) + { + $this->http_status('207 Multi-Status'); + } + else + { + $this->http_status($retval); + header('Content-Type: text/html'); + echo "Error $retval\n"; + echo "

$retval

\n"; + switch (substr($retval, 0 ,3)) + { + case '501': // Not Implemented + echo "The requested feature is not (yet) supported by this server.\n"; + break; + default: + echo "The request could not be handled by this server.\n"; + } + echo '(URI ' . $this->_SERVER['REQUEST_URI'] . ")
\n
\n"; + echo "\n"; + return; + } + // dav header + $dav = array(1); // assume we are always dav class 1 compliant + $allow = false; + + // allow extending class to modify DAV + if (method_exists($this,'OPTIONS')) { + $this->OPTIONS($this->path,$dav,$allow); + } + header("DAV: " .join(", ", $dav)); header('Content-Type: text/xml; charset="utf-8"'); // ... and payload echo "\n"; - echo "\n"; + echo ($this->crrnd?'<':'\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 +783,14 @@ class HTTP_WebDAV_Server $path = $file['path']; if (!is_string($path) || $path==="") continue; - echo " \n"; + if ($this->crrnd) + { + echo " \n"; + } + else + { + echo " \n"; + } /* TODO right now the user implementation has to make sure collections end in a slash, this should be done in here @@ -723,12 +798,19 @@ 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 ($this->crrnd) + { + echo " $href\n"; + } + else + { + echo " $href\n"; + } // report all found properties and their values (if any) if (isset($file["props"]) && is_array($file["props"])) { - echo " \n"; - echo " \n"; + echo ' <'.($this->crrnd?'':'D:')."propstat>\n"; + echo ' <'.($this->crrnd?'':'D:')."prop>\n"; foreach ($file["props"] as &$prop) { @@ -738,7 +820,7 @@ class HTTP_WebDAV_Server if (!isset($prop["val"]) || $prop["val"] === "" || $prop["val"] === false) { // empty properties (cannot use empty() for check as "0" is a legal value here) if ($prop["ns"]=="DAV:") { - echo " \n"; + echo ' <'.($this->crrnd?'':'D:')."$prop[name]/>\n"; } else if (!empty($prop["ns"])) { echo " <".$ns_hash[$prop["ns"]].":$prop[name]/>\n"; } else { @@ -748,29 +830,34 @@ class HTTP_WebDAV_Server // some WebDAV properties need special treatment switch ($prop["name"]) { case "creationdate": - echo " " + echo ' <'.($this->crrnd?'':'D:')."creationdate ns0:dt=\"dateTime.tz\">" . gmdate("Y-m-d\\TH:i:s\\Z", $prop['val']) - . "\n"; + . 'crrnd?'':'D:')."creationdate>\n"; break; case "getlastmodified": - echo " " + echo ' <'.($this->crrnd?'':'D:')."getlastmodified ns0:dt=\"dateTime.rfc1123\">" . gmdate("D, d M Y H:i:s ", $prop['val']) - . "GMT\n"; + . "GMTcrrnd?'':'D:')."getlastmodified>\n"; break; case "supportedlock": - echo " $prop[val]\n"; + echo ' <'.($this->crrnd?'':'D:')."supportedlock>$prop[val]crrnd?'':'D:')."supportedlock>\n"; break; case "lockdiscovery": - echo " \n"; + echo ' <'.($this->crrnd?'':'D:')."lockdiscovery>\n"; echo $prop["val"]; - echo " \n"; + echo ' crrnd?'':'D:')."lockdiscovery>\n"; break; default: - echo " ". - (is_array($prop['val']) ? - $this->_hierarchical_prop_encode($prop['val']) : - $this->_prop_encode(htmlspecialchars($prop['val']))). - "\n"; + if (is_array($prop['val'])) + { + $val = $this->_hierarchical_prop_encode($prop['val']); + } elseif (isset($prop['raw'])) { + $val = $this->_prop_encode(''); + } else { + $val = $this->_prop_encode(htmlspecialchars($prop['val'])); + } + echo ' <'.($this->crrnd?'':'D:')."$prop[name]>$val". + 'crrnd?'':'D:')."$prop[name]>\n"; break; } } else { @@ -783,66 +870,90 @@ class HTTP_WebDAV_Server $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[name]"; - if (is_array($subprop['val'])) // val contains only attributes, no value - { - foreach($subprop['val'] as $attr => $val) - { - $vals .= ' '.$attr.'="'.htmlspecialchars($val).'"'; - } - $vals .= '/>'; - } - else - { - $vals .= '>'.htmlspecialchars($subprop['val']).""; - } - } - echo " <".$ns_hash[$prop['ns']].":$prop[name]$extra_ns>$vals\n"; - } - else - // properties from namespaces != "DAV:" or without any namespace - if ($prop["ns"]) { - echo " <" . $ns_hash[$prop["ns"]] . ":$prop[name]>" - . $this->_prop_encode(htmlspecialchars($prop['val'])) - . "\n"; + 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[name]"; + if (is_array($subprop['val'])) // val contains only attributes, no value + { + foreach($subprop['val'] as $attr => $val) + { + $vals .= ' '.$attr.'="'.htmlspecialchars($val).'"'; + } + $vals .= '/>'; + } + else + { + $vals .= '>'; + if (isset($subprop['raw'])) { + $vals .= ''; + } else { + $vals .= htmlspecialchars($subprop['val']); + } + $vals .= ""; + } + } + echo ' <'.$ns_hash[$prop['ns']].":$prop[name]$extra_ns>$vals\n"; } else { - echo " <$prop[name] xmlns=\"\">" - . $this->_prop_encode(htmlspecialchars($prop['val'])) - . "\n"; + if ($prop['raw']) + { + $val = ''; + } else { + $val = htmlspecialchars($prop['val']); + } + $val = $this->_prop_encode($val); + // properties from namespaces != "DAV:" or without any namespace + if ($prop['ns']) { + if ($this->crrnd) { + echo " <$prop[name] xmlns=".'"'.$prop["ns"].'">' + . $val . "\n"; + } else { + echo " <" . $ns_hash[$prop["ns"]] . ":$prop[name]>" + . $val . '\n"; + } + } else { + echo " <$prop[name] xmlns=\"\">$val\n"; + } } } } - echo " \n"; - echo " HTTP/1.1 200 OK\n"; - echo " \n"; + if ($this->crrnd) + { + echo " \n"; + echo " HTTP/1.1 200 OK\n"; + echo " \n"; + } + else + { + echo " \n"; + echo " HTTP/1.1 200 OK\n"; + echo " \n"; + } } // now report all properties requested but not found if (isset($file["noprops"])) { - echo " \n"; - echo " \n"; + echo ' <'.($this->crrnd?'':'D:')."propstat>\n"; + echo ' <'.($this->crrnd?'':'D:')."prop>\n"; foreach ($file["noprops"] as &$prop) { if ($prop["ns"] == "DAV:") { - echo " \n"; + echo ' <'.($this->crrnd?'':'D:')."$prop[name]/>\n"; } else if ($prop["ns"] == "") { echo " <$prop[name] xmlns=\"\"/>\n"; } else { @@ -850,15 +961,24 @@ class HTTP_WebDAV_Server } } - echo " \n"; - echo " HTTP/1.1 404 Not Found\n"; - echo " \n"; + if ($this->crrnd) + { + echo " \n"; + echo " HTTP/1.1 404 Not Found\n"; + echo " \n"; + } + else + { + echo " \n"; + echo " HTTP/1.1 404 Not Found\n"; + echo " \n"; + } } - echo " \n"; + echo ' crrnd?'':'D:')."response>\n"; } - echo "\n"; + echo 'crrnd?'':'D:')."multistatus>\n"; } @@ -896,24 +1016,24 @@ class HTTP_WebDAV_Server echo "\n"; echo "\n"; - echo " \n"; - echo " ".$this->_urlencode($this->_mergePathes($this->_SERVER["SCRIPT_NAME"], $this->path))."\n"; + echo ' <'.($this->crrnd?'':'D:')."response>\n"; + echo ' <'.($this->crrnd?'':'D:')."href>".$this->_urlencode($this->_mergePathes($this->_SERVER["SCRIPT_NAME"], $this->path)).'crrnd?'':'D:')."href>\n"; foreach ($options["props"] as $prop) { - echo " \n"; - echo " <$prop[name] xmlns=\"$prop[ns]\"/>\n"; - echo " HTTP/1.1 $prop[status]\n"; - echo " \n"; + echo ' <'.($this->crrnd?'':'D:')."propstat>\n"; + echo ' <'.($this->crrnd?'':'D:')."prop><$prop[name] xmlns=\"$prop[ns]\"/>crrnd?'':'D:')."prop>\n"; + echo ' <'.($this->crrnd?'':'D:')."status>HTTP/1.1 $prop[status]crrnd?'':'D:')."status>\n"; + echo ' crrnd?'':'D:')."propstat>\n"; } if ($responsedescr) { - echo " ". + echo ' <'.($this->crrnd?'':'D:')."responsedescription>". $this->_prop_encode(htmlspecialchars($responsedescr)). - "\n"; + 'crrnd?'':'D:')."responsedescription>\n"; } - echo " \n"; - echo "\n"; + echo ' crrnd?'':'D:')."response>\n"; + echo 'crrnd?'':'D:')."multistatus>\n"; } else { $this->http_status("423 Locked"); } @@ -1503,17 +1623,17 @@ class HTTP_WebDAV_Server header("Lock-Token: <$options[locktoken]>"); echo "\n"; echo "\n"; - echo " \n"; - echo " \n"; - echo " \n"; - echo " \n"; - echo " $options[depth]\n"; - echo " $options[owner]\n"; - echo " $timeout\n"; - echo " $options[locktoken]\n"; - echo " \n"; - echo " \n"; - echo "\n\n"; + echo ' <'.($this->crrnd?'':'D:')."lockdiscovery>\n"; + echo ' <'.($this->crrnd?'':'D:')."activelock>\n"; + echo ' <'.($this->crrnd?'':'D:')."lockscope>crrnd?'':'D:')."lockscope>\n"; + echo ' <'.($this->crrnd?'':'D:')."locktype>crrnd?'':'D:')."locktype>\n"; + echo ' <'.($this->crrnd?'':'D:')."depth>$options[depth]crrnd?'':'D:')."depth>\n"; + echo ' <'.($this->crrnd?'':'D:')."owner>$options[owner]crrnd?'':'D:')."owner>\n"; + echo ' <'.($this->crrnd?'':'D:')."timeout>$timeoutcrrnd?'':'D:')."timeout>\n"; + echo ' <'.($this->crrnd?'':'D:')."locktoken>$options[locktoken]crrnd?'':'D:')."locktoken>\n"; + echo ' crrnd?'':'D:')."activelock>\n"; + echo ' crrnd?'':'D:')."lockdiscovery>\n"; + echo 'crrnd?'':'D:')."prop>\n\n"; } } @@ -1550,6 +1670,49 @@ class HTTP_WebDAV_Server // }}} + // {{{ http_ACL() + + /** + * 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 .= '<'.($this->crrnd?'':'D:')."$violation/>\n"; + } + $content .= 'crrnd?'':'D:')."error>\n"; + } + header("Content-length: ".$this->bytes($content)); + if ($content) echo $options['content']; + } + + // }}} + // }}} // {{{ _copymove() @@ -1645,20 +1808,27 @@ class HTTP_WebDAV_Server * @param string XML namespace (optional) * @param string property name * @param string property value + * @praram boolen property raw-flag * @return array property array */ function mkprop() { - $args = func_get_args(); - if (count($args) == 3) { - return array("ns" => $args[0], - "name" => $args[1], - "val" => $args[2]); - } else { - return array("ns" => "DAV:", - "name" => $args[0], - "val" => $args[1]); - } + $args = func_get_args(); + switch (count($args)) { + case 4: + return array('ns' => $args[0], + 'name' => $args[1], + 'val' => $args[2], + 'raw' => true); + case 3: + return array('ns' => $args[0], + 'name' => $args[1], + 'val' => $args[2]); + default: + return array("ns" => "DAV:", + "name" => $args[0], + "val" => $args[1]); + } } // {{{ _check_auth @@ -2005,16 +2175,32 @@ class HTTP_WebDAV_Server } // genreate response block - $activelocks.= " - - - - $lock[depth] - $lock[owner] - $timeout - $lock[token] - - "; + if ($this->crrnd) + { + $activelocks.= " + + <$lock[scope]/> + <$lock[type]/> + $lock[depth] + $lock[owner] + $timeout + $lock[token] + + "; + } + else + { + $activelocks.= " + + + + $lock[depth] + $lock[owner] + $timeout + $lock[token] + + "; + } } // return generated response @@ -2087,7 +2273,7 @@ class HTTP_WebDAV_Server /** * Encode a hierarchical properties like: - * + * * * * @@ -2100,7 +2286,7 @@ class HTTP_WebDAV_Server * * * - * + * * @param array $props * @return string */ @@ -2108,14 +2294,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/phpgwapi/inc/class.groupdav.inc.php b/phpgwapi/inc/class.groupdav.inc.php index f4fa71d256..d92a862288 100644 --- a/phpgwapi/inc/class.groupdav.inc.php +++ b/phpgwapi/inc/class.groupdav.inc.php @@ -36,6 +36,10 @@ require_once('HTTP/WebDAV/Server.php'); */ class groupdav extends HTTP_WebDAV_Server { + /** + * DAV namespace + */ + const DAV = 'DAV:'; /** * GroupDAV namespace */ @@ -70,7 +74,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 +105,19 @@ class groupdav extends HTTP_WebDAV_Server * @var groupdav_handler */ var $handler; + /** + * principal URL + * + * @var string + */ + var $principalURL; + /** + * Reference to the accounts class + * + * @var accounts + */ + var $accounts; + function __construct() { @@ -115,11 +132,24 @@ class groupdav extends HTTP_WebDAV_Server case 'davkit': // iCal app in OS X 10.6 created wrong request, if full url given $this->client_require_href_as_url = false; break; + case 'cfnetwork': + $this->crrnd = true; // Apple Addressbook.app does not cope with namespace redundancy } parent::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'] . '/'; + } + $this->principalURL .= 'principals/users/'.$GLOBALS['egw_info']['user']['account_lid'].'/'; + $this->accounts = $GLOBALS['egw']->accounts; } /** @@ -130,7 +160,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,11 +176,24 @@ class groupdav extends HTTP_WebDAV_Server switch($app) { case 'calendar': + $dav[] = 2; + $dav[] = 'access-control'; $dav[] = 'calendar-access'; + //$dav[] = 'calendar-schedule'; + //$dav[] = 'calendar-proxy'; + //$dav[] = 'calendar-avialibility'; + //$dav[] = 'calendarserver-private-events'; break; case 'addressbook': - $dav[] = 'addressbook'; + $dav[] = 2; + $dav[] = 3; + $dav[] = 'access-control'; + $dav[] = 'addressbook-access'; break; + default: + $dav[] = 2; + $dav[] = 'access-control'; + $dav[] = 'calendar-access'; } // not yet implemented: $dav[] = 'access-control'; } @@ -162,62 +205,101 @@ class groupdav extends HTTP_WebDAV_Server * @param array return array for file properties * @return bool true on success */ - function PROPFIND(&$options, &$files,$method='PROPFIND') + function PROPFIND(&$options, &$files, $method='PROPFIND') { if ($this->debug) error_log(__CLASS__."::$method(".array2string($options,true).')'); + + if (groupdav_handler::get_agent() == 'cfnetwork' && // Apple Addressbook + $options['root']['name'] == 'propfind') + { + foreach ($options['props'] as $props) + { + if ($props['name'] == 'current-user-privilege-set') + { + if ($this->debug > 2) error_log(__CLASS__."::$method: current-user-privilege-set not implemented!"); + return '501 Not Implemented'; + } + } + } // parse path in form [/account_lid]/app[/more] if (!self::_parse_path($options['path'],$id,$app,$user,$user_prefix) && $app && !$user) { - if ($this->debug > 1) error_log(__CLASS__."::$method: user=$user, app=$app, id=$id: 404 not found!"); + 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: user='$user', app='$app', id='$id'"); + + if ($user) + { + $account_lid = $this->accounts->id2name($user); + } + else + { + $account_lid = $GLOBALS['egw_info']['user']['account_lid']; + } + $account = $this->accounts->read($account_lid); + $displayname = $GLOBALS['egw']->translation->convert($account['account_fullname'], + $GLOBALS['egw']->translation->charset(),'utf-8'); $files = array('files' => array()); + $path = $user_prefix = $this->_slashify($user_prefix); - if (!$app) // root folder containing apps + if (!$app) // user root folder containing apps { + if (empty($user_prefix)) + { + $user_prefix = '/'.$GLOBALS['egw_info']['user']['account_lid'].'/'; + } + if ($options['depth']) + { + $displayname = 'EGroupware (Cal|Card|Group)DAV server'; + } // self url - $files['files'][] = array( - 'path' => $user_prefix.'/', - 'props' => array( - self::mkprop('displayname','EGroupware (Cal|Card|Group)DAV server'), - self::mkprop('resourcetype','collection'), + $props = array( + self::mkprop('displayname',$displayname), + self::mkprop('resourcetype',array(self::mkprop('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.$user_prefix.'calendar/'))), + self::mkprop(groupdav::CARDDAV,'addressbook-home-set',array( + self::mkprop('href',$this->base_uri.$user_prefix))), + self::mkprop('current-user-principal',array(self::mkprop('href',$this->principalURL))), + self::mkprop(groupdav::CALDAV,'calendar-user-address-set',array( + self::mkprop('href','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/'))), + ); + //$props = self::current_user_privilege_set($props); + $files['files'][] = array( + 'path' => $path, + 'props' => $props, ); if ($options['depth']) { - if (empty($user_prefix)) + if (strlen($path) == 1) // GroupDAV Root { // principals collection $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'].'/'))), - ), - ); - // 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('displayname',lang('Accounts')), + self::mkprop('resourcetype',array(self::mkprop('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.$user_prefix.'calendar/'))), + self::mkprop(groupdav::CARDDAV,'addressbook-home-set',array( + self::mkprop('href',$this->base_uri.'/'))), + self::mkprop('principal-URL',array(self::mkprop('href',$this->principalURL))), + ), + ); } foreach($this->root as $app => $data) { if (!$GLOBALS['egw_info']['user']['apps'][$app]) continue; // no rights for the given app $files['files'][] = array( - 'path' => $user_prefix.'/'.$app.'/', + 'path' => $path.$app.'/', 'props' => $this->_properties($app,false,$user), ); } @@ -234,12 +316,12 @@ class groupdav extends HTTP_WebDAV_Server if ($method != 'REPORT' && !$id) // no self URL for REPORT requests (only PROPFIND) or propfinds on an id { $files['files'][0] = array( - 'path' => '/'.$app.'/', + 'path' => $path.$app.'/', // KAddressbook doubles the folder, if the self URL contains the GroupDAV/CalDAV resourcetypes - 'props' => $this->_properties($app,$app=='addressbook'&&strpos($_SERVER['HTTP_USER_AGENT'],'KHTML') !== false), + 'props' => $this->_properties($app,$app=='addressbook'&&strpos($_SERVER['HTTP_USER_AGENT'],'KHTML') !== false,$user), ); } - 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')) @@ -249,7 +331,7 @@ class groupdav extends HTTP_WebDAV_Server } return true; // depth 0 --> show only the self url } - return $handler->propfind($options['path'],$options,$files,$user,$id); + return $handler->propfind($this->_slashify($options['path']),$options,$files,$user,$id); } return '501 Not Implemented'; } @@ -264,12 +346,54 @@ class groupdav extends HTTP_WebDAV_Server */ function _properties($app,$no_extra_types=false,$user=null) { - if (!$user) $user = $GLOBALS['egw_info']['user']['account_fullname']; - + if ($this->debug) error_log(__CLASS__."::$method: user='$user', app='$app'"); + if ($user) + { + $account_lid = $this->accounts->id2name($user); + } + else + { + $account_lid = $GLOBALS['egw_info']['user']['account_lid']; + } + $account = $this->accounts->read($account_lid); + $displayname = $GLOBALS['egw']->translation->convert($account['account_fullname'], + $GLOBALS['egw']->translation->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('alternate-URI-set',array( + self::mkprop('href','MAILTO:'.$GLOBALS['egw_info']['user']['email']))), + self::mkprop(groupdav::CALDAV,'calendar-user-address-set',array( + self::mkprop('href','MAILTO:'.$GLOBALS['egw_info']['user']['email']))), + self::mkprop('principal-collection-set',array( + self::mkprop('href',$this->base_uri.'/principals/users/'), + self::mkprop('href',$this->base_uri.'/principals/groups/'), + )), + ); + + switch ($app) + { + case 'calendar': + $props[] = self::mkprop(groupdav::CALDAV,'calendar-home-set',array( + self::mkprop('href',$this->base_uri.'/'.$account_lid.'/calendar/'))); + break; + case 'infolog': + $props[] = self::mkprop(groupdav::CALDAV,'calendar-home-set',array( + self::mkprop('href',$this->base_uri.'/'.$account_lid.'/infolog/'))); + $displayname = $this->translation->convert(lang($app).' '. + common::grab_owner_name($user),$this->egw_charset,'utf-8'); + break; + default: + $props[] = self::mkprop(groupdav::CALDAV,'calendar-home-set',array( + self::mkprop('href',$this->base_uri.'/'.$account_lid.'/calendar/'))); + $displayname = $this->translation->convert(lang($app).' '. + common::grab_owner_name($user),$this->egw_charset,'utf-8'); + } + $props[] = self::mkprop(groupdav::CARDDAV,'addressbook-home-set',array( + self::mkprop('href',$this->base_uri.'/'.$account_lid.'/'))); + $props[] = self::mkprop('displayname',$displayname); + foreach((array)$this->root[$app] as $prop => $values) { if ($prop == 'resourcetype') @@ -296,7 +420,9 @@ class groupdav extends HTTP_WebDAV_Server } if (method_exists($app.'_groupdav','extra_properties')) { - $props = ExecMethod($app.'_groupdav::extra_properties',$props); + $displayname = $GLOBALS['egw']->translation->convert($account['account_fullname'], + $GLOBALS['egw']->translation->charset(),'utf-8'); + $props = ExecMethod2($app.'_groupdav::extra_properties',$props,$displayname,$this->base_uri); } return $props; } @@ -377,10 +503,10 @@ class groupdav extends HTTP_WebDAV_Server echo '

(Cal|Card|Group)DAV '; $path = '/groupdav.php'; - foreach(explode('/',substr($options['path'],0,-1)) as $n => $name) + foreach(explode('/',$this->_unslashify($options['path'])) as $n => $name) { $path .= ($n != 1 ? '/' : '').$name; - echo html::a_href(htmlspecialchars($name.'/'),$path.($n ? '/' : '')); + echo html::a_href(htmlspecialchars($name.'/'),$path); } echo "

\n"; @@ -401,6 +527,7 @@ class groupdav extends HTTP_WebDAV_Server $props = $this->props2array($file['props']); //echo $file['path']; _debug_array($props); $class = $class == 'row_on' ? 'row_off' : 'row_on'; + if (substr($file['path'],-1) == '/') { $name = basename(substr($file['path'],0,-1)).'/'; @@ -409,6 +536,7 @@ class groupdav extends HTTP_WebDAV_Server { $name = basename($file['path']); } + echo "\t\n\t\t$n\n\t\t".html::a_href(htmlspecialchars($name),'/groupdav.php'.$file['path'])."\n"; echo "\t\t".$props['DAV:getcontentlength']."\n"; echo "\t\t".(!empty($props['DAV:getlastmodified']) ? date('Y-m-d H:i:s',$props['DAV:getlastmodified']) : '')."\n"; @@ -497,7 +625,7 @@ class groupdav extends HTTP_WebDAV_Server default: $ns = $prop['ns']; } - $arr[$ns.':'.$prop['name']] = is_array($prop['val']) ? + $arr[$ns.':'.$prop['name']] = is_array($prop['val']) ? $this->_hierarchical_prop_encode($prop['val']) : $prop['val']; } return $arr; @@ -661,6 +789,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 * @@ -673,29 +829,73 @@ class groupdav extends HTTP_WebDAV_Server */ function _parse_path($path,&$id,&$app,&$user,&$user_prefix=null) { - if ($this->debug) error_log(__METHOD__." called with ('$path') id=$id, app='$app', user=$user"); - $parts = explode('/',$path); - - if (count($parts) > 3 || !$GLOBALS['egw']->accounts->name2id($parts[1])) + if ($this->debug) { - list($id) = explode('.',array_pop($parts)); // remove evtl. .ics extension + error_log(__METHOD__." called with ('$path') id=$id, app='$app', user=$user"); } - $app = array_pop($parts); + if ($path[0] == '/') + { + $path = substr($path, 1); + } + $parts = explode('/', $this->_unslashify($path)); - if (($user = array_pop($parts))) + if ($this->accounts->name2id($parts[0])) + { + // /$user/$app/... + $user = array_shift($parts); + } + + $app = array_shift($parts); + + if ($user) { $user_prefix = '/'.$user; - $user = $GLOBALS['egw']->accounts->name2id($user,'account_lid',$app != 'addressbook' ? 'u' : null); + $user = $this->accounts->name2id($user,'account_lid',$app != 'addressbook' ? 'u' : null); } else { $user_prefix = ''; $user = $GLOBALS['egw_info']['user']['account_id']; } - if (!($ok = $id && in_array($app,array('addressbook','calendar','infolog','principals','groups')) && $user)) + + if (($id = array_pop($parts))) { - if ($this->debug) error_log(__METHOD__."('$path') returning false: id=$id, app='$app', user=$user"); + list($id) = explode('.',$id); // remove evtl. .ics extension + } + + $ok = $id && $user && in_array($app,array('addressbook','calendar','infolog','principals','groups')); + if ($this->debug) + { + error_log(__METHOD__."('$path') returning " . ($ok ? 'true' : 'false') . ": id='$id', app='$app', user='$user', user_prefix='$user_prefix'"); } return $ok; } + /** + * Add the privileges of the current user + * + * @param array $props=array() regular props by the groupdav handler + * @return array + */ + static function current_user_privilege_set(array $props=array()) + { + $props[] = HTTP_WebDAV_Server::mkprop('current-user-privilege-set', + array(HTTP_WebDAV_Server::mkprop('privilege', + array(//HTTP_WebDAV_Server::mkprop('all',''), + HTTP_WebDAV_Server::mkprop('read',''), + HTTP_WebDAV_Server::mkprop('read-free-busy',''), + //HTTP_WebDAV_Server::mkprop('read-current-user-privilege-set',''), + HTTP_WebDAV_Server::mkprop('bind',''), + HTTP_WebDAV_Server::mkprop('unbind',''), + HTTP_WebDAV_Server::mkprop('schedule-post',''), + HTTP_WebDAV_Server::mkprop('schedule-post-vevent',''), + HTTP_WebDAV_Server::mkprop('schedule-respond',''), + HTTP_WebDAV_Server::mkprop('schedule-respond-vevent',''), + HTTP_WebDAV_Server::mkprop('schedule-deliver',''), + HTTP_WebDAV_Server::mkprop('schedule-deliver-vevent',''), + HTTP_WebDAV_Server::mkprop('write',''), + HTTP_WebDAV_Server::mkprop('write-properties',''), + HTTP_WebDAV_Server::mkprop('write-content',''), + )))); + return $props; + } } diff --git a/phpgwapi/inc/class.groupdav_groups.inc.php b/phpgwapi/inc/class.groupdav_groups.inc.php deleted file mode 100644 index ae628587b4..0000000000 --- a/phpgwapi/inc/class.groupdav_groups.inc.php +++ /dev/null @@ -1,169 +0,0 @@ - - * @copyright (c) 2008 by Ralf Becker - * @version $Id$ - */ - -/** - * eGroupWare: GroupDAV access: groupdav/caldav/carddav groups handlers - */ -class groupdav_groups extends groupdav_handler -{ - /** - * Reference to the accounts class - * - * @var accounts - */ - var $accounts; - - /** - * Constructor - * - * @param string $app 'calendar', 'addressbook' or 'infolog' - * @param int $debug=null debug-level to set - * @param string $base_uri=null base url of handler - */ - function __construct($app,$debug=null,$base_uri=null) - { - parent::__construct($app,$debug,$base_uri); - - $this->accounts = $GLOBALS['egw']->accounts; - } - - /** - * Handle propfind request for an application folder - * - * @param string $path - * @param array $options - * @param array &$files - * @param int $user account_id - * @return mixed boolean true on success, false on failure or string with http status (eg. '404 Not Found') - */ - function propfind($path,$options,&$files,$user) - { - list(,,$user) = explode('/',$path); - foreach($user ? array($this->accounts->read($user)) : $this->accounts->search(array('type' => 'groups')) as $account) - { - $props = array( - HTTP_WebDAV_Server::mkprop('displayname',lang('Group').' '.$account['account_lid']), - 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/'), - ); - foreach($this->accounts->members($account['account_id']) as $uid => $user) - { - $props[] = HTTP_WebDAV_Server::mkprop('group-membership',$this->base_uri.'/principals/'.$user); - } - $files['files'][] = array( - 'path' => '/groups/'.$account['account_lid'], - 'props' => $props, - ); - if ($this->debug > 1) error_log(__METHOD__."($path) path=/principals/".$account['account_lid'].', props='.array2string($props)); - } - return true; - } - - /** - * Handle get request for an applications entry - * - * @param array &$options - * @param int $id - * @return mixed boolean true on success, false on failure or string with http status (eg. '404 Not Found') - */ - function get(&$options,$id) - { - if (!is_array($account = $this->_common_get_put_delete('GET',$options,$id))) - { - return $account; - } - $options['data'] = 'Principal: '.$account['account_lid']. - "\nURL: ".$this->base_uri.$options['path']. - "\nName: ".lang('Group').' '.$account['account_lid']. - ($account['account_email'] ? "\nEmail: ".$account['account_email'] : ''). - "\nMembers: ".implode(', ',$this->accounts->members($id))."\n"; - $options['mimetype'] = 'text/plain; charset=utf-8'; - header('Content-Encoding: identity'); - header('ETag: '.$this->get_etag($account)); - return true; - } - - /** - * Handle get request for an applications entry - * - * @param array &$options - * @param int $id - * @param int $user=null account_id of owner, default null - * @return mixed boolean true on success, false on failure or string with http status (eg. '404 Not Found') - */ - function put(&$options,$id,$user=null) - { - return false; - } - - /** - * Handle get request for an applications entry - * - * @param array &$options - * @param int $id - * @return mixed boolean true on success, false on failure or string with http status (eg. '404 Not Found') - */ - function delete(&$options,$id) - { - return false; - } - - /** - * Read an entry - * - * @param string/int $id - * @return array/boolean array with entry, false if no read rights, null if $id does not exist - */ - function read($id) - { - return $this->accounts->read($id); - } - - /** - * Check if user has the neccessary rights on an entry - * - * @param int $acl EGW_ACL_READ, EGW_ACL_EDIT or EGW_ACL_DELETE - * @param array/int $entry entry-array or id - * @return boolean null if entry does not exist, false if no access, true if access permitted - */ - function check_access($acl,$entry) - { - if ($acl != EGW_ACL_READ) - { - return false; - } - if (!is_array($entry) && !$this->accounts->name2id($entry,'account_lid','g')) - { - return null; - } - return true; - } - - /** - * Get the etag for an entry, can be reimplemented for other algorithm or field names - * - * @param array/int $event array with event or cal_id - * @return string/boolean string with etag or false - */ - function get_etag($account) - { - if (!is_array($account)) - { - $account = $this->read($account); - } - return '"'.$account['account_id'].':'.md5(serialize($account)).'"'; - } -} \ No newline at end of file diff --git a/phpgwapi/inc/class.groupdav_handler.inc.php b/phpgwapi/inc/class.groupdav_handler.inc.php index 90a9abeb96..d5bfde9eb2 100644 --- a/phpgwapi/inc/class.groupdav_handler.inc.php +++ b/phpgwapi/inc/class.groupdav_handler.inc.php @@ -37,6 +37,12 @@ abstract class groupdav_handler * @var translation */ var $translation; + /** + * Reference to the accounts class + * + * @var accounts + */ + var $accounts; /** * Translates method names into ACL bits * @@ -59,6 +65,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,17 +90,29 @@ 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.'principals/users/'. + $GLOBALS['egw_info']['user']['account_lid'].'/'; + } + $this->agent = self::get_agent(); $this->translation =& $GLOBALS['egw']->translation; $this->egw_charset = $this->translation->charset(); + $this->accounts = $GLOBALS['egw']->accounts; } /** @@ -105,12 +129,13 @@ abstract class groupdav_handler /** * Propfind callback, if interator is used * + * @param string $path * @param array $filter * @param array|boolean $start false=return all or array(start,num) * @param int &$total * @return array with "files" array with values for keys path and props */ - function &propfind_callback(array $filter,$start,&$total) { } + function &propfind_callback($path, array $filter,$start,&$total) { } /** * Handle get request for an applications entry @@ -161,9 +186,11 @@ abstract class groupdav_handler * Add extra properties for collections * * @param array $props=array() regular props by the groupdav handler + * @param string $displayname + * @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(), $displayname, $base_uri=null) { return $props; } @@ -185,7 +212,7 @@ abstract class groupdav_handler // error_log(__METHOD__."(".array2string($entry).") Cant create etag!"); return false; } - return '"'.$entry['id'].':'.(isset($entry['etag']) ? $entry['etag'] : $entry['modified']).'"'; + return 'EGw-'.$entry['id'].':'.(isset($entry['etag']) ? $entry['etag'] : $entry['modified']).'-wGE'; } /** @@ -196,7 +223,7 @@ abstract class groupdav_handler */ static function etag2value($etag) { - list(,$val) = explode(':',substr($etag,1,-1),2); + list(,$val) = explode(':',substr($etag,4,-4),2); return $val; } @@ -211,7 +238,7 @@ abstract class groupdav_handler * @param array &$options * @param int $id * @param boolean &$return_no_access=false if set to true on call, instead of '403 Forbidden' the entry is returned and $return_no_access===false - * @return array/string entry on success, string with http-error-code on failure, null for PUT on an unknown id + * @return array|string entry on success, string with http-error-code on failure, null for PUT on an unknown id */ function _common_get_put_delete($method,&$options,$id,&$return_no_access=false) { @@ -260,11 +287,13 @@ abstract class groupdav_handler * * @static * @param string $app 'calendar', 'addressbook' or 'infolog' + * @param int $user=null owner of the collection, default current user * @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 +302,14 @@ 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]; } @@ -294,6 +329,7 @@ abstract class groupdav_handler $user_agent = strtolower($_SERVER['HTTP_USER_AGENT']); foreach(array( 'davkit' => 'davkit', // Apple iCal + 'cfnetwork' => 'cfnetwork', // Apple Addressbook 'bionicmessage.net' => 'funambol', // funambol GroupDAV connector from bionicmessage.net 'zideone' => 'zideone', // zideone outlook plugin 'lightning' => 'lightning', // Lighting (SOGo connector for addressbook) @@ -333,6 +369,13 @@ abstract class groupdav_handler */ class groupdav_propfind_iterator implements Iterator { + /** + * current path + * + * @var string + */ + protected $path; + /** * Handler to call for entries * @@ -352,8 +395,16 @@ class groupdav_propfind_iterator implements Iterator * * @var array */ + protected $common_files; + + /** + * current chunk + * + * @var array + */ protected $files; + /** * Start value for callback * @@ -374,6 +425,8 @@ class groupdav_propfind_iterator implements Iterator */ public $debug = false; + /** + /** * Constructor * @@ -381,13 +434,14 @@ class groupdav_propfind_iterator implements Iterator * @param array $filter filter for propfind call * @param array $files=null extra files/responses to return too */ - public function __construct(groupdav_handler $handler,array $filter,array &$files=null) + public function __construct(groupdav_handler $handler, $path, array $filter,array &$files=null) { - if ($this->debug) error_log(__METHOD__."(,".array2string($filter).",)"); - + if ($this->debug) error_log(__METHOD__."('$path', ".array2string($filter).",)"); + $this->path = $path; $this->handler = $handler; $this->filter = $filter; $this->files = $files; + $this->common_files = $files; reset($this->files); } @@ -399,7 +453,6 @@ class groupdav_propfind_iterator implements Iterator public function current() { if ($this->debug) error_log(__METHOD__."() returning ".array2string(current($this->files))); - return current($this->files); } @@ -410,7 +463,7 @@ class groupdav_propfind_iterator implements Iterator */ public function key() { - $current = $this->current(); + $current = current($this->files); if ($this->debug) error_log(__METHOD__."() returning ".array2string($current['path'])); return $current['path']; // we return path as key @@ -424,21 +477,20 @@ class groupdav_propfind_iterator implements Iterator if (next($this->files) !== false) { if ($this->debug) error_log(__METHOD__."() returning TRUE"); - return true; } - if (is_array($this->files) && count($this->files) < self::CHUNK_SIZE) // less entries then asked --> no further available + if (is_array($this->files) && count($this->files) < self::CHUNK_SIZE) // less entries then asked --> no further available { if ($this->debug) error_log(__METHOD__."() returning FALSE (no more entries)"); - return false; // no further entries } // try query further files via propfind callback of handler and store result in $this->files - $this->files = $this->handler->propfind_callback($this->filter,array($this->start,self::CHUNK_SIZE)); + $this->files = $this->handler->propfind_callback($this->path,$this->filter,array($this->start,self::CHUNK_SIZE)); $this->start += self::CHUNK_SIZE; reset($this->files); if ($this->debug) error_log(__METHOD__."() returning ".array2string(current($this->files) !== false)); + return current($this->files) !== false; } @@ -451,7 +503,8 @@ class groupdav_propfind_iterator implements Iterator // query first set of files via propfind callback of handler and store result in $this->files $this->start = 0; - $this->files = $this->handler->propfind_callback($this->filter,array($this->start,self::CHUNK_SIZE)); + $files = $this->handler->propfind_callback($this->path,$this->filter,array($this->start,self::CHUNK_SIZE)); + $this->files = $this->common_files + $files; $this->start += self::CHUNK_SIZE; reset($this->files); } diff --git a/phpgwapi/inc/class.groupdav_principals.inc.php b/phpgwapi/inc/class.groupdav_principals.inc.php index a11e7c3de8..5a9c6030fd 100644 --- a/phpgwapi/inc/class.groupdav_principals.inc.php +++ b/phpgwapi/inc/class.groupdav_principals.inc.php @@ -7,7 +7,7 @@ * @package api * @subpackage groupdav * @author Ralf Becker - * @copyright (c) 2008 by Ralf Becker + * @copyright (c) 2008-10 by Ralf Becker * @version $Id$ */ @@ -16,12 +16,6 @@ */ class groupdav_principals extends groupdav_handler { - /** - * Reference to the accounts class - * - * @var accounts - */ - var $accounts; /** * Constructor @@ -29,12 +23,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); - - $this->accounts = $GLOBALS['egw']->accounts; + parent::__construct($app,$debug,$base_uri,$principalURL); } /** @@ -48,6 +41,40 @@ class groupdav_principals extends groupdav_handler */ function propfind($path,$options,&$files,$user) { + list(,$principals,$type,$name,$rest) = explode('/',$path,5); + // /principals/users/$name/ + // /users/$name/calendar-proxy-read/ + // /users/$name/calendar-proxy-write/ + // /groups/$name/ + // /resources/$resource/ + // /__uids__/$uid/.../ + + switch($type) + { + case 'users': + $files['files'] = $this->propfind_users($name,$rest,$options); + break; + case 'groups': + $files['files'] = $this->propfind_groups($name,$rest,$options); + break; + case 'resources': + $files['files'] = $this->propfind_resources($name,$rest,$options); + break; + case '__uids__': + $files['files'] = $this->propfind_uids($name,$rest,$options); + break; + case '': + $files['files'] = $this->propfind_principals($options); + break; + default: + return '404 Not Found'; + } + if (!is_array($files['files'])) + { + return $files['files']; + } + return true; + list(,,$id) = explode('/',$path); if ($id && !($id = $this->accounts->id2name($id))) { @@ -55,18 +82,20 @@ class groupdav_principals extends groupdav_handler } 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'])), + $displayname = $this->translation->convert($account['account_fullname'], + $this->translation->charset(),'utf-8'); + $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('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('principal-URL',$this->base_uri.'/principals/'.$account['account_lid']), + HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'calendar-home-set',$this->base_uri.$account['account_lid'].'/calendar/'), HTTP_WebDAV_Server::mkprop(groupdav::CALDAV,'calendar-user-address-set','MAILTO:'.$account['account_email']), ); foreach($this->accounts->memberships($account['account_id']) as $gid => $group) { - $props[] = HTTP_WebDAV_Server::mkprop('group-membership',$_SERVER['SCRIPT_NAME'].'/groups/'.$group); + $props[] = HTTP_WebDAV_Server::mkprop('group-membership',$this->base_uri.'/groups/'.$group); } $files['files'][] = array( 'path' => '/principals/'.$account['account_lid'], @@ -74,7 +103,251 @@ class groupdav_principals extends groupdav_handler ); if ($this->debug > 1) error_log(__METHOD__."($path) path=/principals/".$account['account_lid'].', props='.array2string($props)); } - return true; + return files; + } + + /** + * Do propfind in /pricipals/users + * + * @param string $name name of account or empty + * @param string $rest rest of path behind account-name + * @param array $options + * @return array|string array with files or HTTP error code + */ + protected function propfind_users($name,$rest,array $options) + { + //echo "

".__METHOD__."($name,$rest,".array2string($options).")

\n"; + if (empty($name)) + { + $files = array(); + // add /pricipals/users/ entry + $files[] = $this->add_collection('/principals/users/',array( + HTTP_WebDAV_Server::mkprop('current-user-principal',array( + HTTP_WebDAV_Server::mkprop('href',$this->base_uri.'/principals/'.$GLOBALS['egw_info']['user']['account_lid'].'/'))), + )); + if ($options['depth']) + { + // add all users + foreach($this->accounts->search(array('type' => 'accounts')) as $account) + { + $files[] = $this->add_account($account); + } + } + } + else + { + if (!($id = $this->accounts->name2id($name,'account_lid','u')) || + !($account = $this->accounts->read($id))) + { + return '404 Not Found'; + } + switch((string)$rest) + { + case '': + $files[] = $this->add_account($account); + $files[] = $this->add_collection('/principals/users/'.$account['account_lid'].'/calendar-proxy-read'); + $files[] = $this->add_collection('/principals/users/'.$account['account_lid'].'/calendar-proxy-write'); + break; + case 'calendar-proxy-read': + case 'calendar-proxy-write': + $files = array(); + $files[] = $this->add_collection('/principals/users/'.$account['account_lid'].'/'.$rest); + // add proxys + break; + default: + return '404 Not Found'; + } + } + return $files; + } + + /** + * Do propfind in /pricipals/groups + * + * @param string $name name of group or empty + * @param string $rest rest of path behind account-name + * @param array $options + * @return array|string array with files or HTTP error code + */ + protected function propfind_groups($name,$rest,array $options) + { + //echo "

".__METHOD__."($name,$rest,".array2string($options).")

\n"; + if (empty($name)) + { + $files = array(); + // add /pricipals/users/ entry + $files[] = $this->add_collection('/principals/groups/',array( + HTTP_WebDAV_Server::mkprop('current-user-principal',array( + HTTP_WebDAV_Server::mkprop('href',$this->base_uri.'/principals/'.$GLOBALS['egw_info']['user']['account_lid'].'/'))), + )); + if ($options['depth']) + { + // add all users + foreach($this->accounts->search(array('type' => 'groups')) as $account) + { + $files[] = $this->add_group($account); + } + } + } + else + { + if (!($id = $this->accounts->name2id($name,'account_lid','g')) || + !($account = $this->accounts->read($id))) + { + return '404 Not Found'; + } + switch((string)$rest) + { + case '': + $files[] = $this->add_group($account); + $files[] = $this->add_collection('/principals/groups/'.$account['account_lid'].'/calendar-proxy-read'); + $files[] = $this->add_collection('/principals/groups/'.$account['account_lid'].'/calendar-proxy-write'); + break; + case 'calendar-proxy-read': + case 'calendar-proxy-write': + $files = array(); + $files[] = $this->add_collection('/principals/groups/'.$account['account_lid'].'/'.$rest); + // add proxys + break; + default: + return '404 Not Found'; + } + } + return $files; + } + + /** + * Add collection of a single account to a collection + * + * @param array $account + * @return array with values for keys 'path' and 'props' + */ + protected function add_account(array $account) + { + //echo "

".__METHOD__."(".array2string($account).")

\n"; + + $displayname = $this->translation->convert($account['account_fullname'], + $this->translation->charset(),'utf-8'); + $memberships = array(); + foreach($this->accounts->memberships($account['account_id']) as $gid => $group) + { + if ($group) + { + $memberships[] = HTTP_WebDAV_Server::mkprop('href', + $this->base_uri.'/principals/groups/'.$group); + } + } + $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',array( + HTTP_WebDAV_Server::mkprop('href','MAILTO:'.$account['account_email']))), + HTTP_WebDAV_Server::mkprop('principal-URL',$this->base_uri.'/principals/users/'.$account['account_lid'].'/'), + 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',array( + HTTP_WebDAV_Server::mkprop('href','MAILTO:'.$account['account_email']))), + HTTP_WebDAV_Server::mkprop(groupdav::CARDDAV,'addressbook-home-set',array( + HTTP_WebDAV_Server::mkprop('href',$this->base_uri.'/'.$account['account_lid'].'/'))), + HTTP_WebDAV_Server::mkprop('group-member-ship', $memberships), + HTTP_WebDAV_Server::mkprop('principal-URL',array(HTTP_WebDAV_Server::mkprop('href',$this->principalURL))), + ); + if ($this->debug > 1) error_log(__METHOD__."($path) path=/principals/users/".$account['account_lid'].', props='.array2string($props)); + return array( + 'path' => '/principals/users/'.$account['account_lid'].'/', + 'props' => $props, + ); + } + + /** + * Add collection of a single group to a collection + * + * @param array $account + * @return array with values for keys 'path' and 'props' + */ + protected function add_group(array $account) + { + $displayname = $this->translation->convert(lang('Group').' '.$account['account_lid'], + $this->translation->charset(),'utf-8'); + $members = array(); + foreach($this->accounts->members($account['account_id']) as $gid => $user) + { + if ($user) + { + $members[] = HTTP_WebDAV_Server::mkprop('href', + $this->base_uri.'/principals/users/'.$user); + } + } + $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(groupdav::CALDAV,'calendar-home-set',array( + HTTP_WebDAV_Server::mkprop('href',$this->base_uri.'/'.$account['account_lid'].'/calendar/'))), + HTTP_WebDAV_Server::mkprop(groupdav::CARDDAV,'addressbook-home-set',array( + HTTP_WebDAV_Server::mkprop('href',$this->base_uri.'/'.$account['account_lid'].'/'))), + HTTP_WebDAV_Server::mkprop('group-member-set', $members), + //HTTP_WebDAV_Server::mkprop('principal-URL',array(self::mkprop('href',$this->principalURL))), + ); + $files['files'][] = array( + 'path' => '/principals/groups/'.$account['account_lid'].'/', + 'props' => $props, + ); + if ($this->debug > 1) error_log(__METHOD__."($path) path=/principals/groups/".$account['account_lid'].', props='.array2string($props)); + return array( + 'path' => '/principals/groups/'.$account['account_lid'].'/', + 'props' => $props, + ); + } + + /** + * Add a collection + * + * @param string $path + * @param array $props=array() extra properties 'resourcetype' is added anyway + * @return array + */ + protected function add_collection($path,$props=array()) + { + //echo "

".__METHOD__."($path,".array($props).")

\n"; + $props[] = HTTP_WebDAV_Server::mkprop('resourcetype',array( + HTTP_WebDAV_Server::mkprop('collection',''), + HTTP_WebDAV_Server::mkprop('resourcetype','principal'), + )); + return array( + 'path' => $path, + 'props' => $props, + ); + } + + /** + * Do propfind of /pricipals + * + * @param string $name name of group or empty + * @param string $rest name of rest of path behind group-name + * @param array $options + * @return array|string array with files or HTTP error code + */ + protected function propfind_principals(array $options) + { + //echo "

".__METHOD__."(".array($options).")

\n"; + $files = array(); + $files[] = $this->add_collection('/principals/',array( + HTTP_WebDAV_Server::mkprop('current-user-principal',array( + HTTP_WebDAV_Server::mkprop('href',$this->base_uri.'/principals/users/'.$GLOBALS['egw_info']['user']['account_lid'].'/'))), + )); + + if ($options['depth']) + { + $options['depth'] = 0; + $files = array_merge($files,$this->propfind_users('','',$options)); + $files = array_merge($files,$this->propfind_groups('','',$options)); + //$files = array_merge($this->propfind_resources('','',$options)); + //$files = array_merge($this->propfind_uids('','',$options)); + } + return $files; } /** @@ -90,9 +363,12 @@ class groupdav_principals extends groupdav_handler { return $account; } + $name = $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: ".$name. "\nEmail: ".$account['account_email']. "\nMemberships: ".implode(', ',$this->accounts->memberships($id))."\n"; $options['mimetype'] = 'text/plain; charset=utf-8'; @@ -134,7 +410,8 @@ class groupdav_principals extends groupdav_handler */ function read($id) { - return $this->accounts->read($id); + return false; + //return $this->accounts->read($id); } /** @@ -169,6 +446,6 @@ class groupdav_principals extends groupdav_handler { $account = $this->read($account); } - return '"'.$account['account_id'].':'.md5(serialize($account)).'"'; + return 'EGw-'.$account['account_id'].':'.md5(serialize($account)).'-wGE'; } } \ No newline at end of file diff --git a/phpgwapi/inc/class.vfs_webdav_server.inc.php b/phpgwapi/inc/class.vfs_webdav_server.inc.php index 3a7c6af4d7..12d425b8bc 100644 --- a/phpgwapi/inc/class.vfs_webdav_server.inc.php +++ b/phpgwapi/inc/class.vfs_webdav_server.inc.php @@ -263,7 +263,8 @@ class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem // type and size (caller already made sure that path exists) if (is_dir($fspath)) { // directory (WebDAV collection) - $info['props'][] = HTTP_WebDAV_Server::mkprop ('resourcetype', HTTP_WebDAV_Server::mkprop('collection','')); + $info['props'][] = HTTP_WebDAV_Server::mkprop ('resourcetype', array( + HTTP_WebDAV_Server::mkprop('collection', ''))); $info['props'][] = HTTP_WebDAV_Server::mkprop ('getcontenttype', 'httpd/unix-directory'); } else { // plain file (WebDAV resource) @@ -471,4 +472,4 @@ class vfs_webdav_server extends HTTP_WebDAV_Server_Filesystem { return egw_vfs::checkLock($path); } -} +} \ No newline at end of file