xref: /plugin/davcal/vendor/sabre/vobject/lib/Component/VCalendar.php (revision a1a3b6794e0e143a4a8b51d3185ce2d339be61ab)
1*a1a3b679SAndreas Boehler<?php
2*a1a3b679SAndreas Boehler
3*a1a3b679SAndreas Boehlernamespace Sabre\VObject\Component;
4*a1a3b679SAndreas Boehler
5*a1a3b679SAndreas Boehleruse DateTime;
6*a1a3b679SAndreas Boehleruse DateTimeZone;
7*a1a3b679SAndreas Boehleruse Sabre\VObject;
8*a1a3b679SAndreas Boehleruse Sabre\VObject\Component;
9*a1a3b679SAndreas Boehleruse Sabre\VObject\Recur\EventIterator;
10*a1a3b679SAndreas Boehleruse Sabre\VObject\Recur\NoInstancesException;
11*a1a3b679SAndreas Boehler
12*a1a3b679SAndreas Boehler/**
13*a1a3b679SAndreas Boehler * The VCalendar component
14*a1a3b679SAndreas Boehler *
15*a1a3b679SAndreas Boehler * This component adds functionality to a component, specific for a VCALENDAR.
16*a1a3b679SAndreas Boehler *
17*a1a3b679SAndreas Boehler * @copyright Copyright (C) 2011-2015 fruux GmbH (https://fruux.com/).
18*a1a3b679SAndreas Boehler * @author Evert Pot (http://evertpot.com/)
19*a1a3b679SAndreas Boehler * @license http://sabre.io/license/ Modified BSD License
20*a1a3b679SAndreas Boehler */
21*a1a3b679SAndreas Boehlerclass VCalendar extends VObject\Document {
22*a1a3b679SAndreas Boehler
23*a1a3b679SAndreas Boehler    /**
24*a1a3b679SAndreas Boehler     * The default name for this component.
25*a1a3b679SAndreas Boehler     *
26*a1a3b679SAndreas Boehler     * This should be 'VCALENDAR' or 'VCARD'.
27*a1a3b679SAndreas Boehler     *
28*a1a3b679SAndreas Boehler     * @var string
29*a1a3b679SAndreas Boehler     */
30*a1a3b679SAndreas Boehler    static $defaultName = 'VCALENDAR';
31*a1a3b679SAndreas Boehler
32*a1a3b679SAndreas Boehler    /**
33*a1a3b679SAndreas Boehler     * This is a list of components, and which classes they should map to.
34*a1a3b679SAndreas Boehler     *
35*a1a3b679SAndreas Boehler     * @var array
36*a1a3b679SAndreas Boehler     */
37*a1a3b679SAndreas Boehler    static $componentMap = array(
38*a1a3b679SAndreas Boehler        'VALARM'        => 'Sabre\\VObject\\Component\\VAlarm',
39*a1a3b679SAndreas Boehler        'VEVENT'        => 'Sabre\\VObject\\Component\\VEvent',
40*a1a3b679SAndreas Boehler        'VFREEBUSY'     => 'Sabre\\VObject\\Component\\VFreeBusy',
41*a1a3b679SAndreas Boehler        'VAVAILABILITY' => 'Sabre\\VObject\\Component\\VAvailability',
42*a1a3b679SAndreas Boehler        'AVAILABLE'     => 'Sabre\\VObject\\Component\\Available',
43*a1a3b679SAndreas Boehler        'VJOURNAL'      => 'Sabre\\VObject\\Component\\VJournal',
44*a1a3b679SAndreas Boehler        'VTIMEZONE'     => 'Sabre\\VObject\\Component\\VTimeZone',
45*a1a3b679SAndreas Boehler        'VTODO'         => 'Sabre\\VObject\\Component\\VTodo',
46*a1a3b679SAndreas Boehler    );
47*a1a3b679SAndreas Boehler
48*a1a3b679SAndreas Boehler    /**
49*a1a3b679SAndreas Boehler     * List of value-types, and which classes they map to.
50*a1a3b679SAndreas Boehler     *
51*a1a3b679SAndreas Boehler     * @var array
52*a1a3b679SAndreas Boehler     */
53*a1a3b679SAndreas Boehler    static $valueMap = array(
54*a1a3b679SAndreas Boehler        'BINARY'           => 'Sabre\\VObject\\Property\\Binary',
55*a1a3b679SAndreas Boehler        'BOOLEAN'          => 'Sabre\\VObject\\Property\\Boolean',
56*a1a3b679SAndreas Boehler        'CAL-ADDRESS'      => 'Sabre\\VObject\\Property\\ICalendar\\CalAddress',
57*a1a3b679SAndreas Boehler        'DATE'             => 'Sabre\\VObject\\Property\\ICalendar\\Date',
58*a1a3b679SAndreas Boehler        'DATE-TIME'        => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
59*a1a3b679SAndreas Boehler        'DURATION'         => 'Sabre\\VObject\\Property\\ICalendar\\Duration',
60*a1a3b679SAndreas Boehler        'FLOAT'            => 'Sabre\\VObject\\Property\\Float',
61*a1a3b679SAndreas Boehler        'INTEGER'          => 'Sabre\\VObject\\Property\\Integer',
62*a1a3b679SAndreas Boehler        'PERIOD'           => 'Sabre\\VObject\\Property\\ICalendar\\Period',
63*a1a3b679SAndreas Boehler        'RECUR'            => 'Sabre\\VObject\\Property\\ICalendar\\Recur',
64*a1a3b679SAndreas Boehler        'TEXT'             => 'Sabre\\VObject\\Property\\Text',
65*a1a3b679SAndreas Boehler        'TIME'             => 'Sabre\\VObject\\Property\\Time',
66*a1a3b679SAndreas Boehler        'UNKNOWN'          => 'Sabre\\VObject\\Property\\Unknown', // jCard / jCal-only.
67*a1a3b679SAndreas Boehler        'URI'              => 'Sabre\\VObject\\Property\\Uri',
68*a1a3b679SAndreas Boehler        'UTC-OFFSET'       => 'Sabre\\VObject\\Property\\UtcOffset',
69*a1a3b679SAndreas Boehler    );
70*a1a3b679SAndreas Boehler
71*a1a3b679SAndreas Boehler    /**
72*a1a3b679SAndreas Boehler     * List of properties, and which classes they map to.
73*a1a3b679SAndreas Boehler     *
74*a1a3b679SAndreas Boehler     * @var array
75*a1a3b679SAndreas Boehler     */
76*a1a3b679SAndreas Boehler    static $propertyMap = array(
77*a1a3b679SAndreas Boehler        // Calendar properties
78*a1a3b679SAndreas Boehler        'CALSCALE'      => 'Sabre\\VObject\\Property\\FlatText',
79*a1a3b679SAndreas Boehler        'METHOD'        => 'Sabre\\VObject\\Property\\FlatText',
80*a1a3b679SAndreas Boehler        'PRODID'        => 'Sabre\\VObject\\Property\\FlatText',
81*a1a3b679SAndreas Boehler        'VERSION'       => 'Sabre\\VObject\\Property\\FlatText',
82*a1a3b679SAndreas Boehler
83*a1a3b679SAndreas Boehler        // Component properties
84*a1a3b679SAndreas Boehler        'ATTACH'            => 'Sabre\\VObject\\Property\\Uri',
85*a1a3b679SAndreas Boehler        'CATEGORIES'        => 'Sabre\\VObject\\Property\\Text',
86*a1a3b679SAndreas Boehler        'CLASS'             => 'Sabre\\VObject\\Property\\FlatText',
87*a1a3b679SAndreas Boehler        'COMMENT'           => 'Sabre\\VObject\\Property\\FlatText',
88*a1a3b679SAndreas Boehler        'DESCRIPTION'       => 'Sabre\\VObject\\Property\\FlatText',
89*a1a3b679SAndreas Boehler        'GEO'               => 'Sabre\\VObject\\Property\\Float',
90*a1a3b679SAndreas Boehler        'LOCATION'          => 'Sabre\\VObject\\Property\\FlatText',
91*a1a3b679SAndreas Boehler        'PERCENT-COMPLETE'  => 'Sabre\\VObject\\Property\\Integer',
92*a1a3b679SAndreas Boehler        'PRIORITY'          => 'Sabre\\VObject\\Property\\Integer',
93*a1a3b679SAndreas Boehler        'RESOURCES'         => 'Sabre\\VObject\\Property\\Text',
94*a1a3b679SAndreas Boehler        'STATUS'            => 'Sabre\\VObject\\Property\\FlatText',
95*a1a3b679SAndreas Boehler        'SUMMARY'           => 'Sabre\\VObject\\Property\\FlatText',
96*a1a3b679SAndreas Boehler
97*a1a3b679SAndreas Boehler        // Date and Time Component Properties
98*a1a3b679SAndreas Boehler        'COMPLETED'     => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
99*a1a3b679SAndreas Boehler        'DTEND'         => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
100*a1a3b679SAndreas Boehler        'DUE'           => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
101*a1a3b679SAndreas Boehler        'DTSTART'       => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
102*a1a3b679SAndreas Boehler        'DURATION'      => 'Sabre\\VObject\\Property\\ICalendar\\Duration',
103*a1a3b679SAndreas Boehler        'FREEBUSY'      => 'Sabre\\VObject\\Property\\ICalendar\\Period',
104*a1a3b679SAndreas Boehler        'TRANSP'        => 'Sabre\\VObject\\Property\\FlatText',
105*a1a3b679SAndreas Boehler
106*a1a3b679SAndreas Boehler        // Time Zone Component Properties
107*a1a3b679SAndreas Boehler        'TZID'          => 'Sabre\\VObject\\Property\\FlatText',
108*a1a3b679SAndreas Boehler        'TZNAME'        => 'Sabre\\VObject\\Property\\FlatText',
109*a1a3b679SAndreas Boehler        'TZOFFSETFROM'  => 'Sabre\\VObject\\Property\\UtcOffset',
110*a1a3b679SAndreas Boehler        'TZOFFSETTO'    => 'Sabre\\VObject\\Property\\UtcOffset',
111*a1a3b679SAndreas Boehler        'TZURL'         => 'Sabre\\VObject\\Property\\Uri',
112*a1a3b679SAndreas Boehler
113*a1a3b679SAndreas Boehler        // Relationship Component Properties
114*a1a3b679SAndreas Boehler        'ATTENDEE'      => 'Sabre\\VObject\\Property\\ICalendar\\CalAddress',
115*a1a3b679SAndreas Boehler        'CONTACT'       => 'Sabre\\VObject\\Property\\FlatText',
116*a1a3b679SAndreas Boehler        'ORGANIZER'     => 'Sabre\\VObject\\Property\\ICalendar\\CalAddress',
117*a1a3b679SAndreas Boehler        'RECURRENCE-ID' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
118*a1a3b679SAndreas Boehler        'RELATED-TO'    => 'Sabre\\VObject\\Property\\FlatText',
119*a1a3b679SAndreas Boehler        'URL'           => 'Sabre\\VObject\\Property\\Uri',
120*a1a3b679SAndreas Boehler        'UID'           => 'Sabre\\VObject\\Property\\FlatText',
121*a1a3b679SAndreas Boehler
122*a1a3b679SAndreas Boehler        // Recurrence Component Properties
123*a1a3b679SAndreas Boehler        'EXDATE'        => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
124*a1a3b679SAndreas Boehler        'RDATE'         => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
125*a1a3b679SAndreas Boehler        'RRULE'         => 'Sabre\\VObject\\Property\\ICalendar\\Recur',
126*a1a3b679SAndreas Boehler        'EXRULE'        => 'Sabre\\VObject\\Property\\ICalendar\\Recur', // Deprecated since rfc5545
127*a1a3b679SAndreas Boehler
128*a1a3b679SAndreas Boehler        // Alarm Component Properties
129*a1a3b679SAndreas Boehler        'ACTION'        => 'Sabre\\VObject\\Property\\FlatText',
130*a1a3b679SAndreas Boehler        'REPEAT'        => 'Sabre\\VObject\\Property\\Integer',
131*a1a3b679SAndreas Boehler        'TRIGGER'       => 'Sabre\\VObject\\Property\\ICalendar\\Duration',
132*a1a3b679SAndreas Boehler
133*a1a3b679SAndreas Boehler        // Change Management Component Properties
134*a1a3b679SAndreas Boehler        'CREATED'       => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
135*a1a3b679SAndreas Boehler        'DTSTAMP'       => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
136*a1a3b679SAndreas Boehler        'LAST-MODIFIED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
137*a1a3b679SAndreas Boehler        'SEQUENCE'      => 'Sabre\\VObject\\Property\\Integer',
138*a1a3b679SAndreas Boehler
139*a1a3b679SAndreas Boehler        // Request Status
140*a1a3b679SAndreas Boehler        'REQUEST-STATUS' => 'Sabre\\VObject\\Property\\Text',
141*a1a3b679SAndreas Boehler
142*a1a3b679SAndreas Boehler        // Additions from draft-daboo-valarm-extensions-04
143*a1a3b679SAndreas Boehler        'ALARM-AGENT'    => 'Sabre\\VObject\\Property\\Text',
144*a1a3b679SAndreas Boehler        'ACKNOWLEDGED'   => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
145*a1a3b679SAndreas Boehler        'PROXIMITY'      => 'Sabre\\VObject\\Property\\Text',
146*a1a3b679SAndreas Boehler        'DEFAULT-ALARM'  => 'Sabre\\VObject\\Property\\Boolean',
147*a1a3b679SAndreas Boehler
148*a1a3b679SAndreas Boehler        // Additions from draft-daboo-calendar-availability-05
149*a1a3b679SAndreas Boehler        'BUSYTYPE'       => 'Sabre\\VObject\\Property\\Text',
150*a1a3b679SAndreas Boehler
151*a1a3b679SAndreas Boehler    );
152*a1a3b679SAndreas Boehler
153*a1a3b679SAndreas Boehler    /**
154*a1a3b679SAndreas Boehler     * Returns the current document type.
155*a1a3b679SAndreas Boehler     *
156*a1a3b679SAndreas Boehler     * @return void
157*a1a3b679SAndreas Boehler     */
158*a1a3b679SAndreas Boehler    function getDocumentType() {
159*a1a3b679SAndreas Boehler
160*a1a3b679SAndreas Boehler        return self::ICALENDAR20;
161*a1a3b679SAndreas Boehler
162*a1a3b679SAndreas Boehler    }
163*a1a3b679SAndreas Boehler
164*a1a3b679SAndreas Boehler    /**
165*a1a3b679SAndreas Boehler     * Returns a list of all 'base components'. For instance, if an Event has
166*a1a3b679SAndreas Boehler     * a recurrence rule, and one instance is overridden, the overridden event
167*a1a3b679SAndreas Boehler     * will have the same UID, but will be excluded from this list.
168*a1a3b679SAndreas Boehler     *
169*a1a3b679SAndreas Boehler     * VTIMEZONE components will always be excluded.
170*a1a3b679SAndreas Boehler     *
171*a1a3b679SAndreas Boehler     * @param string $componentName filter by component name
172*a1a3b679SAndreas Boehler     * @return VObject\Component[]
173*a1a3b679SAndreas Boehler     */
174*a1a3b679SAndreas Boehler    function getBaseComponents($componentName = null) {
175*a1a3b679SAndreas Boehler
176*a1a3b679SAndreas Boehler        $components = array();
177*a1a3b679SAndreas Boehler        foreach($this->children as $component) {
178*a1a3b679SAndreas Boehler
179*a1a3b679SAndreas Boehler            if (!$component instanceof VObject\Component)
180*a1a3b679SAndreas Boehler                continue;
181*a1a3b679SAndreas Boehler
182*a1a3b679SAndreas Boehler            if (isset($component->{'RECURRENCE-ID'}))
183*a1a3b679SAndreas Boehler                continue;
184*a1a3b679SAndreas Boehler
185*a1a3b679SAndreas Boehler            if ($componentName && $component->name !== strtoupper($componentName))
186*a1a3b679SAndreas Boehler                continue;
187*a1a3b679SAndreas Boehler
188*a1a3b679SAndreas Boehler            if ($component->name === 'VTIMEZONE')
189*a1a3b679SAndreas Boehler                continue;
190*a1a3b679SAndreas Boehler
191*a1a3b679SAndreas Boehler            $components[] = $component;
192*a1a3b679SAndreas Boehler
193*a1a3b679SAndreas Boehler        }
194*a1a3b679SAndreas Boehler
195*a1a3b679SAndreas Boehler        return $components;
196*a1a3b679SAndreas Boehler
197*a1a3b679SAndreas Boehler    }
198*a1a3b679SAndreas Boehler
199*a1a3b679SAndreas Boehler    /**
200*a1a3b679SAndreas Boehler     * Returns the first component that is not a VTIMEZONE, and does not have
201*a1a3b679SAndreas Boehler     * an RECURRENCE-ID.
202*a1a3b679SAndreas Boehler     *
203*a1a3b679SAndreas Boehler     * If there is no such component, null will be returned.
204*a1a3b679SAndreas Boehler     *
205*a1a3b679SAndreas Boehler     * @param string $componentName filter by component name
206*a1a3b679SAndreas Boehler     * @return VObject\Component|null
207*a1a3b679SAndreas Boehler     */
208*a1a3b679SAndreas Boehler    function getBaseComponent($componentName = null) {
209*a1a3b679SAndreas Boehler
210*a1a3b679SAndreas Boehler        foreach($this->children as $component) {
211*a1a3b679SAndreas Boehler
212*a1a3b679SAndreas Boehler            if (!$component instanceof VObject\Component)
213*a1a3b679SAndreas Boehler                continue;
214*a1a3b679SAndreas Boehler
215*a1a3b679SAndreas Boehler            if (isset($component->{'RECURRENCE-ID'}))
216*a1a3b679SAndreas Boehler                continue;
217*a1a3b679SAndreas Boehler
218*a1a3b679SAndreas Boehler            if ($componentName && $component->name !== strtoupper($componentName))
219*a1a3b679SAndreas Boehler                continue;
220*a1a3b679SAndreas Boehler
221*a1a3b679SAndreas Boehler            if ($component->name === 'VTIMEZONE')
222*a1a3b679SAndreas Boehler                continue;
223*a1a3b679SAndreas Boehler
224*a1a3b679SAndreas Boehler            return $component;
225*a1a3b679SAndreas Boehler
226*a1a3b679SAndreas Boehler        }
227*a1a3b679SAndreas Boehler
228*a1a3b679SAndreas Boehler    }
229*a1a3b679SAndreas Boehler
230*a1a3b679SAndreas Boehler    /**
231*a1a3b679SAndreas Boehler     * If this calendar object, has events with recurrence rules, this method
232*a1a3b679SAndreas Boehler     * can be used to expand the event into multiple sub-events.
233*a1a3b679SAndreas Boehler     *
234*a1a3b679SAndreas Boehler     * Each event will be stripped from it's recurrence information, and only
235*a1a3b679SAndreas Boehler     * the instances of the event in the specified timerange will be left
236*a1a3b679SAndreas Boehler     * alone.
237*a1a3b679SAndreas Boehler     *
238*a1a3b679SAndreas Boehler     * In addition, this method will cause timezone information to be stripped,
239*a1a3b679SAndreas Boehler     * and normalized to UTC.
240*a1a3b679SAndreas Boehler     *
241*a1a3b679SAndreas Boehler     * This method will alter the VCalendar. This cannot be reversed.
242*a1a3b679SAndreas Boehler     *
243*a1a3b679SAndreas Boehler     * This functionality is specifically used by the CalDAV standard. It is
244*a1a3b679SAndreas Boehler     * possible for clients to request expand events, if they are rather simple
245*a1a3b679SAndreas Boehler     * clients and do not have the possibility to calculate recurrences.
246*a1a3b679SAndreas Boehler     *
247*a1a3b679SAndreas Boehler     * @param DateTime $start
248*a1a3b679SAndreas Boehler     * @param DateTime $end
249*a1a3b679SAndreas Boehler     * @param DateTimeZone $timeZone reference timezone for floating dates and
250*a1a3b679SAndreas Boehler     *                     times.
251*a1a3b679SAndreas Boehler     * @return void
252*a1a3b679SAndreas Boehler     */
253*a1a3b679SAndreas Boehler    function expand(DateTime $start, DateTime $end, DateTimeZone $timeZone = null) {
254*a1a3b679SAndreas Boehler
255*a1a3b679SAndreas Boehler        $newEvents = array();
256*a1a3b679SAndreas Boehler
257*a1a3b679SAndreas Boehler        if (!$timeZone) {
258*a1a3b679SAndreas Boehler            $timeZone = new DateTimeZone('UTC');
259*a1a3b679SAndreas Boehler        }
260*a1a3b679SAndreas Boehler
261*a1a3b679SAndreas Boehler        // An array of events. Events are indexed by UID. Each item in this
262*a1a3b679SAndreas Boehler        // array is a list of one or more events that match the UID.
263*a1a3b679SAndreas Boehler        $recurringEvents = array();
264*a1a3b679SAndreas Boehler
265*a1a3b679SAndreas Boehler        foreach($this->select('VEVENT') as $key=>$vevent) {
266*a1a3b679SAndreas Boehler
267*a1a3b679SAndreas Boehler            $uid = (string)$vevent->UID;
268*a1a3b679SAndreas Boehler            if (!$uid) {
269*a1a3b679SAndreas Boehler                throw new \LogicException('Event did not have a UID!');
270*a1a3b679SAndreas Boehler            }
271*a1a3b679SAndreas Boehler
272*a1a3b679SAndreas Boehler            if (isset($vevent->{'RECURRENCE-ID'}) || isset($vevent->RRULE)) {
273*a1a3b679SAndreas Boehler                if (isset($recurringEvents[$uid])) {
274*a1a3b679SAndreas Boehler                    $recurringEvents[$uid][] = $vevent;
275*a1a3b679SAndreas Boehler                } else {
276*a1a3b679SAndreas Boehler                    $recurringEvents[$uid] = array($vevent);
277*a1a3b679SAndreas Boehler                }
278*a1a3b679SAndreas Boehler                continue;
279*a1a3b679SAndreas Boehler            }
280*a1a3b679SAndreas Boehler
281*a1a3b679SAndreas Boehler            if (!isset($vevent->RRULE)) {
282*a1a3b679SAndreas Boehler                if ($vevent->isInTimeRange($start, $end)) {
283*a1a3b679SAndreas Boehler                    $newEvents[] = $vevent;
284*a1a3b679SAndreas Boehler                }
285*a1a3b679SAndreas Boehler                continue;
286*a1a3b679SAndreas Boehler            }
287*a1a3b679SAndreas Boehler
288*a1a3b679SAndreas Boehler        }
289*a1a3b679SAndreas Boehler
290*a1a3b679SAndreas Boehler        foreach($recurringEvents as $events) {
291*a1a3b679SAndreas Boehler
292*a1a3b679SAndreas Boehler            try {
293*a1a3b679SAndreas Boehler                $it = new EventIterator($events, $timeZone);
294*a1a3b679SAndreas Boehler
295*a1a3b679SAndreas Boehler            } catch (NoInstancesException $e) {
296*a1a3b679SAndreas Boehler                // This event is recurring, but it doesn't have a single
297*a1a3b679SAndreas Boehler                // instance. We are skipping this event from the output
298*a1a3b679SAndreas Boehler                // entirely.
299*a1a3b679SAndreas Boehler                continue;
300*a1a3b679SAndreas Boehler            }
301*a1a3b679SAndreas Boehler            $it->fastForward($start);
302*a1a3b679SAndreas Boehler
303*a1a3b679SAndreas Boehler            while($it->valid() && $it->getDTStart() < $end) {
304*a1a3b679SAndreas Boehler
305*a1a3b679SAndreas Boehler                if ($it->getDTEnd() > $start) {
306*a1a3b679SAndreas Boehler
307*a1a3b679SAndreas Boehler                    $newEvents[] = $it->getEventObject();
308*a1a3b679SAndreas Boehler
309*a1a3b679SAndreas Boehler                }
310*a1a3b679SAndreas Boehler                $it->next();
311*a1a3b679SAndreas Boehler
312*a1a3b679SAndreas Boehler            }
313*a1a3b679SAndreas Boehler
314*a1a3b679SAndreas Boehler        }
315*a1a3b679SAndreas Boehler
316*a1a3b679SAndreas Boehler        // Wiping out all old VEVENT objects
317*a1a3b679SAndreas Boehler        unset($this->VEVENT);
318*a1a3b679SAndreas Boehler
319*a1a3b679SAndreas Boehler        // Setting all properties to UTC time.
320*a1a3b679SAndreas Boehler        foreach($newEvents as $newEvent) {
321*a1a3b679SAndreas Boehler
322*a1a3b679SAndreas Boehler            foreach($newEvent->children as $child) {
323*a1a3b679SAndreas Boehler                if ($child instanceof VObject\Property\ICalendar\DateTime && $child->hasTime()) {
324*a1a3b679SAndreas Boehler                    $dt = $child->getDateTimes($timeZone);
325*a1a3b679SAndreas Boehler                    // We only need to update the first timezone, because
326*a1a3b679SAndreas Boehler                    // setDateTimes will match all other timezones to the
327*a1a3b679SAndreas Boehler                    // first.
328*a1a3b679SAndreas Boehler                    $dt[0]->setTimeZone(new DateTimeZone('UTC'));
329*a1a3b679SAndreas Boehler                    $child->setDateTimes($dt);
330*a1a3b679SAndreas Boehler                }
331*a1a3b679SAndreas Boehler
332*a1a3b679SAndreas Boehler            }
333*a1a3b679SAndreas Boehler            $this->add($newEvent);
334*a1a3b679SAndreas Boehler
335*a1a3b679SAndreas Boehler        }
336*a1a3b679SAndreas Boehler
337*a1a3b679SAndreas Boehler        // Removing all VTIMEZONE components
338*a1a3b679SAndreas Boehler        unset($this->VTIMEZONE);
339*a1a3b679SAndreas Boehler
340*a1a3b679SAndreas Boehler    }
341*a1a3b679SAndreas Boehler
342*a1a3b679SAndreas Boehler    /**
343*a1a3b679SAndreas Boehler     * This method should return a list of default property values.
344*a1a3b679SAndreas Boehler     *
345*a1a3b679SAndreas Boehler     * @return array
346*a1a3b679SAndreas Boehler     */
347*a1a3b679SAndreas Boehler    protected function getDefaults() {
348*a1a3b679SAndreas Boehler
349*a1a3b679SAndreas Boehler        return array(
350*a1a3b679SAndreas Boehler            'VERSION' => '2.0',
351*a1a3b679SAndreas Boehler            'PRODID' => '-//Sabre//Sabre VObject ' . VObject\Version::VERSION . '//EN',
352*a1a3b679SAndreas Boehler            'CALSCALE' => 'GREGORIAN',
353*a1a3b679SAndreas Boehler        );
354*a1a3b679SAndreas Boehler
355*a1a3b679SAndreas Boehler    }
356*a1a3b679SAndreas Boehler
357*a1a3b679SAndreas Boehler    /**
358*a1a3b679SAndreas Boehler     * A simple list of validation rules.
359*a1a3b679SAndreas Boehler     *
360*a1a3b679SAndreas Boehler     * This is simply a list of properties, and how many times they either
361*a1a3b679SAndreas Boehler     * must or must not appear.
362*a1a3b679SAndreas Boehler     *
363*a1a3b679SAndreas Boehler     * Possible values per property:
364*a1a3b679SAndreas Boehler     *   * 0 - Must not appear.
365*a1a3b679SAndreas Boehler     *   * 1 - Must appear exactly once.
366*a1a3b679SAndreas Boehler     *   * + - Must appear at least once.
367*a1a3b679SAndreas Boehler     *   * * - Can appear any number of times.
368*a1a3b679SAndreas Boehler     *   * ? - May appear, but not more than once.
369*a1a3b679SAndreas Boehler     *
370*a1a3b679SAndreas Boehler     * @var array
371*a1a3b679SAndreas Boehler     */
372*a1a3b679SAndreas Boehler    function getValidationRules() {
373*a1a3b679SAndreas Boehler
374*a1a3b679SAndreas Boehler        return array(
375*a1a3b679SAndreas Boehler            'PRODID' => 1,
376*a1a3b679SAndreas Boehler            'VERSION' => 1,
377*a1a3b679SAndreas Boehler
378*a1a3b679SAndreas Boehler            'CALSCALE' => '?',
379*a1a3b679SAndreas Boehler            'METHOD' => '?',
380*a1a3b679SAndreas Boehler        );
381*a1a3b679SAndreas Boehler
382*a1a3b679SAndreas Boehler    }
383*a1a3b679SAndreas Boehler
384*a1a3b679SAndreas Boehler    /**
385*a1a3b679SAndreas Boehler     * Validates the node for correctness.
386*a1a3b679SAndreas Boehler     *
387*a1a3b679SAndreas Boehler     * The following options are supported:
388*a1a3b679SAndreas Boehler     *   Node::REPAIR - May attempt to automatically repair the problem.
389*a1a3b679SAndreas Boehler     *   Node::PROFILE_CARDDAV - Validate the vCard for CardDAV purposes.
390*a1a3b679SAndreas Boehler     *   Node::PROFILE_CALDAV - Validate the iCalendar for CalDAV purposes.
391*a1a3b679SAndreas Boehler     *
392*a1a3b679SAndreas Boehler     * This method returns an array with detected problems.
393*a1a3b679SAndreas Boehler     * Every element has the following properties:
394*a1a3b679SAndreas Boehler     *
395*a1a3b679SAndreas Boehler     *  * level - problem level.
396*a1a3b679SAndreas Boehler     *  * message - A human-readable string describing the issue.
397*a1a3b679SAndreas Boehler     *  * node - A reference to the problematic node.
398*a1a3b679SAndreas Boehler     *
399*a1a3b679SAndreas Boehler     * The level means:
400*a1a3b679SAndreas Boehler     *   1 - The issue was repaired (only happens if REPAIR was turned on).
401*a1a3b679SAndreas Boehler     *   2 - A warning.
402*a1a3b679SAndreas Boehler     *   3 - An error.
403*a1a3b679SAndreas Boehler     *
404*a1a3b679SAndreas Boehler     * @param int $options
405*a1a3b679SAndreas Boehler     * @return array
406*a1a3b679SAndreas Boehler     */
407*a1a3b679SAndreas Boehler    function validate($options = 0) {
408*a1a3b679SAndreas Boehler
409*a1a3b679SAndreas Boehler        $warnings = parent::validate($options);
410*a1a3b679SAndreas Boehler
411*a1a3b679SAndreas Boehler        if ($ver = $this->VERSION) {
412*a1a3b679SAndreas Boehler            if ((string)$ver !== '2.0') {
413*a1a3b679SAndreas Boehler                $warnings[] = array(
414*a1a3b679SAndreas Boehler                    'level' => 3,
415*a1a3b679SAndreas Boehler                    'message' => 'Only iCalendar version 2.0 as defined in rfc5545 is supported.',
416*a1a3b679SAndreas Boehler                    'node' => $this,
417*a1a3b679SAndreas Boehler                );
418*a1a3b679SAndreas Boehler            }
419*a1a3b679SAndreas Boehler
420*a1a3b679SAndreas Boehler        }
421*a1a3b679SAndreas Boehler
422*a1a3b679SAndreas Boehler        $uidList = array();
423*a1a3b679SAndreas Boehler
424*a1a3b679SAndreas Boehler        $componentsFound = 0;
425*a1a3b679SAndreas Boehler
426*a1a3b679SAndreas Boehler        $componentTypes = array();
427*a1a3b679SAndreas Boehler
428*a1a3b679SAndreas Boehler        foreach($this->children as $child) {
429*a1a3b679SAndreas Boehler            if($child instanceof Component) {
430*a1a3b679SAndreas Boehler                $componentsFound++;
431*a1a3b679SAndreas Boehler
432*a1a3b679SAndreas Boehler                if (!in_array($child->name, array('VEVENT', 'VTODO', 'VJOURNAL'))) {
433*a1a3b679SAndreas Boehler                    continue;
434*a1a3b679SAndreas Boehler                }
435*a1a3b679SAndreas Boehler                $componentTypes[] = $child->name;
436*a1a3b679SAndreas Boehler
437*a1a3b679SAndreas Boehler                $uid = (string)$child->UID;
438*a1a3b679SAndreas Boehler                $isMaster = isset($child->{'RECURRENCE-ID'})?0:1;
439*a1a3b679SAndreas Boehler                if (isset($uidList[$uid])) {
440*a1a3b679SAndreas Boehler                    $uidList[$uid]['count']++;
441*a1a3b679SAndreas Boehler                    if ($isMaster && $uidList[$uid]['hasMaster']) {
442*a1a3b679SAndreas Boehler                        $warnings[] = array(
443*a1a3b679SAndreas Boehler                            'level' => 3,
444*a1a3b679SAndreas Boehler                            'message' => 'More than one master object was found for the object with UID ' . $uid,
445*a1a3b679SAndreas Boehler                            'node' => $this,
446*a1a3b679SAndreas Boehler                        );
447*a1a3b679SAndreas Boehler                    }
448*a1a3b679SAndreas Boehler                    $uidList[$uid]['hasMaster']+=$isMaster;
449*a1a3b679SAndreas Boehler                } else {
450*a1a3b679SAndreas Boehler                    $uidList[$uid] = array(
451*a1a3b679SAndreas Boehler                        'count' => 1,
452*a1a3b679SAndreas Boehler                        'hasMaster' => $isMaster,
453*a1a3b679SAndreas Boehler                    );
454*a1a3b679SAndreas Boehler                }
455*a1a3b679SAndreas Boehler
456*a1a3b679SAndreas Boehler            }
457*a1a3b679SAndreas Boehler        }
458*a1a3b679SAndreas Boehler
459*a1a3b679SAndreas Boehler        if ($componentsFound===0) {
460*a1a3b679SAndreas Boehler            $warnings[] = array(
461*a1a3b679SAndreas Boehler                'level' => 3,
462*a1a3b679SAndreas Boehler                'message' => 'An iCalendar object must have at least 1 component.',
463*a1a3b679SAndreas Boehler                'node' => $this,
464*a1a3b679SAndreas Boehler            );
465*a1a3b679SAndreas Boehler        }
466*a1a3b679SAndreas Boehler
467*a1a3b679SAndreas Boehler        if ($options & self::PROFILE_CALDAV) {
468*a1a3b679SAndreas Boehler            if (count($uidList)>1) {
469*a1a3b679SAndreas Boehler                $warnings[] = array(
470*a1a3b679SAndreas Boehler                    'level' => 3,
471*a1a3b679SAndreas Boehler                    'message' => 'A calendar object on a CalDAV server may only have components with the same UID.',
472*a1a3b679SAndreas Boehler                    'node' => $this,
473*a1a3b679SAndreas Boehler                );
474*a1a3b679SAndreas Boehler            }
475*a1a3b679SAndreas Boehler            if (count(array_unique($componentTypes))===0) {
476*a1a3b679SAndreas Boehler                $warnings[] = array(
477*a1a3b679SAndreas Boehler                    'level' => 3,
478*a1a3b679SAndreas Boehler                    'message' => 'A calendar object on a CalDAV server must have at least 1 component (VTODO, VEVENT, VJOURNAL).',
479*a1a3b679SAndreas Boehler                    'node' => $this,
480*a1a3b679SAndreas Boehler                );
481*a1a3b679SAndreas Boehler            }
482*a1a3b679SAndreas Boehler            if (count(array_unique($componentTypes))>1) {
483*a1a3b679SAndreas Boehler                $warnings[] = array(
484*a1a3b679SAndreas Boehler                    'level' => 3,
485*a1a3b679SAndreas Boehler                    'message' => 'A calendar object on a CalDAV server may only have 1 type of component (VEVENT, VTODO or VJOURNAL).',
486*a1a3b679SAndreas Boehler                    'node' => $this,
487*a1a3b679SAndreas Boehler                );
488*a1a3b679SAndreas Boehler            }
489*a1a3b679SAndreas Boehler
490*a1a3b679SAndreas Boehler            if (isset($this->METHOD)) {
491*a1a3b679SAndreas Boehler                $warnings[] = array(
492*a1a3b679SAndreas Boehler                    'level' => 3,
493*a1a3b679SAndreas Boehler                    'message' => 'A calendar object on a CalDAV server MUST NOT have a METHOD property.',
494*a1a3b679SAndreas Boehler                    'node' => $this,
495*a1a3b679SAndreas Boehler                );
496*a1a3b679SAndreas Boehler            }
497*a1a3b679SAndreas Boehler        }
498*a1a3b679SAndreas Boehler
499*a1a3b679SAndreas Boehler        return $warnings;
500*a1a3b679SAndreas Boehler
501*a1a3b679SAndreas Boehler    }
502*a1a3b679SAndreas Boehler
503*a1a3b679SAndreas Boehler    /**
504*a1a3b679SAndreas Boehler     * Returns all components with a specific UID value.
505*a1a3b679SAndreas Boehler     *
506*a1a3b679SAndreas Boehler     * @return array
507*a1a3b679SAndreas Boehler     */
508*a1a3b679SAndreas Boehler    function getByUID($uid) {
509*a1a3b679SAndreas Boehler
510*a1a3b679SAndreas Boehler        return array_filter($this->children, function($item) use ($uid) {
511*a1a3b679SAndreas Boehler
512*a1a3b679SAndreas Boehler            if (!$item instanceof Component) {
513*a1a3b679SAndreas Boehler                return false;
514*a1a3b679SAndreas Boehler            }
515*a1a3b679SAndreas Boehler            if (!$itemUid = $item->select('UID')) {
516*a1a3b679SAndreas Boehler                return false;
517*a1a3b679SAndreas Boehler            }
518*a1a3b679SAndreas Boehler            $itemUid = current($itemUid)->getValue();
519*a1a3b679SAndreas Boehler            return $uid === $itemUid;
520*a1a3b679SAndreas Boehler
521*a1a3b679SAndreas Boehler        });
522*a1a3b679SAndreas Boehler
523*a1a3b679SAndreas Boehler    }
524*a1a3b679SAndreas Boehler
525*a1a3b679SAndreas Boehler
526*a1a3b679SAndreas Boehler}
527