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