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