<?php /** * CalDAV Server - handle PUT method * * @package davical * @subpackage caldav * @author Andrew McMillan <*****@*****.**> * @copyright Catalyst .Net Ltd, Morphoss Ltd * @license http://gnu.org/copyleft/gpl.html GNU GPL v2 or later */ dbg_error_log("PUT", "method handler"); require_once 'DAVResource.php'; $dav_resource = new DAVResource($request->path); if (!$dav_resource->HavePrivilegeTo('DAV::write-content')) { $request->DoResponse(403); } if (!$dav_resource->Exists() && !$dav_resource->HavePrivilegeTo('DAV::bind')) { $request->DoResponse(403); } if (!ini_get('open_basedir') && (isset($c->dbg['ALL']) || isset($c->dbg['put']) && $c->dbg['put'])) { $fh = fopen('/tmp/PUT.txt', 'w'); if ($fh) { fwrite($fh, $request->raw_post); fclose($fh); } } include_once 'caldav-PUT-functions.php'; controlRequestContainer($dav_resource->GetProperty('username'), $dav_resource->GetProperty('user_no'), $dav_resource->bound_from(), true); $lock_opener = $request->FailIfLocked(); if ($dav_resource->IsCollection()) {
/** * 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, $collection, $parent_path = null) { global $c, $session, $request, $reply, $property_list; $bound_from = $collection->bound_from(); $bound_to = $collection->dav_name(); if (!isset($parent_path)) { $parent_path = $collection->dav_name(); } dbg_error_log('PROPFIND', 'Getting collection contents: Depth %d, Path: %s, Bound from: %s, Bound to: %s', $depth, $collection->dav_name(), $bound_from, $bound_to); $date_format = AwlDatabase::HttpDateFormat; $responses = array(); if (!$collection->IsCalendar() && !$collection->IsAddressbook()) { /** * Calendar/Addressbook collections may not contain collections, so we are only looking in the other ones */ $params = array(':session_principal' => $session->principal_id, ':scan_depth' => $c->permission_scan_depth); if ($bound_from == '/') { $sql = "SELECT usr.*, '/' || username || '/' AS dav_name, md5(username || updated::text) AS dav_etag, "; $sql .= "to_char(joined at time zone 'GMT',{$date_format}) AS created, "; $sql .= "to_char(updated at time zone 'GMT',{$date_format}) AS modified, "; $sql .= 'FALSE AS is_calendar, TRUE AS is_principal, FALSE AS is_addressbook, \'principal\' AS type, '; $sql .= 'principal_id AS collection_id, '; $sql .= 'principal.* '; $sql .= 'FROM usr JOIN principal USING (user_no) '; $sql .= "WHERE (pprivs(:session_principal::int8,principal.principal_id,:scan_depth::int) & 1::BIT(24))::INT4::BOOLEAN "; $sql .= 'ORDER BY usr.user_no'; } else { $qry = new AwlQuery('SELECT * FROM dav_binding WHERE dav_binding.parent_container = :this_dav_name ORDER BY bind_id', array(':this_dav_name' => $bound_from)); if ($qry->Exec('PROPFIND', __LINE__, __FILE__) && $qry->rows() > 0) { while ($binding = $qry->Fetch()) { $resource = new DAVResource($binding->dav_name); if ($resource->IsExternal()) { require_once "external-fetch.php"; update_external($resource); } if ($resource->HavePrivilegeTo('DAV::read', false)) { $resource->set_bind_location(str_replace($bound_from, $bound_to, $binding->dav_name)); $responses[] = $resource->RenderAsXML($property_list, $reply); if ($depth > 0) { $responses = array_merge($responses, get_collection_contents($depth - 1, $resource, $binding->dav_name)); } } } } $sql = 'SELECT principal.*, collection.*, \'collection\' AS type '; $sql .= 'FROM collection LEFT JOIN principal USING (user_no) '; $sql .= 'WHERE parent_container = :this_dav_name '; $sql .= ' ORDER BY collection_id'; $params[':this_dav_name'] = $bound_from; unset($params[':session_principal']); unset($params[':scan_depth']); } $qry = new AwlQuery($sql, $params); if ($qry->Exec('PROPFIND', __LINE__, __FILE__) && $qry->rows() > 0) { while ($subcollection = $qry->Fetch()) { $resource = new DAVResource($subcollection); if (!$resource->HavePrivilegeTo('DAV::read')) { continue; } $resource->set_bind_location(str_replace($bound_from, $bound_to, $subcollection->dav_name)); $responses[] = $resource->RenderAsXML($property_list, $reply); if ($depth > 0) { $responses = array_merge($responses, get_collection_contents($depth - 1, $resource, str_replace($resource->parent_path(), $parent_path, $resource->dav_name()))); } } } if ((!isset($c->disable_caldav_proxy) || $c->disable_caldav_proxy == false) && $collection->IsPrincipal()) { // Caldav Proxy: 5.1 par. 2: Add child resources calendar-proxy-(read|write) dbg_error_log('PROPFIND', 'Adding calendar-proxy-read and write. Path: %s', $bound_from); $response = add_proxy_response('read', $bound_from); if (isset($response)) { $responses[] = $response; } $response = add_proxy_response('write', $bound_from); if (isset($response)) { $responses[] = $response; } } } /** * freebusy permission is not allowed to see the items in a collection. Must have at least read permission. */ if ($collection->HavePrivilegeTo('DAV::read', false)) { dbg_error_log('PROPFIND', 'Getting collection items: Depth %d, Path: %s', $depth, $bound_from); $privacy_clause = ' '; $todo_clause = ' '; $time_limit_clause = ' '; if ($collection->IsCalendar()) { if (!$collection->HavePrivilegeTo('all', false)) { $privacy_clause = " AND (calendar_item.class != 'PRIVATE' OR calendar_item.class IS NULL) "; } if (isset($c->hide_TODO) && ($c->hide_TODO === true || is_string($c->hide_TODO) && preg_match($c->hide_TODO, $_SERVER['HTTP_USER_AGENT'])) && !$collection->HavePrivilegeTo('all')) { $todo_clause = " AND caldav_data.caldav_type NOT IN ('VTODO') "; } if (isset($c->hide_older_than) && intval($c->hide_older_than > 0)) { $time_limit_clause = " AND (CASE WHEN caldav_data.caldav_type<>'VEVENT' OR calendar_item.dtstart IS NULL THEN true ELSE calendar_item.dtstart > (now() - interval '" . intval($c->hide_older_than) . " days') END) "; } } $sql = 'SELECT collection.*, principal.*, calendar_item.*, caldav_data.*, '; $sql .= "to_char(coalesce(calendar_item.created, caldav_data.created) at time zone 'GMT',{$date_format}) AS created, "; $sql .= "to_char(coalesce(calendar_item.last_modified, caldav_data.modified) at time zone 'GMT',{$date_format}) AS modified, "; $sql .= 'summary AS dav_displayname '; $sql .= 'FROM caldav_data LEFT JOIN calendar_item USING( dav_id, user_no, dav_name, collection_id) '; $sql .= 'LEFT JOIN collection USING(collection_id,user_no) LEFT JOIN principal USING(user_no) '; $sql .= 'WHERE collection.dav_name = :collection_dav_name ' . $time_limit_clause . ' ' . $todo_clause . ' ' . $privacy_clause; if (isset($c->strict_result_ordering) && $c->strict_result_ordering) { $sql .= " ORDER BY caldav_data.dav_id"; } $qry = new AwlQuery($sql, array(':collection_dav_name' => $bound_from)); if ($qry->Exec('PROPFIND', __LINE__, __FILE__) && $qry->rows() > 0) { while ($item = $qry->Fetch()) { if ($bound_from != $bound_to) { $item->bound_from = $item->dav_name; $item->dav_name = str_replace($bound_from, $bound_to, $item->dav_name); } $resource = new DAVResource($item); $responses[] = $resource->RenderAsXML($property_list, $reply, $parent_path); } } } return $responses; }
if (!$qry->QDo($sql, $params) || $qry->rows() <= 0) { $request->DoResponse(500, translate("Database error")); } $row = $qry->Fetch(); } $new_token = $row->new_sync_token; if ($sync_token == $new_token) { // No change, so we just re-send the old token. $responses[] = new XMLElement('sync-token', 'data:,' . $new_token); } else { $hide_older = ''; if (isset($c->hide_older_than) && intval($c->hide_older_than) > 0) { $hide_older = " AND (CASE WHEN caldav_data.caldav_type<>'VEVENT' OR calendar_item.dtstart IS NULL THEN true ELSE calendar_item.dtstart > (now() - interval '" . intval($c->hide_older_than) . " days') END)"; } $hide_todo = ''; if (isset($c->hide_TODO) && ($c->hide_TODO === true || is_string($c->hide_TODO) && preg_match($c->hide_TODO, $_SERVER['HTTP_USER_AGENT'])) && !$collection->HavePrivilegeTo('all')) { $hide_todo = " AND caldav_data.caldav_type NOT IN ('VTODO') "; } if ($sync_token == 0) { $sql = <<<EOSQL SELECT collection.*, calendar_item.*, caldav_data.*, addressbook_resource.*, 201 AS sync_status FROM collection LEFT JOIN caldav_data USING (collection_id) LEFT JOIN calendar_item USING (dav_id) LEFT JOIN addressbook_resource USING (dav_id) WHERE collection.collection_id = :collection_id {$hide_older} {$hide_todo} ORDER BY collection.collection_id, caldav_data.dav_id EOSQL; unset($params[':sync_token']); } else { $sql = <<<EOSQL SELECT collection.*, calendar_item.*, caldav_data.*, addressbook_resource.*, sync_changes.*
/** Work out which ones are actually used here */ $comps = $ical->GetComponents('VTIMEZONE', false); foreach ($comps as $k => $comp) { $tzid = $comp->GetPParamValue('DTSTART', 'TZID'); if (isset($tzid) && !isset($need_zones[$tzid])) { $need_zones[$tzid] = 1; } $tzid = $comp->GetPParamValue('DUE', 'TZID'); if (isset($tzid) && !isset($need_zones[$tzid])) { $need_zones[$tzid] = 1; } $tzid = $comp->GetPParamValue('DTEND', 'TZID'); if (isset($tzid) && !isset($need_zones[$tzid])) { $need_zones[$tzid] = 1; } if ($dav_resource->HavePrivilegeTo('all', false) || $session->user_no == $event->user_no || $session->user_no == $event->logged_user || $c->allow_get_email_visibility && $comp->IsAttendee($session->email)) { /** * These people get to see all of the event, and they should always * get any alarms as well. */ $vcal->AddComponent($comp); continue; } /** No visibility even of the existence of these events if they aren't admin/owner/attendee */ if ($event->class == 'PRIVATE') { continue; } if (!$dav_resource->HavePrivilegeTo('DAV::read') || $event->class == 'CONFIDENTIAL') { $vcal->AddComponent(obfuscated_event($comp)); } elseif (isset($c->hide_alarm) && $c->hide_alarm) { // Otherwise we hide the alarms (if configured to)
function export_iCalendar(DAVResource $dav_resource) { global $session, $c, $request; if (!$dav_resource->IsCalendar() && !(isset($c->get_includes_subcollections) && $c->get_includes_subcollections)) { /** RFC2616 says we must send an Allow header if we send a 405 */ header("Allow: PROPFIND,PROPPATCH,OPTIONS,MKCOL,REPORT,DELETE"); $request->DoResponse(405, translate("GET requests on collections are only supported for calendars.")); } /** * The CalDAV specification does not define GET on a collection, but typically this is * used as a .ics download for the whole collection, which is what we do also. */ if (isset($c->get_includes_subcollections) && $c->get_includes_subcollections) { $where = 'caldav_data.collection_id IN '; $where .= '(SELECT bound_source_id FROM dav_binding WHERE dav_binding.dav_name ~ :path_match '; $where .= 'UNION '; $where .= 'SELECT collection_id FROM collection WHERE collection.dav_name ~ :path_match) '; $params = array(':path_match' => '^' . $dav_resource->dav_name()); $distinct = 'DISTINCT ON (calendar_item.uid) '; } else { $where = 'caldav_data.collection_id = :collection_id '; $params = array(':collection_id' => $dav_resource->resource_id()); $distinct = ''; } $sql = 'SELECT ' . $distinct . ' caldav_data, class, caldav_type, calendar_item.user_no, logged_user '; $sql .= 'FROM collection INNER JOIN caldav_data USING(collection_id) '; $sql .= 'INNER JOIN calendar_item USING ( dav_id ) WHERE ' . $where; if (isset($c->strict_result_ordering) && $c->strict_result_ordering) { $sql .= ' ORDER BY calendar_item.uid, calendar_item.dav_id'; } $qry = new AwlQuery($sql, $params); if (!$qry->Exec("GET", __LINE__, __FILE__)) { $request->DoResponse(500, translate("Database Error")); } /** * Here we are constructing a whole calendar response for this collection, including * the timezones that are referred to by the events we have selected. */ $vcal = new iCalComponent(); $vcal->VCalendar(); $displayname = $dav_resource->GetProperty('displayname'); if (isset($displayname)) { $vcal->AddProperty("X-WR-CALNAME", $displayname); } if (!empty($c->auto_refresh_duration)) { $vcal->AddProperty("X-APPLE-AUTO-REFRESH-INTERVAL", $c->auto_refresh_duration); $vcal->AddProperty("AUTO-REFRESH", $c->auto_refresh_duration); $vcal->AddProperty("X-PUBLISHED-TTL", $c->auto_refresh_duration); } $need_zones = array(); $timezones = array(); while ($event = $qry->Fetch()) { $ical = new iCalComponent($event->caldav_data); /** Save the timezone component(s) into a minimal set for inclusion later */ $event_zones = $ical->GetComponents('VTIMEZONE', true); foreach ($event_zones as $k => $tz) { $tzid = $tz->GetPValue('TZID'); if (!isset($tzid)) { continue; } if ($tzid != '' && !isset($timezones[$tzid])) { $timezones[$tzid] = $tz; } } /** Work out which ones are actually used here */ $comps = $ical->GetComponents('VTIMEZONE', false); foreach ($comps as $k => $comp) { $tzid = $comp->GetPParamValue('DTSTART', 'TZID'); if (isset($tzid) && !isset($need_zones[$tzid])) { $need_zones[$tzid] = 1; } $tzid = $comp->GetPParamValue('DUE', 'TZID'); if (isset($tzid) && !isset($need_zones[$tzid])) { $need_zones[$tzid] = 1; } $tzid = $comp->GetPParamValue('DTEND', 'TZID'); if (isset($tzid) && !isset($need_zones[$tzid])) { $need_zones[$tzid] = 1; } if ($dav_resource->HavePrivilegeTo('all', false) || $session->user_no == $event->user_no || $session->user_no == $event->logged_user || isset($session->email) && $c->allow_get_email_visibility && $comp->IsAttendee($session->email)) { /** * These people get to see all of the event, and they should always * get any alarms as well. */ $vcal->AddComponent($comp); continue; } /** No visibility even of the existence of these events if they aren't admin/owner/attendee */ if ($event->class == 'PRIVATE') { continue; } if (!$dav_resource->HavePrivilegeTo('DAV::read') || $event->class == 'CONFIDENTIAL') { $vcal->AddComponent(obfuscated_event($comp)); } elseif (isset($c->hide_alarm) && $c->hide_alarm) { // Otherwise we hide the alarms (if configured to) $comp->ClearComponents('VALARM'); $vcal->AddComponent($comp); } else { $vcal->AddComponent($comp); } } } /** Put the timezones on there that we need */ foreach ($need_zones as $tzid => $v) { if (isset($timezones[$tzid])) { $vcal->AddComponent($timezones[$tzid]); } } return $vcal->Render(); }
/** * Deliver scheduling replies to organizer and other attendees * @param vComponent $ical the VCALENDAR to deliver * @return false on error */ function handle_schedule_reply(vCalendar $ical) { global $c, $session, $request; $resources = $ical->GetComponents('VTIMEZONE', false); $ic = $resources[0]; $etag = md5($request->raw_post); $organizer = $ical->GetOrganizer(); // for now we treat events with out organizers as an error if (empty($organizer)) { return false; } $att = $ical->GetAttendees(); $attendees = array_merge($organizer, $att); dbg_error_log("PUT", "Attempting to deliver scheduling request for %d attendees", count($attendees)); foreach ($attendees as $k => $attendee) { $attendee_email = preg_replace('/^mailto:/i', '', $attendee->Value()); dbg_error_log("PUT", "Delivering to %s", $attendee_email); $attendee_principal = new DAVPrincipal(array('email' => $attendee_email, 'options' => array('allow_by_email' => true))); $deliver_path = $attendee_principal->internal_url('schedule_inbox'); $attendee_email = preg_replace('/^mailto:/i', '', $attendee->Value()); if ($attendee_email == $request->principal->email) { dbg_error_log("PUT", "not delivering to owner"); continue; } $ar = new DAVResource($deliver_path); if (!$ar->HavePrivilegeTo('schedule-deliver-reply')) { $reply = new XMLDocument(array('DAV:' => '')); $privnodes = array($reply->href($attendee_principal->url('schedule_inbox')), new XMLElement('privilege')); // RFC3744 specifies that we can only respond with one needed privilege, so we pick the first. $reply->NSElement($privnodes[1], 'schedule-deliver-reply'); $xml = new XMLElement('need-privileges', new XMLElement('resource', $privnodes)); $xmldoc = $reply->Render('error', $xml); $request->DoResponse(403, $xmldoc, 'text/xml; charset="utf-8"'); continue; } $ncal = new vCalendar(array('METHOD' => 'REPLY')); $ncal->AddComponent(array_merge($ical->GetComponents('VEVENT', false), array($ic))); $content = $ncal->Render(); write_resource(new DAVResource($deliver_path . $etag . '.ics'), $content, $ar, $request->user_no, md5($content), $put_action_type = 'INSERT', $caldav_context = true, $log_action = true, $etag); } $request->DoResponse(201, 'Created'); }
$where .= 'OR collection.collection_id IN (SELECT bound_source_id FROM dav_binding WHERE dav_binding.dav_name ~ :path_match)) '; $params = array(':path_match' => '^' . $target_collection->bound_from()); } if (is_array($qry_filters)) { dbg_log_array("calquery", "qry_filters", $qry_filters, true); $components = array(); $filter_fragment = SqlFilterFragment($qry_filters, $components); if ($filter_fragment !== false) { $where .= ' ' . $filter_fragment['sql']; $params = array_merge($params, $filter_fragment['params']); } } if ($target_collection->Privileges() != privilege_to_bits('DAV::all')) { $where .= " AND (calendar_item.class != 'PRIVATE' OR calendar_item.class IS NULL) "; } if (isset($c->hide_TODO) && $c->hide_TODO && !$target_collection->HavePrivilegeTo('DAV::write-content')) { $where .= " AND caldav_data.caldav_type NOT IN ('VTODO') "; } if (isset($c->hide_older_than) && intval($c->hide_older_than > 0)) { $where .= " AND calendar_item.dtstart > (now() - interval '" . intval($c->hide_older_than) . " days') "; } $sql = 'SELECT caldav_data.*,calendar_item.* FROM collection INNER JOIN caldav_data USING(collection_id) INNER JOIN calendar_item USING(dav_id) ' . $where; if (isset($c->strict_result_ordering) && $c->strict_result_ordering) { $sql .= " ORDER BY caldav_data.dav_id"; } $qry = new AwlQuery($sql, $params); if ($qry->Exec("calquery", __LINE__, __FILE__) && $qry->rows() > 0) { while ($calendar_object = $qry->Fetch()) { if (!$need_post_filter || apply_filter($qry_filters, $calendar_object)) { if ($bound_from != $target_collection->dav_name()) { $calendar_object->dav_name = str_replace($bound_from, $target_collection->dav_name(), $calendar_object->dav_name);
<?php /** * CalDAV Server - handle PROPPATCH method * * @package davical * @subpackage caldav * @author Andrew McMillan <*****@*****.**> * @copyright Morphoss Ltd - http://www.morphoss.com/ * @license http://gnu.org/copyleft/gpl.html GNU GPL v2 */ dbg_error_log("PROPPATCH", "method handler"); require_once 'iCalendar.php'; require_once 'DAVResource.php'; $dav_resource = new DAVResource($request->path); if (!($dav_resource->HavePrivilegeTo('DAV::write-properties') || $dav_resource->IsBinding())) { $request->DoResponse(403); } $position = 0; $xmltree = BuildXMLTree($request->xml_tags, $position); // echo $xmltree->Render(); if ($xmltree->GetTag() != "DAV::propertyupdate") { $request->DoResponse(403); } /** * Find the properties being set, and the properties being removed */ $setprops = $xmltree->GetPath("/DAV::propertyupdate/DAV::set/DAV::prop/*"); $rmprops = $xmltree->GetPath("/DAV::propertyupdate/DAV::remove/DAV::prop/*"); /** * We build full status responses for failures. For success we just record
<?php /** * CalDAV Server - handle PROPPATCH method * * @package davical * @subpackage caldav * @author Andrew McMillan <*****@*****.**> * @copyright Morphoss Ltd - http://www.morphoss.com/ * @license http://gnu.org/copyleft/gpl.html GNU GPL v2 */ dbg_error_log("PROPPATCH", "method handler"); require_once 'vCalendar.php'; require_once 'DAVResource.php'; $dav_resource = new DAVResource($request->path); if (!$dav_resource->HavePrivilegeTo('DAV::write-properties')) { $parent = $dav_resource->GetParentContainer(); if (!$dav_resource->IsBinding() || !$parent->HavePrivilegeTo('DAV::write')) { $request->PreconditionFailed(403, 'DAV::write-properties', 'You do not have permission to write properties to that resource'); } } $position = 0; $xmltree = BuildXMLTree($request->xml_tags, $position); // echo $xmltree->Render(); if ($xmltree->GetNSTag() != "DAV::propertyupdate") { $request->PreconditionFailed(403, 'DAV::propertyupdate', 'XML request did not contain a <propertyupdate> tag'); } /** * Find the properties being set, and the properties being removed */ $setprops = $xmltree->GetPath("/DAV::propertyupdate/DAV::set/DAV::prop/*");
/** * Deliver scheduling replies to organizer and other attendees * @param iCalComponent $ical the VCALENDAR to deliver * @return false on error */ function handle_schedule_reply($ical) { global $c, $session, $request; $resources = $ical->GetComponents('VTIMEZONE', false); $ic = $resources[0]; $etag = md5($request->raw_post); $organizer = $ic->GetProperties('ORGANIZER'); // for now we treat events with out organizers as an error if (count($organizer) < 1) { return false; } $attendees = array_merge($organizer, $ic->GetProperties('ATTENDEE')); $wr_attendees = $ic->GetProperties('X-WR-ATTENDEE'); if (count($wr_attendees) > 0) { dbg_error_log("POST", "Non-compliant iCal request. Using X-WR-ATTENDEE property"); foreach ($wr_attendees as $k => $v) { $attendees[] = $v; } } dbg_error_log("POST", "Attempting to deliver scheduling request for %d attendees", count($attendees)); foreach ($attendees as $k => $attendee) { $attendee_email = preg_replace('/^mailto:/', '', $attendee->Value()); dbg_error_log("POST", "Delivering to %s", $attendee_email); $attendee_principal = new CalDAVPrincipal(array('email' => $attendee_email, 'options' => array('allow_by_email' => true))); $deliver_path = preg_replace('/^.*caldav.php/', '', $attendee_principal->schedule_inbox_url); $attendee_email = preg_replace('/^mailto:/', '', $attendee->Value()); if ($attendee_email == $request->principal->email) { dbg_error_log("POST", "not delivering to owner"); continue; } $ar = new DAVResource($deliver_path); if (!$ar->HavePrivilegeTo('schedule-deliver-reply')) { $reply = new XMLDocument(array('DAV:' => '')); $privnodes = array($reply->href(ConstructURL($attendee_principal->schedule_inbox_url)), new XMLElement('privilege')); // RFC3744 specifies that we can only respond with one needed privilege, so we pick the first. $reply->NSElement($privnodes[1], 'schedule-deliver-reply'); $xml = new XMLElement('need-privileges', new XMLElement('resource', $privnodes)); $xmldoc = $reply->Render('error', $xml); $request->DoResponse(403, $xmldoc, 'text/xml; charset="utf-8"'); continue; } $ncal = new iCalComponent(); $ncal->VCalendar(); $ncal->AddProperty('METHOD', 'REPLY'); $ncal->AddComponent(array_merge($ical->GetComponents('VEVENT', false), array($ic))); $content = $ncal->Render(); write_resource($attendee_principal->user_no, $deliver_path . $etag . '.ics', $content, $ar->GetProperty('collection_id'), $request->user_no, md5($content), $ncal, $put_action_type = 'INSERT', $caldav_context = true, $log_action = true, $etag); } $request->DoResponse(201, 'Created'); }
/** * Return XML for a single component from the DB * * @param array $properties The properties for this component * @param string $item The DB row data for this component * * @return string An XML document which is the response for the component */ function component_to_xml($properties, $item) { global $session, $c, $request, $reply; dbg_error_log("REPORT", "Building XML Response for item '%s'", $item->dav_name); $denied = array(); $unsupported = array(); $caldav_data = $item->caldav_data; $displayname = preg_replace('{^.*/}', '', $item->dav_name); $type = 'unknown'; $contenttype = 'text/plain'; switch (strtoupper($item->caldav_type)) { case 'VJOURNAL': case 'VEVENT': case 'VTODO': $displayname = $item->summary; $type = 'calendar'; $contenttype = 'text/calendar'; if (isset($properties['urn:ietf:params:xml:ns:caldav:calendar-data']) || isset($properties['DAV::displayname'])) { if (!$request->AllowedTo('all') && $session->user_no != $item->user_no) { // the user is not admin / owner of this calendar looking at his calendar and can not admin the other cal if ($item->class == 'CONFIDENTIAL' || !$request->AllowedTo('read')) { dbg_error_log("REPORT", "Anonymising confidential event for: %s", $item->dav_name); $vcal = new vCalendar($caldav_data); $caldav_data = $vcal->Confidential()->Render(); $displayname = translate('Busy'); } } } if (isset($c->hide_alarm) && $c->hide_alarm) { $dav_resource = new DAVResource($item->dav_name); if (isset($properties['urn:ietf:params:xml:ns:caldav:calendar-data']) && !$dav_resource->HavePrivilegeTo('write')) { dbg_error_log("REPORT", "Stripping event alarms for: %s", $item->dav_name); $vcal = new vCalendar($caldav_data); $vcal->ClearComponents('VALARM'); $caldav_data = $vcal->Render(); } } break; case 'VCARD': $displayname = $item->fn; $type = 'vcard'; $contenttype = 'text/vcard'; break; } $url = ConstructURL($item->dav_name); $prop = new XMLElement("prop"); $need_resource = false; foreach ($properties as $full_tag => $v) { $base_tag = preg_replace('{^.*:}', '', $full_tag); switch ($full_tag) { case 'DAV::getcontentlength': $contentlength = strlen($caldav_data); $prop->NewElement($base_tag, $contentlength); break; case 'DAV::getlastmodified': $prop->NewElement($base_tag, ISODateToHTTPDate($item->modified)); break; case 'urn:ietf:params:xml:ns:caldav:calendar-data': if ($type == 'calendar') { $reply->CalDAVElement($prop, $base_tag, $caldav_data); } else { $unsupported[] = $base_tag; } break; case 'urn:ietf:params:xml:ns:carddav:address-data': if ($type == 'vcard') { $reply->CardDAVElement($prop, $base_tag, $caldav_data); } else { $unsupported[] = $base_tag; } break; case 'DAV::getcontenttype': $prop->NewElement($base_tag, $contenttype); break; case 'DAV::current-user-principal': $prop->NewElement("current-user-principal", $request->current_user_principal_xml); break; case 'DAV::displayname': $prop->NewElement($base_tag, $displayname); break; case 'DAV::resourcetype': $prop->NewElement($base_tag); // Just an empty resourcetype for a non-collection. break; case 'DAV::getetag': $prop->NewElement($base_tag, '"' . $item->dav_etag . '"'); break; case '"current-user-privilege-set"': $prop->NewElement($base_tag, privileges($request->permissions)); break; default: // It's harder. We need the DAVResource() to get this one. $need_resource = true; } if ($need_resource) { break; } } $href = new XMLElement("href", $url); if ($need_resource) { if (!isset($dav_resource)) { $dav_resource = new DAVResource($item->dav_name); } $elements = $dav_resource->GetPropStat(array_keys($properties), $reply); array_unshift($elements, $href); } else { $elements = array($href); $status = new XMLElement("status", "HTTP/1.1 200 OK"); $elements[] = new XMLElement("propstat", array($prop, $status)); if (count($denied) > 0) { $status = new XMLElement("status", "HTTP/1.1 403 Forbidden"); $noprop = new XMLElement("prop"); foreach ($denied as $k => $v) { $reply->NSElement($noprop, $v); } $elements[] = new XMLElement("propstat", array($noprop, $status)); } if (!$request->PreferMinimal() && count($unsupported) > 0) { $status = new XMLElement("status", "HTTP/1.1 404 Not Found"); $noprop = new XMLElement("prop"); foreach ($unsupported as $k => $v) { $reply->NSElement($noprop, $v); } $elements[] = new XMLElement("propstat", array($noprop, $status)); } } $response = new XMLElement("response", $elements); return $response; }