/** * Given a dav_id and an original vCalendar, pull out each of the VALARMs * and write the values into the calendar_alarm table. * * @param int $dav_id The dav_id of the caldav_data we're processing * @param vComponent The VEVENT or VTODO containing the VALARM * @return null */ function write_alarms($dav_id, vComponent $ical) { $qry = new AwlQuery('DELETE FROM calendar_alarm WHERE dav_id = ' . $dav_id); $qry->Exec('PUT', __LINE__, __FILE__); $alarms = $ical->GetComponents('VALARM'); if (count($alarms) < 1) { return; } $qry->SetSql('INSERT INTO calendar_alarm ( dav_id, action, trigger, summary, description, component, next_trigger ) VALUES( ' . $dav_id . ', :action, :trigger, :summary, :description, :component, :related::timestamp with time zone + :related_trigger::interval )'); $qry->Prepare(); foreach ($alarms as $v) { $trigger = array_merge($v->GetProperties('TRIGGER')); if ($trigger == null) { continue; } // Bogus data. $trigger = $trigger[0]; $related = null; $related_trigger = '0M'; $trigger_type = $trigger->GetParameterValue('VALUE'); if (!isset($trigger_type) || $trigger_type == 'DURATION') { switch ($trigger->GetParameterValue('RELATED')) { case 'DTEND': $related = $ical->GetProperty('DTEND'); break; case 'DUE': $related = $ical->GetProperty('DUE'); break; default: $related = $ical->GetProperty('DTSTART'); } $duration = $trigger->Value(); if (!preg_match('{^-?P(:?\\d+W)?(:?\\d+D)?(:?T(:?\\d+H)?(:?\\d+M)?(:?\\d+S)?)?$}', $duration)) { continue; } $minus = substr($duration, 0, 1) == '-'; $related_trigger = trim(preg_replace('#[PT-]#', ' ', $duration)); if ($minus) { $related_trigger = preg_replace('{(\\d+[WDHMS])}', '-$1 ', $related_trigger); } else { $related_trigger = preg_replace('{(\\d+[WDHMS])}', '$1 ', $related_trigger); } } else { if ($trigger_type == 'DATE-TIME') { $related = $trigger; } else { if (false === strtotime($trigger->Value())) { continue; } // Invalid date. $related = $trigger; } } $related_date = new RepeatRuleDateTime($related); $qry->Bind(':action', $v->GetPValue('ACTION')); $qry->Bind(':trigger', $trigger->Render()); $qry->Bind(':summary', $v->GetPValue('SUMMARY')); $qry->Bind(':description', $v->GetPValue('DESCRIPTION')); $qry->Bind(':component', $v->Render()); $qry->Bind(':related', $related_date->UTC()); $qry->Bind(':related_trigger', $related_trigger); $qry->Exec('PUT', __LINE__, __FILE__); } }
function get_freebusy($path_match, $range_start, $range_end, $bin_privs = null) { global $request, $c; $debugging = false; // if ( $debugging ) { // 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) "; } $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'," . AWLDatabase::SqlUTCFormat . ') AS start, '; $sql .= "to_char(calendar_item.dtend at time zone 'GMT'," . AWLDatabase::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 (isset($c->_workaround_client_freebusy_bug) && $c->_workaround_client_freebusy_bug) { $extra = ';BUSY'; } } // 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)); // if ( $debugging ) echo "=================== $calendar_object->dav_id ========================\n"; $dtstart_type = 'DTSTART'; foreach ($expansion as $k => $v) { // if ( $debugging ) print $k."\n".$v->Render(); $start_date = $v->GetProperty($dtstart_type); if (!isset($start_date) && $v->GetType() != 'VTODO') { $dtstart_type = 'DUE'; $start_date = $v->GetProperty($dtstart_type); } $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) { // if ( $debugging ) // echo "-----------------------------------------------------\n"; continue; } // if ( $debugging ) // echo "+++++++++++++++++++++++++++++++++++++++++++++++++++++\n"; $thisfb = $start_date->UTC() . '/' . $end_date->UTC() . $extra; array_push($fbtimes, $thisfb); } } } $freebusy = new vComponent(); $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; }
function caldav_get_feed($request, $collection) { global $c, $session; dbg_error_log("feed", "GET method handler"); $collection->NeedPrivilege(array('DAV::read')); if (!$collection->Exists()) { $request->DoResponse(404, translate("Resource Not Found.")); } if (!$collection->IsCollection() || !$collection->IsCalendar() && !(isset($c->get_includes_subcollections) && $c->get_includes_subcollections)) { $request->DoResponse(405, translate("Feeds are only supported for calendars at present.")); } // Try and pull the answer out of a hat $cache = getCacheInstance(); $cache_ns = 'collection-' . $collection->dav_name(); $cache_key = 'feed' . $session->user_no; $response = $cache->get($cache_ns, $cache_key); if ($response !== false) { return $response; } $principal = $collection->GetProperty('principal'); /** * 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. */ $sql = 'SELECT caldav_data, caldav_type, caldav_data.user_no, caldav_data.dav_name,'; $sql .= ' caldav_data.modified, caldav_data.created, '; $sql .= ' summary, dtstart, dtend, calendar_item.description '; $sql .= ' FROM collection INNER JOIN caldav_data USING(collection_id) INNER JOIN calendar_item USING ( dav_id ) WHERE '; if (isset($c->get_includes_subcollections) && $c->get_includes_subcollections) { $sql .= ' (collection.dav_name ~ :path_match '; $sql .= ' OR collection.collection_id IN (SELECT bound_source_id FROM dav_binding WHERE dav_binding.dav_name ~ :path_match)) '; $params = array(':path_match' => '^' . $request->path); } else { $sql .= ' caldav_data.collection_id = :collection_id '; $params = array(':collection_id' => $collection->resource_id()); } $sql .= ' ORDER BY caldav_data.created DESC'; $sql .= ' LIMIT ' . (isset($c->feed_item_limit) ? $c->feed_item_limit : 15); $qry = new AwlQuery($sql, $params); if (!$qry->Exec("GET", __LINE__, __FILE__)) { $request->DoResponse(500, translate("Database Error")); } /** * Here we are constructing the feed response for this collection, including * the timezones that are referred to by the events we have selected. * Library used: http://framework.zend.com/manual/en/zend.feed.writer.html */ require_once 'AtomFeed.php'; $feed = new AtomFeed(); $feed->setTitle('DAViCal Atom Feed: ' . $collection->GetProperty('displayname')); $url = $c->protocol_server_port . $collection->url(); $url = preg_replace('{/$}', '.ics', $url); $feed->setLink($url); $feed->setFeedLink($c->protocol_server_port_script . $request->path, 'atom'); $feed->addAuthor(array('name' => $principal->GetProperty('displayname'), 'email' => $principal->GetProperty('email'), 'uri' => $c->protocol_server_port . $principal->url())); $feed_description = $collection->GetProperty('description'); if (isset($feed_description) && $feed_description != '') { $feed->setDescription($feed_description); } require_once 'RRule-v2.php'; $need_zones = array(); $timezones = array(); while ($event = $qry->Fetch()) { if ($event->caldav_type != 'VEVENT' && $event->caldav_type != 'VTODO' && $event->caldav_type != 'VJOURNAL') { dbg_error_log('feed', 'Skipping peculiar "%s" component in VCALENDAR', $event->caldav_type); continue; } $is_todo = $event->caldav_type == 'VTODO'; $ical = new vComponent($event->caldav_data); $event_data = $ical->GetComponents('VTIMEZONE', false); $item = $feed->createEntry(); $item->setId($c->protocol_server_port_script . ConstructURL($event->dav_name)); $dt_created = new RepeatRuleDateTime($event->created); $item->setDateCreated($dt_created->epoch()); $dt_modified = new RepeatRuleDateTime($event->modified); $item->setDateModified($dt_modified->epoch()); $summary = $event->summary; $p_title = $summary != '' ? $summary : translate('No summary'); if ($is_todo) { $p_title = "TODO: " . $p_title; } $item->setTitle($p_title); $content = ""; $dt_start = new RepeatRuleDateTime($event->dtstart); if ($dt_start != null) { $p_time = '<strong>' . translate('Time') . ':</strong> ' . strftime(translate('%F %T'), $dt_start->epoch()); $dt_end = new RepeatRuleDateTime($event->dtend); if ($dt_end != null) { $p_time .= ' - ' . ($dt_end->AsDate() == $dt_start->AsDate() ? strftime(translate('%T'), $dt_end->epoch()) : strftime(translate('%F %T'), $dt_end->epoch())); } $content .= $p_time; } $p_location = $event_data[0]->GetProperty('LOCATION'); if ($p_location != null) { $content .= '<br />' . '<strong>' . translate('Location') . '</strong>: ' . hyperlink($p_location->Value()); } $p_attach = $event_data[0]->GetProperty('ATTACH'); if ($p_attach != null) { $content .= '<br />' . '<strong>' . translate('Attachment') . '</strong>: ' . hyperlink($p_attach->Value()); } $p_url = $event_data[0]->GetProperty('URL'); if ($p_url != null) { $content .= '<br />' . '<strong>' . translate('URL') . '</strong>: ' . hyperlink($p_url->Value()); } $p_cat = $event_data[0]->GetProperty('CATEGORIES'); if ($p_cat != null) { $content .= '<br />' . '<strong>' . translate('Categories') . '</strong>: ' . $p_cat->Value(); $categories = explode(',', $p_cat->Value()); foreach ($categories as $category) { $item->addCategory(array('term' => trim($category))); } } $p_description = $event->description; if ($p_description != '') { $content .= '<br />' . '<br />' . '<strong>' . translate('Description') . '</strong>:<br />' . nl2br(hyperlink($p_description)); $item->setDescription($p_description); } $item->setContent($content); $feed->addEntry($item); //break; } $last_modified = new RepeatRuleDateTime($collection->GetProperty('modified')); $feed->setDateModified($last_modified->epoch()); $response = $feed->export('atom'); $cache->set($cache_ns, $cache_key, $response); return $response; }
function setDate($year = null, $month = null, $day = null) { if (!isset($year)) { $year = parent::format('Y'); } if (!isset($month)) { $month = parent::format('m'); } if (!isset($day)) { $day = parent::format('d'); } if ($day < 0) { $day += RepeatRuleDateTime::daysInMonth($year, $month) + 1; } parent::setDate($year, $month, $day); return $this; }
public function __construct($date = null, $dtz = null) { if (!isset(self::$UTCzone)) { self::$UTCzone = new RepeatRuleTimeZone('UTC'); } $this->is_date = false; if (!isset($date)) { $date = date('Ymd\\THis'); // Floating $dtz = self::$UTCzone; $this->tzid = null; } if (is_object($date) && method_exists($date, 'GetParameterValue')) { $tzid = $date->GetParameterValue('TZID'); $actual_date = $date->Value(); if (isset($tzid)) { $dtz = new RepeatRuleTimeZone($tzid); $this->tzid = $dtz->tzid(); } else { $dtz = self::$UTCzone; if (substr($actual_date, -1) == 'Z') { $this->tzid = 'UTC'; $actual_date = substr($actual_date, 0, strlen($actual_date) - 1); } } if (strlen($actual_date) == 8) { // We allow dates without VALUE=DATE parameter, but we don't create them like that $this->is_date; } $date = $actual_date; if (DEBUG_RRULE) { printf("Date%s property%s: %s%s\n", $this->is_date ? "" : "Time", isset($this->tzid) ? ' with timezone' : '', $date, isset($this->tzid) ? ' in ' . $this->tzid : ''); } } elseif (preg_match('/;TZID= ([^:;]+) (?: ;.* )? : ( \\d{8} (?:T\\d{6})? ) (Z)?/x', $date, $matches)) { $date = $matches[2]; $this->is_date = strlen($date) == 8; if (isset($matches[3]) && $matches[3] == 'Z') { $dtz = self::$UTCzone; $this->tzid = 'UTC'; } else { if (isset($matches[1]) && $matches[1] != '') { $dtz = new RepeatRuleTimeZone($matches[1]); $this->tzid = $dtz->tzid(); } else { $dtz = self::$UTCzone; $this->tzid = null; } } if (DEBUG_RRULE) { printf("Date%s property%s: %s%s\n", $this->is_date ? "" : "Time", isset($this->tzid) ? ' with timezone' : '', $date, isset($this->tzid) ? ' in ' . $this->tzid : ''); } } elseif (($dtz === null || $dtz == '') && preg_match('{;VALUE=DATE (?:;[^:]+) : ((?:[12]\\d{3}) (?:0[1-9]|1[012]) (?:0[1-9]|[12]\\d|3[01]Z?) )$}x', $date, $matches)) { $this->is_date = true; $date = $matches[1]; // Floating $dtz = self::$UTCzone; $this->tzid = null; if (DEBUG_RRULE) { printf("Floating Date value: %s\n", $date); } } elseif ($dtz === null || $dtz == '') { $dtz = self::$UTCzone; if (preg_match('/(\\d{8}(T\\d{6})?)(Z?)/', $date, $matches)) { $date = $matches[1]; $this->tzid = $matches[3] == 'Z' ? 'UTC' : null; } $this->is_date = strlen($date) == 8; if (DEBUG_RRULE) { printf("Date%s value with timezone: %s in %s\n", $this->is_date ? "" : "Time", $date, $this->tzid); } } elseif (is_string($dtz)) { $dtz = new RepeatRuleTimeZone($dtz); $this->tzid = $dtz->tzid(); $type = gettype($date); if (DEBUG_RRULE) { printf("Date%s {$type} with timezone: %s in %s\n", $this->is_date ? "" : "Time", $date, $this->tzid); } } else { $this->tzid = $dtz->getName(); $type = gettype($date); if (DEBUG_RRULE) { printf("Date%s {$type} with timezone: %s in %s\n", $this->is_date ? "" : "Time", $date, $this->tzid); } } parent::__construct($date, $dtz); return $this; }
/** * Expand the instances for a STANDARD or DAYLIGHT component of a VTIMEZONE * * @param object $vResource is a VCALENDAR with a VTIMEZONE containing components needing expansion * @param object $range_start A RepeatRuleDateTime which is the beginning of the range for events. * @param object $range_end A RepeatRuleDateTime which is the end of the range for events. * @param int $offset_from The offset from UTC in seconds at the onset time. * * @return array of onset datetimes with UTC from/to offsets */ function expand_timezone_onsets(vCalendar $vResource, RepeatRuleDateTime $range_start, RepeatRuleDateTime $range_end) { global $c; $vtimezones = $vResource->GetComponents(); $vtz = $vtimezones[0]; $components = $vtz->GetComponents(); $instances = array(); $dtstart = null; $is_date = false; $has_repeats = false; $zone_tz = $vtz->GetPValue('TZID'); foreach ($components as $k => $comp) { if (DEBUG_EXPAND) { printf("Starting TZ expansion for component '%s' in timezone '%s'\n", $comp->GetType(), $zone_tz); foreach ($instances as $k => $v) { print ' : ' . $k; } print "\n"; } $dtstart_prop = $comp->GetProperty('DTSTART'); if (!isset($dtstart_prop)) { continue; } $dtstart = new RepeatRuleDateTime($dtstart_prop); $dtstart->setTimeZone('UTC'); $offset_from = $comp->GetPValue('TZOFFSETFROM'); $offset_from = $offset_from / 100 * 3600 + abs($offset_from) % 100 * 60 * ($offset_from < 0 ? -1 : 0); $offset_from *= -1; $offset_from = "{$offset_from} seconds"; dbg_error_log('tz/update', "%s of offset\n", $offset_from); $dtstart->modify($offset_from); $is_date = $dtstart->isDate(); $instances[$dtstart->UTC('Y-m-d\\TH:i:s\\Z')] = $comp; $rrule = $comp->GetProperty('RRULE'); $has_repeats = isset($rrule); if (!$has_repeats) { continue; } $recur = $comp->GetProperty('RRULE'); if (isset($recur)) { $recur = $recur->Value(); $this_start = clone $dtstart; $rule = new RepeatRule($this_start, $recur, $is_date); $i = 0; $result_limit = 1000; while ($date = $rule->next()) { $instances[$date->UTC('Y-m-d\\TH:i:s\\Z')] = $comp; if ($i++ >= $result_limit || $date > $range_end) { break; } } if (DEBUG_EXPAND) { print "After rrule_expand"; foreach ($instances as $k => $v) { print ' : ' . $k; } print "\n"; } } $properties = $comp->GetProperties('RDATE'); if (count($properties)) { foreach ($properties as $p) { $timezone = $p->GetParameterValue('TZID'); $rdate = $p->Value(); $rdates = explode(',', $rdate); foreach ($rdates as $k => $v) { $rdate = new RepeatRuleDateTime($v, $timezone, $is_date); if ($return_floating_times) { $rdate->setAsFloat(); } $instances[$rdate->UTC('Y-m-d\\TH:i:s\\Z')] = $comp; if ($rdate > $range_end) { break; } } } if (DEBUG_EXPAND) { print "After rdate_expand"; foreach ($instances as $k => $v) { print ' : ' . $k; } print "\n"; } } } ksort($instances); $onsets = array(); $start_utc = $range_start->UTC('Y-m-d\\TH:i:s\\Z'); $end_utc = $range_end->UTC('Y-m-d\\TH:i:s\\Z'); foreach ($instances as $utc => $comp) { if ($utc > $end_utc) { if (DEBUG_EXPAND) { printf("We're done: {$utc} is out of the range.\n"); } break; } if ($utc < $start_utc) { continue; } $onsets[$utc] = array('from' => $comp->GetPValue('TZOFFSETFROM'), 'to' => $comp->GetPValue('TZOFFSETTO'), 'name' => $comp->GetPValue('TZNAME'), 'type' => $comp->GetType()); } return $onsets; }
while ($alarm = $qry->Fetch()) { if ($args->debug) { printf("refresh: Processing alarm for '%s' based on '%s','%s', '%s'\n", $alarm->dav_name, $alarm->dtstart, $alarm->rrule, $alarm->trigger); } $ic = new vComponent($alarm->caldav_data); $expanded = expand_event_instances($ic, $earliest, $expand_range_end); $expanded->MaskComponents(array('VEVENT' => 1, 'VTODO' => 1, 'VJOURNAL' => 1)); $instances = $expanded->GetComponents(); $trigger = new vProperty($alarm->trigger); $related = $trigger->GetParameterValue('RELATED'); $first = new RepeatRuleDateTime($alarm->dtstart); $first->modify($trigger->Value()); $next = null; $last = null; foreach ($instances as $k => $component) { $when = new RepeatRuleDateTime($component->GetPValue('DTSTART')); // a UTC value if ($args->debug) { printf("refresh: Looking at event instance on '%s'\n", $when->UTC()); } if ($related == 'END') { $when->modify($component->GetPValue('DURATION')); } $when->modify($trigger->Value()); if ($when > $expand_range_start && $when < $expand_range_end && (!isset($next) || $when < $next)) { $next = clone $when; } if ($args->set_last && (!isset($last) || $when > $last)) { $last = clone $when; } }
/** * 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()); } } }
require_once 'AwlCache.php'; require_once 'RRule-v2.php'; require_once 'vCalendar.php'; /** * Essentially what we are doing is: * DELETE FROM caldav_data JOIN calendar_item USING(dav_id) WHERE dav_name LIKE '/someprincipal/%' AND ( (rrule IS NULL AND dtend < archive_before_date) OR (last_instance_end < archive_before_date) ) */ $recent = new RepeatRuleDateTime(gmdate('Ymd\\THis\\Z')); $recent->modify('P-6D'); $archive_before_date = new RepeatRuleDateTime(gmdate('Ymd\\THis\\Z')); $archive_before_date->modify($args->older); if ($archive_before_date > $recent) { echo "Cowardly refusing to archive events before " + $archive_before_date->format('Y-m-d H:i:s') + "\n"; } if ($args->debug) { printf("Archiving event instances finished before '%s'\n", $archive_before_date->UTC()); } // SQL to create an archive collection but only if it doesn't exist already. $archive_collection_sql = <<<EOSQL INSERT INTO collection (user_no, parent_container, dav_name, dav_etag, dav_displayname, is_calendar, created, modified, public_events_only, publicly_readable, is_addressbook, resourcetypes, schedule_transp, timezone, description) SELECT user_no, parent_container, :archive_dav_name, random(), 'Archive of ' || dav_displayname, true, current_timestamp, current_timestamp, false, false, false, resourcetypes, schedule_transp, timezone,