DAViCal
Loading...
Searching...
No Matches
DAVResource.php
1<?php
11
12require_once('AwlCache.php');
13require_once('AwlQuery.php');
14require_once('DAVPrincipal.php');
15require_once('DAVTicket.php');
16require_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' )
367 $this->FetchPrincipal();
368 }
369 else {
370 $this->FetchResource();
371 }
372 dbg_error_log( 'DAVResource', ':FromPath: Path "%s" is%s a collection%s.',
373 $this->dav_name,
374 ($this->_is_collection ? ' ' . $this->resourcetypes
375 : ' not'),
376 ($this->_is_principal ? ' and a principal'
377 : '')
378 );
379 }
380
381
382 private function ReadCollectionFromDatabase() {
383 global $c, $session;
384
385 $this->collection = (object) array(
386 'collection_id' => -1,
387 'type' => 'nonexistent',
388 'is_calendar' => false, 'is_principal' => false, 'is_addressbook' => false
389 );
390
391 $base_sql = 'SELECT collection.*, path_privs(:session_principal::int8, collection.dav_name,:scan_depth::int), ';
392 $base_sql .= 'p.principal_id, p.type_id AS principal_type_id, ';
393 $base_sql .= 'p.displayname AS principal_displayname, p.default_privileges AS principal_default_privileges, ';
394 $base_sql .= 'timezones.vtimezone ';
395 $base_sql .= 'FROM collection LEFT JOIN principal p USING (user_no) ';
396 $base_sql .= 'LEFT JOIN timezones ON (collection.timezone=timezones.tzid) ';
397 $base_sql .= 'WHERE ';
398 $sql = $base_sql .'collection.dav_name = :raw_path ';
399 $params = array( ':raw_path' => $this->dav_name, ':session_principal' => $session->principal_id, ':scan_depth' => $c->permission_scan_depth );
400 if ( !preg_match( '#/$#', $this->dav_name ) ) {
401 $sql .= ' OR collection.dav_name = :up_to_slash OR collection.dav_name = :plus_slash ';
402 $params[':up_to_slash'] = preg_replace( '#[^/]*$#', '', $this->dav_name);
403 $params[':plus_slash'] = $this->dav_name.'/';
404 }
405 $sql .= 'ORDER BY LENGTH(collection.dav_name) DESC LIMIT 1';
406 $qry = new AwlQuery( $sql, $params );
407 if ( $qry->Exec('DAVResource') && $qry->rows() == 1 && ($row = $qry->Fetch()) ) {
408 $this->collection = $row;
409 $this->collection->exists = true;
410
411 if ( $row->is_calendar == 't' )
412 $this->collection->type = 'calendar';
413 else if ( $row->is_addressbook == 't' )
414 $this->collection->type = 'addressbook';
415 else if ( preg_match( '#^((/[^/]+/)\.(in|out)/)[^/]*$#', $this->dav_name, $matches ) )
416 $this->collection->type = 'schedule-'. $matches[3]. 'box';
417 else
418 $this->collection->type = 'collection';
419
420 # Vanilla collection, safe to cache.
421 $this->_collection_is_cacheable = true;
422 }
423 else if ( preg_match( '{^( ( / ([^/]+) / ) \.(in|out)/ ) [^/]*$}x', $this->dav_name, $matches ) ) {
424 // The request is for a scheduling inbox or outbox (or something inside one) and we should auto-create it
425 $params = array( ':username' => $matches[3], ':parent_container' => $matches[2], ':dav_name' => $matches[1] );
426 $params[':boxname'] = ($matches[4] == 'in' ? ' Inbox' : ' Outbox');
427 $this->collection_type = 'schedule-'. $matches[4]. 'box';
428 $params[':resourcetypes'] = sprintf('<DAV::collection/><urn:ietf:params:xml:ns:caldav:%s/>', $this->collection_type );
429 $sql = <<<EOSQL
430INSERT INTO collection ( user_no, parent_container, dav_name, dav_displayname, is_calendar, created, modified, dav_etag, resourcetypes )
431 VALUES( (SELECT user_no FROM usr WHERE username = text(:username)),
432 :parent_container, :dav_name,
433 (SELECT fullname FROM usr WHERE username = text(:username)) || :boxname,
434 FALSE, current_timestamp, current_timestamp, '1', :resourcetypes )
435EOSQL;
436 $qry = new AwlQuery( $sql, $params );
437 $qry->Exec('DAVResource');
438 dbg_error_log( 'DAVResource', 'Created new collection as "%s".', trim($params[':boxname']) );
439
440 $params = array( ':raw_path' => $this->dav_name, ':session_principal' => $session->principal_id, ':scan_depth' => $c->permission_scan_depth );
441 $qry = new AwlQuery( $base_sql . ' dav_name = :raw_path', $params );
442 if ( $qry->Exec('DAVResource') && $qry->rows() == 1 && ($row = $qry->Fetch()) ) {
443 $this->collection = $row;
444 $this->collection->exists = true;
445 $this->collection->type = $this->collection_type;
446
447 # Vanilla collection, safe to cache.
448 $this->_collection_is_cacheable = true;
449 }
450 }
451 else if ( preg_match( '#^(/([^/]+)/calendar-proxy-(read|write))/?[^/]*$#', $this->dav_name, $matches ) ) {
452 $this->collection->type = 'proxy';
453 $this->_is_proxy_resource = true;
454 $this->proxy_type = $matches[3];
455 $this->collection->proxy_type = $matches[3];
456 $this->collection->dav_name = $this->dav_name;
457 $this->collection->dav_displayname = sprintf( '%s proxy %s', $matches[2], $matches[3] );
458 $this->collection->exists = true;
459 $this->collection->parent_container = '/' . $matches[2] . '/';
460
461 # Proxy collection, fetch from cache can handle the _is_proxy_resource and proxy_type metadata.
462 $this->_collection_is_cacheable = true;
463 }
464 else if ( preg_match( '#^(/[^/]+)/?$#', $this->dav_name, $matches)
465 || preg_match( '#^((/principals/[^/]+/)[^/]+)/?$#', $this->dav_name, $matches) ) {
466 $this->_is_principal = true;
467 $this->FetchPrincipal();
468 $this->collection->is_principal = true;
469 $this->collection->type = 'principal';
470
471 # We don't cache principals as a collection.
472 $this->_collection_is_cacheable = false;
473 }
474 else if ( $this->dav_name == '/' ) {
475 $this->collection->dav_name = '/';
476 $this->collection->type = 'root';
477 $this->collection->exists = true;
478 $this->collection->displayname = $c->system_name;
479 $this->collection->default_privileges = (1 | 16 | 32);
480 $this->collection->parent_container = '/';
481
482 # Vanilla collection, safe to cache.
483 $this->_collection_is_cacheable = true;
484 }
485 else {
486 $sql = <<<EOSQL
487SELECT collection.*, path_privs(:session_principal::int8, collection.dav_name,:scan_depth::int), p.principal_id,
488 p.type_id AS principal_type_id, p.displayname AS principal_displayname, p.default_privileges AS principal_default_privileges,
489 timezones.vtimezone, dav_binding.access_ticket_id, dav_binding.parent_container AS bind_parent_container,
490 dav_binding.dav_displayname, owner.dav_name AS bind_owner_url, dav_binding.dav_name AS bound_to,
491 dav_binding.external_url AS external_url, dav_binding.type AS external_type, dav_binding.bind_id AS bind_id
492FROM dav_binding
493 LEFT JOIN collection ON (collection.collection_id=bound_source_id)
494 LEFT JOIN principal p USING (user_no)
495 LEFT JOIN dav_principal owner ON (dav_binding.dav_owner_id=owner.principal_id)
496 LEFT JOIN timezones ON (collection.timezone=timezones.tzid)
497 WHERE dav_binding.dav_name = :raw_path
498EOSQL;
499 $params = array( ':raw_path' => $this->dav_name, ':session_principal' => $session->principal_id, ':scan_depth' => $c->permission_scan_depth );
500 if ( !preg_match( '#/$#', $this->dav_name ) ) {
501 $sql .= ' OR dav_binding.dav_name = :up_to_slash OR collection.dav_name = :plus_slash OR dav_binding.dav_name = :plus_slash ';
502 $params[':up_to_slash'] = preg_replace( '#[^/]*$#', '', $this->dav_name);
503 $params[':plus_slash'] = $this->dav_name.'/';
504 }
505 $sql .= ' ORDER BY LENGTH(dav_binding.dav_name) DESC LIMIT 1';
506 $qry = new AwlQuery( $sql, $params );
507 if ( $qry->Exec('DAVResource',__LINE__,__FILE__) && $qry->rows() == 1 && ($row = $qry->Fetch()) ) {
508 $this->collection = $row;
509 $this->collection->exists = true;
510 $this->collection->parent_set = $row->parent_container;
511 $this->collection->parent_container = $row->bind_parent_container;
512 $this->collection->bound_from = $row->dav_name;
513 $this->collection->dav_name = $row->bound_to;
514 if ( $row->is_calendar == 't' )
515 $this->collection->type = 'calendar';
516 else if ( $row->is_addressbook == 't' )
517 $this->collection->type = 'addressbook';
518 else if ( preg_match( '#^((/[^/]+/)\.(in|out)/)[^/]*$#', $this->dav_name, $matches ) )
519 $this->collection->type = 'schedule-'. $matches[3]. 'box';
520 else
521 $this->collection->type = 'collection';
522 if ( isset($row->external_url) && strlen($row->external_url) > 8 ) {
523 $this->_is_external = true;
524 if ( $row->external_type == 'calendar' )
525 $this->collection->type = 'calendar';
526 else if ( $row->external_type == 'addressbook' )
527 $this->collection->type = 'addressbook';
528 else
529 $this->collection->type = 'collection';
530 }
531 $this->_is_binding = true;
532 $this->bound_from = str_replace( $row->bound_to, $row->dav_name, $this->dav_name);
533 if ( isset($row->access_ticket_id) ) {
534 if ( !isset($this->tickets) ) $this->tickets = array();
535 $this->tickets[] = new DAVTicket($row->access_ticket_id);
536 }
537
538
539 # A lot of metadata is stored outside of the Collection, and the
540 # Collection is what we cache. Caching this type isn't safe as we
541 # can't restore all the metadata.
542 $this->_collection_is_cacheable = false;
543 }
544 else {
545 dbg_error_log( 'DAVResource', 'No collection for path "%s".', $this->dav_name );
546 $this->collection->exists = false;
547 $this->collection->dav_name = preg_replace('{/[^/]*$}', '/', $this->dav_name);
548 $this->_collection_is_cacheable = false;
549 }
550 }
551
552 }
553
557 protected function FetchCollection() {
558 global $session;
559
571 dbg_error_log( 'DAVResource', ':FetchCollection: Looking for collection for "%s".', $this->dav_name );
572
573 // Try and pull the answer out of a hat
574 $cache = getCacheInstance();
575 $cache_ns = 'collection-'.preg_replace( '{/[^/]*$}', '/', $this->dav_name);
576 $cache_key = 'dav_resource'.$session->user_no;
577 $this->collection = $cache->get( $cache_ns, $cache_key );
578
579 if ( $this->collection === false ) {
580 $this->ReadCollectionFromDatabase();
581
582 if ( $this->collection->type != 'principal' && $this->_collection_is_cacheable ) {
583 $cache_ns = 'collection-'.$this->collection->dav_name;
584 @dbg_error_log( 'Cache', ':FetchCollection: Setting cache ns "%s" key "%s". Type: %s', $cache_ns, $cache_key, $this->collection->type );
585 $cache->set( $cache_ns, $cache_key, $this->collection );
586 }
587
588 @dbg_error_log( 'DAVResource', ':FetchCollection: Found collection named "%s" of type "%s".', $this->collection->dav_name, $this->collection->type );
589 }
590 else {
591 @dbg_error_log( 'Cache', ':FetchCollection: Got cache ns "%s" key "%s". dav_name: "%s", Type: %s', $cache_ns, $cache_key, $this->collection->dav_name, $this->collection->type );
592
593 if ($this->collection->type == 'principal') {
594 $this->_is_principal = true;
595 $this->FetchPrincipal();
596 $this->collection->is_principal = true;
597
598 } else if ($this->collection->type == 'calendar') {
599 $this->_is_calendar = true;
600
601 } else if ($this->collection->type == 'proxy') {
602 $this->_is_proxy_resource = true;
603 $this->proxy_type = $this->collection->proxy_type;
604 }
605
606 @dbg_error_log( 'DAVResource', ':FetchCollection: Read cached collection named "%s" of type "%s".', $this->collection->dav_name, $this->collection->type );
607 }
608
609 if ( isset($this->collection->bound_from) ) {
610 $this->_is_binding = true;
611 $this->bound_from = str_replace( $this->collection->bound_to, $this->collection->bound_from, $this->dav_name);
612 if ( isset($this->collection->access_ticket_id) ) {
613 if ( !isset($this->tickets) ) $this->tickets = array();
614 $this->tickets[] = new DAVTicket($this->collection->access_ticket_id);
615 }
616 }
617
618 $this->_is_collection = ( $this->_is_principal || $this->collection->dav_name == $this->dav_name || $this->collection->dav_name == $this->dav_name.'/' );
619 if ( $this->_is_collection ) {
620 $this->dav_name = $this->collection->dav_name;
621 $this->resource_id = $this->collection->collection_id;
622 $this->_is_calendar = ($this->collection->type == 'calendar');
623 $this->_is_addressbook = ($this->collection->type == 'addressbook');
624 $this->contenttype = 'httpd/unix-directory';
625 if ( !isset($this->exists) && isset($this->collection->exists) ) {
626 // If this seems peculiar it's because we only set it to false above...
627 $this->exists = $this->collection->exists;
628 }
629 if ( $this->exists ) {
630 if ( isset($this->collection->dav_etag) ) $this->unique_tag = '"'.$this->collection->dav_etag.'"';
631 if ( isset($this->collection->created) ) $this->created = $this->collection->created;
632 if ( isset($this->collection->modified) ) $this->modified = $this->collection->modified;
633 if ( isset($this->collection->dav_displayname) ) $this->collection->displayname = $this->collection->dav_displayname;
634 }
635 else {
636 if ( !isset($this->parent) ) $this->GetParentContainer();
637 $this->user_no = $this->parent->GetProperty('user_no');
638 }
639 if ( isset($this->collection->resourcetypes) )
640 $this->resourcetypes = $this->collection->resourcetypes;
641 else {
642 $this->resourcetypes = '<DAV::collection/>';
643 if ( $this->_is_principal ) $this->resourcetypes .= '<DAV::principal/>';
644 if ( $this->_is_addressbook ) $this->resourcetypes .= '<urn:ietf:params:xml:ns:carddav:addressbook/>';
645 if ( $this->_is_calendar ) $this->resourcetypes .= '<urn:ietf:params:xml:ns:caldav:calendar/>';
646 }
647 }
648 }
649
650
654 protected function FetchPrincipal() {
655 if ( isset($this->principal) ) return;
656 $this->principal = new DAVPrincipal( array( "path" => $this->bound_from() ) );
657 if ( $this->_is_principal ) {
658 $this->exists = $this->principal->Exists();
659 $this->collection->dav_name = $this->dav_name();
660 $this->collection->type = 'principal';
661 if ( $this->exists ) {
662 $this->collection = $this->principal->AsCollection();
663 $this->displayname = $this->principal->GetProperty('displayname');
664 $this->user_no = $this->principal->user_no();
665 $this->resource_id = $this->principal->principal_id();
666 $this->created = $this->principal->created;
667 $this->modified = $this->principal->modified;
668 $this->resourcetypes = $this->principal->resourcetypes;
669 }
670 }
671 }
672
673
677 protected function FetchResource() {
678 if ( isset($this->exists) ) return; // True or false, we've got what we can already
679 if ( $this->_is_collection ) return; // We have all we're going to read
680
681 $sql = <<<EOQRY
682SELECT calendar_item.*, addressbook_resource.*, caldav_data.*
683 FROM caldav_data LEFT OUTER JOIN calendar_item USING (collection_id,dav_id)
684 LEFT OUTER JOIN addressbook_resource USING (dav_id)
685 WHERE caldav_data.dav_name = :dav_name
686EOQRY;
687 $params = array( ':dav_name' => $this->bound_from() );
688
689 $qry = new AwlQuery( $sql, $params );
690 if ( $qry->Exec('DAVResource') && $qry->rows() > 0 ) {
691 $this->exists = true;
692 $row = $qry->Fetch();
693 $this->FromRow($row);
694 }
695 else {
696 $this->exists = false;
697 }
698 }
699
700
704 protected function FetchDeadProperties() {
705 if ( isset($this->dead_properties) ) return;
706
707 $this->dead_properties = array();
708 if ( !$this->exists || !$this->_is_collection ) return;
709
710 $qry = new AwlQuery('SELECT property_name, property_value FROM property WHERE dav_name= :dav_name', array(':dav_name' => $this->dav_name) );
711 if ( $qry->Exec('DAVResource') ) {
712 while ( $property = $qry->Fetch() ) {
713 $this->dead_properties[$property->property_name] = self::BuildDeadPropertyXML($property->property_name,$property->property_value);
714 }
715 }
716 }
717
724 public static function BuildDeadPropertyXML($property_name, $raw_string) {
725 if ( !preg_match('{^\s*<.*>\s*$}s', $raw_string) ) return $raw_string;
726 $xmlns = null;
727 if ( preg_match( '{^(.*):([^:]+)$}', $property_name, $matches) ) {
728 $xmlns = $matches[1];
729 $property_name = $matches[2];
730 }
731 $xml = sprintf('<%s%s>%s</%s>', $property_name, (isset($xmlns)?' xmlns="'.$xmlns.'"':''), $raw_string, $property_name);
732 $xml_parser = xml_parser_create_ns('UTF-8');
733 $xml_tags = array();
734 xml_parser_set_option ( $xml_parser, XML_OPTION_SKIP_WHITE, 1 );
735 xml_parser_set_option ( $xml_parser, XML_OPTION_CASE_FOLDING, 0 );
736 $rc = xml_parse_into_struct( $xml_parser, $xml, $xml_tags );
737 if ( $rc == false ) {
738 $errno = xml_get_error_code($xml_parser);
739 dbg_error_log( 'ERROR', 'XML parsing error: %s (%d) at line %d, column %d',
740 xml_error_string($errno), $errno,
741 xml_get_current_line_number($xml_parser), xml_get_current_column_number($xml_parser) );
742 dbg_error_log( 'ERROR', "Error occurred in:\n%s\n",$xml);
743 if ($errno >= 200 && $errno < 300 && count($xml_tags) >= 3) {
744 // XML namespace error, but parsing was probably fine: continue and return tags (cf. #9)
745 dbg_error_log( 'ERROR', 'XML namespace error but tags extracted, trying to continue');
746 } else {
747 return $raw_string;
748 }
749 }
750 xml_parser_free($xml_parser);
751 $xmltree = BuildXMLTree( $xml_tags );
752 return $xmltree->GetContent();
753 }
754
758 protected function FetchPrivileges() {
759 global $session, $request;
760
761 if ( $this->dav_name == '/' || $this->dav_name == '' || $this->_is_external ) {
762 $this->privileges = (1 | 16 | 32); // read + read-acl + read-current-user-privilege-set
763 dbg_error_log( 'DAVResource', ':FetchPrivileges: Read permissions for user accessing /' );
764 return;
765 }
766
767 if ( $session->AllowedTo('Admin') ) {
768 $this->privileges = privilege_to_bits('all');
769 dbg_error_log( 'DAVResource', ':FetchPrivileges: Full permissions for an administrator.' );
770 return;
771 }
772
773 if ( $this->IsPrincipal() ) {
774 if ( !isset($this->principal) ) $this->FetchPrincipal();
775 $this->privileges = $this->principal->Privileges();
776 dbg_error_log( 'DAVResource', ':FetchPrivileges: Privileges of "%s" for user accessing principal "%s"', $this->privileges, $this->principal->username() );
777 return;
778 }
779
780 if ( ! isset($this->collection) ) $this->FetchCollection();
781 $this->privileges = 0;
782 if ( !isset($this->collection->path_privs) ) {
783 if ( !isset($this->parent) ) $this->GetParentContainer();
784
785 $this->collection->path_privs = $this->parent->Privileges();
786 $this->collection->user_no = $this->parent->GetProperty('user_no');
787 $this->collection->principal_id = $this->parent->GetProperty('principal_id');
788 }
789
790 $this->privileges = $this->collection->path_privs;
791 if ( is_string($this->privileges) ) $this->privileges = bindec( $this->privileges );
792
793 dbg_error_log( 'DAVResource', ':FetchPrivileges: Privileges of "%s" for user "%s" accessing "%s"',
794 decbin($this->privileges), $session->username, $this->dav_name() );
795
796 if ( isset($request->ticket) && $request->ticket->MatchesPath($this->bound_from()) ) {
797 $this->privileges |= $request->ticket->privileges();
798 dbg_error_log( 'DAVResource', ':FetchPrivileges: Applying permissions for ticket "%s" now: %s', $request->ticket->id(), decbin($this->privileges) );
799 }
800
801 if ( isset($this->tickets) ) {
802 if ( !isset($this->resource_id) ) $this->FetchResource();
803 foreach( $this->tickets AS $k => $ticket ) {
804 if ( $ticket->MatchesResource($this->resource_id()) || $ticket->MatchesPath($this->bound_from()) ) {
805 $this->privileges |= $ticket->privileges();
806 dbg_error_log( 'DAVResource', ':FetchPrivileges: Applying permissions for ticket "%s" now: %s', $ticket->id(), decbin($this->privileges) );
807 }
808 }
809 }
810 }
811
812
817 if ( $this->dav_name == '/' ) return null;
818 if ( !isset($this->parent) ) {
819 if ( $this->_is_collection ) {
820 dbg_error_log( 'DAVResource', 'Retrieving "%s" - parent of "%s" (dav_name: %s)', $this->parent_path(), $this->collection->dav_name, $this->dav_name() );
821 $this->parent = new DAVResource( $this->parent_path() );
822 }
823 else {
824 dbg_error_log( 'DAVResource', 'Retrieving "%s" - parent of "%s" (dav_name: %s)', $this->parent_path(), $this->collection->dav_name, $this->dav_name() );
825 $this->parent = new DAVResource($this->collection->dav_name);
826 }
827 }
828 return $this->parent;
829 }
830
831
837 deprecated('DAVResource::FetchParentContainer');
838 return $this->GetParentContainer();
839 }
840
841
845 function Privileges() {
846 if ( !isset($this->privileges) ) $this->FetchPrivileges();
847 return $this->privileges;
848 }
849
850
857 function HavePrivilegeTo( $do_what, $any = null ) {
858 if ( !isset($this->privileges) ) $this->FetchPrivileges();
859 if ( !isset($any) ) $any = ($do_what != 'all');
860 $test_bits = privilege_to_bits( $do_what );
861 dbg_error_log( 'DAVResource', 'Testing %s privileges of "%s" (%s) against allowed "%s" => "%s" (%s)', ($any?'any':'exactly'),
862 $do_what, decbin($test_bits), decbin($this->privileges), ($this->privileges & $test_bits), decbin($this->privileges & $test_bits) );
863 if ( $any ) {
864 return ($this->privileges & $test_bits) > 0;
865 }
866 else {
867 return ($this->privileges & $test_bits) == $test_bits;
868 }
869 }
870
871
879 function NeedPrivilege( $privilege, $any = null ) {
880 global $request;
881
882 // Do the test
883 if ( $this->HavePrivilegeTo($privilege, $any) ) return;
884
885 // They failed, so output the error
886 $request->NeedPrivilege( $privilege, $this->dav_name );
887 exit(0); // Unecessary, but might clarify things
888 }
889
890
894 function BuildPrivileges( $privilege_names=null, &$xmldoc=null ) {
895 if ( $privilege_names == null ) {
896 if ( !isset($this->privileges) ) $this->FetchPrivileges();
897 $privilege_names = bits_to_privilege($this->privileges, ($this->_is_collection ? $this->collection->type : null ) );
898 }
899 return privileges_to_XML( $privilege_names, $xmldoc);
900 }
901
902
907 if ( isset($this->supported_methods) ) return $this->supported_methods;
908
909 $this->supported_methods = array(
910 'OPTIONS' => '',
911 'PROPFIND' => '',
912 'REPORT' => '',
913 'DELETE' => '',
914 'LOCK' => '',
915 'UNLOCK' => '',
916 'MOVE' => ''
917 );
918 if ( $this->IsCollection() ) {
919/* if ( $this->IsPrincipal() ) {
920 $this->supported_methods['MKCALENDAR'] = '';
921 $this->supported_methods['MKCOL'] = '';
922 } */
923 switch ( $this->collection->type ) {
924 case 'root':
925 case 'email':
926 // We just override the list completely here.
927 $this->supported_methods = array(
928 'OPTIONS' => '',
929 'PROPFIND' => '',
930 'REPORT' => ''
931 );
932 break;
933
934 case 'schedule-outbox':
935 $this->supported_methods = array_merge(
936 $this->supported_methods,
937 array(
938 'POST' => '', 'PROPPATCH' => '', 'MKTICKET' => '', 'DELTICKET' => ''
939 )
940 );
941 break;
942 case 'schedule-inbox':
943 case 'calendar':
944 $this->supported_methods['GET'] = '';
945 $this->supported_methods['PUT'] = '';
946 $this->supported_methods['HEAD'] = '';
947 $this->supported_methods['MKTICKET'] = '';
948 $this->supported_methods['DELTICKET'] = '';
949 $this->supported_methods['ACL'] = '';
950 break;
951 case 'collection':
952 $this->supported_methods['MKTICKET'] = '';
953 $this->supported_methods['DELTICKET'] = '';
954 $this->supported_methods['BIND'] = '';
955 $this->supported_methods['ACL'] = '';
956 case 'principal':
957 $this->supported_methods['GET'] = '';
958 $this->supported_methods['HEAD'] = '';
959 $this->supported_methods['MKCOL'] = '';
960 $this->supported_methods['MKCALENDAR'] = '';
961 $this->supported_methods['PROPPATCH'] = '';
962 $this->supported_methods['BIND'] = '';
963 $this->supported_methods['ACL'] = '';
964 break;
965 }
966 }
967 else {
968 $this->supported_methods = array_merge(
969 $this->supported_methods,
970 array(
971 'GET' => '', 'HEAD' => '', 'PUT' => '', 'MKTICKET' => '', 'DELTICKET' => ''
972 )
973 );
974 }
975
976 return $this->supported_methods;
977 }
978
979
984 if ( !isset($this->supported_methods) ) $this->FetchSupportedMethods();
985 $methods = array();
986 foreach( $this->supported_methods AS $k => $v ) {
987// dbg_error_log( 'DAVResource', ':BuildSupportedMethods: Adding method "%s" which is "%s".', $k, $v );
988 $methods[] = new XMLElement( 'supported-method', null, array('name' => $k) );
989 }
990 return $methods;
991 }
992
993
998 if ( isset($this->supported_reports) ) return $this->supported_reports;
999
1000 $this->supported_reports = array(
1001 'DAV::principal-property-search' => '',
1002 'DAV::principal-search-property-set' => '',
1003 'DAV::expand-property' => '',
1004 'DAV::principal-match' => '',
1005 'DAV::sync-collection' => ''
1006 );
1007
1008 if ( !isset($this->collection) ) $this->FetchCollection();
1009
1010 if ( $this->collection->is_calendar ) {
1011 $this->supported_reports = array_merge(
1012 $this->supported_reports,
1013 array(
1014 'urn:ietf:params:xml:ns:caldav:calendar-query' => '',
1015 'urn:ietf:params:xml:ns:caldav:calendar-multiget' => '',
1016 'urn:ietf:params:xml:ns:caldav:free-busy-query' => ''
1017 )
1018 );
1019 }
1020 if ( $this->collection->is_addressbook ) {
1021 $this->supported_reports = array_merge(
1022 $this->supported_reports,
1023 array(
1024 'urn:ietf:params:xml:ns:carddav:addressbook-query' => '',
1025 'urn:ietf:params:xml:ns:carddav:addressbook-multiget' => ''
1026 )
1027 );
1028 }
1029 return $this->supported_reports;
1030 }
1031
1032
1036 function BuildSupportedReports( &$reply ) {
1037 if ( !isset($this->supported_reports) ) $this->FetchSupportedReports();
1038 $reports = array();
1039 foreach( $this->supported_reports AS $k => $v ) {
1040 dbg_error_log( 'DAVResource', ':BuildSupportedReports: Adding supported report "%s" which is "%s".', $k, $v );
1041 $report = new XMLElement('report');
1042 $reply->NSElement($report, $k );
1043 $reports[] = new XMLElement('supported-report', $report );
1044 }
1045 return $reports;
1046 }
1047
1048
1052 function FetchTickets( ) {
1053 global $c;
1054 if ( isset($this->access_tickets) ) return;
1055 $this->access_tickets = array();
1056
1057 $sql =
1058'SELECT access_ticket.*, COALESCE( resource.dav_name, collection.dav_name) AS target_dav_name,
1059 (access_ticket.expires < current_timestamp) AS expired,
1060 dav_principal.dav_name AS principal_dav_name,
1061 EXTRACT( \'epoch\' FROM (access_ticket.expires - current_timestamp)) AS seconds,
1062 path_privs(access_ticket.dav_owner_id,collection.dav_name,:scan_depth) AS grantor_collection_privileges
1063 FROM access_ticket JOIN collection ON (target_collection_id = collection_id)
1064 JOIN dav_principal ON (dav_owner_id = principal_id)
1065 LEFT JOIN caldav_data resource ON (resource.dav_id = access_ticket.target_resource_id)
1066 WHERE target_collection_id = :collection_id ';
1067 $params = array(':collection_id' => $this->collection->collection_id, ':scan_depth' => $c->permission_scan_depth);
1068 if ( $this->IsCollection() ) {
1069 $sql .= 'AND target_resource_id IS NULL';
1070 }
1071 else {
1072 if ( !isset($this->exists) ) $this->FetchResource();
1073 $sql .= 'AND target_resource_id = :dav_id';
1074 $params[':dav_id'] = $this->resource->dav_id;
1075 }
1076 if ( isset($this->exists) && !$this->exists ) return;
1077
1078 $qry = new AwlQuery( $sql, $params );
1079 if ( $qry->Exec('DAVResource',__LINE__,__FILE__) && $qry->rows() ) {
1080 while( $ticket = $qry->Fetch() ) {
1081 $this->access_tickets[] = $ticket;
1082 }
1083 }
1084 }
1085
1086
1097 function BuildTicketinfo( &$reply ) {
1098 global $session, $request;
1099
1100 if ( !isset($this->access_tickets) ) $this->FetchTickets();
1101 $tickets = array();
1102 $show_all = $this->HavePrivilegeTo('DAV::read-acl');
1103 foreach( $this->access_tickets AS $meh => $trow ) {
1104 if ( !$show_all && ( $trow->dav_owner_id == $session->principal_id || $request->ticket->id() == $trow->ticket_id ) ) continue;
1105 dbg_error_log( 'DAVResource', ':BuildTicketinfo: Adding access_ticket "%s" which is "%s".', $trow->ticket_id, $trow->privileges );
1106 $ticket = new XMLElement( $reply->Tag( 'ticketinfo', 'http://www.xythos.com/namespaces/StorageServer', 'TKT' ) );
1107 $reply->NSElement($ticket, 'http://www.xythos.com/namespaces/StorageServer:id', $trow->ticket_id );
1108 $reply->NSElement($ticket, 'http://www.xythos.com/namespaces/StorageServer:owner', $reply->href( ConstructURL($trow->principal_dav_name)) );
1109 $reply->NSElement($ticket, 'http://www.xythos.com/namespaces/StorageServer:timeout', (isset($trow->seconds) ? sprintf( 'Seconds-%d', $trow->seconds) : 'infinity') );
1110 $reply->NSElement($ticket, 'http://www.xythos.com/namespaces/StorageServer:visits', 'infinity' );
1111 $privs = array();
1112 foreach( bits_to_privilege(bindec($trow->privileges) & bindec($trow->grantor_collection_privileges) ) AS $k => $v ) {
1113 $privs[] = $reply->NewXMLElement($v);
1114 }
1115 $reply->NSElement($ticket, 'DAV::privilege', $privs );
1116 $tickets[] = $ticket;
1117 }
1118 return $tickets;
1119 }
1120
1121
1129 function IsLocked( $depth = 0 ) {
1130 if ( !isset($this->_locks_found) ) {
1131 $this->_locks_found = array();
1135 $sql = 'SELECT * FROM locks WHERE :this_path::text ~ (\'^\'||dav_name||:match_end)::text';
1136 $qry = new AwlQuery($sql, array( ':this_path' => $this->dav_name, ':match_end' => ($depth == DEPTH_INFINITY ? '' : '$') ) );
1137 if ( $qry->Exec('DAVResource',__LINE__,__FILE__) ) {
1138 while( $lock_row = $qry->Fetch() ) {
1139 $this->_locks_found[$lock_row->opaquelocktoken] = $lock_row;
1140 }
1141 }
1142 else {
1143 $this->DoResponse(500,i18n("Database Error"));
1144 // Does not return.
1145 }
1146 }
1147
1148 foreach( $this->_locks_found AS $lock_token => $lock_row ) {
1149 if ( $lock_row->depth == DEPTH_INFINITY || $lock_row->dav_name == $this->dav_name ) {
1150 return $lock_token;
1151 }
1152 }
1153
1154 return false; // Nothing matched
1155 }
1156
1157
1161 function IsCollection() {
1162 return $this->_is_collection;
1163 }
1164
1165
1169 function IsPrincipal() {
1170 return $this->_is_collection && $this->_is_principal;
1171 }
1172
1173
1177 function IsCalendar() {
1178 return $this->_is_collection && $this->_is_calendar;
1179 }
1180
1181
1186 function IsProxyCollection( $type = 'any' ) {
1187 if ( $this->_is_proxy_resource ) {
1188 return ($type == 'any' || $type == $this->proxy_type);
1189 }
1190 return false;
1191 }
1192
1193
1198 function IsSchedulingCollection( $type = 'any' ) {
1199 if ( $this->_is_collection && preg_match( '{schedule-(inbox|outbox)}', $this->collection->type, $matches ) ) {
1200 return ($type == 'any' || $type == $matches[1]);
1201 }
1202 return false;
1203 }
1204
1205
1210 function IsInSchedulingCollection( $type = 'any' ) {
1211 if ( !$this->_is_collection && preg_match( '{schedule-(inbox|outbox)}', $this->collection->type, $matches ) ) {
1212 return ($type == 'any' || $type == $matches[1]);
1213 }
1214 return false;
1215 }
1216
1217
1221 function IsAddressbook() {
1222 return $this->_is_collection && $this->_is_addressbook;
1223 }
1224
1225
1229 function IsBinding() {
1230 return $this->_is_binding;
1231 }
1232
1233
1237 function IsExternal() {
1238 return $this->_is_external;
1239 }
1240
1241
1245 function Exists() {
1246 if ( ! isset($this->exists) ) {
1247 if ( $this->IsPrincipal() ) {
1248 if ( !isset($this->principal) ) $this->FetchPrincipal();
1249 $this->exists = $this->principal->Exists();
1250 }
1251 else if ( ! $this->IsCollection() ) {
1252 if ( !isset($this->resource) ) $this->FetchResource();
1253 }
1254 }
1255// dbg_error_log('DAVResource',' Checking whether "%s" exists. It would appear %s.', $this->dav_name, ($this->exists ? 'so' : 'not') );
1256 return $this->exists;
1257 }
1258
1259
1263 function ContainerExists() {
1264 if ( $this->collection->dav_name != $this->dav_name ) {
1265 return $this->collection->exists;
1266 }
1267 $parent = $this->GetParentContainer();
1268 return $parent->Exists();
1269 }
1270
1271
1276 function url() {
1277 if ( !isset($this->dav_name) ) {
1278 throw Exception("What! How can dav_name not be set?");
1279 }
1280 return ConstructURL($this->dav_name);
1281 }
1282
1283
1288 function dav_name() {
1289 if ( isset($this->dav_name) ) return $this->dav_name;
1290 return null;
1291 }
1292
1293
1298 function bound_from() {
1299 if ( isset($this->bound_from) ) return $this->bound_from;
1300 return $this->dav_name();
1301 }
1302
1303
1307 function set_bind_location( $new_dav_name ) {
1308 if ( !isset($this->bound_from) && isset($this->dav_name) ) {
1309 $this->bound_from = $this->dav_name;
1310 }
1311 $this->dav_name = $new_dav_name;
1312 return $this->dav_name;
1313 }
1314
1315
1319 function parent_path() {
1320 if ( $this->IsCollection() ) {
1321 if ( !isset($this->collection) ) $this->FetchCollection();
1322 if ( !isset($this->collection->parent_container) ) {
1323 $this->collection->parent_container = preg_replace( '{[^/]+/$}', '', $this->bound_from());
1324 }
1325 return $this->collection->parent_container;
1326 }
1327 return preg_replace( '{[^/]+$}', '', $this->bound_from());
1328 }
1329
1330
1331
1335 function principal_url() {
1336 if ( !isset($this->principal) ) $this->FetchPrincipal();
1337 return $this->principal->url();
1338 }
1339
1340
1344 function user_no() {
1345 if ( !isset($this->principal) ) $this->FetchPrincipal();
1346 return $this->principal->user_no();
1347 }
1348
1349
1353 function collection_id() {
1354 if ( !isset($this->collection) ) $this->FetchCollection();
1355 return $this->collection->collection_id;
1356 }
1357
1358
1362 function timezone_name() {
1363 if ( !isset($this->collection) ) $this->FetchCollection();
1364 return $this->collection->timezone;
1365 }
1366
1367
1371 function resource() {
1372 if ( !isset($this->resource) ) $this->FetchResource();
1373 return $this->resource;
1374 }
1375
1376
1380 function unique_tag() {
1381 if ( isset($this->unique_tag) ) return $this->unique_tag;
1382 if ( $this->IsPrincipal() && !isset($this->principal) ) {
1383 $this->FetchPrincipal();
1384 $this->unique_tag = $this->principal->unique_tag();
1385 }
1386 else if ( !$this->_is_collection && !isset($this->resource) ) $this->FetchResource();
1387
1388 if ( $this->exists !== true || !isset($this->unique_tag) ) $this->unique_tag = '';
1389
1390 return $this->unique_tag;
1391 }
1392
1393
1397 function resource_id() {
1398 if ( isset($this->resource_id) ) return $this->resource_id;
1399 if ( $this->IsPrincipal() && !isset($this->principal) ) $this->FetchPrincipal();
1400 else if ( !$this->_is_collection && !isset($this->resource) ) $this->FetchResource();
1401
1402 if ( $this->exists !== true || !isset($this->resource_id) ) $this->resource_id = null;
1403
1404 return $this->resource_id;
1405 }
1406
1407
1411 function sync_token( $cachedOK = true ) {
1412 dbg_error_log('DAVResource', 'Request for a%scached sync-token', ($cachedOK ? ' ' : 'n un') );
1413 if ( $this->IsPrincipal() ) return null;
1414 if ( $this->collection_id() == 0 ) return null;
1415 if ( !isset($this->sync_token) || !$cachedOK ) {
1416 $sql = 'SELECT new_sync_token( 0, :collection_id) AS sync_token';
1417 $params = array( ':collection_id' => $this->collection_id());
1418 $qry = new AwlQuery($sql, $params );
1419 if ( !$qry->Exec() || !$row = $qry->Fetch() ) {
1420 if ( !$qry->QDo('SELECT new_sync_token( 0, :collection_id) AS sync_token', $params) ) throw new Exception('Problem with database query');
1421 $row = $qry->Fetch();
1422 }
1423 $this->sync_token = 'data:,'.$row->sync_token;
1424 }
1425 dbg_error_log('DAVResource', 'Returning sync token of "%s"', $this->sync_token );
1426 return $this->sync_token;
1427 }
1428
1432 function IsPublic() {
1433 return ( isset($this->collection->publicly_readable) && $this->collection->publicly_readable == 't' );
1434 }
1435
1436
1440 function IsPublicOnly() {
1441 return ( isset($this->collection->publicly_events_only) && $this->collection->publicly_events_only == 't' );
1442 }
1443
1444
1448 function ContainerType() {
1449 if ( $this->IsPrincipal() ) return 'root';
1450 if ( !$this->IsCollection() ) return $this->collection->type;
1451
1452 if ( ! isset($this->collection->parent_container) ) return null;
1453
1454 if ( isset($this->parent_container_type) ) return $this->parent_container_type;
1455
1456 if ( preg_match('#/[^/]+/#', $this->collection->parent_container) ) {
1457 $this->parent_container_type = 'principal';
1458 }
1459 else {
1460 $qry = new AwlQuery('SELECT * FROM collection WHERE dav_name = :parent_name',
1461 array( ':parent_name' => $this->collection->parent_container ) );
1462 if ( $qry->Exec('DAVResource') && $qry->rows() > 0 && $parent = $qry->Fetch() ) {
1463 if ( $parent->is_calendar == 't' )
1464 $this->parent_container_type = 'calendar';
1465 else if ( $parent->is_addressbook == 't' )
1466 $this->parent_container_type = 'addressbook';
1467 else if ( preg_match( '#^((/[^/]+/)\.(in|out)/)[^/]*$#', $this->dav_name, $matches ) )
1468 $this->parent_container_type = 'schedule-'. $matches[3]. 'box';
1469 else
1470 $this->parent_container_type = 'collection';
1471 }
1472 else
1473 $this->parent_container_type = null;
1474 }
1475 return $this->parent_container_type;
1476 }
1477
1478
1482 function BuildACE( &$xmldoc, $privs, $principal ) {
1483 $privilege_names = bits_to_privilege($privs, ($this->_is_collection ? $this->collection->type : 'resource'));
1484 $privileges = array();
1485 foreach( $privilege_names AS $k ) {
1486 $privilege = new XMLElement('privilege');
1487 if ( isset($xmldoc) )
1488 $xmldoc->NSElement($privilege,$k);
1489 else
1490 $privilege->NewElement($k);
1491 $privileges[] = $privilege;
1492 }
1493 $ace = new XMLElement('ace', array(
1494 new XMLElement('principal', $principal),
1495 new XMLElement('grant', $privileges ) )
1496 );
1497 return $ace;
1498 }
1499
1503 function GetACL( &$xmldoc ) {
1504 if ( !isset($this->principal) ) $this->FetchPrincipal();
1505 $default_privs = $this->principal->default_privileges;
1506 if ( isset($this->collection->default_privileges) ) $default_privs = $this->collection->default_privileges;
1507
1508 $acl = array();
1509 $acl[] = $this->BuildACE($xmldoc, pow(2,25) - 1, new XMLElement('property', new XMLElement('owner')) );
1510
1511 $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',
1512 array( ':collection_id' => $this->collection->collection_id,
1513 ':principal_id' => $this->principal->principal_id() ) );
1514 if ( $qry->Exec('DAVResource') && $qry->rows() > 0 ) {
1515 $by_collection = null;
1516 while( $grant = $qry->Fetch() ) {
1517 if ( !isset($by_collection) ) $by_collection = isset($grant->by_collection);
1518 if ( $by_collection && !isset($grant->by_collection) ) break;
1519 $acl[] = $this->BuildACE($xmldoc, $grant->privileges, $xmldoc->href(ConstructURL($grant->dav_name)) );
1520 }
1521 }
1522
1523 $acl[] = $this->BuildACE($xmldoc, $default_privs, new XMLElement('authenticated') );
1524
1525 return $acl;
1526
1527 }
1528
1529
1533 function GetProperty( $name ) {
1534// dbg_error_log( 'DAVResource', ':GetProperty: Fetching "%s".', $name );
1535 $value = null;
1536
1537 switch( $name ) {
1538 case 'collection_id':
1539 return $this->collection_id();
1540 break;
1541
1542 case 'principal_id':
1543 if ( !isset($this->principal) ) $this->FetchPrincipal();
1544 return $this->principal->principal_id();
1545 break;
1546
1547 case 'resourcetype':
1548 if ( isset($this->resourcetypes) ) {
1549 $this->resourcetypes = preg_replace('{^\s*<(.*)/>\s*$}', '$1', $this->resourcetypes);
1550 $type_list = preg_split('{(/>\s*<|\n)}', $this->resourcetypes);
1551 foreach( $type_list AS $k => $resourcetype ) {
1552 if ( preg_match( '{^([^:]+):([^:]+) \s+ xmlns:([^=]+)="([^"]+)" \s* $}x', $resourcetype, $matches ) ) {
1553 $type_list[$k] = $matches[4] .':' .$matches[2];
1554 }
1555 else if ( preg_match( '{^([^:]+) \s+ xmlns="([^"]+)" \s* $}x', $resourcetype, $matches ) ) {
1556 $type_list[$k] = $matches[2] .':' .$matches[1];
1557 }
1558 }
1559 return $type_list;
1560 }
1561
1562 case 'resource':
1563 if ( !isset($this->resource) ) $this->FetchResource();
1564 return clone($this->resource);
1565 break;
1566
1567 case 'dav-data':
1568 if ( !isset($this->resource) ) $this->FetchResource();
1569 dbg_error_log( 'DAVResource', ':GetProperty: dav-data: fetched resource does%s exist.', ($this->exists?'':' not') );
1570 return $this->resource->caldav_data;
1571 break;
1572
1573 case 'principal':
1574 if ( !isset($this->principal) ) $this->FetchPrincipal();
1575 return clone($this->principal);
1576 break;
1577
1578 default:
1579 if ( isset($this->{$name}) ) {
1580 if ( ! is_object($this->{$name}) ) return $this->{$name};
1581 return clone($this->{$name});
1582 }
1583 if ( $this->_is_principal ) {
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 else if ( $this->_is_collection ) {
1589 if ( isset($this->collection->{$name}) ) return $this->collection->{$name};
1590 if ( isset($this->principal->{$name}) ) return $this->principal->{$name};
1591 }
1592 else {
1593 if ( !isset($this->resource) ) $this->FetchResource();
1594 if ( isset($this->resource->{$name}) ) return $this->resource->{$name};
1595 if ( !isset($this->principal) ) $this->FetchPrincipal();
1596 if ( isset($this->principal->{$name}) ) return $this->principal->{$name};
1597 if ( isset($this->collection->{$name}) ) return $this->collection->{$name};
1598 }
1599 if ( isset($this->{$name}) ) {
1600 if ( ! is_object($this->{$name}) ) return $this->{$name};
1601 return clone($this->{$name});
1602 }
1603 // dbg_error_log( 'DAVResource', ':GetProperty: Failed to find property "%s" on "%s".', $name, $this->dav_name );
1604 }
1605
1606 return $value;
1607 }
1608
1609
1613 function DAV_AllProperties() {
1614 if ( !isset($this->dead_properties) ) $this->FetchDeadProperties();
1615 $allprop = array_merge( (isset($this->dead_properties)?array_keys($this->dead_properties):array()),
1616 (isset($include_properties)?$include_properties:array()),
1617 array(
1618 'DAV::getcontenttype', 'DAV::resourcetype', 'DAV::getcontentlength', 'DAV::displayname', 'DAV::getlastmodified',
1619 'DAV::creationdate', 'DAV::getetag', 'DAV::getcontentlanguage', 'DAV::supportedlock', 'DAV::lockdiscovery',
1620 'DAV::owner', 'DAV::principal-URL', 'DAV::current-user-principal',
1621 'urn:ietf:params:xml:ns:carddav:max-resource-size', 'urn:ietf:params:xml:ns:carddav:supported-address-data',
1622 'urn:ietf:params:xml:ns:carddav:addressbook-description', 'urn:ietf:params:xml:ns:carddav:addressbook-home-set'
1623 ) );
1624
1625 return $allprop;
1626 }
1627
1628
1632 function ResourceProperty( $tag, $prop, &$reply, &$denied ) {
1633 global $c, $session, $request;
1634
1635// dbg_error_log( 'DAVResource', 'Processing "%s" on "%s".', $tag, $this->dav_name );
1636
1637 if ( $reply === null ) $reply = $GLOBALS['reply'];
1638
1639 switch( $tag ) {
1640 case 'DAV::allprop':
1641 $property_list = $this->DAV_AllProperties();
1642 $discarded = array();
1643 foreach( $property_list AS $k => $v ) {
1644 $this->ResourceProperty($v, $prop, $reply, $discarded);
1645 }
1646 break;
1647
1648 case 'DAV::href':
1649 $prop->NewElement('href', ConstructURL($this->dav_name) );
1650 break;
1651
1652 case 'DAV::resource-id':
1653 if ( $this->resource_id > 0 )
1654 $reply->DAVElement( $prop, 'resource-id', $reply->href(ConstructURL('/.resources/'.$this->resource_id) ) );
1655 else
1656 return false;
1657 break;
1658
1659 case 'DAV::parent-set':
1660 $sql = <<<EOQRY
1661SELECT b.parent_container FROM dav_binding b JOIN collection c ON (b.bound_source_id=c.collection_id)
1662 WHERE regexp_replace( b.dav_name, '^.*/', c.dav_name ) = :bound_from
1663EOQRY;
1664 $qry = new AwlQuery($sql, array( ':bound_from' => $this->bound_from() ) );
1665 $parents = array();
1666 if ( $qry->Exec('DAVResource',__LINE__,__FILE__) && $qry->rows() > 0 ) {
1667 while( $row = $qry->Fetch() ) {
1668 $parents[$row->parent_container] = true;
1669 }
1670 }
1671 $parents[preg_replace( '{(?<=/)[^/]+/?$}','',$this->bound_from())] = true;
1672 $parents[preg_replace( '{(?<=/)[^/]+/?$}','',$this->dav_name())] = true;
1673
1674 $parent_set = $reply->DAVElement( $prop, 'parent-set' );
1675 foreach( $parents AS $parent => $v ) {
1676 if ( preg_match( '{^(.*)?/([^/]+)/?$}', $parent, $matches ) ) {
1677 $reply->DAVElement($parent_set, 'parent', array(
1678 new XMLElement( 'href', ConstructURL($matches[1])),
1679 new XMLElement( 'segment', $matches[2])
1680 ));
1681 }
1682 else if ( $parent == '/' ) {
1683 $reply->DAVElement($parent_set, 'parent', array(
1684 new XMLElement( 'href', '/'),
1685 new XMLElement( 'segment', ( ConstructURL('/') == '/caldav.php/' ? 'caldav.php' : ''))
1686 ));
1687 }
1688 }
1689 break;
1690
1691 case 'DAV::getcontenttype':
1692 if ( !isset($this->contenttype) && !$this->_is_collection && !isset($this->resource) ) $this->FetchResource();
1693 $prop->NewElement('getcontenttype', $this->contenttype );
1694 break;
1695
1696 case 'DAV::resourcetype':
1697 $resourcetypes = $prop->NewElement('resourcetype' );
1698 if ( $this->_is_collection ) {
1699 $type_list = $this->GetProperty('resourcetype');
1700 if ( !is_array($type_list) ) return true;
1701 // dbg_error_log( 'DAVResource', ':ResourceProperty: "%s" are "%s".', $tag, implode(', ',$type_list) );
1702 foreach( $type_list AS $k => $v ) {
1703 if ( $v == '' ) continue;
1704 $reply->NSElement( $resourcetypes, $v );
1705 }
1706 if ( $this->_is_binding ) {
1707 $reply->NSElement( $resourcetypes, 'http://xmlns.davical.org/davical:webdav-binding' );
1708 }
1709 }
1710 break;
1711
1712 case 'DAV::getlastmodified':
1714 $reply->NSElement($prop, $tag, ISODateToHTTPDate($this->GetProperty('modified')) );
1715 break;
1716
1717 case 'DAV::creationdate':
1719 $reply->NSElement($prop, $tag, DateToISODate($this->GetProperty('created'), true) );
1720 break;
1721
1722 case 'DAV::getcontentlength':
1723 if ( $this->_is_collection ) return false;
1724 if ( !isset($this->resource) ) $this->FetchResource();
1725 if ( isset($this->resource) ) {
1726 $reply->NSElement($prop, $tag, strlen($this->resource->caldav_data) );
1727 }
1728 break;
1729
1730 case 'DAV::getcontentlanguage':
1731 $locale = (isset($c->current_locale) ? $c->current_locale : '');
1732 if ( isset($this->locale) && $this->locale != '' ) $locale = $this->locale;
1733 $reply->NSElement($prop, $tag, $locale );
1734 break;
1735
1736 case 'DAV::acl-restrictions':
1737 $reply->NSElement($prop, $tag, array( new XMLElement('grant-only'), new XMLElement('no-invert') ) );
1738 break;
1739
1740 case 'DAV::inherited-acl-set':
1741 $inherited_acls = array();
1742 if ( ! $this->_is_collection ) {
1743 $inherited_acls[] = $reply->href(ConstructURL($this->collection->dav_name));
1744 }
1745 $reply->NSElement($prop, $tag, $inherited_acls );
1746 break;
1747
1748 case 'DAV::owner':
1749 // The principal-URL of the owner
1750 if ( $this->IsExternal() ){
1751 $reply->DAVElement( $prop, 'owner', $reply->href( ConstructURL($this->collection->bound_from )) );
1752 }
1753 else {
1754 $reply->DAVElement( $prop, 'owner', $reply->href( ConstructURL(DeconstructURL($this->principal_url())) ) );
1755 }
1756 break;
1757
1758 case 'DAV::add-member':
1759 if ( ! $this->_is_collection ) return false;
1760 if ( $this->_is_principal ) return false;
1761 if ( isset($c->post_add_member) && $c->post_add_member === false ) return false;
1762 $reply->DAVElement( $prop, 'add-member', $reply->href(ConstructURL(DeconstructURL($this->url())).'?add_member') );
1763 break;
1764
1765 // Empty tag responses.
1766 case 'DAV::group':
1767 case 'DAV::alternate-URI-set':
1768 $reply->NSElement($prop, $tag );
1769 break;
1770
1771 case 'DAV::getetag':
1772 if ( $this->_is_collection ) return false;
1773 $reply->NSElement($prop, $tag, $this->unique_tag() );
1774 break;
1775
1776 case 'http://calendarserver.org/ns/:getctag':
1777 if ( ! $this->_is_collection ) return false;
1778 $reply->NSElement($prop, $tag, $this->unique_tag() );
1779 break;
1780
1781 case 'DAV::sync-token':
1782 if ( ! $this->_is_collection ) return false;
1783 $sync_token = $this->sync_token();
1784 if ( empty($sync_token) ) return false;
1785 $reply->NSElement($prop, $tag, $sync_token );
1786 break;
1787
1788 case 'http://calendarserver.org/ns/:calendar-proxy-read-for':
1789 $proxy_type = 'read';
1790 case 'http://calendarserver.org/ns/:calendar-proxy-write-for':
1791 if ( isset($c->disable_caldav_proxy) && $c->disable_caldav_proxy ) return false;
1792 if ( !isset($proxy_type) ) $proxy_type = 'write';
1793 // ProxyFor is an already constructed URL
1794 $this->FetchPrincipal();
1795 $reply->CalendarserverElement($prop, 'calendar-proxy-'.$proxy_type.'-for', $reply->href( $this->principal->ProxyFor($proxy_type) ) );
1796 break;
1797
1798 case 'http://calendarserver.org/ns/:group-member-set':
1799 case 'DAV::group-member-set':
1800 if ( $this->_is_proxy_resource ) {
1801 $this->FetchPrincipal();
1802 if ( $this->proxy_type == 'read' ) {
1803 $reply->DAVElement( $prop, 'group-member-set', $reply->href( $this->principal->ReadProxyGroup() ) );
1804 } else {
1805 $reply->DAVElement( $prop, 'group-member-set', $reply->href( $this->principal->WriteProxyGroup() ) );
1806 }
1807 } else {
1808 return false; // leave this to DAVPrincipal
1809 }
1810 break;
1811
1812 case 'http://calendarserver.org/ns/:group-membership':
1813 case 'DAV::group-membership':
1814 if ( $this->_is_proxy_resource ) {
1815 /* the calendar-proxy-{read,write} pseudo-principal should not be a member of any group */
1816 $reply->NSElement($prop, $tag );
1817 } else {
1818 return false; // leave this to DAVPrincipal
1819 }
1820 break;
1821
1822 case 'DAV::current-user-privilege-set':
1823 if ( $this->HavePrivilegeTo('DAV::read-current-user-privilege-set') ) {
1824 $reply->NSElement($prop, $tag, $this->BuildPrivileges() );
1825 }
1826 else {
1827 $denied[] = $tag;
1828 }
1829 break;
1830
1831 case 'urn:ietf:params:xml:ns:caldav:supported-calendar-data':
1832 if ( ! $this->IsCalendar() && ! $this->IsSchedulingCollection() ) return false;
1833 $reply->NSElement($prop, $tag, 'text/calendar' );
1834 break;
1835
1836 case 'urn:ietf:params:xml:ns:caldav:supported-calendar-component-set':
1837 if ( ! $this->_is_collection ) return false;
1838 if ( $this->IsCalendar() ) {
1839 if ( !isset($this->dead_properties) ) $this->FetchDeadProperties();
1840 if ( isset($this->dead_properties[$tag]) ) {
1841 $set_of_components = $this->dead_properties[$tag];
1842 foreach( $set_of_components AS $k => $v ) {
1843 if ( preg_match('{(VEVENT|VTODO|VJOURNAL|VTIMEZONE|VFREEBUSY|VPOLL|VAVAILABILITY)}', $v, $matches) ) {
1844 $set_of_components[$k] = $matches[1];
1845 }
1846 else {
1847 unset( $set_of_components[$k] );
1848 }
1849 }
1850 }
1851 else if ( isset($c->default_calendar_components) && is_array($c->default_calendar_components) ) {
1852 $set_of_components = $c->default_calendar_components;
1853 }
1854 else {
1855 $set_of_components = array( 'VEVENT', 'VTODO', 'VJOURNAL' );
1856 }
1857 }
1858 else if ( $this->IsSchedulingCollection() )
1859 $set_of_components = array( 'VEVENT', 'VTODO', 'VFREEBUSY' );
1860 else return false;
1861 $components = array();
1862 foreach( $set_of_components AS $v ) {
1863 $components[] = $reply->NewXMLElement( 'comp', '', array('name' => $v), 'urn:ietf:params:xml:ns:caldav');
1864 }
1865 $reply->CalDAVElement($prop, 'supported-calendar-component-set', $components );
1866 break;
1867
1868 case 'DAV::supported-method-set':
1869 $prop->NewElement('supported-method-set', $this->BuildSupportedMethods() );
1870 break;
1871
1872 case 'DAV::supported-report-set':
1873 $prop->NewElement('supported-report-set', $this->BuildSupportedReports( $reply ) );
1874 break;
1875
1876 case 'DAV::supportedlock':
1877 $prop->NewElement('supportedlock',
1878 new XMLElement( 'lockentry',
1879 array(
1880 new XMLElement('lockscope', new XMLElement('exclusive')),
1881 new XMLElement('locktype', new XMLElement('write')),
1882 )
1883 )
1884 );
1885 break;
1886
1887 case 'DAV::supported-privilege-set':
1888 $prop->NewElement('supported-privilege-set', $request->BuildSupportedPrivileges($reply) );
1889 break;
1890
1891 case 'DAV::principal-collection-set':
1892 $prop->NewElement( 'principal-collection-set', $reply->href( ConstructURL('/') ) );
1893 break;
1894
1895 # iPhone devices incorrectly implement DAV:current-user-principal from
1896 # RFC 5397. They assume that current-user-principal is the href for the
1897 # resource being queried. The RFC says it should be the current resource.
1898 # See: https://gitlab.com/davical-project/davical/-/issues/335
1899 case 'DAV::current-user-principal':
1900 if ( preg_match('/iPhone/', $_SERVER['HTTP_USER_AGENT']) )
1901 $prop->NewElement('current-user-principal', $reply->href( ConstructURL(DeconstructURL($request->principal->url())) ) );
1902 else
1903 $prop->NewElement('current-user-principal', $reply->href( ConstructURL(DeconstructURL($session->principal->url())) ) );
1904 break;
1905
1906 case 'SOME-DENIED-PROPERTY':
1907 $denied[] = $reply->Tag($tag);
1908 break;
1909
1910 case 'urn:ietf:params:xml:ns:caldav:calendar-timezone':
1911 if ( ! $this->_is_collection ) return false;
1912 if ( !isset($this->collection->vtimezone) || $this->collection->vtimezone == '' ) return false;
1913
1914 $cal = new iCalComponent();
1915 $cal->VCalendar();
1916 $cal->AddComponent( new iCalComponent($this->collection->vtimezone) );
1917 $reply->NSElement($prop, $tag, $cal->Render() );
1918 break;
1919
1920 case 'urn:ietf:params:xml:ns:carddav:address-data':
1921 case 'urn:ietf:params:xml:ns:caldav:calendar-data':
1922 if ( $this->_is_collection ) return false;
1923 if ( !isset($c->sync_resource_data_ok) || $c->sync_resource_data_ok == false ) return false;
1924 if ( !isset($this->resource) ) $this->FetchResource();
1925 $reply->NSElement($prop, $tag, $this->resource->caldav_data );
1926 break;
1927
1928 case 'urn:ietf:params:xml:ns:carddav:max-resource-size':
1929 if ( ! $this->_is_collection || !$this->_is_addressbook ) return false;
1930 $reply->NSElement($prop, $tag, $c->carddav_max_resource_size );
1931 break;
1932
1933 case 'urn:ietf:params:xml:ns:carddav:supported-address-data':
1934 if ( ! $this->_is_collection || !$this->_is_addressbook ) return false;
1935 $address_data = $reply->NewXMLElement( 'address-data', false,
1936 array( 'content-type' => 'text/vcard', 'version' => '3.0'), 'urn:ietf:params:xml:ns:carddav');
1937 $reply->NSElement($prop, $tag, $address_data );
1938 break;
1939
1940 case 'DAV::acl':
1941 if ( $this->HavePrivilegeTo('DAV::read-acl') ) {
1942 $reply->NSElement($prop, $tag, $this->GetACL( $reply ) );
1943 }
1944 else {
1945 $denied[] = $tag;
1946 }
1947 break;
1948
1949 case 'http://www.xythos.com/namespaces/StorageServer:ticketdiscovery':
1950 case 'DAV::ticketdiscovery':
1951 $reply->NSElement($prop,'http://www.xythos.com/namespaces/StorageServer:ticketdiscovery', $this->BuildTicketinfo($reply) );
1952 break;
1953
1954 default:
1955 $property_value = $this->GetProperty(preg_replace('{^(DAV:|urn:ietf:params:xml:ns:ca(rd|l)dav):}', '', $tag));
1956 if ( isset($property_value) ) {
1957 $reply->NSElement($prop, $tag, $property_value );
1958 }
1959 else {
1960 if ( !isset($this->dead_properties) ) $this->FetchDeadProperties();
1961 if ( isset($this->dead_properties[$tag]) ) {
1962 $reply->NSElement($prop, $tag, $this->dead_properties[$tag] );
1963 }
1964 else {
1965// dbg_error_log( 'DAVResource', 'Request for unsupported property "%s" of path "%s".', $tag, $this->dav_name );
1966 return false;
1967 }
1968 }
1969 }
1970
1971 return true;
1972 }
1973
1974
1982 function GetPropStat( $properties, &$reply, $props_only = false ) {
1983 global $request;
1984
1985 dbg_error_log('DAVResource',':GetPropStat: propstat for href "%s"', $this->dav_name );
1986
1987 $prop = new XMLElement('prop', null, null, 'DAV:');
1988 $denied = array();
1989 $not_found = array();
1990 foreach( $properties AS $k => $tag ) {
1991 if ( is_object($tag) ) {
1992 dbg_error_log( 'DAVResource', ':GetPropStat: "$properties" should be an array of text. Assuming this object is an XMLElement!.' );
1993 $tag = $tag->GetNSTag();
1994 }
1995 $found = $this->ResourceProperty($tag, $prop, $reply, $denied );
1996 if ( !$found ) {
1997 if ( !isset($this->principal) ) $this->FetchPrincipal();
1998 $found = $this->principal->PrincipalProperty( $tag, $prop, $reply, $denied );
1999 }
2000 if ( ! $found ) {
2001// dbg_error_log( 'DAVResource', 'Request for unsupported property "%s" of resource "%s".', $tag, $this->dav_name );
2002 $not_found[] = $tag;
2003 }
2004 }
2005 if ( $props_only ) return $prop;
2006
2007 $status = new XMLElement('status', 'HTTP/1.1 200 OK', null, 'DAV:' );
2008
2009 $elements = array( new XMLElement( 'propstat', array($prop,$status), null, 'DAV:' ) );
2010
2011 if ( count($denied) > 0 ) {
2012 $status = new XMLElement('status', 'HTTP/1.1 403 Forbidden', null, 'DAV:' );
2013 $noprop = new XMLElement('prop', null, null, 'DAV:');
2014 foreach( $denied AS $k => $v ) {
2015 $reply->NSElement($noprop, $v);
2016 }
2017 $elements[] = new XMLElement( 'propstat', array( $noprop, $status), null, 'DAV:' );
2018 }
2019
2020 if ( !$request->PreferMinimal() && count($not_found) > 0 ) {
2021 $status = new XMLElement('status', 'HTTP/1.1 404 Not Found', null, 'DAV:' );
2022 $noprop = new XMLElement('prop', null, null, 'DAV:');
2023 foreach( $not_found AS $k => $v ) {
2024 $reply->NSElement($noprop,$v);
2025 }
2026 $elements[] = new XMLElement( 'propstat', array( $noprop, $status), null, 'DAV:' );
2027 }
2028 return $elements;
2029 }
2030
2031
2040 function RenderAsXML( $properties, &$reply, $bound_parent_path = null ) {
2041 dbg_error_log('DAVResource',':RenderAsXML: Resource "%s" exists(%d)', $this->dav_name, $this->Exists() );
2042
2043 if ( !$this->Exists() ) return null;
2044
2045 $elements = $this->GetPropStat( $properties, $reply );
2046 if ( isset($bound_parent_path) ) {
2047 $dav_name = str_replace( $this->parent_path(), $bound_parent_path, $this->dav_name );
2048 }
2049 else {
2050 $dav_name = $this->dav_name;
2051 }
2052
2053 array_unshift( $elements, $reply->href(ConstructURL($dav_name)));
2054
2055 $response = new XMLElement( 'response', $elements, null, 'DAV:' );
2056
2057 return $response;
2058 }
2059
2060}
NeedPrivilege( $privilege, $any=null)
FromPath($inpath)
static BuildDeadPropertyXML($property_name, $raw_string)
__construct( $parameters=null, ?DAVResource $prefetched_collection=null)
BuildSupportedReports(&$reply)
BuildTicketinfo(&$reply)
BuildPrivileges( $privilege_names=null, &$xmldoc=null)
HavePrivilegeTo( $do_what, $any=null)
IsLocked( $depth=0)