Пример #1
0
/**
* 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);
        }
    }
}