'{DAV:}displayname', 'description' => '{urn:ietf:params:xml:ns:caldav}calendar-description', 'timezone' => '{urn:ietf:params:xml:ns:caldav}calendar-timezone', 'calendarorder' => '{http://apple.com/ns/ical/}calendar-order', 'calendarcolor' => '{http://apple.com/ns/ical/}calendar-color', ]; public function __construct (CalendarCollectionManager $calMgr) { $this->calMgr = $calMgr; } public function getCalendarsForUser ($principalUri) { $calendars = []; foreach ($this->calMgr->getCollections($principalUri) as $row) { $components = []; if ($row['components']) { $components = explode(',', $row['components']); } $calendar = [ 'id' => [(int) $row['id'], (int) $row['id']], 'uri' => $row['uri'], 'principaluri' => $row['principaluri'], '{'.CalDAV\Plugin::NS_CALENDARSERVER.'}getctag' => 'http://sabre.io/ns/sync/'.($row['synctoken'] ? $row['synctoken'] : '0'), '{http://sabredav.org/ns}sync-token' => $row['synctoken'] ? $row['synctoken'] : '0', '{'.CalDAV\Plugin::NS_CALDAV.'}supported-calendar-component-set' => new CalDAV\Xml\Property\SupportedCalendarComponentSet($components), ]; foreach ($this->propertyMap as $dbName => $xmlName) { $calendar[$xmlName] = $row[$dbName]; } $calendars[] = $calendar; } return $calendars; } public function createCalendar ($principalUri, $calendarUri, array $properties) { $sccs = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set'; $components = 'VEVENT,VTODO'; if (isset($properties[$sccs])) { if (!($properties[$sccs] instanceof CalDAV\Xml\Property\SupportedCalendarComponentSet)) { throw new DAV\Exception('The '.$sccs.' property must be of type: \Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet'); } $components = implode(',', $properties[$sccs]->getValue()); } $values = [ 'components' => $components, ]; foreach ($this->propertyMap as $xmlName => $dbName) { if (isset($properties[$xmlName])) { $values[$dbName] = $properties[$xmlName]; } } $calendarId = $this->calMgr->createCollection($principalUri, $calendarUri, $values); return [ $calendarId, $calendarId ]; } public function updateCalendar ($calendarId, \Sabre\DAV\PropPatch $propPatch) { if (!is_array($calendarId)) { throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId'); } list($calendarId) = $calendarId; $propPatch->handle($supportedProperties, function ($mutations) use ($calendarId) { $values = []; foreach ($this->propertyMap as $xmlName => $dbName) { if (isset($mutations[$xmlName])) { $values[$dbName] = $mutations[$xmlName]; } } $this->calMgr->updateCollection($calendarId, $values); return true; }); } public function deleteCalendar ($calendarId) { if (!is_array($calendarId)) { throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId'); } list($calendarId) = $calendarId; $this->calMgr->deleteCollection($calendarId); } public function getCalendarObjects ($calendarId) { if (!is_array($calendarId)) { throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId'); } list($calendarId) = $calendarId; $result = []; foreach ($this->calMgr->getObjects($calendarId) as $row) { $result[] = [ 'id' => $row['id'], 'uri' => $row['uri'], 'lastmodified' => (int) $row['lastmodified'], 'etag' => '"'.$row['etag'].'"', 'size' => (int) $row['size'], 'component' => strtolower($row['componenttype']), ]; } return $result; } public function getCalendarObject ($calendarId, $objectUri) { if (!is_array($calendarId)) { throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId'); } list($calendarId ) = $calendarId; $row = $this->calMgr->getObject($calendarId, $objectUri); if (!$row) { return null; } return [ 'id' => $row['id'], 'uri' => $row['uri'], 'lastmodified' => (int) $row['lastmodified'], 'etag' => '"'.$row['etag'].'"', 'size' => (int) $row['size'], 'calendardata' => $row['calendardata'], 'component' => strtolower($row['componenttype']), ]; } public function getMultipleCalendarObjects($calendarId, array $uris) { if (!is_array($calendarId)) { throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId'); } list($calendarId) = $calendarId; $result = []; foreach (array_chunk($uris, 900) as $chunk) { $row = $this->calMgr()->getObject($calendarId, $chunk); $result[] = [ 'id' => $row['id'], 'uri' => $row['uri'], 'lastmodified' => (int) $row['lastmodified'], 'etag' => '"'.$row['etag'].'"', 'size' => (int) $row['size'], 'calendardata' => $row['calendardata'], 'component' => strtolower($row['componenttype']), ]; } return $result; } public function createCalendarObject ($calendarId, $objectUri, $calendarData) { if (!is_array($calendarId)) { throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId'); } list($calendarId) = $calendarId; return '"'.$this->calMgr->createObject($calendarId, $objectUri, $calendarData, $this->getDenormalizedData($calendarData)).'"'; } public function updateCalendarObject ($calendarId, $objectUri, $calendarData) { if (!is_array($calendarId)) { throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId'); } list($calendarId) = $calendarId; return '"'.$this->calMgr->updateObject($calendarId, $objectUri, $calendarData, $this->getDenormalizedData($calendarData)).'"'; } public function deleteCalendarObject ($calendarId, $objectUri) { if (!is_array($calendarId)) { throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId'); } list($calendarId) = $calendarId; $this->calMgr->deleteObject($calendarId, $objectUri); } public function calendarQuery ($calendarId, array $filters) { if (!is_array($calendarId)) { throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId'); } list($calendarId) = $calendarId; $componentType = NULL; $requirePostFilter = true; $timeRange = NULL; // if no filters were specified, we don't need to filter after a query if (!$filters['prop-filters'] && !$filters['comp-filters']) { $requirePostFilter = false; } // Figuring out if there's a component filter if (count($filters['comp-filters']) > 0 && !$filters['comp-filters'][0]['is-not-defined']) { $componentType = $filters['comp-filters'][0]['name']; // Checking if we need post-filters if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['time-range'] && !$filters['comp-filters'][0]['prop-filters']) { $requirePostFilter = false; } // There was a time-range filter if ('VEVENT' == $componentType && isset($filters['comp-filters'][0]['time-range'])) { $timeRange = $filters['comp-filters'][0]['time-range']; // If start time OR the end time is not specified, we can do a // 100% accurate mysql query. if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['prop-filters'] && (!$timeRange['start'] || !$timeRange['end'])) { $requirePostFilter = false; } } } $entries = $this->calMgr->getObjects($calendarId); foreach ($entries as $key => $entry) { if ($componentType && $componentType !== $entry['componenttype']) unset($entries[$key]); continue; if ($timeRange) { if ($timeRange['start'] && $entry['lastoccurence'] > $timeRange['start']->getTimeStamp()) unset($entries[$key]); continue; if ($timeRange['end'] && $entry['firstoccurence'] < $timeRange['end']->getTimeStamp()) unset($entries[$key]); continue; } } $result = []; foreach ($entries as $row) { if ($requirePostFilter) { $row['calendarid'] = [$calendarId, $calendarId]; if (!$this->validateFilterForObject($row, $filters)) { continue; } } $result[] = $row['uri']; } return $result; } public function getCalendarObjectByUID ($principalUri, $uid) { return $this->calMgr->getUriByUID($principalUri, $uid); } public function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit = null) { if (!is_array($calendarId)) { throw new \InvalidArgumentException('The value passed to $calendarId is expected to be an array with a calendarId and an instanceId'); } list($calendarId) = $calendarId; return $this->calMgr->getChanges($calendarid, $syncToken, $syncLevel); } protected function getDenormalizedData ($calendarData) { $vObject = VObject\Reader::read($calendarData); $componentType = null; $component = null; $firstOccurence = null; $lastOccurence = null; $uid = null; foreach ($vObject->getComponents() as $component) { if ('VTIMEZONE' !== $component->name) { $componentType = $component->name; $uid = (string) $component->UID; break; } } if (!$componentType) { throw new \Sabre\DAV\Exception\BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component'); } if ('VEVENT' === $componentType) { $firstOccurence = $component->DTSTART->getDateTime()->getTimeStamp(); // Finding the last occurence is a bit harder if (!isset($component->RRULE)) { if (isset($component->DTEND)) { $lastOccurence = $component->DTEND->getDateTime()->getTimeStamp(); } elseif (isset($component->DURATION)) { $endDate = clone $component->DTSTART->getDateTime(); $endDate = $endDate->add(VObject\DateTimeParser::parse($component->DURATION->getValue())); $lastOccurence = $endDate->getTimeStamp(); } elseif (!$component->DTSTART->hasTime()) { $endDate = clone $component->DTSTART->getDateTime(); $endDate = $endDate->modify('+1 day'); $lastOccurence = $endDate->getTimeStamp(); } else { $lastOccurence = $firstOccurence; } } else { $it = new VObject\Recur\EventIterator($vObject, (string) $component->UID); $maxDate = new \DateTime(self::MAX_DATE); if ($it->isInfinite()) { $lastOccurence = $maxDate->getTimeStamp(); } else { $end = $it->getDtEnd(); while ($it->valid() && $end < $maxDate) { $end = $it->getDtEnd(); $it->next(); } $lastOccurence = $end->getTimeStamp(); } } // Ensure Occurence values are positive if ($firstOccurence < 0) { $firstOccurence = 0; } if ($lastOccurence < 0) { $lastOccurence = 0; } } // Destroy circular references to PHP will GC the object. $vObject->destroy(); return [ 'etag' => md5($calendarData), 'size' => strlen($calendarData), 'componenttype' => $componentType, 'firstoccurence' => $firstOccurence, 'lastoccurence' => $lastOccurence, 'uid' => $uid, ]; } }