2008-05-08 22:31:32 +02:00
< ? php
/**
2009-10-17 14:22:40 +02:00
* EGroupware : CalDAV / CardDAV / GroupDAV access
2008-05-08 22:31:32 +02:00
*
* @ link http :// www . egroupware . org
* @ license http :// opensource . org / licenses / gpl - license . php GPL - GNU General Public License
* @ package api
* @ subpackage groupdav
* @ author Ralf Becker < RalfBecker - AT - outdoor - training . de >
2012-02-10 09:58:08 +01:00
* @ copyright ( c ) 2007 - 12 by Ralf Becker < RalfBecker - AT - outdoor - training . de >
2008-05-08 22:31:32 +02:00
* @ version $Id $
*/
require_once ( 'HTTP/WebDAV/Server.php' );
/**
2009-10-17 14:22:40 +02:00
* EGroupware : GroupDAV access
2008-05-08 22:31:32 +02:00
*
2009-10-03 12:22:14 +02:00
* Using a modified PEAR HTTP / WebDAV / Server class from egw - pear !
*
* One can use the following url ' s releative ( ! ) to http :// domain . com / egroupware / groupdav . php
*
2011-11-06 10:40:33 +01:00
* - / base of Cal | Card | GroupDAV tree , only certain clients ( KDE , Apple ) can autodetect folders from here
* - / principals / principal - collection - set for WebDAV ACL
* - / principals / users /< username >/
* - / principals / groups /< groupname >/
* - /< username >/ users home - set with
2009-10-03 12:22:14 +02:00
* - /< username >/ addressbook / addressbook of user or group < username > given the user has rights to view it
2012-02-10 12:44:52 +01:00
* - /< username >/ addressbook -< other - username >/ shared addressbooks from other user or group
2009-10-03 12:22:14 +02:00
* - /< username >/ calendar / calendar of user < username > given the user has rights to view it
2012-02-10 12:44:52 +01:00
* - /< username >/ calendar -< other - username >/ shared calendar from other user or group
2011-11-06 10:40:33 +01:00
* - /< username >/ inbox / scheduling inbox of user < username >
* - /< username >/ outbox / scheduling outbox of user < username >
2009-10-03 12:22:14 +02:00
* - /< username >/ infolog / InfoLog ' s of user < username > given the user has rights to view it
2011-11-06 10:40:33 +01:00
* - / addressbook / all addressbooks current user has rights to , announced as directory - gateway now
* - / calendar / calendar of current user
* - / infolog / infologs of current user
2009-10-03 12:22:14 +02:00
*
* Calling one of the above collections with a GET request / regular browser generates an automatic index
* from the data of a allprop PROPFIND , allow to browse CalDAV / CardDAV / GroupDAV tree with a regular browser .
2008-05-08 22:31:32 +02:00
*
2011-11-06 10:40:33 +01:00
* @ link http :// www . groupdav . org / GroupDAV spec
* @ link http :// caldav . calconnect . org / CalDAV resources
* @ link http :// carddav . calconnect . org / CardDAV resources
* @ link http :// calendarserver . org / Apple calendar and contacts server
2008-05-08 22:31:32 +02:00
*/
class groupdav extends HTTP_WebDAV_Server
{
2010-03-07 00:06:43 +01:00
/**
* DAV namespace
*/
const DAV = 'DAV:' ;
2008-05-08 22:31:32 +02:00
/**
* GroupDAV namespace
*/
const GROUPDAV = 'http://groupdav.org/' ;
/**
* CalDAV namespace
*/
const CALDAV = 'urn:ietf:params:xml:ns:caldav' ;
/**
* CardDAV namespace
*/
const CARDDAV = 'urn:ietf:params:xml:ns:carddav' ;
2010-01-06 00:25:17 +01:00
/**
2011-11-06 10:40:33 +01:00
* Apple Calendarserver namespace ( eg . for ctag )
2010-01-06 00:25:17 +01:00
*/
const CALENDARSERVER = 'http://calendarserver.org/ns/' ;
2011-11-06 10:40:33 +01:00
/**
* Apple Addressbookserver namespace ( eg . for ctag )
*/
const ADDRESSBOOKSERVER = 'http://addressbookserver.org/ns/' ;
2010-04-13 17:31:59 +02:00
/**
* Apple iCal namespace ( eg . for calendar color )
*/
2010-09-28 10:32:11 +02:00
const ICAL = 'http://apple.com/ns/ical/' ;
2008-05-08 22:31:32 +02:00
/**
* Realm and powered by string
*/
2010-04-13 17:31:59 +02:00
const REALM = 'EGroupware CalDAV/CardDAV/GroupDAV server' ;
2008-05-08 22:31:32 +02:00
var $dav_powered_by = self :: REALM ;
2008-05-20 06:59:26 +02:00
var $http_auth_realm = self :: REALM ;
2008-05-08 22:31:32 +02:00
2011-11-06 10:40:33 +01:00
/**
* Folders in root or user home
*
* @ var array
*/
2008-05-08 22:31:32 +02:00
var $root = array (
2011-11-06 10:40:33 +01:00
'addressbook' => array (
'resourcetype' => array ( self :: GROUPDAV => 'vcard-collection' , self :: CARDDAV => 'addressbook' ),
'component-set' => array ( self :: GROUPDAV => 'VCARD' ),
),
2008-08-04 21:08:09 +02:00
'calendar' => array (
'resourcetype' => array ( self :: GROUPDAV => 'vevent-collection' , self :: CALDAV => 'calendar' ),
'component-set' => array ( self :: GROUPDAV => 'VEVENT' ),
),
2011-11-06 10:40:33 +01:00
'inbox' => array (
'resourcetype' => array ( self :: CALDAV => 'schedule-inbox' ),
'app' => 'calendar' ,
'user-only' => true , // display just in user home
),
'outbox' => array (
'resourcetype' => array ( self :: CALDAV => 'schedule-outbox' ),
'app' => 'calendar' ,
'user-only' => true , // display just in user home
2008-08-04 21:08:09 +02:00
),
'infolog' => array (
2010-03-07 00:06:43 +01:00
'resourcetype' => array ( self :: GROUPDAV => 'vtodo-collection' , self :: CALDAV => 'calendar' ),
2008-08-04 21:08:09 +02:00
'component-set' => array ( self :: GROUPDAV => 'VTODO' ),
),
2008-05-08 22:31:32 +02:00
);
/**
* Debug level : 0 = nothing , 1 = function calls , 2 = more info , 3 = complete $_SERVER array
*
2010-10-31 08:56:29 +01:00
* Can now be enabled on a per user basis in GroupDAV prefs , if it is set here to 0 !
*
2008-05-08 22:31:32 +02:00
* The debug messages are send to the apache error_log
*
* @ var integer
*/
2010-03-07 00:32:28 +01:00
var $debug = 0 ;
2008-05-08 22:31:32 +02:00
/**
* eGW ' s charset
*
* @ var string
*/
var $egw_charset ;
/**
* Instance of our application specific handler
*
* @ var groupdav_handler
*/
var $handler ;
2010-03-07 00:06:43 +01:00
/**
2011-11-06 10:40:33 +01:00
* current - user - principal URL
2010-03-07 00:06:43 +01:00
*
* @ var string
*/
2011-11-06 10:40:33 +01:00
var $current_user_principal ;
2010-03-07 00:06:43 +01:00
/**
* Reference to the accounts class
*
* @ var accounts
*/
var $accounts ;
2011-11-06 10:40:33 +01:00
/**
* Supported privileges with name and description
*
* privileges are hierarchical
*
* @ var array
*/
var $supported_privileges = array (
'all' => array (
'*description*' => 'all privileges' ,
'read' => array (
'*description*' => 'read resource' ,
'read-free-busy' => array (
'*ns*' => self :: CALDAV ,
'*description*' => 'allow free busy report query' ,
'*only*' => '/calendar/' ,
),
),
'write' => array (
'*description*' => 'write resource' ,
'write-properties' => 'write resource properties' ,
'write-content' => 'write resource content' ,
'bind' => 'add child resource' ,
'unbind' => 'remove child resource' ,
),
'unlock' => 'unlock resource without ownership of lock' ,
'read-acl' => 'read resource access control list' ,
'write-acl' => 'write resource access control list' ,
'read-current-user-privilege-set' => 'read privileges for current principal' ,
'schedule-deliver' => array (
'*ns*' => self :: CALDAV ,
'*description*' => 'schedule privileges for current principal' ,
'*only*' => '/inbox/' ,
),
'schedule-send' => array (
'*ns*' => self :: CALDAV ,
'*description*' => 'schedule privileges for current principal' ,
'*only*' => '/outbox/' ,
),
),
);
/**
* $options parameter to PROPFIND request , eg . to check what props are requested
*
* @ var array
*/
var $propfind_options ;
2010-03-07 00:06:43 +01:00
2008-05-08 22:31:32 +02:00
function __construct ()
{
2010-10-31 08:56:29 +01:00
if ( ! $this -> debug ) $this -> debug = ( int ) $GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ 'groupdav' ][ 'debug_level' ];
2008-05-17 14:54:26 +02:00
if ( $this -> debug > 2 ) error_log ( 'groupdav: $_SERVER=' . array2string ( $_SERVER ));
2008-05-08 22:31:32 +02:00
2012-02-20 10:07:02 +01:00
// setting our own exception handler, to be able to still log the requests
set_exception_handler ( array ( __CLASS__ , 'exception_handler' ));
2011-11-06 10:40:33 +01:00
// crrnd: client refuses redundand namespace declarations
// cnrnd: client needs redundand namespace declarations
// setting redundand namespaces as the default for (Cal|Card|Group)DAV, as the majority of the clients either require or can live with it
$this -> cnrnd = true ;
2009-09-14 10:44:37 +02:00
// identify clients, which do NOT support path AND full url in <D:href> of PROPFIND request
2011-11-06 10:40:33 +01:00
switch (( $agent = groupdav_handler :: get_agent ()))
2009-09-14 10:44:37 +02:00
{
2011-09-26 12:21:25 +02:00
case 'akonadi' :
$this -> cnrnd = true ; // Akonadi seems to require redundant namespaces, see KDE bug #265096 https://bugs.kde.org/show_bug.cgi?id=265096
break ;
2009-09-14 10:44:37 +02:00
case 'kde' : // KAddressbook (at least in 3.5 can NOT subscribe / does NOT find addressbook)
$this -> client_require_href_as_url = true ;
2011-09-26 12:21:25 +02:00
$this -> cnrnd = false ; // KDE before Akonadi seems NOT to work with cnrnd (redundant namespaces)
2009-09-14 10:44:37 +02:00
break ;
2010-09-25 11:08:37 +02:00
case 'cfnetwork' : // Apple addressbook app
case 'dataaccess' : // iPhone addressbook
$this -> client_require_href_as_url = false ;
$this -> cnrnd = true ;
break ;
2009-09-14 10:44:37 +02:00
case 'davkit' : // iCal app in OS X 10.6 created wrong request, if full url given
2011-08-02 15:01:21 +02:00
case 'coredav' : // iCal app in OS X 10.7
2011-11-08 22:10:37 +01:00
case 'calendarstore' : // Apple iCal 5.0.1 under OS X 10.7.2
2009-09-14 10:44:37 +02:00
$this -> client_require_href_as_url = false ;
2010-10-20 20:59:27 +02:00
$this -> cnrnd = true ;
2009-09-14 10:44:37 +02:00
break ;
2010-04-21 19:44:36 +02:00
case 'cfnetwork_old' :
$this -> crrnd = true ; // Older Apple Addressbook.app does not cope with namespace redundancy
2010-05-18 12:45:46 +02:00
break ;
case 'neon' :
$this -> cnrnd = true ; // neon clients like cadaver
2010-10-20 20:59:27 +02:00
break ;
2009-09-14 10:44:37 +02:00
}
2011-11-06 10:40:33 +01:00
if ( $this -> debug ) error_log ( __METHOD__ . " () HTTP_USER_AGENT=' $_SERVER[HTTP_USER_AGENT] ' --> ' $agent ' --> client_requires_href_as_url= $this->client_require_href_as_url , crrnd(client refuses redundand namespace declarations)= $this->crrnd , cnrnd(client needs redundand namespace declarations)= $this->cnrnd " );
2010-09-28 10:32:11 +02:00
// adding EGroupware version to X-Dav-Powered-By header eg. "EGroupware 1.8.001 CalDAV/CardDAV/GroupDAV server"
$this -> dav_powered_by = str_replace ( 'EGroupware' , 'EGroupware ' . $GLOBALS [ 'egw_info' ][ 'server' ][ 'versions' ][ 'phpgwapi' ],
$this -> dav_powered_by );
2008-05-08 22:31:32 +02:00
parent :: HTTP_WebDAV_Server ();
2012-02-10 12:28:56 +01:00
// hack to allow to use query parameters in WebDAV, which HTTP_WebDAV_Server interprets as part of the path
list ( $this -> _SERVER [ 'REQUEST_URI' ]) = explode ( '?' , $this -> _SERVER [ 'REQUEST_URI' ]);
/* if ( substr ( $this -> _SERVER [ 'REQUEST_URI' ], - 13 ) == '/;add-member/' )
{
$_GET [ 'add-member' ] = '' ;
$this -> _SERVER [ 'REQUEST_URI' ] = substr ( $this -> _SERVER [ 'REQUEST_URI' ], 0 , - 12 );
} */
//error_log($_SERVER['REQUEST_URI']." --> ".$this->_SERVER['REQUEST_URI']);
2008-05-08 22:31:32 +02:00
2010-09-25 11:08:37 +02:00
$this -> egw_charset = translation :: charset ();
2010-03-07 00:06:43 +01:00
if ( strpos ( $this -> base_uri , 'http' ) === 0 )
{
2011-11-06 10:40:33 +01:00
$this -> current_user_principal = $this -> _slashify ( $this -> base_uri );
2010-03-07 00:06:43 +01:00
}
else
{
2011-11-06 10:40:33 +01:00
$this -> current_user_principal = ( @ $_SERVER [ " HTTPS " ] === " on " ? " https: " : " http: " ) .
2010-03-07 00:06:43 +01:00
'//' . $_SERVER [ 'HTTP_HOST' ] . $_SERVER [ 'SCRIPT_NAME' ] . '/' ;
}
2011-11-06 10:40:33 +01:00
$this -> current_user_principal .= 'principals/users/' . $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_lid' ] . '/' ;
2010-10-31 08:56:29 +01:00
2010-09-25 11:08:37 +02:00
// if client requires pathes instead of URLs
2011-11-06 10:40:33 +01:00
if ( ! $this -> client_require_href_as_url )
2010-09-25 11:08:37 +02:00
{
2011-11-06 10:40:33 +01:00
$this -> current_user_principal = parse_url ( $this -> current_user_principal , PHP_URL_PATH );
2010-09-25 11:08:37 +02:00
}
2010-03-07 00:06:43 +01:00
$this -> accounts = $GLOBALS [ 'egw' ] -> accounts ;
2008-05-08 22:31:32 +02:00
}
2008-05-17 14:54:26 +02:00
/**
* get the handler for $app
*
* @ param string $app
* @ return groupdav_handler
*/
function app_handler ( $app )
2008-05-08 22:31:32 +02:00
{
2011-11-06 10:40:33 +01:00
if ( isset ( $this -> root [ $app ][ 'app' ])) $app = $this -> root [ $app ][ 'app' ];
return groupdav_handler :: app_handler ( $app , $this );
2008-05-08 22:31:32 +02:00
}
/**
* OPTIONS request , allow to modify the standard responses from the pear - class
*
* @ param string $path
* @ param array & $dav
* @ param array & $allow
*/
function OPTIONS ( $path , & $dav , & $allow )
{
2012-02-10 12:44:52 +01:00
// locking support
if ( ! in_array ( '2' , $dav )) $dav [] = '2' ;
if ( preg_match ( '#/(calendar(-[^/]+)?|inbox|outbox)/#' , $path )) // eg. /<username>/calendar-<otheruser>/
2008-05-08 22:31:32 +02:00
{
2011-11-06 10:40:33 +01:00
$app = 'calendar' ;
}
2012-02-10 13:24:43 +01:00
elseif ( preg_match ( '#/addressbook(-[^/]+)?/#' , $path )) // eg. /<username>/addressbook-<otheruser>/
2011-11-06 10:40:33 +01:00
{
$app = 'addressbook' ;
2008-05-08 22:31:32 +02:00
}
2011-11-06 10:40:33 +01:00
// CalDAV and CardDAV
$dav [] = 'access-control' ;
if ( $app !== 'addressbook' ) // CalDAV
{
$dav [] = 'calendar-access' ;
$dav [] = 'calendar-auto-schedule' ;
$dav [] = 'calendar-proxy' ;
// required by iOS iCal to use principal-property-search to autocomplete participants (and locations)
$dav [] = 'calendarserver-principal-property-search' ;
2012-02-10 13:24:43 +01:00
// required by iOS & OS X iCal to show private checkbox (X-CALENDARSERVER-ACCESS: CONFIDENTIAL on VCALENDAR)
$dav [] = 'calendarserver-private-events' ;
2011-11-06 10:40:33 +01:00
// other capabilities calendarserver announces
//$dav[] = 'calendar-schedule';
//$dav[] = 'calendar-availability';
//$dav[] = 'inbox-availability';
//$dav[] = 'calendarserver-private-comments';
//$dav[] = 'calendarserver-sharing';
//$dav[] = 'calendarserver-sharing-no-scheduling';
}
if ( $app !== 'calendar' ) // CardDAV
{
$dav [] = 'addressbook' ; // CardDAV uses "addressbook" NOT "addressbook-access"
}
//error_log(__METHOD__."('$path') --> app='$app' --> DAV: ".implode(', ', $dav));
2008-05-08 22:31:32 +02:00
}
/**
* PROPFIND and REPORT method handler
*
* @ param array general parameter passing array
* @ param array return array for file properties
* @ return bool true on success
*/
2010-03-07 00:06:43 +01:00
function PROPFIND ( & $options , & $files , $method = 'PROPFIND' )
2008-05-08 22:31:32 +02:00
{
2012-01-21 02:47:49 +01:00
if ( $this -> debug ) error_log ( __CLASS__ . " :: $method ( " . array2string ( $options ) . ')' );
2008-05-08 22:31:32 +02:00
2011-11-06 10:40:33 +01:00
// make options (readonly) available to all class methods, eg. prop_requested
$this -> propfind_options = $options ;
2008-05-08 22:31:32 +02:00
// parse path in form [/account_lid]/app[/more]
2012-02-10 09:53:50 +01:00
if ( ! self :: _parse_path ( $options [ 'path' ], $id , $app , $user , $user_prefix ) && $app && ! $user && $user !== 0 )
2008-05-08 22:31:32 +02:00
{
2010-03-07 00:06:43 +01:00
if ( $this -> debug > 1 ) error_log ( __CLASS__ . " :: $method : user=' $user ', app=' $app ', id=' $id ': 404 not found! " );
2008-05-08 22:31:32 +02:00
return '404 Not Found' ;
}
2010-03-07 00:06:43 +01:00
if ( $this -> debug > 1 ) error_log ( __CLASS__ . " :: $method : user=' $user ', app=' $app ', id=' $id ' " );
2008-05-17 09:05:57 +02:00
$files = array ( 'files' => array ());
2010-03-07 00:06:43 +01:00
$path = $user_prefix = $this -> _slashify ( $user_prefix );
2008-05-08 22:31:32 +02:00
2010-03-07 00:06:43 +01:00
if ( ! $app ) // user root folder containing apps
2008-05-08 22:31:32 +02:00
{
2011-11-06 10:40:33 +01:00
// add root with current users apps
$this -> add_home ( $files , $path , $user , $options [ 'depth' ]);
// add principals and user-homes
if ( $path == '/' && $options [ 'depth' ])
2010-03-07 00:06:43 +01:00
{
2011-11-06 10:40:33 +01:00
// principals collection
$files [ 'files' ][] = $this -> add_collection ( '/principals/' , array (
2011-11-23 17:37:43 +01:00
'displayname' => lang ( 'Accounts' ),
2011-11-06 10:40:33 +01:00
));
// todo: account_selection owngroups and none!!!
2012-02-10 09:53:50 +01:00
foreach ( $this -> accounts -> search ( array ( 'type' => 'both' , 'order' => 'account_lid' )) as $account )
2011-11-06 10:40:33 +01:00
{
2012-02-10 09:53:50 +01:00
$this -> add_home ( $files , $path . $account [ 'account_lid' ] . '/' , $account [ 'account_id' ], $options [ 'depth' ] == 'infinity' ? 'infinity' : $options [ 'depth' ] - 1 );
2011-11-06 10:40:33 +01:00
}
2010-03-07 00:06:43 +01:00
}
2011-11-06 10:40:33 +01:00
return true ;
}
if ( $app != 'principals' && ! isset ( $GLOBALS [ 'egw_info' ][ 'user' ][ 'apps' ][ $this -> root [ $app ][ 'app' ] ? $this -> root [ $app ][ 'app' ] : $app ]))
{
if ( $this -> debug ) error_log ( __CLASS__ . " :: $method (path= $options[path] ) 403 Forbidden: no app rights for ' $app ' " );
return " 403 Forbidden: no app rights for ' $app ' " ; // no rights for the given app
}
if (( $handler = self :: app_handler ( $app )))
{
if ( $method != 'REPORT' && ! $id ) // no self URL for REPORT requests (only PROPFIND) or propfinds on an id
2010-09-18 10:45:46 +02:00
{
2011-11-06 10:40:33 +01:00
// KAddressbook doubles the folder, if the self URL contains the GroupDAV/CalDAV resourcetypes
2012-02-10 09:58:08 +01:00
$files [ 'files' ][ 0 ] = $this -> add_app ( $app , $app == 'addressbook' && $handler -> get_agent () == 'kde' , $user ,
2012-02-10 12:13:38 +01:00
$this -> _slashify ( $options [ 'path' ]));
2011-11-06 10:40:33 +01:00
2012-01-21 02:47:49 +01:00
// Hack for iOS 5.0.1 addressbook to stop asking directory gateway permissions with depth=1
if ( $method == 'PROPFIND' && $options [ 'path' ] == '/addressbook/' && $handler -> get_agent () == 'dataaccess' )
{
error_log ( __CLASS__ . " :: $method ( " . array2string ( $options ) . ') Enabling hack for iOS 5.0.1 addressbook: force Depth: 0 on PROPFIND for directory gateway!' );
return true ;
}
2011-11-06 10:40:33 +01:00
if ( ! $options [ 'depth' ]) return true ; // depth 0 --> show only the self url
2010-09-18 10:45:46 +02:00
}
2011-11-06 10:40:33 +01:00
return $handler -> propfind ( $this -> _slashify ( $options [ 'path' ]), $options , $files , $user , $id );
}
return '501 Not Implemented' ;
}
/**
* Add a collection to a PROPFIND request
*
* @ param string $path
* @ param array $props = array () extra properties 'resourcetype' is added anyway , name => value pairs or name => HTTP_WebDAV_Server ([ namespace ,] name , value )
* @ param array $privileges = array ( 'read' ) values for current - user - privilege - set
* @ param array $supported_privileges = null default $this -> supported_privileges
* @ return array with values for keys 'path' and 'props'
*/
public function add_collection ( $path , array $props = array (), array $privileges = array ( 'read' , 'read-acl' , 'read-current-user-privilege-set' ), array $supported_privileges = null )
{
// resourcetype: collection
$props [ 'resourcetype' ][] = self :: mkprop ( 'collection' , '' );
if ( ! isset ( $props [ 'getcontenttype' ])) $props [ 'getcontenttype' ] = 'httpd/unix-directory' ;
return $this -> add_resource ( $path , $props , $privileges , $supported_privileges );
}
/**
* Add a resource to a PROPFIND request
*
* @ param string $path
* @ param array $props = array () extra properties 'resourcetype' is added anyway , name => value pairs or name => HTTP_WebDAV_Server ([ namespace ,] name , value )
* @ param array $privileges = array ( 'read' ) values for current - user - privilege - set
* @ param array $supported_privileges = null default $this -> supported_privileges
* @ return array with values for keys 'path' and 'props'
*/
public function add_resource ( $path , array $props = array (), array $privileges = array ( 'read' , 'read-current-user-privilege-set' ), array $supported_privileges = null )
{
// props for all collections: current-user-principal and principal-collection-set
$props [ 'current-user-principal' ] = array (
self :: mkprop ( 'href' , $this -> current_user_principal ));
$props [ 'principal-collection-set' ] = array (
self :: mkprop ( 'href' , $this -> base_uri . '/principals/' ));
// required props per WebDAV standard
foreach ( array (
'displayname' => basename ( $path ),
'getetag' => 'none' ,
'getcontentlength' => '' ,
'getlastmodified' => '' ,
'getcontenttype' => '' ,
'resourcetype' => '' ,
) as $name => $default )
{
if ( ! isset ( $props [ $name ])) $props [ $name ] = $default ;
}
// if requested add privileges
if ( is_null ( $supported_privileges )) $supported_privileges = $this -> supported_privileges ;
if ( $this -> prop_requested ( 'current-user-privilege-set' ) === true )
{
foreach ( $privileges as $name )
{
$props [ 'current-user-privilege-set' ][] = self :: mkprop ( 'privilege' , array (
is_array ( $name ) ? self :: mkprop ( $name [ 'ns' ], $name [ 'name' ], '' ) : self :: mkprop ( $name , '' )));
}
}
if ( $this -> prop_requested ( 'supported-privilege-set' ) === true )
{
foreach ( $supported_privileges as $name => $data )
2010-09-18 10:45:46 +02:00
{
2011-11-06 10:40:33 +01:00
$props [ 'supported-privilege-set' ][] = $this -> supported_privilege ( $name , $data , $path );
2010-10-10 00:49:10 +02:00
}
2011-11-06 10:40:33 +01:00
}
if ( ! isset ( $props [ 'owner' ]) && $this -> prop_requested ( 'owner' ) === true )
{
$props [ 'owner' ] = '' ;
}
2010-10-20 11:42:06 +02:00
2011-11-06 10:40:33 +01:00
if ( $this -> debug > 1 ) error_log ( __METHOD__ . " (path=' $path ', props= " . array2string ( $props ) . ')' );
// convert simple associative properties to HTTP_WebDAV_Server ones
foreach ( $props as $name => & $prop )
{
if ( ! is_array ( $prop ) || ! isset ( $prop [ 'name' ]))
{
$prop = self :: mkprop ( $name , $prop );
}
// add quotes around etag, if they are not already there
if ( $prop [ 'name' ] == 'getetag' && $prop [ 'val' ][ 0 ] != '"' )
2010-10-10 00:49:10 +02:00
{
2011-11-06 10:40:33 +01:00
$prop [ 'val' ] = '"' . $prop [ 'val' ] . '"' ;
2010-09-18 10:45:46 +02:00
}
2011-11-06 10:40:33 +01:00
}
2010-10-31 08:56:29 +01:00
2011-11-06 10:40:33 +01:00
return array (
'path' => $path ,
'props' => $props ,
);
}
/**
* Generate ( hierachical ) supported - privilege property
*
* @ param string $name name of privilege
* @ param string | array $data string with describtion or array with agregated privileges plus value for key '*description*' , '*ns*' , '*only*'
* @ param string $path = null path to match with $data [ '*only*' ]
* @ return array of self :: mkprop () arrays
*/
protected function supported_privilege ( $name , $data , $path = null )
{
$props = array ();
$props [] = self :: mkprop ( 'privilege' , array ( is_array ( $data ) && $data [ '*ns*' ] ?
self :: mkprop ( $data [ '*ns*' ], $name , '' ) : self :: mkprop ( $name , '' )));
$props [] = self :: mkprop ( 'description' , is_array ( $data ) ? $data [ '*description*' ] : $data );
if ( is_array ( $data ))
{
foreach ( $data as $name => $data )
2008-05-08 22:31:32 +02:00
{
2011-11-06 10:40:33 +01:00
if ( $name [ 0 ] == '*' ) continue ;
if ( is_array ( $data ) && $data [ '*only*' ] && strpos ( $path , $data [ '*only*' ]) === false )
2009-10-03 12:22:14 +02:00
{
2011-11-06 10:40:33 +01:00
continue ; // wrong path
2008-05-17 09:05:57 +02:00
}
2011-11-06 10:40:33 +01:00
$props [] = $this -> supported_privilege ( $name , $data , $path );
2008-05-08 22:31:32 +02:00
}
}
2011-11-06 10:40:33 +01:00
return self :: mkprop ( 'supported-privilege' , $props );
}
/**
* Checks if a given property was requested in propfind request
*
* @ param string $name property name
* @ param string $ns = null namespace , if that is to be checked too
2012-02-10 09:50:19 +01:00
* @ param boolean $return_prop = false if true return the property array with values for 'name' , 'xmlns' , 'attrs' , 'children'
2012-02-10 09:58:08 +01:00
* @ return boolean | string | array true : $name explicitly requested ( or autoindex ), " all " : allprop or " names " : propname requested , false : $name was not requested
2011-11-06 10:40:33 +01:00
*/
2012-02-10 09:50:19 +01:00
function prop_requested ( $name , $ns = null , $return_prop = false )
2011-11-06 10:40:33 +01:00
{
if ( ! is_array ( $this -> propfind_options ) || ! isset ( $this -> propfind_options [ 'props' ]))
2008-05-08 22:31:32 +02:00
{
2012-02-10 09:58:08 +01:00
$ret = true ; // no props set, should happen only in autoindex, we return true to show all available props
2008-05-08 22:31:32 +02:00
}
2012-02-10 09:58:08 +01:00
elseif ( ! is_array ( $this -> propfind_options [ 'props' ]))
2008-05-08 22:31:32 +02:00
{
2012-02-10 09:58:08 +01:00
$ret = $this -> propfind_options [ 'props' ]; // "all": allprop or "names": propname
}
else
{
$ret = false ;
foreach ( $this -> propfind_options [ 'props' ] as $prop )
2008-05-08 22:31:32 +02:00
{
2012-02-10 09:58:08 +01:00
if ( $prop [ 'name' ] == $name && ( is_null ( $ns ) || $prop [ 'xmlns' ] == $ns ))
{
$ret = $return_prop ? $prop : true ;
break ;
}
2008-05-10 22:32:03 +02:00
}
2008-05-08 22:31:32 +02:00
}
2012-02-10 09:58:08 +01:00
//error_log(__METHOD__."('$name', '$ns', $return_prop) propfind_options=".array2string($this->propfind_options));
2011-11-06 10:40:33 +01:00
return $ret ;
}
/**
* Add user home with addressbook , calendar , infolog
*
* @ param array $files
* @ param string $path / or /< username >/
* @ param int $user
* @ param int $depth
* @ return string | boolean http status or true | false
*/
protected function add_home ( array & $files , $path , $user , $depth )
{
if ( $user )
{
$account_lid = $this -> accounts -> id2name ( $user );
}
else
{
$account_lid = $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_lid' ];
}
$account = $this -> accounts -> read ( $account_lid );
$calendar_user_address_set = array (
self :: mkprop ( 'href' , 'urn:uuid:' . $account [ 'account_lid' ]),
);
if ( $user < 0 )
{
$principalType = 'groups' ;
$displayname = lang ( 'Group' ) . ' ' . $account [ 'account_lid' ];
}
else
{
$principalType = 'users' ;
$displayname = $account [ 'account_fullname' ];
$calendar_user_address_set [] = self :: mkprop ( 'href' , 'MAILTO:' . $account [ 'account_email' ]);
}
$calendar_user_address_set [] = self :: mkprop ( 'href' , $this -> base_uri . '/principals/' . $principalType . '/' . $account [ 'account_lid' ] . '/' );
if ( $depth && $path == '/' )
{
$displayname = 'EGroupware (Cal|Card|Group)DAV server' ;
}
$displayname = translation :: convert ( $displayname , translation :: charset (), 'utf-8' );
// self url
2012-02-10 12:17:12 +01:00
$props = array (
2011-11-06 10:40:33 +01:00
'displayname' => $displayname ,
'owner' => $path == '/' ? '' : array ( self :: mkprop ( 'href' , $this -> base_uri . '/principals/' . $principalType . '/' . $account_lid . '/' )),
2012-02-10 12:17:12 +01:00
);
if ( $path != '/' )
{
// add props modifyable via proppatch from client, eg. jqcalendar stores it's preferences there
foreach (( array ) $GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ 'groupdav' ] as $name => $value )
{
list ( $prop , $prop4path , $ns ) = explode ( ':' , $name , 3 );
if ( $prop4path == $path && ! in_array ( $ns , self :: $ns_needs_explicit_named_props ))
{
$props [] = self :: mkprop ( $ns , $prop , $value );
//error_log(__METHOD__."() arbitrary $ns:$prop=".array2string($value));
}
}
}
$files [ 'files' ][] = $this -> add_collection ( $path , $props );
2011-11-06 10:40:33 +01:00
if ( $depth )
{
foreach ( $this -> root as $app => $data )
{
if ( ! $GLOBALS [ 'egw_info' ][ 'user' ][ 'apps' ][ $data [ 'app' ] ? $data [ 'app' ] : $app ]) continue ; // no rights for the given app
if ( ! empty ( $data [ 'user-only' ]) && ( $path == '/' || $user < 0 )) continue ;
2012-02-10 09:53:50 +01:00
$files [ 'files' ][] = $this -> add_app ( $app , false , $user , $path . $app . '/' );
2012-02-10 09:58:08 +01:00
// only add global /addressbook-accounts/ as the one in home-set is added (and controled) by add_shared
if ( $path == '/' && $app == 'addressbook' &&
! $GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ 'addressbook' ][ 'hide_accounts' ])
{
2012-02-10 12:18:12 +01:00
$file = $this -> add_app ( $app , false , 0 , $path . $app . '-accounts/' );
$file [ 'props' ][ 'resourcetype' ][ 'val' ][] = self :: mkprop ( self :: CALENDARSERVER , 'shared' , '' );
$files [ 'files' ][] = $file ;
2012-02-10 09:58:08 +01:00
}
2012-02-10 09:53:50 +01:00
// added shared calendars or addressbooks
$this -> add_shared ( $files [ 'files' ], $path , $app , $user );
2011-11-06 10:40:33 +01:00
}
}
return true ;
2008-05-08 22:31:32 +02:00
}
2012-02-10 09:53:50 +01:00
/**
* Add shared addressbook , calendar , infolog to user home
*
* @ param array & $files
* @ param string $path /< username >/
* @ param int $app
* @ param int $user
* @ return string | boolean http status or true | false
*/
protected function add_shared ( array & $files , $path , $app , $user )
{
// currently only show shared calendars/addressbooks for current user and not in the root
2012-02-10 12:27:26 +01:00
if ( $path == '/' || $user != $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ] ||
! isset ( $GLOBALS [ 'egw_info' ][ 'user' ][ 'apps' ][ $app ])) // also avoids principals, inbox and outbox
2012-02-10 09:53:50 +01:00
{
return true ;
}
2012-02-10 12:27:26 +01:00
$handler = $this -> app_handler ( $app );
if (( $shared = $handler -> get_shared ()))
{
foreach ( $shared as $id => $owner )
{
$file = $this -> add_app ( $app , false , $id , $path . $app . '-' . $owner . '/' );
2012-02-10 12:44:52 +01:00
// mark other users calendar as shared (iOS 5.0.1 AB does NOT display AB marked as shared!)
if ( $app == 'calendar' ) $file [ 'props' ][ 'resourcetype' ][ 'val' ][] = self :: mkprop ( self :: CALENDARSERVER , 'shared' , '' );
2012-02-10 12:27:26 +01:00
$files [] = $file ;
}
2012-02-10 09:53:50 +01:00
}
return true ;
}
/**
* Format an account - name for use in displayname
*
* @ param int | array $account
* @ return string
*/
public function account_name ( $account )
{
if ( is_array ( $account ))
{
if ( $account [ 'account_id' ] < 0 )
{
$name = lang ( 'Group' ) . ' ' . $account [ 'account_lid' ];
}
else
{
$name = $account [ 'account_fullname' ];
}
}
else
{
if ( $account < 0 )
{
$name = lang ( 'Group' ) . ' ' . $this -> accounts -> id2name ( $account , 'account_lid' );
}
else
{
$name = $this -> accounts -> id2name ( $account , 'account_fullname' );
}
if ( empty ( $name )) $name = '#' . $account ;
}
return $name ;
}
2008-05-08 22:31:32 +02:00
/**
2011-11-06 10:40:33 +01:00
* Add an application collection to a user home or the root
2008-05-08 22:31:32 +02:00
*
* @ param string $app
2008-07-08 07:52:04 +02:00
* @ param boolean $no_extra_types = false should the GroupDAV and CalDAV types be added ( KAddressbook has problems with it in self URL )
2009-10-03 12:22:14 +02:00
* @ param int $user = null owner of the collection , default current user
2010-04-13 17:31:59 +02:00
* @ param string $path = '/'
2011-11-06 10:40:33 +01:00
* @ return array with values for keys 'path' and 'props'
2008-05-08 22:31:32 +02:00
*/
2011-11-06 10:40:33 +01:00
protected function add_app ( $app , $no_extra_types = false , $user = null , $path = '/' )
2008-05-08 22:31:32 +02:00
{
2010-11-03 11:05:08 +01:00
if ( $this -> debug ) error_log ( __METHOD__ . " (app=' $app ', no_extra_types= $no_extra_types , user=' $user ', path=' $path ') " );
2011-02-13 22:08:29 +01:00
$user_preferences = $GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ];
2010-03-07 00:06:43 +01:00
if ( $user )
{
$account_lid = $this -> accounts -> id2name ( $user );
2011-02-13 22:08:29 +01:00
if ( $user >= 0 && $GLOBALS [ 'egw' ] -> preferences -> account_id != $user )
{
$GLOBALS [ 'egw' ] -> preferences -> __construct ( $user );
$user_preferences = $GLOBALS [ 'egw' ] -> preferences -> read_repository ();
$GLOBALS [ 'egw' ] -> preferences -> __construct ( $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_lid' ]);
}
2010-03-07 00:06:43 +01:00
}
else
{
$account_lid = $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_lid' ];
}
2011-03-05 11:21:32 +01:00
2010-03-07 00:06:43 +01:00
$account = $this -> accounts -> read ( $account_lid );
2010-10-31 08:56:29 +01:00
2010-09-18 10:45:46 +02:00
if ( $user < 0 )
{
$principalType = 'groups' ;
}
else
{
$principalType = 'users' ;
}
2010-10-31 08:56:29 +01:00
2008-08-04 21:08:09 +02:00
$props = array (
2011-11-06 10:40:33 +01:00
'owner' => array ( self :: mkprop ( 'href' , $this -> base_uri . '/principals/' . $principalType . '/' . $account_lid . '/' )),
2010-03-07 00:06:43 +01:00
);
switch ( $app )
{
2011-11-06 10:40:33 +01:00
case 'inbox' :
2012-02-10 09:53:50 +01:00
$props [ 'displayname' ] = lang ( 'Scheduling inbox' ) . ' ' . $this -> account_name ( $user );
2011-11-06 10:40:33 +01:00
break ;
case 'outbox' :
2012-02-10 09:53:50 +01:00
$props [ 'displayname' ] = lang ( 'Scheduling outbox' ) . ' ' . $this -> account_name ( $user );
2010-03-07 00:06:43 +01:00
break ;
2012-02-10 09:53:50 +01:00
case 'addressbook' :
if ( $path == '/addressbook/' )
{
$props [ 'displayname' ] = lang ( 'All addressbooks' );
break ;
}
elseif ( ! $user && ! $GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ 'addressbook' ][ 'hide_accounts' ])
{
unset ( $props [ 'owner' ]);
$props [ 'displayname' ] = lang ( $app ) . ' ' . lang ( 'Accounts' );
break ;
}
// fall through
2010-03-07 00:06:43 +01:00
default :
2012-02-10 09:53:50 +01:00
$props [ 'displayname' ] = translation :: convert ( lang ( $app ) . ' ' . $this -> account_name ( $user ), $this -> egw_charset , 'utf-8' );
2011-11-09 14:25:19 +01:00
}
2012-02-10 12:28:56 +01:00
// rfc 5995 (Use POST to add members to WebDAV collections): we use collection path with add-member query param
/* leaving it switched off , until further testing , because OS X iCal seem to ignore it and OS X Addressbook uses POST to full URL without ? add - member
if ( $app && ! in_array ( $app , array ( 'inbox' , 'outbox' , 'principals' ))) // not on inbox, outbox or principals
{
$props [ 'add-member' ][] = self :: mkprop ( 'href' , $this -> base_uri . $path . '?add-member' );
} */
2011-11-09 14:25:19 +01:00
// add props modifyable via proppatch from client, eg. calendar-color, see self::$proppatch_props
foreach (( array ) $GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ $app ] as $name => $value )
{
2012-02-10 09:58:08 +01:00
unset ( $ns );
list ( $prop , $prop4user , $ns ) = explode ( ':' , $name , 3 );
if ( $prop4user == ( string ) $user && isset ( self :: $proppatch_props [ $prop ]) && ! isset ( $ns ))
2011-11-09 14:25:19 +01:00
{
$props [ $prop ] = self :: mkprop ( self :: $proppatch_props [ $prop ], $prop , $value );
2012-02-10 09:58:08 +01:00
//error_log(__METHOD__."() explicit ".self::$proppatch_props[$prop].":$prop=".array2string($value));
}
// props in arbitrary namespaces not mentioned in self::$ns_needs_explicit_named_props
elseif ( isset ( $ns ) && ! in_array ( $ns , self :: $ns_needs_explicit_named_props ))
{
$props [] = self :: mkprop ( $ns , $prop , $value );
//error_log(__METHOD__."() arbitrary $ns:$prop=".array2string($value));
2011-11-09 14:25:19 +01:00
}
2010-03-07 00:06:43 +01:00
}
2009-10-03 12:22:14 +02:00
foreach (( array ) $this -> root [ $app ] as $prop => $values )
2008-05-08 22:31:32 +02:00
{
2011-11-06 10:40:33 +01:00
switch ( $prop )
2008-08-04 21:08:09 +02:00
{
2011-11-06 10:40:33 +01:00
case 'resourcetype' ;
if ( ! $no_extra_types )
2008-08-04 21:08:09 +02:00
{
2011-11-06 10:40:33 +01:00
foreach ( $this -> root [ $app ][ 'resourcetype' ] as $ns => $type )
{
$props [ 'resourcetype' ][] = self :: mkprop ( $ns , $type , '' );
}
// add /addressbook/ as directory gateway
2012-02-10 09:59:10 +01:00
if ( $path == '/addressbook/' )
2011-11-06 10:40:33 +01:00
{
$props [ 'resourcetype' ][] = self :: mkprop ( self :: CARDDAV , 'directory' , '' );
}
2008-08-04 21:08:09 +02:00
}
2011-11-06 10:40:33 +01:00
break ;
case 'app' :
case 'user-only' :
break ; // no props, already handled
default :
if ( is_array ( $values ))
{
foreach ( $values as $ns => $value )
{
$props [ $prop ] = self :: mkprop ( $ns , $prop , $value );
}
}
else
{
$props [ $prop ] = $values ;
}
break ;
2008-05-08 22:31:32 +02:00
}
}
2011-11-06 10:40:33 +01:00
// add other handler specific properties
if (( $handler = self :: app_handler ( $app )))
2008-08-04 21:08:09 +02:00
{
2011-11-06 10:40:33 +01:00
if ( method_exists ( $handler , 'extra_properties' ))
{
2012-02-10 11:45:45 +01:00
$props = $handler -> extra_properties ( $props , $this -> account_name ( $account ), $this -> base_uri , $user , $path );
2011-11-06 10:40:33 +01:00
}
// add ctag if handler implements it
if ( method_exists ( $handler , 'getctag' ) && $this -> prop_requested ( 'getctag' ) === true )
{
$props [ 'getctag' ] = self :: mkprop (
groupdav :: CALENDARSERVER , 'getctag' , $handler -> getctag ( $path , $user ));
}
2008-08-04 21:08:09 +02:00
}
2012-02-10 09:53:50 +01:00
if ( $handler && $user )
{
return $this -> add_collection ( $path , $props , $handler -> current_user_privileges ( $path , $user ));
}
return $this -> add_collection ( $path , $props );
2008-05-08 22:31:32 +02:00
}
/**
* CalDAV / CardDAV REPORT method handler
*
* just calls PROPFIND ()
*
* @ param array general parameter passing array
* @ param array return array for file properties
* @ return bool true on success
*/
function REPORT ( & $options , & $files )
{
2008-05-17 14:54:26 +02:00
if ( $this -> debug > 1 ) error_log ( __METHOD__ . '(' . array2string ( $options ) . ')' );
2008-05-08 22:31:32 +02:00
return $this -> PROPFIND ( $options , $files , 'REPORT' );
}
/**
* CalDAV / CardDAV REPORT method handler to get HTTP_WebDAV_Server to process REPORT requests
*
* Just calls http_PROPFIND ()
*/
function http_REPORT ()
{
parent :: http_PROPFIND ( 'REPORT' );
}
/**
* GET method handler
*
2009-08-16 17:24:43 +02:00
* @ param array $options parameter passing array
2008-05-08 22:31:32 +02:00
* @ return bool true on success
*/
function GET ( & $options )
{
2008-05-17 14:54:26 +02:00
if ( $this -> debug ) error_log ( __METHOD__ . '(' . array2string ( $options ) . ')' );
2008-05-08 22:31:32 +02:00
2009-10-03 12:22:14 +02:00
if ( ! $this -> _parse_path ( $options [ 'path' ], $id , $app , $user ) || $app == 'principals' )
2008-05-08 22:31:32 +02:00
{
2009-08-16 17:24:43 +02:00
return $this -> autoindex ( $options );
error_log ( __METHOD__ . " ( " . array2string ( $options ) . " ) 404 Not Found " );
2008-05-08 22:31:32 +02:00
return '404 Not Found' ;
}
2008-05-17 14:54:26 +02:00
if (( $handler = self :: app_handler ( $app )))
2008-05-08 22:31:32 +02:00
{
2011-03-05 11:21:32 +01:00
return $handler -> get ( $options , $id , $user );
2008-05-08 22:31:32 +02:00
}
2009-08-16 17:24:43 +02:00
error_log ( __METHOD__ . " ( " . array2string ( $options ) . " ) 501 Not Implemented " );
2008-05-08 22:31:32 +02:00
return '501 Not Implemented' ;
}
2009-08-16 17:24:43 +02:00
/**
* Display an automatic index ( listing and properties ) for a collection
*
* @ param array $options parameter passing array , index " path " contains requested path
*/
protected function autoindex ( $options )
{
$propfind_options = array (
'path' => $options [ 'path' ],
'depth' => 1 ,
);
$files = array ();
if (( $ret = $this -> PROPFIND ( $propfind_options , $files )) !== true )
{
return $ret ; // no collection
}
2011-11-06 10:40:33 +01:00
header ( 'Content-type: text/html; charset=' . translation :: charset ());
2009-08-16 17:24:43 +02:00
echo " <html> \n <head> \n \t <title> " . 'EGroupware (Cal|Card|Group)DAV server ' . htmlspecialchars ( $options [ 'path' ]) . " </title> \n " ;
echo " \t <meta http-equiv='content-type' content='text/html; charset=utf-8' /> \n " ;
2011-11-06 10:40:33 +01:00
echo " \t <style type='text/css'> \n .th { background-color: #e0e0e0; } \n .row_on { background-color: #F1F1F1; vertical-align: top; } \n " .
" .row_off { background-color: #ffffff; vertical-align: top; } \n td { padding-left: 5px; } \n th { padding-left: 5px; text-align: left; } \n \t </style> \n " ;
2009-08-16 17:24:43 +02:00
echo " </head> \n <body> \n " ;
echo '<h1>(Cal|Card|Group)DAV ' ;
$path = '/groupdav.php' ;
2010-02-26 12:04:01 +01:00
foreach ( explode ( '/' , $this -> _unslashify ( $options [ 'path' ])) as $n => $name )
2009-08-16 17:24:43 +02:00
{
$path .= ( $n != 1 ? '/' : '' ) . $name ;
2010-02-26 12:04:01 +01:00
echo html :: a_href ( htmlspecialchars ( $name . '/' ), $path );
2009-08-16 17:24:43 +02:00
}
echo " </h1> \n " ;
2009-10-03 12:22:14 +02:00
2012-02-10 09:53:50 +01:00
static $props2show = array (
2012-02-10 12:29:55 +01:00
'DAV:displayname' => 'Displayname' ,
2012-02-10 09:53:50 +01:00
'DAV:getlastmodified' => 'Last modified' ,
'DAV:getetag' => 'ETag' ,
'DAV:getcontenttype' => 'Content type' ,
'DAV:resourcetype' => 'Resource type' ,
//'DAV:owner' => 'Owner',
//'DAV:current-user-privilege-set' => 'current-user-privilege-set',
2012-02-10 12:29:55 +01:00
//'DAV:getcontentlength' => 'Size',
2012-02-10 09:53:50 +01:00
);
2009-10-17 14:22:40 +02:00
$n = 0 ;
foreach ( $files [ 'files' ] as $file )
{
if ( ! isset ( $collection_props ))
{
2010-01-07 05:24:45 +01:00
$collection_props = $this -> props2array ( $file [ 'props' ]);
2009-10-17 14:22:40 +02:00
echo '<h3>' . lang ( 'Collection listing' ) . ': ' . htmlspecialchars ( $collection_props [ 'DAV:displayname' ]) . " </h3> \n " ;
continue ; // own entry --> displaying properies later
}
if ( ! $n ++ )
{
2012-02-10 09:53:50 +01:00
echo " <table> \n \t <tr class='th'> \n \t \t <th>#</th> \n \t \t <th> " . lang ( 'Name' ) . " </th> " ;
foreach ( $props2show as $label ) echo " \t \t <th> " . lang ( $label ) . " </th> \n " ;
echo " \t </tr> \n " ;
2009-10-17 14:22:40 +02:00
}
2010-01-07 05:24:45 +01:00
$props = $this -> props2array ( $file [ 'props' ]);
2009-10-17 14:22:40 +02:00
//echo $file['path']; _debug_array($props);
$class = $class == 'row_on' ? 'row_off' : 'row_on' ;
2010-03-07 00:06:43 +01:00
2009-10-17 14:22:40 +02:00
if ( substr ( $file [ 'path' ], - 1 ) == '/' )
{
$name = basename ( substr ( $file [ 'path' ], 0 , - 1 )) . '/' ;
}
else
{
$name = basename ( $file [ 'path' ]);
}
2010-03-07 00:06:43 +01:00
2011-11-06 10:40:33 +01:00
echo " \t <tr class=' $class '> \n \t \t <td> $n </td> \n \t \t <td> " .
html :: a_href ( htmlspecialchars ( $name ), '/groupdav.php' . strtr ( $file [ 'path' ], array (
'%' => '%25' ,
'#' => '%23' ,
'?' => '%3F' ,
))) . " </td> \n " ;
2012-02-10 09:53:50 +01:00
foreach ( $props2show as $prop => $label )
{
echo " \t \t <td> " . ( $prop == 'DAV:getlastmodified' &&! empty ( $props [ $prop ]) ? date ( 'Y-m-d H:i:s' , $props [ $prop ]) : $props [ $prop ]) . " </td> \n " ;
}
echo " \t </tr> \n " ;
2009-10-17 14:22:40 +02:00
}
if ( ! $n )
2009-08-16 17:24:43 +02:00
{
echo '<p>' . lang ( 'Collection empty.' ) . " </p> \n " ;
}
else
{
echo " </table> \n " ;
}
echo '<h3>' . lang ( 'Properties' ) . " </h3> \n " ;
echo " <table> \n \t <tr class='th'><th> " . lang ( 'Namespace' ) . " </th><th> " . lang ( 'Name' ) . " </th><th> " . lang ( 'Value' ) . " </th></tr> \n " ;
foreach ( $collection_props as $name => $value )
{
$class = $class == 'row_on' ? 'row_off' : 'row_on' ;
$ns = explode ( ':' , $name );
$name = array_pop ( $ns );
$ns = implode ( ':' , $ns );
2011-11-06 10:40:33 +01:00
echo " \t <tr class=' $class '> \n \t \t <td> " . htmlspecialchars ( $ns ) . " </td><td style='white-space: nowrap'> " . htmlspecialchars ( $name ) . " </td> \n " ;
echo " \t \t <td> " . $value . " </td> \n \t </tr> \n " ;
2009-08-16 17:24:43 +02:00
}
echo " </table> \n " ;
2011-11-06 10:40:33 +01:00
$dav = array ( 1 );
$allow = false ;
$this -> OPTIONS ( $options [ 'path' ], $dav , $allow );
echo " <p>DAV: " . implode ( ', ' , $dav ) . " </p> \n " ;
2009-08-16 17:24:43 +02:00
echo " </body> \n </html> \n " ;
common :: egw_exit ();
}
/**
* Format a property value for output
*
* @ param mixed $value
* @ return string
*/
2010-01-07 05:24:45 +01:00
protected function prop_value ( $value )
2009-08-16 17:24:43 +02:00
{
if ( is_array ( $value ))
{
if ( isset ( $value [ 0 ][ 'ns' ]))
{
2010-01-07 05:24:45 +01:00
$value = $this -> _hierarchical_prop_encode ( $value );
2009-08-16 17:24:43 +02:00
}
2011-11-06 10:40:33 +01:00
$value = array2string ( $value );
}
if ( $value [ 0 ] == '<' && function_exists ( 'tidy_repair_string' ))
{
$value = tidy_repair_string ( $value , array (
'indent' => true ,
'show-body-only' => true ,
'output-encoding' => 'utf-8' ,
'input-encoding' => 'utf-8' ,
'input-xml' => true ,
'output-xml' => true ,
'wrap' => 0 ,
));
2009-08-16 17:24:43 +02:00
}
2011-11-06 10:40:33 +01:00
if ( preg_match ( '/\<(D:)?href\>[^<]+\<\/(D:)?href\>/i' , $value ))
2009-08-16 17:24:43 +02:00
{
2012-02-10 09:53:50 +01:00
$value = '<pre>' . preg_replace ( '/\<(D:)?href\>(' . preg_quote ( $this -> base_uri . '/' , '/' ) . ')?([^<]+)\<\/(D:)?href\>/i' , '<\\1href><a href="\\2\\3">\\3</a></\\4href>' , $value ) . '</pre>' ;
2009-08-16 17:24:43 +02:00
}
else
{
2011-11-06 10:40:33 +01:00
$value = $value [ 0 ] == '<' || strpos ( $value , " \n " ) !== false ? '<pre>' . htmlspecialchars ( $value ) . '</pre>' : htmlspecialchars ( $value );
2009-08-16 17:24:43 +02:00
}
return $value ;
}
/**
* Return numeric indexed array with values for keys 'ns' , 'name' and 'val' as array 'ns:name' => 'val'
*
* @ param array $props
* @ return array
*/
2010-01-07 05:24:45 +01:00
protected function props2array ( array $props )
2009-08-16 17:24:43 +02:00
{
$arr = array ();
foreach ( $props as $prop )
{
2011-11-06 10:40:33 +01:00
$ns_hash = array ( 'DAV:' => 'D' );
2009-08-16 17:24:43 +02:00
switch ( $prop [ 'ns' ])
{
case 'DAV:' ;
$ns = 'DAV' ;
break ;
case self :: CALDAV :
2011-11-06 10:40:33 +01:00
$ns = $ns_hash [ $prop [ 'ns' ]] = 'CalDAV' ;
2009-08-16 17:24:43 +02:00
break ;
case self :: CARDDAV :
2011-11-06 10:40:33 +01:00
$ns = $ns_hash [ $prop [ 'ns' ]] = 'CardDAV' ;
2009-08-16 17:24:43 +02:00
break ;
case self :: GROUPDAV :
2011-11-06 10:40:33 +01:00
$ns = $ns_hash [ $prop [ 'ns' ]] = 'GroupDAV' ;
2009-08-16 17:24:43 +02:00
break ;
default :
$ns = $prop [ 'ns' ];
}
2011-11-06 10:40:33 +01:00
if ( is_array ( $prop [ 'val' ]))
{
$prop [ 'val' ] = $this -> _hierarchical_prop_encode ( $prop [ 'val' ], $prop [ 'ns' ], $ns_defs = '' , $ns_hash );
// hack to show real namespaces instead of not (visibly) defined shortcuts
unset ( $ns_hash [ 'DAV:' ]);
$value = strtr ( $v = $this -> prop_value ( $prop [ 'val' ]), array_flip ( $ns_hash ));
}
else
{
$value = $this -> prop_value ( $prop [ 'val' ]);
}
$arr [ $ns . ':' . $prop [ 'name' ]] = $value ;
2009-08-16 17:24:43 +02:00
}
return $arr ;
}
2010-05-17 16:20:34 +02:00
/**
* POST method handler
*
* @ param array parameter passing array
* @ return bool true on success
*/
function POST ( & $options )
{
2012-02-10 12:28:56 +01:00
// for some reason OS X Addressbook (CFNetwork user-agent) uses now (DAV:add-member given with collection URL+"?add-member")
// POST to the collection URL plus a UID like name component (like for regular PUT) to create new entrys
if ( isset ( $_GET [ 'add-member' ]) || groupdav_handler :: get_agent () == 'cfnetwork' )
{
$_GET [ 'add-member' ] = '' ; // otherwise we give no Location header
return $this -> PUT ( $options );
}
2010-05-17 16:20:34 +02:00
// read the content in a string, if a stream is given
if ( isset ( $options [ 'stream' ]))
{
$options [ 'content' ] = '' ;
while ( ! feof ( $options [ 'stream' ]))
{
$options [ 'content' ] .= fread ( $options [ 'stream' ], 8192 );
}
}
if ( $this -> debug ) error_log ( __METHOD__ . '(' . array2string ( $options ) . ')' );
2010-10-31 08:56:29 +01:00
2010-05-17 16:20:34 +02:00
$this -> _parse_path ( $options [ 'path' ], $id , $app , $user );
2010-10-31 08:56:29 +01:00
2010-05-17 16:20:34 +02:00
if (( $handler = self :: app_handler ( $app )) && method_exists ( $handler , 'post' ))
{
return $handler -> post ( $options , $id , $user );
}
return '501 Not Implemented' ;
}
2010-10-31 08:56:29 +01:00
2011-11-09 14:25:19 +01:00
/**
2012-02-10 09:58:08 +01:00
* Namespaces which need to be eplicitly named in self :: $proppatch_props ,
* because we consider them protected , if not explicitly named
*
* @ var array
*/
static $ns_needs_explicit_named_props = array ( self :: DAV , self :: CALDAV , self :: CARDDAV , self :: CALENDARSERVER );
/**
* props modifyable via proppatch from client for name - spaces mentioned in self :: $ns_needs_explicit_named_props
*
* Props named here are stored in prefs without namespace !
2011-11-09 14:25:19 +01:00
*
* @ var array name => namespace pairs
*/
static $proppatch_props = array (
'displayname' => self :: DAV ,
'calendar-description' => self :: CALDAV ,
2012-02-10 09:58:08 +01:00
'addressbook-description' => self :: CARDDAV ,
'calendar-color' => self :: ICAL , // only mentioned that old prefs still work
2011-11-09 14:25:19 +01:00
'calendar-order' => self :: ICAL ,
);
/**
* PROPPATCH method handler
*
2012-02-10 09:58:08 +01:00
* @ param array & $options general parameter passing array
* @ return string with responsedescription or null , individual status in $options [ 'props' ][][ 'status' ]
2011-11-09 14:25:19 +01:00
*/
function PROPPATCH ( & $options )
{
if ( $this -> debug ) error_log ( __CLASS__ . " :: $method ( " . array2string ( $options ) . ')' );
// parse path in form [/account_lid]/app[/more]
2012-02-10 09:58:08 +01:00
self :: _parse_path ( $options [ 'path' ], $id , $app , $user , $user_prefix ); // allways returns false if eg. !$id
2012-02-10 12:17:12 +01:00
if ( $app == 'principals' || $id || $options [ 'path' ] == '/' )
2011-11-09 14:25:19 +01:00
{
if ( $this -> debug > 1 ) error_log ( __CLASS__ . " :: $method : user=' $user ', app=' $app ', id=' $id ': 404 not found! " );
2012-02-10 09:58:08 +01:00
foreach ( $options [ 'props' ] as & $prop ) $prop [ 'status' ] = '403 Forbidden' ;
return 'NOT allowed to PROPPATCH that resource!' ;
2011-11-09 14:25:19 +01:00
}
// store selected props in preferences, eg. calendar-color, see self::$proppatch_props
2012-02-10 09:58:08 +01:00
foreach ( $options [ 'props' ] as & $prop )
2011-11-09 14:25:19 +01:00
{
2012-02-10 09:58:08 +01:00
if (( isset ( self :: $proppatch_props [ $prop [ 'name' ]]) && self :: $proppatch_props [ $prop [ 'name' ]] === $prop [ 'xmlns' ] ||
! in_array ( $prop [ 'xmlns' ], self :: $ns_needs_explicit_named_props )))
2011-11-09 14:25:19 +01:00
{
2012-02-10 12:17:12 +01:00
if ( ! $app )
{
$app = 'groupdav' ;
$name = $prop [ 'name' ] . ':' . $options [ 'path' ] . ':' . $prop [ 'ns' ];
}
else
{
$name = $prop [ 'name' ] . ':' . $user . ( isset ( self :: $proppatch_props [ $prop [ 'name' ]]) &&
self :: $proppatch_props [ $prop [ 'name' ]] == $prop [ 'ns' ] ? '' : ':' . $prop [ 'ns' ]);
}
2012-02-10 09:58:08 +01:00
//error_log("preferences['user']['$app']['$name']=".array2string($GLOBALS['egw_info']['user']['preferences'][$app][$name]).($GLOBALS['egw_info']['user']['preferences'][$app][$name] !== $prop['val'] ? ' !== ':' === ')."prop['val']=".array2string($prop['val']));
if ( $GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ $app ][ $name ] !== $prop [ 'val' ]) // nothing to change otherwise
{
if ( isset ( $prop [ 'val' ]))
{
$GLOBALS [ 'egw' ] -> preferences -> add ( $app , $name , $prop [ 'val' ]);
}
else
{
$GLOBALS [ 'egw' ] -> preferences -> delete ( $app , $name );
}
$need_save = true ;
}
$prop [ 'status' ] = '200 OK' ;
}
else
{
$prop [ 'status' ] = '409 Conflict' ; // could also be "403 Forbidden"
2011-11-09 14:25:19 +01:00
}
}
2012-02-10 09:58:08 +01:00
if ( $need_save ) $GLOBALS [ 'egw' ] -> preferences -> save_repository ();
2011-11-09 14:25:19 +01:00
}
2008-05-08 22:31:32 +02:00
/**
* PUT method handler
*
* @ param array parameter passing array
* @ return bool true on success
*/
function PUT ( & $options )
{
// read the content in a string, if a stream is given
if ( isset ( $options [ 'stream' ]))
{
$options [ 'content' ] = '' ;
while ( ! feof ( $options [ 'stream' ]))
{
$options [ 'content' ] .= fread ( $options [ 'stream' ], 8192 );
}
}
2010-10-31 08:56:29 +01:00
2008-05-17 14:54:26 +02:00
if ( $this -> debug ) error_log ( __METHOD__ . '(' . array2string ( $options ) . ')' );
2008-05-08 22:31:32 +02:00
2010-10-20 17:47:30 +02:00
if ( ! $this -> _parse_path ( $options [ 'path' ], $id , $app , $user , $prefix ))
2008-05-08 22:31:32 +02:00
{
return '404 Not Found' ;
}
2008-05-17 14:54:26 +02:00
if (( $handler = self :: app_handler ( $app )))
2008-05-08 22:31:32 +02:00
{
2010-10-20 17:47:30 +02:00
$status = $handler -> put ( $options , $id , $user , $prefix );
2008-05-08 22:31:32 +02:00
// set default stati: true --> 204 No Content, false --> should be already handled
if ( is_bool ( $status )) $status = $status ? '204 No Content' : '400 Something went wrong' ;
return $status ;
}
return '501 Not Implemented' ;
}
/**
* DELETE method handler
*
* @ param array general parameter passing array
* @ return bool true on success
*/
function DELETE ( $options )
{
2008-05-17 14:54:26 +02:00
if ( $this -> debug ) error_log ( __METHOD__ . '(' . array2string ( $options ) . ')' );
2008-05-08 22:31:32 +02:00
if ( ! $this -> _parse_path ( $options [ 'path' ], $id , $app , $user ))
{
return '404 Not Found' ;
}
2008-05-17 14:54:26 +02:00
if (( $handler = self :: app_handler ( $app )))
2008-05-08 22:31:32 +02:00
{
$status = $handler -> delete ( $options , $id );
// set default stati: true --> 204 No Content, false --> should be already handled
if ( is_bool ( $status )) $status = $status ? '204 No Content' : '400 Something went wrong' ;
return $status ;
}
return '501 Not Implemented' ;
}
/**
* MKCOL method handler
*
* @ param array general parameter passing array
* @ return bool true on success
*/
function MKCOL ( $options )
{
2008-05-17 14:54:26 +02:00
if ( $this -> debug ) error_log ( __METHOD__ . '(' . array2string ( $options ) . ')' );
2008-05-08 22:31:32 +02:00
return '501 Not Implemented' ;
}
/**
* MOVE method handler
*
* @ param array general parameter passing array
* @ return bool true on success
*/
function MOVE ( $options )
{
2008-05-17 14:54:26 +02:00
if ( $this -> debug ) error_log ( __METHOD__ . '(' . array2string ( $options ) . ')' );
2008-05-08 22:31:32 +02:00
return '501 Not Implemented' ;
}
/**
* COPY method handler
*
* @ param array general parameter passing array
* @ return bool true on success
*/
function COPY ( $options , $del = false )
{
2008-05-17 14:54:26 +02:00
if ( $this -> debug ) error_log ( 'groupdav::' . ( $del ? 'MOVE' : 'COPY' ) . '(' . array2string ( $options ) . ')' );
2008-05-08 22:31:32 +02:00
return '501 Not Implemented' ;
}
/**
* LOCK method handler
*
* @ param array general parameter passing array
* @ return bool true on success
*/
function LOCK ( & $options )
{
self :: _parse_path ( $options [ 'path' ], $id , $app , $user );
$path = egw_vfs :: app_entry_lock_path ( $app , $id );
2008-05-17 14:54:26 +02:00
if ( $this -> debug ) error_log ( __METHOD__ . '(' . array2string ( $options ) . " ) path= $path " );
2008-05-08 22:31:32 +02:00
// get the app handler, to check if the user has edit access to the entry (required to make locks)
2008-05-17 14:54:26 +02:00
$handler = self :: app_handler ( $app );
2008-05-08 22:31:32 +02:00
// TODO recursive locks on directories not supported yet
if ( ! $id || ! empty ( $options [ 'depth' ]) || ! $handler -> check_access ( EGW_ACL_EDIT , $id ))
{
return '409 Conflict' ;
}
$options [ 'timeout' ] = time () + 300 ; // 5min. hardcoded
// dont know why, but HTTP_WebDAV_Server passes the owner in D:href tags, which get's passed unchanged to checkLock/PROPFIND
// that's wrong according to the standard and cadaver does not show it on discover --> strip_tags removes eventual tags
if (( $ret = egw_vfs :: lock ( $path , $options [ 'locktoken' ], $options [ 'timeout' ], strip_tags ( $options [ 'owner' ]),
$options [ 'scope' ], $options [ 'type' ], isset ( $options [ 'update' ]), false )) && ! isset ( $options [ 'update' ])) // false = no ACL check
{
return $ret ? '200 OK' : '409 Conflict' ;
}
return $ret ;
}
/**
* UNLOCK method handler
*
* @ param array general parameter passing array
* @ return bool true on success
*/
function UNLOCK ( & $options )
{
self :: _parse_path ( $options [ 'path' ], $id , $app , $user );
$path = egw_vfs :: app_entry_lock_path ( $app , $id );
2008-05-17 14:54:26 +02:00
if ( $this -> debug ) error_log ( __METHOD__ . '(' . array2string ( $options ) . " ) path= $path " );
2008-05-08 22:31:32 +02:00
return egw_vfs :: unlock ( $path , $options [ 'token' ]) ? '204 No Content' : '409 Conflict' ;
}
/**
* checkLock () helper
*
* @ param string resource path to check for locks
* @ return bool true on success
*/
function checkLock ( $path )
{
self :: _parse_path ( $path , $id , $app , $user );
$path = egw_vfs :: app_entry_lock_path ( $app , $id );
return egw_vfs :: checkLock ( $path );
}
2010-03-07 00:06:43 +01:00
/**
* 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 ;
}
2008-05-08 22:31:32 +02:00
/**
* Parse a path into it ' s id , app and user parts
*
* @ param string $path
* @ param int & $id
* @ param string & $app addressbook , calendar , infolog ( = infolog )
* @ param int & $user
2009-10-03 12:22:14 +02:00
* @ param string & $user_prefix = null
2008-05-08 22:31:32 +02:00
* @ return boolean true on success , false on error
*/
2009-10-03 12:22:14 +02:00
function _parse_path ( $path , & $id , & $app , & $user , & $user_prefix = null )
2008-05-08 22:31:32 +02:00
{
2010-02-26 12:04:01 +01:00
if ( $this -> debug )
{
error_log ( __METHOD__ . " called with (' $path ') id= $id , app=' $app ', user= $user " );
}
if ( $path [ 0 ] == '/' )
{
2011-11-23 17:37:43 +01:00
$path = substr ( $path , 1 );
2010-02-26 12:04:01 +01:00
}
$parts = explode ( '/' , $this -> _unslashify ( $path ));
2008-05-08 22:31:32 +02:00
2010-10-31 08:56:29 +01:00
if (( $account_id = $this -> accounts -> name2id ( $parts [ 0 ], 'account_lid' )) ||
2010-10-20 16:37:48 +02:00
( $account_id = $this -> accounts -> name2id ( $parts [ 0 ] = urldecode ( $parts [ 0 ]))))
2009-10-03 12:22:14 +02:00
{
2010-02-26 12:04:01 +01:00
// /$user/$app/...
$user = array_shift ( $parts );
2009-10-03 12:22:14 +02:00
}
2008-05-08 22:31:32 +02:00
2010-02-26 12:04:01 +01:00
$app = array_shift ( $parts );
2012-02-10 09:58:08 +01:00
// /addressbook-accounts/
if ( ! $account_id && $app == 'addressbook-accounts' )
{
$app = 'addressbook' ;
$user = 0 ;
$user_prefix = '/' ;
}
2012-02-10 09:53:50 +01:00
// shared calendars/addressbooks at /<currentuser>/(calendar|addressbook|infolog)-<username>
2012-02-10 09:58:08 +01:00
elseif ( $account_id == $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ] && strpos ( $app , '-' ) !== false )
2012-02-10 09:53:50 +01:00
{
list ( $app , $username ) = explode ( '-' , $app , 2 );
2012-02-10 09:58:08 +01:00
if ( $username == 'accounts' && ! $GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ 'addressbook' ][ 'hide_accounts' ])
2012-02-10 09:53:50 +01:00
{
$account_id = 0 ;
}
elseif ( ! ( $account_id = $this -> accounts -> name2id ( $username , 'account_lid' )) &&
! ( $account_id = $this -> accounts -> name2id ( $username = urldecode ( $username ))))
{
return false ;
}
$user = $account_id ;
$user_prefix = '/' . $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_lid' ] . '/' . $app . '-' . $username ;
}
elseif ( $user )
2008-05-08 22:31:32 +02:00
{
2009-10-03 12:22:14 +02:00
$user_prefix = '/' . $user ;
2010-03-22 16:04:21 +01:00
$user = $account_id ;
2008-05-08 22:31:32 +02:00
}
else
{
2009-10-03 12:22:14 +02:00
$user_prefix = '' ;
2008-05-08 22:31:32 +02:00
$user = $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_id' ];
}
2010-02-26 12:04:01 +01:00
2011-04-05 22:39:13 +02:00
$id = array_pop ( $parts );
2010-02-26 12:04:01 +01:00
2012-02-10 12:28:56 +01:00
$ok = ( $id || isset ( $_GET [ 'add-member' ]) && $_SERVER [ 'REQUEST_METHOD' ] == 'POST' ) &&
( $user || $user === 0 ) && in_array ( $app , array ( 'addressbook' , 'calendar' , 'infolog' , 'principals' ));
2010-03-07 00:06:43 +01:00
if ( $this -> debug )
2008-05-08 22:31:32 +02:00
{
2010-03-07 00:06:43 +01:00
error_log ( __METHOD__ . " (' $path ') returning " . ( $ok ? 'true' : 'false' ) . " : id=' $id ', app=' $app ', user=' $user ', user_prefix=' $user_prefix ' " );
2008-05-08 22:31:32 +02:00
}
return $ok ;
}
2011-11-23 17:37:43 +01:00
2012-02-20 10:07:02 +01:00
private static $request_starttime ;
2011-11-23 17:37:43 +01:00
/**
* Serve WebDAV HTTP request
*
2012-02-17 10:12:23 +01:00
* Reimplemented to add logging
2011-11-23 17:37:43 +01:00
*/
function ServeRequest ()
{
if (( $debug_level = $GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ 'groupdav' ][ 'debug_level' ]) === 'r' ||
$debug_level === 'f' || $this -> debug )
{
2012-02-20 10:07:02 +01:00
self :: $request_starttime = microtime ( true );
2011-11-23 17:37:43 +01:00
$this -> store_request = true ;
ob_start ();
}
parent :: ServeRequest ();
2012-02-20 10:07:02 +01:00
if ( self :: $request_starttime ) self :: log_request ();
}
/**
* Log the request
*
* @ param string $extra = '' extra text to add below request - log , eg . exception thrown
*/
private function log_request ( $extra = '' )
{
if ( self :: $request_starttime )
2011-11-23 17:37:43 +01:00
{
2012-02-20 10:07:02 +01:00
if (( $debug_level = $GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ 'groupdav' ][ 'debug_level' ]) === 'f' )
2011-11-24 13:21:02 +01:00
{
$msg_file = $GLOBALS [ 'egw_info' ][ 'server' ][ 'files_dir' ];
$msg_file .= '/groupdav' ;
if ( ! file_exists ( $msg_file ) && ! mkdir ( $msg_file , 0700 ))
{
error_log ( __METHOD__ . " () Could NOT create directory ' $msg_file '! " );
return ;
}
$msg_file .= '/' . $GLOBALS [ 'egw_info' ][ 'user' ][ 'account_lid' ] . '-' .
str_replace ( '/' , '!' , $_SERVER [ 'HTTP_USER_AGENT' ]) . '.log' ;
2012-02-17 10:12:23 +01:00
$content = '*** ' . $_SERVER [ 'REMOTE_ADDR' ] . ' ' . date ( 'c' ) . " \n " ;
2011-11-24 13:21:02 +01:00
}
2012-02-17 10:12:23 +01:00
$content .= $_SERVER [ 'REQUEST_METHOD' ] . ' ' . $_SERVER [ 'REQUEST_URI' ] . ' HTTP/1.1' . " \n " ;
2011-11-23 17:37:43 +01:00
// reconstruct headers
foreach ( $_SERVER as $name => $value )
{
list ( $type , $name ) = explode ( '_' , $name , 2 );
2011-11-24 13:21:02 +01:00
if ( $type == 'HTTP' || $type == 'CONTENT' )
{
2012-02-17 10:12:23 +01:00
$content .= str_replace ( ' ' , '-' , ucwords ( strtolower (( $type == 'HTTP' ? '' : $type . ' ' ) . str_replace ( '_' , ' ' , $name )))) .
': ' . ( $name == 'AUTHORIZATION' ? 'Basic ***************' : $value ) . " \n " ;
2011-11-24 13:21:02 +01:00
}
2011-11-23 17:37:43 +01:00
}
2012-02-17 10:12:23 +01:00
$content .= " \n " ;
2011-11-23 17:37:43 +01:00
if ( $this -> request )
{
2012-02-18 11:50:20 +01:00
$content .= $this -> request . " \n " ;
2011-11-23 17:37:43 +01:00
}
2012-02-17 10:12:23 +01:00
$content .= 'HTTP/1.1 ' . $this -> _http_status . " \n " ;
foreach ( headers_list () as $line ) $content .= $line . " \n " ;
if (( $c = ob_get_flush ())) $content .= " \n " ;
2012-02-20 10:07:02 +01:00
if ( $debug_level !== 'f' && strlen ( $c ) > 1536 ) $c = substr ( $c , 0 , 1536 ) . " \n *** LOG TRUNKATED \n " ;
2012-02-17 10:12:23 +01:00
$content .= $c ;
2012-02-20 10:07:02 +01:00
if ( $extra ) $content .= $extra ;
$content .= sprintf ( '*** %s --> "%s" took %5.3f s' , $_SERVER [ 'REQUEST_METHOD' ] . ( $_SERVER [ 'REQUEST_METHOD' ] == 'REPORT' ? ' ' . $this -> propfind_options [ 'root' ][ 'name' ] : '' ) . ' ' . $_SERVER [ 'PATH_INFO' ], $this -> _http_status , microtime ( true ) - self :: $request_starttime ) . " \n \n " ;
2012-02-17 10:12:23 +01:00
self :: log ( $content , $msg_file );
}
}
/**
* Log to a given file or error_log
*
* @ param string $content
* @ param string $msg_file = null
*/
private static function log ( $content , $msg_file = null )
{
if ( $msg_file && ( $f = fopen ( $msg_file , 'a' )))
{
flock ( $f , LOCK_EX );
fwrite ( $f , $content );
flock ( $f , LOCK_UN );
fclose ( $f );
}
else
{
foreach ( $explode ( " \n " , $content ) as $line ) error_log ( $line );
2011-11-23 17:37:43 +01:00
}
}
2012-02-20 10:07:02 +01:00
/**
* Exception handler , which additionally logs the request ( incl . a trace )
*
* Does NOT return and get installed in constructor .
*
* @ param Exception $e
*/
public static function exception_handler ( Exception $e )
{
// logging exception as regular egw_execption_hander does
_egw_log_exception ( $e , $headline );
// exception handler sending message back to the client as basic auth message
$error = str_replace ( array ( " \r " , " \n " ), array ( '' , ' | ' ), $e -> getMessage ());
header ( 'WWW-Authenticate: Basic realm="' . $headline . ': ' . $error . '"' );
header ( 'HTTP/1.1 401 Unauthorized' );
header ( 'X-WebDAV-Status: 401 Unauthorized' , true );
// if our own logging is active, log the request plus a trace, if enabled in server-config
if ( self :: $request_starttime && isset ( $GLOBALS [ 'groupdav' ]) && is_a ( $GLOBALS [ 'groupdav' ], __CLASS__ ))
{
$GLOBALS [ 'groupdav' ] -> _http_status = '401 Unauthorized' ; // to correctly log it
if ( $GLOBALS [ 'egw_info' ][ 'server' ][ 'exception_show_trace' ])
{
$GLOBALS [ 'groupdav' ] -> log_request ( " \n " . $e -> getTraceAsString () . " \n " );
}
else
{
$GLOBALS [ 'groupdav' ] -> log_request ();
}
}
if ( is_object ( $GLOBALS [ 'egw' ]))
{
common :: egw_exit ();
}
exit ;
}
2008-05-08 22:31:32 +02:00
}