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