diff --git a/inc/AwlQuery.php b/inc/AwlQuery.php index 3bd1ef49..c1283253 100644 --- a/inc/AwlQuery.php +++ b/inc/AwlQuery.php @@ -417,7 +417,7 @@ class AwlQuery if ( ! $success ) { // query failed - $this->errorstring = sprintf( 'SQL error "%s" - %s"', $this->error_info[0], $this->error_info[2]); + $this->errorstring = sprintf( 'SQL error "%s" - %s"', $this->error_info[0], (isset($this->error_info[2]) ? $this->error_info[2] : '')); $this->_log_query( $this->location, 'QF', $this->errorstring, $line, $file ); } elseif ( $this->execution_time > $this->query_time_warning ) { diff --git a/inc/CalDAVPrincipal.php b/inc/CalDAVPrincipal.php index 512a7fb4..b9eec62f 100644 --- a/inc/CalDAVPrincipal.php +++ b/inc/CalDAVPrincipal.php @@ -60,7 +60,12 @@ class CalDAVPrincipal /** * @var RFC3744: The principals that are direct members of this group. */ - var $group_member_set; + protected $_is_group; + + /** + * @var RFC3744: The principals that are direct members of this group. + */ + protected $group_member_set; /** * @var RFC3744: The groups in which the principal is directly a member. @@ -160,11 +165,13 @@ class CalDAVPrincipal foreach( $usr AS $k => $v ) { $this->{$k} = $v; } - if ( !isset($this->modified) ) $this->modified = ISODateToHTTPDate($this->updated); - if ( !isset($this->created) ) $this->created = ISODateToHTTPDate($this->joined); + if ( !isset($this->modified) ) $this->modified = $this->updated; + if ( !isset($this->created) ) $this->created = $this->joined; $this->dav_etag = md5($this->username . $this->updated); + $this->_is_group = (isset($usr->type_id) && $usr->type_id == 3); + $this->by_email = false; $this->principal_url = ConstructURL( '/'.$this->username.'/', true ); $this->url = $this->principal_url; @@ -188,15 +195,24 @@ class CalDAVPrincipal $this->xmpp_server = $c->notifications_server['host']; } - $this->group_member_set = array(); - $qry = new PgQuery('SELECT * FROM relationship LEFT JOIN usr ON (from_user = usr.user_no) LEFT JOIN role_member ON (to_user = role_member.user_no) LEFT JOIN roles USING (role_no) WHERE to_user = ? AND role_name = '."'Group'", $this->user_no ); - if ( $qry->Exec('CalDAVPrincipal') && $qry->rows > 0 ) { - while( $membership = $qry->Fetch() ) { - $this->group_member_set[] = ConstructURL( '/'. $membership->username . '/', true); + if ( $this->_is_group ) { + $this->group_member_set = array(); + $qry = new PgQuery('SELECT * FROM group_member JOIN principal ON (principal_id=member_id) JOIN usr USING(user_no) WHERE group_id = ?', $this->principal_id ); + if ( $qry->Exec('CalDAVPrincipal') && $qry->rows > 0 ) { + while( $member = $qry->Fetch() ) { + $this->group_member_set[] = ConstructURL( '/'. $member->username . '/', true); + } + } + } + + $this->group_membership = array(); + $qry = new PgQuery('SELECT * FROM group_member JOIN principal ON (principal_id=group_id) JOIN usr USING(user_no) WHERE member_id = ?', $this->principal_id ); + if ( $qry->Exec('CalDAVPrincipal') && $qry->rows > 0 ) { + while( $group = $qry->Fetch() ) { + $this->group_membership[] = ConstructURL( '/'. $group->username . '/', true); } } - $this->group_membership = null; $this->read_proxy_group = null; $this->write_proxy_group = null; $this->write_proxy_for = null; @@ -206,7 +222,7 @@ class CalDAVPrincipal * calendar-free-busy-set has been dropped from draft 5 of the scheduling extensions for CalDAV * but we'll keep replying to it for a while longer since iCal appears to want it... */ - $qry = new PgQuery('SELECT dav_name FROM collection WHERE user_no = ? AND is_calendar', $this->user_no); + $qry = new PgQuery('SELECT dav_name FROM collection WHERE user_no = ? AND is_calendar ORDER BY user_no, collection_id', $this->user_no); $this->calendar_free_busy_set = array(); if( $qry->Exec('CalDAVPrincipal',__LINE__,__FILE__) && $qry->rows > 0 ) { while( $calendar = $qry->Fetch() ) { @@ -224,14 +240,6 @@ class CalDAVPrincipal function FetchProxyGroups() { global $c; - $this->group_membership = array(); - $qry = new PgQuery('SELECT * FROM relationship LEFT JOIN usr ON (to_user = user_no) LEFT JOIN role_member USING (user_no) LEFT JOIN roles USING (role_no) WHERE from_user = ? AND role_name = '."'Group'", $this->user_no ); - if ( $qry->Exec('CalDAVPrincipal') && $qry->rows > 0 ) { - while( $membership = $qry->Fetch() ) { - $this->group_membership[] = ConstructURL( '/'. $membership->username . '/', true); - } - } - $this->read_proxy_group = array(); $this->write_proxy_group = array(); $this->write_proxy_for = array(); @@ -252,14 +260,12 @@ class CalDAVPrincipal if ($relationship->confers == 'R') { if ($relationship->from_user_no == $this->user_no) { // spec says without trailing slash, CalServ does it with slash, and so do we. - $this->group_membership[] = ConstructURL( '/'. $relationship->to_username . '/calendar-proxy-read/', true); $this->read_proxy_for[] = ConstructURL( '/'. $relationship->to_username . '/', true); } else /* ($relationship->to_user_no == $this->user_no) */ { $this->read_proxy_group[] = ConstructURL( '/'. $relationship->from_username . '/', true); } } else if (preg_match('/[WA]/', $relationship->confers)) { if ($relationship->from_user_no == $this->user_no) { - $this->group_membership[] = ConstructURL( '/'. $relationship->to_username . '/calendar-proxy-write/', true); $this->write_proxy_for[] = ConstructURL( '/'. $relationship->to_username . '/', true); } else /* ($relationship->to_user_no == $this->user_no) */ { @@ -291,29 +297,30 @@ class CalDAVPrincipal /** - * Accessor for the read proxy for + * Accessor for read or write proxy + * @param string read/write - which sort of proxy list is requested. */ - function ReadProxyFor() { + function ProxyFor( $type ) { if ( !isset($this->read_proxy_for) ) $this->FetchProxyGroups(); + if ( $type == 'write' ) return $this->write_proxy_for; return $this->read_proxy_for; } /** - * Accessor for the write proxy for + * Accessor for the group membership - the groups this principal is a member of */ - function WriteProxyFor() { - if ( !isset($this->write_proxy_for) ) $this->FetchProxyGroups(); - return $this->write_proxy_for; + function GroupMembership() { + return $this->group_membership; } /** - * Accessor for the group membership + * Accessor for the group member set - the members of this group */ - function GroupMembership() { - if ( !isset($this->group_membership) ) $this->FetchProxyGroups(); - return $this->group_membership; + function GroupMemberSet() { + if ( ! $this->_is_group ) return null; + return $this->group_member_set; } @@ -345,7 +352,7 @@ class CalDAVPrincipal $username = $user->username; } } - elseif( $user = getUserByName( $username, 'principal') ) { + elseif( $user = getUserByName( $username) ) { $user_no = $user->user_no; } return $username; @@ -376,6 +383,15 @@ class CalDAVPrincipal } + /** + * Is this a group principal? + * @return boolean Whether this is a group principal + */ + function IsGroup() { + return $this->_is_group; + } + + /** * Return the privileges bits for the current session user to this resource */ @@ -391,6 +407,7 @@ class CalDAVPrincipal $collection = (object) array( 'collection_id' => (isset($this->collection_id) ? $this->collection_id : 0), 'is_calendar' => 'f', + 'is_addressbook' => 'f', 'is_principal' => 't', 'user_no' => (isset($this->user_no) ? $this->user_no : 0), 'username' => (isset($this->username) ? $this->username : 0), @@ -405,6 +422,117 @@ class CalDAVPrincipal return $collection; } + /** + * Returns properties which are specific to this principal + */ + function PrincipalProperty( $tag, $prop, &$reply, &$denied ) { + + dbg_error_log('principal',': RenderAsXML: Principal Property "%s"', $tag ); + switch( $tag ) { + case 'DAV::getcontenttype': + $prop->NewElement('getcontenttype', 'httpd/unix-directory' ); + break; + + case 'DAV::resourcetype': + $prop->NewElement('resourcetype', array( new XMLElement('principal'), new XMLElement('collection')) ); + break; + + case 'DAV::displayname': + $prop->NewElement('displayname', $this->fullname ); + break; + + case 'DAV::principal-URL': + $prop->NewElement('principal-URL', $reply->href($this->principal_url) ); + break; + + case 'DAV::getlastmodified': + $prop->NewElement('getlastmodified', ISODateToHTTPDate($this->modified) ); + break; + + case 'DAV::creationdate': + $prop->NewElement('creationdate', DateToISODate($this->created) ); + break; + + case 'DAV::getcontentlanguage': + /** Use the principal's locale by preference, otherwise system default */ + $locale = (isset($c->current_locale) ? $c->current_locale : ''); + if ( isset($this->locale) && $this->locale != '' ) $locale = $this->locale; + $prop->NewElement('getcontentlanguage', $locale ); + break; + + case 'DAV::group-member-set': + if ( ! $this->_is_group ) return false; + $prop->NewElement('group-member-set', $reply->href($this->group_member_set) ); + break; + + case 'DAV::group-membership': + $prop->NewElement('group-membership', $reply->href($this->GroupMembership()) ); + break; + + case 'urn:ietf:params:xml:ns:caldav:schedule-inbox-URL': + $reply->CalDAVElement($prop, 'schedule-inbox-URL', $reply->href($this->schedule_inbox_url) ); + break; + + case 'urn:ietf:params:xml:ns:caldav:schedule-outbox-URL': + $reply->CalDAVElement($prop, 'schedule-outbox-URL', $reply->href($this->schedule_outbox_url) ); + break; + + case 'http://calendarserver.org/ns/:dropbox-home-URL': + $reply->CalendarserverElement($prop, 'dropbox-home-URL', $reply->href($this->dropbox_url) ); + break; + + case 'http://calendarserver.org/ns/:notifications-URL': + $reply->CalendarserverElement($prop, 'notifications-URL', $reply->href($this->notifications_url) ); + break; + + case 'http://calendarserver.org/ns/:xmpp-server': + if ( ! isset( $this->xmpp_uri ) ) return false; + $reply->CalendarserverElement($prop, 'xmpp-server', $this->xmpp_server ); + break; + + case 'http://calendarserver.org/ns/:xmpp-uri': + if ( ! isset( $this->xmpp_uri ) ) return false; + $reply->CalendarserverElement($prop, 'xmpp-uri', $this->xmpp_uri ); + break; + + case 'urn:ietf:params:xml:ns:caldav:calendar-home-set': + $reply->CalDAVElement($prop, 'calendar-home-set', $reply->href( $this->calendar_home_set ) ); + break; + + case 'urn:ietf:params:xml:ns:caldav:calendar-free-busy-set': + $reply->CalDAVElement( $prop, 'calendar-free-busy-set', $reply->href( $this->calendar_free_busy_set ) ); + break; + + case 'urn:ietf:params:xml:ns:caldav:calendar-user-address-set': + $reply->CalDAVElement($prop, 'calendar-user-address-set', $reply->href($this->user_address_set) ); + break; + + case 'DAV::owner': + // After a careful reading of RFC3744 we see that this must be the principal-URL of the owner + $reply->DAVElement( $prop, 'owner', $reply->href( $this->principal_url ) ); + break; + + case 'DAV::principal-collection-set': + $reply->DAVElement( $prop, 'principal-collection-set', $reply->href( ConstructURL('/') ) ); + break; + + // Empty tag responses. + case 'DAV::alternate-URI-set': + $prop->NewElement( $reply->Tag($tag)); + break; + + case 'SOME-DENIED-PROPERTY': /** @todo indicating the style for future expansion */ + $denied[] = $reply->Tag($tag); + break; + + default: + return false; + break; + } + + return true; + } + /** * Render XML for a single Principal (user) from the DB @@ -416,7 +544,7 @@ class CalDAVPrincipal * @return string An XML fragment with the requested properties for this principal */ function RenderAsXML( $properties, &$reply, $props_only = false ) { - global $session, $c, $request; + global $request; dbg_error_log('principal',': RenderAsXML: Principal "%s"', $this->username ); @@ -424,117 +552,9 @@ class CalDAVPrincipal $denied = array(); $not_found = array(); foreach( $properties AS $k => $tag ) { - dbg_error_log('principal',': RenderAsXML: Principal Property "%s"', $tag ); - switch( $tag ) { - case 'DAV::getcontenttype': - $prop->NewElement('getcontenttype', 'httpd/unix-directory' ); - break; - - case 'DAV::resourcetype': - $prop->NewElement('resourcetype', array( new XMLElement('principal'), new XMLElement('collection')) ); - break; - - case 'DAV::displayname': - $prop->NewElement('displayname', $this->fullname ); - break; - - case 'DAV::principal-URL': - $prop->NewElement('principal-URL', $reply->href($this->principal_url) ); - break; - - case 'DAV::getlastmodified': - $prop->NewElement('getlastmodified', $this->modified ); - break; - - case 'DAV::creationdate': - $prop->NewElement('creationdate', $this->created ); - break; - - case 'DAV::getcontentlanguage': - /** Use the principal's locale by preference, otherwise system default */ - $locale = (isset($c->current_locale) ? $c->current_locale : ''); - if ( isset($this->locale) && $this->locale != '' ) $locale = $this->locale; - $prop->NewElement('getcontentlanguage', $locale ); - break; - - case 'DAV::group-member-set': - $prop->NewElement('group-member-set', $reply->href($this->group_member_set) ); - break; - - case 'DAV::group-membership': - $prop->NewElement('group-membership', $reply->href($this->GroupMembership()) ); - break; - - case 'urn:ietf:params:xml:ns:caldav:schedule-inbox-URL': - $reply->CalDAVElement($prop, 'schedule-inbox-URL', $reply->href($this->schedule_inbox_url) ); - break; - - case 'urn:ietf:params:xml:ns:caldav:schedule-outbox-URL': - $reply->CalDAVElement($prop, 'schedule-outbox-URL', $reply->href($this->schedule_outbox_url) ); - break; - - case 'http://calendarserver.org/ns/:dropbox-home-URL': - $reply->CalendarserverElement($prop, 'dropbox-home-URL', $reply->href($this->dropbox_url) ); - break; - - case 'http://calendarserver.org/ns/:notifications-URL': - $reply->CalendarserverElement($prop, 'notifications-URL', $reply->href($this->notifications_url) ); - break; - - case 'http://calendarserver.org/ns/:xmpp-server': - if ( isset ( $this->xmpp_uri ) ) - $reply->CalendarserverElement($prop, 'xmpp-server', $this->xmpp_server ); - else - $not_found[] = $reply->Tag($tag); - break; - - case 'http://calendarserver.org/ns/:xmpp-uri': - if ( isset ( $this->xmpp_uri ) ) - $reply->CalendarserverElement($prop, 'xmpp-uri', $this->xmpp_uri ); - else - $not_found[] = $reply->Tag($tag); - break; - - case 'urn:ietf:params:xml:ns:caldav:calendar-home-set': - $reply->CalDAVElement($prop, 'calendar-home-set', $reply->href( $this->calendar_home_set ) ); - break; - - case 'urn:ietf:params:xml:ns:caldav:calendar-user-address-set': - $reply->CalDAVElement($prop, 'calendar-user-address-set', $reply->href($this->user_address_set) ); - break; - - case 'DAV::owner': - // After a careful reading of RFC3744 we see that this must be the principal-URL of the owner - $reply->DAVElement( $prop, 'owner', $reply->href( $this->principal_url ) ); - break; - - case 'DAV::principal-collection-set': - $reply->DAVElement( $prop, 'principal-collection-set', $reply->href( ConstructURL('/') ) ); - break; - - // Empty tag responses. - case 'DAV::alternate-URI-set': - case 'DAV::getcontentlength': - $prop->NewElement( $reply->Tag($tag)); - break; - - case 'SOME-DENIED-PROPERTY': /** @todo indicating the style for future expansion */ - $denied[] = $reply->Tag($tag); - break; - - case 'http://calendarserver.org/ns/:getctag': - case 'DAV::getetag': - case 'urn:ietf:params:xml:ns:caldav:supported-calendar-component-set': - // These will 404 on a Principal, since they don't apply - $not_found[] = $reply->Tag($tag); - break; - - default: - if ( ! $request->ServerProperty( $tag, $prop, $reply ) ) { - dbg_error_log( 'principal', 'Request for unsupported property "%s" of principal "%s".', $tag, $this->username ); - $not_found[] = $reply->Tag($tag); - } - break; + if ( ! $this->PrincipalProperty( $tag, $prop, $reply, $denied ) && ! $request->ServerProperty( $tag, $prop, $reply ) ) { + dbg_error_log( 'principal', 'Request for unsupported property "%s" of principal "%s".', $tag, $this->username ); + $not_found[] = $reply->Tag($tag); } } diff --git a/inc/CalDAVRequest.php b/inc/CalDAVRequest.php index ed48a4d7..6d9f6e95 100644 --- a/inc/CalDAVRequest.php +++ b/inc/CalDAVRequest.php @@ -940,7 +940,7 @@ EOSQL; /** * Return general server-related properties for this URL */ - function ServerProperty( $tag, $prop, $reply = null ) { + function ServerProperty( $tag, $prop, &$reply = null ) { global $c, $session; if ( $reply === null ) $reply = $GLOBALS['reply']; diff --git a/inc/DAVResource.php b/inc/DAVResource.php index 006342e5..9278bf57 100644 --- a/inc/DAVResource.php +++ b/inc/DAVResource.php @@ -63,37 +63,43 @@ function privilege_to_bits( $raw_privs ) { function bits_to_privilege( $raw_bits ) { $out_priv = array(); + if ( is_string($raw_bits) ) { + $raw_bits = bindec($raw_bits); + } + if ( ($raw_bits & 16777215) == 16777215 ) $out_priv[] = 'all'; - if ( (in_bits & 1) != 0 ) $out_priv[] = 'DAV::read'; - if ( (in_bits & 8) != 0 ) $out_priv[] = 'DAV::unlock'; - if ( (in_bits & 16) != 0 ) $out_priv[] = 'DAV::read-acl'; - if ( (in_bits & 32) != 0 ) $out_priv[] = 'DAV::read-current-user-privilege-set'; - if ( (in_bits & 256) != 0 ) $out_priv[] = 'DAV::write-acl'; - if ( (in_bits & 512) != 0 ) $out_priv[] = 'urn:ietf:params:xml:ns:caldav:read-free-busy'; + if ( ($raw_bits & 1) != 0 ) $out_priv[] = 'DAV::read'; + if ( ($raw_bits & 8) != 0 ) $out_priv[] = 'DAV::unlock'; + if ( ($raw_bits & 16) != 0 ) $out_priv[] = 'DAV::read-acl'; + if ( ($raw_bits & 32) != 0 ) $out_priv[] = 'DAV::read-current-user-privilege-set'; + if ( ($raw_bits & 256) != 0 ) $out_priv[] = 'DAV::write-acl'; + if ( ($raw_bits & 512) != 0 ) $out_priv[] = 'urn:ietf:params:xml:ns:caldav:read-free-busy'; - if ( (in_bits & 198) != 0 ) { - if ( (in_bits & 198) == 198 ) $out_priv[] = 'DAV::write'; - if ( (in_bits & 2) != 0 ) $out_priv[] = 'DAV::write-properties'; - if ( (in_bits & 4) != 0 ) $out_priv[] = 'DAV::write-content'; - if ( (in_bits & 64) != 0 ) $out_priv[] = 'DAV::bind'; - if ( (in_bits & 128) != 0 ) $out_priv[] = 'DAV::unbind'; + if ( ($raw_bits & 198) != 0 ) { + if ( ($raw_bits & 198) == 198 ) $out_priv[] = 'DAV::write'; + if ( ($raw_bits & 2) != 0 ) $out_priv[] = 'DAV::write-properties'; + if ( ($raw_bits & 4) != 0 ) $out_priv[] = 'DAV::write-content'; + if ( ($raw_bits & 64) != 0 ) $out_priv[] = 'DAV::bind'; + if ( ($raw_bits & 128) != 0 ) $out_priv[] = 'DAV::unbind'; } - if ( (in_bits & 7168) != 0 ) { - if ( (in_bits & 7168) == 7168 ) $out_priv[] = 'urn:ietf:params:xml:ns:caldav:schedule-deliver'; - if ( (in_bits & 1024) != 0 ) $out_priv[] = 'urn:ietf:params:xml:ns:caldav:schedule-deliver-invite'; - if ( (in_bits & 2048) != 0 ) $out_priv[] = 'urn:ietf:params:xml:ns:caldav:schedule-deliver-reply'; - if ( (in_bits & 4096) != 0 ) $out_priv[] = 'urn:ietf:params:xml:ns:caldav:schedule-query-freebusy'; + if ( ($raw_bits & 7168) != 0 ) { + if ( ($raw_bits & 7168) == 7168 ) $out_priv[] = 'urn:ietf:params:xml:ns:caldav:schedule-deliver'; + if ( ($raw_bits & 1024) != 0 ) $out_priv[] = 'urn:ietf:params:xml:ns:caldav:schedule-deliver-invite'; + if ( ($raw_bits & 2048) != 0 ) $out_priv[] = 'urn:ietf:params:xml:ns:caldav:schedule-deliver-reply'; + if ( ($raw_bits & 4096) != 0 ) $out_priv[] = 'urn:ietf:params:xml:ns:caldav:schedule-query-freebusy'; } - if ( (in_bits & 57344) != 0 ) { - if ( (in_bits & 57344) == 57344 ) $out_priv[] = 'urn:ietf:params:xml:ns:caldav:schedule-send'; - if ( (in_bits & 8192) != 0 ) $out_priv[] = 'urn:ietf:params:xml:ns:caldav:schedule-send-invite'; - if ( (in_bits & 16384) != 0 ) $out_priv[] = 'urn:ietf:params:xml:ns:caldav:schedule-send-reply'; - if ( (in_bits & 32768) != 0 ) $out_priv[] = 'urn:ietf:params:xml:ns:caldav:schedule-send-freebusy'; + if ( ($raw_bits & 57344) != 0 ) { + if ( ($raw_bits & 57344) == 57344 ) $out_priv[] = 'urn:ietf:params:xml:ns:caldav:schedule-send'; + if ( ($raw_bits & 8192) != 0 ) $out_priv[] = 'urn:ietf:params:xml:ns:caldav:schedule-send-invite'; + if ( ($raw_bits & 16384) != 0 ) $out_priv[] = 'urn:ietf:params:xml:ns:caldav:schedule-send-reply'; + if ( ($raw_bits & 32768) != 0 ) $out_priv[] = 'urn:ietf:params:xml:ns:caldav:schedule-send-freebusy'; } +// dbg_error_log( 'DAVResource', ' Privilege bit "%s" is "%s".', $raw_bits, implode(', ', $out_priv) ); + return $out_priv; } @@ -126,9 +132,9 @@ class DAVResource protected $resource; /** - * @var The type of the resource, possibly multiple + * @var The types of the resource, possibly multiple */ - protected $resourcetype; + protected $resourcetypes; /** * @var The type of the content @@ -170,6 +176,11 @@ class DAVResource */ 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. */ @@ -180,6 +191,11 @@ class DAVResource */ 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. */ @@ -198,14 +214,16 @@ class DAVResource $this->resource = null; $this->collection = null; $this->principal = null; - $this->resourcetype = null; + $this->resourcetypes = null; $this->contenttype = null; $this->privileges = null; + $this->dead_properties = null; - $this->_is_collection = false; - $this->_is_principal = false; - $this->_is_calendar = false; + $this->_is_collection = false; + $this->_is_principal = false; + $this->_is_calendar = false; $this->_is_addressbook = false; + $this->_is_proxy = false; if ( isset($parameters) && is_object($parameters) ) { $this->FromRow($parameters); } @@ -230,19 +248,73 @@ class DAVResource if ( $row == null ) return; $this->exists = true; + $this->dav_name = $row->dav_name; $this->_is_collection = preg_match( '{/$}', $row->dav_name ); + if ( $this->_is_collection ) { + $this->contenttype = 'httpd/unix-directory'; + $this->collection = (object) array(); + + $this->_is_principal = preg_match( '{^/[^/]+/$}', $row->dav_name ); + if ( preg_match( '#^(/principals/[^/]+/[^/]+)/?$#', $row->dav_name, $matches) ) { + $this->collection->dav_name = $matches[1].'/'; + $this->collection->type = 'principal_link'; + $this->_is_principal = true; + } + } + else { + $this->resource = (object) array(); + } + + dbg_error_log( 'DAVResource', ':FromRow: Named "%s" is%s a collection.', $row->dav_name, ($this->_is_collection?'':' not') ); + foreach( $row AS $k => $v ) { - dbg_error_log( 'DAVResource', 'Processing resource property "%s" has "%s".', $row->dav_name, $k ); - $this->resource->{$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 'dav_etag': $this->unique_tag = '"'.$v.'"'; break; - case 'is_calendar': if ( $this->_is_collection) $this->_is_calendar = ($v == 't'); break; - case 'is_addressbook': if ( $this->_is_collection) $this->_is_addressbook = ($v == 't'); break; - case 'is_principal': if ( $this->_is_collection) $this->_is_principal = ($v == 't'); 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 ( 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'); + if ( $this->_is_principal && !isset($this->resourcetypes) ) { + $this->resourcetypes = ''; + } + if ( isset($this->collection->dav_displayname) ) $this->collection->displayname = $this->collection->dav_displayname; + } + else { + $this->resourcetypes = ''; + if ( isset($this->resource->caldav_data) ) { + if ( substr($this->resource->caldav_data,0,15) == 'BEGIN:VCALENDAR' ) $this->contenttype = 'text/calendar'; + $this->resource->displayname = $this->resource->summary; } } } @@ -276,6 +348,16 @@ class DAVResource if ( substr($ourpath,0,1) != '/' ) $ourpath = '/'.$ourpath; $this->dav_name = $ourpath; + + $this->FetchCollection(); + if ( $this->_is_collection ) { + if ( $this->_is_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':'') ); } @@ -295,10 +377,12 @@ class DAVResource * 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, 'resourcetypes' => '', + 'is_calendar' => false, 'is_principal' => false, 'is_addressbook' => false ); $base_sql = 'SELECT collection.*, path_privileges(:session_principal, collection.dav_name), '; @@ -316,6 +400,7 @@ class DAVResource $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' ) @@ -343,13 +428,16 @@ EOSQL; $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; } } - else if ( preg_match( '#^((/[^/]+/)calendar-proxy-(read|write))/?[^/]*$#', $this->dav_name, $matches ) ) { + 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 = $matches[1].'/'; + $this->collection->dav_displayname = sprintf( '%s proxy %s', $matches[2], $matches[3] ); + $this->collection->exists = true; } else if ( preg_match( '#^(/[^/]+)/?$#', $this->dav_name, $matches) ) { $this->collection->dav_name = $matches[1].'/'; @@ -364,6 +452,8 @@ EOSQL; else if ( $this->dav_name == '/' ) { $this->collection->dav_name = '/'; $this->collection->type = 'root'; + $this->collection->exists = true; + $this->collection->displayname = $c->system_name; } else { dbg_error_log( 'DAVResource', 'No collection for path "%s".', $this->dav_name ); @@ -371,24 +461,29 @@ EOSQL; $this->collection->dav_name = preg_replace('{/[^/]*$}', '/', $this->dav_name); } + dbg_error_log( 'DAVResource', ':FetchCollection: Found collection named "%s" of type "%s".', $this->collection->dav_name, $this->collection->type ); + $this->_is_collection = ( $this->collection->dav_name == $this->dav_name || $this->collection->dav_name == $this->dav_name.'/' ); if ( $this->_is_collection ) { $this->dav_name = $this->collection->dav_name; $this->_is_calendar = ($this->collection->type == 'calendar'); $this->_is_addressbook = ($this->collection->type == 'addressbook'); $this->contenttype = 'httpd/unix-directory'; - 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->resourcetype) ) - $this->resourcetype = $this->collection->resourcetype; + 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; + } + if ( isset($this->collection->resourcetypes) ) + $this->resourcetypes = $this->collection->resourcetypes; else { - $this->resourcetype = ''; - if ( $this->_is_principal ) - $this->resourcetype .= ''; - else { - $this->exists = (!isset($this->collection->exists) || $this->collection->exists); - } + $this->resourcetypes = ''; + if ( $this->_is_principal ) $this->resourcetypes .= ''; } } } @@ -400,12 +495,13 @@ EOSQL; function FetchPrincipal() { global $c, $session; $this->principal = new CalDAVPrincipal( array( "path" => $this->dav_name ) ); - if ( $this->IsPrincipal() ) { - $this->contenttype = 'httpd/unix-directory'; + if ( $this->_is_principal && $this->principal->Exists() ) { +// $this->contenttype = 'httpd/unix-directory'; + $this->exists = true; $this->unique_tag = $this->principal->dav_etag; $this->created = $this->principal->created; $this->modified = $this->principal->modified; - $this->resourcetype = ''; + $this->resourcetypes = ''; } } @@ -417,7 +513,6 @@ EOSQL; global $c, $session; if ( isset($this->exists) ) return; // True or false, we've got what we can already - if ( !isset($this->collection) ) $this->FetchCollection(); if ( $this->_is_collection ) return; // We have all we're going to read $sql = <<unique_tag = $this->resource->dav_etag; $this->created = $this->resource->created; $this->modified = $this->resource->modified; - $this->contenttype = 'text/calendar'; - $this->resourcetype = ''; + if ( substr($this->resource->caldav_data,0,15) == 'BEGIN:VCALENDAR' ) { + $this->contenttype = 'text/calendar'; + } + $this->resourcetypes = ''; } else { $this->exists = false; @@ -442,6 +539,22 @@ EOQRY; } + /** + * 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; + } + } + } + + /** * Build permissions for this URL */ @@ -450,32 +563,32 @@ EOQRY; if ( $this->dav_name == '/' || $this->dav_name == '' ) { $this->privileges = 1; // read -// dbg_error_log( 'DAVResource', 'Read permissions for user accessing /' ); + 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.' ); + 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 principal "%s"', $this->privileges, $this->principal->username ); + dbg_error_log( 'DAVResource', 'Privileges of "%s" for user accessing principal "%s"', $this->privileges, $this->principal->username ); return; } $this->privileges = 0; - if ( !isset($this->collection) ) $this->FetchCollection(); if ( !isset($this->collection->path_privileges) ) { $parent_path = preg_replace('{/[^/]*/$}', '/', $this->collection->dav_name ); -// dbg_error_log( 'DAVResource', 'Checking privileges of "%s" - parent of "%s"', $parent_path, $this->collection->dav_name ); + dbg_error_log( 'DAVResource', 'Checking privileges of "%s" - parent of "%s"', $parent_path, $this->collection->dav_name ); $parent = new DAVResource( $parent_path ); $this->collection->path_privileges = $parent->Privileges(); + $this->collection->user_no = $parent->GetProperty('user_no'); } $this->privileges = $this->collection->path_privileges; @@ -505,7 +618,7 @@ EOQRY; /** * Returns the array of privilege names converted into XMLElements */ - function BuildPrivileges( $privilege_names=null, $xmldoc=null ) { + function BuildPrivileges( $privilege_names=null, &$xmldoc=null ) { if ( $privilege_names == null ) { if ( !isset($this->privileges) ) $this->FetchPrivileges(); $privilege_names = bits_to_privilege($this->privileges); @@ -530,7 +643,6 @@ EOQRY; */ function FetchSupportedMethods( ) { if ( isset($this->supported_methods) ) return $this->supported_methods; - if ( !isset($this->collection) ) $this->FetchCollection(); $this->supported_methods = array( 'OPTIONS' => '', @@ -643,7 +755,7 @@ EOQRY; * Checks whether this resource is a principal */ function IsPrincipal() { - return $this->_is_collection; + return $this->_is_collection && $this->_is_principal; } @@ -651,7 +763,19 @@ EOQRY; * Checks whether this resource is a calendar */ function IsCalendar() { - return $this->_is_calendar; + 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; } @@ -659,7 +783,7 @@ EOQRY; * Checks whether this resource is an addressbook */ function IsAddressbook() { - return $this->_is_addressbook; + return $this->_is_collection && $this->_is_addressbook; } @@ -672,10 +796,7 @@ EOQRY; if ( !isset($this->principal) ) $this->FetchPrincipal(); $this->exists = $this->principal->Exists(); } - else if ( $this->IsCollection() ) { - if ( !isset($this->collection) ) $this->FetchCollection(); - } - else { + else if ( ! $this->IsCollection() ) { if ( !isset($this->resource) ) $this->FetchResource(); } } @@ -710,11 +831,8 @@ EOQRY; */ function unique_tag() { if ( isset($this->unique_tag) ) return $this->unique_tag; - if ( $this->IsCollection() && !isset($this->collection) ) { - $this->FetchCollection(); - if ( $this->IsPrincipal() && !isset($this->principal) ) $this->FetchPrincipal(); - } - else if ( !isset($this->resource) ) $this->FetchResource(); + 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 = ''; @@ -726,7 +844,6 @@ EOQRY; * Checks whether the target collection is publicly_readable */ function IsPublic() { - if ( !isset($this->collection) ) $this->FetchCollection(); return ( isset($this->collection->publicly_readable) && $this->collection->publicly_readable == 't' ); } @@ -735,7 +852,6 @@ EOQRY; * Return the type of whatever contains this resource, or would if it existed. */ function ContainerType() { - if ( !isset($this->collection) ) $this->FetchCollection(); if ( $this->IsPrincipal() ) return 'root'; if ( !$this->IsCollection() ) return $this->collection->type; @@ -772,15 +888,21 @@ EOQRY; function GetProperty( $name ) { global $c, $session; -// dbg_error_log( 'DAVResource', 'Processing "%s".', $name ); +// dbg_error_log( 'DAVResource', ':GetProperty: Fetching "%s".', $name ); $value = null; switch( $name ) { case 'collection_id': - if ( !isset($this->collection) ) $this->FetchCollection(); return $this->collection->collection_id; break; + case 'resourcetype': + if ( isset($this->resourcetypes) ) { + $this->resourcetypes = preg_replace('{^<(.*)/>$}', '$1', $this->resourcetypes); + $type_list = explode('/><', $this->resourcetypes); + return $type_list; + } + default: if ( $this->_is_principal ) { if ( !isset($this->principal) ) $this->FetchPrincipal(); @@ -788,7 +910,6 @@ EOQRY; if ( isset($this->collection->{$name}) ) return $this->collection->{$name}; } else if ( $this->_is_collection ) { - if ( !isset($this->collection) ) $this->FetchCollection(); if ( isset($this->collection->{$name}) ) return $this->collection->{$name}; if ( isset($this->principal->{$name}) ) return $this->principal->{$name}; } @@ -797,55 +918,89 @@ EOQRY; 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) ) $this->FetchCollection(); if ( isset($this->collection->{$name}) ) return $this->collection->{$name}; } - dbg_error_log( 'ERROR', 'Request for property "%s" which is not understood.', $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()), 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' + ) ); + + return $allprop; + } + + /** * Return general server-related properties for this URL */ - function ResourceProperty( $tag, $prop, $reply = null, &$denied ) { + function ResourceProperty( $tag, $prop, &$reply, &$denied ) { global $c, $session; +// dbg_error_log( 'DAVResource', 'Processing "%s" on "%s".', $tag, $this->dav_name ); + if ( $reply === null ) $reply = $GLOBALS['reply']; - dbg_error_log( 'DAVResource', 'Processing "%s" on "%s".', $tag, $this->dav_name ); - 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::getcontenttype': - if ( isset($this->contenttype) ) $prop->NewElement('getcontenttype', $this->contenttype ); + if ( !isset($this->contenttype) && !$this->_is_collection && !isset($this->resource) ) $this->FetchResource(); + $prop->NewElement('getcontenttype', $this->contenttype ); break; case 'DAV::resourcetype': - $prop->NewElement('resourcetype', $this->resourcetype ); - break; - - case 'DAV::displayname': - if ( isset($this->displayname) ) $prop->NewElement('displayname', $this->displayname ); + $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 ); + } break; case 'DAV::getlastmodified': - $prop->NewElement('getlastmodified', $this->modified ); + /** peculiarly, it seems that getlastmodified is HTTP Date format! */ + $reply->NSElement($prop, $tag, ISODateToHTTPDate($this->GetProperty('modified')) ); break; case 'DAV::creationdate': - $prop->NewElement('creationdate', $this->created ); + /** 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(); + $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; - $prop->NewElement('getcontentlanguage', $locale ); + $reply->NSElement($prop, $tag, $locale ); break; case 'DAV::owner': @@ -855,46 +1010,77 @@ EOQRY; // Empty tag responses. case 'DAV::alternate-URI-set': - case 'DAV::getcontentlength': - $prop->NewElement( $reply->Tag($tag)); + $reply->NSElement($prop, $tag ); break; case 'DAV::getetag': - if ( $this->_is_collection ) { - return false; - } - else { - $prop->NewElement('getetag', $this->unique_tag ); + 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_tag() ); + break; + + case 'http://calendarserver.org/ns/:calendar-proxy-read-for': + $proxy_type = 'read'; + case 'http://calendarserver.org/ns/:calendar-proxy-write-for': + if ( ! $this->_is_proxy_request ) return true; + 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': + $reply->NSElement($prop, $tag, $this->BuildPrivileges() ); + 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 'SOME-DENIED-PROPERTY': /** @todo indicating the style for future expansion */ $denied[] = $reply->Tag($tag); break; - case 'http://calendarserver.org/ns/:getctag': - if ( $this->_is_collection ) { - $prop->NewElement('http://calendarserver.org/ns/:getctag', $this->unique_tag ); - } - else { - return false; - } - break; - case 'urn:ietf:params:xml:ns:caldav:calendar-data': - if ( $this->_is_collection ) { - if ( !isset($this->resource) ) $this->FetchResource(); - $reply->CalDAVElement($prop, $k, $this->resource->caldav_data ); - } - else { - return false; - } + if ( $this->_is_collection ) return false; + if ( !isset($this->resource) ) $this->FetchResource(); + $reply->NSElement($prop, $tag, $this->resource->caldav_data ); break; default: - dbg_error_log( 'DAVResource', 'Request for unsupported property "%s" of path "%s".', $tag, $this->dav_name ); - return false; + $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; } @@ -902,25 +1088,35 @@ EOQRY; /** * Construct XML propstat fragment for this resource * - * @param array $properties The requested properties 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 ) { - global $session, $c, $request, $reply; + function GetPropStat( $properties, &$reply, $props_only = false ) { + global $request; - dbg_error_log('DAVResource',': GetPropStat: href "%s"', $this->dav_name ); + 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 ) { -// dbg_error_log( 'DAVResource', 'Looking at resource "%s" for property [%s]"%s".', $this->dav_name, $k, $tag ); - if ( ! $this->ResourceProperty($tag, $prop, $reply, $denied ) ) { + 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 && ! $request->ServerProperty($tag, $prop, $reply) ) { 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) ) ); @@ -958,44 +1154,17 @@ EOQRY; function RenderAsXML( $properties, &$reply, $props_only = false ) { global $session, $c, $request; - dbg_error_log('DAVResource',': RenderAsXML: Principal "%s"', $this->username ); + dbg_error_log('DAVResource',':RenderAsXML: Resource "%s"', $this->dav_name ); - $prop = new XMLElement('prop'); - $denied = array(); - $not_found = array(); - foreach( $properties AS $k => $tag ) { - if ( ! $this->ResourceProperty($tag, $prop, $reply) ) { - dbg_error_log( 'DAVResource', 'Request for unsupported property "%s" of principal "%s".', $tag, $this->username ); - $not_found[] = $reply->Tag($tag); - } + if ( !$this->Exists() ) return null; + + if ( $props_only ) { + dbg_error_log('LOG WARNING','DAVResource::RenderAsXML Called misguidedly - should be to DAVResource::GetPropStat' ); + return $this->GetPropStat( $properties, $reply, true ); } - if ( $props_only ) return $prop; - - $status = new XMLElement('status', 'HTTP/1.1 200 OK' ); - - $propstat = new XMLElement( 'propstat', array( $prop, $status) ); - $href = $reply->href( ConstructURL($this->dav_name) ); /** @TODO: make ::href() into an accessor */ - - $elements = array($href,$propstat); - - if ( count($denied) > 0 ) { - $status = new XMLElement('status', 'HTTP/1.1 403 Forbidden' ); - $noprop = new XMLElement('prop'); - foreach( $denied AS $k => $v ) { - $noprop->NewElement( $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) ); - } + $elements = $this->GetPropStat( $properties, $reply ); + array_unshift( $elements, $reply->href(ConstructURL($this->dav_name))); $response = new XMLElement( 'response', $elements ); diff --git a/inc/always.php b/inc/always.php index 381cd171..d46f76e3 100644 --- a/inc/always.php +++ b/inc/always.php @@ -298,3 +298,12 @@ function ISODateToHTTPDate( $isodate ) { // Use strtotime since strptime is not available on Windows platform. return( gmstrftime('%a, %d %b %Y %T GMT', strtotime($isodate)) ); } + +/** +* Convert a date into ISO format into the sparkly new ISO format. +* @param string $indate The date to convert +*/ +function DateToISODate( $indate ) { + // Use strtotime since strptime is not available on Windows platform. + return( date('c', strtotime($indate)) ); +} diff --git a/inc/always.php.in b/inc/always.php.in index 21f6bcc2..4bc717e7 100644 --- a/inc/always.php.in +++ b/inc/always.php.in @@ -298,3 +298,12 @@ function ISODateToHTTPDate( $isodate ) { // Use strtotime since strptime is not available on Windows platform. return( gmstrftime('%a, %d %b %Y %T GMT', strtotime($isodate)) ); } + +/** +* Convert a date into ISO format into the sparkly new ISO format. +* @param string $indate The date to convert +*/ +function DateToISODate( $indate ) { + // Use strtotime since strptime is not available on Windows platform. + return( date('c', strtotime($indate)) ); +} diff --git a/inc/caldav-MOVE.php b/inc/caldav-MOVE.php index 5b3242c3..342a0277 100644 --- a/inc/caldav-MOVE.php +++ b/inc/caldav-MOVE.php @@ -120,7 +120,7 @@ if ( $src->IsCollection() ) { $sql = 'UPDATE collection SET dav_name = :dst_name '; $params = array(':dst_name' => $dst_name); if ( $src_user_no != $dst_user_no ) { - $sql .= ', user_no = :dst_user_no'; + $sql .= ', user_no = :dst_user_no '; $params[':dst_user_no'] = $dst_user_no; } $sql .= 'WHERE collection_id = :src_collection'; diff --git a/inc/caldav-PUT-functions.php b/inc/caldav-PUT-functions.php index 56c0b199..7b2d4b3e 100644 --- a/inc/caldav-PUT-functions.php +++ b/inc/caldav-PUT-functions.php @@ -15,7 +15,7 @@ * return true if it's a whole calendar */ -include_once('iCalendar.php'); +require_once('iCalendar.php'); /** * A regex which will match most reasonable timezones acceptable to PostgreSQL. diff --git a/inc/caldav-REPORT-principal.php b/inc/caldav-REPORT-principal.php index e89d83f1..f5810d90 100644 --- a/inc/caldav-REPORT-principal.php +++ b/inc/caldav-REPORT-principal.php @@ -39,7 +39,7 @@ foreach( $searches AS $k => $search ) { } } if ( $where != "" ) $where = "WHERE $where"; -$sql = "SELECT * FROM usr $where"; +$sql = "SELECT * FROM usr JOIN principal USING(user_no) $where"; $qry = new PgQuery($sql); diff --git a/inc/caldav-REPORT-sync-collection.php b/inc/caldav-REPORT-sync-collection.php index d32126fa..739437fe 100644 --- a/inc/caldav-REPORT-sync-collection.php +++ b/inc/caldav-REPORT-sync-collection.php @@ -103,7 +103,7 @@ if ( $qry->Exec("REPORT",__LINE__,__FILE__) ) { ); if ( $object->sync_status != 404 ) { $dav_resource = new DAVResource($object); - $resultset = array_merge( $resultset, $dav_resource->GetPropStat($proplist) ); + $resultset = array_merge( $resultset, $dav_resource->GetPropStat($proplist,$reply) ); } $responses[] = new XMLElement( 'sync-response', $resultset ); $first_status = $object->sync_status; diff --git a/inc/caldav-REPORT.php b/inc/caldav-REPORT.php index 40f04851..00dc66b8 100644 --- a/inc/caldav-REPORT.php +++ b/inc/caldav-REPORT.php @@ -60,13 +60,6 @@ switch( $xmltree->GetTag() ) { exit; // Not that it should return anyway. } -// Must have read privilege for all other reports -if ( ! ($request->AllowedTo('read') ) ) { - // If they got this far they *do* have freebusy access, so can know the - // calendar really exists. Informing them is therefore OK. - $request->DoResponse( 404, translate("You may not access that calendar") ); -} - /** * Return XML for a single calendar (or todo) entry from the DB @@ -88,7 +81,7 @@ function calendar_to_xml( $properties, $item ) { if ( !$request->AllowedTo('all') && $session->user_no != $item->user_no ){ // the user is not admin / owner of this calendarlooking at his calendar and can not admin the other cal /** @todo We should examine the ORGANIZER and ATTENDEE fields in the event. If this person is there then they should see this */ - if ( $item->class == 'CONFIDENTIAL' ) { + if ( $item->class == 'CONFIDENTIAL' || !$request->AllowedTo('read') ) { $ical = new iCalComponent( $caldav_data ); $resources = $ical->GetComponents('VTIMEZONE',false); $first = $resources[0]; @@ -106,6 +99,7 @@ function calendar_to_xml( $properties, $item ) { $ical->SetComponents(array($confidential),$confidential->GetType()); $caldav_data = $ical->Render(); + $displayname = translate('Busy'); } } }