static function getInstance($name) { $qry = new AwlQuery('SELECT * FROM timezones WHERE tzid = ? ORDER BY active DESC', $name); if ($qry->Exec('VTimezone', __LINE__, __FILE__) && $qry->rows() > 0 && ($row = $qry->Fetch())) { $vtz = new vComponent($row->vtimezone); if ($vtz->GetType() == 'VTIMEZONE') { return $vtz; } $tmp = $vtz->GetComponents('VTIMEZONE'); if (count($tmp) < 1 || $tmp[0]->GetType() != 'VTIMEZONE') { return null; } $vtz = $tmp[0]; return $vtz; } return null; }
/** * 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__); } }
/** * Expand the event instances for an iCalendar VEVENT (or VTODO) * * Note: expansion here does not apply modifications to instances other than modifying start/end/due/duration. * * @param object $vResource A vComponent which is a VCALENDAR containing components needing expansion * @param object $range_start A RepeatRuleDateTime which is the beginning of the range for events, default -6 weeks * @param object $range_end A RepeatRuleDateTime which is the end of the range for events, default +6 weeks * * @return vComponent The original vComponent, with the instances of the internal components expanded. */ function expand_event_instances(vComponent $vResource, $range_start = null, $range_end = null, $return_floating_times = false) { global $c; $components = $vResource->GetComponents(); $clear_instance_props = array('DTSTART' => true, 'DUE' => true, 'DTEND' => true); if (empty($c->expanded_instances_include_rrule)) { $clear_instance_props += array('RRULE' => true, 'RDATE' => true, 'EXDATE' => true); } if (empty($range_start)) { $range_start = new RepeatRuleDateTime(); $range_start->modify('-6 weeks'); } if (empty($range_end)) { $range_end = clone $range_start; $range_end->modify('+6 months'); } $instances = array(); $expand = false; $dtstart = null; $is_date = false; $has_repeats = false; $dtstart_type = 'DTSTART'; foreach ($components as $k => $comp) { if ($comp->GetType() != 'VEVENT' && $comp->GetType() != 'VTODO' && $comp->GetType() != 'VJOURNAL') { continue; } if (!isset($dtstart)) { $dtstart_prop = $comp->GetProperty($dtstart_type); if (!isset($dtstart_prop) && $comp->GetType() != 'VTODO') { $dtstart_type = 'DUE'; $dtstart_prop = $comp->GetProperty($dtstart_type); } if (!isset($dtstart_prop)) { continue; } $dtstart = new RepeatRuleDateTime($dtstart_prop); if ($return_floating_times) { $dtstart->setAsFloat(); } if (DEBUG_RRULE) { printf("Component is: %s (floating: %s)\n", $comp->GetType(), $return_floating_times ? "yes" : "no"); } $is_date = $dtstart->isDate(); $instances[$dtstart->FloatOrUTC($return_floating_times)] = $comp; $rrule = $comp->GetProperty('RRULE'); $has_repeats = isset($rrule); } $p = $comp->GetProperty('RECURRENCE-ID'); if (isset($p) && $p->Value() != '') { $range = $p->GetParameterValue('RANGE'); $recur_utc = new RepeatRuleDateTime($p); if ($is_date) { $recur_utc->setAsDate(); } $recur_utc = $recur_utc->FloatOrUTC($return_floating_times); if (isset($range) && $range == 'THISANDFUTURE') { foreach ($instances as $k => $v) { if (DEBUG_RRULE) { printf("Removing overridden instance at: {$k}\n"); } if ($k >= $recur_utc) { unset($instances[$k]); } } } else { unset($instances[$recur_utc]); } } else { if (DEBUG_RRULE) { $p = $comp->GetProperty('SUMMARY'); $summary = isset($p) ? $p->Value() : 'not set'; $p = $comp->GetProperty('UID'); $uid = isset($p) ? $p->Value() : 'not set'; printf("Processing event '%s' with UID '%s' starting on %s\n", $summary, $uid, $dtstart->FloatOrUTC($return_floating_times)); print "Instances at start"; foreach ($instances as $k => $v) { print ' : ' . $k; } print "\n"; } } $instances += rrule_expand($dtstart, 'RRULE', $comp, $range_end, null, $return_floating_times); if (DEBUG_RRULE) { print "After rrule_expand"; foreach ($instances as $k => $v) { print ' : ' . $k; } print "\n"; } $instances += rdate_expand($dtstart, 'RDATE', $comp, $range_end, null, $return_floating_times); if (DEBUG_RRULE) { print "After rdate_expand"; foreach ($instances as $k => $v) { print ' : ' . $k; } print "\n"; } foreach (rdate_expand($dtstart, 'EXDATE', $comp, $range_end, null, $return_floating_times) as $k => $v) { unset($instances[$k]); } if (DEBUG_RRULE) { print "After exdate_expand"; foreach ($instances as $k => $v) { print ' : ' . $k; } print "\n"; } } $last_duration = null; $early_start = null; $new_components = array(); $start_utc = $range_start->FloatOrUTC($return_floating_times); $end_utc = $range_end->FloatOrUTC($return_floating_times); foreach ($instances as $utc => $comp) { if ($utc > $end_utc) { if (DEBUG_RRULE) { printf("We're done: {$utc} is out of the range.\n"); } break; } $end_type = $comp->GetType() == 'VTODO' ? 'DUE' : 'DTEND'; $duration = $comp->GetProperty('DURATION'); if (!isset($duration) || $duration->Value() == '') { $instance_start = $comp->GetProperty($dtstart_type); $dtsrt = new RepeatRuleDateTime($instance_start); if ($return_floating_times) { $dtsrt->setAsFloat(); } $instance_end = $comp->GetProperty($end_type); if (isset($instance_end)) { $dtend = new RepeatRuleDateTime($instance_end); $duration = Rfc5545Duration::fromTwoDates($dtsrt, $dtend); } else { if ($instance_start->GetParameterValue('VALUE') == 'DATE') { $duration = new Rfc5545Duration('P1D'); } else { $duration = new Rfc5545Duration(0); } } } else { $duration = new Rfc5545Duration($duration->Value()); } if ($utc < $start_utc) { if (isset($early_start) && isset($last_duration) && $duration->equals($last_duration)) { if ($utc < $early_start) { if (DEBUG_RRULE) { printf("Next please: {$utc} is before {$early_start} and before {$start_utc}.\n"); } continue; } } else { /** Calculate the latest possible start date when this event would overlap our range start */ $latest_start = clone $range_start; $latest_start->modify('-' . $duration); $early_start = $latest_start->FloatOrUTC($return_floating_times); $last_duration = $duration; if ($utc < $early_start) { if (DEBUG_RRULE) { printf("Another please: {$utc} is before {$early_start} and before {$start_utc}.\n"); } continue; } } } $component = clone $comp; $component->ClearProperties($clear_instance_props); $component->AddProperty($dtstart_type, $utc, $is_date ? array('VALUE' => 'DATE') : null); $component->AddProperty('DURATION', $duration); if ($has_repeats && $dtstart->FloatOrUTC($return_floating_times) != $utc) { $component->AddProperty('RECURRENCE-ID', $utc, $is_date ? array('VALUE' => 'DATE') : null); } $new_components[$utc] = $component; } // Add overriden instances foreach ($components as $k => $comp) { $p = $comp->GetProperty('RECURRENCE-ID'); if (isset($p) && $p->Value() != '') { $recurrence_id = $p->Value(); if (!isset($new_components[$recurrence_id])) { // The component we're replacing is outside the range. Unless the replacement // is *in* the range we will move along to the next one. $dtstart_prop = $comp->GetProperty($dtstart_type); if (!isset($dtstart_prop)) { continue; } // No start: no expansion. Note that we consider 'DUE' to be a start if DTSTART is missing $dtstart = new RepeatRuleDateTime($dtstart_prop); $is_date = $dtstart->isDate(); if ($return_floating_times) { $dtstart->setAsFloat(); } $dtstart = $dtstart->FloatOrUTC($return_floating_times); if ($dtstart > $end_utc) { continue; } // Start after end of range, skip it $end_type = $comp->GetType() == 'VTODO' ? 'DUE' : 'DTEND'; $duration = $comp->GetProperty('DURATION'); if (!isset($duration) || $duration->Value() == '') { $instance_end = $comp->GetProperty($end_type); if (isset($instance_end)) { $dtend = new RepeatRuleDateTime($instance_end); if ($return_floating_times) { $dtend->setAsFloat(); } $dtend = $dtend->FloatOrUTC($return_floating_times); } else { $dtend = $dtstart + ($is_date ? $dtstart + 86400 : 0); } } else { $duration = new Rfc5545Duration($duration->Value()); $dtend = $dtstart + $duration->asSeconds(); } if ($dtend < $start_utc) { continue; } // End before start of range: skip that too. } if (DEBUG_RRULE) { printf("Replacing overridden instance at %s\n", $recurrence_id); } $new_components[$recurrence_id] = $comp; } } $vResource->SetComponents($new_components); return $vResource; }
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 update_caldav_data($old_data, $dav_id) { $vResource = new vComponent($old_data); //$expanded = expand_event_instances($vResource, $expand_range_start, $expand_range_end); $event = $vResource->GetComponents("VEVENT")[0]; $attendeeName = "ATTENDEE"; $vResource->ClearProperties($attendeeName); $davIdArray = array(':dav_id' => $dav_id); $attendeeQry = new AwlQuery("SELECT params, attendee FROM calendar_attendee WHERE dav_id = :dav_id", $davIdArray); $attendeeQry->Execute(); while ($arow = $attendeeQry->Fetch()) { $attendeeParameters = $arow->params; $attendeeValue = $arow->attendee; // separe value $event->AddProperty($attendeeName, $attendeeValue, $attendeeParameters); } $rendered = $vResource->Render(); $sql = 'UPDATE caldav_data SET caldav_data=:dav_data, dav_etag=:etag WHERE dav_id=:dav_id'; $davIdArray[':etag'] = md5($rendered); $davIdArray[':dav_data'] = $rendered; $query = new AwlQuery($sql, $davIdArray); $query->Execute(); }