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