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