/** * 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_calendar_collection($ics_content, $user_no, $path, $caldav_context, $appending = false) { global $c, $session, $tz_regex; $calendar = new vComponent($ics_content); $timezones = $calendar->GetComponents('VTIMEZONE', true); $components = $calendar->GetComponents('VTIMEZONE', false); // Add a parameter to calendars on import so it will only load events 'after' @author karora // date, or an RFC5545 duration format offset from the current date. $after = null; if (isset($_GET['after'])) { $after = $_GET['after']; if (strtoupper(substr($after, 0, 1)) == 'P' || strtoupper(substr($after, 0, 1)) == '-P') { $duration = new Rfc5545Duration($after); $duration = $duration->asSeconds(); $after = time() - abs($duration); } else { $after = new RepeatRuleDateTime($after); $after = $after->epoch(); } } $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 vComponent */ $resources = array(); foreach ($components as $k => $comp) { $uid = $comp->GetPValue('UID'); if ($uid == null || $uid == '') { $uid = uuid(); $comp->AddProperty('UID', $uid); dbg_error_log('LOG WARN', ' PUT: New collection resource does not have a UID - we assign one!'); } if (!isset($resources[$uid])) { $resources[$uid] = array(); } $resources[$uid][] = $comp; /** Ensure we have the timezone component for this in our array as well */ $tzid = GetTZID($comp); if (!empty($tzid) && !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(); $collection_id = $collection->collection_id; // Fetch the current collection data $qry->QDo('SELECT dav_name, caldav_data FROM caldav_data WHERE collection_id=:collection_id', array(':collection_id' => $collection_id)); $current_data = array(); while ($row = $qry->Fetch()) { $current_data[$row->dav_name] = $row->caldav_data; } if (!(isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import)) { $qry->Begin(); } $base_params = array(':collection_id' => $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; $dav_data_update = <<<EOSQL UPDATE caldav_data SET user_no=:user_no, caldav_data=:dav_data, dav_etag=:etag, caldav_type=:caldav_type, logged_user=:session_user, modified=current_timestamp WHERE collection_id=:collection_id AND dav_name=:dav_name 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; $calitem_update = <<<EOSQL UPDATE calendar_item SET user_no=:user_no, 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 collection_id=:collection_id AND dav_name=:dav_name EOSQL; $last_olson = ''; if (count($resources) > 0) { $qry->QDo('SELECT new_sync_token(0,' . $collection_id . ')'); } foreach ($resources as $uid => $resource) { /** Construct the VCALENDAR data */ $vcal = new vCalendar(); $vcal->SetComponents($resource); $icalendar = $vcal->Render(); $dav_name = sprintf('%s%s.ics', $path, preg_replace('{[&?\\/@%+:]}', '', $uid)); /** Do we need to do anything? */ $inserting = true; if (isset($current_data[$dav_name])) { if ($icalendar == $current_data[$dav_name]) { unset($current_data[$dav_name]); continue; } $sync_change = 200; unset($current_data[$dav_name]); $inserting = false; } else { $sync_change = 201; } if (isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import) { $qry->Begin(); } /** 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; // We don't allow any of &?\/@%+: in the UID to appear in the path, but anything else is fair game. $dav_data_params[':dav_name'] = $dav_name; $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; $dtstart = $first->GetPValue('DTSTART'); $calitem_params[':dtstart'] = $dtstart; if ((!isset($dtstart) || $dtstart == '') && $first->GetPValue('DUE') != '') { $dtstart = $first->GetPValue('DUE'); if (isset($after)) { $dtstart_date = new RepeatRuleDateTime($first->GetProperty('DUE')); } } else { if (isset($after)) { $dtstart_date = new RepeatRuleDateTime($first->GetProperty('DTSTART')); } } $calitem_params[':rrule'] = $first->GetPValue('RRULE'); // Skip it if it's after our start date for this import. if (isset($after) && empty($calitem_params[':rrule']) && $dtstart_date->epoch() < $after) { continue; } // Do we actually need to do anything? $inserting = true; if (isset($current_data[$dav_name])) { if ($icalendar == $current_data[$dav_name]) { if ($after == null) { unset($current_data[$dav_name]); continue; } } $sync_change = 200; unset($current_data[$dav_name]); $inserting = false; } else { $sync_change = 201; } // Write to the caldav_data table if (!$qry->QDo($inserting ? $dav_data_insert : $dav_data_update, $dav_data_params)) { rollback_on_error($caldav_context, $user_no, $path); } // Get the dav_id for this row $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; } $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. * */ $dtstart_prop = $first->GetProperty('DTSTART'); if (empty($dtstart_prop)) { dbg_error_log('PUT', 'Invalid VEVENT without DTSTART, UID="%s" in collection %d', $uid, $collection_id); continue; } $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'; } } } $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 (empty($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 = GetTZID($first); if (!empty($tzid) && !empty($resource[$tzid])) { $tz = $resource[$tzid]; $olson = $vcal->GetOlsonName($tz); 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); $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; $params[':last_modified'] = isset($tz) ? $tz->GetPValue('LAST-MODIFIED') : null; if (empty($params[':last_modified'])) { $params[':last_modified'] = gmdate('Ymd\\THis\\Z'); } $qry->QDo('INSERT INTO timezones (tzid, olson_name, active, vtimezone, last_modified) VALUES(:tzid,:olson_name,false,:vtimezone,:last_modified)', $params); } } else { $tz = $olson = $tzid = null; } $sql = str_replace('##dtend##', $dtend, $inserting ? $calitem_insert : $calitem_update); $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[':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 ($inserting) { $created = $first->GetPValue('CREATED'); if ($created == '00001231T000000Z') { $created = '20001231T000000Z'; } $calitem_params[':created'] = $created; } // Write the calendar_item row for this entry if (!$qry->QDo($sql, $calitem_params)) { rollback_on_error($caldav_context, $user_no, $path); } write_alarms($dav_id, $first); write_attendees($dav_id, $vcal); $qry->QDo("SELECT write_sync_change( {$collection_id}, {$sync_change}, :dav_name)", array(':dav_name' => $dav_name)); do_scheduling_requests($vcal, true); if (isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import) { $qry->Commit(); } } if (!$appending && count($current_data) > 0) { $params = array(':collection_id' => $collection_id); if (isset($c->skip_bad_event_on_import) && $c->skip_bad_event_on_import) { $qry->Begin(); } foreach ($current_data as $dav_name => $data) { $params[':dav_name'] = $dav_name; $qry->QDo('DELETE FROM caldav_data WHERE collection_id = :collection_id AND dav_name = :dav_name', $params); $qry->QDo('SELECT write_sync_change(:collection_id, 404, :dav_name)', $params); } 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); } } }
case 'urn:ietf:params:xml:ns:caldav:calendar-timezone': if ($dav_resource->IsCollection() && $dav_resource->IsCalendar() && !$dav_resource->IsBinding()) { $tzcomponent = $setting->GetPath('urn:ietf:params:xml:ns:caldav:calendar-timezone'); $tzstring = $tzcomponent[0]->GetContent(); $calendar = new vCalendar($tzstring); $timezones = $calendar->GetComponents('VTIMEZONE'); if (count($timezones) == 0) { break; } $tz = $timezones[0]; // Backward compatibility $tzid = $tz->GetPValue('TZID'); $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'] = $calendar->GetOlsonName($tz); $params[':vtimezone'] = isset($tz) ? $tz->Render() : null; $qry->QDo('INSERT INTO timezones (tzid, olson_name, active, vtimezone) VALUES(:tzid,:olson_name,false,:vtimezone)', $params); } $qry->QDo('UPDATE collection SET timezone = :tzid WHERE dav_name = :dav_name', array(':tzid' => $tzid, ':dav_name' => $dav_resource->dav_name())); } else { add_failure('set', $tag, 'HTTP/1.1 409 Conflict', translate("calendar-timezone property is only valid for a calendar.")); } break; /** * The following properties are read-only, so they will cause the request to fail */ /** * The following properties are read-only, so they will cause the request to fail */ case 'http://calendarserver.org/ns/:getctag':