function write_updated_zone($vtimezone, $tzid) { global $new_zones, $modified_zones; if (empty($vtimezone)) { dbg_error_log('tz/updatecheck', 'Skipping zone "%s" - no data from server', $tzid); return; } $tzrow = fetch_db_zone($tzid); if (isset($tzrow) && $vtimezone == $tzrow->vtimezone) { dbg_error_log('tz/updatecheck', 'Skipping zone "%s" - no change', $tzid); return; } $vtz = new vCalendar($vtimezone); $last_modified = $vtz->GetPValue('LAST-MODIFIED'); if (empty($last_modified)) { $last_modified = gmdate('Ymd\\THis\\Z'); // Then it was probably that way when we last updated the data, too :-( if (!empty($tzrow)) { $old_vtz = new vCalendar($tzrow->vtimezone); $old_vtz->ClearProperties('LAST-MODIFIED'); // We need to add & remove this property so the Render is equivalent. $vtz->AddProperty('LAST-MODIFIED', $last_modified); $vtz->ClearProperties('LAST-MODIFIED'); if ($vtz->Render() == $old_vtz->Render()) { dbg_error_log('tz/updatecheck', 'Skipping zone "%s" - no change', $tzid); return; } } $vtz->AddProperty('LAST-MODIFIED', $last_modified); } dbg_error_log('tz/updatecheck', 'Writing %s zone for "%s"', empty($tzrow) ? "new" : "updated", $tzid); printf("Writing %s zone for '%s'\n", empty($tzrow) ? "new" : "updated", $tzid); $params = array(':tzid' => $tzid, ':olson_name' => $tzid, ':vtimezone' => $vtz->Render(), ':last_modified' => $last_modified, ':etag' => md5($vtz->Render())); if (empty($tzrow)) { $new_zones++; $sql = 'INSERT INTO timezones(tzid,active,olson_name,last_modified,etag,vtimezone) '; $sql .= 'VALUES(:tzid,TRUE,:olson_name,:last_modified,:etag,:vtimezone)'; } else { $modified_zones++; $sql = 'UPDATE timezones SET active=TRUE, olson_name=:olson_name, last_modified=:last_modified, '; $sql .= 'etag=:etag, vtimezone=:vtimezone WHERE tzid=:tzid'; } $qry = new AwlQuery($sql, $params); $qry->Exec('tz/update', __LINE__, __FILE__); }
/** * Actually write the resource to the database. All checking of whether this is reasonable * should be done before this is called. * * @param DAVResource $resource The resource being written * @param string $caldav_data The actual data to be written * @param DAVResource $collection The collection containing the resource being written * @param int $author The user_no who wants to put this resource on the server * @param string $etag An etag unique for this event * @param string $put_action_type INSERT or UPDATE depending on what we are to do * @param boolean $caldav_context True, if we are responding via CalDAV, false for other ways of calling this * @param string Either 'INSERT' or 'UPDATE': the type of action we are doing * @param boolean $log_action Whether to log the fact that we are writing this into an action log (if configured) * @param string $weak_etag An etag that is NOT modified on ATTENDEE changes for this event * * @return boolean True for success, false for failure. */ function write_resource(DAVResource $resource, $caldav_data, DAVResource $collection, $author, &$etag, $put_action_type, $caldav_context, $log_action = true, $weak_etag = null) { global $tz_regex, $session; $path = $resource->bound_from(); $user_no = $collection->user_no(); $vcal = new vCalendar($caldav_data); $resources = $vcal->GetComponents('VTIMEZONE', false); // Not matching VTIMEZONE if (!isset($resources[0])) { $resource_type = 'Unknown'; /** @todo Handle writing non-calendar resources, like address book entries or random file data */ rollback_on_error($caldav_context, $user_no, $path, translate('No calendar content'), 412); return false; } else { $first = $resources[0]; if (!$first instanceof vComponent) { print $vcal->Render(); fatal('This is not a vComponent!'); } $resource_type = $first->GetType(); } $collection_id = $collection->collection_id(); $qry = new AwlQuery(); $qry->Begin(); $dav_params = array(':etag' => $etag, ':dav_data' => $caldav_data, ':caldav_type' => $resource_type, ':session_user' => $author, ':weak_etag' => $weak_etag); $calitem_params = array(':etag' => $etag); if ($put_action_type == 'INSERT') { $qry->QDo('SELECT nextval(\'dav_id_seq\') AS dav_id, null AS caldav_data'); } else { $qry->QDo('SELECT dav_id, caldav_data FROM caldav_data WHERE dav_name = :dav_name ', array(':dav_name' => $path)); } if ($qry->rows() != 1 || !($row = $qry->Fetch())) { // No dav_id? => We're toast! trace_bug('No dav_id for "%s" on %s!!!', $path, $create_resource ? 'create' : 'update'); rollback_on_error($caldav_context, $user_no, $path); return false; } $dav_id = $row->dav_id; $old_dav_data = $row->caldav_data; $dav_params[':dav_id'] = $dav_id; $calitem_params[':dav_id'] = $dav_id; $due = null; if ($first->GetType() == 'VTODO') { $due = $first->GetPValue('DUE'); } $calitem_params[':due'] = $due; $dtstart = $first->GetPValue('DTSTART'); if (empty($dtstart)) { $dtstart = $due; } $calitem_params[':dtstart'] = $dtstart; $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 { // In this case we'll construct the SQL directly as a calculation relative to :dtstart $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. * */ $dtstart_prop = $first->GetProperty('DTSTART'); $value_type = $dtstart_prop->GetParameterValue('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'; } } } $dtstamp = $first->GetPValue('DTSTAMP'); if (!isset($dtstamp) || $dtstamp == '') { // Strictly, we're dealing with an out of spec component here, but we'll try and survive $dtstamp = gmdate('Ymd\\THis\\Z'); } $calitem_params[':dtstamp'] = $dtstamp; $last_modified = $first->GetPValue('LAST-MODIFIED'); if (!isset($last_modified) || $last_modified == '') { $last_modified = $dtstamp; } $dav_params[':modified'] = $last_modified; $calitem_params[':modified'] = $last_modified; $created = $first->GetPValue('CREATED'); if ($created == '00001231T000000Z') { $created = '20001231T000000Z'; } $class = $first->GetPValue('CLASS'); /* Check and see if we should over ride the class. */ /** @todo is there some way we can move this out of this function? Or at least get rid of the need for the SQL query here. */ if (public_events_only($user_no, $path)) { $class = 'PUBLIC'; } /* * It seems that some calendar clients don't set a class... * RFC2445, 4.8.1.3: * Default is PUBLIC */ if (!isset($class) || $class == '') { $class = 'PUBLIC'; } $calitem_params[':class'] = $class; /** Calculate what timezone to set, first, if possible */ $last_olson = 'Turkmenikikamukau'; // I really hope this location doesn't exist! $tzid = GetTZID($first); if (!empty($tzid)) { $timezones = $vcal->GetComponents('VTIMEZONE'); foreach ($timezones as $k => $tz) { if ($tz->GetPValue('TZID') != $tzid) { /** * We'll skip any tz definitions that are for a TZID other than the DTSTART/DUE on the first VEVENT/VTODO */ dbg_error_log('ERROR', ' Event uses TZID[%s], skipping included TZID[%s]!', $tz->GetPValue('TZID'), $tzid); continue; } $olson = olson_from_tzstring($tzid); if (empty($olson)) { $olson = $tz->GetPValue('X-LIC-LOCATION'); if (!empty($olson)) { $olson = olson_from_tzstring($olson); } } } dbg_error_log('PUT', ' Using TZID[%s] and location of [%s]', $tzid, isset($olson) ? $olson : ''); if (!empty($olson) && $olson != $last_olson && preg_match($tz_regex, $olson)) { dbg_error_log('PUT', ' Setting timezone to %s', $olson); if ($olson != '') { $qry->QDo('SET TIMEZONE TO \'' . $olson . "'"); } $last_olson = $olson; } $params = array(':tzid' => $tzid); $qry = new AwlQuery('SELECT 1 FROM timezones WHERE tzid = :tzid', $params); if ($qry->Exec('PUT', __LINE__, __FILE__) && $qry->rows() == 0) { $params[':olson_name'] = $olson; $params[':vtimezone'] = isset($tz) ? $tz->Render() : null; $qry->QDo('INSERT INTO timezones (tzid, olson_name, active, vtimezone) VALUES(:tzid,:olson_name,false,:vtimezone)', $params); } if (!isset($olson) || $olson == '') { $olson = $tzid; } } $qry->QDo('SELECT new_sync_token(0,' . $collection_id . ')'); $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[':percent_complete'] = $first->GetPValue('PERCENT-COMPLETE'); $calitem_params[':status'] = $first->GetPValue('STATUS'); if (!$collection->IsSchedulingCollection()) { if (do_scheduling_requests($vcal, $put_action_type == 'INSERT', $old_dav_data, true)) { $dav_params[':dav_data'] = $vcal->Render(null, true); $etag = null; } } if (!isset($dav_params[':modified'])) { $dav_params[':modified'] = 'now'; } if ($put_action_type == 'INSERT') { $sql = 'INSERT INTO caldav_data ( dav_id, user_no, dav_name, dav_etag, caldav_data, caldav_type, logged_user, created, modified, collection_id, weak_etag ) VALUES( :dav_id, :user_no, :dav_name, :etag, :dav_data, :caldav_type, :session_user, :created, :modified, :collection_id, :weak_etag )'; $dav_params[':collection_id'] = $collection_id; $dav_params[':user_no'] = $user_no; $dav_params[':dav_name'] = $path; $dav_params[':created'] = isset($created) && $created != '' ? $created : $dtstamp; } else { $sql = 'UPDATE caldav_data SET caldav_data=:dav_data, dav_etag=:etag, caldav_type=:caldav_type, logged_user=:session_user, modified=:modified, weak_etag=:weak_etag WHERE dav_id=:dav_id'; } $qry = new AwlQuery($sql, $dav_params); if (!$qry->Exec('PUT', __LINE__, __FILE__)) { fatal('Insert into calendar_item failed...'); rollback_on_error($caldav_context, $user_no, $path); return false; } if ($put_action_type == 'INSERT') { $sql = <<<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, :dav_id, :etag, :uid, :dtstamp, :dtstart, {$dtend}, :summary, :location, :class, :transp, :description, :rrule, :tzid, :modified, :url, :priority, :created, :due, :percent_complete, :status, :collection_id ) EOSQL; $sync_change = 201; $calitem_params[':collection_id'] = $collection_id; $calitem_params[':user_no'] = $user_no; $calitem_params[':dav_name'] = $path; $calitem_params[':created'] = $dav_params[':created']; } else { $sql = <<<EOSQL UPDATE calendar_item SET dav_etag=:etag, uid=:uid, dtstamp=:dtstamp, dtstart=:dtstart, dtend={$dtend}, summary=:summary, location=:location, class=:class, transp=:transp, description=:description, rrule=:rrule, tz_id=:tzid, last_modified=:modified, url=:url, priority=:priority, due=:due, percent_complete=:percent_complete, status=:status WHERE dav_id=:dav_id EOSQL; $sync_change = 200; } write_alarms($dav_id, $first); write_attendees($dav_id, $vcal); if ($log_action && function_exists('log_caldav_action')) { log_caldav_action($put_action_type, $first->GetPValue('UID'), $user_no, $collection_id, $path); } else { if ($log_action) { dbg_error_log('PUT', 'No log_caldav_action( %s, %s, %s, %s, %s) can be called.', $put_action_type, $first->GetPValue('UID'), $user_no, $collection_id, $path); } } $qry = new AwlQuery($sql, $calitem_params); if (!$qry->Exec('PUT', __LINE__, __FILE__)) { rollback_on_error($caldav_context, $user_no, $path); return false; } $qry->QDo("SELECT write_sync_change( {$collection_id}, {$sync_change}, :dav_name)", array(':dav_name' => $path)); $qry->Commit(); if (function_exists('post_commit_action')) { post_commit_action($put_action_type, $first->GetPValue('UID'), $user_no, $collection_id, $path); } // Uncache anything to do with the collection $cache = getCacheInstance(); $cache_ns = 'collection-' . preg_replace('{/[^/]*$}', '/', $path); $cache->delete($cache_ns, null); dbg_error_log('PUT', 'User: %d, ETag: %s, Path: %s', $author, $etag, $path); return true; // Success! }
/** * Writes the data to a member in the collection and returns the segment_name of the * resource in our internal namespace. * * @param vCalendar $vcal The resource to be written. * @param boolean $create_resource True if this is a new resource. * @param boolean $do_scheduling True if we should also do scheduling for this write. Default false. * @param string $segment_name The name of the resource within the collection, or null if this * call should invent one based on the UID of the vCalendar. * @param boolean $log_action Whether to log this action. Defaults to false since this is normally called * in situations where one is writing secondary data. * @return string The segment_name of the resource within the collection, as written, or false on failure. */ function WriteCalendarMember(vCalendar $vcal, $create_resource, $do_scheduling = false, $segment_name = null, $log_action = false) { if (!$this->IsSchedulingCollection() && !$this->IsCalendar()) { dbg_error_log('PUT', '"%s" is not a calendar or scheduling collection!', $this->dav_name); return false; } global $session, $caldav_context; $resources = $vcal->GetComponents('VTIMEZONE', false); // Not matching VTIMEZONE $user_no = $this->user_no(); $collection_id = $this->collection_id(); if (!isset($resources[0])) { dbg_error_log('PUT', 'No calendar content!'); rollback_on_error($caldav_context, $user_no, $this->dav_name . '/' . $segment_name, translate('No calendar content'), 412); return false; } else { $first = $resources[0]; $resource_type = $first->GetType(); } $uid = $vcal->GetUID(); if (empty($segment_name)) { $segment_name = $uid . '.ics'; } $path = $this->dav_name() . $segment_name; $caldav_data = $vcal->Render(); $etag = md5($caldav_data); $weak_etag = null; $qry = new AwlQuery(); $existing_transaction_state = $qry->TransactionState(); if ($existing_transaction_state == 0) { $qry->Begin(); } if ($create_resource) { $qry->QDo('SELECT nextval(\'dav_id_seq\') AS dav_id'); } else { $qry->QDo('SELECT dav_id FROM caldav_data WHERE dav_name = :dav_name ', array(':dav_name' => $path)); } if ($qry->rows() != 1 || !($row = $qry->Fetch())) { if (!$create_resource) { // Looks like we will have to create it, even if the caller thought we wouldn't $qry->QDo('SELECT nextval(\'dav_id_seq\') AS dav_id'); if ($qry->rows() != 1 || !($row = $qry->Fetch())) { // No dav_id? => We're toast! trace_bug('No dav_id for "%s" on %s!!!', $path, $create_resource ? 'create' : 'update'); rollback_on_error($caldav_context, $user_no, $path); return false; } $create_resource = true; dbg_error_log('PUT', 'Unexpected need to create resource at "%s"', $path); } } $dav_id = $row->dav_id; $calitem_params = array(':dav_name' => $path, ':user_no' => $user_no, ':etag' => $etag, ':dav_id' => $dav_id); $dav_params = array_merge($calitem_params, array(':dav_data' => $caldav_data, ':caldav_type' => $resource_type, ':session_user' => $session->user_no, ':weak_etag' => $weak_etag)); if (!$this->IsSchedulingCollection() && $do_scheduling) { if (do_scheduling_requests($vcal, $create_resource)) { $dav_params[':dav_data'] = $vcal->Render(null, true); $etag = null; } } if ($create_resource) { $sql = 'INSERT INTO caldav_data ( dav_id, user_no, dav_name, dav_etag, caldav_data, caldav_type, logged_user, created, modified, collection_id, weak_etag ) VALUES( :dav_id, :user_no, :dav_name, :etag, :dav_data, :caldav_type, :session_user, current_timestamp, current_timestamp, :collection_id, :weak_etag )'; $dav_params[':collection_id'] = $collection_id; } else { $sql = 'UPDATE caldav_data SET caldav_data=:dav_data, dav_etag=:etag, caldav_type=:caldav_type, logged_user=:session_user, modified=current_timestamp, weak_etag=:weak_etag WHERE dav_id=:dav_id'; } if (!$qry->QDo($sql, $dav_params)) { rollback_on_error($caldav_context, $user_no, $path); return false; } $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 = preg_replace('#[PT]#', ' ', $first->GetPValue('DURATION')); $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; $class = $first->GetPValue('CLASS'); /* * It seems that some calendar clients don't set a class... * RFC2445, 4.8.1.3: Default is PUBLIC */ if ($this->IsPublicOnly() || !isset($class) || $class == '') { $class = 'PUBLIC'; } $calitem_params[':class'] = $class; /** Calculate what timezone to set, first, if possible */ $last_olson = 'Turkmenikikamukau'; // I really hope this location doesn't exist! $tzid = self::GetTZID($first); if (!empty($tzid)) { $tz = $vcal->GetTimeZone($tzid); $olson = $vcal->GetOlsonName($tz); if (!empty($olson) && $olson != $last_olson) { dbg_error_log('PUT', ' Setting timezone to %s', $olson); $qry->QDo('SET TIMEZONE TO \'' . $olson . "'"); $last_olson = $olson; } } $created = $first->GetPValue('CREATED'); if ($created == '00001231T000000Z') { $created = '20001231T000000Z'; } $calitem_params[':created'] = $created; $calitem_params[':tzid'] = $tzid; $calitem_params[':uid'] = $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'); if ($create_resource) { $sql = <<<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; $sync_change = 201; } else { $sql = <<<EOSQL UPDATE calendar_item SET dav_etag=:etag, uid=:uid, dtstamp=:dtstamp, dtstart=:dtstart, dtend={$dtend}, summary=:summary, location=:location, class=:class, transp=:transp, description=:description, rrule=:rrule, tz_id=:tzid, last_modified=:modified, url=:url, priority=:priority, created=:created, due=:due, percent_complete=:percent_complete, status=:status WHERE user_no=:user_no AND dav_name=:dav_name EOSQL; $sync_change = 200; } if (!$this->IsSchedulingCollection()) { $this->WriteCalendarAlarms($dav_id, $vcal); $this->WriteCalendarAttendees($dav_id, $vcal); $put_action_type = $create_resource ? 'INSERT' : 'UPDATE'; if ($log_action && function_exists('log_caldav_action')) { log_caldav_action($put_action_type, $first->GetPValue('UID'), $user_no, $collection_id, $path); } else { if ($log_action) { dbg_error_log('PUT', 'No log_caldav_action( %s, %s, %s, %s, %s) can be called.', $put_action_type, $first->GetPValue('UID'), $user_no, $collection_id, $path); } } } $qry = new AwlQuery($sql, $calitem_params); if (!$qry->Exec('PUT', __LINE__, __FILE__)) { rollback_on_error($caldav_context, $user_no, $path); return false; } $qry->QDo("SELECT write_sync_change( {$collection_id}, {$sync_change}, :dav_name)", array(':dav_name' => $path)); if ($existing_transaction_state == 0) { $qry->Commit(); } dbg_error_log('PUT', 'User: %d, ETag: %s, Path: %s', $session->user_no, $etag, $path); return $segment_name; }
function handle_freebusy_request($ic) { global $c, $session, $request, $ical; $request->NeedPrivilege('CALDAV:schedule-send-freebusy'); $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) { $remote = new iSchedule(); $answer = $remote->sendRequest($attendee->Value(), 'VFREEBUSY/REQUEST', $ical->Render()); if ($answer === false) { $reply->CalDAVElement($response, "request-status", "3.7;Invalid Calendar User"); $reply->CalDAVElement($response, "calendar-data"); $responses[] = $response; continue; } foreach ($answer as $a) { if ($a === false) { $reply->CalDAVElement($response, "request-status", "3.7;Invalid Calendar User"); $reply->CalDAVElement($response, "calendar-data"); } elseif (substr($a, 0, 1) >= 1) { $reply->CalDAVElement($response, "request-status", $a); $reply->CalDAVElement($response, "calendar-data"); } else { $reply->CalDAVElement($response, "request-status", "2.0;Success"); $reply->CalDAVElement($response, "calendar-data", $a); } $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 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); }
$qry = new AwlQuery($sql, $params); if (!$qry->Exec()) { exit(1); } if ($qry->rows() < 1) { $sql = 'SELECT our_tzno, tzid, active, olson_name, vtimezone, etag, '; $sql .= 'to_char(last_modified,\'Dy, DD Mon IYYY HH24:MI:SS "GMT"\') AS last_modified '; $sql .= 'FROM timezones JOIN tz_aliases USING(our_tzno) WHERE tzalias=:tzid'; if (!$qry->Exec()) { exit(1); } if ($qry->rows() < 1) { $request->DoResponse(404); } } $tz = $qry->Fetch(); $vtz = new vCalendar($tz->vtimezone); $vtz->AddProperty('TZ-URL', $c->protocol_server_port . $_SERVER['REQUEST_URI']); $vtz->AddProperty('TZNAME', $tz->olson_name); if ($qry->QDo('SELECT * FROM tz_localnames WHERE our_tzno = :our_tzno', array(':our_tzno' => $tz->our_tzno)) && $qry->rows()) { while ($name = $qry->Fetch()) { if (strpos($_SERVER['QUERY_STRING'], 'lang=' . $name->locale) !== false) { $vtz->AddProperty('TZNAME', $name->localised_name, array('LANGUAGE', str_replace('_', '-', $name->locale))); } } } header('ETag: "' . $tz->etag . '"'); header('Last-Modified: ' . $tz->last_modified); header('Content-Disposition: Attachment; Filename="' . str_replace('/', '-', $tzid . '.ics"')); $request->DoResponse(200, $vtz->Render(), 'text/calendar; charset=UTF-8'); exit(0);
function ischedule_freebusy_request($ic, $attendees, $attendees_fail) { global $c, $session, $request; $reply = new XMLDocument(array("urn:ietf:params:xml:ns:ischedule" => "I")); $icalAttendees = $ic->GetAttendees(); $responses = array(); $ical = $ic->GetComponents('VFREEBUSY'); $ical = $ical[0]; $fbq_start = $ical->GetPValue('DTSTART'); $fbq_end = $ical->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); foreach ($attendees as $k => $attendee) { $response = $reply->NewXMLElement("response", false, false, 'urn:ietf:params:xml:ns:ischedule'); $fb = get_freebusy('^' . $attendee->dav_name, $range_start, $range_end); $fb->AddProperty('UID', $ical->GetPValue('UID')); $fb->SetProperties($ical->GetProperties('ORGANIZER'), 'ORGANIZER'); foreach ($ical->GetProperties('ATTENDEE') as $at) { if ($at->Value() == 'mailto:' . $attendee->email) { $fb->AddProperty($at); } } $vcal = new vCalendar(array('METHOD' => 'REPLY')); $vcal->AddComponent($fb); $response = $reply->NewXMLElement("response", false, false, 'urn:ietf:params:xml:ns:ischedule'); $response->NewElement("recipient", 'mailto:' . $attendee->email, false, 'urn:ietf:params:xml:ns:ischedule'); $response->NewElement("request-status", "2.0;Success", false, 'urn:ietf:params:xml:ns:ischedule'); $response->NewElement("calendar-data", $vcal->Render(), false, 'urn:ietf:params:xml:ns:ischedule'); $responses[] = $response; } foreach ($attendees_fail as $k => $attendee) { $XMLresponse = $reply->NewXMLElement("response", false, false, 'urn:ietf:params:xml:ns:ischedule'); $XMLresponse->NewElement("recipient", $reply->href('mailto:' . $attendee)); $XMLresponse->NewElement("request-status", '5.3;cannot schedule this user, unknown or access denied'); $responses[] = $XMLresponse; } $response = $reply->NewXMLElement("schedule-response", $responses, $reply->GetXmlNsArray(), 'urn:ietf:params:xml:ns:ischedule'); $request->XMLResponse(200, $response); }
/** * Send an iMIP message since they look like a non-local user. * * @param string $method The METHOD parameter from the iTIP * @param string $to_email The e-mail address we're going to send to * @param vCalendar $vcal The iTIP part of the message. */ function doImipMessage($method, $to_email, vCalendar $itip) { global $c, $request; header('Debug: Sending iMIP ' . $method . ' message to ' . $to_email); $mime = new MultiPart(); $mime->addPart($itip->Render(), 'text/calendar; charset=UTF-8; method=' . $method); $friendly_part = isset($c->iMIP->template[$method]) ? $c->iMIP->template[$method] : <<<EOTEMPLATE This is a meeting ##METHOD## which your e-mail program should be able to import into your calendar. Alternatively you could save the attachment and load that into your calendar instead. EOTEMPLATE; $components = $itip->GetComponents('VTIMEZONE', false); $replaceable = array('METHOD', 'DTSTART', 'DTEND', 'SUMMARY', 'DESCRIPTION', 'URL'); foreach ($replaceable as $pname) { $search = '##' . $pname . '##'; if (strstr($friendly_part, $search) !== false) { $property = $itip->GetProperty($pname); if (empty($property)) { $property = $components[0]->GetProperty($pname); } if (empty($property)) { $replace = ''; } else { switch ($pname) { case 'DTSTART': case 'DTEND': $when = new RepeatRuleDateTime($property); $replace = $when->format('c'); break; default: $replace = $property->GetValue(); } } $friendly_part = str_replace($search, $replace, $friendly_part); } } $mime->addPart($friendly_part, 'text/plain'); $email = new EMail(); $email->SetFrom($request->principal->email()); $email->AddTo($to_email); $email->SetSubject($components[0]->GetPValue('SUMMARY')); $email->SetBody($mime->getMimeParts()); if (isset($c->iMIP->pretend_email)) { $email->Pretend($mime->getMimeHeaders()); } else { if (!isset($c->iMIP->send_email) || !$c->iMIP->send_email) { $email->PretendLog($mime->getMimeHeaders()); } else { $email->Send($mime->getMimeHeaders()); } } }
if (preg_match('{^/(\\S+@[a-z0-9][a-z0-9-]*[.][a-z0-9.-]+)/?$}i', $request->path, $matches)) { $principal = new Principal('email', $matches[1]); $path_match = '^' . $principal->dav_name(); } if (isset($fb_format) && $fb_format != 'text/calendar') { $request->DoResponse(406, translate('This server only supports the text/calendar format for freebusy URLs')); } if (!$request->HavePrivilegeTo('read-free-busy')) { $request->DoResponse(404); } require_once "freebusy-functions.php"; switch ($_SERVER['REQUEST_METHOD']) { case 'GET': $range_start = new RepeatRuleDateTime($fb_start); if (!isset($fb_end)) { $range_end = clone $range_start; $range_end->modify($fb_period); } else { $range_end = new RepeatRuleDateTime($fb_end); } $freebusy = get_freebusy($path_match, $range_start, $range_end); $result = new vCalendar(); $result->AddComponent($freebusy); $request->DoResponse(200, $result->Render(), 'text/calendar'); break; default: dbg_error_log("freebusy", "Unhandled request method >>%s<<", $_SERVER['REQUEST_METHOD']); dbg_log_array("freebusy", 'HEADERS', $raw_headers); dbg_log_array("freebusy", '_SERVER', $_SERVER, true); @dbg_error_log("freebusy", "RAW: %s", str_replace("\n", "", str_replace("\r", "", $request->raw_post))); }
/** * 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; }