Logo Search packages:      
Sourcecode: davical version File versions  Download package

caldav-PROPFIND.php

<?php
/**
* CalDAV Server - handle PROPFIND method
*
* @package   davical
* @subpackage   propfind
* @author    Andrew McMillan <andrew@catalyst.net.nz>
* @copyright Catalyst .Net Ltd, Andrew McMillan
* @license   http://gnu.org/copyleft/gpl.html GNU GPL v2 or later
*/
dbg_error_log("PROPFIND", "method handler");

if ( ! ($request->AllowedTo('read') || $request->AllowedTo('freebusy')) ) {
  $request->DoResponse( 403, translate("You may not access that calendar") );
}

require_once("iCalendar.php");
require_once("XMLDocument.php");

$href_list = array();
$prop_list = array();
$unsupported = array();
$arbitrary = array();

$reply = new XMLDocument( array( "DAV:" => "" ) );

foreach( $request->xml_tags AS $k => $v ) {

  $ns_tag = $v['tag'];
  dbg_error_log( "PROPFIND", " Handling Tag '%s' => '%s' ", $k, $ns_tag );

  switch ( $ns_tag ) {
    case 'DAV::propfind':
    case 'DAV::prop':
      dbg_error_log( "PROPFIND", ":Request: %s -> %s", $v['type'], $ns_tag );
      break;

    case 'DAV::href':
      $href_list[] = $v['value'];
      dbg_error_log( "PROPFIND", "Adding href '%s'", $v['value'] );
      break;


    /**
    * Handled DAV properties
    */
    case 'DAV::acl':                            /** Only vaguely supported as yet.  Will need work.*/
    case 'DAV::creationdate':                   /** should work fine */
    case 'DAV::getlastmodified':                /** should work fine */
    case 'DAV::displayname':                    /** should work fine */
    case 'DAV::getcontentlength':               /** should work fine */
    case 'DAV::getcontenttype':                 /** should work fine */
    case 'DAV::getetag':                        /** should work fine */
    case 'DAV::supportedlock':                  /** should work fine */
    case 'DAV::principal-URL':                  /** should work fine */
    case 'DAV::owner':                          /** should work fine */
    case 'DAV::resourcetype':                   /** should work fine */
    case 'DAV::current-user-principal':         /** only vaguely supported */
    case 'DAV::getcontentlanguage':             /** should return the user's chosen locale, or default locale */
    case 'DAV::current-user-privilege-set':     /** only vaguely supported */
    case 'DAV::allprop':                        /** limited support, needs to be checked for correctness at some point */

    /**
    * Handled CalDAV properties
    */
    case 'urn:ietf:params:xml:ns:caldav:calendar-home-set':                /** Should work fine */
    case 'urn:ietf:params:xml:ns:caldav:calendar-user-address-set':        /** Should work fine */
    case 'urn:ietf:params:xml:ns:caldav:schedule-inbox-URL':               /** Support in development */
    case 'urn:ietf:params:xml:ns:caldav:schedule-outbox-URL':              /** Support in development */
    case 'urn:ietf:params:xml:ns:caldav:calendar-free-busy-set':           /** Deprecated, but should work fine */

    /**
    * Handled calendarserver properties
    */
    case 'http://calendarserver.org/ns/:getctag':                        /** Calendar Server extension like etag - should work fine (we just return etag) */

      $prop_list[$ns_tag] = $ns_tag;
      dbg_error_log( "PROPFIND", "Adding attribute '%s'", $ns_tag );
      break;

    /** fixed server definitions - should work fine */
    case 'DAV::supported-collation-set':
    case 'DAV::supported-calendar-component-set':
    case 'DAV::principal-collection-set':
    case 'DAV::supported-privilege-set':

//    case 'dropbox-home-URL':   // HTTP://CALENDARSERVER.ORG/NS/
//    case 'notifications-URL':  // HTTP://CALENDARSERVER.ORG/NS/
      if ( $_SERVER['PATH_INFO'] == '/' || $_SERVER['PATH_INFO'] == '' ) {
        $arbitrary[$ns_tag] = $ns_tag;
        dbg_error_log( "PROPFIND", "Adding arbitrary DAV property '%s'", $ns_tag );
      }
      else {
        $prop_list[$ns_tag] = $ns_tag;
        dbg_error_log( "PROPFIND", "Adding attribute '%s'", $ns_tag );
      }
      break;


    case 'urn:ietf:params:xml:ns:caldav:calendar-timezone':           // Ignored
    case 'urn:ietf:params:xml:ns:caldav:supported-calendar-data':     // Ignored
    case 'urn:ietf:params:xml:ns:caldav:max-resource-size':           // Ignored - should be a server setting
    case 'urn:ietf:params:xml:ns:caldav:min-date-time':               // Ignored - should be a server setting
    case 'urn:ietf:params:xml:ns:caldav:max-date-time':               // Ignored - should be a server setting
    case 'urn:ietf:params:xml:ns:caldav:max-instances':               // Ignored - should be a server setting
    case 'urn:ietf:params:xml:ns:caldav:max-attendees-per-instance':  // Ignored - should be a server setting
      /** These are ignored specifically */
      break;

    /**
    * Add the ones that are specifically unsupported here.
    */
//    case 'DAV::checked-out':   // DAV:
//    case 'DAV::checked-in':    // DAV:
//    case 'DAV::source':        // DAV:
//    case 'DAV::lockdiscovery': // DAV:
//    case 'http://apache.org/dav/props/:executable':         //
    case 'This is not a supported property':  // an impossible example
      $unsupported[$ns_tag] = "";
      dbg_error_log( "PROPFIND", "Unsupported tag >>%s<< ", $ns_tag);
      break;

    /**
    * Arbitrary DAV properties may also be reported
    */
    case 'urn:ietf:params:xml:ns:caldav:calendar-description':        // Supported purely as an arbitrary property
    default:
      $arbitrary[$ns_tag] = $ns_tag;
      dbg_error_log( "PROPFIND", "Adding arbitrary property '%s'", $ns_tag );
      break;
  }
}


/**
* Returns the array of privilege names converted into XMLElements
*/
function privileges($privilege_names, $container="privilege") {
  global $reply;
  $privileges = array();
  foreach( $privilege_names AS $k => $v ) {
    $privileges[] = $reply->NewXMLElement($container, $reply->NewXMLElement($k));
  }
  return $privileges;
}


/**
* Adds any arbitrary properties that were requested by the PROPFIND into the response.
*
* @param reference $prop An XMLElement of the partly constructed response
* @param reference $denied An XMLElement of properties to which access is denied
* @param object $record A record with a dav_name attribute which the properties apply to
*/
function add_arbitrary_properties(&$prop, $record) {
  global $arbitrary, $reply;

  if ( count($arbitrary) > 0 ) {
    $sql = "";
    foreach( $arbitrary AS $k => $v ) {
      $sql .= ($sql == "" ? "" : ", ") . qpg($k);
    }
    $qry = new PgQuery("SELECT property_name, property_value FROM property WHERE dav_name=? AND property_name IN ($sql)", $record->dav_name );
    while( $qry->Exec("PROPFIND") && $property = $qry->Fetch() ) {
      $reply->NSElement($prop, $property->property_name, $property->property_value);
    }
  }
}


/**
* Handles any properties related to the DAV::PRINCIPAL in the request
*/
function add_principal_properties( &$prop, &$denied ) {
  global $prop_list, $session, $c, $request, $reply;

  $allprop = isset($prop_list['DAV::allprop']);

  if ( isset($prop_list['DAV::principal-URL'] ) ) {
    $reply->DAVElement( $prop, "principal-URL", $reply->href( $request->principal->url ) );
  }
  if ( isset($prop_list['DAV::alternate-URI-set'] ) ) {
    $reply->DAVElement( $prop, "alternate-URI-set" );  // Empty - there are no alternatives!
  }

  if ( isset($prop_list['urn:ietf:params:xml:ns:caldav:calendar-home-set'] ) ) {
    $home_set = array();
    $chs = $request->principal->calendar_home_set;
    foreach( $chs AS $k => $url ) {
      $home_set[] = $reply->href( $url );
    }
    $reply->CalDAVElement( $prop, "calendar-home-set", $home_set );
  }
  if ( isset($prop_list['urn:ietf:params:xml:ns:caldav:schedule-inbox-URL'] ) ) {
    $reply->CalDAVElement( $prop, "schedule-inbox-URL", $reply->href( $request->principal->schedule_inbox_url) );
  }
  if ( isset($prop_list['urn:ietf:params:xml:ns:caldav:schedule-outbox-URL'] ) ) {
    $reply->CalDAVElement( $prop, "schedule-outbox-URL", $reply->href( $request->principal->schedule_outbox_url) );
  }

  if ( isset($prop_list['http://calendarserver.org/ns/:dropbox-home-URL'] ) ) {
    $reply->CalendarserverElement($prop, "dropbox-home-URL", $reply->href( $request->principal->dropbox_url) );
  }
  if ( isset($prop_list['http://calendarserver.org/ns/:notifications-URL'] ) ) {
    $reply->CalendarserverElement($prop, "notifications-URL", $reply->href( $request->principal->notifications_url) );
  }

  if ( isset($prop_list['urn:ietf:params:xml:ns:caldav:calendar-user-address-set'] ) ) {
    $addr_set = array();
    $uas = $request->principal->user_address_set;
    foreach( $uas AS $k => $v ) {
      $addr_set[] = $reply->href( $v );
    }
    $reply->CalDAVElement( $prop, "calendar-user-address-set", $addr_set );
  }
}


/**
* Handles any properties related to the DAV::PRINCIPAL in the request
*/
function add_general_properties( &$prop, &$denied, $record ) {
  global $prop_list, $session, $c, $request, $reply;

  $allprop = isset($prop_list['DAV::allprop']);

  if ( $allprop || isset($prop_list['DAV::getlastmodified']) ) {
    $reply->DAVElement( $prop, 'getlastmodified', ( isset($record->modified)? $record->modified : false ));
  }
  if ( $allprop || isset($prop_list['DAV::creationdate']) ) {
    $reply->DAVElement( $prop, "creationdate", $record->created );
  }
  if ( $allprop || isset($prop_list['DAV::getetag']) ) {
    $reply->DAVElement( $prop, "getetag", '"'.$record->dav_etag.'"' );
  }

  if ( isset($prop_list['DAV::owner']) ) {
    // After a careful reading of RFC3744 we see that this must be the principal-URL of the owner
    $reply->DAVElement( $prop, "owner", $reply->href( $request->principal->url ) );
  }
  if ( isset($prop_list['DAV::principal-collection-set']) ) {
    $reply->DAVElement( $prop, "principal-collection-set", $reply->href( ConstructURL('/') ) );
  }
  if ( isset($prop_list['DAV::current-user-principal']) ) {
    $reply->DAVElement( $prop, "current-user-principal", $request->current_user_principal_xml);
  }

  if ( isset($prop_list['DAV::acl']) ) {
    /**
    * @todo This information is semantically valid but presents an incorrect picture.
    */
    $principal = $reply->NewXMLElement("principal");
    $reply->DAVElement( $principal, "authenticated");
    $grant = $reply->NewXMLElement( "grant", array(privileges($request->permissions)) );
    $reply->DAVElement( $prop, "acl", $reply->NewXMLElement( "ace", array( $principal, $grant ) ) );
  }

  if ( $allprop || isset($prop_list['DAV::getcontentlanguage']) ) {
    $reply->DAVElement( $prop, "getcontentlanguage", (isset($c->current_locale) ? $c->current_locale : "") );
  }

  if ( isset($prop_list['DAV::supportedlock']) ) {
    $reply->DAVElement( $prop, "supportedlock",
       $reply->NewXMLElement( "lockentry",
         array(
           $reply->NewXMLElement("lockscope", $reply->NewXMLElement("exclusive")),
           $reply->NewXMLElement("locktype",  $reply->NewXMLElement("write")),
         )
       )
     );
  }

  if ( isset($prop_list['DAV::current-user-privilege-set']) ) {
    $reply->DAVElement( $prop, "current-user-privilege-set", privileges($request->permissions) );
  }

  if ( isset($prop_list['DAV::supported-privilege-set']) ) {
    $reply->DAVElement( $prop, "supported-privilege-set", privileges( $request->SupportedPrivileges(), "supported-privilege") );
  }

}


/**
* Build the <propstat><prop></prop><status></status></propstat> part of the response
*/
function build_propstat_response( $prop, $denied, $url ) {
  global $reply, $arbitrary, $prop_list;

  $response = array( $reply->href($url),
                     $reply->NewXMLElement( "propstat", array( $prop,
                                                               $reply->NewXMLElement("status", "HTTP/1.1 200 OK" )
                                                              )
                                           )
                    );

  $missed = array_merge($prop_list, $arbitrary);
  if ( isset($missed['DAV::allprop']) ) unset($missed['DAV::allprop']);
  foreach( $prop->content AS $k => $v ) {
    if ( isset($missed[$v->GetNSTag()]) ) unset($missed[$v->GetNSTag()]);
  }
  if ( isset($denied->content) && is_array($denied->content) ) {
    foreach( $denied->content AS $k => $v ) {
      if ( isset($missed[$v->GetNSTag()]) ) unset($missed[$v->GetNSTag()]);
    }
  }
  if ( count($missed) > 0 ) {
    $not_found = $reply->NewXMLElement("prop", false, false, 'DAV:');
    foreach( $missed AS $tag => $v ) {
      $reply->NSElement($not_found, $tag);
    }
    $response[] = $reply->NewXMLElement( "propstat",
                                         array( $not_found,
                                                $reply->NewXMLElement("status", "HTTP/1.1 404 Not Found", false, 'DAV:' )
                                              ), false, 'DAV:'
                                        );
  }

  if ( is_array($denied->content) && count($denied->content) > 0 ) {
    $response[] = $reply->NewXMLElement( "propstat",
                                         array( $denied,
                                                $reply->NewXMLElement("status", "HTTP/1.1 403 Forbidden", false, 'DAV:' )
                                              ), false, 'DAV:'
                                        );
  }

  $response = $reply->NewXMLElement( "response", $response, false, 'DAV:' );

  return $response;
}


/**
* Returns an XML sub-tree for a single collection record from the DB
*/
function collection_to_xml( $collection ) {
  global $arbitrary, $prop_list, $session, $c, $request, $reply;

  dbg_error_log("PROPFIND","Building XML Response for collection '%s'", $collection->dav_name );

  $allprop = isset($prop_list['DAV::allprop']);

  $url = ConstructURL($collection->dav_name);

  $prop = $reply->NewXMLElement( "prop", false, false, 'DAV:');
  $denied = $reply->NewXMLElement( "prop", false, false, 'DAV:');

  $collection->type = ($collection->is_calendar == 't' ? 'calendar' : ($collection->is_principal == 't' ? 'principal' : '') );
  if ( preg_match( '#^((/[^/]+/)\.(in|out)/)[^/]*$#', $collection->dav_name, $matches ) ) {
    $collection->type = $matches[3];
  }

  /**
  * First process any static values we do support
  */
  if ( isset($prop_list['urn:ietf:params:xml:ns:caldav:supported-collation-set']) ) {
    $collations = array();
    $collations[] = $reply->NewXMLElement($reply->Caldav("supported-collation"), 'i;ascii-casemap');
    $collations[] = $reply->NewXMLElement($reply->Caldav("supported-collation"), 'i;octet');
    $prop->NewElement($reply->Caldav("supported-collation-set"), $collations );
  }
  if ( isset($prop_list['urn:ietf:params:xml:ns:caldav:supported-calendar-component-set']) ) {
    $components = array();
    $components[] = $reply->NewXMLElement( "comp", '', array("name" => "VEVENT"), false, false,'urn:ietf:params:xml:ns:caldav');
    $components[] = $reply->NewXMLElement( "comp", '', array("name" => "VTODO"), false, false,'urn:ietf:params:xml:ns:caldav');
    $reply->CalDAVElement($prop, "supported-calendar-component-set", $components );
  }
  if ( $allprop || isset($prop_list['DAV::getcontenttype']) ) {
    $reply->DAVElement( $prop, "getcontenttype", "httpd/unix-directory" );  // Strictly text/icalendar perhaps
  }

  /**
  * Process any dynamic values we do support
  */
  if ( $allprop || isset($prop_list['DAV::getcontentlength'])
                || isset($prop_list['DAV::resourcetype']) ) {
    $resourcetypes = array( $reply->NewXMLElement("collection", false, false, 'DAV:') );
    $contentlength = false;
    if ( $collection->is_calendar == 't' ) {
      $resourcetypes[] = $reply->NewXMLElement( "calendar", false, false,'urn:ietf:params:xml:ns:caldav');
      $resourcetypes[] = $reply->NewXMLElement( "schedule-calendar", false, false, 'urn:ietf:params:xml:ns:caldav' );
      $lqry = new PgQuery("SELECT sum(length(caldav_data)) FROM caldav_data WHERE user_no = ? AND dav_name ~ ?;", $collection->user_no, $collection->dav_name.'[^/]+$' );
      if ( $lqry->Exec("PROPFIND",__LINE__,__FILE__) && $row = $lqry->Fetch() ) {
        $contentlength = $row->sum;
      }
    }
    else if ( $collection->type == 'in' ) {
      $resourcetypes[] = $reply->NewXMLElement( "schedule-inbox", false, false, 'urn:ietf:params:xml:ns:caldav');
    }
    else if ( $collection->type == 'out' ) {
      $resourcetypes[] = $reply->NewXMLElement( "schedule-outbox", false, false, 'urn:ietf:params:xml:ns:caldav');
    }
    if ( $collection->is_principal == 't' ) {
      $resourcetypes[] = $reply->NewXMLElement( "principal", false, false, 'DAV:');
    }
    if ( $allprop || isset($prop_list['DAV::getcontentlength']) ) {
      $reply->DAVElement( $prop, "getcontentlength", $contentlength );  // Not strictly correct as a GET on this URL would be longer
    }
    if ( $allprop || isset($prop_list['DAV::resourcetype']) ) {
      $reply->DAVElement( $prop, "resourcetype", $resourcetypes );
    }
  }

  if ( $allprop || isset($prop_list['DAV::displayname']) ) {
    $displayname = ( $collection->dav_displayname == "" ? ucfirst(trim(str_replace("/"," ", $collection->dav_name))) : $collection->dav_displayname );
    $reply->DAVElement( $prop, "displayname", $displayname );
  }
  if ( isset($prop_list['http://calendarserver.org/ns/:getctag']) ) {
    // Calendar Server extension which only applies to collections.  We return the etag, which does the needful.
    $reply->CalendarserverElement($prop, 'getctag', '"'.$collection->dav_etag.'"');
  }

  if ( isset($prop_list['urn:ietf:params:xml:ns:caldav:calendar-free-busy-set'] ) ) {
    if ( $collection->type == 'in' && $session->user_no == $collection->user_no ) {
      $fb_set = array();
      foreach( $request->principal->calendar_free_busy_set AS $k => $v ) {
        $fb_set[] = $reply->href( $v, false, "DAV:" );
      }
      $reply->CalDAVElement( $prop, "calendar-free-busy-set", $fb_set );
    }
    else if ( $session->user_no != $collection->user_no ) {
      $reply->CalDAVElement( $denied, "calendar-free-busy-set");
    }
  }

  /**
  * Then look at any properties related to the principal
  */
  add_principal_properties( $prop, $denied );

  /**
  * And any properties that are server/request related, or standard fields
  * from our query.
  */
  add_general_properties( $prop, $denied, $collection );

  /**
  * Arbitrary collection properties
  */
  add_arbitrary_properties($prop, $collection);

  return build_propstat_response( $prop, $denied, $url );
}


/**
* Return XML for a single data item from the DB
*/
function item_to_xml( $item ) {
  global $prop_list, $session, $c, $request, $reply;

  dbg_error_log("PROPFIND","Building XML Response for item '%s'", $item->dav_name );

  $allprop = isset($prop_list['DAV::allprop']);

  $url = ConstructURL($item->dav_name);

  $prop = $reply->NewXMLElement("prop", false, false, 'DAV:');
  $denied  = $reply->NewXMLElement("prop", false, false, 'DAV:');


  if ( $allprop || isset($prop_list['DAV::getcontentlength']) ) {
    $contentlength = strlen($item->caldav_data);
    $reply->DAVElement( $prop, "getcontentlength", $contentlength );
  }
  if ( $allprop || isset($prop_list['DAV::getcontenttype']) ) {
    $reply->DAVElement( $prop, "getcontenttype", "text/calendar" );
  }
  if ( $allprop || isset($prop_list['DAV::displayname']) ) {
    $reply->DAVElement( $prop, "displayname", $item->dav_displayname );
  }

  /**
  * Non-collections should return an empty resource type, it appears from RFC2518 8.1.2
  */
  if ( $allprop || isset($prop_list['DAV::resourcetype']) ) {
    $reply->DAVElement( $prop, "resourcetype");
  }

  /**
  * Then look at any properties related to the principal
  */
  add_principal_properties( $prop, $denied );

  /**
  * And any properties that are server/request related.
  */
  add_general_properties( $prop, $denied, $item );

  add_arbitrary_properties($prop, $item );

  return build_propstat_response( $prop, $denied, $url );
}

/**
* Get XML response for items in the collection
* If '/' is requested, a list of visible users is given, otherwise
* a list of calendars for the user which are parented by this path.
*/
function get_collection_contents( $depth, $user_no, $collection ) {
  global $session, $request, $reply, $prop_list;

  dbg_error_log("PROPFIND","Getting collection contents: Depth %d, User: %d, Path: %s", $depth, $user_no, $collection->dav_name );

  $responses = array();
  if ( $collection->is_calendar != 't' ) {
    /**
    * Calendar collections may not contain calendar collections.
    */
    if ( $collection->dav_name == '/' ) {
      $sql = "SELECT usr.*, '/' || username || '/' AS dav_name, md5( '/' || username || '/') AS dav_etag, ";
      $sql .= "to_char(updated at time zone 'GMT',?) AS created, ";
      $sql .= "to_char(updated at time zone 'GMT',?) AS modified, ";
      $sql .= "fullname AS dav_displayname, FALSE AS is_calendar, TRUE AS is_principal FROM usr ";
      $sql .= "WHERE get_permissions($session->user_no,user_no) ~ '[RAW]';";
    }
    else {
      $sql = "SELECT user_no, dav_name, dav_etag, ";
      $sql .= "to_char(created at time zone 'GMT',?) AS created, ";
      $sql .= "to_char(modified at time zone 'GMT',?) AS modified, ";
      $sql .= "dav_displayname, is_calendar, FALSE AS is_principal FROM collection ";
      $sql .= "WHERE parent_container=".qpg($collection->dav_name);
    }
    $qry = new PgQuery($sql, PgQuery::Plain(iCalendar::HttpDateFormat()), PgQuery::Plain(iCalendar::HttpDateFormat()));

    if( $qry->Exec("PROPFIND",__LINE__,__FILE__) && $qry->rows > 0 ) {
      while( $subcollection = $qry->Fetch() ) {
        if ( $subcollection->is_principal == "t" ) {
          $principal = new CalDAVPrincipal($subcollection);
          $responses[] = $principal->RenderAsXML($prop_list, &$reply);
        }
        else {
          $responses[] = collection_to_xml( $subcollection );
        }
        if ( $depth > 0 ) {
          $responses = array_merge( $responses, get_collection_contents( $depth - 1,  $user_no, $subcollection ) );
        }
      }
    }
  }

  /**
  * freebusy permission is not allowed to see the items in a collection.  Must have at least read permission.
  */
  if ( $request->AllowedTo('read') ) {
    dbg_error_log("PROPFIND","Getting collection items: Depth %d, User: %d, Path: %s", $depth, $user_no, $collection->dav_name );
    $privacy_clause = " ";
    if ( ! $request->AllowedTo('all') ) {
      $privacy_clause = " AND (calendar_item.class != 'PRIVATE' OR calendar_item.class IS NULL) ";
    }

    $sql = "SELECT caldav_data.dav_name, caldav_data, caldav_data.dav_etag, ";
    $sql .= "to_char(coalesce(calendar_item.created, caldav_data.created) at time zone 'GMT',?) AS created, ";
    $sql .= "to_char(last_modified at time zone 'GMT',?) AS modified, ";
    $sql .= "summary AS dav_displayname ";
    $sql .= "FROM caldav_data JOIN calendar_item USING( dav_id, user_no, dav_name) ";
    $sql .= "WHERE dav_name ~ ".qpg('^'.$collection->dav_name.'[^/]+$'). $privacy_clause;
    if ( isset($c->strict_result_ordering) && $c->strict_result_ordering ) $sql .= " ORDER BY dav_id";
    $qry = new PgQuery($sql, PgQuery::Plain(iCalendar::HttpDateFormat()), PgQuery::Plain(iCalendar::HttpDateFormat()));
    if( $qry->Exec("PROPFIND",__LINE__,__FILE__) && $qry->rows > 0 ) {
      while( $item = $qry->Fetch() ) {
        $responses[] = item_to_xml( $item );
      }
    }
  }

  return $responses;
}


/**
* Get XML response for a single collection.  If Depth is >0 then
* subsidiary collections will also be got up to $depth
*/
function get_collection( $depth, $user_no, $collection_path ) {
  global $c, $request;
  $responses = array();

  dbg_error_log("PROPFIND","Getting collection: Depth %d, User: %d, Path: %s", $depth, $user_no, $collection_path );

  if ( $collection_path == null || $collection_path == '/' || $collection_path == '' ) {
    $collection->dav_name = $collection_path;
    $collection->dav_etag = md5($c->system_name . $collection_path);
    $collection->is_calendar = 'f';
    $collection->is_principal = 'f';
    $collection->user_no = 0;
    $collection->dav_displayname = $c->system_name;
    $collection->created = date('Ymd\THis');
    $responses[] = collection_to_xml( $collection );
  }
  else {
    $user_no = intval($user_no);
    if ( preg_match( '#^/[^/]+/$#', $collection_path) ) {
      $sql = "SELECT user_no, '/' || username || '/' AS dav_name, md5( '/' || username || '/') AS dav_etag, ";
      $sql .= "to_char(updated at time zone 'GMT',?) AS created, ";
      $sql .= "to_char(updated at time zone 'GMT',?) AS modified, ";
      $sql .= "fullname AS dav_displayname, FALSE AS is_calendar, TRUE AS is_principal FROM usr WHERE user_no = $user_no ; ";
    }
    else {
      $sql = "SELECT user_no, dav_name, dav_etag, ";
      $sql .= "to_char(created at time zone 'GMT',?) AS created, ";
      $sql .= "to_char(modified at time zone 'GMT',?) AS modified, ";
      $sql .= "dav_displayname, is_calendar, FALSE AS is_principal FROM collection WHERE user_no = $user_no AND dav_name = ".qpg($collection_path);
    }
    $qry = new PgQuery($sql, PgQuery::Plain(iCalendar::HttpDateFormat()), PgQuery::Plain(iCalendar::HttpDateFormat()) );
    if( $qry->Exec("PROPFIND",__LINE__,__FILE__) && $qry->rows > 0 && $collection = $qry->Fetch() ) {
      $responses[] = collection_to_xml( $collection );
    }
    elseif ( $c->collections_always_exist ) {
      dbg_error_log("PROPFIND","Using $c->collections_always_exist setting is deprecated" );
      $collection->dav_name = $collection_path;
      $collection->dav_etag = md5($collection_path);
      $collection->is_calendar = 't';  // Everything is a calendar, if it always exists!
      $collection->is_principal = 'f';
      $collection->user_no = $user_no;
      $collection->dav_displayname = $collection_path;
      $collection->created = date('Ymd"T"His');
      $responses[] = collection_to_xml( $collection );
    }
  }
  if ( $depth > 0 && isset($collection) ) {
    $responses = array_merge($responses, get_collection_contents( $depth-1,  $user_no, $collection ) );
  }
  return $responses;
}


/**
* Get XML response for a single item.  Depth is irrelevant for this.
*/
function get_item( $item_path ) {
  global $session, $request;
  $responses = array();

  dbg_error_log("PROPFIND","Getting item: Path: %s", $item_path );

  $privacy_clause = " ";
  if ( ! $request->AllowedTo('all') ) {
    $privacy_clause = " AND (calendar_item.class != 'PRIVATE' OR calendar_item.class IS NULL) ";
  }

  $sql = "SELECT caldav_data.dav_name, caldav_data, caldav_data.dav_etag, ";
  $sql .= "to_char(coalesce(calendar_item.created, caldav_data.created) at time zone 'GMT',?) AS created, ";
  $sql .= "to_char(last_modified at time zone 'GMT',?) AS modified, ";
  $sql .= "summary AS dav_displayname ";
  $sql .= "FROM caldav_data JOIN calendar_item USING( user_no, dav_name)  WHERE dav_name = ? $privacy_clause";
  $qry = new PgQuery($sql, PgQuery::Plain(iCalendar::HttpDateFormat()), PgQuery::Plain(iCalendar::HttpDateFormat()), $item_path);
  if( $qry->Exec("PROPFIND",__LINE__,__FILE__) && $qry->rows > 0 ) {
    while( $item = $qry->Fetch() ) {
      $responses[] = item_to_xml( $item );
    }
  }
  return $responses;
}


$request->UnsupportedRequest($unsupported); // Won't return if there was unsupported stuff.

/**
* Something that we can handle, at least roughly correctly.
*/
$url = ConstructURL( $request->path );
$url = preg_replace( '#/$#', '', $url);
if ( $request->IsCollection() ) {
  $responses = get_collection( $request->depth, $request->user_no, $request->path );
}
elseif ( $request->AllowedTo('read') ) {
  $responses = get_item( $request->path );
}
else {
  $request->DoResponse( 403, translate("You do not have appropriate rights to view that resource.") );
}

$xmldoc = $reply->Render("multistatus", $responses);
$etag = md5($xmldoc);
header("ETag: \"$etag\"");
$request->DoResponse( 207, $xmldoc, 'text/xml; charset="utf-8"' );


Generated by  Doxygen 1.6.0   Back to index