source: contrib/davical/inc/DAVResource.php @ 3733

Revision 3733, 64.8 KB checked in by gabriel.malheiros, 13 years ago (diff)

Ticket #1541 - <Davical customizado para o Expresso.Utiliza Caldav e CardDav?>

Line 
1<?php
2/**
3* An object representing a DAV 'resource'
4*
5* @package   davical
6* @subpackage   Resource
7* @author    Andrew McMillan <andrew@mcmillan.net.nz>
8* @copyright Morphoss Ltd
9* @license   http://gnu.org/copyleft/gpl.html GNU GPL v3 or later
10*/
11
12require_once('AwlQuery.php');
13require_once('DAVTicket.php');
14include_once ("drivers_ldap.php");
15
16/**
17* A class for things to do with a DAV Resource
18*
19* @package   davical
20*/
21class DAVResource
22{
23  /**
24  * @var The partial URL of the resource within our namespace, which this resource is being retrieved as
25  */
26  protected $dav_name;
27
28  /**
29  * @var Boolean: does the resource actually exist yet?
30  */
31  protected $exists;
32
33  /**
34  * @var The unique etag associated with the current version of the resource
35  */
36  protected $unique_tag;
37
38  /**
39  * @var The actual resource content, if it exists and is not a collection
40  */
41  protected $resource;
42
43  /**
44  * @var The parent of the resource, which will always be a collection
45  */
46  protected $parent;
47
48  /**
49  * @var The types of the resource, possibly multiple
50  */
51  protected $resourcetypes;
52
53  /**
54  * @var The type of the content
55  */
56  protected $contenttype;
57
58  /**
59  * @var The canonical name which this resource exists at
60  */
61  protected $bound_from;
62
63  /**
64  * @var An object which is the collection record for this resource, or for it's container
65  */
66  private $collection;
67
68  /**
69  * @var An object which is the principal for this resource, or would be if it existed.
70  */
71  private $principal;
72
73  /**
74  * @var A bit mask representing the current user's privileges towards this DAVResource
75  */
76  private $privileges;
77
78  /**
79  * @var True if this resource is a collection of any kind
80  */
81  private $_is_collection;
82
83  /**
84  * @var True if this resource is a principal-URL
85  */
86  private $_is_principal;
87
88  /**
89  * @var True if this resource is a calendar collection
90  */
91  private $_is_calendar;
92
93  /**
94  * @var True if this resource is a binding to another resource
95  */
96  private $_is_binding;
97
98  /**
99  * @var True if this resource is an addressbook collection
100  */
101  private $_is_addressbook;
102
103  /**
104  * @var True if this resource is, or is in, a proxy collection
105  */
106  private $_is_proxy_request;
107
108  /**
109  * @var An array of the methods we support on this resource.
110  */
111  private $supported_methods;
112
113  /**
114  * @var An array of the reports we support on this resource.
115  */
116  private $supported_reports;
117
118  /**
119  * @var An array of the dead properties held for this resource
120  */
121  private $dead_properties;
122
123  /**
124  * @var An array of the component types we support on this resource.
125  */
126  private $supported_components;
127
128  /**
129  * @var An array of DAVTicket objects if any apply to this resource, such as via a bind.
130  */
131  private $tickets;
132
133  /**
134  * Constructor
135  * @param mixed $parameters If null, an empty Resourced is created.
136  *     If it is an object then it is expected to be a record that was
137  *     read elsewhere.
138  */
139  function __construct( $parameters = null ) {
140    $this->exists        = null;
141    $this->bound_from    = null;
142    $this->dav_name      = null;
143    $this->unique_tag    = null;
144    $this->resource      = null;
145    $this->collection    = null;
146    $this->principal     = null;
147    $this->parent        = null;
148    $this->resourcetypes = null;
149    $this->contenttype   = null;
150    $this->privileges    = null;
151    $this->dead_properties   = null;
152    $this->supported_methods = null;
153    $this->supported_reports = null;
154
155    $this->_is_collection    = false;
156    $this->_is_principal     = false;
157    $this->_is_calendar      = false;
158    $this->_is_binding       = false;
159    $this->_is_addressbook   = false;
160    $this->_is_proxy_request = false;
161    if ( isset($parameters) && is_object($parameters) ) {
162      $this->FromRow($parameters);
163    }
164    else if ( isset($parameters) && is_array($parameters) ) {
165      if ( isset($parameters['path']) ) {
166        $this->FromPath($parameters['path']);
167      }
168    }
169    else if ( isset($parameters) && is_string($parameters) ) {
170      $this->FromPath($parameters);
171    }
172 
173  }
174
175
176  /**
177  * Initialise from a database row
178  * @param object $row The row from the DB.
179  */
180  function FromRow($row) {
181    global $c;
182
183    if ( $row == null ) return;
184
185    $this->exists = true;
186    $this->dav_name = $row->dav_name;
187    $this->bound_from = (isset($row->bound_from)? $row->bound_from : $row->dav_name);
188    $this->_is_collection = preg_match( '{/$}', $this->dav_name );
189
190
191    if ( $row->owner)
192    {
193    $this->exists = true;
194    // $this->dav_name = $row->dav_name;
195    $this->dav_name = $row->owner;
196    //$this->bound_from = (isset($row->bound_from)? $row->bound_from : $row->dav_name);
197    $this->bound_from = (isset($row->bound_from)? $row->bound_from : $row->owner);
198    $this->_is_collection = preg_match( '{/$}', $this->dav_name );
199    }
200    else if ( $row->id_owner)
201     {
202          $this->exists = true;
203          // $this->dav_name = $row->dav_name;
204          $this->dav_name = $row->id_owner;
205          //$this->bound_from = (isset($row->bound_from)? $row->bound_from : $row->dav_name);
206          $this->bound_from = (isset($row->bound_from)? $row->bound_from : $row->id_owner);
207          $this->_is_collection = preg_match( '{/$}', $this->dav_name );
208     } 
209    //else
210    // { return;
211    // }
212    if ( $this->_is_collection ) {
213      $this->contenttype = 'httpd/unix-directory';
214      $this->collection = (object) array();
215      $this->resource_id = $row->collection_id;
216
217      $this->_is_principal = preg_match( '{^/[^/]+/$}', $this->dav_name );
218      if ( preg_match( '#^(/principals/[^/]+/[^/]+)/?$#', $this->dav_name, $matches) ) {
219        $this->collection->dav_name = $matches[1].'/';
220        $this->collection->type = 'principal_link';
221        $this->_is_principal = true;
222      }
223    }
224    else {
225      $this->resource = (object) array();
226      //if ( isset($row->dav_id) ) $this->resource_id = $row->dav_id;
227      if ( isset($row->cal_id) ) $this->resource_id = $row->cal_id;
228      if ( isset($row->id_contact) ) $this->resource_id = $row->id_contact;
229       
230    }
231
232    dbg_error_log( 'DAVResource', ':FromRow: Named "%s" is%s a collection.', $this->dav_name, ($this->_is_collection?'':' not') );
233
234
235    foreach( $row AS $k => $v ) {
236      if ( $this->_is_collection )
237        $this->collection->{$k} = $v;
238      else
239        $this->resource->{$k} = $v;
240      switch ( $k ) {
241        case 'last_update':
242                           $lastupdate = $v; 
243      }
244    }
245    foreach( $row AS $k => $v ) {
246      if ( $this->_is_collection )
247        $this->collection->{$k} = $v;
248      else
249        $this->resource->{$k} = $v;
250      switch ( $k ) {
251        case 'created':
252        case 'modified':
253        case 'resourcetypes':
254          $this->{$k} = $v;
255          break;
256
257        case 'cal_id':
258          $this->resourcetypes = '';
259          $this->unique_tag = '"'.md5($v.$this->dav_name.$lastupdate).'"';
260          break;
261        case 'id_contact':
262          $this->resourcetypes = '';
263          $this->unique_tag ='"'.md5($v.$this->dav_name.$lastupdate).'"';
264          break;
265       
266        case 'dav_etag':
267          $this->unique_tag = '"'.$v.'"';
268          break;
269
270      }
271    }
272
273    if ( $this->_is_collection ) {
274      if ( !isset( $this->collection->type ) || $this->collection->type == 'collection' ) {
275        if ( $this->_is_principal )
276          $this->collection->type = 'principal';
277        else if ( $row->is_calendar == 't' ) {
278          $this->collection->type = 'calendar';
279        }
280        else if ( $row->is_addressbook == 't' ) {
281          $this->collection->type = 'addressbook';
282        }
283        else if ( isset($row->is_proxy) && $row->is_proxy == 't' ) {
284          $this->collection->type = 'proxy';
285        }
286        else if ( preg_match( '#^((/[^/]+/)\.(in|out)/)[^/]*$#', $this->dav_name, $matches ) )
287          $this->collection->type = 'schedule-'. $matches[3]. 'box';
288        else if ( $this->dav_name == '/' )
289          $this->collection->type = 'root';
290        else
291          $this->collection->type = 'collection';
292      }
293
294      $this->_is_calendar     = ($this->collection->is_calendar == 't');
295      $this->_is_addressbook  = ($this->collection->is_addressbook == 't');
296      $this->_is_proxy_request= ($this->collection->type == 'proxy');
297      if ( $this->_is_principal && !isset($this->resourcetypes) ) {
298        $this->resourcetypes = '<DAV::collection/><DAV::principal/>';
299      }
300      else if ( $this->_is_proxy_request ) {
301        $this->resourcetypes  = $this->collection->resourcetypes;
302      }
303      if ( isset($this->collection->dav_displayname) ) $this->collection->displayname = $this->collection->dav_displayname;
304    }
305    else {
306      $this->resourcetypes = '';
307      //if ( isset($this->resource->caldav_data) ) {
308      if ( isset($this->resource->owner) ) {
309        //if ( substr($this->resource->caldav_data,0,15) == 'BEGIN:VCALENDAR' ) $this->contenttype = 'text/calendar';
310        $this->contenttype = 'text/calendar';
311        $this->resource->displayname = $this->resource->title;
312      }
313      else
314        { 
315        $this->contenttype = 'text/vcard';
316        $this->resource->displayname = $this->resource->given_names;
317       }
318         
319    }
320 }
321
322
323  /**
324  * Initialise from a path
325  * @param object $inpath The path to populate the resource data from
326  */
327  function FromPath($inpath) {
328    global $c;
329
330    $this->dav_name = DeconstructURL($inpath);
331
332    $this->FetchCollection();
333    if ( $this->_is_collection ) {
334      if ( $this->_is_principal || $this->collection->type == 'principal' )   $this->FetchPrincipal();
335     } 
336  else {
337      $this->FetchResource();
338    }
339    dbg_error_log( 'DAVResource', ':FromPath: Path "%s" is%s a collection%s.',
340               $this->dav_name, ($this->_is_collection?' '.$this->resourcetypes:' not'), ($this->_is_principal?' and a principal':'') );
341  }
342
343
344  /**
345  * Find the collection associated with this resource.
346  */
347  function FetchCollection() {
348    global $c, $session, $request;
349    /**
350    * RFC4918, 8.3: Identifiers for collections SHOULD end in '/'
351    *    - also discussed at more length in 5.2
352    *
353    * So we look for a collection which matches one of the following URLs:
354    *  - The exact request.
355    *  - If the exact request, doesn't end in '/', then the request URL with a '/' appended
356    *  - The request URL truncated to the last '/'
357    * The collection URL for this request is therefore the longest row in the result, so we
358    * can "... ORDER BY LENGTH(dav_name) DESC LIMIT 1"
359    */
360    dbg_error_log( 'DAVResource', ':FetchCollection: Looking for collection for "%s".', $this->dav_name );
361
362    $this->collection = (object) array(
363      'collection_id' => -1,
364      'type' => 'nonexistent',
365      'is_calendar' => false, 'is_principal' => false, 'is_addressbook' => false
366    );
367
368    $base_sql = 'SELECT collection.*, path_privs(:session_principal::int8, collection.dav_name,:scan_depth::int), ';
369    $base_sql .= 'p.principal_id, p.type_id AS principal_type_id, ';
370    $base_sql .= 'p.displayname AS principal_displayname, p.default_privileges AS principal_default_privileges, ';
371    $base_sql .= 'time_zone.tz_spec ';
372    $base_sql .= 'FROM collection LEFT JOIN principal p USING (user_no) ';
373    $base_sql .= 'LEFT JOIN time_zone ON (collection.timezone=time_zone.tz_id) ';
374    $base_sql .= 'WHERE ';
375    $sql = $base_sql .'collection.dav_name = :raw_path ';
376    $params = array( ':raw_path' => $this->dav_name, ':session_principal' => $session->principal_id, ':scan_depth' => $c->permission_scan_depth );
377    if ( !preg_match( '#/$#', $this->dav_name ) ) {
378      $sql .= ' OR collection.dav_name = :up_to_slash OR collection.dav_name = :plus_slash ';
379      $params[':up_to_slash'] = preg_replace( '#[^/]*$#', '', $this->dav_name);
380      $params[':plus_slash']  = $this->dav_name.'/';
381    }
382    $sql .= 'ORDER BY LENGTH(collection.dav_name) DESC LIMIT 1';
383    $qry = new AwlQuery( $sql, $params );
384    if ( $qry->Exec('DAVResource') && $qry->rows() == 1 && ($row = $qry->Fetch()) ) {
385      $this->collection = $row;
386      $this->collection->exists = true;
387      if ( $row->is_calendar == 't' )
388        $this->collection->type = 'calendar';
389      else if ( $row->is_addressbook == 't' )
390        $this->collection->type = 'addressbook';
391      else if ( preg_match( '#^((/[^/]+/)\.(in|out)/)[^/]*$#', $this->dav_name, $matches ) )
392        $this->collection->type = 'schedule-'. $matches[3]. 'box';
393      else
394        $this->collection->type = 'collection';
395    }
396    else if ( preg_match( '{^( ( / ([^/]+) / ) \.(in|out)/ ) [^/]*$}x', $this->dav_name, $matches ) ) {
397      // The request is for a scheduling inbox or outbox (or something inside one) and we should auto-create it
398      $params = array( ':username' => $matches[3], ':parent_container' => $matches[2], ':dav_name' => $matches[1] );
399      $params[':boxname'] = ($matches[4] == 'in' ? ' Inbox' : ' Outbox');
400      $this->collection_type = 'schedule-'. $matches[4]. 'box';
401      $params[':resourcetypes'] = sprintf('<DAV::collection/><urn:ietf:params:xml:ns:caldav:%s/>', $this->collection_type );
402      $sql = <<<EOSQL
403INSERT INTO collection ( user_no, parent_container, dav_name, dav_displayname, is_calendar, created, modified, dav_etag, resourcetypes )
404    VALUES( (SELECT user_no FROM usr WHERE username = :username),
405            :parent_container, :dav_name,
406            (SELECT fullname FROM usr WHERE username = :username) || :boxname,
407             FALSE, current_timestamp, current_timestamp, '1', :resourcetypes )
408EOSQL;
409      $qry = new AwlQuery( $sql, $params );
410      $qry->Exec('DAVResource');
411      dbg_error_log( 'DAVResource', 'Created new collection as "%s".', trim($params[':boxname']) );
412
413      $params = array( ':raw_path' => $this->dav_name, ':session_principal' => $session->principal_id, ':scan_depth' => $c->permission_scan_depth );
414      $qry = new AwlQuery( $base_sql . ' dav_name = :raw_path', $params );
415      if ( $qry->Exec('DAVResource') && $qry->rows() == 1 && ($row = $qry->Fetch()) ) {
416        $this->collection = $row;
417        $this->collection->exists = true;
418        $this->collection->type = $this->collection_type;
419      }
420    }
421    else if ( preg_match( '#^(/([^/]+)/calendar-proxy-(read|write))/?[^/]*$#', $this->dav_name, $matches ) ) {
422      $this->collection->type = 'proxy';
423      $this->_is_proxy_request = true;
424      $this->proxy_type = $matches[3];
425      $this->collection->dav_name = $this->dav_name;
426      $this->collection->dav_displayname = sprintf( '%s proxy %s', $matches[2], $matches[3] );
427      $this->collection->exists = true;
428      $this->collection->parent_container = $matches[1] . '/';
429    }
430    else if ( preg_match( '#^(/[^/]+)/?$#', $this->dav_name, $matches)
431           || preg_match( '#^((/principals/[^/]+/)[^/]+)/?$#', $this->dav_name, $matches) ) {
432      $this->_is_principal = true;
433      $this->FetchPrincipal();
434    }
435    else if ( $this->dav_name == '/' ) {
436      $this->collection->dav_name = '/';
437      $this->collection->type = 'root';
438      $this->collection->exists = true;
439      $this->collection->displayname = $c->system_name;
440      $this->collection->default_privileges = (1 | 16 | 32);
441      $this->collection->parent_container = '/';
442    }
443    else {
444      $sql = <<<EOSQL
445SELECT collection.*, path_privs(:session_principal::int8, collection.dav_name,:scan_depth::int), p.principal_id,
446    p.type_id AS principal_type_id, p.displayname AS principal_displayname, p.default_privileges AS principal_default_privileges,
447    time_zone.tz_spec, dav_binding.access_ticket_id, dav_binding.parent_container AS bind_parent_container,
448    dav_binding.dav_displayname, owner.dav_name AS bind_owner_url, dav_binding.dav_name AS bound_to
449FROM dav_binding
450    LEFT JOIN collection ON (collection.collection_id=bound_source_id)
451    LEFT JOIN principal p USING (user_no)
452    LEFT JOIN dav_principal owner ON (dav_binding.dav_owner_id=owner.principal_id)
453    LEFT JOIN time_zone ON (collection.timezone=time_zone.tz_id)
454 WHERE dav_binding.dav_name = :raw_path
455EOSQL;
456      $params = array( ':raw_path' => $this->dav_name, ':session_principal' => $session->principal_id, ':scan_depth' => $c->permission_scan_depth );
457      if ( !preg_match( '#/$#', $this->dav_name ) ) {
458        $sql .= ' OR dav_binding.dav_name = :up_to_slash OR collection.dav_name = :plus_slash ';
459        $params[':up_to_slash'] = preg_replace( '#[^/]*$#', '', $this->dav_name);
460        $params[':plus_slash']  = $this->dav_name.'/';
461      }
462      $sql .= ' ORDER BY LENGTH(dav_binding.dav_name) DESC LIMIT 1';
463      $qry = new AwlQuery( $sql, $params );
464      if ( $qry->Exec('DAVResource',__LINE__,__FILE__) && $qry->rows() == 1 && ($row = $qry->Fetch()) ) {
465        $this->collection = $row;
466        $this->collection->exists = true;
467        $this->_is_binding = true;
468        $this->collection->parent_set = $row->parent_container;
469        $this->collection->parent_container = $row->bind_parent_container;
470        $this->bound_from = str_replace( $row->bound_to, $row->dav_name, $this->dav_name);
471        $this->collection->bound_from = $row->dav_name;
472        $this->collection->dav_name = $row->bound_to;
473        if ( isset($row->access_ticket_id) ) {
474          if ( !isset($this->tickets) ) $this->tickets = array();
475          $this->tickets[] = new DAVTicket($row->access_ticket_id);
476        }
477        if ( $row->is_calendar == 't' )
478          $this->collection->type = 'calendar';
479        else if ( $row->is_addressbook == 't' )
480          $this->collection->type = 'addressbook';
481        else if ( preg_match( '#^((/[^/]+/)\.(in|out)/)[^/]*$#', $this->dav_name, $matches ) )
482          $this->collection->type = 'schedule-'. $matches[3]. 'box';
483        else
484          $this->collection->type = 'collection';
485      }
486      else {
487        dbg_error_log( 'DAVResource', 'No collection for path "%s".', $this->dav_name );
488        $this->collection->exists = false;
489        $this->collection->dav_name = preg_replace('{/[^/]*$}', '/', $this->dav_name);
490      }
491    }
492
493    @dbg_error_log( 'DAVResource', ':FetchCollection: Found collection named "%s" of type "%s" davname "%s".', $this->collection->dav_name, $this->collection->type,$this->dav_name );
494
495    $this->_is_collection = ( $this->_is_principal || $this->collection->dav_name == $this->dav_name || $this->collection->dav_name == $this->dav_name.'/' || preg_match('{addressbook}',$this->dav_name) );
496    if ( $this->_is_collection ) {
497      $this->dav_name = $this->collection->dav_name;
498      $this->resource_id = $this->collection->collection_id;
499      $this->_is_calendar    = ($this->collection->type == 'calendar');
500      $this->_is_addressbook = ($this->collection->type == 'addressbook');
501      $this->contenttype = 'httpd/unix-directory';
502      if ( !isset($this->exists) && isset($this->collection->exists) ) {
503        // If this seems peculiar it's because we only set it to false above...
504        $this->exists = $this->collection->exists;
505      }
506      if ( $this->exists ) {
507        if ( isset($this->collection->dav_etag) ) $this->unique_tag = '"'.$this->collection->dav_etag.'"';
508        if ( isset($this->collection->created) )  $this->created = $this->collection->created;
509        if ( isset($this->collection->modified) ) $this->modified = $this->collection->modified;
510        if ( isset($this->collection->dav_displayname) ) $this->collection->displayname = $this->collection->dav_displayname;
511      }
512      else {
513        if ( !isset($this->parent) ){
514            $this->FetchParentContainer();
515           }
516        $this->user_no = $this->parent->GetProperty('user_no');
517      }
518      if ( isset($this->collection->resourcetypes) )
519        $this->resourcetypes = $this->collection->resourcetypes;
520      else {
521        $this->resourcetypes = '<DAV::collection/>';
522        if ( $this->_is_principal )   $this->resourcetypes .= '<DAV::principal/>';
523        if ( $this->_is_addressbook ) $this->resourcetypes .= '<urn:ietf:params:xml:ns:carddav:addressbook/>';
524        if ( $this->_is_calendar )    $this->resourcetypes .= '<urn:ietf:params:xml:ns:caldav:calendar/>';
525      }
526    }
527  }
528
529
530  /**
531  * Find the principal associated with this resource.
532  */
533  function FetchPrincipal() {
534    if ( isset($this->principal) ) return;
535    $this->principal = new CalDAVPrincipal( array( "path" => $this->bound_from() ) );
536    if ( $this->_is_principal && $this->principal->Exists() ) {
537      $this->exists = true;
538      $this->unique_tag = $this->principal->dav_etag;
539      $this->created = $this->principal->created;
540      $this->modified = $this->principal->modified;
541      $this->resourcetypes = '<DAV::collection/><DAV::principal/>';
542      $this->resource_id = $this->principal->principal_id;
543      $this->collection = $this->principal->AsCollection();
544      $this->user_no = $this->principal->user_no;
545    }
546    elseif ( $this->_is_principal ) {
547      $this->exists = false;
548      $this->collection->dav_name = $this->dav_name;
549      $this->collection->type = 'principal';
550    }
551  }
552
553
554  /**
555  * Retrieve the actual resource.
556  */
557  function FetchResource() {
558    global $c, $session;
559
560    if ( isset($this->exists) ) return;   // True or false, we've got what we can already
561    if ( $this->_is_collection ) return;   // We have all we're going to read
562
563    $sql = <<<EOQRY
564SELECT * FROM caldav_data LEFT JOIN calendar_item USING (collection_id,dav_id)
565     WHERE caldav_data.dav_name = :dav_name
566EOQRY;
567    $params = array( ':dav_name' => $this->bound_from() );
568
569    $qry = new AwlQuery( $sql, $params );
570    if ( $qry->Exec('DAVResource') && $qry->rows() > 0 ) {
571      $this->exists = true;
572      $this->resource = $qry->Fetch();
573      $this->unique_tag = $this->resource->dav_etag;
574      $this->created = $this->resource->created;
575      $this->modified = $this->resource->modified;
576      $this->resource_id = $this->resource->dav_id;
577      if ( substr($this->resource->caldav_data,0,15) == 'BEGIN:VCALENDAR' ) {
578        $this->contenttype = 'text/calendar';
579      }
580      $this->resourcetypes = '';
581    }
582    else {
583      $this->exists = false;
584    }
585  }
586
587
588  /**
589  * Fetch any dead properties for this URL
590  */
591  function FetchDeadProperties() {
592    if ( isset($this->dead_properties) ) return;
593
594    $this->dead_properties = array();
595    $qry = new AwlQuery('SELECT property_name, property_value FROM property WHERE dav_name= :dav_name', array(':dav_name' => $this->dav_name) );
596    if ( $qry->Exec('DAVResource') ) {
597      while ( $property = $qry->Fetch() ) {
598        $this->dead_properties[$property->property_name] = $property->property_value;
599      }
600    }
601  }
602
603
604  /**
605  * Fetch the parent to this resource.
606  */
607  function FetchParentContainer() {
608    if ( $this->dav_name == '/' ) return null;
609    if ( !isset($this->parent) ) {
610      if ( $this->_is_collection ) {
611        dbg_error_log( 'DAVResource', 'Retrieving "%s" - parent of "%s" (dav_name: %s)', $this->parent_path(), $this->collection->dav_name, $this->dav_name() );
612        $this->parent = new DAVResource( $this->parent_path() );
613      }
614      else {
615        dbg_error_log( 'DAVResource', 'Retrieving "%s" - parent of "%s" (dav_name: %s)', $this->parent_path(), $this->collection->dav_name, $this->dav_name() );
616        $this->parent = new DAVResource($this->collection->dav_name);
617      }
618    }
619    return $this->parent;
620  }
621
622
623  /**
624  * Build permissions for this URL
625  */
626  function FetchPrivileges() {
627    global $session, $request;
628
629    if ( $this->dav_name == '/' || $this->dav_name == '' ) {
630      $this->privileges = (1 | 16 | 32); // read + read-acl + read-current-user-privilege-set
631      dbg_error_log( 'DAVResource', 'Read permissions for user accessing /' );
632      return;
633    }
634
635    if ( $session->AllowedTo('Admin') ) {
636      $this->privileges = privilege_to_bits('all');
637      dbg_error_log( 'DAVResource', 'Full permissions for an administrator.' );
638      return;
639    }
640
641    if ( $this->IsPrincipal() ) {
642      if ( !isset($this->principal) ) $this->FetchPrincipal();
643      $this->privileges = $this->principal->Privileges();
644      dbg_error_log( 'DAVResource', 'Privileges of "%s" for user accessing "%s"', $this->privileges, $this->principal->username() );
645      return;
646    }
647
648    if ( ! isset($this->collection) ) $this->FetchCollection();
649    $this->privileges = 0;
650    //if ( !isset($this->collection->path_privs) ) {
651     if ( !isset($this->parent) ) $this->FetchParentContainer();
652     // $this->collection->path_privs = $this->parent->Privileges();
653     // $this->collection->user_no = $this->parent->GetProperty('user_no');
654     // $this->collection->principal_id = $this->parent->GetProperty('principal_id');
655     
656       //$path = preg_replace("/\/|addressbook/","",$this->collection->dav_name);
657       $path = preg_split('/\//', $this->collection->dav_name);
658       dbg_error_log( 'DAVResource', 'NOVA PASTA "%s"',$path[1]);
659       $filtro = "uid=".$path[1];
660       $atributos = array("uidNumber");
661       $uidnumber = ldapDrivers::requestAtributo($filtro, $atributos);
662       $this->collection->user_no = $uidnumber['uidNumber']; 
663     //}
664   if ( $session->username != $path[1] && $this->collection->is_calendar ) {
665         $this->privileges = 0;
666         $newpath = $this->collection->user_no;
667         $filtro = "uid=".$session->username;
668         $atributos = array("uidNumber");
669         $accountt = ldapDrivers::requestAtributo($filtro, $atributos);
670         $account = $accountt['uidNumber'];
671         $qry = new AwlQuery("select acl_rights from phpgw_acl where acl_location = :account and acl_account = :path" ,array(':account' => $account ,':path' => $newpath));
672         if ( $qry->Exec('Request',__LINE__,__FILE__) &&  $qry->rows() == 1 && $permission_result = $qry->Fetch()){
673               switch( $permission_result->acl_rights ) {
674                     case '1' :   $this->privileges = privilege_to_bits( array('read','read-free-busy','read-acl'));
675                                  dbg_error_log( "caldav", "Basic read permissions for user accessing " );
676                                  break;
677                     case '3' :   $this->privileges = privilege_to_bits( array('read','read-free-busy','read-acl','write'));
678                                  dbg_error_log( "caldav", "Basic read/write permissions for user accessing " );
679                                  break;
680                     case '7' :   $this->privileges = privilege_to_bits( array('read','read-free-busy','read-acl','write'));
681                                  dbg_error_log( "caldav", "Basic read/write/edit permissions for user accessing " );
682                                  break;
683                     case '15' :  $this->privileges = privilege_to_bits( array('read','read-free-busy','read-acl','write','schedule-send'));
684                                  dbg_error_log( "caldav", "Basic read/write/edit/delete  permissions for user accessing  " );
685                                  break;
686                     case '31' :  $this->privileges = privilege_to_bits('all');
687                                  dbg_error_log( "caldav", "Full permissions for %s for path %s", ( $session->user_no == $this->user_no ? "user accessing their own hierarchy" : "a systems administrator"), $this->dav_name );
688                                  break;
689             }
690
691         }
692      }
693    else if (  $session->username == $path[1] )
694      { 
695         $this->privileges = privilege_to_bits('all');             
696     }
697    //$this->privileges = privilege_to_bits('all');//$this->collection->path_privs;
698    if ( is_string($this->privileges) ) $this->privileges = bindec( $this->privileges );
699
700    dbg_error_log( 'DAVResource', 'Privileges of "%s" for user "%s" accessing "%s"',
701                       decbin($this->privileges), $session->username, $this->dav_name() );
702
703    if ( isset($request->ticket) && $request->ticket->MatchesPath($this->bound_from()) ) {
704      $this->privileges |= $request->ticket->privileges();
705      dbg_error_log( 'DAVResource', 'Applying permissions for ticket "%s" now: %s', $request->ticket->id(), decbin($this->privileges) );
706    }
707
708    if ( isset($this->tickets) ) {
709      if ( !isset($this->resource_id) ) $this->FetchResource();
710      foreach( $this->tickets AS $k => $ticket ) {
711        if ( $ticket->MatchesResource($this->resource_id()) || $ticket->MatchesPath($this->bound_from()) ) {
712          $this->privileges |= $ticket->privileges();
713          dbg_error_log( 'DAVResource', 'Applying permissions for ticket "%s" now: %s', $ticket->id(), decbin($this->privileges) );
714        }
715      }
716    }
717  }
718
719
720  /**
721  * Return the privileges bits for the current session user to this resource
722  */
723  function Privileges() {
724    if ( !isset($this->privileges) ) $this->FetchPrivileges();
725    return $this->privileges;
726  }
727
728
729  /**
730  * Is the user has the privileges to do what is requested.
731  * @param $do_what mixed The request privilege name, or array of privilege names, to be checked.
732  * @param $any boolean Whether we accept any of the privileges. The default is true, unless the requested privilege is 'all', when it is false.
733  * @return boolean Whether they do have one of those privileges against this resource.
734  */
735  function HavePrivilegeTo( $do_what, $any = null ) {
736    if ( !isset($this->privileges) ) $this->FetchPrivileges();
737    if ( !isset($any) ) $any = ($do_what != 'all');
738    $test_bits = privilege_to_bits( $do_what );
739    dbg_error_log( 'DAVResource', 'Testing %s privileges of "%s" (%s) against allowed "%s" => "%s" (%s)', ($any?'any':'exactly'),
740        $do_what, decbin($test_bits), decbin($this->privileges), ($this->privileges & $test_bits), decbin($this->privileges & $test_bits) );
741    if ( $any ) {
742      return ($this->privileges & $test_bits) > 0;
743    }
744    else {
745      return ($this->privileges & $test_bits) == $test_bits;
746    }
747  }
748
749
750  /**
751  * Check if we have the needed privilege or send an error response.
752  *
753  * @param string $privilege The name of the needed privilege.
754  */
755  function NeedPrivilege( $privilege, $any = null ) {
756    global $request;
757
758    if ( $this->HavePrivilegeTo($privilege, $any) ) return;
759
760    $request->NeedPrivilege( $privilege, $this->dav_name );
761    exit(0);  // Unecessary, but might clarify things
762  }
763
764
765  /**
766  * Returns the array of privilege names converted into XMLElements
767  */
768  function BuildPrivileges( $privilege_names=null, &$xmldoc=null ) {
769    if ( $privilege_names == null ) {
770      if ( !isset($this->privileges) ) $this->FetchPrivileges();
771      $privilege_names = bits_to_privilege($this->privileges, ($this->_is_collection ? $this->collection->type : null ) );
772    }
773    return privileges_to_XML( $privilege_names, $xmldoc);
774  }
775
776
777  /**
778  * Returns the array of supported methods
779  */
780  function FetchSupportedMethods( ) {
781    if ( isset($this->supported_methods) ) return $this->supported_methods;
782
783    $this->supported_methods = array(
784      'OPTIONS' => '',
785      'PROPFIND' => '',
786      'REPORT' => '',
787      'DELETE' => '',
788      'LOCK' => '',
789      'UNLOCK' => '',
790      'MOVE' => ''
791    );
792    if ( $this->IsCollection() ) {
793/*      if ( $this->IsPrincipal() ) {
794        $this->supported_methods['MKCALENDAR'] = '';
795        $this->supported_methods['MKCOL'] = '';
796      } */
797      switch ( $this->collection->type ) {
798        case 'root':
799        case 'email':
800          // We just override the list completely here.
801          $this->supported_methods = array(
802            'OPTIONS' => '',
803            'PROPFIND' => '',
804            'REPORT' => ''
805          );
806          break;
807
808        case 'schedule-outbox':
809          $this->supported_methods = array_merge(
810            $this->supported_methods,
811            array(
812              'POST' => '', 'PROPPATCH' => '', 'MKTICKET' => '', 'DELTICKET' => ''
813            )
814          );
815          break;
816        case 'schedule-inbox':
817        case 'calendar':
818          $this->supported_methods['GET'] = '';
819          $this->supported_methods['PUT'] = '';
820          $this->supported_methods['HEAD'] = '';
821          $this->supported_methods['MKTICKET'] = '';
822          $this->supported_methods['DELTICKET'] = '';
823          break;
824        case 'collection':
825          $this->supported_methods['MKTICKET'] = '';
826          $this->supported_methods['DELTICKET'] = '';
827        case 'principal':
828          $this->supported_methods['GET'] = '';
829          $this->supported_methods['PUT'] = '';
830          $this->supported_methods['HEAD'] = '';
831          $this->supported_methods['MKCOL'] = '';
832          $this->supported_methods['MKCALENDAR'] = '';
833          $this->supported_methods['PROPPATCH'] = '';
834          break;
835      }
836    }
837    else {
838      $this->supported_methods = array_merge(
839        $this->supported_methods,
840        array(
841          'GET' => '', 'HEAD' => '', 'PUT' => '', 'MKTICKET' => '', 'DELTICKET' => ''
842        )
843      );
844    }
845
846    return $this->supported_methods;
847  }
848
849
850  /**
851  * Returns the array of supported methods converted into XMLElements
852  */
853  function BuildSupportedMethods( ) {
854    if ( !isset($this->supported_methods) ) $this->FetchSupportedMethods();
855    $methods = array();
856    foreach( $this->supported_methods AS $k => $v ) {
857//      dbg_error_log( 'DAVResource', ':BuildSupportedMethods: Adding method "%s" which is "%s".', $k, $v );
858      $methods[] = new XMLElement( 'supported-method', null, array('name' => $k) );
859    }
860    return $methods;
861  }
862
863
864  /**
865  * Returns the array of supported reports
866  */
867  function FetchSupportedReports( ) {
868    if ( isset($this->supported_reports) ) return $this->supported_reports;
869   /************* comentado por incompatibilidade do sync-collection
870    $this->supported_reports = array(
871      'DAV::principal-property-search' => '',
872      'DAV::principal-search-property-set' => '',
873      'DAV::expand-property' => '',
874      'DAV::sync-collection' => ''
875    ); */
876    $this->supported_reports = array(
877      'DAV::principal-property-search' => '',
878      'DAV::principal-search-property-set' => '',
879      'DAV::expand-property' => ''
880       
881    );
882
883    if ( !isset($this->collection) ) $this->FetchCollection();
884
885    if ( $this->collection->is_calendar ) {
886      $this->supported_reports = array_merge(
887        $this->supported_reports,
888        array(
889          'urn:ietf:params:xml:ns:caldav:calendar-query' => '',
890          'urn:ietf:params:xml:ns:caldav:calendar-multiget' => '',
891          'urn:ietf:params:xml:ns:caldav:free-busy-query' => ''
892        )
893      );
894    }
895    if ( $this->collection->is_addressbook ) {
896      $this->supported_reports = array_merge(
897        $this->supported_reports,
898        array(
899          'urn:ietf:params:xml:ns:carddav:addressbook-query' => '',
900          'urn:ietf:params:xml:ns:carddav:addressbook-multiget' => ''
901        )
902      );
903    }
904    return $this->supported_reports;
905  }
906
907
908  /**
909  * Returns the array of supported reports converted into XMLElements
910  */
911  function BuildSupportedReports( &$reply ) {
912    if ( !isset($this->supported_reports) ) $this->FetchSupportedReports();
913    $reports = array();
914    foreach( $this->supported_reports AS $k => $v ) {
915      dbg_error_log( 'DAVResource', ':BuildSupportedReports: Adding supported report "%s" which is "%s".', $k, $v );
916      $report = new XMLElement('report');
917      $reply->NSElement($report, $k );
918      $reports[] = new XMLElement('supported-report', $report );
919    }
920    return $reports;
921  }
922
923
924  /**
925  * Fetches an array of the access_ticket records applying to this path
926  */
927  function FetchTickets( ) {
928    global $c;
929    if ( isset($this->access_tickets) ) return;
930    $this->access_tickets = array();
931
932    $sql =
933'SELECT access_ticket.*, COALESCE( resource.dav_name, collection.dav_name) AS target_dav_name,
934        (access_ticket.expires < current_timestamp) AS expired,
935        dav_principal.dav_name AS principal_dav_name,
936        EXTRACT( \'epoch\' FROM (access_ticket.expires - current_timestamp)) AS seconds,
937        path_privs(access_ticket.dav_owner_id,collection.dav_name,:scan_depth) AS grantor_collection_privileges
938    FROM access_ticket JOIN collection ON (target_collection_id = collection_id)
939        JOIN dav_principal ON (dav_owner_id = principal_id)
940        LEFT JOIN caldav_data resource ON (resource.dav_id = access_ticket.target_resource_id)
941  WHERE target_collection_id = :collection_id ';
942    $params = array(':collection_id' => $this->collection->collection_id, ':scan_depth' => $c->permission_scan_depth);
943    if ( $this->IsCollection() ) {
944      $sql .= 'AND target_resource_id IS NULL';
945    }
946    else {
947      if ( !isset($this->exists) ) $this->FetchResource();
948      $sql .= 'AND target_resource_id = :dav_id';
949      $params[':dav_id'] = $this->resource->dav_id;
950    }
951    if ( isset($this->exists) && !$this->exists ) return;
952
953    $qry = new AwlQuery( $sql, $params );
954    if ( $qry->Exec('DAVResource',__LINE__,__FILE__) && $qry->rows() ) {
955      while( $ticket = $qry->Fetch() ) {
956        $this->access_tickets[] = $ticket;
957      }
958    }
959  }
960
961
962  /**
963  * Returns the array of tickets converted into XMLElements
964  *
965  * If the current user does not have DAV::read-acl privilege on this resource they
966  * will only get to see the tickets where they are the owner, or which they supplied
967  * along with the request.
968  *
969  * @param &XMLDocument $reply A reference to the XMLDocument used to construct the reply
970  * @return XMLTreeFragment A fragment of an XMLDocument to go in the reply
971  */
972  function BuildTicketinfo( &$reply ) {
973    global $session, $request;
974
975    if ( !isset($this->access_tickets) ) $this->FetchTickets();
976    $tickets = array();
977    $show_all = $this->HavePrivilegeTo('DAV::read-acl');
978    foreach( $this->access_tickets AS $meh => $trow ) {
979      if ( !$show_all && ( $trow->dav_owner_id == $session->principal_id || $request->ticket->id() == $trow->ticket_id ) ) continue;
980      dbg_error_log( 'DAVResource', ':BuildTicketinfo: Adding access_ticket "%s" which is "%s".', $trow->ticket_id, $trow->privileges );
981      $ticket = new XMLElement( $reply->Tag( 'ticketinfo', 'http://www.xythos.com/namespaces/StorageServer', 'TKT' ) );
982      $reply->NSElement($ticket, 'http://www.xythos.com/namespaces/StorageServer:id', $trow->ticket_id );
983      $reply->NSElement($ticket, 'http://www.xythos.com/namespaces/StorageServer:owner', $reply->href( ConstructURL($trow->principal_dav_name)) );
984      $reply->NSElement($ticket, 'http://www.xythos.com/namespaces/StorageServer:timeout', (isset($trow->seconds) ? sprintf( 'Seconds-%d', $trow->seconds) : 'infinity') );
985      $reply->NSElement($ticket, 'http://www.xythos.com/namespaces/StorageServer:visits', 'infinity' );
986      $privs = array();
987      foreach( bits_to_privilege(bindec($trow->privileges) & bindec($trow->grantor_collection_privileges) ) AS $k => $v ) {
988        $privs[] = $reply->NewXMLElement($v);
989      }
990      $reply->NSElement($ticket, 'DAV::privilege', $privs );
991      $tickets[] = $ticket;
992    }
993    return $tickets;
994  }
995
996
997  /**
998  * Checks whether the resource is locked, returning any lock token, or false
999  *
1000  * @todo This logic does not catch all locking scenarios.  For example an infinite
1001  * depth request should check the permissions for all collections and resources within
1002  * that.  At present we only maintain permissions on a per-collection basis though.
1003  */
1004  function IsLocked( $depth = 0 ) {
1005    if ( !isset($this->_locks_found) ) {
1006      $this->_locks_found = array();
1007      /**
1008      * Find the locks that might apply and load them into an array
1009      */
1010      $sql = 'SELECT * FROM locks WHERE :this_path::text ~ (\'^\'||dav_name||:match_end)::text';
1011      $qry = new AwlQuery($sql, array( ':this_path' => $this->dav_name, ':match_end' => ($depth == DEPTH_INFINITY ? '' : '$') ) );
1012      if ( $qry->Exec('DAVResource',__LINE__,__FILE__) ) {
1013        while( $lock_row = $qry->Fetch() ) {
1014          $this->_locks_found[$lock_row->opaquelocktoken] = $lock_row;
1015        }
1016      }
1017      else {
1018        $this->DoResponse(500,i18n("Database Error"));
1019        // Does not return.
1020      }
1021    }
1022
1023    foreach( $this->_locks_found AS $lock_token => $lock_row ) {
1024      if ( $lock_row->depth == DEPTH_INFINITY || $lock_row->dav_name == $this->dav_name ) {
1025        return $lock_token;
1026      }
1027    }
1028
1029    return false;  // Nothing matched
1030  }
1031
1032
1033  /**
1034  * Checks whether this resource is a collection
1035  */
1036  function IsCollection() {
1037    return $this->_is_collection;
1038  }
1039
1040
1041  /**
1042  * Checks whether this resource is a principal
1043  */
1044  function IsPrincipal() {
1045    return $this->_is_collection && $this->_is_principal;
1046  }
1047
1048
1049  /**
1050  * Checks whether this resource is a calendar
1051  */
1052  function IsCalendar() {
1053    return $this->_is_collection && $this->_is_calendar;
1054  }
1055
1056
1057  /**
1058  * Checks whether this resource is a calendar
1059  * @param string $type The type of scheduling collection, 'read', 'write' or 'any'
1060  */
1061  function IsSchedulingCollection( $type = 'any' ) {
1062    if ( $this->_is_collection && preg_match( '{schedule-(inbox|outbox)}', $this->collection->type, $matches ) ) {
1063      return ($type == 'any' || $type == $matches[1]);
1064    }
1065    return false;
1066  }
1067
1068
1069  /**
1070  * Checks whether this resource is an addressbook
1071  */
1072  function IsAddressbook() {
1073    return $this->_is_collection && $this->_is_addressbook;
1074  }
1075
1076
1077  /**
1078  * Checks whether this resource is a bind to another resource
1079  */
1080  function IsBinding() {
1081    return $this->_is_binding;
1082  }
1083
1084
1085  /**
1086  * Checks whether this resource actually exists, in the virtual sense, within the hierarchy
1087  */
1088  function Exists() {
1089    if ( ! isset($this->exists) ) {
1090      if ( $this->IsPrincipal() ) {
1091        if ( !isset($this->principal) ) $this->FetchPrincipal();
1092        $this->exists = $this->principal->Exists();
1093      }
1094      else if ( ! $this->IsCollection() ) {
1095        if ( !isset($this->resource) ) $this->FetchResource();
1096      }
1097    }
1098    //dbg_error_log('DAVResource',' Checking whether "%s" exists.  It would appear %s.', $this->dav_name, ($this->exists ? 'so' : 'not') );
1099    return $this->exists;
1100  }
1101
1102
1103  /**
1104  * Checks whether the container for this resource actually exists, in the virtual sense, within the hierarchy
1105  */
1106  function ContainerExists() {
1107    if ( $this->collection->dav_name != $this->dav_name ) {
1108      return $this->collection->exists;
1109    }
1110    $parent = $this->FetchParentContainer();
1111    return $parent->Exists();
1112  }
1113
1114
1115  /**
1116  * Returns the dav_name of the resource in our internal namespace
1117  */
1118  function dav_name() {
1119    if ( isset($this->dav_name) ) return $this->dav_name;
1120    return null;
1121  }
1122
1123
1124  /**
1125  * Returns the dav_name of the resource we are bound to, within our internal namespace
1126  */
1127  function bound_from() {
1128    if ( isset($this->bound_from) ) return $this->bound_from;
1129    return $this->dav_name();
1130  }
1131
1132
1133  /**
1134  * Sets the dav_name of the resource we are bound as
1135  */
1136  function set_bind_location( $new_dav_name ) {
1137    if ( !isset($this->bound_from) && isset($this->dav_name) ) {
1138      $this->bound_from = $this->dav_name;
1139    }
1140    $this->dav_name = $new_dav_name;
1141    return $this->dav_name;
1142  }
1143
1144
1145  /**
1146  * Returns the dav_name of the resource in our internal namespace
1147  */
1148  function parent_path() {
1149    if ( $this->IsCollection() ) {
1150      if ( !isset($this->collection) ) $this->FetchCollection();
1151      if ( !isset($this->collection->parent_container) ) {
1152        $this->collection->parent_container = preg_replace( '{[^/]+/$}', '', $this->bound_from());
1153      }
1154      return $this->collection->parent_container;
1155    }
1156    return preg_replace( '{[^/]+$}', '', $this->bound_from());
1157  }
1158
1159
1160
1161  /**
1162  * Returns the principal-URL for this resource
1163  */
1164  function principal_url() {
1165    if ( !isset($this->principal) ) $this->FetchPrincipal();
1166    if ( $this->principal->Exists() ) {
1167      return $this->principal->principal_url;
1168    }
1169    return null;
1170  }
1171
1172
1173  /**
1174  * Returns the database row for this resource
1175  */
1176  function resource() {
1177    if ( !isset($this->resource) ) $this->FetchResource();
1178    return $this->resource;
1179  }
1180
1181
1182  /**
1183  * Returns the unique_tag (ETag ) for this resource
1184  */
1185  function unique_tag() {
1186    if ( isset($this->unique_tag) ) return $this->unique_tag;
1187    if ( isset($this->unique_tag) )
1188             {
1189         $tag = md5(Date("U"));
1190         return $tag;
1191         }   
1192    if ( $this->IsPrincipal() && !isset($this->principal) ) $this->FetchPrincipal();
1193    else if ( !$this->_is_collection && !isset($this->resource) ) $this->FetchResource();
1194
1195    if ( $this->exists !== true || !isset($this->unique_tag) ) $this->unique_tag = '';
1196     
1197     $tag = md5(Date("U"));
1198     return $tag;
1199
1200     //return $this->unique_tag;
1201  }
1202  /**
1203  * Returns the unique_tag (getctag) for this resource
1204  */
1205  function unique_ctag() {
1206    //if ( isset($this->unique_tag) ) return $this->unique_tag;
1207    if ( isset($this->unique_tag) )
1208             {
1209         $tag = md5(Date("U"));
1210         return $tag;
1211         }   
1212    if ( $this->IsPrincipal() && !isset($this->principal) ) $this->FetchPrincipal();
1213    else if ( !$this->_is_collection && !isset($this->resource) ) $this->FetchResource();
1214
1215    if ( $this->exists !== true || !isset($this->unique_tag) ) $this->unique_tag = '';
1216     
1217     $tag = md5(Date("U"));
1218     return $tag;
1219
1220     //return $this->unique_tag;
1221  }
1222
1223
1224  /**
1225  * Returns the definitive resource_id for this resource - usually a dav_id
1226  */
1227  function resource_id() {
1228    if ( isset($this->resource_id) ) return $this->resource_id;
1229    if ( $this->IsPrincipal() && !isset($this->principal) ) $this->FetchPrincipal();
1230    else if ( !$this->_is_collection && !isset($this->resource) ) $this->FetchResource();
1231
1232    if ( $this->exists !== true || !isset($this->resource_id) ) $this->resource_id = null;
1233
1234    return $this->resource_id;
1235  }
1236
1237
1238  /**
1239  * Checks whether the target collection is publicly_readable
1240  */
1241  function IsPublic() {
1242    return ( isset($this->collection->publicly_readable) && $this->collection->publicly_readable == 't' );
1243  }
1244
1245
1246  /**
1247  * Return the type of whatever contains this resource, or would if it existed.
1248  */
1249  function ContainerType() {
1250    if ( $this->IsPrincipal() ) return 'root';
1251    if ( !$this->IsCollection() ) return $this->collection->type;
1252
1253    if ( ! isset($this->collection->parent_container) ) return null;
1254
1255    if ( isset($this->parent_container_type) ) return $this->parent_container_type;
1256
1257    if ( preg_match('#/[^/]+/#', $this->collection->parent_container) ) {
1258      $this->parent_container_type = 'principal';
1259    }
1260    else {
1261      $qry = new AwlQuery('SELECT * FROM collection WHERE dav_name = :parent_name',
1262                                array( ':parent_name' => $this->collection->parent_container ) );
1263      if ( $qry->Exec('DAVResource') && $qry->rows() > 0 && $parent = $qry->Fetch() ) {
1264        if ( $parent->is_calendar == 't' )
1265          $this->parent_container_type = 'calendar';
1266        else if ( $parent->is_addressbook == 't' )
1267          $this->parent_container_type = 'addressbook';
1268        else if ( preg_match( '#^((/[^/]+/)\.(in|out)/)[^/]*$#', $this->dav_name, $matches ) )
1269          $this->parent_container_type = 'schedule-'. $matches[3]. 'box';
1270        else
1271          $this->parent_container_type = 'collection';
1272      }
1273      else
1274        $this->parent_container_type = null;
1275    }
1276    return $this->parent_container_type;
1277  }
1278
1279
1280  /**
1281  * BuildACE - construct an XMLElement subtree for a DAV::ace
1282  */
1283  function BuildACE( &$xmldoc, $privs, $principal ) {
1284    $privilege_names = bits_to_privilege($privs, ($this->_is_collection ? $this->collection->type : 'resource'));
1285    $privileges = array();
1286    foreach( $privilege_names AS $k ) {
1287      $privilege = new XMLElement('privilege');
1288      if ( isset($xmldoc) )
1289        $xmldoc->NSElement($privilege,$k);
1290      else
1291        $privilege->NewElement($k);
1292      $privileges[] = $privilege;
1293    }
1294    $ace = new XMLElement('ace', array(
1295                new XMLElement('principal', $principal),
1296                new XMLElement('grant', $privileges ) )
1297              );
1298    return $ace;
1299  }
1300
1301  /**
1302  * Return ACL settings
1303  */
1304  function GetACL( &$xmldoc ) {
1305    global $c, $session;
1306
1307    if ( !isset($this->principal) ) $this->FetchPrincipal();
1308    $default_privs = $this->principal->default_privileges;
1309    if ( isset($this->collection->default_privileges) ) $default_privs = $this->collection->default_privileges;
1310
1311    $acl = array();
1312    $acl[] = $this->BuildACE($xmldoc, pow(2,25) - 1, new XMLElement('property', new XMLElement('owner')) );
1313
1314    $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',
1315                                array( ':collection_id' => $this->collection->collection_id, ':principal_id' => $this->principal->principal_id ) );
1316    if ( $qry->Exec('DAVResource') && $qry->rows() > 0 ) {
1317      $by_collection = null;
1318      while( $grant = $qry->Fetch() ) {
1319        if ( !isset($by_collection) ) $by_collection = isset($grant->by_collection);
1320        if ( $by_collection &&  !isset($grant->by_collection) ) break;
1321        $acl[] = $this->BuildACE($xmldoc, $grant->privileges, $xmldoc->href(ConstructURL($grant->dav_name)) );
1322      }
1323    }
1324
1325    $acl[] = $this->BuildACE($xmldoc, $default_privs, new XMLElement('authenticated') );
1326
1327    return $acl;
1328
1329  }
1330
1331
1332  /**
1333  * Return general server-related properties, in plain form
1334  */
1335  function GetProperty( $name ) {
1336    global $c, $session;
1337
1338    //dbg_error_log( 'DAVResource', ':GetProperty: Fetching "%s".', $name );
1339    $value = null;
1340
1341    switch( $name ) {
1342      case 'collection_id':
1343        return $this->collection->collection_id;
1344        break;
1345
1346      case 'resourcetype':
1347        if ( isset($this->resourcetypes) ) {
1348          $this->resourcetypes = preg_replace('{^\s*<(.*)/>\s*$}', '$1', $this->resourcetypes);
1349          $type_list = preg_split('{(/>\s*<|\n)}', $this->resourcetypes);
1350          foreach( $type_list AS $k => $resourcetype ) {
1351            if ( preg_match( '{^([^:]+):([^:]+) \s+ xmlns:([^=]+)="([^"]+)" \s* $}x', $resourcetype, $matches ) ) {
1352              $type_list[$k] = $matches[4] .':' .$matches[2];
1353            }
1354            else if ( preg_match( '{^([^:]+) \s+ xmlns="([^"]+)" \s* $}x', $resourcetype, $matches ) ) {
1355              $type_list[$k] = $matches[2] .':' .$matches[1];
1356            }
1357          }
1358          return $type_list;
1359        }
1360
1361      default:
1362        if ( isset($this->{$name}) ) return $this->{$name};
1363        if ( $this->_is_principal ) {
1364          if ( !isset($this->principal) ) $this->FetchPrincipal();
1365          if ( isset($this->principal->{$name}) ) return $this->principal->{$name};
1366          if ( isset($this->collection->{$name}) ) return $this->collection->{$name};
1367        }
1368        else if ( $this->_is_collection ) {
1369          if ( isset($this->collection->{$name}) ) return $this->collection->{$name};
1370          if ( isset($this->principal->{$name}) ) return $this->principal->{$name};
1371        }
1372        else {
1373          if ( !isset($this->resource) ) $this->FetchResource();
1374          if ( isset($this->resource->{$name}) ) return $this->resource->{$name};
1375          if ( !isset($this->principal) ) $this->FetchPrincipal();
1376          if ( isset($this->principal->{$name}) ) return $this->principal->{$name};
1377          if ( isset($this->collection->{$name}) ) return $this->collection->{$name};
1378        }
1379      //  dbg_error_log( 'DAVResource', ':GetProperty: Failed to find property "%s" on "%s".', $name, $this->dav_name );
1380    }
1381
1382    return $value;
1383  }
1384
1385
1386  /**
1387  * Return an array which is an expansion of the DAV::allprop
1388  */
1389  function DAV_AllProperties() {
1390    if ( isset($this->dead_properties) ) $this->FetchDeadProperties();
1391    $allprop = array_merge( (isset($this->dead_properties)?$this->dead_properties:array()),
1392      (isset($include_properties)?$include_properties:array()),
1393      array(
1394        'DAV::getcontenttype', 'DAV::resourcetype', 'DAV::getcontentlength', 'DAV::displayname', 'DAV::getlastmodified',
1395        'DAV::creationdate', 'DAV::getetag', 'DAV::getcontentlanguage', 'DAV::supportedlock', 'DAV::lockdiscovery',
1396        'DAV::owner', 'DAV::principal-URL', 'DAV::current-user-principal',
1397        'urn:ietf:params:xml:ns:carddav:max-resource-size', 'urn:ietf:params:xml:ns:carddav:supported-address-data',
1398        'urn:ietf:params:xml:ns:carddav:addressbook-description', 'urn:ietf:params:xml:ns:carddav:addressbook-home-set'
1399      ) );
1400
1401    return $allprop;
1402  }
1403
1404
1405  /**
1406  * Return general server-related properties for this URL
1407  */
1408  function ResourceProperty( $tag, $prop, &$reply, &$denied ) {
1409    global $c, $session, $request;
1410
1411//    dbg_error_log( 'DAVResource', 'Processing "%s" on "%s".', $tag, $this->dav_name );
1412
1413    if ( $reply === null ) $reply = $GLOBALS['reply'];
1414
1415    switch( $tag ) {
1416      case 'DAV::allprop':
1417        $property_list = $this->DAV_AllProperties();
1418        $discarded = array();
1419        foreach( $property_list AS $k => $v ) {
1420          $this->ResourceProperty($v, $prop, $reply, $discarded);
1421        }
1422        break;
1423
1424      case 'DAV::href':
1425        $prop->NewElement('href', ConstructURL($this->dav_name) );
1426        break;
1427
1428      case 'DAV::resource-id':
1429        if ( $this->resource_id > 0 )
1430          $reply->DAVElement( $prop, 'resource-id', $reply->href(ConstructURL('/.resources/'.$this->resource_id) ) );
1431        else
1432          return false;
1433        break;
1434
1435      case 'DAV::parent-set':
1436        $sql = <<<EOQRY
1437SELECT b.parent_container FROM dav_binding b JOIN collection c ON (b.bound_source_id=c.collection_id)
1438 WHERE regexp_replace( b.dav_name, '^.*/', c.dav_name ) = :bound_from
1439EOQRY;
1440        $qry = new AwlQuery($sql, array( ':bound_from' => $this->bound_from() ) );
1441        $parents = array();
1442        if ( $qry->Exec('DAVResource',__LINE__,__FILE__) && $qry->rows() > 0 ) {
1443          while( $row = $qry->Fetch() ) {
1444            $parents[$row->parent_container] = true;
1445          }
1446        }
1447        $parents[preg_replace( '{(?<=/)[^/]+/?$}','',$this->bound_from())] = true;
1448        $parents[preg_replace( '{(?<=/)[^/]+/?$}','',$this->dav_name())] = true;
1449
1450        $parent_set = $reply->DAVElement( $prop, 'parent-set' );
1451        foreach( $parents AS $parent => $v ) {
1452          if ( preg_match( '{^(.*)?/([^/]+)/?$}', $parent, $matches ) ) {
1453            $reply->DAVElement($parent_set, 'parent', array(
1454                                new XMLElement( 'href', ConstructURL($matches[1])),
1455                                new XMLElement( 'segment', $matches[2])
1456                              ));
1457          }
1458          else if ( $parent == '/' ) {
1459            $reply->DAVElement($parent_set, 'parent', array(
1460                                new XMLElement( 'href', '/'),
1461                                new XMLElement( 'segment', ( ConstructURL('/') == '/caldav.php/' ? 'caldav.php' : ''))
1462                              ));
1463          }
1464        }
1465        break;
1466
1467      case 'DAV::getcontenttype':
1468        if ( !isset($this->contenttype) && !$this->_is_collection && !isset($this->resource) ) $this->FetchResource();
1469        //if ( !isset($this->contenttype)  && !isset($this->resource) ) $this->FetchResource();
1470        $prop->NewElement('getcontenttype', $this->contenttype );
1471        break;
1472
1473      case 'DAV::resourcetype':
1474        $resourcetypes = $prop->NewElement('resourcetype' );
1475        $type_list = $this->GetProperty('resourcetype');
1476        if ( !is_array($type_list) ) return true;
1477//        dbg_error_log( 'DAVResource', ':ResourceProperty: "%s" are "%s".', $tag, implode(', ',$type_list) );
1478        foreach( $type_list AS $k => $v ) {
1479          if ( $v == '' ) continue;
1480          $reply->NSElement( $resourcetypes, $v );
1481        }
1482        if ( $this->_is_binding ) {
1483          $reply->NSElement( $resourcetypes, 'http://xmlns.davical.org/davical:webdav-binding' );
1484        }
1485        break;
1486
1487      case 'DAV::getlastmodified':
1488        /** peculiarly, it seems that getlastmodified is HTTP Date format! */
1489        $reply->NSElement($prop, $tag, ISODateToHTTPDate($this->GetProperty('modified')) );
1490        break;
1491
1492      case 'DAV::creationdate':
1493        /** bizarrely, it seems that creationdate is ISO8601 format */
1494        $reply->NSElement($prop, $tag, DateToISODate($this->GetProperty('created')) );
1495        break;
1496
1497      case 'DAV::getcontentlength':
1498        if ( $this->_is_collection ) return false;
1499        if ( !isset($this->resource) ) $this->FetchResource();
1500        if ( isset($this->resource) ) {
1501          $reply->NSElement($prop, $tag, strlen($this->resource->caldav_data) );
1502        }
1503        break;
1504
1505      case 'DAV::getcontentlanguage':
1506        $locale = (isset($c->current_locale) ? $c->current_locale : '');
1507        if ( isset($this->locale) && $this->locale != '' ) $locale = $this->locale;
1508        $reply->NSElement($prop, $tag, $locale );
1509        break;
1510
1511      case 'DAV::acl-restrictions':
1512        $reply->NSElement($prop, $tag, array( new XMLElement('grant-only'), new XMLElement('no-invert') ) );
1513        break;
1514
1515      case 'DAV::inherited-acl-set':
1516        $inherited_acls = array();
1517        if ( ! $this->_is_collection ) {
1518          $inherited_acls[] = $reply->href(ConstructURL($this->collection->dav_name));
1519        }
1520        $reply->NSElement($prop, $tag, $inherited_acls );
1521        break;
1522
1523      case 'DAV::owner':
1524        // After a careful reading of RFC3744 we see that this must be the principal-URL of the owner
1525        $owner_url = ( isset($this->_is_binding) && $this->_is_binding ? $this->collection->bind_owner_url : $this->principal_url() );
1526        $reply->DAVElement( $prop, 'owner', $reply->href( $owner_url ) );
1527        break;
1528
1529      // Empty tag responses.
1530      case 'DAV::group':
1531      case 'DAV::alternate-URI-set':
1532        $reply->NSElement($prop, $tag );
1533        break;
1534
1535      case 'DAV::getetag':
1536        if ( $this->_is_collection ) return false;
1537        $reply->NSElement($prop, $tag, $this->unique_tag() );
1538        break;
1539
1540      case 'http://calendarserver.org/ns/:getctag':
1541        if ( ! $this->_is_collection ) return false;
1542        $reply->NSElement($prop, $tag, $this->unique_ctag() );
1543        break;
1544
1545      case 'http://calendarserver.org/ns/:calendar-proxy-read-for':
1546        $proxy_type = 'read';
1547      case 'http://calendarserver.org/ns/:calendar-proxy-write-for':
1548        if ( !isset($proxy_type) ) $proxy_type = 'write';
1549        $reply->CalendarserverElement($prop, 'calendar-proxy-'.$proxy_type.'-for', $reply->href( $this->principal->ProxyFor($proxy_type) ) );
1550        break;
1551
1552      case 'DAV::current-user-privilege-set':
1553        if ( $this->HavePrivilegeTo('DAV::read-current-user-privilege-set') ) {
1554        //if(true){   
1555          $reply->NSElement($prop, $tag, $this->BuildPrivileges() );
1556        }
1557        else {
1558          $denied[] = $tag;
1559        }
1560        break;
1561
1562      case 'urn:ietf:params:xml:ns:caldav:supported-calendar-data':
1563        if ( ! $this->IsCalendar() && ! $this->IsSchedulingCollection() ) return false;
1564        $reply->NSElement($prop, $tag, 'text/calendar' );
1565        break;
1566
1567      case 'urn:ietf:params:xml:ns:caldav:supported-calendar-component-set':
1568        if ( ! $this->_is_collection ) return false;
1569        if ( $this->IsCalendar() )
1570          $set_of_components = array( 'VEVENT', 'VTODO', 'VJOURNAL', 'VTIMEZONE', 'VFREEBUSY' );
1571        else if ( $this->IsSchedulingCollection() )
1572          $set_of_components = array( 'VEVENT', 'VTODO', 'VFREEBUSY' );
1573        else return false;
1574        $components = array();
1575        foreach( $set_of_components AS $v ) {
1576          $components[] = $reply->NewXMLElement( 'comp', '', array('name' => $v), 'urn:ietf:params:xml:ns:caldav');
1577        }
1578        $reply->CalDAVElement($prop, 'supported-calendar-component-set', $components );
1579        break;
1580
1581      case 'DAV::supported-method-set':
1582        $prop->NewElement('supported-method-set', $this->BuildSupportedMethods() );
1583        break;
1584
1585      case 'DAV::supported-report-set':
1586        $prop->NewElement('supported-report-set', $this->BuildSupportedReports( $reply ) );
1587        break;
1588
1589      case 'DAV::supportedlock':
1590        $prop->NewElement('supportedlock',
1591          new XMLElement( 'lockentry',
1592            array(
1593              new XMLElement('lockscope', new XMLElement('exclusive')),
1594              new XMLElement('locktype',  new XMLElement('write')),
1595            )
1596          )
1597        );
1598        break;
1599
1600      case 'DAV::supported-privilege-set':
1601        $prop->NewElement('supported-privilege-set', $request->BuildSupportedPrivileges($reply) );
1602        break;
1603
1604      case 'DAV::current-user-principal':
1605        $prop->NewElement('current-user-principal', $reply->href( $request->principal->principal_url ) );
1606        break;
1607
1608      case 'SOME-DENIED-PROPERTY':  /** @todo indicating the style for future expansion */
1609        $denied[] = $reply->Tag($tag);
1610        break;
1611
1612      case 'urn:ietf:params:xml:ns:caldav:calendar-timezone':
1613        if ( ! $this->_is_collection ) return false;
1614        if ( !isset($this->collection->tz_spec) || $this->collection->tz_spec == '' ) return false;
1615
1616        $cal = new iCalComponent();
1617        $cal->VCalendar();
1618        $cal->AddComponent( new iCalComponent($this->collection->tz_spec) );
1619        $reply->NSElement($prop, $tag, $cal->Render() );
1620        break;
1621
1622      case 'urn:ietf:params:xml:ns:carddav:address-data':
1623      case 'urn:ietf:params:xml:ns:caldav:calendar-data':
1624        if ( $this->_is_collection ) return false;
1625        if ( !isset($this->resource) ) $this->FetchResource();
1626        $reply->NSElement($prop, $tag, $this->resource->caldav_data );
1627        break;
1628
1629      case 'urn:ietf:params:xml:ns:carddav:max-resource-size':
1630        if ( ! $this->_is_collection || !$this->_is_addressbook ) return false;
1631        $reply->NSElement($prop, $tag, 65500 );
1632        break;
1633
1634      case 'urn:ietf:params:xml:ns:carddav:supported-address-data':
1635        if ( ! $this->_is_collection || !$this->_is_addressbook ) return false;
1636        $address_data = $reply->NewXMLElement( 'address-data', false,
1637                      array( 'content-type' => 'text/vcard', 'version' => '3.0'), 'urn:ietf:params:xml:ns:carddav');
1638        $reply->NSElement($prop, $tag, $address_data );
1639        break;
1640
1641      case 'DAV::acl':
1642        if ( $this->HavePrivilegeTo('DAV::read-acl') ) {
1643          $reply->NSElement($prop, $tag, $this->GetACL( $reply ) );
1644        }
1645        else {
1646          $denied[] = $tag;
1647        }
1648        break;
1649
1650      case 'http://www.xythos.com/namespaces/StorageServer:ticketdiscovery':
1651      case 'DAV::ticketdiscovery':
1652        $reply->NSElement($prop,'http://www.xythos.com/namespaces/StorageServer:ticketdiscovery', $this->BuildTicketinfo($reply) );
1653        break;
1654
1655      default:
1656        $property_value = $this->GetProperty(preg_replace('{^.*:}', '', $tag));
1657        if ( isset($property_value) ) {
1658          $reply->NSElement($prop, $tag, $property_value );
1659        }
1660        else {
1661          if ( !isset($this->dead_properties) ) $this->FetchDeadProperties();
1662          if ( isset($this->dead_properties[$tag]) ) {
1663            $reply->NSElement($prop, $tag, $this->dead_properties[$tag] );
1664          }
1665          else {
1666//            dbg_error_log( 'DAVResource', 'Request for unsupported property "%s" of path "%s".', $tag, $this->dav_name );
1667            return false;
1668          }
1669        }
1670    }
1671
1672    return true;
1673  }
1674
1675
1676  /**
1677  * Construct XML propstat fragment for this resource
1678  *
1679  * @param array of string $properties The requested properties for this resource
1680  *
1681  * @return string An XML fragment with the requested properties for this resource
1682  */
1683  function GetPropStat( $properties, &$reply, $props_only = false ) {
1684    global $request;
1685
1686    dbg_error_log('DAVResource',':GetPropStat: propstat for href "%s"', $this->dav_name );
1687
1688    $prop = new XMLElement('prop');
1689    $denied = array();
1690    $not_found = array();
1691    foreach( $properties AS $k => $tag ) {
1692      if ( is_object($tag) ) {
1693        dbg_error_log( 'DAVResource', ':GetPropStat: "$properties" should be an array of text. Assuming this object is an XMLElement!.' );
1694        $tag = $tag->GetTag();
1695      }
1696      $found = $this->ResourceProperty($tag, $prop, $reply, $denied );
1697      if ( !$found ) {
1698        if ( !isset($this->principal) ) $this->FetchPrincipal();
1699        $found = $this->principal->PrincipalProperty( $tag, $prop, $reply, $denied );
1700      }
1701      if ( ! $found ) {
1702        dbg_error_log( 'DAVResource', 'Request for unsupported property "%s" of resource "%s".', $tag, $this->dav_name );
1703        $not_found[] = $reply->Tag($tag);
1704      }
1705    }
1706    if ( $props_only ) return $prop;
1707
1708    $status = new XMLElement('status', 'HTTP/1.1 200 OK' );
1709
1710    $elements = array( new XMLElement( 'propstat', array($prop,$status) ) );
1711
1712    if ( count($denied) > 0 ) {
1713      $status = new XMLElement('status', 'HTTP/1.1 403 Forbidden' );
1714      $noprop = new XMLElement('prop');
1715      foreach( $denied AS $k => $v ) {
1716        $reply->NSElement($noprop, $v);
1717      }
1718      $elements[] = new XMLElement( 'propstat', array( $noprop, $status) );
1719    }
1720
1721    if ( count($not_found) > 0 ) {
1722      $status = new XMLElement('status', 'HTTP/1.1 404 Not Found' );
1723      $noprop = new XMLElement('prop');
1724      foreach( $not_found AS $k => $v ) {
1725        $noprop->NewElement($v);
1726      }
1727      $elements[] = new XMLElement( 'propstat', array( $noprop, $status) );
1728    }
1729    return $elements;
1730  }
1731
1732
1733  /**
1734  * Render XML for this resource
1735  *
1736  * @param array $properties The requested properties for this principal
1737  * @param reference $reply A reference to the XMLDocument being used for the reply
1738  *
1739  * @return string An XML fragment with the requested properties for this principal
1740  */
1741  function RenderAsXML( $properties, &$reply, $bound_parent_path = null ) {
1742    global $session, $c, $request;
1743
1744    dbg_error_log('DAVResource',':RenderAsXML: Resource "%s" exists(%d)', $this->dav_name, $this->Exists() );
1745
1746    if ( !$this->Exists() ) return null;
1747    $elements = $this->GetPropStat( $properties, $reply );
1748    if ( isset($bound_parent_path) ) {
1749      $dav_name = str_replace( $this->parent_path(), $bound_parent_path, $this->dav_name );
1750      $dav_name = "/".$this->dav_name;   
1751     }
1752    else {
1753      $dav_name =  "/".$this->dav_name;
1754    }
1755
1756    array_unshift( $elements, $reply->href(ConstructURL($dav_name)));
1757
1758    $response = new XMLElement( 'response', $elements );
1759
1760    return $response;
1761  }
1762
1763}
Note: See TracBrowser for help on using the repository browser.