* @copyright Morphoss Ltd
* @license http://gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once('AwlQuery.php');
require_once('DAVTicket.php');
include_once ("drivers_ldap.php");
/**
* A class for things to do with a DAV Resource
*
* @package davical
*/
class DAVResource
{
/**
* @var The partial URL of the resource within our namespace, which this resource is being retrieved as
*/
protected $dav_name;
/**
* @var Boolean: does the resource actually exist yet?
*/
protected $exists;
/**
* @var The unique etag associated with the current version of the resource
*/
protected $unique_tag;
/**
* @var The actual resource content, if it exists and is not a collection
*/
protected $resource;
/**
* @var The parent of the resource, which will always be a collection
*/
protected $parent;
/**
* @var The types of the resource, possibly multiple
*/
protected $resourcetypes;
/**
* @var The type of the content
*/
protected $contenttype;
/**
* @var The canonical name which this resource exists at
*/
protected $bound_from;
/**
* @var An object which is the collection record for this resource, or for it's container
*/
private $collection;
/**
* @var An object which is the principal for this resource, or would be if it existed.
*/
private $principal;
/**
* @var A bit mask representing the current user's privileges towards this DAVResource
*/
private $privileges;
/**
* @var True if this resource is a collection of any kind
*/
private $_is_collection;
/**
* @var True if this resource is a principal-URL
*/
private $_is_principal;
/**
* @var True if this resource is a calendar collection
*/
private $_is_calendar;
/**
* @var True if this resource is a binding to another resource
*/
private $_is_binding;
/**
* @var True if this resource is an addressbook collection
*/
private $_is_addressbook;
/**
* @var True if this resource is, or is in, a proxy collection
*/
private $_is_proxy_request;
/**
* @var An array of the methods we support on this resource.
*/
private $supported_methods;
/**
* @var An array of the reports we support on this resource.
*/
private $supported_reports;
/**
* @var An array of the dead properties held for this resource
*/
private $dead_properties;
/**
* @var An array of the component types we support on this resource.
*/
private $supported_components;
/**
* @var An array of DAVTicket objects if any apply to this resource, such as via a bind.
*/
private $tickets;
/**
* Constructor
* @param mixed $parameters If null, an empty Resourced is created.
* If it is an object then it is expected to be a record that was
* read elsewhere.
*/
function __construct( $parameters = null ) {
$this->exists = null;
$this->bound_from = null;
$this->dav_name = null;
$this->unique_tag = null;
$this->resource = null;
$this->collection = null;
$this->principal = null;
$this->parent = null;
$this->resourcetypes = null;
$this->contenttype = null;
$this->privileges = null;
$this->dead_properties = null;
$this->supported_methods = null;
$this->supported_reports = null;
$this->_is_collection = false;
$this->_is_principal = false;
$this->_is_calendar = false;
$this->_is_binding = false;
$this->_is_addressbook = false;
$this->_is_proxy_request = false;
if ( isset($parameters) && is_object($parameters) ) {
$this->FromRow($parameters);
}
else if ( isset($parameters) && is_array($parameters) ) {
if ( isset($parameters['path']) ) {
$this->FromPath($parameters['path']);
}
}
else if ( isset($parameters) && is_string($parameters) ) {
$this->FromPath($parameters);
}
}
/**
* Initialise from a database row
* @param object $row The row from the DB.
*/
function FromRow($row) {
global $c;
if ( $row == null ) return;
$this->exists = true;
$this->dav_name = $row->dav_name;
$this->bound_from = (isset($row->bound_from)? $row->bound_from : $row->dav_name);
$this->_is_collection = preg_match( '{/$}', $this->dav_name );
if ( $row->owner)
{
$this->exists = true;
// $this->dav_name = $row->dav_name;
$this->dav_name = $row->owner;
//$this->bound_from = (isset($row->bound_from)? $row->bound_from : $row->dav_name);
$this->bound_from = (isset($row->bound_from)? $row->bound_from : $row->owner);
$this->_is_collection = preg_match( '{/$}', $this->dav_name );
}
else if ( $row->id_owner)
{
$this->exists = true;
// $this->dav_name = $row->dav_name;
$this->dav_name = $row->id_owner;
//$this->bound_from = (isset($row->bound_from)? $row->bound_from : $row->dav_name);
$this->bound_from = (isset($row->bound_from)? $row->bound_from : $row->id_owner);
$this->_is_collection = preg_match( '{/$}', $this->dav_name );
}
//else
// { return;
// }
if ( $this->_is_collection ) {
$this->contenttype = 'httpd/unix-directory';
$this->collection = (object) array();
$this->resource_id = $row->collection_id;
$this->_is_principal = preg_match( '{^/[^/]+/$}', $this->dav_name );
if ( preg_match( '#^(/principals/[^/]+/[^/]+)/?$#', $this->dav_name, $matches) ) {
$this->collection->dav_name = $matches[1].'/';
$this->collection->type = 'principal_link';
$this->_is_principal = true;
}
}
else {
$this->resource = (object) array();
//if ( isset($row->dav_id) ) $this->resource_id = $row->dav_id;
if ( isset($row->cal_id) ) $this->resource_id = $row->cal_id;
if ( isset($row->id_contact) ) $this->resource_id = $row->id_contact;
}
dbg_error_log( 'DAVResource', ':FromRow: Named "%s" is%s a collection.', $this->dav_name, ($this->_is_collection?'':' not') );
foreach( $row AS $k => $v ) {
if ( $this->_is_collection )
$this->collection->{$k} = $v;
else
$this->resource->{$k} = $v;
switch ( $k ) {
case 'last_update':
$lastupdate = $v;
}
}
foreach( $row AS $k => $v ) {
if ( $this->_is_collection )
$this->collection->{$k} = $v;
else
$this->resource->{$k} = $v;
switch ( $k ) {
case 'created':
case 'modified':
case 'resourcetypes':
$this->{$k} = $v;
break;
case 'cal_id':
$this->resourcetypes = '';
$this->unique_tag = '"'.md5($v.$this->dav_name.$lastupdate).'"';
break;
case 'id_contact':
$this->resourcetypes = '';
$this->unique_tag ='"'.md5($v.$this->dav_name.$lastupdate).'"';
break;
case 'dav_etag':
$this->unique_tag = '"'.$v.'"';
break;
}
}
if ( $this->_is_collection ) {
if ( !isset( $this->collection->type ) || $this->collection->type == 'collection' ) {
if ( $this->_is_principal )
$this->collection->type = 'principal';
else if ( $row->is_calendar == 't' ) {
$this->collection->type = 'calendar';
}
else if ( $row->is_addressbook == 't' ) {
$this->collection->type = 'addressbook';
}
else if ( isset($row->is_proxy) && $row->is_proxy == 't' ) {
$this->collection->type = 'proxy';
}
else if ( preg_match( '#^((/[^/]+/)\.(in|out)/)[^/]*$#', $this->dav_name, $matches ) )
$this->collection->type = 'schedule-'. $matches[3]. 'box';
else if ( $this->dav_name == '/' )
$this->collection->type = 'root';
else
$this->collection->type = 'collection';
}
$this->_is_calendar = ($this->collection->is_calendar == 't');
$this->_is_addressbook = ($this->collection->is_addressbook == 't');
$this->_is_proxy_request= ($this->collection->type == 'proxy');
if ( $this->_is_principal && !isset($this->resourcetypes) ) {
$this->resourcetypes = '';
}
else if ( $this->_is_proxy_request ) {
$this->resourcetypes = $this->collection->resourcetypes;
}
if ( isset($this->collection->dav_displayname) ) $this->collection->displayname = $this->collection->dav_displayname;
}
else {
$this->resourcetypes = '';
//if ( isset($this->resource->caldav_data) ) {
if ( isset($this->resource->owner) ) {
//if ( substr($this->resource->caldav_data,0,15) == 'BEGIN:VCALENDAR' ) $this->contenttype = 'text/calendar';
$this->contenttype = 'text/calendar';
$this->resource->displayname = $this->resource->title;
}
else
{
$this->contenttype = 'text/vcard';
$this->resource->displayname = $this->resource->given_names;
}
}
}
/**
* Initialise from a path
* @param object $inpath The path to populate the resource data from
*/
function FromPath($inpath) {
global $c;
$this->dav_name = DeconstructURL($inpath);
$this->FetchCollection();
if ( $this->_is_collection ) {
if ( $this->_is_principal || $this->collection->type == 'principal' ) $this->FetchPrincipal();
}
else {
$this->FetchResource();
}
dbg_error_log( 'DAVResource', ':FromPath: Path "%s" is%s a collection%s.',
$this->dav_name, ($this->_is_collection?' '.$this->resourcetypes:' not'), ($this->_is_principal?' and a principal':'') );
}
/**
* Find the collection associated with this resource.
*/
function FetchCollection() {
global $c, $session, $request;
/**
* RFC4918, 8.3: Identifiers for collections SHOULD end in '/'
* - also discussed at more length in 5.2
*
* So we look for a collection which matches one of the following URLs:
* - The exact request.
* - If the exact request, doesn't end in '/', then the request URL with a '/' appended
* - The request URL truncated to the last '/'
* The collection URL for this request is therefore the longest row in the result, so we
* can "... ORDER BY LENGTH(dav_name) DESC LIMIT 1"
*/
dbg_error_log( 'DAVResource', ':FetchCollection: Looking for collection for "%s".', $this->dav_name );
$this->collection = (object) array(
'collection_id' => -1,
'type' => 'nonexistent',
'is_calendar' => false, 'is_principal' => false, 'is_addressbook' => false
);
$base_sql = 'SELECT collection.*, path_privs(:session_principal::int8, collection.dav_name,:scan_depth::int), ';
$base_sql .= 'p.principal_id, p.type_id AS principal_type_id, ';
$base_sql .= 'p.displayname AS principal_displayname, p.default_privileges AS principal_default_privileges, ';
$base_sql .= 'time_zone.tz_spec ';
$base_sql .= 'FROM collection LEFT JOIN principal p USING (user_no) ';
$base_sql .= 'LEFT JOIN time_zone ON (collection.timezone=time_zone.tz_id) ';
$base_sql .= 'WHERE ';
$sql = $base_sql .'collection.dav_name = :raw_path ';
$params = array( ':raw_path' => $this->dav_name, ':session_principal' => $session->principal_id, ':scan_depth' => $c->permission_scan_depth );
if ( !preg_match( '#/$#', $this->dav_name ) ) {
$sql .= ' OR collection.dav_name = :up_to_slash OR collection.dav_name = :plus_slash ';
$params[':up_to_slash'] = preg_replace( '#[^/]*$#', '', $this->dav_name);
$params[':plus_slash'] = $this->dav_name.'/';
}
$sql .= 'ORDER BY LENGTH(collection.dav_name) DESC LIMIT 1';
$qry = new AwlQuery( $sql, $params );
if ( $qry->Exec('DAVResource') && $qry->rows() == 1 && ($row = $qry->Fetch()) ) {
$this->collection = $row;
$this->collection->exists = true;
if ( $row->is_calendar == 't' )
$this->collection->type = 'calendar';
else if ( $row->is_addressbook == 't' )
$this->collection->type = 'addressbook';
else if ( preg_match( '#^((/[^/]+/)\.(in|out)/)[^/]*$#', $this->dav_name, $matches ) )
$this->collection->type = 'schedule-'. $matches[3]. 'box';
else
$this->collection->type = 'collection';
}
else if ( preg_match( '{^( ( / ([^/]+) / ) \.(in|out)/ ) [^/]*$}x', $this->dav_name, $matches ) ) {
// The request is for a scheduling inbox or outbox (or something inside one) and we should auto-create it
$params = array( ':username' => $matches[3], ':parent_container' => $matches[2], ':dav_name' => $matches[1] );
$params[':boxname'] = ($matches[4] == 'in' ? ' Inbox' : ' Outbox');
$this->collection_type = 'schedule-'. $matches[4]. 'box';
$params[':resourcetypes'] = sprintf('', $this->collection_type );
$sql = <<Exec('DAVResource');
dbg_error_log( 'DAVResource', 'Created new collection as "%s".', trim($params[':boxname']) );
$params = array( ':raw_path' => $this->dav_name, ':session_principal' => $session->principal_id, ':scan_depth' => $c->permission_scan_depth );
$qry = new AwlQuery( $base_sql . ' dav_name = :raw_path', $params );
if ( $qry->Exec('DAVResource') && $qry->rows() == 1 && ($row = $qry->Fetch()) ) {
$this->collection = $row;
$this->collection->exists = true;
$this->collection->type = $this->collection_type;
}
}
else if ( preg_match( '#^(/([^/]+)/calendar-proxy-(read|write))/?[^/]*$#', $this->dav_name, $matches ) ) {
$this->collection->type = 'proxy';
$this->_is_proxy_request = true;
$this->proxy_type = $matches[3];
$this->collection->dav_name = $this->dav_name;
$this->collection->dav_displayname = sprintf( '%s proxy %s', $matches[2], $matches[3] );
$this->collection->exists = true;
$this->collection->parent_container = $matches[1] . '/';
}
else if ( preg_match( '#^(/[^/]+)/?$#', $this->dav_name, $matches)
|| preg_match( '#^((/principals/[^/]+/)[^/]+)/?$#', $this->dav_name, $matches) ) {
$this->_is_principal = true;
$this->FetchPrincipal();
}
else if ( $this->dav_name == '/' ) {
$this->collection->dav_name = '/';
$this->collection->type = 'root';
$this->collection->exists = true;
$this->collection->displayname = $c->system_name;
$this->collection->default_privileges = (1 | 16 | 32);
$this->collection->parent_container = '/';
}
else {
$sql = << $this->dav_name, ':session_principal' => $session->principal_id, ':scan_depth' => $c->permission_scan_depth );
if ( !preg_match( '#/$#', $this->dav_name ) ) {
$sql .= ' OR dav_binding.dav_name = :up_to_slash OR collection.dav_name = :plus_slash ';
$params[':up_to_slash'] = preg_replace( '#[^/]*$#', '', $this->dav_name);
$params[':plus_slash'] = $this->dav_name.'/';
}
$sql .= ' ORDER BY LENGTH(dav_binding.dav_name) DESC LIMIT 1';
$qry = new AwlQuery( $sql, $params );
if ( $qry->Exec('DAVResource',__LINE__,__FILE__) && $qry->rows() == 1 && ($row = $qry->Fetch()) ) {
$this->collection = $row;
$this->collection->exists = true;
$this->_is_binding = true;
$this->collection->parent_set = $row->parent_container;
$this->collection->parent_container = $row->bind_parent_container;
$this->bound_from = str_replace( $row->bound_to, $row->dav_name, $this->dav_name);
$this->collection->bound_from = $row->dav_name;
$this->collection->dav_name = $row->bound_to;
if ( isset($row->access_ticket_id) ) {
if ( !isset($this->tickets) ) $this->tickets = array();
$this->tickets[] = new DAVTicket($row->access_ticket_id);
}
if ( $row->is_calendar == 't' )
$this->collection->type = 'calendar';
else if ( $row->is_addressbook == 't' )
$this->collection->type = 'addressbook';
else if ( preg_match( '#^((/[^/]+/)\.(in|out)/)[^/]*$#', $this->dav_name, $matches ) )
$this->collection->type = 'schedule-'. $matches[3]. 'box';
else
$this->collection->type = 'collection';
}
else {
dbg_error_log( 'DAVResource', 'No collection for path "%s".', $this->dav_name );
$this->collection->exists = false;
$this->collection->dav_name = preg_replace('{/[^/]*$}', '/', $this->dav_name);
}
}
@dbg_error_log( 'DAVResource', ':FetchCollection: Found collection named "%s" of type "%s" davname "%s".', $this->collection->dav_name, $this->collection->type,$this->dav_name );
$this->_is_collection = ( $this->_is_principal || $this->collection->dav_name == $this->dav_name || $this->collection->dav_name == $this->dav_name.'/' || preg_match('{addressbook}',$this->dav_name) );
if ( $this->_is_collection ) {
$this->dav_name = $this->collection->dav_name;
$this->resource_id = $this->collection->collection_id;
$this->_is_calendar = ($this->collection->type == 'calendar');
$this->_is_addressbook = ($this->collection->type == 'addressbook');
$this->contenttype = 'httpd/unix-directory';
if ( !isset($this->exists) && isset($this->collection->exists) ) {
// If this seems peculiar it's because we only set it to false above...
$this->exists = $this->collection->exists;
}
if ( $this->exists ) {
if ( isset($this->collection->dav_etag) ) $this->unique_tag = '"'.$this->collection->dav_etag.'"';
if ( isset($this->collection->created) ) $this->created = $this->collection->created;
if ( isset($this->collection->modified) ) $this->modified = $this->collection->modified;
if ( isset($this->collection->dav_displayname) ) $this->collection->displayname = $this->collection->dav_displayname;
}
else {
if ( !isset($this->parent) ){
$this->FetchParentContainer();
}
$this->user_no = $this->parent->GetProperty('user_no');
}
if ( isset($this->collection->resourcetypes) )
$this->resourcetypes = $this->collection->resourcetypes;
else {
$this->resourcetypes = '';
if ( $this->_is_principal ) $this->resourcetypes .= '';
if ( $this->_is_addressbook ) $this->resourcetypes .= '';
if ( $this->_is_calendar ) $this->resourcetypes .= '';
}
}
}
/**
* Find the principal associated with this resource.
*/
function FetchPrincipal() {
if ( isset($this->principal) ) return;
$this->principal = new CalDAVPrincipal( array( "path" => $this->bound_from() ) );
if ( $this->_is_principal && $this->principal->Exists() ) {
$this->exists = true;
$this->unique_tag = $this->principal->dav_etag;
$this->created = $this->principal->created;
$this->modified = $this->principal->modified;
$this->resourcetypes = '';
$this->resource_id = $this->principal->principal_id;
$this->collection = $this->principal->AsCollection();
$this->user_no = $this->principal->user_no;
}
elseif ( $this->_is_principal ) {
$this->exists = false;
$this->collection->dav_name = $this->dav_name;
$this->collection->type = 'principal';
}
}
/**
* Retrieve the actual resource.
*/
function FetchResource() {
global $c, $session;
if ( isset($this->exists) ) return; // True or false, we've got what we can already
if ( $this->_is_collection ) return; // We have all we're going to read
$sql = << $this->bound_from() );
$qry = new AwlQuery( $sql, $params );
if ( $qry->Exec('DAVResource') && $qry->rows() > 0 ) {
$this->exists = true;
$this->resource = $qry->Fetch();
$this->unique_tag = $this->resource->dav_etag;
$this->created = $this->resource->created;
$this->modified = $this->resource->modified;
$this->resource_id = $this->resource->dav_id;
if ( substr($this->resource->caldav_data,0,15) == 'BEGIN:VCALENDAR' ) {
$this->contenttype = 'text/calendar';
}
$this->resourcetypes = '';
}
else {
$this->exists = false;
}
}
/**
* Fetch any dead properties for this URL
*/
function FetchDeadProperties() {
if ( isset($this->dead_properties) ) return;
$this->dead_properties = array();
$qry = new AwlQuery('SELECT property_name, property_value FROM property WHERE dav_name= :dav_name', array(':dav_name' => $this->dav_name) );
if ( $qry->Exec('DAVResource') ) {
while ( $property = $qry->Fetch() ) {
$this->dead_properties[$property->property_name] = $property->property_value;
}
}
}
/**
* Fetch the parent to this resource.
*/
function FetchParentContainer() {
if ( $this->dav_name == '/' ) return null;
if ( !isset($this->parent) ) {
if ( $this->_is_collection ) {
dbg_error_log( 'DAVResource', 'Retrieving "%s" - parent of "%s" (dav_name: %s)', $this->parent_path(), $this->collection->dav_name, $this->dav_name() );
$this->parent = new DAVResource( $this->parent_path() );
}
else {
dbg_error_log( 'DAVResource', 'Retrieving "%s" - parent of "%s" (dav_name: %s)', $this->parent_path(), $this->collection->dav_name, $this->dav_name() );
$this->parent = new DAVResource($this->collection->dav_name);
}
}
return $this->parent;
}
/**
* Build permissions for this URL
*/
function FetchPrivileges() {
global $session, $request;
if ( $this->dav_name == '/' || $this->dav_name == '' ) {
$this->privileges = (1 | 16 | 32); // read + read-acl + read-current-user-privilege-set
dbg_error_log( 'DAVResource', 'Read permissions for user accessing /' );
return;
}
if ( $session->AllowedTo('Admin') ) {
$this->privileges = privilege_to_bits('all');
dbg_error_log( 'DAVResource', 'Full permissions for an administrator.' );
return;
}
if ( $this->IsPrincipal() ) {
if ( !isset($this->principal) ) $this->FetchPrincipal();
$this->privileges = $this->principal->Privileges();
dbg_error_log( 'DAVResource', 'Privileges of "%s" for user accessing "%s"', $this->privileges, $this->principal->username() );
return;
}
if ( ! isset($this->collection) ) $this->FetchCollection();
$this->privileges = 0;
//if ( !isset($this->collection->path_privs) ) {
if ( !isset($this->parent) ) $this->FetchParentContainer();
// $this->collection->path_privs = $this->parent->Privileges();
// $this->collection->user_no = $this->parent->GetProperty('user_no');
// $this->collection->principal_id = $this->parent->GetProperty('principal_id');
//$path = preg_replace("/\/|addressbook/","",$this->collection->dav_name);
$path = preg_split('/\//', $this->collection->dav_name);
dbg_error_log( 'DAVResource', 'NOVA PASTA "%s"',$path[1]);
$filtro = "uid=".$path[1];
$atributos = array("uidNumber");
$uidnumber = ldapDrivers::requestAtributo($filtro, $atributos);
$this->collection->user_no = $uidnumber['uidNumber'];
//}
if ( $session->username != $path[1] && $this->collection->is_calendar ) {
$this->privileges = 0;
$newpath = $this->collection->user_no;
$filtro = "uid=".$session->username;
$atributos = array("uidNumber");
$accountt = ldapDrivers::requestAtributo($filtro, $atributos);
$account = $accountt['uidNumber'];
$qry = new AwlQuery("select acl_rights from phpgw_acl where acl_location = :account and acl_account = :path" ,array(':account' => $account ,':path' => $newpath));
if ( $qry->Exec('Request',__LINE__,__FILE__) && $qry->rows() == 1 && $permission_result = $qry->Fetch()){
switch( $permission_result->acl_rights ) {
case '1' : $this->privileges = privilege_to_bits( array('read','read-free-busy','read-acl'));
dbg_error_log( "caldav", "Basic read permissions for user accessing " );
break;
case '3' : $this->privileges = privilege_to_bits( array('read','read-free-busy','read-acl','write'));
dbg_error_log( "caldav", "Basic read/write permissions for user accessing " );
break;
case '7' : $this->privileges = privilege_to_bits( array('read','read-free-busy','read-acl','write'));
dbg_error_log( "caldav", "Basic read/write/edit permissions for user accessing " );
break;
case '15' : $this->privileges = privilege_to_bits( array('read','read-free-busy','read-acl','write','schedule-send'));
dbg_error_log( "caldav", "Basic read/write/edit/delete permissions for user accessing " );
break;
case '31' : $this->privileges = privilege_to_bits('all');
dbg_error_log( "caldav", "Full permissions for %s for path %s", ( $session->user_no == $this->user_no ? "user accessing their own hierarchy" : "a systems administrator"), $this->dav_name );
break;
}
}
}
else if ( $session->username == $path[1] )
{
$this->privileges = privilege_to_bits('all');
}
//$this->privileges = privilege_to_bits('all');//$this->collection->path_privs;
if ( is_string($this->privileges) ) $this->privileges = bindec( $this->privileges );
dbg_error_log( 'DAVResource', 'Privileges of "%s" for user "%s" accessing "%s"',
decbin($this->privileges), $session->username, $this->dav_name() );
if ( isset($request->ticket) && $request->ticket->MatchesPath($this->bound_from()) ) {
$this->privileges |= $request->ticket->privileges();
dbg_error_log( 'DAVResource', 'Applying permissions for ticket "%s" now: %s', $request->ticket->id(), decbin($this->privileges) );
}
if ( isset($this->tickets) ) {
if ( !isset($this->resource_id) ) $this->FetchResource();
foreach( $this->tickets AS $k => $ticket ) {
if ( $ticket->MatchesResource($this->resource_id()) || $ticket->MatchesPath($this->bound_from()) ) {
$this->privileges |= $ticket->privileges();
dbg_error_log( 'DAVResource', 'Applying permissions for ticket "%s" now: %s', $ticket->id(), decbin($this->privileges) );
}
}
}
}
/**
* Return the privileges bits for the current session user to this resource
*/
function Privileges() {
if ( !isset($this->privileges) ) $this->FetchPrivileges();
return $this->privileges;
}
/**
* Is the user has the privileges to do what is requested.
* @param $do_what mixed The request privilege name, or array of privilege names, to be checked.
* @param $any boolean Whether we accept any of the privileges. The default is true, unless the requested privilege is 'all', when it is false.
* @return boolean Whether they do have one of those privileges against this resource.
*/
function HavePrivilegeTo( $do_what, $any = null ) {
if ( !isset($this->privileges) ) $this->FetchPrivileges();
if ( !isset($any) ) $any = ($do_what != 'all');
$test_bits = privilege_to_bits( $do_what );
dbg_error_log( 'DAVResource', 'Testing %s privileges of "%s" (%s) against allowed "%s" => "%s" (%s)', ($any?'any':'exactly'),
$do_what, decbin($test_bits), decbin($this->privileges), ($this->privileges & $test_bits), decbin($this->privileges & $test_bits) );
if ( $any ) {
return ($this->privileges & $test_bits) > 0;
}
else {
return ($this->privileges & $test_bits) == $test_bits;
}
}
/**
* Check if we have the needed privilege or send an error response.
*
* @param string $privilege The name of the needed privilege.
*/
function NeedPrivilege( $privilege, $any = null ) {
global $request;
if ( $this->HavePrivilegeTo($privilege, $any) ) return;
$request->NeedPrivilege( $privilege, $this->dav_name );
exit(0); // Unecessary, but might clarify things
}
/**
* Returns the array of privilege names converted into XMLElements
*/
function BuildPrivileges( $privilege_names=null, &$xmldoc=null ) {
if ( $privilege_names == null ) {
if ( !isset($this->privileges) ) $this->FetchPrivileges();
$privilege_names = bits_to_privilege($this->privileges, ($this->_is_collection ? $this->collection->type : null ) );
}
return privileges_to_XML( $privilege_names, $xmldoc);
}
/**
* Returns the array of supported methods
*/
function FetchSupportedMethods( ) {
if ( isset($this->supported_methods) ) return $this->supported_methods;
$this->supported_methods = array(
'OPTIONS' => '',
'PROPFIND' => '',
'REPORT' => '',
'DELETE' => '',
'LOCK' => '',
'UNLOCK' => '',
'MOVE' => ''
);
if ( $this->IsCollection() ) {
/* if ( $this->IsPrincipal() ) {
$this->supported_methods['MKCALENDAR'] = '';
$this->supported_methods['MKCOL'] = '';
} */
switch ( $this->collection->type ) {
case 'root':
case 'email':
// We just override the list completely here.
$this->supported_methods = array(
'OPTIONS' => '',
'PROPFIND' => '',
'REPORT' => ''
);
break;
case 'schedule-outbox':
$this->supported_methods = array_merge(
$this->supported_methods,
array(
'POST' => '', 'PROPPATCH' => '', 'MKTICKET' => '', 'DELTICKET' => ''
)
);
break;
case 'schedule-inbox':
case 'calendar':
$this->supported_methods['GET'] = '';
$this->supported_methods['PUT'] = '';
$this->supported_methods['HEAD'] = '';
$this->supported_methods['MKTICKET'] = '';
$this->supported_methods['DELTICKET'] = '';
break;
case 'collection':
$this->supported_methods['MKTICKET'] = '';
$this->supported_methods['DELTICKET'] = '';
case 'principal':
$this->supported_methods['GET'] = '';
$this->supported_methods['PUT'] = '';
$this->supported_methods['HEAD'] = '';
$this->supported_methods['MKCOL'] = '';
$this->supported_methods['MKCALENDAR'] = '';
$this->supported_methods['PROPPATCH'] = '';
break;
}
}
else {
$this->supported_methods = array_merge(
$this->supported_methods,
array(
'GET' => '', 'HEAD' => '', 'PUT' => '', 'MKTICKET' => '', 'DELTICKET' => ''
)
);
}
return $this->supported_methods;
}
/**
* Returns the array of supported methods converted into XMLElements
*/
function BuildSupportedMethods( ) {
if ( !isset($this->supported_methods) ) $this->FetchSupportedMethods();
$methods = array();
foreach( $this->supported_methods AS $k => $v ) {
// dbg_error_log( 'DAVResource', ':BuildSupportedMethods: Adding method "%s" which is "%s".', $k, $v );
$methods[] = new XMLElement( 'supported-method', null, array('name' => $k) );
}
return $methods;
}
/**
* Returns the array of supported reports
*/
function FetchSupportedReports( ) {
if ( isset($this->supported_reports) ) return $this->supported_reports;
/************* comentado por incompatibilidade do sync-collection
$this->supported_reports = array(
'DAV::principal-property-search' => '',
'DAV::principal-search-property-set' => '',
'DAV::expand-property' => '',
'DAV::sync-collection' => ''
); */
$this->supported_reports = array(
'DAV::principal-property-search' => '',
'DAV::principal-search-property-set' => '',
'DAV::expand-property' => ''
);
if ( !isset($this->collection) ) $this->FetchCollection();
if ( $this->collection->is_calendar ) {
$this->supported_reports = array_merge(
$this->supported_reports,
array(
'urn:ietf:params:xml:ns:caldav:calendar-query' => '',
'urn:ietf:params:xml:ns:caldav:calendar-multiget' => '',
'urn:ietf:params:xml:ns:caldav:free-busy-query' => ''
)
);
}
if ( $this->collection->is_addressbook ) {
$this->supported_reports = array_merge(
$this->supported_reports,
array(
'urn:ietf:params:xml:ns:carddav:addressbook-query' => '',
'urn:ietf:params:xml:ns:carddav:addressbook-multiget' => ''
)
);
}
return $this->supported_reports;
}
/**
* Returns the array of supported reports converted into XMLElements
*/
function BuildSupportedReports( &$reply ) {
if ( !isset($this->supported_reports) ) $this->FetchSupportedReports();
$reports = array();
foreach( $this->supported_reports AS $k => $v ) {
dbg_error_log( 'DAVResource', ':BuildSupportedReports: Adding supported report "%s" which is "%s".', $k, $v );
$report = new XMLElement('report');
$reply->NSElement($report, $k );
$reports[] = new XMLElement('supported-report', $report );
}
return $reports;
}
/**
* Fetches an array of the access_ticket records applying to this path
*/
function FetchTickets( ) {
global $c;
if ( isset($this->access_tickets) ) return;
$this->access_tickets = array();
$sql =
'SELECT access_ticket.*, COALESCE( resource.dav_name, collection.dav_name) AS target_dav_name,
(access_ticket.expires < current_timestamp) AS expired,
dav_principal.dav_name AS principal_dav_name,
EXTRACT( \'epoch\' FROM (access_ticket.expires - current_timestamp)) AS seconds,
path_privs(access_ticket.dav_owner_id,collection.dav_name,:scan_depth) AS grantor_collection_privileges
FROM access_ticket JOIN collection ON (target_collection_id = collection_id)
JOIN dav_principal ON (dav_owner_id = principal_id)
LEFT JOIN caldav_data resource ON (resource.dav_id = access_ticket.target_resource_id)
WHERE target_collection_id = :collection_id ';
$params = array(':collection_id' => $this->collection->collection_id, ':scan_depth' => $c->permission_scan_depth);
if ( $this->IsCollection() ) {
$sql .= 'AND target_resource_id IS NULL';
}
else {
if ( !isset($this->exists) ) $this->FetchResource();
$sql .= 'AND target_resource_id = :dav_id';
$params[':dav_id'] = $this->resource->dav_id;
}
if ( isset($this->exists) && !$this->exists ) return;
$qry = new AwlQuery( $sql, $params );
if ( $qry->Exec('DAVResource',__LINE__,__FILE__) && $qry->rows() ) {
while( $ticket = $qry->Fetch() ) {
$this->access_tickets[] = $ticket;
}
}
}
/**
* Returns the array of tickets converted into XMLElements
*
* If the current user does not have DAV::read-acl privilege on this resource they
* will only get to see the tickets where they are the owner, or which they supplied
* along with the request.
*
* @param &XMLDocument $reply A reference to the XMLDocument used to construct the reply
* @return XMLTreeFragment A fragment of an XMLDocument to go in the reply
*/
function BuildTicketinfo( &$reply ) {
global $session, $request;
if ( !isset($this->access_tickets) ) $this->FetchTickets();
$tickets = array();
$show_all = $this->HavePrivilegeTo('DAV::read-acl');
foreach( $this->access_tickets AS $meh => $trow ) {
if ( !$show_all && ( $trow->dav_owner_id == $session->principal_id || $request->ticket->id() == $trow->ticket_id ) ) continue;
dbg_error_log( 'DAVResource', ':BuildTicketinfo: Adding access_ticket "%s" which is "%s".', $trow->ticket_id, $trow->privileges );
$ticket = new XMLElement( $reply->Tag( 'ticketinfo', 'http://www.xythos.com/namespaces/StorageServer', 'TKT' ) );
$reply->NSElement($ticket, 'http://www.xythos.com/namespaces/StorageServer:id', $trow->ticket_id );
$reply->NSElement($ticket, 'http://www.xythos.com/namespaces/StorageServer:owner', $reply->href( ConstructURL($trow->principal_dav_name)) );
$reply->NSElement($ticket, 'http://www.xythos.com/namespaces/StorageServer:timeout', (isset($trow->seconds) ? sprintf( 'Seconds-%d', $trow->seconds) : 'infinity') );
$reply->NSElement($ticket, 'http://www.xythos.com/namespaces/StorageServer:visits', 'infinity' );
$privs = array();
foreach( bits_to_privilege(bindec($trow->privileges) & bindec($trow->grantor_collection_privileges) ) AS $k => $v ) {
$privs[] = $reply->NewXMLElement($v);
}
$reply->NSElement($ticket, 'DAV::privilege', $privs );
$tickets[] = $ticket;
}
return $tickets;
}
/**
* Checks whether the resource is locked, returning any lock token, or false
*
* @todo This logic does not catch all locking scenarios. For example an infinite
* depth request should check the permissions for all collections and resources within
* that. At present we only maintain permissions on a per-collection basis though.
*/
function IsLocked( $depth = 0 ) {
if ( !isset($this->_locks_found) ) {
$this->_locks_found = array();
/**
* Find the locks that might apply and load them into an array
*/
$sql = 'SELECT * FROM locks WHERE :this_path::text ~ (\'^\'||dav_name||:match_end)::text';
$qry = new AwlQuery($sql, array( ':this_path' => $this->dav_name, ':match_end' => ($depth == DEPTH_INFINITY ? '' : '$') ) );
if ( $qry->Exec('DAVResource',__LINE__,__FILE__) ) {
while( $lock_row = $qry->Fetch() ) {
$this->_locks_found[$lock_row->opaquelocktoken] = $lock_row;
}
}
else {
$this->DoResponse(500,i18n("Database Error"));
// Does not return.
}
}
foreach( $this->_locks_found AS $lock_token => $lock_row ) {
if ( $lock_row->depth == DEPTH_INFINITY || $lock_row->dav_name == $this->dav_name ) {
return $lock_token;
}
}
return false; // Nothing matched
}
/**
* Checks whether this resource is a collection
*/
function IsCollection() {
return $this->_is_collection;
}
/**
* Checks whether this resource is a principal
*/
function IsPrincipal() {
return $this->_is_collection && $this->_is_principal;
}
/**
* Checks whether this resource is a calendar
*/
function IsCalendar() {
return $this->_is_collection && $this->_is_calendar;
}
/**
* Checks whether this resource is a calendar
* @param string $type The type of scheduling collection, 'read', 'write' or 'any'
*/
function IsSchedulingCollection( $type = 'any' ) {
if ( $this->_is_collection && preg_match( '{schedule-(inbox|outbox)}', $this->collection->type, $matches ) ) {
return ($type == 'any' || $type == $matches[1]);
}
return false;
}
/**
* Checks whether this resource is an addressbook
*/
function IsAddressbook() {
return $this->_is_collection && $this->_is_addressbook;
}
/**
* Checks whether this resource is a bind to another resource
*/
function IsBinding() {
return $this->_is_binding;
}
/**
* Checks whether this resource actually exists, in the virtual sense, within the hierarchy
*/
function Exists() {
if ( ! isset($this->exists) ) {
if ( $this->IsPrincipal() ) {
if ( !isset($this->principal) ) $this->FetchPrincipal();
$this->exists = $this->principal->Exists();
}
else if ( ! $this->IsCollection() ) {
if ( !isset($this->resource) ) $this->FetchResource();
}
}
//dbg_error_log('DAVResource',' Checking whether "%s" exists. It would appear %s.', $this->dav_name, ($this->exists ? 'so' : 'not') );
return $this->exists;
}
/**
* Checks whether the container for this resource actually exists, in the virtual sense, within the hierarchy
*/
function ContainerExists() {
if ( $this->collection->dav_name != $this->dav_name ) {
return $this->collection->exists;
}
$parent = $this->FetchParentContainer();
return $parent->Exists();
}
/**
* Returns the dav_name of the resource in our internal namespace
*/
function dav_name() {
if ( isset($this->dav_name) ) return $this->dav_name;
return null;
}
/**
* Returns the dav_name of the resource we are bound to, within our internal namespace
*/
function bound_from() {
if ( isset($this->bound_from) ) return $this->bound_from;
return $this->dav_name();
}
/**
* Sets the dav_name of the resource we are bound as
*/
function set_bind_location( $new_dav_name ) {
if ( !isset($this->bound_from) && isset($this->dav_name) ) {
$this->bound_from = $this->dav_name;
}
$this->dav_name = $new_dav_name;
return $this->dav_name;
}
/**
* Returns the dav_name of the resource in our internal namespace
*/
function parent_path() {
if ( $this->IsCollection() ) {
if ( !isset($this->collection) ) $this->FetchCollection();
if ( !isset($this->collection->parent_container) ) {
$this->collection->parent_container = preg_replace( '{[^/]+/$}', '', $this->bound_from());
}
return $this->collection->parent_container;
}
return preg_replace( '{[^/]+$}', '', $this->bound_from());
}
/**
* Returns the principal-URL for this resource
*/
function principal_url() {
if ( !isset($this->principal) ) $this->FetchPrincipal();
if ( $this->principal->Exists() ) {
return $this->principal->principal_url;
}
return null;
}
/**
* Returns the database row for this resource
*/
function resource() {
if ( !isset($this->resource) ) $this->FetchResource();
return $this->resource;
}
/**
* Returns the unique_tag (ETag ) for this resource
*/
function unique_tag() {
if ( isset($this->unique_tag) ) return $this->unique_tag;
if ( isset($this->unique_tag) )
{
$tag = md5(Date("U"));
return $tag;
}
if ( $this->IsPrincipal() && !isset($this->principal) ) $this->FetchPrincipal();
else if ( !$this->_is_collection && !isset($this->resource) ) $this->FetchResource();
if ( $this->exists !== true || !isset($this->unique_tag) ) $this->unique_tag = '';
$tag = md5(Date("U"));
return $tag;
//return $this->unique_tag;
}
/**
* Returns the unique_tag (getctag) for this resource
*/
function unique_ctag() {
//if ( isset($this->unique_tag) ) return $this->unique_tag;
if ( isset($this->unique_tag) )
{
$tag = md5(Date("U"));
return $tag;
}
if ( $this->IsPrincipal() && !isset($this->principal) ) $this->FetchPrincipal();
else if ( !$this->_is_collection && !isset($this->resource) ) $this->FetchResource();
if ( $this->exists !== true || !isset($this->unique_tag) ) $this->unique_tag = '';
$tag = md5(Date("U"));
return $tag;
//return $this->unique_tag;
}
/**
* Returns the definitive resource_id for this resource - usually a dav_id
*/
function resource_id() {
if ( isset($this->resource_id) ) return $this->resource_id;
if ( $this->IsPrincipal() && !isset($this->principal) ) $this->FetchPrincipal();
else if ( !$this->_is_collection && !isset($this->resource) ) $this->FetchResource();
if ( $this->exists !== true || !isset($this->resource_id) ) $this->resource_id = null;
return $this->resource_id;
}
/**
* Checks whether the target collection is publicly_readable
*/
function IsPublic() {
return ( isset($this->collection->publicly_readable) && $this->collection->publicly_readable == 't' );
}
/**
* Return the type of whatever contains this resource, or would if it existed.
*/
function ContainerType() {
if ( $this->IsPrincipal() ) return 'root';
if ( !$this->IsCollection() ) return $this->collection->type;
if ( ! isset($this->collection->parent_container) ) return null;
if ( isset($this->parent_container_type) ) return $this->parent_container_type;
if ( preg_match('#/[^/]+/#', $this->collection->parent_container) ) {
$this->parent_container_type = 'principal';
}
else {
$qry = new AwlQuery('SELECT * FROM collection WHERE dav_name = :parent_name',
array( ':parent_name' => $this->collection->parent_container ) );
if ( $qry->Exec('DAVResource') && $qry->rows() > 0 && $parent = $qry->Fetch() ) {
if ( $parent->is_calendar == 't' )
$this->parent_container_type = 'calendar';
else if ( $parent->is_addressbook == 't' )
$this->parent_container_type = 'addressbook';
else if ( preg_match( '#^((/[^/]+/)\.(in|out)/)[^/]*$#', $this->dav_name, $matches ) )
$this->parent_container_type = 'schedule-'. $matches[3]. 'box';
else
$this->parent_container_type = 'collection';
}
else
$this->parent_container_type = null;
}
return $this->parent_container_type;
}
/**
* BuildACE - construct an XMLElement subtree for a DAV::ace
*/
function BuildACE( &$xmldoc, $privs, $principal ) {
$privilege_names = bits_to_privilege($privs, ($this->_is_collection ? $this->collection->type : 'resource'));
$privileges = array();
foreach( $privilege_names AS $k ) {
$privilege = new XMLElement('privilege');
if ( isset($xmldoc) )
$xmldoc->NSElement($privilege,$k);
else
$privilege->NewElement($k);
$privileges[] = $privilege;
}
$ace = new XMLElement('ace', array(
new XMLElement('principal', $principal),
new XMLElement('grant', $privileges ) )
);
return $ace;
}
/**
* Return ACL settings
*/
function GetACL( &$xmldoc ) {
global $c, $session;
if ( !isset($this->principal) ) $this->FetchPrincipal();
$default_privs = $this->principal->default_privileges;
if ( isset($this->collection->default_privileges) ) $default_privs = $this->collection->default_privileges;
$acl = array();
$acl[] = $this->BuildACE($xmldoc, pow(2,25) - 1, new XMLElement('property', new XMLElement('owner')) );
$qry = new AwlQuery('SELECT dav_principal.dav_name, grants.* FROM grants JOIN dav_principal ON (to_principal=principal_id) WHERE by_collection = :collection_id OR by_principal = :principal_id ORDER BY by_collection',
array( ':collection_id' => $this->collection->collection_id, ':principal_id' => $this->principal->principal_id ) );
if ( $qry->Exec('DAVResource') && $qry->rows() > 0 ) {
$by_collection = null;
while( $grant = $qry->Fetch() ) {
if ( !isset($by_collection) ) $by_collection = isset($grant->by_collection);
if ( $by_collection && !isset($grant->by_collection) ) break;
$acl[] = $this->BuildACE($xmldoc, $grant->privileges, $xmldoc->href(ConstructURL($grant->dav_name)) );
}
}
$acl[] = $this->BuildACE($xmldoc, $default_privs, new XMLElement('authenticated') );
return $acl;
}
/**
* Return general server-related properties, in plain form
*/
function GetProperty( $name ) {
global $c, $session;
//dbg_error_log( 'DAVResource', ':GetProperty: Fetching "%s".', $name );
$value = null;
switch( $name ) {
case 'collection_id':
return $this->collection->collection_id;
break;
case 'resourcetype':
if ( isset($this->resourcetypes) ) {
$this->resourcetypes = preg_replace('{^\s*<(.*)/>\s*$}', '$1', $this->resourcetypes);
$type_list = preg_split('{(/>\s*<|\n)}', $this->resourcetypes);
foreach( $type_list AS $k => $resourcetype ) {
if ( preg_match( '{^([^:]+):([^:]+) \s+ xmlns:([^=]+)="([^"]+)" \s* $}x', $resourcetype, $matches ) ) {
$type_list[$k] = $matches[4] .':' .$matches[2];
}
else if ( preg_match( '{^([^:]+) \s+ xmlns="([^"]+)" \s* $}x', $resourcetype, $matches ) ) {
$type_list[$k] = $matches[2] .':' .$matches[1];
}
}
return $type_list;
}
default:
if ( isset($this->{$name}) ) return $this->{$name};
if ( $this->_is_principal ) {
if ( !isset($this->principal) ) $this->FetchPrincipal();
if ( isset($this->principal->{$name}) ) return $this->principal->{$name};
if ( isset($this->collection->{$name}) ) return $this->collection->{$name};
}
else if ( $this->_is_collection ) {
if ( isset($this->collection->{$name}) ) return $this->collection->{$name};
if ( isset($this->principal->{$name}) ) return $this->principal->{$name};
}
else {
if ( !isset($this->resource) ) $this->FetchResource();
if ( isset($this->resource->{$name}) ) return $this->resource->{$name};
if ( !isset($this->principal) ) $this->FetchPrincipal();
if ( isset($this->principal->{$name}) ) return $this->principal->{$name};
if ( isset($this->collection->{$name}) ) return $this->collection->{$name};
}
// dbg_error_log( 'DAVResource', ':GetProperty: Failed to find property "%s" on "%s".', $name, $this->dav_name );
}
return $value;
}
/**
* Return an array which is an expansion of the DAV::allprop
*/
function DAV_AllProperties() {
if ( isset($this->dead_properties) ) $this->FetchDeadProperties();
$allprop = array_merge( (isset($this->dead_properties)?$this->dead_properties:array()),
(isset($include_properties)?$include_properties:array()),
array(
'DAV::getcontenttype', 'DAV::resourcetype', 'DAV::getcontentlength', 'DAV::displayname', 'DAV::getlastmodified',
'DAV::creationdate', 'DAV::getetag', 'DAV::getcontentlanguage', 'DAV::supportedlock', 'DAV::lockdiscovery',
'DAV::owner', 'DAV::principal-URL', 'DAV::current-user-principal',
'urn:ietf:params:xml:ns:carddav:max-resource-size', 'urn:ietf:params:xml:ns:carddav:supported-address-data',
'urn:ietf:params:xml:ns:carddav:addressbook-description', 'urn:ietf:params:xml:ns:carddav:addressbook-home-set'
) );
return $allprop;
}
/**
* Return general server-related properties for this URL
*/
function ResourceProperty( $tag, $prop, &$reply, &$denied ) {
global $c, $session, $request;
// dbg_error_log( 'DAVResource', 'Processing "%s" on "%s".', $tag, $this->dav_name );
if ( $reply === null ) $reply = $GLOBALS['reply'];
switch( $tag ) {
case 'DAV::allprop':
$property_list = $this->DAV_AllProperties();
$discarded = array();
foreach( $property_list AS $k => $v ) {
$this->ResourceProperty($v, $prop, $reply, $discarded);
}
break;
case 'DAV::href':
$prop->NewElement('href', ConstructURL($this->dav_name) );
break;
case 'DAV::resource-id':
if ( $this->resource_id > 0 )
$reply->DAVElement( $prop, 'resource-id', $reply->href(ConstructURL('/.resources/'.$this->resource_id) ) );
else
return false;
break;
case 'DAV::parent-set':
$sql = << $this->bound_from() ) );
$parents = array();
if ( $qry->Exec('DAVResource',__LINE__,__FILE__) && $qry->rows() > 0 ) {
while( $row = $qry->Fetch() ) {
$parents[$row->parent_container] = true;
}
}
$parents[preg_replace( '{(?<=/)[^/]+/?$}','',$this->bound_from())] = true;
$parents[preg_replace( '{(?<=/)[^/]+/?$}','',$this->dav_name())] = true;
$parent_set = $reply->DAVElement( $prop, 'parent-set' );
foreach( $parents AS $parent => $v ) {
if ( preg_match( '{^(.*)?/([^/]+)/?$}', $parent, $matches ) ) {
$reply->DAVElement($parent_set, 'parent', array(
new XMLElement( 'href', ConstructURL($matches[1])),
new XMLElement( 'segment', $matches[2])
));
}
else if ( $parent == '/' ) {
$reply->DAVElement($parent_set, 'parent', array(
new XMLElement( 'href', '/'),
new XMLElement( 'segment', ( ConstructURL('/') == '/caldav.php/' ? 'caldav.php' : ''))
));
}
}
break;
case 'DAV::getcontenttype':
if ( !isset($this->contenttype) && !$this->_is_collection && !isset($this->resource) ) $this->FetchResource();
//if ( !isset($this->contenttype) && !isset($this->resource) ) $this->FetchResource();
$prop->NewElement('getcontenttype', $this->contenttype );
break;
case 'DAV::resourcetype':
$resourcetypes = $prop->NewElement('resourcetype' );
$type_list = $this->GetProperty('resourcetype');
if ( !is_array($type_list) ) return true;
// dbg_error_log( 'DAVResource', ':ResourceProperty: "%s" are "%s".', $tag, implode(', ',$type_list) );
foreach( $type_list AS $k => $v ) {
if ( $v == '' ) continue;
$reply->NSElement( $resourcetypes, $v );
}
if ( $this->_is_binding ) {
$reply->NSElement( $resourcetypes, 'http://xmlns.davical.org/davical:webdav-binding' );
}
break;
case 'DAV::getlastmodified':
/** peculiarly, it seems that getlastmodified is HTTP Date format! */
$reply->NSElement($prop, $tag, ISODateToHTTPDate($this->GetProperty('modified')) );
break;
case 'DAV::creationdate':
/** bizarrely, it seems that creationdate is ISO8601 format */
$reply->NSElement($prop, $tag, DateToISODate($this->GetProperty('created')) );
break;
case 'DAV::getcontentlength':
if ( $this->_is_collection ) return false;
if ( !isset($this->resource) ) $this->FetchResource();
if ( isset($this->resource) ) {
$reply->NSElement($prop, $tag, strlen($this->resource->caldav_data) );
}
break;
case 'DAV::getcontentlanguage':
$locale = (isset($c->current_locale) ? $c->current_locale : '');
if ( isset($this->locale) && $this->locale != '' ) $locale = $this->locale;
$reply->NSElement($prop, $tag, $locale );
break;
case 'DAV::acl-restrictions':
$reply->NSElement($prop, $tag, array( new XMLElement('grant-only'), new XMLElement('no-invert') ) );
break;
case 'DAV::inherited-acl-set':
$inherited_acls = array();
if ( ! $this->_is_collection ) {
$inherited_acls[] = $reply->href(ConstructURL($this->collection->dav_name));
}
$reply->NSElement($prop, $tag, $inherited_acls );
break;
case 'DAV::owner':
// After a careful reading of RFC3744 we see that this must be the principal-URL of the owner
$owner_url = ( isset($this->_is_binding) && $this->_is_binding ? $this->collection->bind_owner_url : $this->principal_url() );
$reply->DAVElement( $prop, 'owner', $reply->href( $owner_url ) );
break;
// Empty tag responses.
case 'DAV::group':
case 'DAV::alternate-URI-set':
$reply->NSElement($prop, $tag );
break;
case 'DAV::getetag':
if ( $this->_is_collection ) return false;
$reply->NSElement($prop, $tag, $this->unique_tag() );
break;
case 'http://calendarserver.org/ns/:getctag':
if ( ! $this->_is_collection ) return false;
$reply->NSElement($prop, $tag, $this->unique_ctag() );
break;
case 'http://calendarserver.org/ns/:calendar-proxy-read-for':
$proxy_type = 'read';
case 'http://calendarserver.org/ns/:calendar-proxy-write-for':
if ( !isset($proxy_type) ) $proxy_type = 'write';
$reply->CalendarserverElement($prop, 'calendar-proxy-'.$proxy_type.'-for', $reply->href( $this->principal->ProxyFor($proxy_type) ) );
break;
case 'DAV::current-user-privilege-set':
if ( $this->HavePrivilegeTo('DAV::read-current-user-privilege-set') ) {
//if(true){
$reply->NSElement($prop, $tag, $this->BuildPrivileges() );
}
else {
$denied[] = $tag;
}
break;
case 'urn:ietf:params:xml:ns:caldav:supported-calendar-data':
if ( ! $this->IsCalendar() && ! $this->IsSchedulingCollection() ) return false;
$reply->NSElement($prop, $tag, 'text/calendar' );
break;
case 'urn:ietf:params:xml:ns:caldav:supported-calendar-component-set':
if ( ! $this->_is_collection ) return false;
if ( $this->IsCalendar() )
$set_of_components = array( 'VEVENT', 'VTODO', 'VJOURNAL', 'VTIMEZONE', 'VFREEBUSY' );
else if ( $this->IsSchedulingCollection() )
$set_of_components = array( 'VEVENT', 'VTODO', 'VFREEBUSY' );
else return false;
$components = array();
foreach( $set_of_components AS $v ) {
$components[] = $reply->NewXMLElement( 'comp', '', array('name' => $v), 'urn:ietf:params:xml:ns:caldav');
}
$reply->CalDAVElement($prop, 'supported-calendar-component-set', $components );
break;
case 'DAV::supported-method-set':
$prop->NewElement('supported-method-set', $this->BuildSupportedMethods() );
break;
case 'DAV::supported-report-set':
$prop->NewElement('supported-report-set', $this->BuildSupportedReports( $reply ) );
break;
case 'DAV::supportedlock':
$prop->NewElement('supportedlock',
new XMLElement( 'lockentry',
array(
new XMLElement('lockscope', new XMLElement('exclusive')),
new XMLElement('locktype', new XMLElement('write')),
)
)
);
break;
case 'DAV::supported-privilege-set':
$prop->NewElement('supported-privilege-set', $request->BuildSupportedPrivileges($reply) );
break;
case 'DAV::current-user-principal':
$prop->NewElement('current-user-principal', $reply->href( $request->principal->principal_url ) );
break;
case 'SOME-DENIED-PROPERTY': /** @todo indicating the style for future expansion */
$denied[] = $reply->Tag($tag);
break;
case 'urn:ietf:params:xml:ns:caldav:calendar-timezone':
if ( ! $this->_is_collection ) return false;
if ( !isset($this->collection->tz_spec) || $this->collection->tz_spec == '' ) return false;
$cal = new iCalComponent();
$cal->VCalendar();
$cal->AddComponent( new iCalComponent($this->collection->tz_spec) );
$reply->NSElement($prop, $tag, $cal->Render() );
break;
case 'urn:ietf:params:xml:ns:carddav:address-data':
case 'urn:ietf:params:xml:ns:caldav:calendar-data':
if ( $this->_is_collection ) return false;
if ( !isset($this->resource) ) $this->FetchResource();
$reply->NSElement($prop, $tag, $this->resource->caldav_data );
break;
case 'urn:ietf:params:xml:ns:carddav:max-resource-size':
if ( ! $this->_is_collection || !$this->_is_addressbook ) return false;
$reply->NSElement($prop, $tag, 65500 );
break;
case 'urn:ietf:params:xml:ns:carddav:supported-address-data':
if ( ! $this->_is_collection || !$this->_is_addressbook ) return false;
$address_data = $reply->NewXMLElement( 'address-data', false,
array( 'content-type' => 'text/vcard', 'version' => '3.0'), 'urn:ietf:params:xml:ns:carddav');
$reply->NSElement($prop, $tag, $address_data );
break;
case 'DAV::acl':
if ( $this->HavePrivilegeTo('DAV::read-acl') ) {
$reply->NSElement($prop, $tag, $this->GetACL( $reply ) );
}
else {
$denied[] = $tag;
}
break;
case 'http://www.xythos.com/namespaces/StorageServer:ticketdiscovery':
case 'DAV::ticketdiscovery':
$reply->NSElement($prop,'http://www.xythos.com/namespaces/StorageServer:ticketdiscovery', $this->BuildTicketinfo($reply) );
break;
default:
$property_value = $this->GetProperty(preg_replace('{^.*:}', '', $tag));
if ( isset($property_value) ) {
$reply->NSElement($prop, $tag, $property_value );
}
else {
if ( !isset($this->dead_properties) ) $this->FetchDeadProperties();
if ( isset($this->dead_properties[$tag]) ) {
$reply->NSElement($prop, $tag, $this->dead_properties[$tag] );
}
else {
// dbg_error_log( 'DAVResource', 'Request for unsupported property "%s" of path "%s".', $tag, $this->dav_name );
return false;
}
}
}
return true;
}
/**
* Construct XML propstat fragment for this resource
*
* @param array of string $properties The requested properties for this resource
*
* @return string An XML fragment with the requested properties for this resource
*/
function GetPropStat( $properties, &$reply, $props_only = false ) {
global $request;
dbg_error_log('DAVResource',':GetPropStat: propstat for href "%s"', $this->dav_name );
$prop = new XMLElement('prop');
$denied = array();
$not_found = array();
foreach( $properties AS $k => $tag ) {
if ( is_object($tag) ) {
dbg_error_log( 'DAVResource', ':GetPropStat: "$properties" should be an array of text. Assuming this object is an XMLElement!.' );
$tag = $tag->GetTag();
}
$found = $this->ResourceProperty($tag, $prop, $reply, $denied );
if ( !$found ) {
if ( !isset($this->principal) ) $this->FetchPrincipal();
$found = $this->principal->PrincipalProperty( $tag, $prop, $reply, $denied );
}
if ( ! $found ) {
dbg_error_log( 'DAVResource', 'Request for unsupported property "%s" of resource "%s".', $tag, $this->dav_name );
$not_found[] = $reply->Tag($tag);
}
}
if ( $props_only ) return $prop;
$status = new XMLElement('status', 'HTTP/1.1 200 OK' );
$elements = array( new XMLElement( 'propstat', array($prop,$status) ) );
if ( count($denied) > 0 ) {
$status = new XMLElement('status', 'HTTP/1.1 403 Forbidden' );
$noprop = new XMLElement('prop');
foreach( $denied AS $k => $v ) {
$reply->NSElement($noprop, $v);
}
$elements[] = new XMLElement( 'propstat', array( $noprop, $status) );
}
if ( count($not_found) > 0 ) {
$status = new XMLElement('status', 'HTTP/1.1 404 Not Found' );
$noprop = new XMLElement('prop');
foreach( $not_found AS $k => $v ) {
$noprop->NewElement($v);
}
$elements[] = new XMLElement( 'propstat', array( $noprop, $status) );
}
return $elements;
}
/**
* Render XML for this resource
*
* @param array $properties The requested properties for this principal
* @param reference $reply A reference to the XMLDocument being used for the reply
*
* @return string An XML fragment with the requested properties for this principal
*/
function RenderAsXML( $properties, &$reply, $bound_parent_path = null ) {
global $session, $c, $request;
dbg_error_log('DAVResource',':RenderAsXML: Resource "%s" exists(%d)', $this->dav_name, $this->Exists() );
if ( !$this->Exists() ) return null;
$elements = $this->GetPropStat( $properties, $reply );
if ( isset($bound_parent_path) ) {
$dav_name = str_replace( $this->parent_path(), $bound_parent_path, $this->dav_name );
$dav_name = "/".$this->dav_name;
}
else {
$dav_name = "/".$this->dav_name;
}
array_unshift( $elements, $reply->href(ConstructURL($dav_name)));
$response = new XMLElement( 'response', $elements );
return $response;
}
}