function testing_get_method($filename) { $body = file_get_contents($filename); $ical = new iCalComponent(); $ical->ParseFrom($body); $props = $ical->GetPropertiesByPath("VCALENDAR/METHOD"); if (count($props) > 0) { printf("METHOD %s\n", $props[0]->Value()); } }
/** * Generate a VTODO from a SyncAppointment(Exception) * @param string $data * @param string $id * @return iCalComponent */ private function _ParseASTaskToVTodo($data, $id) { $vtodo = new iCalComponent(); $vtodo->SetType("VTODO"); if (isset($data->body)) { $vtodo->AddProperty("DESCRIPTION", $data->body); } if (isset($data->asbody->data)) { if (isset($data->nativebodytype) && $data->nativebodytype == SYNC_BODYPREFERENCE_RTF) { $rtfparser = new rtf(); $rtfparser->loadrtf(base64_decode($data->asbody->data)); $rtfparser->output("ascii"); $rtfparser->parse(); $vtodo->AddProperty("DESCRIPTION", $rtfparser->out); } else { $vtodo->AddProperty("DESCRIPTION", $data->asbody->data); } } if (isset($data->complete)) { if ($data->complete == "0") { $vtodo->AddProperty("STATUS", "NEEDS-ACTION"); } else { $vtodo->AddProperty("STATUS", "COMPLETED"); } } if (isset($data->datecompleted)) { $vtodo->AddProperty("COMPLETED", gmdate("Ymd\\THis\\Z", $data->datecompleted)); } if ($data->utcduedate) { $vtodo->AddProperty("DUE", gmdate("Ymd\\THis\\Z", $data->utcduedate)); } if (isset($data->importance)) { if ($data->importance == "1") { $vtodo->AddProperty("PRIORITY", 6); } elseif ($data->importance == "2") { $vtodo->AddProperty("PRIORITY", 9); } else { $vtodo->AddProperty("PRIORITY", 1); } } if (isset($data->recurrence)) { $vtodo->AddProperty("RRULE", $this->_GenerateRecurrence($data->recurrence)); } if ($data->reminderset && $data->remindertime) { $valarm = new iCalComponent(); $valarm->SetType("VALARM"); $valarm->AddProperty("ACTION", "DISPLAY"); $valarm->AddProperty("TRIGGER;VALUE=DATE-TIME", gmdate("Ymd\\THis\\Z", $data->remindertime)); $vtodo->AddComponent($valarm); } if (isset($data->sensitivity)) { switch ($data->sensitivity) { case "0": $vtodo->AddProperty("CLASS", "PUBLIC"); break; case "2": $vtodo->AddProperty("CLASS", "PRIVATE"); break; case "3": $vtodo->AddProperty("CLASS", "CONFIDENTIAL"); break; } } if (isset($data->utcstartdate)) { $vtodo->AddProperty("DTSTART", gmdate("Ymd\\THis\\Z", $data->utcstartdate)); } if (isset($data->subject)) { $vtodo->AddProperty("SUMMARY", $data->subject); } if (isset($data->rtf)) { $rtfparser = new rtf(); $rtfparser->loadrtf(base64_decode($data->rtf)); $rtfparser->output("ascii"); $rtfparser->parse(); $vtodo->AddProperty("DESCRIPTION", $rtfparser->out); } if (isset($data->categories) && is_array($data->categories)) { $vtodo->AddProperty("CATEGORIES", implode(",", $data->categories)); } return $vtodo; }
/** * The constructor takes an array of args. If there is an element called 'icalendar' * then that will be parsed into the iCalendar object. Otherwise the array elements * are converted into properties of the iCalendar object directly. */ function iRemoteCalendar($args) { global $c; $this->tz_locn = ""; if (!isset($args) || !(is_array($args) || is_object($args))) { return; } if (is_object($args)) { settype($args, 'array'); } $this->component = new iCalComponent(); if (isset($args['icalendar'])) { $this->component->ParseFrom($args['icalendar']); $this->lines = preg_split('/\\r?\\n/', $args['icalendar']); $this->SaveTimeZones(); $first =& $this->component->FirstNonTimezone(); if ($first) { $this->type = $first->GetType(); $this->properties = $first->GetProperties(); } else { $this->properties = array(); } $this->properties['VCALENDAR'] = array('***ERROR*** This class is being referenced in an unsupported way!'); return; } if (isset($args['type'])) { $this->type = $args['type']; unset($args['type']); } else { $this->type = 'VEVENT'; // Default to event } $this->component->SetType('VCALENDAR'); $this->component->SetProperties(array(new iCalProp('PRODID:-//davical.org//NONSGML AWL Calendar//EN'), new iCalProp('VERSION:2.0'), new iCalProp('CALSCALE:GREGORIAN'))); $first = new iCalComponent(); $first->SetType($this->type); $this->properties = array(); foreach ($args as $k => $v) { dbg_error_log("iCalendar", ":Initialise: %s to >>>%s<<<", $k, $v); $property = new iCalProp(); $property->Name($k); $property->Value($v); $this->properties[] = $property; } $first->SetProperties($this->properties); $this->component->SetComponents(array($first)); $this->properties['VCALENDAR'] = array('***ERROR*** This class is being referenced in an unsupported way!'); /** * @todo Need to handle timezones!!! */ if ($this->tz_locn == "") { $this->tz_locn = $this->Get("tzid"); if ((!isset($this->tz_locn) || $this->tz_locn == "") && isset($c->local_tzid)) { $this->tz_locn = $c->local_tzid; } } }
/** * Modify a text/calendar part to transform it in a reply * * @access private * @param $part MIME part * @param $response Response numeric value * @param $condition_value string * @return string MIME text/calendar */ function reply_meeting_calendar($part, $response, $username) { $status_attendee = "ACCEPTED"; // 1 or default is ACCEPTED $status_event = "CONFIRMED"; switch ($response) { case 1: $status_attendee = "ACCEPTED"; $status_event = "CONFIRMED"; break; case 2: $status_attendee = $status_event = "TENTATIVE"; break; case 3: // We won't hit this case ever, because we won't create an event if we are rejecting it $status_attendee = "DECLINED"; $status_event = "CANCELLED"; break; } $ical = new iCalComponent(); $ical->ParseFrom($part->body); $ical->SetPValue("METHOD", "REPLY"); $ical->SetCPParameterValue("VEVENT", "STATUS", $status_event, null); // Update my information as attendee, but only mine $ical->SetCPParameterValue("VEVENT", "ATTENDEE", "PARTSTAT", $status_attendee, sprintf("MAILTO:%s", $username)); $ical->SetCPParameterValue("VEVENT", "ATTENDEE", "RSVP", null, sprintf("MAILTO:%s", $username)); return $ical->Render(); }
function obfuscated_event($icalendar) { // The user is not admin / owner of this calendar looking at his calendar and can not admin the other cal, // or maybe they don't have *read* access but they got here, so they must at least have free/busy access // so we will present an obfuscated version of the event that just says "Busy" (translated :-) $confidential = new iCalComponent(); $confidential->SetType($icalendar->GetType()); $confidential->AddProperty('SUMMARY', translate('Busy')); $confidential->AddProperty('CLASS', 'CONFIDENTIAL'); $confidential->SetProperties($icalendar->GetProperties('DTSTART'), 'DTSTART'); $confidential->SetProperties($icalendar->GetProperties('RRULE'), 'RRULE'); $confidential->SetProperties($icalendar->GetProperties('DURATION'), 'DURATION'); $confidential->SetProperties($icalendar->GetProperties('DTEND'), 'DTEND'); $confidential->SetProperties($icalendar->GetProperties('UID'), 'UID'); $confidential->SetProperties($icalendar->GetProperties('CREATED'), 'CREATED'); return $confidential; }
/** * Converts a text/calendar part into SyncMeetingRequest * * @access private * @param $part MIME part * @param $output SyncMail object */ private function parseMeetingCalendar($part, &$output) { $ical = new iCalComponent(); $ical->ParseFrom($part->body); if (isset($part->ctype_parameters["method"])) { switch (strtolower($part->ctype_parameters["method"])) { case "cancel": $output->messageclass = "IPM.Schedule.Meeting.Canceled"; break; case "counter": $output->messageclass = "IPM.Schedule.Meeting.Resp.Tent"; break; case "reply": $props = $ical->GetPropertiesByPath('!VTIMEZONE/ATTENDEE'); if (count($props) == 1) { $props_params = $props[0]->Parameters(); if (isset($props_params["PARTSTAT"])) { switch (strtolower($props_params["PARTSTAT"])) { case "accepted": $output->messageclass = "IPM.Schedule.Meeting.Resp.Pos"; break; case "needs-action": case "tentative": $output->messageclass = "IPM.Schedule.Meeting.Resp.Tent"; break; case "declined": $output->messageclass = "IPM.Schedule.Meeting.Resp.Neg"; break; default: ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->parseMeetingCalendar() - Unknown reply status %s", strtolower($props_params["PARTSTAT"]))); $output->messageclass = "IPM.Appointment"; break; } } else { ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->parseMeetingCalendar() - No reply status found")); $output->messageclass = "IPM.Appointment"; } } else { ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->parseMeetingCalendar() - There are not attendees")); $output->messageclass = "IPM.Appointment"; } break; case "request": $output->messageclass = "IPM.Schedule.Meeting.Request"; break; default: ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->parseMeetingCalendar() - Unknown method %s", strtolower($part->headers["method"]))); $output->messageclass = "IPM.Appointment"; break; } } else { ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->parseMeetingCalendar() - No method header")); $output->messageclass = "IPM.Appointment"; } $props = $ical->GetPropertiesByPath('VEVENT/DTSTAMP'); if (count($props) == 1) { $output->meetingrequest->dtstamp = Utils::MakeUTCDate($props[0]->Value()); } $props = $ical->GetPropertiesByPath('VEVENT/UID'); if (count($props) == 1) { $output->meetingrequest->globalobjid = $props[0]->Value(); } $props = $ical->GetPropertiesByPath('VEVENT/DTSTART'); if (count($props) == 1) { $output->meetingrequest->starttime = Utils::MakeUTCDate($props[0]->Value()); if (strlen($props[0]->Value()) == 8) { $output->meetingrequest->alldayevent = 1; } } $props = $ical->GetPropertiesByPath('VEVENT/DTEND'); if (count($props) == 1) { $output->meetingrequest->endtime = Utils::MakeUTCDate($props[0]->Value()); if (strlen($props[0]->Value()) == 8) { $output->meetingrequest->alldayevent = 1; } } $props = $ical->GetPropertiesByPath('VEVENT/ORGANIZER'); if (count($props) == 1) { $output->meetingrequest->organizer = str_ireplace("MAILTO:", "", $props[0]->Value()); } $props = $ical->GetPropertiesByPath('VEVENT/LOCATION'); if (count($props) == 1) { $output->meetingrequest->location = $props[0]->Value(); } $props = $ical->GetPropertiesByPath('VEVENT/CLASS'); if (count($props) == 1) { switch ($props[0]->Value()) { case "PUBLIC": $output->meetingrequest->sensitivity = "0"; break; case "PRIVATE": $output->meetingrequest->sensitivity = "2"; break; case "CONFIDENTIAL": $output->meetingrequest->sensitivity = "3"; break; default: ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->parseMeetingCalendar() - No sensitivity class. Using 2")); $output->meetingrequest->sensitivity = "2"; break; } } // Get $tz from first timezone $props = $ical->GetPropertiesByPath("VTIMEZONE/TZID"); if (count($props) > 0) { // TimeZones shouldn't have dots $tzname = str_replace(".", "", $props[0]->Value()); $tz = TimezoneUtil::GetFullTZFromTZName($tzname); } else { $tz = TimezoneUtil::GetFullTZ(); } $output->meetingrequest->timezone = base64_encode(TimezoneUtil::getSyncBlobFromTZ($tz)); // Fixed values $output->meetingrequest->instancetype = 0; $output->meetingrequest->responserequested = 1; $output->meetingrequest->busystatus = 2; // TODO: reminder $output->meetingrequest->reminder = ""; }
<?php require_once 'vendor/autoload.php'; define('LOGLEVEL', LOGLEVEL_DEBUG); define('LOGUSERLEVEL', LOGLEVEL_DEVICEID); date_default_timezone_set('Europe/Madrid'); $body = file_get_contents('testing/samples/meeting_request.txt'); $ical = new iCalComponent(); $ical->ParseFrom($body); $props = $ical->GetPropertiesByPath("VTIMEZONE/TZID"); if (count($props) > 0) { $tzid = $props[0]->Value(); printf("TZID %s\n", $props[0]->Value()); } print_r(TimezoneUtil::GetFullTZFromTZName($tzid)); $body = file_get_contents('testing/samples/meeting_request_rim.txt'); $ical = new iCalComponent(); $ical->ParseFrom($body); $props = $ical->GetPropertiesByPath("VTIMEZONE/TZID"); if (count($props) > 0) { $tzid = $props[0]->Value(); printf("TZID %s\n", $props[0]->Value()); } print_r(TimezoneUtil::GetFullTZFromTZName($tzid));
<?php /** * Handle the FREE-BUSY-QUERY variant of REPORT */ include_once "freebusy-functions.php"; $fbq_content = $xmltree->GetContent('urn:ietf:params:xml:ns:caldav:free-busy-query'); $fbq_start = $fbq_content[0]->GetAttribute('start'); $fbq_end = $fbq_content[0]->GetAttribute('end'); if (!(isset($fbq_start) || isset($fbq_end))) { $request->DoResponse(400, 'All valid freebusy requests MUST contain a time-range filter'); } $range_start = new RepeatRuleDateTime($fbq_start); $range_end = new RepeatRuleDateTime($fbq_end); /** We use the same code for the REPORT, the POST and the freebusy GET... */ $freebusy = get_freebusy('^' . $request->path . $request->DepthRegexTail(), $range_start, $range_end); $result = new iCalComponent(); $result->VCalendar(); $result->AddComponent($freebusy); $request->DoResponse(200, $result->Render(), 'text/calendar'); // Won't return from that
/** * Changes every property passed as an associative array (key will be * uppercased) on given component. DTSTART, DTEND and * DURATION are ignored, use make_start and make_end instead * * @param iCalComponent $component * @param array $properties */ function change_properties($component, $properties) { $properties = array_change_key_case($properties, CASE_UPPER); foreach ($properties as $p => $v) { if ($p == 'DTSTART' || $p == 'DTEND' || $p == 'DURATION') { continue; } // TODO: multivalued properties? // TRANSP if ($p == 'TRANSP') { if ($v != 'OPAQUE' && $v != 'TRANSPARENT') { log_message('ERROR', 'Invalid TRANSP value (' . $v . '). Ignoring.'); continue; } } $component->deleteProperty($p); if (!empty($v)) { $component->setProperty($p, $v); } } return $component; }
function handle_freebusy_request($ic) { global $c, $session, $request; $reply = new XMLDocument(array("DAV:" => "", "urn:ietf:params:xml:ns:caldav" => "C")); $responses = array(); $fbq_start = $ic->GetPValue('DTSTART'); $fbq_end = $ic->GetPValue('DTEND'); if (!(isset($fbq_start) || isset($fbq_end))) { $request->DoResponse(400, 'All valid freebusy requests MUST contain a DTSTART and a DTEND'); } $range_start = new RepeatRuleDateTime($fbq_start); $range_end = new RepeatRuleDateTime($fbq_end); $attendees = $ic->GetProperties('ATTENDEE'); if (preg_match('# iCal/\\d#', $_SERVER['HTTP_USER_AGENT'])) { dbg_error_log("POST", "Non-compliant iCal request. Using X-WR-ATTENDEE property"); $wr_attendees = $ic->GetProperties('X-WR-ATTENDEE'); foreach ($wr_attendees as $k => $v) { $attendees[] = $v; } } dbg_error_log("POST", "Responding with free/busy for %d attendees", count($attendees)); foreach ($attendees as $k => $attendee) { $attendee_email = preg_replace('/^mailto:/', '', $attendee->Value()); dbg_error_log("POST", "Calculating free/busy for %s", $attendee_email); /** @TODO: Refactor this so we only do one query here and loop through the results */ $params = array(':session_principal' => $session->principal_id, ':scan_depth' => $c->permission_scan_depth, ':email' => $attendee_email); $qry = new AwlQuery('SELECT pprivs(:session_principal::int8,principal_id,:scan_depth::int) AS p, username FROM usr JOIN principal USING(user_no) WHERE lower(usr.email) = lower(:email)', $params); if (!$qry->Exec('POST', __LINE__, __FILE__)) { $request->DoResponse(501, 'Database error'); } if ($qry->rows() > 1) { // Unlikely, but if we get more than one result we'll do an exact match instead. if (!$qry->QDo('SELECT pprivs(:session_principal::int8,principal_id,:scan_depth::int) AS p, username FROM usr JOIN principal USING(user_no) WHERE usr.email = :email', $params)) { $request->DoResponse(501, 'Database error'); } if ($qry->rows() == 0) { /** Sigh... Go back to the original case-insensitive match */ $qry->QDo('SELECT pprivs(:session_principal::int8,principal_id,:scan_depth::int) AS p, username FROM usr JOIN principal USING(user_no) WHERE lower(usr.email) = lower(:email)', $params); } } $response = $reply->NewXMLElement("response", false, false, 'urn:ietf:params:xml:ns:caldav'); $reply->CalDAVElement($response, "recipient", $reply->href($attendee->Value())); if ($qry->rows() == 0) { $reply->CalDAVElement($response, "request-status", "3.7;Invalid Calendar User"); $reply->CalDAVElement($response, "calendar-data"); $responses[] = $response; continue; } if (!($attendee_usr = $qry->Fetch())) { $request->DoResponse(501, 'Database error'); } if ((privilege_to_bits('schedule-query-freebusy') & bindec($attendee_usr->p)) == 0) { $reply->CalDAVElement($response, "request-status", "3.8;No authority"); $reply->CalDAVElement($response, "calendar-data"); $responses[] = $response; continue; } $attendee_path_match = '^/' . $attendee_usr->username . '/'; $fb = get_freebusy($attendee_path_match, $range_start, $range_end, bindec($attendee_usr->p)); $fb->AddProperty('UID', $ic->GetPValue('UID')); $fb->SetProperties($ic->GetProperties('ORGANIZER'), 'ORGANIZER'); $fb->AddProperty($attendee); $vcal = new iCalComponent(); $vcal->VCalendar(array('METHOD' => 'REPLY')); $vcal->AddComponent($fb); $response = $reply->NewXMLElement("response", false, false, 'urn:ietf:params:xml:ns:caldav'); $reply->CalDAVElement($response, "recipient", $reply->href($attendee->Value())); $reply->CalDAVElement($response, "request-status", "2.0;Success"); // Cargo-cult setting $reply->CalDAVElement($response, "calendar-data", $vcal->Render()); $responses[] = $response; } $response = $reply->NewXMLElement("schedule-response", $responses, $reply->GetXmlNsArray(), 'urn:ietf:params:xml:ns:caldav'); $request->XMLResponse(200, $response); }
function get_freebusy($path_match, $range_start, $range_end, $bin_privs = null) { global $request; // printf( "Path: %s\n", $path_match); // print_r($range_start); // print_r($range_end); if (!isset($bin_privs)) { $bin_privs = $request->Privileges(); } if (!isset($range_start) || !isset($range_end)) { $request->DoResponse(400, 'All valid freebusy requests MUST contain a time-range filter'); } $params = array(':path_match' => $path_match, ':start' => $range_start->UTC(), ':end' => $range_end->UTC()); $where = ' WHERE caldav_data.dav_name ~ :path_match '; $where .= 'AND rrule_event_overlaps( dtstart, dtend, rrule, :start, :end) '; $where .= "AND caldav_data.caldav_type IN ( 'VEVENT', 'VTODO' ) "; $where .= "AND (calendar_item.transp != 'TRANSPARENT' OR calendar_item.transp IS NULL) "; $where .= "AND (calendar_item.status != 'CANCELLED' OR calendar_item.status IS NULL) "; $where .= "AND collection.is_calendar AND collection.schedule_transp = 'opaque' "; if ($bin_privs != privilege_to_bits('all')) { $where .= "AND (calendar_item.class != 'PRIVATE' OR calendar_item.class IS NULL) "; } // $debugging = true; $fbtimes = array(); $sql = 'SELECT caldav_data.caldav_data, calendar_item.rrule, calendar_item.transp, calendar_item.status, '; $sql .= "to_char(calendar_item.dtstart at time zone 'GMT'," . iCalendar::SqlUTCFormat() . ') AS start, '; $sql .= "to_char(calendar_item.dtend at time zone 'GMT'," . iCalendar::SqlUTCFormat() . ') AS finish, '; $sql .= "calendar_item.class, calendar_item.dav_id "; $sql .= 'FROM caldav_data INNER JOIN calendar_item USING(dav_id,user_no,dav_name,collection_id) '; $sql .= 'INNER JOIN collection USING(collection_id)'; $sql .= $where; if (isset($c->strict_result_ordering) && $c->strict_result_ordering) { $sql .= ' ORDER BY dav_id'; } $qry = new AwlQuery($sql, $params); if ($qry->Exec("REPORT", __LINE__, __FILE__) && $qry->rows() > 0) { while ($calendar_object = $qry->Fetch()) { $extra = ''; if ($calendar_object->status == 'TENTATIVE') { $extra = ';BUSY-TENTATIVE'; } // else if ( $debugging ) { // $extra = ';'.$calendar_object->dav_id; // } dbg_error_log("REPORT", " FreeBusy: Not transparent, tentative or cancelled: %s, %s, %s", $calendar_object->start, $calendar_object->finish, $calendar_object->class); $ics = new vComponent($calendar_object->caldav_data); $expanded = expand_event_instances($ics, $range_start, $range_end); $expansion = $expanded->GetComponents(array('VEVENT' => true, 'VTODO' => true, 'VJOURNAL' => true)); foreach ($expansion as $k => $v) { // echo "=====================================================\n"; // printf( "Type: %s\n", $v->GetType()); // print_r($v); // echo "-----------------------------------------------------\n"; $start_date = $v->GetProperty('DTSTART'); if (!isset($start_date)) { continue; } $start_date = new RepeatRuleDateTime($start_date); $duration = $v->GetProperty('DURATION'); $duration = !isset($duration) ? 'P1D' : $duration->Value(); $end_date = clone $start_date; $end_date->modify($duration); if ($end_date == $start_date || $end_date < $range_start || $start_date > $range_end) { continue; } $thisfb = $start_date->UTC() . '/' . $end_date->UTC() . $extra; array_push($fbtimes, $thisfb); } } } $freebusy = new iCalComponent(); $freebusy->SetType('VFREEBUSY'); $freebusy->AddProperty('DTSTAMP', date('Ymd\\THis\\Z')); $freebusy->AddProperty('DTSTART', $range_start->UTC()); $freebusy->AddProperty('DTEND', $range_end->UTC()); sort($fbtimes); foreach ($fbtimes as $k => $v) { $text = explode(';', $v, 2); $freebusy->AddProperty('FREEBUSY', $text[0], isset($text[1]) ? array('FBTYPE' => $text[1]) : null); } return $freebusy; }
/** * Return general server-related properties for this URL */ function ResourceProperty($tag, $prop, &$reply, &$denied) { global $c, $session, $request; // dbg_error_log( 'DAVResource', 'Processing "%s" on "%s".', $tag, $this->dav_name ); if ($reply === null) { $reply = $GLOBALS['reply']; } switch ($tag) { case 'DAV::allprop': $property_list = $this->DAV_AllProperties(); $discarded = array(); foreach ($property_list as $k => $v) { $this->ResourceProperty($v, $prop, $reply, $discarded); } break; case 'DAV::href': $prop->NewElement('href', ConstructURL($this->dav_name)); break; case 'DAV::resource-id': if ($this->resource_id > 0) { $reply->DAVElement($prop, 'resource-id', $reply->href(ConstructURL('/.resources/' . $this->resource_id))); } else { return false; } break; case 'DAV::parent-set': $sql = <<<EOQRY SELECT b.parent_container FROM dav_binding b JOIN collection c ON (b.bound_source_id=c.collection_id) WHERE regexp_replace( b.dav_name, '^.*/', c.dav_name ) = :bound_from EOQRY; $qry = new AwlQuery($sql, array(':bound_from' => $this->bound_from())); $parents = array(); if ($qry->Exec('DAVResource', __LINE__, __FILE__) && $qry->rows() > 0) { while ($row = $qry->Fetch()) { $parents[$row->parent_container] = true; } } $parents[preg_replace('{(?<=/)[^/]+/?$}', '', $this->bound_from())] = true; $parents[preg_replace('{(?<=/)[^/]+/?$}', '', $this->dav_name())] = true; $parent_set = $reply->DAVElement($prop, 'parent-set'); foreach ($parents as $parent => $v) { if (preg_match('{^(.*)?/([^/]+)/?$}', $parent, $matches)) { $reply->DAVElement($parent_set, 'parent', array(new XMLElement('href', ConstructURL($matches[1])), new XMLElement('segment', $matches[2]))); } else { if ($parent == '/') { $reply->DAVElement($parent_set, 'parent', array(new XMLElement('href', '/'), new XMLElement('segment', ConstructURL('/') == '/caldav.php/' ? 'caldav.php' : ''))); } } } break; case 'DAV::getcontenttype': if (!isset($this->contenttype) && !$this->_is_collection && !isset($this->resource)) { $this->FetchResource(); } $prop->NewElement('getcontenttype', $this->contenttype); break; case 'DAV::resourcetype': $resourcetypes = $prop->NewElement('resourcetype'); if ($this->_is_collection) { $type_list = $this->GetProperty('resourcetype'); if (!is_array($type_list)) { return true; } // dbg_error_log( 'DAVResource', ':ResourceProperty: "%s" are "%s".', $tag, implode(', ',$type_list) ); foreach ($type_list as $k => $v) { if ($v == '') { continue; } $reply->NSElement($resourcetypes, $v); } if ($this->_is_binding) { $reply->NSElement($resourcetypes, 'http://xmlns.davical.org/davical:webdav-binding'); } } break; case 'DAV::getlastmodified': /** getlastmodified is HTTP Date format: i.e. the Last-Modified header in response to a GET */ $reply->NSElement($prop, $tag, ISODateToHTTPDate($this->GetProperty('modified'))); break; case 'DAV::creationdate': /** creationdate is ISO8601 format */ $reply->NSElement($prop, $tag, DateToISODate($this->GetProperty('created'), true)); break; case 'DAV::getcontentlength': if ($this->_is_collection) { return false; } if (!isset($this->resource)) { $this->FetchResource(); } if (isset($this->resource)) { $reply->NSElement($prop, $tag, strlen($this->resource->caldav_data)); } break; case 'DAV::getcontentlanguage': $locale = isset($c->current_locale) ? $c->current_locale : ''; if (isset($this->locale) && $this->locale != '') { $locale = $this->locale; } $reply->NSElement($prop, $tag, $locale); break; case 'DAV::acl-restrictions': $reply->NSElement($prop, $tag, array(new XMLElement('grant-only'), new XMLElement('no-invert'))); break; case 'DAV::inherited-acl-set': $inherited_acls = array(); if (!$this->_is_collection) { $inherited_acls[] = $reply->href(ConstructURL($this->collection->dav_name)); } $reply->NSElement($prop, $tag, $inherited_acls); break; case 'DAV::owner': // The principal-URL of the owner if ($this->IsExternal()) { $reply->DAVElement($prop, 'owner', $reply->href(ConstructURL($this->collection->bound_from))); } else { $reply->DAVElement($prop, 'owner', $reply->href(ConstructURL(DeconstructURL($this->principal_url())))); } break; case 'DAV::add-member': if (!$this->_is_collection) { return false; } if (isset($c->post_add_member) && $c->post_add_member === false) { return false; } $reply->DAVElement($prop, 'add-member', $reply->href(ConstructURL(DeconstructURL($this->url())) . '?add-member')); break; // Empty tag responses. // Empty tag responses. case 'DAV::group': case 'DAV::alternate-URI-set': $reply->NSElement($prop, $tag); break; case 'DAV::getetag': if ($this->_is_collection) { return false; } $reply->NSElement($prop, $tag, $this->unique_tag()); break; case 'http://calendarserver.org/ns/:getctag': if (!$this->_is_collection) { return false; } $reply->NSElement($prop, $tag, $this->unique_tag()); break; case 'DAV::sync-token': if (!$this->_is_collection) { return false; } $sync_token = $this->sync_token(); if (empty($sync_token)) { return false; } $reply->NSElement($prop, $tag, $sync_token); break; case 'http://calendarserver.org/ns/:calendar-proxy-read-for': $proxy_type = 'read'; case 'http://calendarserver.org/ns/:calendar-proxy-write-for': if (isset($c->disable_caldav_proxy) && $c->disable_caldav_proxy) { return false; } if (!isset($proxy_type)) { $proxy_type = 'write'; } // ProxyFor is an already constructed URL $reply->CalendarserverElement($prop, 'calendar-proxy-' . $proxy_type . '-for', $reply->href($this->principal->ProxyFor($proxy_type))); break; case 'DAV::current-user-privilege-set': if ($this->HavePrivilegeTo('DAV::read-current-user-privilege-set')) { $reply->NSElement($prop, $tag, $this->BuildPrivileges()); } else { $denied[] = $tag; } break; case 'urn:ietf:params:xml:ns:caldav:supported-calendar-data': if (!$this->IsCalendar() && !$this->IsSchedulingCollection()) { return false; } $reply->NSElement($prop, $tag, 'text/calendar'); break; case 'urn:ietf:params:xml:ns:caldav:supported-calendar-component-set': if (!$this->_is_collection) { return false; } if ($this->IsCalendar()) { if (!isset($this->dead_properties)) { $this->FetchDeadProperties(); } if (isset($this->dead_properties[$tag])) { $set_of_components = $this->dead_properties[$tag]; foreach ($set_of_components as $k => $v) { if (preg_match('{(VEVENT|VTODO|VJOURNAL|VTIMEZONE|VFREEBUSY|VPOLL|VAVAILABILITY)}', $v, $matches)) { $set_of_components[$k] = $matches[1]; } else { unset($set_of_components[$k]); } } } else { if (isset($c->default_calendar_components) && is_array($c->default_calendar_components)) { $set_of_components = $c->default_calendar_components; } else { $set_of_components = array('VEVENT', 'VTODO', 'VJOURNAL'); } } } else { if ($this->IsSchedulingCollection()) { $set_of_components = array('VEVENT', 'VTODO', 'VFREEBUSY'); } else { return false; } } $components = array(); foreach ($set_of_components as $v) { $components[] = $reply->NewXMLElement('comp', '', array('name' => $v), 'urn:ietf:params:xml:ns:caldav'); } $reply->CalDAVElement($prop, 'supported-calendar-component-set', $components); break; case 'DAV::supported-method-set': $prop->NewElement('supported-method-set', $this->BuildSupportedMethods()); break; case 'DAV::supported-report-set': $prop->NewElement('supported-report-set', $this->BuildSupportedReports($reply)); break; case 'DAV::supportedlock': $prop->NewElement('supportedlock', new XMLElement('lockentry', array(new XMLElement('lockscope', new XMLElement('exclusive')), new XMLElement('locktype', new XMLElement('write'))))); break; case 'DAV::supported-privilege-set': $prop->NewElement('supported-privilege-set', $request->BuildSupportedPrivileges($reply)); break; case 'DAV::principal-collection-set': $prop->NewElement('principal-collection-set', $reply->href(ConstructURL('/'))); break; case 'DAV::current-user-principal': $prop->NewElement('current-user-principal', $reply->href(ConstructURL(DeconstructURL($request->principal->url())))); break; case 'SOME-DENIED-PROPERTY': /** indicating the style for future expansion */ $denied[] = $reply->Tag($tag); break; case 'urn:ietf:params:xml:ns:caldav:calendar-timezone': if (!$this->_is_collection) { return false; } if (!isset($this->collection->vtimezone) || $this->collection->vtimezone == '') { return false; } $cal = new iCalComponent(); $cal->VCalendar(); $cal->AddComponent(new iCalComponent($this->collection->vtimezone)); $reply->NSElement($prop, $tag, $cal->Render()); break; case 'urn:ietf:params:xml:ns:carddav:address-data': case 'urn:ietf:params:xml:ns:caldav:calendar-data': if ($this->_is_collection) { return false; } if (!isset($c->sync_resource_data_ok) || $c->sync_resource_data_ok == false) { return false; } if (!isset($this->resource)) { $this->FetchResource(); } $reply->NSElement($prop, $tag, $this->resource->caldav_data); break; case 'urn:ietf:params:xml:ns:carddav:max-resource-size': if (!$this->_is_collection || !$this->_is_addressbook) { return false; } $reply->NSElement($prop, $tag, 65500); break; case 'urn:ietf:params:xml:ns:carddav:supported-address-data': if (!$this->_is_collection || !$this->_is_addressbook) { return false; } $address_data = $reply->NewXMLElement('address-data', false, array('content-type' => 'text/vcard', 'version' => '3.0'), 'urn:ietf:params:xml:ns:carddav'); $reply->NSElement($prop, $tag, $address_data); break; case 'DAV::acl': if ($this->HavePrivilegeTo('DAV::read-acl')) { $reply->NSElement($prop, $tag, $this->GetACL($reply)); } else { $denied[] = $tag; } break; case 'http://www.xythos.com/namespaces/StorageServer:ticketdiscovery': case 'DAV::ticketdiscovery': $reply->NSElement($prop, 'http://www.xythos.com/namespaces/StorageServer:ticketdiscovery', $this->BuildTicketinfo($reply)); break; default: $property_value = $this->GetProperty(preg_replace('{^(DAV:|urn:ietf:params:xml:ns:ca(rd|l)dav):}', '', $tag)); if (isset($property_value)) { $reply->NSElement($prop, $tag, $property_value); } else { if (!isset($this->dead_properties)) { $this->FetchDeadProperties(); } if (isset($this->dead_properties[$tag])) { $reply->NSElement($prop, $tag, $this->dead_properties[$tag]); } else { // dbg_error_log( 'DAVResource', 'Request for unsupported property "%s" of path "%s".', $tag, $this->dav_name ); return false; } } } return true; }
/** * This function will import a whole calendar * @param string $ics_content the ics file to import * @param int $user_no the user wich will receive this ics file * @param string $path the $path where it will be store such as /user_foo/home/ * @param boolean $caldav_context Whether we are responding via CalDAV or interactively * * Any VEVENTs with the same UID will be concatenated together */ function import_collection($ics_content, $user_no, $path, $caldav_context, $appending = false) { global $c, $session, $tz_regex; if (!ini_get('open_basedir') && (isset($c->dbg['ALL']) || isset($c->dbg['put']))) { $fh = fopen('/tmp/PUT-2.txt', 'w'); if ($fh) { fwrite($fh, $ics_content); fclose($fh); } } $calendar = new iCalComponent($ics_content); $timezones = $calendar->GetComponents('VTIMEZONE', true); $components = $calendar->GetComponents('VTIMEZONE', false); $displayname = $calendar->GetPValue('X-WR-CALNAME'); if (!$appending && isset($displayname)) { $sql = 'UPDATE collection SET dav_displayname = :displayname WHERE dav_name = :dav_name'; $qry = new AwlQuery($sql, array(':displayname' => $displayname, ':dav_name' => $path)); if (!$qry->Exec('PUT', __LINE__, __FILE__)) { rollback_on_error($caldav_context, $user_no, $path); } } $tz_ids = array(); foreach ($timezones as $k => $tz) { $tz_ids[$tz->GetPValue('TZID')] = $k; } /** Build an array of resources. Each resource is an array of iCalComponent */ $resources = array(); foreach ($components as $k => $comp) { $uid = $comp->GetPValue('UID'); if ($uid == null || $uid == '') { continue; } if (!isset($resources[$uid])) { $resources[$uid] = array(); } $resources[$uid][] = $comp; /** Ensure we have the timezone component for this in our array as well */ $tzid = $comp->GetPParamValue('DTSTART', 'TZID'); if (!isset($tzid) || $tzid == '') { $tzid = $comp->GetPParamValue('DUE', 'TZID'); } if (!isset($resources[$uid][$tzid]) && isset($tz_ids[$tzid])) { $resources[$uid][$tzid] = $timezones[$tz_ids[$tzid]]; } } $sql = 'SELECT * FROM collection WHERE dav_name = :dav_name'; $qry = new AwlQuery($sql, array(':dav_name' => $path)); if (!$qry->Exec('PUT', __LINE__, __FILE__)) { rollback_on_error($caldav_context, $user_no, $path); } if (!$qry->rows() == 1) { dbg_error_log('ERROR', ' PUT: Collection does not exist at "%s" for user %d', $path, $user_no); rollback_on_error($caldav_context, $user_no, $path); } $collection = $qry->Fetch(); if (!(isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import)) { $qry->Begin(); } $base_params = array(':collection_id' => $collection->collection_id); if (!$appending) { if (!$qry->QDo('DELETE FROM calendar_item WHERE collection_id = :collection_id', $base_params) || !$qry->QDo('DELETE FROM caldav_data WHERE collection_id = :collection_id', $base_params)) { rollback_on_error($caldav_context, $user_no, $collection->collection_id); } } $dav_data_insert = <<<EOSQL INSERT INTO caldav_data ( user_no, dav_name, dav_etag, caldav_data, caldav_type, logged_user, created, modified, collection_id ) VALUES( :user_no, :dav_name, :etag, :dav_data, :caldav_type, :session_user, current_timestamp, current_timestamp, :collection_id ) EOSQL; $calitem_insert = <<<EOSQL INSERT INTO calendar_item (user_no, dav_name, dav_id, dav_etag, uid, dtstamp, dtstart, dtend, summary, location, class, transp, description, rrule, tz_id, last_modified, url, priority, created, due, percent_complete, status, collection_id ) VALUES ( :user_no, :dav_name, currval('dav_id_seq'), :etag, :uid, :dtstamp, :dtstart, ##dtend##, :summary, :location, :class, :transp, :description, :rrule, :tzid, :modified, :url, :priority, :created, :due, :percent_complete, :status, :collection_id) EOSQL; $last_tz_locn = ''; foreach ($resources as $uid => $resource) { if (isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import) { $qry->Begin(); } /** Construct the VCALENDAR data */ $vcal = new iCalComponent(); $vcal->VCalendar(); $vcal->SetComponents($resource); create_scheduling_requests($vcal); $icalendar = $vcal->Render(); /** As ever, we mostly deal with the first resource component */ $first = $resource[0]; $dav_data_params = $base_params; $dav_data_params[':user_no'] = $user_no; $dav_data_params[':dav_name'] = sprintf('%s%s.ics', $path, $uid); $dav_data_params[':etag'] = md5($icalendar); $calitem_params = $dav_data_params; $dav_data_params[':dav_data'] = $icalendar; $dav_data_params[':caldav_type'] = $first->GetType(); $dav_data_params[':session_user'] = $session->user_no; if (!$qry->QDo($dav_data_insert, $dav_data_params)) { rollback_on_error($caldav_context, $user_no, $path); } $qry->QDo('SELECT dav_id FROM caldav_data WHERE dav_name = :dav_name ', array(':dav_name' => $dav_data_params[':dav_name'])); if ($qry->rows() == 1 && ($row = $qry->Fetch())) { $dav_id = $row->dav_id; } $dtstart = $first->GetPValue('DTSTART'); $calitem_params[':dtstart'] = $dtstart; if ((!isset($dtstart) || $dtstart == '') && $first->GetPValue('DUE') != '') { $dtstart = $first->GetPValue('DUE'); } $dtend = $first->GetPValue('DTEND'); if (isset($dtend) && $dtend != '') { dbg_error_log('PUT', ' DTEND: "%s", DTSTART: "%s", DURATION: "%s"', $dtend, $dtstart, $first->GetPValue('DURATION')); $calitem_params[':dtend'] = $dtend; $dtend = ':dtend'; } else { $dtend = 'NULL'; if ($first->GetPValue('DURATION') != '' and $dtstart != '') { $duration = trim(preg_replace('#[PT]#', ' ', $first->GetPValue('DURATION'))); if ($duration == '') { $duration = '0 seconds'; } $dtend = '(:dtstart::timestamp with time zone + :duration::interval)'; $calitem_params[':duration'] = $duration; } elseif ($first->GetType() == 'VEVENT') { /** * From RFC2445 4.6.1: * For cases where a "VEVENT" calendar component specifies a "DTSTART" * property with a DATE data type but no "DTEND" property, the events * non-inclusive end is the end of the calendar date specified by the * "DTSTART" property. For cases where a "VEVENT" calendar component specifies * a "DTSTART" property with a DATE-TIME data type but no "DTEND" property, * the event ends on the same calendar date and time of day specified by the * "DTSTART" property. * * So we're looking for 'VALUE=DATE', to identify the duration, effectively. * */ $value_type = $first->GetPParamValue('DTSTART', 'VALUE'); dbg_error_log('PUT', 'DTSTART without DTEND. DTSTART value type is %s', $value_type); if (isset($value_type) && $value_type == 'DATE') { $dtend = '(:dtstart::timestamp with time zone::date + \'1 day\'::interval)'; } else { $dtend = ':dtstart'; } } } $last_modified = $first->GetPValue('LAST-MODIFIED'); if (!isset($last_modified) || $last_modified == '') { $last_modified = gmdate('Ymd\\THis\\Z'); } $calitem_params[':modified'] = $last_modified; $dtstamp = $first->GetPValue('DTSTAMP'); if (!isset($dtstamp) || $dtstamp == '') { $dtstamp = $last_modified; } $calitem_params[':dtstamp'] = $dtstamp; /** RFC2445, 4.8.1.3: Default is PUBLIC, or also if overridden by the collection settings */ $class = $collection->public_events_only == 't' ? 'PUBLIC' : $first->GetPValue('CLASS'); if (!isset($class) || $class == '') { $class = 'PUBLIC'; } $calitem_params[':class'] = $class; /** Calculate what timezone to set, first, if possible */ $tzid = $first->GetPParamValue('DTSTART', 'TZID'); if (!isset($tzid) || $tzid == '') { $tzid = $first->GetPParamValue('DUE', 'TZID'); } if (isset($tzid) && $tzid != '') { if (isset($resource[$tzid])) { $tz = $resource[$tzid]; $tz_locn = $tz->GetPValue('X-LIC-LOCATION'); } else { unset($tz); unset($tz_locn); } if (!isset($tz_locn) || !preg_match($tz_regex, $tz_locn)) { if (preg_match('#([^/]+/[^/]+)$#', $tzid, $matches)) { $tz_locn = $matches[1]; } } dbg_error_log('PUT', ' Using TZID[%s] and location of [%s]', $tzid, isset($tz_locn) ? $tz_locn : ''); if (isset($tz_locn) && $tz_locn != $last_tz_locn && preg_match($tz_regex, $tz_locn)) { dbg_error_log('PUT', ' Setting timezone to %s', $tz_locn); if ($tz_locn != '') { $qry->QDo('SET TIMEZONE TO \'' . $tz_locn . "'"); } $last_tz_locn = $tz_locn; } $params = array(':tzid' => $tzid); $qry = new AwlQuery('SELECT tz_locn FROM time_zone WHERE tz_id = :tzid', $params); if ($qry->Exec('PUT', __LINE__, __FILE__) && $qry->rows() == 0) { $params[':tzlocn'] = $tz_locn; $params[':tzspec'] = isset($tz) ? $tz->Render() : null; $qry->QDo('INSERT INTO time_zone (tz_id, tz_locn, tz_spec) VALUES(:tzid,:tzlocn,:tzspec)', $params); } if (!isset($tz_locn) || $tz_locn == '') { $tz_locn = $tzid; } } else { $tzid = null; } $sql = str_replace('##dtend##', $dtend, $calitem_insert); $calitem_params[':tzid'] = $tzid; $calitem_params[':uid'] = $first->GetPValue('UID'); $calitem_params[':summary'] = $first->GetPValue('SUMMARY'); $calitem_params[':location'] = $first->GetPValue('LOCATION'); $calitem_params[':transp'] = $first->GetPValue('TRANSP'); $calitem_params[':description'] = $first->GetPValue('DESCRIPTION'); $calitem_params[':rrule'] = $first->GetPValue('RRULE'); $calitem_params[':url'] = $first->GetPValue('URL'); $calitem_params[':priority'] = $first->GetPValue('PRIORITY'); $calitem_params[':due'] = $first->GetPValue('DUE'); $calitem_params[':percent_complete'] = $first->GetPValue('PERCENT-COMPLETE'); $calitem_params[':status'] = $first->GetPValue('STATUS'); $created = $first->GetPValue('CREATED'); if ($created == '00001231T000000Z') { $created = '20001231T000000Z'; } $calitem_params[':created'] = $created; if (!$qry->QDo($sql, $calitem_params)) { rollback_on_error($caldav_context, $user_no, $path); } write_alarms($dav_id, $first); write_attendees($dav_id, $first); create_scheduling_requests($vcal); if (isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import) { $qry->Commit(); } } if (!(isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import)) { if (!$qry->Commit()) { rollback_on_error($caldav_context, $user_no, $path); } } }
$ic = new iCalComponent($resource->caldav_data); $resource->caldav_data = preg_replace('{(?<!\\r)\\n}', "\r\n", $resource->caldav_data); /** Default deny... */ $allowed = false; if ($dav_resource->HavePrivilegeTo('all', false) || $session->user_no == $resource->user_no || $session->user_no == $resource->logged_user || $c->allow_get_email_visibility && $ic->IsAttendee($session->email)) { /** * These people get to see all of the event, and they should always * get any alarms as well. */ $allowed = true; } else { if ($resource->class != 'PRIVATE') { $allowed = true; // but we may well obfuscate it below if (!$dav_resource->HavePrivilegeTo('DAV::read') || $resource->class == 'CONFIDENTIAL' && !$request->HavePrivilegeTo('DAV::write-content')) { $ical = new iCalComponent($resource->caldav_data); $comps = $ical->GetComponents('VTIMEZONE', false); $confidential = obfuscated_event($comps[0]); $ical->SetComponents(array($confidential), $resource->caldav_type); $resource->caldav_data = $ical->Render(); } } } // else $resource->class == 'PRIVATE' and this person may not see it. if (!$allowed) { $request->DoResponse(403, translate("Forbidden")); } header('Etag: "' . $resource->dav_etag . '"'); header('Content-Length: ' . strlen($resource->caldav_data)); $contenttype = 'text/plain'; switch ($resource->caldav_type) {
/** * 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 ($item->caldav_type) { case 'VJOURNAL': case 'VEVENT': case 'VTODO': $displayname = $item->summary; $type = 'calendar'; $contenttype = 'text/calendar'; break; case 'VCARD': $displayname = $item->fn; $type = 'vcard'; $contenttype = 'text/vcard'; break; } if (isset($properties['calendar-data']) || isset($properties['displayname'])) { if (!$request->AllowedTo('all') && $session->user_no != $item->user_no) { // the user is not admin / owner of this calendarlooking at his calendar and can not admin the other cal /** @todo We should examine the ORGANIZER and ATTENDEE fields in the event. If this person is there then they should see this */ if ($type == 'calendar' && $item->class == 'CONFIDENTIAL' || !$request->AllowedTo('read')) { $ical = new iCalComponent($caldav_data); $resources = $ical->GetComponents('VTIMEZONE', false); $first = $resources[0]; // if the event is confidential we fake one that just says "Busy" $confidential = new iCalComponent(); $confidential->SetType($first->GetType()); $confidential->AddProperty('SUMMARY', translate('Busy')); $confidential->AddProperty('CLASS', 'CONFIDENTIAL'); $confidential->SetProperties($first->GetProperties('DTSTART'), 'DTSTART'); $confidential->SetProperties($first->GetProperties('RRULE'), 'RRULE'); $confidential->SetProperties($first->GetProperties('DURATION'), 'DURATION'); $confidential->SetProperties($first->GetProperties('DTEND'), 'DTEND'); $confidential->SetProperties($first->GetProperties('UID'), 'UID'); $ical->SetComponents(array($confidential), $confidential->GetType()); $caldav_data = $ical->Render(); $displayname = translate('Busy'); } } } $url = ConstructURL($item->dav_name); $prop = new XMLElement("prop"); foreach ($properties as $k => $v) { switch ($k) { case 'getcontentlength': $contentlength = strlen($caldav_data); $prop->NewElement($k, $contentlength); break; case 'getlastmodified': $prop->NewElement($k, ISODateToHTTPDate($item->modified)); break; case 'calendar-data': if ($type == 'calendar') { $reply->CalDAVElement($prop, $k, $caldav_data); } else { $unsupported[] = $k; } break; case 'address-data': if ($type == 'vcard') { $reply->CardDAVElement($prop, $k, $caldav_data); } else { $unsupported[] = $k; } break; case 'getcontenttype': $prop->NewElement($k, $contenttype); break; case 'current-user-principal': $prop->NewElement("current-user-principal", $request->current_user_principal_xml); break; case 'displayname': $prop->NewElement($k, $displayname); break; case 'resourcetype': $prop->NewElement($k); // Just an empty resourcetype for a non-collection. break; case 'getetag': $prop->NewElement($k, '"' . $item->dav_etag . '"'); break; case '"current-user-privilege-set"': $prop->NewElement($k, privileges($request->permissions)); break; case 'SOME-DENIED-PROPERTY': /** indicating the style for future expansion */ $denied[] = $k; break; default: dbg_error_log('REPORT', "Request for unsupported property '%s' of calendar item.", $v); $unsupported[] = $k; } } $status = new XMLElement("status", "HTTP/1.1 200 OK"); $propstat = new XMLElement("propstat", array($prop, $status)); $href = new XMLElement("href", $url); $elements = array($href, $propstat); if (count($denied) > 0) { $status = new XMLElement("status", "HTTP/1.1 403 Forbidden"); $noprop = new XMLElement("prop"); foreach ($denied as $k => $v) { $noprop->NewElement(strtolower($v)); } $elements[] = new XMLElement("propstat", array($noprop, $status)); } if (count($unsupported) > 0) { $status = new XMLElement("status", "HTTP/1.1 404 Not Found"); $noprop = new XMLElement("prop"); foreach ($unsupported as $k => $v) { $noprop->NewElement(strtolower($v)); } $elements[] = new XMLElement("propstat", array($noprop, $status)); } $response = new XMLElement("response", $elements); return $response; }
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(); }
<?php require_once 'vendor/autoload.php'; $body = file_get_contents('testing/samples/meeting_request.txt'); $ical = new iCalComponent(); $ical->ParseFrom($body); $props = $ical->GetPropertiesByPath('!VTIMEZONE/ATTENDEE'); if (count($props) == 1) { if (isset($props[0]->Parameters()["PARTSTAT"])) { printf("DOES THIS CAUSE ERROR? %s\n", $props[0]->Parameters()["PARTSTAT"]); } } // MODIFICATIONS // METHOD $ical->SetPValue("METHOD", "REPLY"); //ATTENDEE $ical->SetCPParameterValue("VEVENT", "ATTENDEE", "PARTSTAT", "ACCEPTED"); printf("%s\n", $ical->Render()); $mail = new Mail_mimepart(); $headers = array("MIME-version" => "1.0", "From" => $mail->encodeHeader("from", "Pedro Picapiedra <*****@*****.**>", "UTF-8"), "To" => $mail->encodeHeader("to", "Pablo Marmol <*****@*****.**>", "UTF-8"), "Date" => gmdate("D, d M Y H:i:s", time()) . " GMT", "Subject" => $mail->encodeHeader("subject", "This is a subject", "UTF-8"), "Content-class" => "urn:content-classes:calendarmessage", "Content-transfer-encoding" => "8BIT"); $mail = new Mail_mimepart($ical->Render(), array("content_type" => "text/calendar; method=REPLY; charset=UTF-8", "headers" => $headers)); $message = ""; $encoded_mail = $mail->encode(); foreach ($encoded_mail["headers"] as $k => $v) { $message .= $k . ": " . $v . "\r\n"; } $message .= "\r\n" . $encoded_mail["body"] . "\r\n"; printf("%s\n", $message); define('LOGLEVEL', LOGLEVEL_DEBUG); define('LOGUSERLEVEL', LOGLEVEL_DEVICEID); $props = $ical->GetPropertiesByPath("VTIMEZONE/TZID");
private function AddDefault(iCalComponent $component) { $properties = $component->GetProperties(); $now = gmdate("Ymd\\THis\\Z"); $a = array(1, 1, 1); foreach ($properties as $property) { //echo "D: " . $property->Name(). ":" . $property->Value() . "<br/>"; if (strcasecmp('DTSTAMP', $property->Name()) === 0) { $property->Value($now); $a[0] = 0; } if (strcasecmp('LAST-MODIFIED', $property->Name()) === 0) { $property->Value($now); $a[1] = 0; } if (strcasecmp('X-WEBCAL-GENERATION', $property->Name()) === 0) { $property->Value('1'); $a[2] = 0; } } for ($i = 0; $i < count($a); $i++) { //echo $i.':'.$a[$i]."<br/>"; if ($a[$i]) { switch ($i) { case 0: $c['DTSTAMP'] = $now; break; case 1: $c['LAST-MODIFIED'] = $now; break; case 2: $c['X-WEBCAL-GENERATION'] = 1; break; default: continue; } $key = key($c); $val = $c[$key]; $component->AddProperty($key, $val); $c = NULL; } } }