DAViCal
DAVResource.php
1 <?php
12 require_once('AwlCache.php');
13 require_once('AwlQuery.php');
14 require_once('DAVPrincipal.php');
15 require_once('DAVTicket.php');
16 require_once('iCalendar.php');
17 
18 
25 {
29  protected $dav_name;
30 
34  protected $exists;
35 
39  protected $unique_tag;
40 
44  protected $resource;
45 
49  protected $parent;
50 
54  protected $resourcetypes;
55 
59  protected $contenttype;
60 
64  protected $bound_from;
65 
69  private $collection;
70 
74  private $principal;
75 
79  private $privileges;
80 
84  private $_is_collection;
85 
89  private $_is_principal;
90 
94  private $_is_calendar;
95 
99  private $_is_binding;
100 
104  private $_is_external;
105 
109  private $_is_addressbook;
110 
114  private $_is_proxy_resource;
115 
119  private $proxy_type;
120 
124  private $supported_methods;
125 
129  private $supported_reports;
130 
134  private $dead_properties;
135 
139  private $supported_components;
140 
144  private $tickets;
145 
152  function __construct( $parameters = null ) {
153  $this->exists = null;
154  $this->bound_from = null;
155  $this->dav_name = null;
156  $this->unique_tag = null;
157  $this->resource = null;
158  $this->collection = null;
159  $this->principal = null;
160  $this->parent = null;
161  $this->resourcetypes = null;
162  $this->contenttype = null;
163  $this->privileges = null;
164  $this->dead_properties = null;
165  $this->supported_methods = null;
166  $this->supported_reports = null;
167 
168  $this->_is_collection = false;
169  $this->_is_principal = false;
170  $this->_is_calendar = false;
171  $this->_is_binding = false;
172  $this->_is_external = false;
173  $this->_is_addressbook = false;
174  $this->_is_proxy_resource = false;
175  if ( isset($parameters) && is_object($parameters) ) {
176  $this->FromRow($parameters);
177  }
178  else if ( isset($parameters) && is_array($parameters) ) {
179  if ( isset($parameters['path']) ) {
180  $this->FromPath($parameters['path']);
181  }
182  }
183  else if ( isset($parameters) && is_string($parameters) ) {
184  $this->FromPath($parameters);
185  }
186  }
187 
188 
193  function FromRow($row) {
194  global $c, $session;
195 
196  if ( $row == null ) return;
197 
198  $this->exists = true;
199  $this->dav_name = $row->dav_name;
200  $this->bound_from = (isset($row->bound_from)? $row->bound_from : $row->dav_name);
201  $this->_is_collection = preg_match( '{/$}', $this->dav_name );
202 
203  if ( $this->_is_collection ) {
204  $this->contenttype = 'httpd/unix-directory';
205  $this->collection = (object) array();
206  $this->resource_id = $row->collection_id;
207 
208  $this->_is_principal = preg_match( '{^/[^/]+/$}', $this->dav_name );
209  if ( preg_match( '#^(/principals/[^/]+/[^/]+)/?$#', $this->dav_name, $matches) ) {
210  $this->collection->dav_name = $matches[1].'/';
211  $this->collection->type = 'principal_link';
212  $this->_is_principal = true;
213  }
214  }
215  else {
216  $this->resource = (object) array();
217  if ( isset($row->dav_id) ) $this->resource_id = $row->dav_id;
218  }
219 
220  dbg_error_log( 'DAVResource', ':FromRow: Named "%s" is%s a collection.', $this->dav_name, ($this->_is_collection?'':' not') );
221 
222  foreach( $row AS $k => $v ) {
223  if ( $this->_is_collection )
224  $this->collection->{$k} = $v;
225  else
226  $this->resource->{$k} = $v;
227  switch ( $k ) {
228  case 'created':
229  case 'modified':
230  $this->{$k} = $v;
231  break;
232 
233  case 'resourcetypes':
234  if ( $this->_is_collection ) $this->{$k} = $v;
235  break;
236 
237  case 'dav_etag':
238  $this->unique_tag = '"'.$v.'"';
239  break;
240 
241  }
242  }
243 
244  if ( $this->_is_collection ) {
245  if ( !isset( $this->collection->type ) || $this->collection->type == 'collection' ) {
246  if ( $this->_is_principal )
247  $this->collection->type = 'principal';
248  else if ( $row->is_calendar == 't' ) {
249  $this->collection->type = 'calendar';
250  }
251  else if ( $row->is_addressbook == 't' ) {
252  $this->collection->type = 'addressbook';
253  }
254  else if ( isset($row->is_proxy) && $row->is_proxy == 't' ) {
255  $this->collection->type = 'proxy';
256  }
257  else if ( preg_match( '#^((/[^/]+/)\.(in|out)/)[^/]*$#', $this->dav_name, $matches ) )
258  $this->collection->type = 'schedule-'. $matches[3]. 'box';
259  else if ( $this->dav_name == '/' )
260  $this->collection->type = 'root';
261  else
262  $this->collection->type = 'collection';
263  }
264 
265  $this->_is_calendar = ($this->collection->is_calendar == 't');
266  $this->_is_addressbook = ($this->collection->is_addressbook == 't');
267  $this->_is_proxy_resource = ($this->collection->type == 'proxy');
268  if ( $this->_is_principal && !isset($this->resourcetypes) ) {
269  $this->resourcetypes = '<DAV::collection/><DAV::principal/>';
270  }
271  else if ( $this->_is_proxy_resource ) {
272  $this->resourcetypes = $this->collection->resourcetypes;
273  preg_match( '#^/[^/]+/calendar-proxy-(read|write)/?[^/]*$#', $this->dav_name, $matches );
274  $this->proxy_type = $matches[1];
275  }
276  if ( isset($this->collection->dav_displayname) ) $this->collection->displayname = $this->collection->dav_displayname;
277  }
278  else {
279  $this->resourcetypes = '';
280  if ( isset($this->resource->caldav_data) ) {
281  if ( isset($this->resource->summary) )$this->resource->displayname = $this->resource->summary;
282  if ( strtoupper(substr($this->resource->caldav_data,0,15)) == 'BEGIN:VCALENDAR' ) {
283  $this->contenttype = 'text/calendar';
284  if ( isset($this->resource->caldav_type) ) $this->contenttype .= "; component=" . strtolower($this->resource->caldav_type);
285  if ( !$this->HavePrivilegeTo('read') && $this->HavePrivilegeTo('read-free-busy') ) {
286  $vcal = new iCalComponent($this->resource->caldav_data);
287  $confidential = $vcal->CloneConfidential();
288  $this->resource->caldav_data = $confidential->Render();
289  $this->resource->displayname = $this->resource->summary = translate('Busy');
290  $this->resource->description = null;
291  $this->resource->location = null;
292  $this->resource->url = null;
293  }
294  else {
295  if ( isset($this->resource->class) && strtoupper($this->resource->class)=='CONFIDENTIAL' && !$this->HavePrivilegeTo('all') && $session->user_no != $this->resource->user_no ) {
296  $vcal = new iCalComponent($this->resource->caldav_data);
297  $confidential = $vcal->CloneConfidential();
298  $this->resource->caldav_data = $confidential->Render();
299  }
300  if ( isset($c->hide_alarm) && $c->hide_alarm && !$this->HavePrivilegeTo('write') ) {
301  $vcal1 = new iCalComponent($this->resource->caldav_data);
302  $comps = $vcal1->GetComponents();
303  $vcal2 = new iCalComponent();
304  $vcal2->VCalendar();
305  foreach( $comps AS $comp ) {
306  $comp->ClearComponents('VALARM');
307  $vcal2->AddComponent($comp);
308  }
309  $this->resource->displayname = $this->resource->summary = $vcal2->GetPValue('SUMMARY');
310  $this->resource->caldav_data = $vcal2->Render();
311  }
312  }
313  }
314  else if ( strtoupper(substr($this->resource->caldav_data,0,11)) == 'BEGIN:VCARD' ) {
315  $this->contenttype = 'text/vcard';
316  }
317  else if ( strtoupper(substr($this->resource->caldav_data,0,11)) == 'BEGIN:VLIST' ) {
318  $this->contenttype = 'text/x-vlist';
319  }
320  }
321  }
322  }
323 
324 
329  function FromPath($inpath) {
330  global $c;
331 
332  $this->dav_name = DeconstructURL($inpath);
333 
334  $this->FetchCollection();
335  if ( $this->_is_collection ) {
336  if ( $this->_is_principal || $this->collection->type == 'principal' ) $this->FetchPrincipal();
337  }
338  else {
339  $this->FetchResource();
340  }
341  dbg_error_log( 'DAVResource', ':FromPath: Path "%s" is%s a collection%s.',
342  $this->dav_name, ($this->_is_collection?' '.$this->resourcetypes:' not'), ($this->_is_principal?' and a principal':'') );
343  }
344 
345 
346  private function ReadCollectionFromDatabase() {
347  global $c, $session;
348 
349  $this->collection = (object) array(
350  'collection_id' => -1,
351  'type' => 'nonexistent',
352  'is_calendar' => false, 'is_principal' => false, 'is_addressbook' => false
353  );
354 
355  $base_sql = 'SELECT collection.*, path_privs(:session_principal::int8, collection.dav_name,:scan_depth::int), ';
356  $base_sql .= 'p.principal_id, p.type_id AS principal_type_id, ';
357  $base_sql .= 'p.displayname AS principal_displayname, p.default_privileges AS principal_default_privileges, ';
358  $base_sql .= 'timezones.vtimezone ';
359  $base_sql .= 'FROM collection LEFT JOIN principal p USING (user_no) ';
360  $base_sql .= 'LEFT JOIN timezones ON (collection.timezone=timezones.tzid) ';
361  $base_sql .= 'WHERE ';
362  $sql = $base_sql .'collection.dav_name = :raw_path ';
363  $params = array( ':raw_path' => $this->dav_name, ':session_principal' => $session->principal_id, ':scan_depth' => $c->permission_scan_depth );
364  if ( !preg_match( '#/$#', $this->dav_name ) ) {
365  $sql .= ' OR collection.dav_name = :up_to_slash OR collection.dav_name = :plus_slash ';
366  $params[':up_to_slash'] = preg_replace( '#[^/]*$#', '', $this->dav_name);
367  $params[':plus_slash'] = $this->dav_name.'/';
368  }
369  $sql .= 'ORDER BY LENGTH(collection.dav_name) DESC LIMIT 1';
370  $qry = new AwlQuery( $sql, $params );
371  if ( $qry->Exec('DAVResource') && $qry->rows() == 1 && ($row = $qry->Fetch()) ) {
372  $this->collection = $row;
373  $this->collection->exists = true;
374  if ( $row->is_calendar == 't' )
375  $this->collection->type = 'calendar';
376  else if ( $row->is_addressbook == 't' )
377  $this->collection->type = 'addressbook';
378  else if ( preg_match( '#^((/[^/]+/)\.(in|out)/)[^/]*$#', $this->dav_name, $matches ) )
379  $this->collection->type = 'schedule-'. $matches[3]. 'box';
380  else
381  $this->collection->type = 'collection';
382  }
383  else if ( preg_match( '{^( ( / ([^/]+) / ) \.(in|out)/ ) [^/]*$}x', $this->dav_name, $matches ) ) {
384  // The request is for a scheduling inbox or outbox (or something inside one) and we should auto-create it
385  $params = array( ':username' => $matches[3], ':parent_container' => $matches[2], ':dav_name' => $matches[1] );
386  $params[':boxname'] = ($matches[4] == 'in' ? ' Inbox' : ' Outbox');
387  $this->collection_type = 'schedule-'. $matches[4]. 'box';
388  $params[':resourcetypes'] = sprintf('<DAV::collection/><urn:ietf:params:xml:ns:caldav:%s/>', $this->collection_type );
389  $sql = <<<EOSQL
390 INSERT INTO collection ( user_no, parent_container, dav_name, dav_displayname, is_calendar, created, modified, dav_etag, resourcetypes )
391  VALUES( (SELECT user_no FROM usr WHERE username = text(:username)),
392  :parent_container, :dav_name,
393  (SELECT fullname FROM usr WHERE username = text(:username)) || :boxname,
394  FALSE, current_timestamp, current_timestamp, '1', :resourcetypes )
395 EOSQL;
396  $qry = new AwlQuery( $sql, $params );
397  $qry->Exec('DAVResource');
398  dbg_error_log( 'DAVResource', 'Created new collection as "%s".', trim($params[':boxname']) );
399 
400  $params = array( ':raw_path' => $this->dav_name, ':session_principal' => $session->principal_id, ':scan_depth' => $c->permission_scan_depth );
401  $qry = new AwlQuery( $base_sql . ' dav_name = :raw_path', $params );
402  if ( $qry->Exec('DAVResource') && $qry->rows() == 1 && ($row = $qry->Fetch()) ) {
403  $this->collection = $row;
404  $this->collection->exists = true;
405  $this->collection->type = $this->collection_type;
406  }
407  }
408  else if ( preg_match( '#^(/([^/]+)/calendar-proxy-(read|write))/?[^/]*$#', $this->dav_name, $matches ) ) {
409  $this->collection->type = 'proxy';
410  $this->_is_proxy_resource = true;
411  $this->proxy_type = $matches[3];
412  $this->collection->dav_name = $this->dav_name;
413  $this->collection->dav_displayname = sprintf( '%s proxy %s', $matches[2], $matches[3] );
414  $this->collection->exists = true;
415  $this->collection->parent_container = '/' . $matches[2] . '/';
416  }
417  else if ( preg_match( '#^(/[^/]+)/?$#', $this->dav_name, $matches)
418  || preg_match( '#^((/principals/[^/]+/)[^/]+)/?$#', $this->dav_name, $matches) ) {
419  $this->_is_principal = true;
420  $this->FetchPrincipal();
421  $this->collection->is_principal = true;
422  $this->collection->type = 'principal';
423  }
424  else if ( $this->dav_name == '/' ) {
425  $this->collection->dav_name = '/';
426  $this->collection->type = 'root';
427  $this->collection->exists = true;
428  $this->collection->displayname = $c->system_name;
429  $this->collection->default_privileges = (1 | 16 | 32);
430  $this->collection->parent_container = '/';
431  }
432  else {
433  $sql = <<<EOSQL
434 SELECT collection.*, path_privs(:session_principal::int8, collection.dav_name,:scan_depth::int), p.principal_id,
435  p.type_id AS principal_type_id, p.displayname AS principal_displayname, p.default_privileges AS principal_default_privileges,
436  timezones.vtimezone, dav_binding.access_ticket_id, dav_binding.parent_container AS bind_parent_container,
437  dav_binding.dav_displayname, owner.dav_name AS bind_owner_url, dav_binding.dav_name AS bound_to,
438  dav_binding.external_url AS external_url, dav_binding.type AS external_type, dav_binding.bind_id AS bind_id
439 FROM dav_binding
440  LEFT JOIN collection ON (collection.collection_id=bound_source_id)
441  LEFT JOIN principal p USING (user_no)
442  LEFT JOIN dav_principal owner ON (dav_binding.dav_owner_id=owner.principal_id)
443  LEFT JOIN timezones ON (collection.timezone=timezones.tzid)
444  WHERE dav_binding.dav_name = :raw_path
445 EOSQL;
446  $params = array( ':raw_path' => $this->dav_name, ':session_principal' => $session->principal_id, ':scan_depth' => $c->permission_scan_depth );
447  if ( !preg_match( '#/$#', $this->dav_name ) ) {
448  $sql .= ' OR dav_binding.dav_name = :up_to_slash OR collection.dav_name = :plus_slash OR dav_binding.dav_name = :plus_slash ';
449  $params[':up_to_slash'] = preg_replace( '#[^/]*$#', '', $this->dav_name);
450  $params[':plus_slash'] = $this->dav_name.'/';
451  }
452  $sql .= ' ORDER BY LENGTH(dav_binding.dav_name) DESC LIMIT 1';
453  $qry = new AwlQuery( $sql, $params );
454  if ( $qry->Exec('DAVResource',__LINE__,__FILE__) && $qry->rows() == 1 && ($row = $qry->Fetch()) ) {
455  $this->collection = $row;
456  $this->collection->exists = true;
457  $this->collection->parent_set = $row->parent_container;
458  $this->collection->parent_container = $row->bind_parent_container;
459  $this->collection->bound_from = $row->dav_name;
460  $this->collection->dav_name = $row->bound_to;
461  if ( $row->is_calendar == 't' )
462  $this->collection->type = 'calendar';
463  else if ( $row->is_addressbook == 't' )
464  $this->collection->type = 'addressbook';
465  else if ( preg_match( '#^((/[^/]+/)\.(in|out)/)[^/]*$#', $this->dav_name, $matches ) )
466  $this->collection->type = 'schedule-'. $matches[3]. 'box';
467  else
468  $this->collection->type = 'collection';
469  if ( strlen($row->external_url) > 8 ) {
470  $this->_is_external = true;
471  if ( $row->external_type == 'calendar' )
472  $this->collection->type = 'calendar';
473  else if ( $row->external_type == 'addressbook' )
474  $this->collection->type = 'addressbook';
475  else
476  $this->collection->type = 'collection';
477  }
478  $this->_is_binding = true;
479  $this->bound_from = str_replace( $row->bound_to, $row->dav_name, $this->dav_name);
480  if ( isset($row->access_ticket_id) ) {
481  if ( !isset($this->tickets) ) $this->tickets = array();
482  $this->tickets[] = new DAVTicket($row->access_ticket_id);
483  }
484  }
485  else {
486  dbg_error_log( 'DAVResource', 'No collection for path "%s".', $this->dav_name );
487  $this->collection->exists = false;
488  $this->collection->dav_name = preg_replace('{/[^/]*$}', '/', $this->dav_name);
489  }
490  }
491 
492  }
493 
497  protected function FetchCollection() {
498  global $session;
499 
511  dbg_error_log( 'DAVResource', ':FetchCollection: Looking for collection for "%s".', $this->dav_name );
512 
513  // Try and pull the answer out of a hat
514  $cache = getCacheInstance();
515  $cache_ns = 'collection-'.preg_replace( '{/[^/]*$}', '/', $this->dav_name);
516  $cache_key = 'dav_resource'.$session->user_no;
517  $this->collection = $cache->get( $cache_ns, $cache_key );
518  if ( $this->collection === false ) {
519  $this->ReadCollectionFromDatabase();
520  if ( $this->collection->type != 'principal' ) {
521  $cache_ns = 'collection-'.$this->collection->dav_name;
522  @dbg_error_log( 'Cache', ':FetchCollection: Setting cache ns "%s" key "%s". Type: %s', $cache_ns, $cache_key, $this->collection->type );
523  $cache->set( $cache_ns, $cache_key, $this->collection );
524  }
525  @dbg_error_log( 'DAVResource', ':FetchCollection: Found collection named "%s" of type "%s".', $this->collection->dav_name, $this->collection->type );
526  }
527  else {
528  @dbg_error_log( 'Cache', ':FetchCollection: Got cache ns "%s" key "%s". Type: %s', $cache_ns, $cache_key, $this->collection->type );
529  if ( preg_match( '#^(/[^/]+)/?$#', $this->dav_name, $matches)
530  || preg_match( '#^((/principals/[^/]+/)[^/]+)/?$#', $this->dav_name, $matches) ) {
531  $this->_is_principal = true;
532  $this->FetchPrincipal();
533  $this->collection->is_principal = true;
534  $this->collection->type = 'principal';
535  }
536  @dbg_error_log( 'DAVResource', ':FetchCollection: Read cached collection named "%s" of type "%s".', $this->collection->dav_name, $this->collection->type );
537  }
538 
539  if ( isset($this->collection->bound_from) ) {
540  $this->_is_binding = true;
541  $this->bound_from = str_replace( $this->collection->bound_to, $this->collection->bound_from, $this->dav_name);
542  if ( isset($this->collection->access_ticket_id) ) {
543  if ( !isset($this->tickets) ) $this->tickets = array();
544  $this->tickets[] = new DAVTicket($this->collection->access_ticket_id);
545  }
546  }
547 
548  $this->_is_collection = ( $this->_is_principal || $this->collection->dav_name == $this->dav_name || $this->collection->dav_name == $this->dav_name.'/' );
549  if ( $this->_is_collection ) {
550  $this->dav_name = $this->collection->dav_name;
551  $this->resource_id = $this->collection->collection_id;
552  $this->_is_calendar = ($this->collection->type == 'calendar');
553  $this->_is_addressbook = ($this->collection->type == 'addressbook');
554  $this->contenttype = 'httpd/unix-directory';
555  if ( !isset($this->exists) && isset($this->collection->exists) ) {
556  // If this seems peculiar it's because we only set it to false above...
557  $this->exists = $this->collection->exists;
558  }
559  if ( $this->exists ) {
560  if ( isset($this->collection->dav_etag) ) $this->unique_tag = '"'.$this->collection->dav_etag.'"';
561  if ( isset($this->collection->created) ) $this->created = $this->collection->created;
562  if ( isset($this->collection->modified) ) $this->modified = $this->collection->modified;
563  if ( isset($this->collection->dav_displayname) ) $this->collection->displayname = $this->collection->dav_displayname;
564  }
565  else {
566  if ( !isset($this->parent) ) $this->GetParentContainer();
567  $this->user_no = $this->parent->GetProperty('user_no');
568  }
569  if ( isset($this->collection->resourcetypes) )
570  $this->resourcetypes = $this->collection->resourcetypes;
571  else {
572  $this->resourcetypes = '<DAV::collection/>';
573  if ( $this->_is_principal ) $this->resourcetypes .= '<DAV::principal/>';
574  if ( $this->_is_addressbook ) $this->resourcetypes .= '<urn:ietf:params:xml:ns:carddav:addressbook/>';
575  if ( $this->_is_calendar ) $this->resourcetypes .= '<urn:ietf:params:xml:ns:caldav:calendar/>';
576  }
577  }
578  }
579 
580 
584  protected function FetchPrincipal() {
585  if ( isset($this->principal) ) return;
586  $this->principal = new DAVPrincipal( array( "path" => $this->bound_from() ) );
587  if ( $this->_is_principal ) {
588  $this->exists = $this->principal->Exists();
589  $this->collection->dav_name = $this->dav_name();
590  $this->collection->type = 'principal';
591  if ( $this->exists ) {
592  $this->collection = $this->principal->AsCollection();
593  $this->displayname = $this->principal->GetProperty('displayname');
594  $this->user_no = $this->principal->user_no();
595  $this->resource_id = $this->principal->principal_id();
596  $this->created = $this->principal->created;
597  $this->modified = $this->principal->modified;
598  $this->resourcetypes = $this->principal->resourcetypes;
599  }
600  }
601  }
602 
603 
607  protected function FetchResource() {
608  if ( isset($this->exists) ) return; // True or false, we've got what we can already
609  if ( $this->_is_collection ) return; // We have all we're going to read
610 
611  $sql = <<<EOQRY
612 SELECT calendar_item.*, addressbook_resource.*, caldav_data.*
613  FROM caldav_data LEFT OUTER JOIN calendar_item USING (collection_id,dav_id)
614  LEFT OUTER JOIN addressbook_resource USING (dav_id)
615  WHERE caldav_data.dav_name = :dav_name
616 EOQRY;
617  $params = array( ':dav_name' => $this->bound_from() );
618 
619  $qry = new AwlQuery( $sql, $params );
620  if ( $qry->Exec('DAVResource') && $qry->rows() > 0 ) {
621  $this->exists = true;
622  $row = $qry->Fetch();
623  $this->FromRow($row);
624  }
625  else {
626  $this->exists = false;
627  }
628  }
629 
630 
634  protected function FetchDeadProperties() {
635  if ( isset($this->dead_properties) ) return;
636 
637  $this->dead_properties = array();
638  if ( !$this->exists || !$this->_is_collection ) return;
639 
640  $qry = new AwlQuery('SELECT property_name, property_value FROM property WHERE dav_name= :dav_name', array(':dav_name' => $this->dav_name) );
641  if ( $qry->Exec('DAVResource') ) {
642  while ( $property = $qry->Fetch() ) {
643  $this->dead_properties[$property->property_name] = self::BuildDeadPropertyXML($property->property_name,$property->property_value);
644  }
645  }
646  }
647 
654  public static function BuildDeadPropertyXML($property_name, $raw_string) {
655  if ( !preg_match('{^\s*<.*>\s*$}s', $raw_string) ) return $raw_string;
656  $xmlns = null;
657  if ( preg_match( '{^(.*):([^:]+)$}', $property_name, $matches) ) {
658  $xmlns = $matches[1];
659  $property_name = $matches[2];
660  }
661  $xml = sprintf('<%s%s>%s</%s>', $property_name, (isset($xmlns)?' xmlns="'.$xmlns.'"':''), $raw_string, $property_name);
662  $xml_parser = xml_parser_create_ns('UTF-8');
663  $xml_tags = array();
664  xml_parser_set_option ( $xml_parser, XML_OPTION_SKIP_WHITE, 1 );
665  xml_parser_set_option ( $xml_parser, XML_OPTION_CASE_FOLDING, 0 );
666  $rc = xml_parse_into_struct( $xml_parser, $xml, $xml_tags );
667  if ( $rc == false ) {
668  $errno = xml_get_error_code($xml_parser);
669  dbg_error_log( 'ERROR', 'XML parsing error: %s (%d) at line %d, column %d',
670  xml_error_string($errno), $errno,
671  xml_get_current_line_number($xml_parser), xml_get_current_column_number($xml_parser) );
672  dbg_error_log( 'ERROR', "Error occurred in:\n%s\n",$xml);
673  if ($errno >= 200 && $errno < 300 && count($xml_tags) >= 3) {
674  // XML namespace error, but parsing was probably fine: continue and return tags (cf. #9)
675  dbg_error_log( 'ERROR', 'XML namespace error but tags extracted, trying to continue');
676  } else {
677  return $raw_string;
678  }
679  }
680  xml_parser_free($xml_parser);
681  $position = 0;
682  $xmltree = BuildXMLTree( $xml_tags, $position);
683  return $xmltree->GetContent();
684  }
685 
689  protected function FetchPrivileges() {
690  global $session, $request;
691 
692  if ( $this->dav_name == '/' || $this->dav_name == '' || $this->_is_external ) {
693  $this->privileges = (1 | 16 | 32); // read + read-acl + read-current-user-privilege-set
694  dbg_error_log( 'DAVResource', ':FetchPrivileges: Read permissions for user accessing /' );
695  return;
696  }
697 
698  if ( $session->AllowedTo('Admin') ) {
699  $this->privileges = privilege_to_bits('all');
700  dbg_error_log( 'DAVResource', ':FetchPrivileges: Full permissions for an administrator.' );
701  return;
702  }
703 
704  if ( $this->IsPrincipal() ) {
705  if ( !isset($this->principal) ) $this->FetchPrincipal();
706  $this->privileges = $this->principal->Privileges();
707  dbg_error_log( 'DAVResource', ':FetchPrivileges: Privileges of "%s" for user accessing principal "%s"', $this->privileges, $this->principal->username() );
708  return;
709  }
710 
711  if ( ! isset($this->collection) ) $this->FetchCollection();
712  $this->privileges = 0;
713  if ( !isset($this->collection->path_privs) ) {
714  if ( !isset($this->parent) ) $this->GetParentContainer();
715 
716  $this->collection->path_privs = $this->parent->Privileges();
717  $this->collection->user_no = $this->parent->GetProperty('user_no');
718  $this->collection->principal_id = $this->parent->GetProperty('principal_id');
719  }
720 
721  $this->privileges = $this->collection->path_privs;
722  if ( is_string($this->privileges) ) $this->privileges = bindec( $this->privileges );
723 
724  dbg_error_log( 'DAVResource', ':FetchPrivileges: Privileges of "%s" for user "%s" accessing "%s"',
725  decbin($this->privileges), $session->username, $this->dav_name() );
726 
727  if ( isset($request->ticket) && $request->ticket->MatchesPath($this->bound_from()) ) {
728  $this->privileges |= $request->ticket->privileges();
729  dbg_error_log( 'DAVResource', ':FetchPrivileges: Applying permissions for ticket "%s" now: %s', $request->ticket->id(), decbin($this->privileges) );
730  }
731 
732  if ( isset($this->tickets) ) {
733  if ( !isset($this->resource_id) ) $this->FetchResource();
734  foreach( $this->tickets AS $k => $ticket ) {
735  if ( $ticket->MatchesResource($this->resource_id()) || $ticket->MatchesPath($this->bound_from()) ) {
736  $this->privileges |= $ticket->privileges();
737  dbg_error_log( 'DAVResource', ':FetchPrivileges: Applying permissions for ticket "%s" now: %s', $ticket->id(), decbin($this->privileges) );
738  }
739  }
740  }
741  }
742 
743 
747  function GetParentContainer() {
748  if ( $this->dav_name == '/' ) return null;
749  if ( !isset($this->parent) ) {
750  if ( $this->_is_collection ) {
751  dbg_error_log( 'DAVResource', 'Retrieving "%s" - parent of "%s" (dav_name: %s)', $this->parent_path(), $this->collection->dav_name, $this->dav_name() );
752  $this->parent = new DAVResource( $this->parent_path() );
753  }
754  else {
755  dbg_error_log( 'DAVResource', 'Retrieving "%s" - parent of "%s" (dav_name: %s)', $this->parent_path(), $this->collection->dav_name, $this->dav_name() );
756  $this->parent = new DAVResource($this->collection->dav_name);
757  }
758  }
759  return $this->parent;
760  }
761 
762 
767  function FetchParentContainer() {
768  deprecated('DAVResource::FetchParentContainer');
769  return $this->GetParentContainer();
770  }
771 
772 
776  function Privileges() {
777  if ( !isset($this->privileges) ) $this->FetchPrivileges();
778  return $this->privileges;
779  }
780 
781 
788  function HavePrivilegeTo( $do_what, $any = null ) {
789  if ( !isset($this->privileges) ) $this->FetchPrivileges();
790  if ( !isset($any) ) $any = ($do_what != 'all');
791  $test_bits = privilege_to_bits( $do_what );
792  dbg_error_log( 'DAVResource', 'Testing %s privileges of "%s" (%s) against allowed "%s" => "%s" (%s)', ($any?'any':'exactly'),
793  $do_what, decbin($test_bits), decbin($this->privileges), ($this->privileges & $test_bits), decbin($this->privileges & $test_bits) );
794  if ( $any ) {
795  return ($this->privileges & $test_bits) > 0;
796  }
797  else {
798  return ($this->privileges & $test_bits) == $test_bits;
799  }
800  }
801 
802 
810  function NeedPrivilege( $privilege, $any = null ) {
811  global $request;
812 
813  // Do the test
814  if ( $this->HavePrivilegeTo($privilege, $any) ) return;
815 
816  // They failed, so output the error
817  $request->NeedPrivilege( $privilege, $this->dav_name );
818  exit(0); // Unecessary, but might clarify things
819  }
820 
821 
825  function BuildPrivileges( $privilege_names=null, &$xmldoc=null ) {
826  if ( $privilege_names == null ) {
827  if ( !isset($this->privileges) ) $this->FetchPrivileges();
828  $privilege_names = bits_to_privilege($this->privileges, ($this->_is_collection ? $this->collection->type : null ) );
829  }
830  return privileges_to_XML( $privilege_names, $xmldoc);
831  }
832 
833 
837  function FetchSupportedMethods( ) {
838  if ( isset($this->supported_methods) ) return $this->supported_methods;
839 
840  $this->supported_methods = array(
841  'OPTIONS' => '',
842  'PROPFIND' => '',
843  'REPORT' => '',
844  'DELETE' => '',
845  'LOCK' => '',
846  'UNLOCK' => '',
847  'MOVE' => ''
848  );
849  if ( $this->IsCollection() ) {
850 /* if ( $this->IsPrincipal() ) {
851  $this->supported_methods['MKCALENDAR'] = '';
852  $this->supported_methods['MKCOL'] = '';
853  } */
854  switch ( $this->collection->type ) {
855  case 'root':
856  case 'email':
857  // We just override the list completely here.
858  $this->supported_methods = array(
859  'OPTIONS' => '',
860  'PROPFIND' => '',
861  'REPORT' => ''
862  );
863  break;
864 
865  case 'schedule-outbox':
866  $this->supported_methods = array_merge(
867  $this->supported_methods,
868  array(
869  'POST' => '', 'PROPPATCH' => '', 'MKTICKET' => '', 'DELTICKET' => ''
870  )
871  );
872  break;
873  case 'schedule-inbox':
874  case 'calendar':
875  $this->supported_methods['GET'] = '';
876  $this->supported_methods['PUT'] = '';
877  $this->supported_methods['HEAD'] = '';
878  $this->supported_methods['MKTICKET'] = '';
879  $this->supported_methods['DELTICKET'] = '';
880  $this->supported_methods['ACL'] = '';
881  break;
882  case 'collection':
883  $this->supported_methods['MKTICKET'] = '';
884  $this->supported_methods['DELTICKET'] = '';
885  $this->supported_methods['BIND'] = '';
886  $this->supported_methods['ACL'] = '';
887  case 'principal':
888  $this->supported_methods['GET'] = '';
889  $this->supported_methods['HEAD'] = '';
890  $this->supported_methods['MKCOL'] = '';
891  $this->supported_methods['MKCALENDAR'] = '';
892  $this->supported_methods['PROPPATCH'] = '';
893  $this->supported_methods['BIND'] = '';
894  $this->supported_methods['ACL'] = '';
895  break;
896  }
897  }
898  else {
899  $this->supported_methods = array_merge(
900  $this->supported_methods,
901  array(
902  'GET' => '', 'HEAD' => '', 'PUT' => '', 'MKTICKET' => '', 'DELTICKET' => ''
903  )
904  );
905  }
906 
907  return $this->supported_methods;
908  }
909 
910 
914  function BuildSupportedMethods( ) {
915  if ( !isset($this->supported_methods) ) $this->FetchSupportedMethods();
916  $methods = array();
917  foreach( $this->supported_methods AS $k => $v ) {
918 // dbg_error_log( 'DAVResource', ':BuildSupportedMethods: Adding method "%s" which is "%s".', $k, $v );
919  $methods[] = new XMLElement( 'supported-method', null, array('name' => $k) );
920  }
921  return $methods;
922  }
923 
924 
928  function FetchSupportedReports( ) {
929  if ( isset($this->supported_reports) ) return $this->supported_reports;
930 
931  $this->supported_reports = array(
932  'DAV::principal-property-search' => '',
933  'DAV::principal-search-property-set' => '',
934  'DAV::expand-property' => '',
935  'DAV::principal-match' => '',
936  'DAV::sync-collection' => ''
937  );
938 
939  if ( !isset($this->collection) ) $this->FetchCollection();
940 
941  if ( $this->collection->is_calendar ) {
942  $this->supported_reports = array_merge(
943  $this->supported_reports,
944  array(
945  'urn:ietf:params:xml:ns:caldav:calendar-query' => '',
946  'urn:ietf:params:xml:ns:caldav:calendar-multiget' => '',
947  'urn:ietf:params:xml:ns:caldav:free-busy-query' => ''
948  )
949  );
950  }
951  if ( $this->collection->is_addressbook ) {
952  $this->supported_reports = array_merge(
953  $this->supported_reports,
954  array(
955  'urn:ietf:params:xml:ns:carddav:addressbook-query' => '',
956  'urn:ietf:params:xml:ns:carddav:addressbook-multiget' => ''
957  )
958  );
959  }
960  return $this->supported_reports;
961  }
962 
963 
967  function BuildSupportedReports( &$reply ) {
968  if ( !isset($this->supported_reports) ) $this->FetchSupportedReports();
969  $reports = array();
970  foreach( $this->supported_reports AS $k => $v ) {
971  dbg_error_log( 'DAVResource', ':BuildSupportedReports: Adding supported report "%s" which is "%s".', $k, $v );
972  $report = new XMLElement('report');
973  $reply->NSElement($report, $k );
974  $reports[] = new XMLElement('supported-report', $report );
975  }
976  return $reports;
977  }
978 
979 
983  function FetchTickets( ) {
984  global $c;
985  if ( isset($this->access_tickets) ) return;
986  $this->access_tickets = array();
987 
988  $sql =
989 'SELECT access_ticket.*, COALESCE( resource.dav_name, collection.dav_name) AS target_dav_name,
990  (access_ticket.expires < current_timestamp) AS expired,
991  dav_principal.dav_name AS principal_dav_name,
992  EXTRACT( \'epoch\' FROM (access_ticket.expires - current_timestamp)) AS seconds,
993  path_privs(access_ticket.dav_owner_id,collection.dav_name,:scan_depth) AS grantor_collection_privileges
994  FROM access_ticket JOIN collection ON (target_collection_id = collection_id)
995  JOIN dav_principal ON (dav_owner_id = principal_id)
996  LEFT JOIN caldav_data resource ON (resource.dav_id = access_ticket.target_resource_id)
997  WHERE target_collection_id = :collection_id ';
998  $params = array(':collection_id' => $this->collection->collection_id, ':scan_depth' => $c->permission_scan_depth);
999  if ( $this->IsCollection() ) {
1000  $sql .= 'AND target_resource_id IS NULL';
1001  }
1002  else {
1003  if ( !isset($this->exists) ) $this->FetchResource();
1004  $sql .= 'AND target_resource_id = :dav_id';
1005  $params[':dav_id'] = $this->resource->dav_id;
1006  }
1007  if ( isset($this->exists) && !$this->exists ) return;
1008 
1009  $qry = new AwlQuery( $sql, $params );
1010  if ( $qry->Exec('DAVResource',__LINE__,__FILE__) && $qry->rows() ) {
1011  while( $ticket = $qry->Fetch() ) {
1012  $this->access_tickets[] = $ticket;
1013  }
1014  }
1015  }
1016 
1017 
1028  function BuildTicketinfo( &$reply ) {
1029  global $session, $request;
1030 
1031  if ( !isset($this->access_tickets) ) $this->FetchTickets();
1032  $tickets = array();
1033  $show_all = $this->HavePrivilegeTo('DAV::read-acl');
1034  foreach( $this->access_tickets AS $meh => $trow ) {
1035  if ( !$show_all && ( $trow->dav_owner_id == $session->principal_id || $request->ticket->id() == $trow->ticket_id ) ) continue;
1036  dbg_error_log( 'DAVResource', ':BuildTicketinfo: Adding access_ticket "%s" which is "%s".', $trow->ticket_id, $trow->privileges );
1037  $ticket = new XMLElement( $reply->Tag( 'ticketinfo', 'http://www.xythos.com/namespaces/StorageServer', 'TKT' ) );
1038  $reply->NSElement($ticket, 'http://www.xythos.com/namespaces/StorageServer:id', $trow->ticket_id );
1039  $reply->NSElement($ticket, 'http://www.xythos.com/namespaces/StorageServer:owner', $reply->href( ConstructURL($trow->principal_dav_name)) );
1040  $reply->NSElement($ticket, 'http://www.xythos.com/namespaces/StorageServer:timeout', (isset($trow->seconds) ? sprintf( 'Seconds-%d', $trow->seconds) : 'infinity') );
1041  $reply->NSElement($ticket, 'http://www.xythos.com/namespaces/StorageServer:visits', 'infinity' );
1042  $privs = array();
1043  foreach( bits_to_privilege(bindec($trow->privileges) & bindec($trow->grantor_collection_privileges) ) AS $k => $v ) {
1044  $privs[] = $reply->NewXMLElement($v);
1045  }
1046  $reply->NSElement($ticket, 'DAV::privilege', $privs );
1047  $tickets[] = $ticket;
1048  }
1049  return $tickets;
1050  }
1051 
1052 
1060  function IsLocked( $depth = 0 ) {
1061  if ( !isset($this->_locks_found) ) {
1062  $this->_locks_found = array();
1066  $sql = 'SELECT * FROM locks WHERE :this_path::text ~ (\'^\'||dav_name||:match_end)::text';
1067  $qry = new AwlQuery($sql, array( ':this_path' => $this->dav_name, ':match_end' => ($depth == DEPTH_INFINITY ? '' : '$') ) );
1068  if ( $qry->Exec('DAVResource',__LINE__,__FILE__) ) {
1069  while( $lock_row = $qry->Fetch() ) {
1070  $this->_locks_found[$lock_row->opaquelocktoken] = $lock_row;
1071  }
1072  }
1073  else {
1074  $this->DoResponse(500,i18n("Database Error"));
1075  // Does not return.
1076  }
1077  }
1078 
1079  foreach( $this->_locks_found AS $lock_token => $lock_row ) {
1080  if ( $lock_row->depth == DEPTH_INFINITY || $lock_row->dav_name == $this->dav_name ) {
1081  return $lock_token;
1082  }
1083  }
1084 
1085  return false; // Nothing matched
1086  }
1087 
1088 
1092  function IsCollection() {
1093  return $this->_is_collection;
1094  }
1095 
1096 
1100  function IsPrincipal() {
1101  return $this->_is_collection && $this->_is_principal;
1102  }
1103 
1104 
1108  function IsCalendar() {
1109  return $this->_is_collection && $this->_is_calendar;
1110  }
1111 
1112 
1117  function IsProxyCollection( $type = 'any' ) {
1118  if ( $this->_is_proxy_resource ) {
1119  return ($type == 'any' || $type == $this->proxy_type);
1120  }
1121  return false;
1122  }
1123 
1124 
1129  function IsSchedulingCollection( $type = 'any' ) {
1130  if ( $this->_is_collection && preg_match( '{schedule-(inbox|outbox)}', $this->collection->type, $matches ) ) {
1131  return ($type == 'any' || $type == $matches[1]);
1132  }
1133  return false;
1134  }
1135 
1136 
1141  function IsInSchedulingCollection( $type = 'any' ) {
1142  if ( !$this->_is_collection && preg_match( '{schedule-(inbox|outbox)}', $this->collection->type, $matches ) ) {
1143  return ($type == 'any' || $type == $matches[1]);
1144  }
1145  return false;
1146  }
1147 
1148 
1152  function IsAddressbook() {
1153  return $this->_is_collection && $this->_is_addressbook;
1154  }
1155 
1156 
1160  function IsBinding() {
1161  return $this->_is_binding;
1162  }
1163 
1164 
1168  function IsExternal() {
1169  return $this->_is_external;
1170  }
1171 
1172 
1176  function Exists() {
1177  if ( ! isset($this->exists) ) {
1178  if ( $this->IsPrincipal() ) {
1179  if ( !isset($this->principal) ) $this->FetchPrincipal();
1180  $this->exists = $this->principal->Exists();
1181  }
1182  else if ( ! $this->IsCollection() ) {
1183  if ( !isset($this->resource) ) $this->FetchResource();
1184  }
1185  }
1186 // dbg_error_log('DAVResource',' Checking whether "%s" exists. It would appear %s.', $this->dav_name, ($this->exists ? 'so' : 'not') );
1187  return $this->exists;
1188  }
1189 
1190 
1194  function ContainerExists() {
1195  if ( $this->collection->dav_name != $this->dav_name ) {
1196  return $this->collection->exists;
1197  }
1198  $parent = $this->GetParentContainer();
1199  return $parent->Exists();
1200  }
1201 
1202 
1207  function url() {
1208  if ( !isset($this->dav_name) ) {
1209  throw Exception("What! How can dav_name not be set?");
1210  }
1211  return ConstructURL($this->dav_name);
1212  }
1213 
1214 
1219  function dav_name() {
1220  if ( isset($this->dav_name) ) return $this->dav_name;
1221  return null;
1222  }
1223 
1224 
1229  function bound_from() {
1230  if ( isset($this->bound_from) ) return $this->bound_from;
1231  return $this->dav_name();
1232  }
1233 
1234 
1238  function set_bind_location( $new_dav_name ) {
1239  if ( !isset($this->bound_from) && isset($this->dav_name) ) {
1240  $this->bound_from = $this->dav_name;
1241  }
1242  $this->dav_name = $new_dav_name;
1243  return $this->dav_name;
1244  }
1245 
1246 
1250  function parent_path() {
1251  if ( $this->IsCollection() ) {
1252  if ( !isset($this->collection) ) $this->FetchCollection();
1253  if ( !isset($this->collection->parent_container) ) {
1254  $this->collection->parent_container = preg_replace( '{[^/]+/$}', '', $this->bound_from());
1255  }
1256  return $this->collection->parent_container;
1257  }
1258  return preg_replace( '{[^/]+$}', '', $this->bound_from());
1259  }
1260 
1261 
1262 
1266  function principal_url() {
1267  if ( !isset($this->principal) ) $this->FetchPrincipal();
1268  return $this->principal->url();
1269  }
1270 
1271 
1275  function user_no() {
1276  if ( !isset($this->principal) ) $this->FetchPrincipal();
1277  return $this->principal->user_no();
1278  }
1279 
1280 
1284  function collection_id() {
1285  if ( !isset($this->collection) ) $this->FetchCollection();
1286  return $this->collection->collection_id;
1287  }
1288 
1289 
1293  function resource() {
1294  if ( !isset($this->resource) ) $this->FetchResource();
1295  return $this->resource;
1296  }
1297 
1298 
1302  function unique_tag() {
1303  if ( isset($this->unique_tag) ) return $this->unique_tag;
1304  if ( $this->IsPrincipal() && !isset($this->principal) ) {
1305  $this->FetchPrincipal();
1306  $this->unique_tag = $this->principal->unique_tag();
1307  }
1308  else if ( !$this->_is_collection && !isset($this->resource) ) $this->FetchResource();
1309 
1310  if ( $this->exists !== true || !isset($this->unique_tag) ) $this->unique_tag = '';
1311 
1312  return $this->unique_tag;
1313  }
1314 
1315 
1319  function resource_id() {
1320  if ( isset($this->resource_id) ) return $this->resource_id;
1321  if ( $this->IsPrincipal() && !isset($this->principal) ) $this->FetchPrincipal();
1322  else if ( !$this->_is_collection && !isset($this->resource) ) $this->FetchResource();
1323 
1324  if ( $this->exists !== true || !isset($this->resource_id) ) $this->resource_id = null;
1325 
1326  return $this->resource_id;
1327  }
1328 
1329 
1333  function sync_token( $cachedOK = true ) {
1334  dbg_error_log('DAVResource', 'Request for a%scached sync-token', ($cachedOK ? ' ' : 'n un') );
1335  if ( $this->IsPrincipal() ) return null;
1336  if ( $this->collection_id() == 0 ) return null;
1337  if ( !isset($this->sync_token) || !$cachedOK ) {
1338  $sql = 'SELECT new_sync_token( 0, :collection_id) AS sync_token';
1339  $params = array( ':collection_id' => $this->collection_id());
1340  $qry = new AwlQuery($sql, $params );
1341  if ( !$qry->Exec() || !$row = $qry->Fetch() ) {
1342  if ( !$qry->QDo('SELECT new_sync_token( 0, :collection_id) AS sync_token', $params) ) throw new Exception('Problem with database query');
1343  $row = $qry->Fetch();
1344  }
1345  $this->sync_token = 'data:,'.$row->sync_token;
1346  }
1347  dbg_error_log('DAVResource', 'Returning sync token of "%s"', $this->sync_token );
1348  return $this->sync_token;
1349  }
1350 
1354  function IsPublic() {
1355  return ( isset($this->collection->publicly_readable) && $this->collection->publicly_readable == 't' );
1356  }
1357 
1358 
1362  function IsPublicOnly() {
1363  return ( isset($this->collection->publicly_events_only) && $this->collection->publicly_events_only == 't' );
1364  }
1365 
1366 
1370  function ContainerType() {
1371  if ( $this->IsPrincipal() ) return 'root';
1372  if ( !$this->IsCollection() ) return $this->collection->type;
1373 
1374  if ( ! isset($this->collection->parent_container) ) return null;
1375 
1376  if ( isset($this->parent_container_type) ) return $this->parent_container_type;
1377 
1378  if ( preg_match('#/[^/]+/#', $this->collection->parent_container) ) {
1379  $this->parent_container_type = 'principal';
1380  }
1381  else {
1382  $qry = new AwlQuery('SELECT * FROM collection WHERE dav_name = :parent_name',
1383  array( ':parent_name' => $this->collection->parent_container ) );
1384  if ( $qry->Exec('DAVResource') && $qry->rows() > 0 && $parent = $qry->Fetch() ) {
1385  if ( $parent->is_calendar == 't' )
1386  $this->parent_container_type = 'calendar';
1387  else if ( $parent->is_addressbook == 't' )
1388  $this->parent_container_type = 'addressbook';
1389  else if ( preg_match( '#^((/[^/]+/)\.(in|out)/)[^/]*$#', $this->dav_name, $matches ) )
1390  $this->parent_container_type = 'schedule-'. $matches[3]. 'box';
1391  else
1392  $this->parent_container_type = 'collection';
1393  }
1394  else
1395  $this->parent_container_type = null;
1396  }
1397  return $this->parent_container_type;
1398  }
1399 
1400 
1404  function BuildACE( &$xmldoc, $privs, $principal ) {
1405  $privilege_names = bits_to_privilege($privs, ($this->_is_collection ? $this->collection->type : 'resource'));
1406  $privileges = array();
1407  foreach( $privilege_names AS $k ) {
1408  $privilege = new XMLElement('privilege');
1409  if ( isset($xmldoc) )
1410  $xmldoc->NSElement($privilege,$k);
1411  else
1412  $privilege->NewElement($k);
1413  $privileges[] = $privilege;
1414  }
1415  $ace = new XMLElement('ace', array(
1416  new XMLElement('principal', $principal),
1417  new XMLElement('grant', $privileges ) )
1418  );
1419  return $ace;
1420  }
1421 
1425  function GetACL( &$xmldoc ) {
1426  if ( !isset($this->principal) ) $this->FetchPrincipal();
1427  $default_privs = $this->principal->default_privileges;
1428  if ( isset($this->collection->default_privileges) ) $default_privs = $this->collection->default_privileges;
1429 
1430  $acl = array();
1431  $acl[] = $this->BuildACE($xmldoc, pow(2,25) - 1, new XMLElement('property', new XMLElement('owner')) );
1432 
1433  $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',
1434  array( ':collection_id' => $this->collection->collection_id,
1435  ':principal_id' => $this->principal->principal_id() ) );
1436  if ( $qry->Exec('DAVResource') && $qry->rows() > 0 ) {
1437  $by_collection = null;
1438  while( $grant = $qry->Fetch() ) {
1439  if ( !isset($by_collection) ) $by_collection = isset($grant->by_collection);
1440  if ( $by_collection && !isset($grant->by_collection) ) break;
1441  $acl[] = $this->BuildACE($xmldoc, $grant->privileges, $xmldoc->href(ConstructURL($grant->dav_name)) );
1442  }
1443  }
1444 
1445  $acl[] = $this->BuildACE($xmldoc, $default_privs, new XMLElement('authenticated') );
1446 
1447  return $acl;
1448 
1449  }
1450 
1451 
1455  function GetProperty( $name ) {
1456 // dbg_error_log( 'DAVResource', ':GetProperty: Fetching "%s".', $name );
1457  $value = null;
1458 
1459  switch( $name ) {
1460  case 'collection_id':
1461  return $this->collection_id();
1462  break;
1463 
1464  case 'principal_id':
1465  if ( !isset($this->principal) ) $this->FetchPrincipal();
1466  return $this->principal->principal_id();
1467  break;
1468 
1469  case 'resourcetype':
1470  if ( isset($this->resourcetypes) ) {
1471  $this->resourcetypes = preg_replace('{^\s*<(.*)/>\s*$}', '$1', $this->resourcetypes);
1472  $type_list = preg_split('{(/>\s*<|\n)}', $this->resourcetypes);
1473  foreach( $type_list AS $k => $resourcetype ) {
1474  if ( preg_match( '{^([^:]+):([^:]+) \s+ xmlns:([^=]+)="([^"]+)" \s* $}x', $resourcetype, $matches ) ) {
1475  $type_list[$k] = $matches[4] .':' .$matches[2];
1476  }
1477  else if ( preg_match( '{^([^:]+) \s+ xmlns="([^"]+)" \s* $}x', $resourcetype, $matches ) ) {
1478  $type_list[$k] = $matches[2] .':' .$matches[1];
1479  }
1480  }
1481  return $type_list;
1482  }
1483 
1484  case 'resource':
1485  if ( !isset($this->resource) ) $this->FetchResource();
1486  return clone($this->resource);
1487  break;
1488 
1489  case 'dav-data':
1490  if ( !isset($this->resource) ) $this->FetchResource();
1491  dbg_error_log( 'DAVResource', ':GetProperty: dav-data: fetched resource does%s exist.', ($this->exists?'':' not') );
1492  return $this->resource->caldav_data;
1493  break;
1494 
1495  case 'principal':
1496  if ( !isset($this->principal) ) $this->FetchPrincipal();
1497  return clone($this->principal);
1498  break;
1499 
1500  default:
1501  if ( isset($this->{$name}) ) {
1502  if ( ! is_object($this->{$name}) ) return $this->{$name};
1503  return clone($this->{$name});
1504  }
1505  if ( $this->_is_principal ) {
1506  if ( !isset($this->principal) ) $this->FetchPrincipal();
1507  if ( isset($this->principal->{$name}) ) return $this->principal->{$name};
1508  if ( isset($this->collection->{$name}) ) return $this->collection->{$name};
1509  }
1510  else if ( $this->_is_collection ) {
1511  if ( isset($this->collection->{$name}) ) return $this->collection->{$name};
1512  if ( isset($this->principal->{$name}) ) return $this->principal->{$name};
1513  }
1514  else {
1515  if ( !isset($this->resource) ) $this->FetchResource();
1516  if ( isset($this->resource->{$name}) ) return $this->resource->{$name};
1517  if ( !isset($this->principal) ) $this->FetchPrincipal();
1518  if ( isset($this->principal->{$name}) ) return $this->principal->{$name};
1519  if ( isset($this->collection->{$name}) ) return $this->collection->{$name};
1520  }
1521  if ( isset($this->{$name}) ) {
1522  if ( ! is_object($this->{$name}) ) return $this->{$name};
1523  return clone($this->{$name});
1524  }
1525  // dbg_error_log( 'DAVResource', ':GetProperty: Failed to find property "%s" on "%s".', $name, $this->dav_name );
1526  }
1527 
1528  return $value;
1529  }
1530 
1531 
1535  function DAV_AllProperties() {
1536  if ( isset($this->dead_properties) ) $this->FetchDeadProperties();
1537  $allprop = array_merge( (isset($this->dead_properties)?$this->dead_properties:array()),
1538  (isset($include_properties)?$include_properties:array()),
1539  array(
1540  'DAV::getcontenttype', 'DAV::resourcetype', 'DAV::getcontentlength', 'DAV::displayname', 'DAV::getlastmodified',
1541  'DAV::creationdate', 'DAV::getetag', 'DAV::getcontentlanguage', 'DAV::supportedlock', 'DAV::lockdiscovery',
1542  'DAV::owner', 'DAV::principal-URL', 'DAV::current-user-principal',
1543  'urn:ietf:params:xml:ns:carddav:max-resource-size', 'urn:ietf:params:xml:ns:carddav:supported-address-data',
1544  'urn:ietf:params:xml:ns:carddav:addressbook-description', 'urn:ietf:params:xml:ns:carddav:addressbook-home-set'
1545  ) );
1546 
1547  return $allprop;
1548  }
1549 
1550 
1554  function ResourceProperty( $tag, $prop, &$reply, &$denied ) {
1555  global $c, $session, $request;
1556 
1557 // dbg_error_log( 'DAVResource', 'Processing "%s" on "%s".', $tag, $this->dav_name );
1558 
1559  if ( $reply === null ) $reply = $GLOBALS['reply'];
1560 
1561  switch( $tag ) {
1562  case 'DAV::allprop':
1563  $property_list = $this->DAV_AllProperties();
1564  $discarded = array();
1565  foreach( $property_list AS $k => $v ) {
1566  $this->ResourceProperty($v, $prop, $reply, $discarded);
1567  }
1568  break;
1569 
1570  case 'DAV::href':
1571  $prop->NewElement('href', ConstructURL($this->dav_name) );
1572  break;
1573 
1574  case 'DAV::resource-id':
1575  if ( $this->resource_id > 0 )
1576  $reply->DAVElement( $prop, 'resource-id', $reply->href(ConstructURL('/.resources/'.$this->resource_id) ) );
1577  else
1578  return false;
1579  break;
1580 
1581  case 'DAV::parent-set':
1582  $sql = <<<EOQRY
1583 SELECT b.parent_container FROM dav_binding b JOIN collection c ON (b.bound_source_id=c.collection_id)
1584  WHERE regexp_replace( b.dav_name, '^.*/', c.dav_name ) = :bound_from
1585 EOQRY;
1586  $qry = new AwlQuery($sql, array( ':bound_from' => $this->bound_from() ) );
1587  $parents = array();
1588  if ( $qry->Exec('DAVResource',__LINE__,__FILE__) && $qry->rows() > 0 ) {
1589  while( $row = $qry->Fetch() ) {
1590  $parents[$row->parent_container] = true;
1591  }
1592  }
1593  $parents[preg_replace( '{(?<=/)[^/]+/?$}','',$this->bound_from())] = true;
1594  $parents[preg_replace( '{(?<=/)[^/]+/?$}','',$this->dav_name())] = true;
1595 
1596  $parent_set = $reply->DAVElement( $prop, 'parent-set' );
1597  foreach( $parents AS $parent => $v ) {
1598  if ( preg_match( '{^(.*)?/([^/]+)/?$}', $parent, $matches ) ) {
1599  $reply->DAVElement($parent_set, 'parent', array(
1600  new XMLElement( 'href', ConstructURL($matches[1])),
1601  new XMLElement( 'segment', $matches[2])
1602  ));
1603  }
1604  else if ( $parent == '/' ) {
1605  $reply->DAVElement($parent_set, 'parent', array(
1606  new XMLElement( 'href', '/'),
1607  new XMLElement( 'segment', ( ConstructURL('/') == '/caldav.php/' ? 'caldav.php' : ''))
1608  ));
1609  }
1610  }
1611  break;
1612 
1613  case 'DAV::getcontenttype':
1614  if ( !isset($this->contenttype) && !$this->_is_collection && !isset($this->resource) ) $this->FetchResource();
1615  $prop->NewElement('getcontenttype', $this->contenttype );
1616  break;
1617 
1618  case 'DAV::resourcetype':
1619  $resourcetypes = $prop->NewElement('resourcetype' );
1620  if ( $this->_is_collection ) {
1621  $type_list = $this->GetProperty('resourcetype');
1622  if ( !is_array($type_list) ) return true;
1623  // dbg_error_log( 'DAVResource', ':ResourceProperty: "%s" are "%s".', $tag, implode(', ',$type_list) );
1624  foreach( $type_list AS $k => $v ) {
1625  if ( $v == '' ) continue;
1626  $reply->NSElement( $resourcetypes, $v );
1627  }
1628  if ( $this->_is_binding ) {
1629  $reply->NSElement( $resourcetypes, 'http://xmlns.davical.org/davical:webdav-binding' );
1630  }
1631  }
1632  break;
1633 
1634  case 'DAV::getlastmodified':
1636  $reply->NSElement($prop, $tag, ISODateToHTTPDate($this->GetProperty('modified')) );
1637  break;
1638 
1639  case 'DAV::creationdate':
1641  $reply->NSElement($prop, $tag, DateToISODate($this->GetProperty('created'), true) );
1642  break;
1643 
1644  case 'DAV::getcontentlength':
1645  if ( $this->_is_collection ) return false;
1646  if ( !isset($this->resource) ) $this->FetchResource();
1647  if ( isset($this->resource) ) {
1648  $reply->NSElement($prop, $tag, strlen($this->resource->caldav_data) );
1649  }
1650  break;
1651 
1652  case 'DAV::getcontentlanguage':
1653  $locale = (isset($c->current_locale) ? $c->current_locale : '');
1654  if ( isset($this->locale) && $this->locale != '' ) $locale = $this->locale;
1655  $reply->NSElement($prop, $tag, $locale );
1656  break;
1657 
1658  case 'DAV::acl-restrictions':
1659  $reply->NSElement($prop, $tag, array( new XMLElement('grant-only'), new XMLElement('no-invert') ) );
1660  break;
1661 
1662  case 'DAV::inherited-acl-set':
1663  $inherited_acls = array();
1664  if ( ! $this->_is_collection ) {
1665  $inherited_acls[] = $reply->href(ConstructURL($this->collection->dav_name));
1666  }
1667  $reply->NSElement($prop, $tag, $inherited_acls );
1668  break;
1669 
1670  case 'DAV::owner':
1671  // The principal-URL of the owner
1672  if ( $this->IsExternal() ){
1673  $reply->DAVElement( $prop, 'owner', $reply->href( ConstructURL($this->collection->bound_from )) );
1674  }
1675  else {
1676  $reply->DAVElement( $prop, 'owner', $reply->href( ConstructURL(DeconstructURL($this->principal_url())) ) );
1677  }
1678  break;
1679 
1680  case 'DAV::add-member':
1681  if ( ! $this->_is_collection ) return false;
1682  if ( $this->_is_principal ) return false;
1683  if ( isset($c->post_add_member) && $c->post_add_member === false ) return false;
1684  $reply->DAVElement( $prop, 'add-member', $reply->href(ConstructURL(DeconstructURL($this->url())).'?add_member') );
1685  break;
1686 
1687  // Empty tag responses.
1688  case 'DAV::group':
1689  case 'DAV::alternate-URI-set':
1690  $reply->NSElement($prop, $tag );
1691  break;
1692 
1693  case 'DAV::getetag':
1694  if ( $this->_is_collection ) return false;
1695  $reply->NSElement($prop, $tag, $this->unique_tag() );
1696  break;
1697 
1698  case 'http://calendarserver.org/ns/:getctag':
1699  if ( ! $this->_is_collection ) return false;
1700  $reply->NSElement($prop, $tag, $this->unique_tag() );
1701  break;
1702 
1703  case 'DAV::sync-token':
1704  if ( ! $this->_is_collection ) return false;
1705  $sync_token = $this->sync_token();
1706  if ( empty($sync_token) ) return false;
1707  $reply->NSElement($prop, $tag, $sync_token );
1708  break;
1709 
1710  case 'http://calendarserver.org/ns/:calendar-proxy-read-for':
1711  $proxy_type = 'read';
1712  case 'http://calendarserver.org/ns/:calendar-proxy-write-for':
1713  if ( isset($c->disable_caldav_proxy) && $c->disable_caldav_proxy ) return false;
1714  if ( !isset($proxy_type) ) $proxy_type = 'write';
1715  // ProxyFor is an already constructed URL
1716  $this->FetchPrincipal();
1717  $reply->CalendarserverElement($prop, 'calendar-proxy-'.$proxy_type.'-for', $reply->href( $this->principal->ProxyFor($proxy_type) ) );
1718  break;
1719 
1720  case 'http://calendarserver.org/ns/:group-member-set':
1721  case 'DAV::group-member-set':
1722  if ( $this->_is_proxy_resource ) {
1723  $this->FetchPrincipal();
1724  if ( $this->proxy_type == 'read' ) {
1725  $reply->DAVElement( $prop, 'group-member-set', $reply->href( $this->principal->ReadProxyGroup() ) );
1726  } else {
1727  $reply->DAVElement( $prop, 'group-member-set', $reply->href( $this->principal->WriteProxyGroup() ) );
1728  }
1729  } else {
1730  return false; // leave this to DAVPrincipal
1731  }
1732  break;
1733 
1734  case 'http://calendarserver.org/ns/:group-membership':
1735  case 'DAV::group-membership':
1736  if ( $this->_is_proxy_resource ) {
1737  /* the calendar-proxy-{read,write} pseudo-principal should not be a member of any group */
1738  $reply->NSElement($prop, $tag );
1739  } else {
1740  return false; // leave this to DAVPrincipal
1741  }
1742  break;
1743 
1744  case 'DAV::current-user-privilege-set':
1745  if ( $this->HavePrivilegeTo('DAV::read-current-user-privilege-set') ) {
1746  $reply->NSElement($prop, $tag, $this->BuildPrivileges() );
1747  }
1748  else {
1749  $denied[] = $tag;
1750  }
1751  break;
1752 
1753  case 'urn:ietf:params:xml:ns:caldav:supported-calendar-data':
1754  if ( ! $this->IsCalendar() && ! $this->IsSchedulingCollection() ) return false;
1755  $reply->NSElement($prop, $tag, 'text/calendar' );
1756  break;
1757 
1758  case 'urn:ietf:params:xml:ns:caldav:supported-calendar-component-set':
1759  if ( ! $this->_is_collection ) return false;
1760  if ( $this->IsCalendar() ) {
1761  if ( !isset($this->dead_properties) ) $this->FetchDeadProperties();
1762  if ( isset($this->dead_properties[$tag]) ) {
1763  $set_of_components = $this->dead_properties[$tag];
1764  foreach( $set_of_components AS $k => $v ) {
1765  if ( preg_match('{(VEVENT|VTODO|VJOURNAL|VTIMEZONE|VFREEBUSY|VPOLL|VAVAILABILITY)}', $v, $matches) ) {
1766  $set_of_components[$k] = $matches[1];
1767  }
1768  else {
1769  unset( $set_of_components[$k] );
1770  }
1771  }
1772  }
1773  else if ( isset($c->default_calendar_components) && is_array($c->default_calendar_components) ) {
1774  $set_of_components = $c->default_calendar_components;
1775  }
1776  else {
1777  $set_of_components = array( 'VEVENT', 'VTODO', 'VJOURNAL' );
1778  }
1779  }
1780  else if ( $this->IsSchedulingCollection() )
1781  $set_of_components = array( 'VEVENT', 'VTODO', 'VFREEBUSY' );
1782  else return false;
1783  $components = array();
1784  foreach( $set_of_components AS $v ) {
1785  $components[] = $reply->NewXMLElement( 'comp', '', array('name' => $v), 'urn:ietf:params:xml:ns:caldav');
1786  }
1787  $reply->CalDAVElement($prop, 'supported-calendar-component-set', $components );
1788  break;
1789 
1790  case 'DAV::supported-method-set':
1791  $prop->NewElement('supported-method-set', $this->BuildSupportedMethods() );
1792  break;
1793 
1794  case 'DAV::supported-report-set':
1795  $prop->NewElement('supported-report-set', $this->BuildSupportedReports( $reply ) );
1796  break;
1797 
1798  case 'DAV::supportedlock':
1799  $prop->NewElement('supportedlock',
1800  new XMLElement( 'lockentry',
1801  array(
1802  new XMLElement('lockscope', new XMLElement('exclusive')),
1803  new XMLElement('locktype', new XMLElement('write')),
1804  )
1805  )
1806  );
1807  break;
1808 
1809  case 'DAV::supported-privilege-set':
1810  $prop->NewElement('supported-privilege-set', $request->BuildSupportedPrivileges($reply) );
1811  break;
1812 
1813  case 'DAV::principal-collection-set':
1814  $prop->NewElement( 'principal-collection-set', $reply->href( ConstructURL('/') ) );
1815  break;
1816 
1817  case 'DAV::current-user-principal':
1818  $prop->NewElement('current-user-principal', $reply->href( ConstructURL(DeconstructURL($session->principal->url())) ) );
1819  break;
1820 
1821  case 'SOME-DENIED-PROPERTY':
1822  $denied[] = $reply->Tag($tag);
1823  break;
1824 
1825  case 'urn:ietf:params:xml:ns:caldav:calendar-timezone':
1826  if ( ! $this->_is_collection ) return false;
1827  if ( !isset($this->collection->vtimezone) || $this->collection->vtimezone == '' ) return false;
1828 
1829  $cal = new iCalComponent();
1830  $cal->VCalendar();
1831  $cal->AddComponent( new iCalComponent($this->collection->vtimezone) );
1832  $reply->NSElement($prop, $tag, $cal->Render() );
1833  break;
1834 
1835  case 'urn:ietf:params:xml:ns:carddav:address-data':
1836  case 'urn:ietf:params:xml:ns:caldav:calendar-data':
1837  if ( $this->_is_collection ) return false;
1838  if ( !isset($c->sync_resource_data_ok) || $c->sync_resource_data_ok == false ) return false;
1839  if ( !isset($this->resource) ) $this->FetchResource();
1840  $reply->NSElement($prop, $tag, $this->resource->caldav_data );
1841  break;
1842 
1843  case 'urn:ietf:params:xml:ns:carddav:max-resource-size':
1844  if ( ! $this->_is_collection || !$this->_is_addressbook ) return false;
1845  $reply->NSElement($prop, $tag, 65500 );
1846  break;
1847 
1848  case 'urn:ietf:params:xml:ns:carddav:supported-address-data':
1849  if ( ! $this->_is_collection || !$this->_is_addressbook ) return false;
1850  $address_data = $reply->NewXMLElement( 'address-data', false,
1851  array( 'content-type' => 'text/vcard', 'version' => '3.0'), 'urn:ietf:params:xml:ns:carddav');
1852  $reply->NSElement($prop, $tag, $address_data );
1853  break;
1854 
1855  case 'DAV::acl':
1856  if ( $this->HavePrivilegeTo('DAV::read-acl') ) {
1857  $reply->NSElement($prop, $tag, $this->GetACL( $reply ) );
1858  }
1859  else {
1860  $denied[] = $tag;
1861  }
1862  break;
1863 
1864  case 'http://www.xythos.com/namespaces/StorageServer:ticketdiscovery':
1865  case 'DAV::ticketdiscovery':
1866  $reply->NSElement($prop,'http://www.xythos.com/namespaces/StorageServer:ticketdiscovery', $this->BuildTicketinfo($reply) );
1867  break;
1868 
1869  default:
1870  $property_value = $this->GetProperty(preg_replace('{^(DAV:|urn:ietf:params:xml:ns:ca(rd|l)dav):}', '', $tag));
1871  if ( isset($property_value) ) {
1872  $reply->NSElement($prop, $tag, $property_value );
1873  }
1874  else {
1875  if ( !isset($this->dead_properties) ) $this->FetchDeadProperties();
1876  if ( isset($this->dead_properties[$tag]) ) {
1877  $reply->NSElement($prop, $tag, $this->dead_properties[$tag] );
1878  }
1879  else {
1880 // dbg_error_log( 'DAVResource', 'Request for unsupported property "%s" of path "%s".', $tag, $this->dav_name );
1881  return false;
1882  }
1883  }
1884  }
1885 
1886  return true;
1887  }
1888 
1889 
1897  function GetPropStat( $properties, &$reply, $props_only = false ) {
1898  global $request;
1899 
1900  dbg_error_log('DAVResource',':GetPropStat: propstat for href "%s"', $this->dav_name );
1901 
1902  $prop = new XMLElement('prop', null, null, 'DAV:');
1903  $denied = array();
1904  $not_found = array();
1905  foreach( $properties AS $k => $tag ) {
1906  if ( is_object($tag) ) {
1907  dbg_error_log( 'DAVResource', ':GetPropStat: "$properties" should be an array of text. Assuming this object is an XMLElement!.' );
1908  $tag = $tag->GetNSTag();
1909  }
1910  $found = $this->ResourceProperty($tag, $prop, $reply, $denied );
1911  if ( !$found ) {
1912  if ( !isset($this->principal) ) $this->FetchPrincipal();
1913  $found = $this->principal->PrincipalProperty( $tag, $prop, $reply, $denied );
1914  }
1915  if ( ! $found ) {
1916 // dbg_error_log( 'DAVResource', 'Request for unsupported property "%s" of resource "%s".', $tag, $this->dav_name );
1917  $not_found[] = $tag;
1918  }
1919  }
1920  if ( $props_only ) return $prop;
1921 
1922  $status = new XMLElement('status', 'HTTP/1.1 200 OK', null, 'DAV:' );
1923 
1924  $elements = array( new XMLElement( 'propstat', array($prop,$status), null, 'DAV:' ) );
1925 
1926  if ( count($denied) > 0 ) {
1927  $status = new XMLElement('status', 'HTTP/1.1 403 Forbidden', null, 'DAV:' );
1928  $noprop = new XMLElement('prop', null, null, 'DAV:');
1929  foreach( $denied AS $k => $v ) {
1930  $reply->NSElement($noprop, $v);
1931  }
1932  $elements[] = new XMLElement( 'propstat', array( $noprop, $status), null, 'DAV:' );
1933  }
1934 
1935  if ( !$request->PreferMinimal() && count($not_found) > 0 ) {
1936  $status = new XMLElement('status', 'HTTP/1.1 404 Not Found', null, 'DAV:' );
1937  $noprop = new XMLElement('prop', null, null, 'DAV:');
1938  foreach( $not_found AS $k => $v ) {
1939  $reply->NSElement($noprop,$v);
1940  }
1941  $elements[] = new XMLElement( 'propstat', array( $noprop, $status), null, 'DAV:' );
1942  }
1943  return $elements;
1944  }
1945 
1946 
1955  function RenderAsXML( $properties, &$reply, $bound_parent_path = null ) {
1956  dbg_error_log('DAVResource',':RenderAsXML: Resource "%s" exists(%d)', $this->dav_name, $this->Exists() );
1957 
1958  if ( !$this->Exists() ) return null;
1959 
1960  $elements = $this->GetPropStat( $properties, $reply );
1961  if ( isset($bound_parent_path) ) {
1962  $dav_name = str_replace( $this->parent_path(), $bound_parent_path, $this->dav_name );
1963  }
1964  else {
1965  $dav_name = $this->dav_name;
1966  }
1967 
1968  array_unshift( $elements, $reply->href(ConstructURL($dav_name)));
1969 
1970  $response = new XMLElement( 'response', $elements, null, 'DAV:' );
1971 
1972  return $response;
1973  }
1974 
1975 }
BuildSupportedMethods()
IsProxyCollection( $type='any')
IsSchedulingCollection( $type='any')
FetchParentContainer()
__construct( $parameters=null)
BuildTicketinfo(&$reply)
sync_token( $cachedOK=true)
BuildSupportedReports(&$reply)
NeedPrivilege( $privilege, $any=null)
BuildACE(&$xmldoc, $privs, $principal)
RenderAsXML( $properties, &$reply, $bound_parent_path=null)
static BuildDeadPropertyXML($property_name, $raw_string)
FetchSupportedMethods()
IsLocked( $depth=0)
GetProperty( $name)
set_bind_location( $new_dav_name)
FromPath($inpath)
BuildPrivileges( $privilege_names=null, &$xmldoc=null)
HavePrivilegeTo( $do_what, $any=null)
GetACL(&$xmldoc)
FetchSupportedReports()
GetPropStat( $properties, &$reply, $props_only=false)
ResourceProperty( $tag, $prop, &$reply, &$denied)
IsInSchedulingCollection( $type='any')