/** * Return a RepeatRuleDateRange from the earliest start to the latest end of the event. * * @todo: This should probably be made part of the VCalendar object when we move the RRule.php into AWL. * * @param object $vResource A vComponent which is a VCALENDAR containing components needing expansion * @return RepeatRuleDateRange Representing the range of time covered by the event. */ function getVCalendarRange($vResource) { global $c; $components = $vResource->GetComponents(); $dtstart = null; $duration = null; $earliest_start = null; $latest_end = null; $has_repeats = false; foreach ($components as $k => $comp) { if ($comp->GetType() == 'VTIMEZONE') { continue; } $range = getComponentRange($comp); $dtstart = $range->from; if (!isset($dtstart)) { continue; } $duration = $range->getDuration(); $rrule = $comp->GetProperty('RRULE'); $limited_occurrences = true; if (isset($rrule)) { $rule = new RepeatRule($dtstart, $rrule); $limited_occurrences = $rule->hasLimitedOccurrences(); } if ($limited_occurrences) { $instances = array(); $instances[$dtstart->FloatOrUTC()] = $dtstart; if (!isset($range_end)) { $range_end = new RepeatRuleDateTime(); $range_end->modify('+150 years'); } $instances += rrule_expand($dtstart, 'RRULE', $comp, $range_end); $instances += rdate_expand($dtstart, 'RDATE', $comp, $range_end); foreach (rdate_expand($dtstart, 'EXDATE', $comp, $range_end) as $k => $v) { unset($instances[$k]); } if (count($instances) < 1) { if (empty($earliest_start) || $dtstart < $earliest_start) { $earliest_start = $dtstart; } $latest_end = null; break; } $instances = array_keys($instances); asort($instances); $first = new RepeatRuleDateTime($instances[0]); $last = new RepeatRuleDateTime($instances[count($instances) - 1]); $last->modify($duration); if (empty($earliest_start) || $first < $earliest_start) { $earliest_start = $first; } if (empty($latest_end) || $last > $latest_end) { $latest_end = $last; } } else { if (empty($earliest_start) || $dtstart < $earliest_start) { $earliest_start = $dtstart; } $latest_end = null; break; } } return new RepeatRuleDateRange($earliest_start, $latest_end); }
/** * 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($vResource, $range_start = null, $range_end = null) { $components = $vResource->GetComponents(); if (!isset($range_start)) { $range_start = new RepeatRuleDateTime(); $range_start->modify('-6 weeks'); } if (!isset($range_end)) { $range_end = clone $range_start; $range_end->modify('+6 months'); } $new_components = array(); $result_limit = 1000; $instances = array(); $expand = false; $dtstart = null; foreach ($components as $k => $comp) { if ($comp->GetType() != 'VEVENT' && $comp->GetType() != 'VTODO' && $comp->GetType() != 'VJOURNAL') { if ($comp->GetType() != 'VTIMEZONE') { $new_components[] = $comp; } continue; } if (!isset($dtstart)) { $dtstart = $comp->GetProperty('DTSTART'); $dtstart = new RepeatRuleDateTime($dtstart); $instances[$dtstart->UTC()] = $comp; } $p = $comp->GetProperty('RECURRENCE-ID'); if (isset($p) && $p->Value() != '') { $range = $p->GetParameterValue('RANGE'); $recur_utc = new RepeatRuleDateTime($p); $recur_utc = $recur_utc->UTC(); 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->UTC()); print "Instances at start"; foreach ($instances as $k => $v) { print ' : ' . $k; } print "\n"; } } $instances = array_merge($instances, rrule_expand($dtstart, 'RRULE', $comp, $range_end)); if (DEBUG_RRULE) { print "After rrule_expand"; foreach ($instances as $k => $v) { print ' : ' . $k; } print "\n"; } $instances = array_merge($instances, rdate_expand($dtstart, 'RDATE', $comp, $range_end)); if (DEBUG_RRULE) { print "After rdate_expand"; foreach ($instances as $k => $v) { print ' : ' . $k; } print "\n"; } foreach (rdate_expand($dtstart, 'EXDATE', $comp, $range_end) 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->UTC(); $end_utc = $range_end->UTC(); foreach ($instances as $utc => $comp) { if ($utc > $end_utc) { break; } $end_type = $comp->GetType() == 'VTODO' ? 'DUE' : 'DTEND'; $duration = $comp->GetProperty('DURATION'); if (!isset($duration) || $duration->Value() == '') { $instance_start = $comp->GetProperty('DTSTART'); $dtsrt = new RepeatRuleDateTime($instance_start); $instance_end = $comp->GetProperty($end_type); if (isset($instance_end)) { $dtend = new RepeatRuleDateTime($instance_end); $duration = $dtstart->RFC5545Duration($dtend); } else { if ($instance_start->GetParameterValue('VALUE') == 'DATE') { $duration = 'P1D'; } else { $duration = 'P0D'; // For clarity } } } else { $duration = $duration->Value(); } if ($utc < $start_utc) { if (isset($early_start) && isset($last_duration) && $duration == $last_duration) { if ($utc < $early_start) { 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->UTC(); $last_duration = $duration; if ($utc < $early_start) { continue; } } } $component = clone $comp; $component->ClearProperties(array('DTSTART' => true, 'DUE' => true, 'DTEND' => true)); $component->AddProperty('DTSTART', $utc); $component->AddProperty('DURATION', $duration); $new_components[] = $component; } $vResource->SetComponents($new_components); return $vResource; }