aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/sabre/vobject/lib/Component/VCalendar.php
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/sabre/vobject/lib/Component/VCalendar.php')
-rw-r--r--vendor/sabre/vobject/lib/Component/VCalendar.php561
1 files changed, 561 insertions, 0 deletions
diff --git a/vendor/sabre/vobject/lib/Component/VCalendar.php b/vendor/sabre/vobject/lib/Component/VCalendar.php
new file mode 100644
index 000000000..988db9dc2
--- /dev/null
+++ b/vendor/sabre/vobject/lib/Component/VCalendar.php
@@ -0,0 +1,561 @@
+<?php
+
+namespace Sabre\VObject\Component;
+
+use DateTimeInterface;
+use DateTimeZone;
+use Sabre\VObject;
+use Sabre\VObject\Component;
+use Sabre\VObject\Property;
+use Sabre\VObject\Recur\EventIterator;
+use Sabre\VObject\Recur\NoInstancesException;
+use Sabre\VObject\InvalidDataException;
+
+/**
+ * The VCalendar component.
+ *
+ * This component adds functionality to a component, specific for a VCALENDAR.
+ *
+ * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
+ * @author Evert Pot (http://evertpot.com/)
+ * @license http://sabre.io/license/ Modified BSD License
+ */
+class VCalendar extends VObject\Document {
+
+ /**
+ * The default name for this component.
+ *
+ * This should be 'VCALENDAR' or 'VCARD'.
+ *
+ * @var string
+ */
+ static $defaultName = 'VCALENDAR';
+
+ /**
+ * This is a list of components, and which classes they should map to.
+ *
+ * @var array
+ */
+ static $componentMap = [
+ 'VCALENDAR' => 'Sabre\\VObject\\Component\\VCalendar',
+ 'VALARM' => 'Sabre\\VObject\\Component\\VAlarm',
+ 'VEVENT' => 'Sabre\\VObject\\Component\\VEvent',
+ 'VFREEBUSY' => 'Sabre\\VObject\\Component\\VFreeBusy',
+ 'VAVAILABILITY' => 'Sabre\\VObject\\Component\\VAvailability',
+ 'AVAILABLE' => 'Sabre\\VObject\\Component\\Available',
+ 'VJOURNAL' => 'Sabre\\VObject\\Component\\VJournal',
+ 'VTIMEZONE' => 'Sabre\\VObject\\Component\\VTimeZone',
+ 'VTODO' => 'Sabre\\VObject\\Component\\VTodo',
+ ];
+
+ /**
+ * List of value-types, and which classes they map to.
+ *
+ * @var array
+ */
+ static $valueMap = [
+ 'BINARY' => 'Sabre\\VObject\\Property\\Binary',
+ 'BOOLEAN' => 'Sabre\\VObject\\Property\\Boolean',
+ 'CAL-ADDRESS' => 'Sabre\\VObject\\Property\\ICalendar\\CalAddress',
+ 'DATE' => 'Sabre\\VObject\\Property\\ICalendar\\Date',
+ 'DATE-TIME' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
+ 'DURATION' => 'Sabre\\VObject\\Property\\ICalendar\\Duration',
+ 'FLOAT' => 'Sabre\\VObject\\Property\\FloatValue',
+ 'INTEGER' => 'Sabre\\VObject\\Property\\IntegerValue',
+ 'PERIOD' => 'Sabre\\VObject\\Property\\ICalendar\\Period',
+ 'RECUR' => 'Sabre\\VObject\\Property\\ICalendar\\Recur',
+ 'TEXT' => 'Sabre\\VObject\\Property\\Text',
+ 'TIME' => 'Sabre\\VObject\\Property\\Time',
+ 'UNKNOWN' => 'Sabre\\VObject\\Property\\Unknown', // jCard / jCal-only.
+ 'URI' => 'Sabre\\VObject\\Property\\Uri',
+ 'UTC-OFFSET' => 'Sabre\\VObject\\Property\\UtcOffset',
+ ];
+
+ /**
+ * List of properties, and which classes they map to.
+ *
+ * @var array
+ */
+ static $propertyMap = [
+ // Calendar properties
+ 'CALSCALE' => 'Sabre\\VObject\\Property\\FlatText',
+ 'METHOD' => 'Sabre\\VObject\\Property\\FlatText',
+ 'PRODID' => 'Sabre\\VObject\\Property\\FlatText',
+ 'VERSION' => 'Sabre\\VObject\\Property\\FlatText',
+
+ // Component properties
+ 'ATTACH' => 'Sabre\\VObject\\Property\\Uri',
+ 'CATEGORIES' => 'Sabre\\VObject\\Property\\Text',
+ 'CLASS' => 'Sabre\\VObject\\Property\\FlatText',
+ 'COMMENT' => 'Sabre\\VObject\\Property\\FlatText',
+ 'DESCRIPTION' => 'Sabre\\VObject\\Property\\FlatText',
+ 'GEO' => 'Sabre\\VObject\\Property\\FloatValue',
+ 'LOCATION' => 'Sabre\\VObject\\Property\\FlatText',
+ 'PERCENT-COMPLETE' => 'Sabre\\VObject\\Property\\IntegerValue',
+ 'PRIORITY' => 'Sabre\\VObject\\Property\\IntegerValue',
+ 'RESOURCES' => 'Sabre\\VObject\\Property\\Text',
+ 'STATUS' => 'Sabre\\VObject\\Property\\FlatText',
+ 'SUMMARY' => 'Sabre\\VObject\\Property\\FlatText',
+
+ // Date and Time Component Properties
+ 'COMPLETED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
+ 'DTEND' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
+ 'DUE' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
+ 'DTSTART' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
+ 'DURATION' => 'Sabre\\VObject\\Property\\ICalendar\\Duration',
+ 'FREEBUSY' => 'Sabre\\VObject\\Property\\ICalendar\\Period',
+ 'TRANSP' => 'Sabre\\VObject\\Property\\FlatText',
+
+ // Time Zone Component Properties
+ 'TZID' => 'Sabre\\VObject\\Property\\FlatText',
+ 'TZNAME' => 'Sabre\\VObject\\Property\\FlatText',
+ 'TZOFFSETFROM' => 'Sabre\\VObject\\Property\\UtcOffset',
+ 'TZOFFSETTO' => 'Sabre\\VObject\\Property\\UtcOffset',
+ 'TZURL' => 'Sabre\\VObject\\Property\\Uri',
+
+ // Relationship Component Properties
+ 'ATTENDEE' => 'Sabre\\VObject\\Property\\ICalendar\\CalAddress',
+ 'CONTACT' => 'Sabre\\VObject\\Property\\FlatText',
+ 'ORGANIZER' => 'Sabre\\VObject\\Property\\ICalendar\\CalAddress',
+ 'RECURRENCE-ID' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
+ 'RELATED-TO' => 'Sabre\\VObject\\Property\\FlatText',
+ 'URL' => 'Sabre\\VObject\\Property\\Uri',
+ 'UID' => 'Sabre\\VObject\\Property\\FlatText',
+
+ // Recurrence Component Properties
+ 'EXDATE' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
+ 'RDATE' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
+ 'RRULE' => 'Sabre\\VObject\\Property\\ICalendar\\Recur',
+ 'EXRULE' => 'Sabre\\VObject\\Property\\ICalendar\\Recur', // Deprecated since rfc5545
+
+ // Alarm Component Properties
+ 'ACTION' => 'Sabre\\VObject\\Property\\FlatText',
+ 'REPEAT' => 'Sabre\\VObject\\Property\\IntegerValue',
+ 'TRIGGER' => 'Sabre\\VObject\\Property\\ICalendar\\Duration',
+
+ // Change Management Component Properties
+ 'CREATED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
+ 'DTSTAMP' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
+ 'LAST-MODIFIED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
+ 'SEQUENCE' => 'Sabre\\VObject\\Property\\IntegerValue',
+
+ // Request Status
+ 'REQUEST-STATUS' => 'Sabre\\VObject\\Property\\Text',
+
+ // Additions from draft-daboo-valarm-extensions-04
+ 'ALARM-AGENT' => 'Sabre\\VObject\\Property\\Text',
+ 'ACKNOWLEDGED' => 'Sabre\\VObject\\Property\\ICalendar\\DateTime',
+ 'PROXIMITY' => 'Sabre\\VObject\\Property\\Text',
+ 'DEFAULT-ALARM' => 'Sabre\\VObject\\Property\\Boolean',
+
+ // Additions from draft-daboo-calendar-availability-05
+ 'BUSYTYPE' => 'Sabre\\VObject\\Property\\Text',
+
+ ];
+
+ /**
+ * Returns the current document type.
+ *
+ * @return int
+ */
+ function getDocumentType() {
+
+ return self::ICALENDAR20;
+
+ }
+
+ /**
+ * Returns a list of all 'base components'. For instance, if an Event has
+ * a recurrence rule, and one instance is overridden, the overridden event
+ * will have the same UID, but will be excluded from this list.
+ *
+ * VTIMEZONE components will always be excluded.
+ *
+ * @param string $componentName filter by component name
+ *
+ * @return VObject\Component[]
+ */
+ function getBaseComponents($componentName = null) {
+
+ $isBaseComponent = function($component) {
+
+ if (!$component instanceof VObject\Component) {
+ return false;
+ }
+ if ($component->name === 'VTIMEZONE') {
+ return false;
+ }
+ if (isset($component->{'RECURRENCE-ID'})) {
+ return false;
+ }
+ return true;
+
+ };
+
+ if ($componentName) {
+ // Early exit
+ return array_filter(
+ $this->select($componentName),
+ $isBaseComponent
+ );
+ }
+
+ $components = [];
+ foreach ($this->children as $childGroup) {
+
+ foreach ($childGroup as $child) {
+
+ if (!$child instanceof Component) {
+ // If one child is not a component, they all are so we skip
+ // the entire group.
+ continue 2;
+ }
+ if ($isBaseComponent($child)) {
+ $components[] = $child;
+ }
+
+ }
+
+ }
+ return $components;
+
+ }
+
+ /**
+ * Returns the first component that is not a VTIMEZONE, and does not have
+ * an RECURRENCE-ID.
+ *
+ * If there is no such component, null will be returned.
+ *
+ * @param string $componentName filter by component name
+ *
+ * @return VObject\Component|null
+ */
+ function getBaseComponent($componentName = null) {
+
+ $isBaseComponent = function($component) {
+
+ if (!$component instanceof VObject\Component) {
+ return false;
+ }
+ if ($component->name === 'VTIMEZONE') {
+ return false;
+ }
+ if (isset($component->{'RECURRENCE-ID'})) {
+ return false;
+ }
+ return true;
+
+ };
+
+ if ($componentName) {
+ foreach ($this->select($componentName) as $child) {
+ if ($isBaseComponent($child)) {
+ return $child;
+ }
+ }
+ return null;
+ }
+
+ // Searching all components
+ foreach ($this->children as $childGroup) {
+ foreach ($childGroup as $child) {
+ if ($isBaseComponent($child)) {
+ return $child;
+ }
+ }
+
+ }
+ return null;
+
+ }
+
+ /**
+ * Expand all events in this VCalendar object and return a new VCalendar
+ * with the expanded events.
+ *
+ * If this calendar object, has events with recurrence rules, this method
+ * can be used to expand the event into multiple sub-events.
+ *
+ * Each event will be stripped from it's recurrence information, and only
+ * the instances of the event in the specified timerange will be left
+ * alone.
+ *
+ * In addition, this method will cause timezone information to be stripped,
+ * and normalized to UTC.
+ *
+ * @param DateTimeInterface $start
+ * @param DateTimeInterface $end
+ * @param DateTimeZone $timeZone reference timezone for floating dates and
+ * times.
+ * @return VCalendar
+ */
+ function expand(DateTimeInterface $start, DateTimeInterface $end, DateTimeZone $timeZone = null) {
+
+ $newChildren = [];
+ $recurringEvents = [];
+
+ if (!$timeZone) {
+ $timeZone = new DateTimeZone('UTC');
+ }
+
+ $stripTimezones = function(Component $component) use ($timeZone, &$stripTimezones) {
+
+ foreach ($component->children() as $componentChild) {
+ if ($componentChild instanceof Property\ICalendar\DateTime && $componentChild->hasTime()) {
+
+ $dt = $componentChild->getDateTimes($timeZone);
+ // We only need to update the first timezone, because
+ // setDateTimes will match all other timezones to the
+ // first.
+ $dt[0] = $dt[0]->setTimeZone(new DateTimeZone('UTC'));
+ $componentChild->setDateTimes($dt);
+ } elseif ($componentChild instanceof Component) {
+ $stripTimezones($componentChild);
+ }
+
+ }
+ return $component;
+
+ };
+
+ foreach ($this->children() as $child) {
+
+ if ($child instanceof Property && $child->name !== 'PRODID') {
+ // We explictly want to ignore PRODID, because we want to
+ // overwrite it with our own.
+ $newChildren[] = clone $child;
+ } elseif ($child instanceof Component && $child->name !== 'VTIMEZONE') {
+
+ // We're also stripping all VTIMEZONE objects because we're
+ // converting everything to UTC.
+ if ($child->name === 'VEVENT' && (isset($child->{'RECURRENCE-ID'}) || isset($child->RRULE) || isset($child->RDATE))) {
+ // Handle these a bit later.
+ $uid = (string)$child->UID;
+ if (!$uid) {
+ throw new InvalidDataException('Every VEVENT object must have a UID property');
+ }
+ if (isset($recurringEvents[$uid])) {
+ $recurringEvents[$uid][] = clone $child;
+ } else {
+ $recurringEvents[$uid] = [clone $child];
+ }
+ } elseif ($child->name === 'VEVENT' && $child->isInTimeRange($start, $end)) {
+ $newChildren[] = $stripTimezones(clone $child);
+ }
+
+ }
+
+ }
+
+ foreach ($recurringEvents as $events) {
+
+ try {
+ $it = new EventIterator($events, $timeZone);
+
+ } catch (NoInstancesException $e) {
+ // This event is recurring, but it doesn't have a single
+ // instance. We are skipping this event from the output
+ // entirely.
+ continue;
+ }
+ $it->fastForward($start);
+
+ while ($it->valid() && $it->getDTStart() < $end) {
+
+ if ($it->getDTEnd() > $start) {
+
+ $newChildren[] = $stripTimezones($it->getEventObject());
+
+ }
+ $it->next();
+
+ }
+
+ }
+
+ return new self($newChildren);
+
+ }
+
+ /**
+ * This method should return a list of default property values.
+ *
+ * @return array
+ */
+ protected function getDefaults() {
+
+ return [
+ 'VERSION' => '2.0',
+ 'PRODID' => '-//Sabre//Sabre VObject ' . VObject\Version::VERSION . '//EN',
+ 'CALSCALE' => 'GREGORIAN',
+ ];
+
+ }
+
+ /**
+ * A simple list of validation rules.
+ *
+ * This is simply a list of properties, and how many times they either
+ * must or must not appear.
+ *
+ * Possible values per property:
+ * * 0 - Must not appear.
+ * * 1 - Must appear exactly once.
+ * * + - Must appear at least once.
+ * * * - Can appear any number of times.
+ * * ? - May appear, but not more than once.
+ *
+ * @var array
+ */
+ function getValidationRules() {
+
+ return [
+ 'PRODID' => 1,
+ 'VERSION' => 1,
+
+ 'CALSCALE' => '?',
+ 'METHOD' => '?',
+ ];
+
+ }
+
+ /**
+ * Validates the node for correctness.
+ *
+ * The following options are supported:
+ * Node::REPAIR - May attempt to automatically repair the problem.
+ * Node::PROFILE_CARDDAV - Validate the vCard for CardDAV purposes.
+ * Node::PROFILE_CALDAV - Validate the iCalendar for CalDAV purposes.
+ *
+ * This method returns an array with detected problems.
+ * Every element has the following properties:
+ *
+ * * level - problem level.
+ * * message - A human-readable string describing the issue.
+ * * node - A reference to the problematic node.
+ *
+ * The level means:
+ * 1 - The issue was repaired (only happens if REPAIR was turned on).
+ * 2 - A warning.
+ * 3 - An error.
+ *
+ * @param int $options
+ *
+ * @return array
+ */
+ function validate($options = 0) {
+
+ $warnings = parent::validate($options);
+
+ if ($ver = $this->VERSION) {
+ if ((string)$ver !== '2.0') {
+ $warnings[] = [
+ 'level' => 3,
+ 'message' => 'Only iCalendar version 2.0 as defined in rfc5545 is supported.',
+ 'node' => $this,
+ ];
+ }
+
+ }
+
+ $uidList = [];
+ $componentsFound = 0;
+ $componentTypes = [];
+
+ foreach ($this->children() as $child) {
+ if ($child instanceof Component) {
+ $componentsFound++;
+
+ if (!in_array($child->name, ['VEVENT', 'VTODO', 'VJOURNAL'])) {
+ continue;
+ }
+ $componentTypes[] = $child->name;
+
+ $uid = (string)$child->UID;
+ $isMaster = isset($child->{'RECURRENCE-ID'}) ? 0 : 1;
+ if (isset($uidList[$uid])) {
+ $uidList[$uid]['count']++;
+ if ($isMaster && $uidList[$uid]['hasMaster']) {
+ $warnings[] = [
+ 'level' => 3,
+ 'message' => 'More than one master object was found for the object with UID ' . $uid,
+ 'node' => $this,
+ ];
+ }
+ $uidList[$uid]['hasMaster'] += $isMaster;
+ } else {
+ $uidList[$uid] = [
+ 'count' => 1,
+ 'hasMaster' => $isMaster,
+ ];
+ }
+
+ }
+ }
+
+ if ($componentsFound === 0) {
+ $warnings[] = [
+ 'level' => 3,
+ 'message' => 'An iCalendar object must have at least 1 component.',
+ 'node' => $this,
+ ];
+ }
+
+ if ($options & self::PROFILE_CALDAV) {
+ if (count($uidList) > 1) {
+ $warnings[] = [
+ 'level' => 3,
+ 'message' => 'A calendar object on a CalDAV server may only have components with the same UID.',
+ 'node' => $this,
+ ];
+ }
+ if (count($componentTypes) === 0) {
+ $warnings[] = [
+ 'level' => 3,
+ 'message' => 'A calendar object on a CalDAV server must have at least 1 component (VTODO, VEVENT, VJOURNAL).',
+ 'node' => $this,
+ ];
+ }
+ if (count(array_unique($componentTypes)) > 1) {
+ $warnings[] = [
+ 'level' => 3,
+ 'message' => 'A calendar object on a CalDAV server may only have 1 type of component (VEVENT, VTODO or VJOURNAL).',
+ 'node' => $this,
+ ];
+ }
+
+ if (isset($this->METHOD)) {
+ $warnings[] = [
+ 'level' => 3,
+ 'message' => 'A calendar object on a CalDAV server MUST NOT have a METHOD property.',
+ 'node' => $this,
+ ];
+ }
+ }
+
+ return $warnings;
+
+ }
+
+ /**
+ * Returns all components with a specific UID value.
+ *
+ * @return array
+ */
+ function getByUID($uid) {
+
+ return array_filter($this->getComponents(), function($item) use ($uid) {
+
+ if (!$itemUid = $item->select('UID')) {
+ return false;
+ }
+ $itemUid = current($itemUid)->getValue();
+ return $uid === $itemUid;
+
+ });
+
+ }
+
+
+}