File manager - Edit - /home/jardides/www/Jardi-design/images/administrator/CalDAV.tar
Back
CalendarObject.php 0000604 00000004645 15247161531 0010127 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * @copyright Copyright (c) 2017, Georg Ehrke * * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Georg Ehrke <oc.list@georgehrke.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CalDAV; use Sabre\VObject\Component; use Sabre\VObject\Property; use Sabre\VObject\Reader; class CalendarObject extends \Sabre\CalDAV\CalendarObject { /** * @inheritdoc */ function get() { $data = parent::get(); if ($this->isShared() && $this->objectData['classification'] === CalDavBackend::CLASSIFICATION_CONFIDENTIAL) { return $this->createConfidentialObject($data); } return $data; } protected function isShared() { if (!isset($this->calendarInfo['{http://owncloud.org/ns}owner-principal'])) { return false; } return $this->calendarInfo['{http://owncloud.org/ns}owner-principal'] !== $this->calendarInfo['principaluri']; } /** * @param string $calData * @return string */ private static function createConfidentialObject($calData) { $vObject = Reader::read($calData); /** @var Component $vElement */ $vElement = null; if(isset($vObject->VEVENT)) { $vElement = $vObject->VEVENT; } if(isset($vObject->VJOURNAL)) { $vElement = $vObject->VJOURNAL; } if(isset($vObject->VTODO)) { $vElement = $vObject->VTODO; } if(!is_null($vElement)) { foreach ($vElement->children() as &$property) { /** @var Property $property */ switch($property->name) { case 'CREATED': case 'DTSTART': case 'RRULE': case 'DURATION': case 'DTEND': case 'CLASS': case 'UID': break; case 'SUMMARY': $property->setValue('Busy'); break; default: $vElement->__unset($property->name); unset($property); break; } } } return $vObject->serialize(); } } BirthdayService.php 0000604 00000021471 15247161531 0010352 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * @copyright Copyright (c) 2016, Georg Ehrke * * @author Achim Königs <garfonso@tratschtante.de> * @author Robin Appelman <robin@icewind.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Georg Ehrke <georg@nextcloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CalDAV; use Exception; use OCA\DAV\CardDAV\CardDavBackend; use OCA\DAV\DAV\GroupPrincipalBackend; use Sabre\VObject\Component\VCalendar; use Sabre\VObject\Component\VCard; use Sabre\VObject\DateTimeParser; use Sabre\VObject\Document; use Sabre\VObject\InvalidDataException; use Sabre\VObject\Property\VCard\DateAndOrTime; use Sabre\VObject\Reader; class BirthdayService { const BIRTHDAY_CALENDAR_URI = 'contact_birthdays'; /** @var GroupPrincipalBackend */ private $principalBackend; /** @var CalDavBackend */ private $calDavBackEnd; /** @var CardDavBackend */ private $cardDavBackEnd; /** * BirthdayService constructor. * * @param CalDavBackend $calDavBackEnd * @param CardDavBackend $cardDavBackEnd * @param GroupPrincipalBackend $principalBackend */ public function __construct(CalDavBackend $calDavBackEnd, CardDavBackend $cardDavBackEnd, GroupPrincipalBackend $principalBackend) { $this->calDavBackEnd = $calDavBackEnd; $this->cardDavBackEnd = $cardDavBackEnd; $this->principalBackend = $principalBackend; } /** * @param int $addressBookId * @param string $cardUri * @param string $cardData */ public function onCardChanged($addressBookId, $cardUri, $cardData) { $targetPrincipals = $this->getAllAffectedPrincipals($addressBookId); $book = $this->cardDavBackEnd->getAddressBookById($addressBookId); $targetPrincipals[] = $book['principaluri']; $datesToSync = [ ['postfix' => '', 'field' => 'BDAY', 'symbol' => '*'], ['postfix' => '-death', 'field' => 'DEATHDATE', 'symbol' => "†"], ['postfix' => '-anniversary', 'field' => 'ANNIVERSARY', 'symbol' => "⚭"], ]; foreach ($targetPrincipals as $principalUri) { $calendar = $this->ensureCalendarExists($principalUri); foreach ($datesToSync as $type) { $this->updateCalendar($cardUri, $cardData, $book, $calendar['id'], $type); } } } /** * @param int $addressBookId * @param string $cardUri */ public function onCardDeleted($addressBookId, $cardUri) { $targetPrincipals = $this->getAllAffectedPrincipals($addressBookId); $book = $this->cardDavBackEnd->getAddressBookById($addressBookId); $targetPrincipals[] = $book['principaluri']; foreach ($targetPrincipals as $principalUri) { $calendar = $this->ensureCalendarExists($principalUri); foreach (['', '-death', '-anniversary'] as $tag) { $objectUri = $book['uri'] . '-' . $cardUri . $tag .'.ics'; $this->calDavBackEnd->deleteCalendarObject($calendar['id'], $objectUri); } } } /** * @param string $principal * @return array|null * @throws \Sabre\DAV\Exception\BadRequest */ public function ensureCalendarExists($principal) { $book = $this->calDavBackEnd->getCalendarByUri($principal, self::BIRTHDAY_CALENDAR_URI); if (!is_null($book)) { return $book; } $this->calDavBackEnd->createCalendar($principal, self::BIRTHDAY_CALENDAR_URI, [ '{DAV:}displayname' => 'Contact birthdays', '{http://apple.com/ns/ical/}calendar-color' => '#FFFFCA', 'components' => 'VEVENT', ]); return $this->calDavBackEnd->getCalendarByUri($principal, self::BIRTHDAY_CALENDAR_URI); } /** * @param string $cardData * @param string $dateField * @param string $summarySymbol * @return null|VCalendar */ public function buildDateFromContact($cardData, $dateField, $summarySymbol) { if (empty($cardData)) { return null; } try { $doc = Reader::read($cardData); // We're always converting to vCard 4.0 so we can rely on the // VCardConverter handling the X-APPLE-OMIT-YEAR property for us. if (!$doc instanceof VCard) { return null; } $doc = $doc->convert(Document::VCARD40); } catch (Exception $e) { return null; } if (!isset($doc->{$dateField})) { return null; } if (!isset($doc->FN)) { return null; } $birthday = $doc->{$dateField}; if (!(string)$birthday) { return null; } // Skip if the BDAY property is not of the right type. if (!$birthday instanceof DateAndOrTime) { return null; } // Skip if we can't parse the BDAY value. try { $dateParts = DateTimeParser::parseVCardDateTime($birthday->getValue()); } catch (InvalidDataException $e) { return null; } $unknownYear = false; if (!$dateParts['year']) { $birthday = '1900-' . $dateParts['month'] . '-' . $dateParts['date']; $unknownYear = true; } try { $date = new \DateTime($birthday); } catch (Exception $e) { return null; } if ($unknownYear) { $summary = $doc->FN->getValue() . ' ' . $summarySymbol; } else { $year = (int)$date->format('Y'); $summary = $doc->FN->getValue() . " ($summarySymbol$year)"; } $vCal = new VCalendar(); $vCal->VERSION = '2.0'; $vEvent = $vCal->createComponent('VEVENT'); $vEvent->add('DTSTART'); $vEvent->DTSTART->setDateTime( $date ); $vEvent->DTSTART['VALUE'] = 'DATE'; $vEvent->add('DTEND'); $date->add(new \DateInterval('P1D')); $vEvent->DTEND->setDateTime( $date ); $vEvent->DTEND['VALUE'] = 'DATE'; $vEvent->{'UID'} = $doc->UID; $vEvent->{'RRULE'} = 'FREQ=YEARLY'; $vEvent->{'SUMMARY'} = $summary; $vEvent->{'TRANSP'} = 'TRANSPARENT'; $alarm = $vCal->createComponent('VALARM'); $alarm->add($vCal->createProperty('TRIGGER', '-PT0M', ['VALUE' => 'DURATION'])); $alarm->add($vCal->createProperty('ACTION', 'DISPLAY')); $alarm->add($vCal->createProperty('DESCRIPTION', $vEvent->{'SUMMARY'})); $vEvent->add($alarm); $vCal->add($vEvent); return $vCal; } /** * @param string $user */ public function syncUser($user) { $principal = 'principals/users/'.$user; $this->ensureCalendarExists($principal); $books = $this->cardDavBackEnd->getAddressBooksForUser($principal); foreach($books as $book) { $cards = $this->cardDavBackEnd->getCards($book['id']); foreach($cards as $card) { $this->onCardChanged($book['id'], $card['uri'], $card['carddata']); } } } /** * @param string $existingCalendarData * @param VCalendar $newCalendarData * @return bool */ public function birthdayEvenChanged($existingCalendarData, $newCalendarData) { try { $existingBirthday = Reader::read($existingCalendarData); } catch (Exception $ex) { return true; } if ($newCalendarData->VEVENT->DTSTART->getValue() !== $existingBirthday->VEVENT->DTSTART->getValue() || $newCalendarData->VEVENT->SUMMARY->getValue() !== $existingBirthday->VEVENT->SUMMARY->getValue() ) { return true; } return false; } /** * @param integer $addressBookId * @return mixed */ protected function getAllAffectedPrincipals($addressBookId) { $targetPrincipals = []; $shares = $this->cardDavBackEnd->getShares($addressBookId); foreach ($shares as $share) { if ($share['{http://owncloud.org/ns}group-share']) { $users = $this->principalBackend->getGroupMemberSet($share['{http://owncloud.org/ns}principal']); foreach ($users as $user) { $targetPrincipals[] = $user['uri']; } } else { $targetPrincipals[] = $share['{http://owncloud.org/ns}principal']; } } return array_values(array_unique($targetPrincipals, SORT_STRING)); } /** * @param string $cardUri * @param string $cardData * @param array $book * @param int $calendarId * @param string $type */ private function updateCalendar($cardUri, $cardData, $book, $calendarId, $type) { $objectUri = $book['uri'] . '-' . $cardUri . $type['postfix'] . '.ics'; $calendarData = $this->buildDateFromContact($cardData, $type['field'], $type['symbol']); $existing = $this->calDavBackEnd->getCalendarObject($calendarId, $objectUri); if (is_null($calendarData)) { if (!is_null($existing)) { $this->calDavBackEnd->deleteCalendarObject($calendarId, $objectUri); } } else { if (is_null($existing)) { $this->calDavBackEnd->createCalendarObject($calendarId, $objectUri, $calendarData->serialize()); } else { if ($this->birthdayEvenChanged($existing['calendardata'], $calendarData)) { $this->calDavBackEnd->updateCalendarObject($calendarId, $objectUri, $calendarData->serialize()); } } } } } Calendar.php 0000604 00000021367 15247161531 0007000 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Joas Schilling <coding@schilljs.com> * @author Lukas Reschke <lukas@statuscode.ch> * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CalDAV; use OCA\DAV\DAV\Sharing\IShareable; use OCP\IL10N; use Sabre\CalDAV\Backend\BackendInterface; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\Exception\NotFound; use Sabre\DAV\PropPatch; /** * Class Calendar * * @package OCA\DAV\CalDAV * @property BackendInterface|CalDavBackend $caldavBackend */ class Calendar extends \Sabre\CalDAV\Calendar implements IShareable { public function __construct(BackendInterface $caldavBackend, $calendarInfo, IL10N $l10n) { parent::__construct($caldavBackend, $calendarInfo); if ($this->getName() === BirthdayService::BIRTHDAY_CALENDAR_URI) { $this->calendarInfo['{DAV:}displayname'] = $l10n->t('Contact birthdays'); } if ($this->getName() === CalDavBackend::PERSONAL_CALENDAR_URI && $this->calendarInfo['{DAV:}displayname'] === CalDavBackend::PERSONAL_CALENDAR_NAME) { $this->calendarInfo['{DAV:}displayname'] = $l10n->t('Personal'); } } /** * Updates the list of shares. * * The first array is a list of people that are to be added to the * resource. * * Every element in the add array has the following properties: * * href - A url. Usually a mailto: address * * commonName - Usually a first and last name, or false * * summary - A description of the share, can also be false * * readOnly - A boolean value * * Every element in the remove array is just the address string. * * @param array $add * @param array $remove * @return void * @throws Forbidden */ public function updateShares(array $add, array $remove) { if ($this->isShared()) { throw new Forbidden(); } $this->caldavBackend->updateShares($this, $add, $remove); } /** * Returns the list of people whom this resource is shared with. * * Every element in this array should have the following properties: * * href - Often a mailto: address * * commonName - Optional, for example a first + last name * * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants. * * readOnly - boolean * * summary - Optional, a description for the share * * @return array */ public function getShares() { if ($this->isShared()) { return []; } return $this->caldavBackend->getShares($this->getResourceId()); } /** * @return int */ public function getResourceId() { return $this->calendarInfo['id']; } /** * @return string */ public function getPrincipalURI() { return $this->calendarInfo['principaluri']; } public function getACL() { $acl = [ [ 'privilege' => '{DAV:}read', 'principal' => $this->getOwner(), 'protected' => true, ]]; if ($this->getName() !== BirthdayService::BIRTHDAY_CALENDAR_URI) { $acl[] = [ 'privilege' => '{DAV:}write', 'principal' => $this->getOwner(), 'protected' => true, ]; } else { $acl[] = [ 'privilege' => '{DAV:}write-properties', 'principal' => $this->getOwner(), 'protected' => true, ]; } if ($this->getOwner() !== parent::getOwner()) { $acl[] = [ 'privilege' => '{DAV:}read', 'principal' => parent::getOwner(), 'protected' => true, ]; if ($this->canWrite()) { $acl[] = [ 'privilege' => '{DAV:}write', 'principal' => parent::getOwner(), 'protected' => true, ]; } else { $acl[] = [ 'privilege' => '{DAV:}write-properties', 'principal' => parent::getOwner(), 'protected' => true, ]; } } if ($this->isPublic()) { $acl[] = [ 'privilege' => '{DAV:}read', 'principal' => 'principals/system/public', 'protected' => true, ]; } $acl = $this->caldavBackend->applyShareAcl($this->getResourceId(), $acl); if (!$this->isShared()) { return $acl; } $allowedPrincipals = [$this->getOwner(), parent::getOwner(), 'principals/system/public']; return array_filter($acl, function($rule) use ($allowedPrincipals) { return in_array($rule['principal'], $allowedPrincipals); }); } public function getChildACL() { return $this->getACL(); } public function getOwner() { if (isset($this->calendarInfo['{http://owncloud.org/ns}owner-principal'])) { return $this->calendarInfo['{http://owncloud.org/ns}owner-principal']; } return parent::getOwner(); } public function delete() { if (isset($this->calendarInfo['{http://owncloud.org/ns}owner-principal']) && $this->calendarInfo['{http://owncloud.org/ns}owner-principal'] !== $this->calendarInfo['principaluri']) { $principal = 'principal:' . parent::getOwner(); $shares = $this->caldavBackend->getShares($this->getResourceId()); $shares = array_filter($shares, function($share) use ($principal){ return $share['href'] === $principal; }); if (empty($shares)) { throw new Forbidden(); } $this->caldavBackend->updateShares($this, [], [ 'href' => $principal ]); return; } parent::delete(); } public function propPatch(PropPatch $propPatch) { // parent::propPatch will only update calendars table // if calendar is shared, changes have to be made to the properties table if (!$this->isShared()) { parent::propPatch($propPatch); } } public function getChild($name) { $obj = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'], $name); if (!$obj) { throw new NotFound('Calendar object not found'); } if ($obj['classification'] === CalDavBackend::CLASSIFICATION_PRIVATE && $this->isShared()) { throw new NotFound('Calendar object not found'); } $obj['acl'] = $this->getChildACL(); return new CalendarObject($this->caldavBackend, $this->calendarInfo, $obj); } public function getChildren() { $objs = $this->caldavBackend->getCalendarObjects($this->calendarInfo['id']); $children = []; foreach ($objs as $obj) { if ($obj['classification'] === CalDavBackend::CLASSIFICATION_PRIVATE && $this->isShared()) { continue; } $obj['acl'] = $this->getChildACL(); $children[] = new CalendarObject($this->caldavBackend, $this->calendarInfo, $obj); } return $children; } public function getMultipleChildren(array $paths) { $objs = $this->caldavBackend->getMultipleCalendarObjects($this->calendarInfo['id'], $paths); $children = []; foreach ($objs as $obj) { if ($obj['classification'] === CalDavBackend::CLASSIFICATION_PRIVATE && $this->isShared()) { continue; } $obj['acl'] = $this->getChildACL(); $children[] = new CalendarObject($this->caldavBackend, $this->calendarInfo, $obj); } return $children; } public function childExists($name) { $obj = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'], $name); if (!$obj) { return false; } if ($obj['classification'] === CalDavBackend::CLASSIFICATION_PRIVATE && $this->isShared()) { return false; } return true; } public function calendarQuery(array $filters) { $uris = $this->caldavBackend->calendarQuery($this->calendarInfo['id'], $filters); if ($this->isShared()) { return array_filter($uris, function ($uri) { return $this->childExists($uri); }); } return $uris; } /** * @param boolean $value * @return string|null */ public function setPublishStatus($value) { $publicUri = $this->caldavBackend->setPublishStatus($value, $this); $this->calendarInfo['publicuri'] = $publicUri; return $publicUri; } /** * @return mixed $value */ public function getPublishStatus() { return $this->caldavBackend->getPublishStatus($this); } private function canWrite() { if (isset($this->calendarInfo['{http://owncloud.org/ns}read-only'])) { return !$this->calendarInfo['{http://owncloud.org/ns}read-only']; } return true; } private function isPublic() { return isset($this->calendarInfo['{http://owncloud.org/ns}public']); } protected function isShared() { if (!isset($this->calendarInfo['{http://owncloud.org/ns}owner-principal'])) { return false; } return $this->calendarInfo['{http://owncloud.org/ns}owner-principal'] !== $this->calendarInfo['principaluri']; } public function isSubscription() { return isset($this->calendarInfo['{http://calendarserver.org/ns/}source']); } } CalDavBackend.php 0000604 00000214211 15247161531 0007661 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * @copyright Copyright (c) 2017 Georg Ehrke * * @author Joas Schilling <coding@schilljs.com> * @author Stefan Weil <sw@weilnetz.de> * @author Thomas Citharel <tcit@tcit.fr> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Georg Ehrke <oc.list@georgehrke.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CalDAV; use OCA\DAV\DAV\Sharing\IShareable; use OCP\DB\QueryBuilder\IQueryBuilder; use OCA\DAV\Connector\Sabre\Principal; use OCA\DAV\DAV\Sharing\Backend; use OCP\IDBConnection; use OCP\IUser; use OCP\IUserManager; use OCP\Security\ISecureRandom; use Sabre\CalDAV\Backend\AbstractBackend; use Sabre\CalDAV\Backend\SchedulingSupport; use Sabre\CalDAV\Backend\SubscriptionSupport; use Sabre\CalDAV\Backend\SyncSupport; use Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp; use Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet; use Sabre\DAV; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\Exception\NotFound; use Sabre\DAV\PropPatch; use Sabre\HTTP\URLUtil; use Sabre\VObject\Component\VCalendar; use Sabre\VObject\DateTimeParser; use Sabre\VObject\Reader; use Sabre\VObject\Recur\EventIterator; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\EventDispatcher\GenericEvent; /** * Class CalDavBackend * * Code is heavily inspired by https://github.com/fruux/sabre-dav/blob/master/lib/CalDAV/Backend/PDO.php * * @package OCA\DAV\CalDAV */ class CalDavBackend extends AbstractBackend implements SyncSupport, SubscriptionSupport, SchedulingSupport { const PERSONAL_CALENDAR_URI = 'personal'; const PERSONAL_CALENDAR_NAME = 'Personal'; /** * We need to specify a max date, because we need to stop *somewhere* * * On 32 bit system the maximum for a signed integer is 2147483647, so * MAX_DATE cannot be higher than date('Y-m-d', 2147483647) which results * in 2038-01-19 to avoid problems when the date is converted * to a unix timestamp. */ const MAX_DATE = '2038-01-01'; const ACCESS_PUBLIC = 4; const CLASSIFICATION_PUBLIC = 0; const CLASSIFICATION_PRIVATE = 1; const CLASSIFICATION_CONFIDENTIAL = 2; /** * List of CalDAV properties, and how they map to database field names * Add your own properties by simply adding on to this array. * * Note that only string-based properties are supported here. * * @var array */ public $propertyMap = [ '{DAV:}displayname' => 'displayname', '{urn:ietf:params:xml:ns:caldav}calendar-description' => 'description', '{urn:ietf:params:xml:ns:caldav}calendar-timezone' => 'timezone', '{http://apple.com/ns/ical/}calendar-order' => 'calendarorder', '{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor', ]; /** * List of subscription properties, and how they map to database field names. * * @var array */ public $subscriptionPropertyMap = [ '{DAV:}displayname' => 'displayname', '{http://apple.com/ns/ical/}refreshrate' => 'refreshrate', '{http://apple.com/ns/ical/}calendar-order' => 'calendarorder', '{http://apple.com/ns/ical/}calendar-color' => 'calendarcolor', '{http://calendarserver.org/ns/}subscribed-strip-todos' => 'striptodos', '{http://calendarserver.org/ns/}subscribed-strip-alarms' => 'stripalarms', '{http://calendarserver.org/ns/}subscribed-strip-attachments' => 'stripattachments', ]; /** @var array properties to index */ public static $indexProperties = ['CATEGORIES', 'COMMENT', 'DESCRIPTION', 'LOCATION', 'RESOURCES', 'STATUS', 'SUMMARY', 'ATTENDEE', 'CONTACT', 'ORGANIZER']; /** @var array parameters to index */ public static $indexParameters = [ 'ATTENDEE' => ['CN'], 'ORGANIZER' => ['CN'], ]; /** * @var string[] Map of uid => display name */ protected $userDisplayNames; /** @var IDBConnection */ private $db; /** @var Backend */ private $sharingBackend; /** @var Principal */ private $principalBackend; /** @var IUserManager */ private $userManager; /** @var ISecureRandom */ private $random; /** @var EventDispatcherInterface */ private $dispatcher; /** @var bool */ private $legacyEndpoint; /** @var string */ private $dbObjectPropertiesTable = 'calendarobjects_props'; /** * CalDavBackend constructor. * * @param IDBConnection $db * @param Principal $principalBackend * @param IUserManager $userManager * @param ISecureRandom $random * @param EventDispatcherInterface $dispatcher * @param bool $legacyEndpoint */ public function __construct(IDBConnection $db, Principal $principalBackend, IUserManager $userManager, ISecureRandom $random, EventDispatcherInterface $dispatcher, $legacyEndpoint = false) { $this->db = $db; $this->principalBackend = $principalBackend; $this->userManager = $userManager; $this->sharingBackend = new Backend($this->db, $principalBackend, 'calendar'); $this->random = $random; $this->dispatcher = $dispatcher; $this->legacyEndpoint = $legacyEndpoint; } /** * Return the number of calendars for a principal * * By default this excludes the automatically generated birthday calendar * * @param $principalUri * @param bool $excludeBirthday * @return int */ public function getCalendarsForUserCount($principalUri, $excludeBirthday = true) { $principalUri = $this->convertPrincipal($principalUri, true); $query = $this->db->getQueryBuilder(); $query->select($query->createFunction('COUNT(*)')) ->from('calendars') ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri))); if ($excludeBirthday) { $query->andWhere($query->expr()->neq('uri', $query->createNamedParameter(BirthdayService::BIRTHDAY_CALENDAR_URI))); } return (int)$query->execute()->fetchColumn(); } /** * Returns a list of calendars for a principal. * * Every project is an array with the following keys: * * id, a unique id that will be used by other functions to modify the * calendar. This can be the same as the uri or a database key. * * uri, which the basename of the uri with which the calendar is * accessed. * * principaluri. The owner of the calendar. Almost always the same as * principalUri passed to this method. * * Furthermore it can contain webdav properties in clark notation. A very * common one is '{DAV:}displayname'. * * Many clients also require: * {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set * For this property, you can just return an instance of * Sabre\CalDAV\Property\SupportedCalendarComponentSet. * * If you return {http://sabredav.org/ns}read-only and set the value to 1, * ACL will automatically be put in read-only mode. * * @param string $principalUri * @return array */ function getCalendarsForUser($principalUri) { $principalUriOriginal = $principalUri; $principalUri = $this->convertPrincipal($principalUri, true); $fields = array_values($this->propertyMap); $fields[] = 'id'; $fields[] = 'uri'; $fields[] = 'synctoken'; $fields[] = 'components'; $fields[] = 'principaluri'; $fields[] = 'transparent'; // Making fields a comma-delimited list $query = $this->db->getQueryBuilder(); $query->select($fields)->from('calendars') ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri))) ->orderBy('calendarorder', 'ASC'); $stmt = $query->execute(); $calendars = []; while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { $components = []; if ($row['components']) { $components = explode(',',$row['components']); } $calendar = [ 'id' => $row['id'], 'uri' => $row['uri'], 'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint), '{' . 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', '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components), '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'), '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($principalUri, !$this->legacyEndpoint), ]; foreach($this->propertyMap as $xmlName=>$dbName) { $calendar[$xmlName] = $row[$dbName]; } $this->addOwnerPrincipal($calendar); if (!isset($calendars[$calendar['id']])) { $calendars[$calendar['id']] = $calendar; } } $stmt->closeCursor(); // query for shared calendars $principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true); $principals = array_map(function($principal) { return urldecode($principal); }, $principals); $principals[]= $principalUri; $fields = array_values($this->propertyMap); $fields[] = 'a.id'; $fields[] = 'a.uri'; $fields[] = 'a.synctoken'; $fields[] = 'a.components'; $fields[] = 'a.principaluri'; $fields[] = 'a.transparent'; $fields[] = 's.access'; $query = $this->db->getQueryBuilder(); $result = $query->select($fields) ->from('dav_shares', 's') ->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id')) ->where($query->expr()->in('s.principaluri', $query->createParameter('principaluri'))) ->andWhere($query->expr()->eq('s.type', $query->createParameter('type'))) ->setParameter('type', 'calendar') ->setParameter('principaluri', $principals, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY) ->execute(); $readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only'; while($row = $result->fetch()) { if ($row['principaluri'] === $principalUri) { continue; } $readOnly = (int) $row['access'] === Backend::ACCESS_READ; if (isset($calendars[$row['id']])) { if ($readOnly) { // New share can not have more permissions then the old one. continue; } if (isset($calendars[$row['id']][$readOnlyPropertyName]) && $calendars[$row['id']][$readOnlyPropertyName] === 0) { // Old share is already read-write, no more permissions can be gained continue; } } list(, $name) = URLUtil::splitPath($row['principaluri']); $uri = $row['uri'] . '_shared_by_' . $name; $row['displayname'] = $row['displayname'] . ' (' . $this->getUserDisplayName($name) . ')'; $components = []; if ($row['components']) { $components = explode(',',$row['components']); } $calendar = [ 'id' => $row['id'], 'uri' => $uri, 'principaluri' => $this->convertPrincipal($principalUri, !$this->legacyEndpoint), '{' . 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', '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components), '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'), '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint), $readOnlyPropertyName => $readOnly, ]; foreach($this->propertyMap as $xmlName=>$dbName) { $calendar[$xmlName] = $row[$dbName]; } $this->addOwnerPrincipal($calendar); $calendars[$calendar['id']] = $calendar; } $result->closeCursor(); return array_values($calendars); } public function getUsersOwnCalendars($principalUri) { $principalUri = $this->convertPrincipal($principalUri, true); $fields = array_values($this->propertyMap); $fields[] = 'id'; $fields[] = 'uri'; $fields[] = 'synctoken'; $fields[] = 'components'; $fields[] = 'principaluri'; $fields[] = 'transparent'; // Making fields a comma-delimited list $query = $this->db->getQueryBuilder(); $query->select($fields)->from('calendars') ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri))) ->orderBy('calendarorder', 'ASC'); $stmt = $query->execute(); $calendars = []; while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { $components = []; if ($row['components']) { $components = explode(',',$row['components']); } $calendar = [ 'id' => $row['id'], 'uri' => $row['uri'], 'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint), '{' . 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', '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components), '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'), ]; foreach($this->propertyMap as $xmlName=>$dbName) { $calendar[$xmlName] = $row[$dbName]; } $this->addOwnerPrincipal($calendar); if (!isset($calendars[$calendar['id']])) { $calendars[$calendar['id']] = $calendar; } } $stmt->closeCursor(); return array_values($calendars); } private function getUserDisplayName($uid) { if (!isset($this->userDisplayNames[$uid])) { $user = $this->userManager->get($uid); if ($user instanceof IUser) { $this->userDisplayNames[$uid] = $user->getDisplayName(); } else { $this->userDisplayNames[$uid] = $uid; } } return $this->userDisplayNames[$uid]; } /** * @return array */ public function getPublicCalendars() { $fields = array_values($this->propertyMap); $fields[] = 'a.id'; $fields[] = 'a.uri'; $fields[] = 'a.synctoken'; $fields[] = 'a.components'; $fields[] = 'a.principaluri'; $fields[] = 'a.transparent'; $fields[] = 's.access'; $fields[] = 's.publicuri'; $calendars = []; $query = $this->db->getQueryBuilder(); $result = $query->select($fields) ->from('dav_shares', 's') ->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id')) ->where($query->expr()->in('s.access', $query->createNamedParameter(self::ACCESS_PUBLIC))) ->andWhere($query->expr()->eq('s.type', $query->createNamedParameter('calendar'))) ->execute(); while($row = $result->fetch()) { list(, $name) = URLUtil::splitPath($row['principaluri']); $row['displayname'] = $row['displayname'] . "($name)"; $components = []; if ($row['components']) { $components = explode(',',$row['components']); } $calendar = [ 'id' => $row['id'], 'uri' => $row['publicuri'], 'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint), '{' . 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', '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components), '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'), '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], $this->legacyEndpoint), '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => (int)$row['access'] === Backend::ACCESS_READ, '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}public' => (int)$row['access'] === self::ACCESS_PUBLIC, ]; foreach($this->propertyMap as $xmlName=>$dbName) { $calendar[$xmlName] = $row[$dbName]; } $this->addOwnerPrincipal($calendar); if (!isset($calendars[$calendar['id']])) { $calendars[$calendar['id']] = $calendar; } } $result->closeCursor(); return array_values($calendars); } /** * @param string $uri * @return array * @throws NotFound */ public function getPublicCalendar($uri) { $fields = array_values($this->propertyMap); $fields[] = 'a.id'; $fields[] = 'a.uri'; $fields[] = 'a.synctoken'; $fields[] = 'a.components'; $fields[] = 'a.principaluri'; $fields[] = 'a.transparent'; $fields[] = 's.access'; $fields[] = 's.publicuri'; $query = $this->db->getQueryBuilder(); $result = $query->select($fields) ->from('dav_shares', 's') ->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id')) ->where($query->expr()->in('s.access', $query->createNamedParameter(self::ACCESS_PUBLIC))) ->andWhere($query->expr()->eq('s.type', $query->createNamedParameter('calendar'))) ->andWhere($query->expr()->eq('s.publicuri', $query->createNamedParameter($uri))) ->execute(); $row = $result->fetch(\PDO::FETCH_ASSOC); $result->closeCursor(); if ($row === false) { throw new NotFound('Node with name \'' . $uri . '\' could not be found'); } list(, $name) = URLUtil::splitPath($row['principaluri']); $row['displayname'] = $row['displayname'] . ' ' . "($name)"; $components = []; if ($row['components']) { $components = explode(',',$row['components']); } $calendar = [ 'id' => $row['id'], 'uri' => $row['publicuri'], 'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint), '{' . 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', '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components), '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'), '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint), '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => (int)$row['access'] === Backend::ACCESS_READ, '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}public' => (int)$row['access'] === self::ACCESS_PUBLIC, ]; foreach($this->propertyMap as $xmlName=>$dbName) { $calendar[$xmlName] = $row[$dbName]; } $this->addOwnerPrincipal($calendar); return $calendar; } /** * @param string $principal * @param string $uri * @return array|null */ public function getCalendarByUri($principal, $uri) { $fields = array_values($this->propertyMap); $fields[] = 'id'; $fields[] = 'uri'; $fields[] = 'synctoken'; $fields[] = 'components'; $fields[] = 'principaluri'; $fields[] = 'transparent'; // Making fields a comma-delimited list $query = $this->db->getQueryBuilder(); $query->select($fields)->from('calendars') ->where($query->expr()->eq('uri', $query->createNamedParameter($uri))) ->andWhere($query->expr()->eq('principaluri', $query->createNamedParameter($principal))) ->setMaxResults(1); $stmt = $query->execute(); $row = $stmt->fetch(\PDO::FETCH_ASSOC); $stmt->closeCursor(); if ($row === false) { return null; } $components = []; if ($row['components']) { $components = explode(',',$row['components']); } $calendar = [ 'id' => $row['id'], 'uri' => $row['uri'], 'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint), '{' . 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', '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components), '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'), ]; foreach($this->propertyMap as $xmlName=>$dbName) { $calendar[$xmlName] = $row[$dbName]; } $this->addOwnerPrincipal($calendar); return $calendar; } public function getCalendarById($calendarId) { $fields = array_values($this->propertyMap); $fields[] = 'id'; $fields[] = 'uri'; $fields[] = 'synctoken'; $fields[] = 'components'; $fields[] = 'principaluri'; $fields[] = 'transparent'; // Making fields a comma-delimited list $query = $this->db->getQueryBuilder(); $query->select($fields)->from('calendars') ->where($query->expr()->eq('id', $query->createNamedParameter($calendarId))) ->setMaxResults(1); $stmt = $query->execute(); $row = $stmt->fetch(\PDO::FETCH_ASSOC); $stmt->closeCursor(); if ($row === false) { return null; } $components = []; if ($row['components']) { $components = explode(',',$row['components']); } $calendar = [ 'id' => $row['id'], 'uri' => $row['uri'], 'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint), '{' . 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', '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components), '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'), ]; foreach($this->propertyMap as $xmlName=>$dbName) { $calendar[$xmlName] = $row[$dbName]; } $this->addOwnerPrincipal($calendar); return $calendar; } /** * Creates a new calendar for a principal. * * If the creation was a success, an id must be returned that can be used to reference * this calendar in other methods, such as updateCalendar. * * @param string $principalUri * @param string $calendarUri * @param array $properties * @return int */ function createCalendar($principalUri, $calendarUri, array $properties) { $values = [ 'principaluri' => $this->convertPrincipal($principalUri, true), 'uri' => $calendarUri, 'synctoken' => 1, 'transparent' => 0, 'components' => 'VEVENT,VTODO', 'displayname' => $calendarUri ]; // Default value $sccs = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set'; if (isset($properties[$sccs])) { if (!($properties[$sccs] instanceof SupportedCalendarComponentSet)) { throw new DAV\Exception('The ' . $sccs . ' property must be of type: \Sabre\CalDAV\Property\SupportedCalendarComponentSet'); } $values['components'] = implode(',',$properties[$sccs]->getValue()); } $transp = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp'; if (isset($properties[$transp])) { $values['transparent'] = (int) ($properties[$transp]->getValue() === 'transparent'); } foreach($this->propertyMap as $xmlName=>$dbName) { if (isset($properties[$xmlName])) { $values[$dbName] = $properties[$xmlName]; } } $query = $this->db->getQueryBuilder(); $query->insert('calendars'); foreach($values as $column => $value) { $query->setValue($column, $query->createNamedParameter($value)); } $query->execute(); $calendarId = $query->getLastInsertId(); $this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createCalendar', new GenericEvent( '\OCA\DAV\CalDAV\CalDavBackend::createCalendar', [ 'calendarId' => $calendarId, 'calendarData' => $this->getCalendarById($calendarId), ])); return $calendarId; } /** * Updates properties for a calendar. * * The list of mutations is stored in a Sabre\DAV\PropPatch object. * To do the actual updates, you must tell this object which properties * you're going to process with the handle() method. * * Calling the handle method is like telling the PropPatch object "I * promise I can handle updating this property". * * Read the PropPatch documentation for more info and examples. * * @param PropPatch $propPatch * @return void */ function updateCalendar($calendarId, PropPatch $propPatch) { $supportedProperties = array_keys($this->propertyMap); $supportedProperties[] = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp'; $propPatch->handle($supportedProperties, function($mutations) use ($calendarId) { $newValues = []; foreach ($mutations as $propertyName => $propertyValue) { switch ($propertyName) { case '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' : $fieldName = 'transparent'; $newValues[$fieldName] = (int) ($propertyValue->getValue() === 'transparent'); break; default : $fieldName = $this->propertyMap[$propertyName]; $newValues[$fieldName] = $propertyValue; break; } } $query = $this->db->getQueryBuilder(); $query->update('calendars'); foreach ($newValues as $fieldName => $value) { $query->set($fieldName, $query->createNamedParameter($value)); } $query->where($query->expr()->eq('id', $query->createNamedParameter($calendarId))); $query->execute(); $this->addChange($calendarId, "", 2); $this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateCalendar', new GenericEvent( '\OCA\DAV\CalDAV\CalDavBackend::updateCalendar', [ 'calendarId' => $calendarId, 'calendarData' => $this->getCalendarById($calendarId), 'shares' => $this->getShares($calendarId), 'propertyMutations' => $mutations, ])); return true; }); } /** * Delete a calendar and all it's objects * * @param mixed $calendarId * @return void */ function deleteCalendar($calendarId) { $this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteCalendar', new GenericEvent( '\OCA\DAV\CalDAV\CalDavBackend::deleteCalendar', [ 'calendarId' => $calendarId, 'calendarData' => $this->getCalendarById($calendarId), 'shares' => $this->getShares($calendarId), ])); $stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ?'); $stmt->execute([$calendarId]); $stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendars` WHERE `id` = ?'); $stmt->execute([$calendarId]); $stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarchanges` WHERE `calendarid` = ?'); $stmt->execute([$calendarId]); $this->sharingBackend->deleteAllShares($calendarId); $query = $this->db->getQueryBuilder(); $query->delete($this->dbObjectPropertiesTable) ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId))) ->execute(); } /** * Delete all of an user's shares * * @param string $principaluri * @return void */ function deleteAllSharesByUser($principaluri) { $this->sharingBackend->deleteAllSharesByUser($principaluri); } /** * Returns all calendar objects within a calendar. * * Every item contains an array with the following keys: * * calendardata - The iCalendar-compatible calendar data * * uri - a unique key which will be used to construct the uri. This can * be any arbitrary string, but making sure it ends with '.ics' is a * good idea. This is only the basename, or filename, not the full * path. * * lastmodified - a timestamp of the last modification time * * etag - An arbitrary string, surrounded by double-quotes. (e.g.: * '"abcdef"') * * size - The size of the calendar objects, in bytes. * * component - optional, a string containing the type of object, such * as 'vevent' or 'vtodo'. If specified, this will be used to populate * the Content-Type header. * * Note that the etag is optional, but it's highly encouraged to return for * speed reasons. * * The calendardata is also optional. If it's not returned * 'getCalendarObject' will be called later, which *is* expected to return * calendardata. * * If neither etag or size are specified, the calendardata will be * used/fetched to determine these numbers. If both are specified the * amount of times this is needed is reduced by a great degree. * * @param mixed $calendarId * @return array */ function getCalendarObjects($calendarId) { $query = $this->db->getQueryBuilder(); $query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'componenttype', 'classification']) ->from('calendarobjects') ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId))); $stmt = $query->execute(); $result = []; foreach($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) { $result[] = [ 'id' => $row['id'], 'uri' => $row['uri'], 'lastmodified' => $row['lastmodified'], 'etag' => '"' . $row['etag'] . '"', 'calendarid' => $row['calendarid'], 'size' => (int)$row['size'], 'component' => strtolower($row['componenttype']), 'classification'=> (int)$row['classification'] ]; } return $result; } /** * Returns information from a single calendar object, based on it's object * uri. * * The object uri is only the basename, or filename and not a full path. * * The returned array must have the same keys as getCalendarObjects. The * 'calendardata' object is required here though, while it's not required * for getCalendarObjects. * * This method must return null if the object did not exist. * * @param mixed $calendarId * @param string $objectUri * @return array|null */ function getCalendarObject($calendarId, $objectUri) { $query = $this->db->getQueryBuilder(); $query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype', 'classification']) ->from('calendarobjects') ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId))) ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri))); $stmt = $query->execute(); $row = $stmt->fetch(\PDO::FETCH_ASSOC); if(!$row) return null; return [ 'id' => $row['id'], 'uri' => $row['uri'], 'lastmodified' => $row['lastmodified'], 'etag' => '"' . $row['etag'] . '"', 'calendarid' => $row['calendarid'], 'size' => (int)$row['size'], 'calendardata' => $this->readBlob($row['calendardata']), 'component' => strtolower($row['componenttype']), 'classification'=> (int)$row['classification'] ]; } /** * Returns a list of calendar objects. * * This method should work identical to getCalendarObject, but instead * return all the calendar objects in the list as an array. * * If the backend supports this, it may allow for some speed-ups. * * @param mixed $calendarId * @param string[] $uris * @return array */ function getMultipleCalendarObjects($calendarId, array $uris) { if (empty($uris)) { return []; } $chunks = array_chunk($uris, 100); $objects = []; $query = $this->db->getQueryBuilder(); $query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype', 'classification']) ->from('calendarobjects') ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId))) ->andWhere($query->expr()->in('uri', $query->createParameter('uri'))); foreach ($chunks as $uris) { $query->setParameter('uri', $uris, IQueryBuilder::PARAM_STR_ARRAY); $result = $query->execute(); while ($row = $result->fetch()) { $objects[] = [ 'id' => $row['id'], 'uri' => $row['uri'], 'lastmodified' => $row['lastmodified'], 'etag' => '"' . $row['etag'] . '"', 'calendarid' => $row['calendarid'], 'size' => (int)$row['size'], 'calendardata' => $this->readBlob($row['calendardata']), 'component' => strtolower($row['componenttype']), 'classification' => (int)$row['classification'] ]; } $result->closeCursor(); } return $objects; } /** * Creates a new calendar object. * * The object uri is only the basename, or filename and not a full path. * * It is possible return an etag from this function, which will be used in * the response to this PUT request. Note that the ETag must be surrounded * by double-quotes. * * However, you should only really return this ETag if you don't mangle the * calendar-data. If the result of a subsequent GET to this object is not * the exact same as this request body, you should omit the ETag. * * @param mixed $calendarId * @param string $objectUri * @param string $calendarData * @return string */ function createCalendarObject($calendarId, $objectUri, $calendarData) { $extraData = $this->getDenormalizedData($calendarData); $query = $this->db->getQueryBuilder(); $query->insert('calendarobjects') ->values([ 'calendarid' => $query->createNamedParameter($calendarId), 'uri' => $query->createNamedParameter($objectUri), 'calendardata' => $query->createNamedParameter($calendarData, IQueryBuilder::PARAM_LOB), 'lastmodified' => $query->createNamedParameter(time()), 'etag' => $query->createNamedParameter($extraData['etag']), 'size' => $query->createNamedParameter($extraData['size']), 'componenttype' => $query->createNamedParameter($extraData['componentType']), 'firstoccurence' => $query->createNamedParameter($extraData['firstOccurence']), 'lastoccurence' => $query->createNamedParameter($extraData['lastOccurence']), 'classification' => $query->createNamedParameter($extraData['classification']), 'uid' => $query->createNamedParameter($extraData['uid']), ]) ->execute(); $this->updateProperties($calendarId, $objectUri, $calendarData); $this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject', new GenericEvent( '\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject', [ 'calendarId' => $calendarId, 'calendarData' => $this->getCalendarById($calendarId), 'shares' => $this->getShares($calendarId), 'objectData' => $this->getCalendarObject($calendarId, $objectUri), ] )); $this->addChange($calendarId, $objectUri, 1); return '"' . $extraData['etag'] . '"'; } /** * Updates an existing calendarobject, based on it's uri. * * The object uri is only the basename, or filename and not a full path. * * It is possible return an etag from this function, which will be used in * the response to this PUT request. Note that the ETag must be surrounded * by double-quotes. * * However, you should only really return this ETag if you don't mangle the * calendar-data. If the result of a subsequent GET to this object is not * the exact same as this request body, you should omit the ETag. * * @param mixed $calendarId * @param string $objectUri * @param string $calendarData * @return string */ function updateCalendarObject($calendarId, $objectUri, $calendarData) { $extraData = $this->getDenormalizedData($calendarData); $query = $this->db->getQueryBuilder(); $query->update('calendarobjects') ->set('calendardata', $query->createNamedParameter($calendarData, IQueryBuilder::PARAM_LOB)) ->set('lastmodified', $query->createNamedParameter(time())) ->set('etag', $query->createNamedParameter($extraData['etag'])) ->set('size', $query->createNamedParameter($extraData['size'])) ->set('componenttype', $query->createNamedParameter($extraData['componentType'])) ->set('firstoccurence', $query->createNamedParameter($extraData['firstOccurence'])) ->set('lastoccurence', $query->createNamedParameter($extraData['lastOccurence'])) ->set('classification', $query->createNamedParameter($extraData['classification'])) ->set('uid', $query->createNamedParameter($extraData['uid'])) ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId))) ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri))) ->execute(); $this->updateProperties($calendarId, $objectUri, $calendarData); $data = $this->getCalendarObject($calendarId, $objectUri); if (is_array($data)) { $this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject', new GenericEvent( '\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject', [ 'calendarId' => $calendarId, 'calendarData' => $this->getCalendarById($calendarId), 'shares' => $this->getShares($calendarId), 'objectData' => $data, ] )); } $this->addChange($calendarId, $objectUri, 2); return '"' . $extraData['etag'] . '"'; } /** * @param int $calendarObjectId * @param int $classification */ public function setClassification($calendarObjectId, $classification) { if (!in_array($classification, [ self::CLASSIFICATION_PUBLIC, self::CLASSIFICATION_PRIVATE, self::CLASSIFICATION_CONFIDENTIAL ])) { throw new \InvalidArgumentException(); } $query = $this->db->getQueryBuilder(); $query->update('calendarobjects') ->set('classification', $query->createNamedParameter($classification)) ->where($query->expr()->eq('id', $query->createNamedParameter($calendarObjectId))) ->execute(); } /** * Deletes an existing calendar object. * * The object uri is only the basename, or filename and not a full path. * * @param mixed $calendarId * @param string $objectUri * @return void */ function deleteCalendarObject($calendarId, $objectUri) { $data = $this->getCalendarObject($calendarId, $objectUri); if (is_array($data)) { $this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject', new GenericEvent( '\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject', [ 'calendarId' => $calendarId, 'calendarData' => $this->getCalendarById($calendarId), 'shares' => $this->getShares($calendarId), 'objectData' => $data, ] )); } $stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ? AND `uri` = ?'); $stmt->execute([$calendarId, $objectUri]); $this->purgeProperties($calendarId, $data['id']); $this->addChange($calendarId, $objectUri, 3); } /** * Performs a calendar-query on the contents of this calendar. * * The calendar-query is defined in RFC4791 : CalDAV. Using the * calendar-query it is possible for a client to request a specific set of * object, based on contents of iCalendar properties, date-ranges and * iCalendar component types (VTODO, VEVENT). * * This method should just return a list of (relative) urls that match this * query. * * The list of filters are specified as an array. The exact array is * documented by Sabre\CalDAV\CalendarQueryParser. * * Note that it is extremely likely that getCalendarObject for every path * returned from this method will be called almost immediately after. You * may want to anticipate this to speed up these requests. * * This method provides a default implementation, which parses *all* the * iCalendar objects in the specified calendar. * * This default may well be good enough for personal use, and calendars * that aren't very large. But if you anticipate high usage, big calendars * or high loads, you are strongly advised to optimize certain paths. * * The best way to do so is override this method and to optimize * specifically for 'common filters'. * * Requests that are extremely common are: * * requests for just VEVENTS * * requests for just VTODO * * requests with a time-range-filter on either VEVENT or VTODO. * * ..and combinations of these requests. It may not be worth it to try to * handle every possible situation and just rely on the (relatively * easy to use) CalendarQueryValidator to handle the rest. * * Note that especially time-range-filters may be difficult to parse. A * time-range filter specified on a VEVENT must for instance also handle * recurrence rules correctly. * A good example of how to interprete all these filters can also simply * be found in Sabre\CalDAV\CalendarQueryFilter. This class is as correct * as possible, so it gives you a good idea on what type of stuff you need * to think of. * * @param mixed $calendarId * @param array $filters * @return array */ function calendarQuery($calendarId, array $filters) { $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 ($componentType == 'VEVENT' && 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; } } } $columns = ['uri']; if ($requirePostFilter) { $columns = ['uri', 'calendardata']; } $query = $this->db->getQueryBuilder(); $query->select($columns) ->from('calendarobjects') ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId))); if ($componentType) { $query->andWhere($query->expr()->eq('componenttype', $query->createNamedParameter($componentType))); } if ($timeRange && $timeRange['start']) { $query->andWhere($query->expr()->gt('lastoccurence', $query->createNamedParameter($timeRange['start']->getTimeStamp()))); } if ($timeRange && $timeRange['end']) { $query->andWhere($query->expr()->lt('firstoccurence', $query->createNamedParameter($timeRange['end']->getTimeStamp()))); } $stmt = $query->execute(); $result = []; while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { if ($requirePostFilter) { if (!$this->validateFilterForObject($row, $filters)) { continue; } } $result[] = $row['uri']; } return $result; } /** * custom Nextcloud search extension for CalDAV * * @param string $principalUri * @param array $filters * @param integer|null $limit * @param integer|null $offset * @return array */ public function calendarSearch($principalUri, array $filters, $limit=null, $offset=null) { $calendars = $this->getCalendarsForUser($principalUri); $ownCalendars = []; $sharedCalendars = []; $uriMapper = []; foreach($calendars as $calendar) { if ($calendar['{http://owncloud.org/ns}owner-principal'] === $principalUri) { $ownCalendars[] = $calendar['id']; } else { $sharedCalendars[] = $calendar['id']; } $uriMapper[$calendar['id']] = $calendar['uri']; } if (count($ownCalendars) === 0 && count($sharedCalendars) === 0) { return []; } $query = $this->db->getQueryBuilder(); // Calendar id expressions $calendarExpressions = []; foreach($ownCalendars as $id) { $calendarExpressions[] = $query->expr() ->eq('c.calendarid', $query->createNamedParameter($id)); } foreach($sharedCalendars as $id) { $calendarExpressions[] = $query->expr()->andX( $query->expr()->eq('c.calendarid', $query->createNamedParameter($id)), $query->expr()->eq('c.classification', $query->createNamedParameter(self::CLASSIFICATION_PUBLIC)) ); } if (count($calendarExpressions) === 1) { $calExpr = $calendarExpressions[0]; } else { $calExpr = call_user_func_array([$query->expr(), 'orX'], $calendarExpressions); } // Component expressions $compExpressions = []; foreach($filters['comps'] as $comp) { $compExpressions[] = $query->expr() ->eq('c.componenttype', $query->createNamedParameter($comp)); } if (count($compExpressions) === 1) { $compExpr = $compExpressions[0]; } else { $compExpr = call_user_func_array([$query->expr(), 'orX'], $compExpressions); } if (!isset($filters['props'])) { $filters['props'] = []; } if (!isset($filters['params'])) { $filters['params'] = []; } $propParamExpressions = []; foreach($filters['props'] as $prop) { $propParamExpressions[] = $query->expr()->andX( $query->expr()->eq('i.name', $query->createNamedParameter($prop)), $query->expr()->isNull('i.parameter') ); } foreach($filters['params'] as $param) { $propParamExpressions[] = $query->expr()->andX( $query->expr()->eq('i.name', $query->createNamedParameter($param['property'])), $query->expr()->eq('i.parameter', $query->createNamedParameter($param['parameter'])) ); } if (count($propParamExpressions) === 1) { $propParamExpr = $propParamExpressions[0]; } else { $propParamExpr = call_user_func_array([$query->expr(), 'orX'], $propParamExpressions); } $query->select(['c.calendarid', 'c.uri']) ->from($this->dbObjectPropertiesTable, 'i') ->join('i', 'calendarobjects', 'c', $query->expr()->eq('i.objectid', 'c.id')) ->where($calExpr) ->andWhere($compExpr) ->andWhere($propParamExpr) ->andWhere($query->expr()->iLike('i.value', $query->createNamedParameter('%'.$this->db->escapeLikeParameter($filters['search-term']).'%'))); if ($offset) { $query->setFirstResult($offset); } if ($limit) { $query->setMaxResults($limit); } $stmt = $query->execute(); $result = []; while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { $path = $uriMapper[$row['calendarid']] . '/' . $row['uri']; if (!in_array($path, $result)) { $result[] = $path; } } return $result; } /** * Searches through all of a users calendars and calendar objects to find * an object with a specific UID. * * This method should return the path to this object, relative to the * calendar home, so this path usually only contains two parts: * * calendarpath/objectpath.ics * * If the uid is not found, return null. * * This method should only consider * objects that the principal owns, so * any calendars owned by other principals that also appear in this * collection should be ignored. * * @param string $principalUri * @param string $uid * @return string|null */ function getCalendarObjectByUID($principalUri, $uid) { $query = $this->db->getQueryBuilder(); $query->selectAlias('c.uri', 'calendaruri')->selectAlias('co.uri', 'objecturi') ->from('calendarobjects', 'co') ->leftJoin('co', 'calendars', 'c', $query->expr()->eq('co.calendarid', 'c.id')) ->where($query->expr()->eq('c.principaluri', $query->createNamedParameter($principalUri))) ->andWhere($query->expr()->eq('co.uid', $query->createNamedParameter($uid))); $stmt = $query->execute(); if ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { return $row['calendaruri'] . '/' . $row['objecturi']; } return null; } /** * The getChanges method returns all the changes that have happened, since * the specified syncToken in the specified calendar. * * This function should return an array, such as the following: * * [ * 'syncToken' => 'The current synctoken', * 'added' => [ * 'new.txt', * ], * 'modified' => [ * 'modified.txt', * ], * 'deleted' => [ * 'foo.php.bak', * 'old.txt' * ] * ); * * The returned syncToken property should reflect the *current* syncToken * of the calendar, as reported in the {http://sabredav.org/ns}sync-token * property This is * needed here too, to ensure the operation is atomic. * * If the $syncToken argument is specified as null, this is an initial * sync, and all members should be reported. * * The modified property is an array of nodenames that have changed since * the last token. * * The deleted property is an array with nodenames, that have been deleted * from collection. * * The $syncLevel argument is basically the 'depth' of the report. If it's * 1, you only have to report changes that happened only directly in * immediate descendants. If it's 2, it should also include changes from * the nodes below the child collections. (grandchildren) * * The $limit argument allows a client to specify how many results should * be returned at most. If the limit is not specified, it should be treated * as infinite. * * If the limit (infinite or not) is higher than you're willing to return, * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception. * * If the syncToken is expired (due to data cleanup) or unknown, you must * return null. * * The limit is 'suggestive'. You are free to ignore it. * * @param string $calendarId * @param string $syncToken * @param int $syncLevel * @param int $limit * @return array */ function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit = null) { // Current synctoken $stmt = $this->db->prepare('SELECT `synctoken` FROM `*PREFIX*calendars` WHERE `id` = ?'); $stmt->execute([ $calendarId ]); $currentToken = $stmt->fetchColumn(0); if (is_null($currentToken)) { return null; } $result = [ 'syncToken' => $currentToken, 'added' => [], 'modified' => [], 'deleted' => [], ]; if ($syncToken) { $query = "SELECT `uri`, `operation` FROM `*PREFIX*calendarchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `calendarid` = ? ORDER BY `synctoken`"; if ($limit>0) { $query.= " LIMIT " . (int)$limit; } // Fetching all changes $stmt = $this->db->prepare($query); $stmt->execute([$syncToken, $currentToken, $calendarId]); $changes = []; // This loop ensures that any duplicates are overwritten, only the // last change on a node is relevant. while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { $changes[$row['uri']] = $row['operation']; } foreach($changes as $uri => $operation) { switch($operation) { case 1 : $result['added'][] = $uri; break; case 2 : $result['modified'][] = $uri; break; case 3 : $result['deleted'][] = $uri; break; } } } else { // No synctoken supplied, this is the initial sync. $query = "SELECT `uri` FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ?"; $stmt = $this->db->prepare($query); $stmt->execute([$calendarId]); $result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN); } return $result; } /** * Returns a list of subscriptions for a principal. * * Every subscription is an array with the following keys: * * id, a unique id that will be used by other functions to modify the * subscription. This can be the same as the uri or a database key. * * uri. This is just the 'base uri' or 'filename' of the subscription. * * principaluri. The owner of the subscription. Almost always the same as * principalUri passed to this method. * * Furthermore, all the subscription info must be returned too: * * 1. {DAV:}displayname * 2. {http://apple.com/ns/ical/}refreshrate * 3. {http://calendarserver.org/ns/}subscribed-strip-todos (omit if todos * should not be stripped). * 4. {http://calendarserver.org/ns/}subscribed-strip-alarms (omit if alarms * should not be stripped). * 5. {http://calendarserver.org/ns/}subscribed-strip-attachments (omit if * attachments should not be stripped). * 6. {http://calendarserver.org/ns/}source (Must be a * Sabre\DAV\Property\Href). * 7. {http://apple.com/ns/ical/}calendar-color * 8. {http://apple.com/ns/ical/}calendar-order * 9. {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set * (should just be an instance of * Sabre\CalDAV\Property\SupportedCalendarComponentSet, with a bunch of * default components). * * @param string $principalUri * @return array */ function getSubscriptionsForUser($principalUri) { $fields = array_values($this->subscriptionPropertyMap); $fields[] = 'id'; $fields[] = 'uri'; $fields[] = 'source'; $fields[] = 'principaluri'; $fields[] = 'lastmodified'; $query = $this->db->getQueryBuilder(); $query->select($fields) ->from('calendarsubscriptions') ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri))) ->orderBy('calendarorder', 'asc'); $stmt =$query->execute(); $subscriptions = []; while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { $subscription = [ 'id' => $row['id'], 'uri' => $row['uri'], 'principaluri' => $row['principaluri'], 'source' => $row['source'], 'lastmodified' => $row['lastmodified'], '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VTODO', 'VEVENT']), ]; foreach($this->subscriptionPropertyMap as $xmlName=>$dbName) { if (!is_null($row[$dbName])) { $subscription[$xmlName] = $row[$dbName]; } } $subscriptions[] = $subscription; } return $subscriptions; } /** * Creates a new subscription for a principal. * * If the creation was a success, an id must be returned that can be used to reference * this subscription in other methods, such as updateSubscription. * * @param string $principalUri * @param string $uri * @param array $properties * @return mixed */ function createSubscription($principalUri, $uri, array $properties) { if (!isset($properties['{http://calendarserver.org/ns/}source'])) { throw new Forbidden('The {http://calendarserver.org/ns/}source property is required when creating subscriptions'); } $values = [ 'principaluri' => $principalUri, 'uri' => $uri, 'source' => $properties['{http://calendarserver.org/ns/}source']->getHref(), 'lastmodified' => time(), ]; $propertiesBoolean = ['striptodos', 'stripalarms', 'stripattachments']; foreach($this->subscriptionPropertyMap as $xmlName=>$dbName) { if (array_key_exists($xmlName, $properties)) { $values[$dbName] = $properties[$xmlName]; if (in_array($dbName, $propertiesBoolean)) { $values[$dbName] = true; } } } $valuesToInsert = array(); $query = $this->db->getQueryBuilder(); foreach (array_keys($values) as $name) { $valuesToInsert[$name] = $query->createNamedParameter($values[$name]); } $query->insert('calendarsubscriptions') ->values($valuesToInsert) ->execute(); return $this->db->lastInsertId('*PREFIX*calendarsubscriptions'); } /** * Updates a subscription * * The list of mutations is stored in a Sabre\DAV\PropPatch object. * To do the actual updates, you must tell this object which properties * you're going to process with the handle() method. * * Calling the handle method is like telling the PropPatch object "I * promise I can handle updating this property". * * Read the PropPatch documentation for more info and examples. * * @param mixed $subscriptionId * @param PropPatch $propPatch * @return void */ function updateSubscription($subscriptionId, PropPatch $propPatch) { $supportedProperties = array_keys($this->subscriptionPropertyMap); $supportedProperties[] = '{http://calendarserver.org/ns/}source'; $propPatch->handle($supportedProperties, function($mutations) use ($subscriptionId) { $newValues = []; foreach($mutations as $propertyName=>$propertyValue) { if ($propertyName === '{http://calendarserver.org/ns/}source') { $newValues['source'] = $propertyValue->getHref(); } else { $fieldName = $this->subscriptionPropertyMap[$propertyName]; $newValues[$fieldName] = $propertyValue; } } $query = $this->db->getQueryBuilder(); $query->update('calendarsubscriptions') ->set('lastmodified', $query->createNamedParameter(time())); foreach($newValues as $fieldName=>$value) { $query->set($fieldName, $query->createNamedParameter($value)); } $query->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId))) ->execute(); return true; }); } /** * Deletes a subscription. * * @param mixed $subscriptionId * @return void */ function deleteSubscription($subscriptionId) { $query = $this->db->getQueryBuilder(); $query->delete('calendarsubscriptions') ->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId))) ->execute(); } /** * Returns a single scheduling object for the inbox collection. * * The returned array should contain the following elements: * * uri - A unique basename for the object. This will be used to * construct a full uri. * * calendardata - The iCalendar object * * lastmodified - The last modification date. Can be an int for a unix * timestamp, or a PHP DateTime object. * * etag - A unique token that must change if the object changed. * * size - The size of the object, in bytes. * * @param string $principalUri * @param string $objectUri * @return array */ function getSchedulingObject($principalUri, $objectUri) { $query = $this->db->getQueryBuilder(); $stmt = $query->select(['uri', 'calendardata', 'lastmodified', 'etag', 'size']) ->from('schedulingobjects') ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri))) ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri))) ->execute(); $row = $stmt->fetch(\PDO::FETCH_ASSOC); if(!$row) { return null; } return [ 'uri' => $row['uri'], 'calendardata' => $row['calendardata'], 'lastmodified' => $row['lastmodified'], 'etag' => '"' . $row['etag'] . '"', 'size' => (int)$row['size'], ]; } /** * Returns all scheduling objects for the inbox collection. * * These objects should be returned as an array. Every item in the array * should follow the same structure as returned from getSchedulingObject. * * The main difference is that 'calendardata' is optional. * * @param string $principalUri * @return array */ function getSchedulingObjects($principalUri) { $query = $this->db->getQueryBuilder(); $stmt = $query->select(['uri', 'calendardata', 'lastmodified', 'etag', 'size']) ->from('schedulingobjects') ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri))) ->execute(); $result = []; foreach($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) { $result[] = [ 'calendardata' => $row['calendardata'], 'uri' => $row['uri'], 'lastmodified' => $row['lastmodified'], 'etag' => '"' . $row['etag'] . '"', 'size' => (int)$row['size'], ]; } return $result; } /** * Deletes a scheduling object from the inbox collection. * * @param string $principalUri * @param string $objectUri * @return void */ function deleteSchedulingObject($principalUri, $objectUri) { $query = $this->db->getQueryBuilder(); $query->delete('schedulingobjects') ->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri))) ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri))) ->execute(); } /** * Creates a new scheduling object. This should land in a users' inbox. * * @param string $principalUri * @param string $objectUri * @param string $objectData * @return void */ function createSchedulingObject($principalUri, $objectUri, $objectData) { $query = $this->db->getQueryBuilder(); $query->insert('schedulingobjects') ->values([ 'principaluri' => $query->createNamedParameter($principalUri), 'calendardata' => $query->createNamedParameter($objectData), 'uri' => $query->createNamedParameter($objectUri), 'lastmodified' => $query->createNamedParameter(time()), 'etag' => $query->createNamedParameter(md5($objectData)), 'size' => $query->createNamedParameter(strlen($objectData)) ]) ->execute(); } /** * Adds a change record to the calendarchanges table. * * @param mixed $calendarId * @param string $objectUri * @param int $operation 1 = add, 2 = modify, 3 = delete. * @return void */ protected function addChange($calendarId, $objectUri, $operation) { $stmt = $this->db->prepare('INSERT INTO `*PREFIX*calendarchanges` (`uri`, `synctoken`, `calendarid`, `operation`) SELECT ?, `synctoken`, ?, ? FROM `*PREFIX*calendars` WHERE `id` = ?'); $stmt->execute([ $objectUri, $calendarId, $operation, $calendarId ]); $stmt = $this->db->prepare('UPDATE `*PREFIX*calendars` SET `synctoken` = `synctoken` + 1 WHERE `id` = ?'); $stmt->execute([ $calendarId ]); } /** * Parses some information from calendar objects, used for optimized * calendar-queries. * * Returns an array with the following keys: * * etag - An md5 checksum of the object without the quotes. * * size - Size of the object in bytes * * componentType - VEVENT, VTODO or VJOURNAL * * firstOccurence * * lastOccurence * * uid - value of the UID property * * @param string $calendarData * @return array */ public function getDenormalizedData($calendarData) { $vObject = Reader::read($calendarData); $componentType = null; $component = null; $firstOccurrence = null; $lastOccurrence = null; $uid = null; $classification = self::CLASSIFICATION_PUBLIC; foreach($vObject->getComponents() as $component) { if ($component->name!=='VTIMEZONE') { $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 ($componentType === 'VEVENT' && $component->DTSTART) { $firstOccurrence = $component->DTSTART->getDateTime()->getTimeStamp(); // Finding the last occurrence is a bit harder if (!isset($component->RRULE)) { if (isset($component->DTEND)) { $lastOccurrence = $component->DTEND->getDateTime()->getTimeStamp(); } elseif (isset($component->DURATION)) { $endDate = clone $component->DTSTART->getDateTime(); $endDate->add(DateTimeParser::parse($component->DURATION->getValue())); $lastOccurrence = $endDate->getTimeStamp(); } elseif (!$component->DTSTART->hasTime()) { $endDate = clone $component->DTSTART->getDateTime(); $endDate->modify('+1 day'); $lastOccurrence = $endDate->getTimeStamp(); } else { $lastOccurrence = $firstOccurrence; } } else { $it = new EventIterator($vObject, (string)$component->UID); $maxDate = new \DateTime(self::MAX_DATE); if ($it->isInfinite()) { $lastOccurrence = $maxDate->getTimestamp(); } else { $end = $it->getDtEnd(); while($it->valid() && $end < $maxDate) { $end = $it->getDtEnd(); $it->next(); } $lastOccurrence = $end->getTimestamp(); } } } if ($component->CLASS) { $classification = CalDavBackend::CLASSIFICATION_PRIVATE; switch ($component->CLASS->getValue()) { case 'PUBLIC': $classification = CalDavBackend::CLASSIFICATION_PUBLIC; break; case 'CONFIDENTIAL': $classification = CalDavBackend::CLASSIFICATION_CONFIDENTIAL; break; } } return [ 'etag' => md5($calendarData), 'size' => strlen($calendarData), 'componentType' => $componentType, 'firstOccurence' => is_null($firstOccurrence) ? null : max(0, $firstOccurrence), 'lastOccurence' => $lastOccurrence, 'uid' => $uid, 'classification' => $classification ]; } private function readBlob($cardData) { if (is_resource($cardData)) { return stream_get_contents($cardData); } return $cardData; } /** * @param IShareable $shareable * @param array $add * @param array $remove */ public function updateShares($shareable, $add, $remove) { $calendarId = $shareable->getResourceId(); $this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateShares', new GenericEvent( '\OCA\DAV\CalDAV\CalDavBackend::updateShares', [ 'calendarId' => $calendarId, 'calendarData' => $this->getCalendarById($calendarId), 'shares' => $this->getShares($calendarId), 'add' => $add, 'remove' => $remove, ])); $this->sharingBackend->updateShares($shareable, $add, $remove); } /** * @param int $resourceId * @return array */ public function getShares($resourceId) { return $this->sharingBackend->getShares($resourceId); } /** * @param boolean $value * @param \OCA\DAV\CalDAV\Calendar $calendar * @return string|null */ public function setPublishStatus($value, $calendar) { $query = $this->db->getQueryBuilder(); if ($value) { $publicUri = $this->random->generate(16, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_DIGITS); $query->insert('dav_shares') ->values([ 'principaluri' => $query->createNamedParameter($calendar->getPrincipalURI()), 'type' => $query->createNamedParameter('calendar'), 'access' => $query->createNamedParameter(self::ACCESS_PUBLIC), 'resourceid' => $query->createNamedParameter($calendar->getResourceId()), 'publicuri' => $query->createNamedParameter($publicUri) ]); $query->execute(); return $publicUri; } $query->delete('dav_shares') ->where($query->expr()->eq('resourceid', $query->createNamedParameter($calendar->getResourceId()))) ->andWhere($query->expr()->eq('access', $query->createNamedParameter(self::ACCESS_PUBLIC))); $query->execute(); return null; } /** * @param \OCA\DAV\CalDAV\Calendar $calendar * @return mixed */ public function getPublishStatus($calendar) { $query = $this->db->getQueryBuilder(); $result = $query->select('publicuri') ->from('dav_shares') ->where($query->expr()->eq('resourceid', $query->createNamedParameter($calendar->getResourceId()))) ->andWhere($query->expr()->eq('access', $query->createNamedParameter(self::ACCESS_PUBLIC))) ->execute(); $row = $result->fetch(); $result->closeCursor(); return $row ? reset($row) : false; } /** * @param int $resourceId * @param array $acl * @return array */ public function applyShareAcl($resourceId, $acl) { return $this->sharingBackend->applyShareAcl($resourceId, $acl); } /** * update properties table * * @param int $calendarId * @param string $objectUri * @param string $calendarData */ public function updateProperties($calendarId, $objectUri, $calendarData) { $objectId = $this->getCalendarObjectId($calendarId, $objectUri); try { $vCalendar = $this->readCalendarData($calendarData); } catch (\Exception $ex) { return; } $this->purgeProperties($calendarId, $objectId); $query = $this->db->getQueryBuilder(); $query->insert($this->dbObjectPropertiesTable) ->values( [ 'calendarid' => $query->createNamedParameter($calendarId), 'objectid' => $query->createNamedParameter($objectId), 'name' => $query->createParameter('name'), 'parameter' => $query->createParameter('parameter'), 'value' => $query->createParameter('value'), ] ); $indexComponents = ['VEVENT', 'VJOURNAL', 'VTODO']; foreach ($vCalendar->getComponents() as $component) { if (!in_array($component->name, $indexComponents)) { continue; } foreach ($component->children() as $property) { if (in_array($property->name, self::$indexProperties)) { $value = $property->getValue(); // is this a shitty db? if (!$this->db->supports4ByteText()) { $value = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $value); } $value = substr($value, 0, 254); $query->setParameter('name', $property->name); $query->setParameter('parameter', null); $query->setParameter('value', $value); $query->execute(); } if (in_array($property->name, array_keys(self::$indexParameters))) { $parameters = $property->parameters(); $indexedParametersForProperty = self::$indexParameters[$property->name]; foreach ($parameters as $key => $value) { if (in_array($key, $indexedParametersForProperty)) { // is this a shitty db? if ($this->db->supports4ByteText()) { $value = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $value); } $value = substr($value, 0, 254); $query->setParameter('name', $property->name); $query->setParameter('parameter', substr($key, 0, 254)); $query->setParameter('value', substr($value, 0, 254)); $query->execute(); } } } } } } /** * read VCalendar data into a VCalendar object * * @param string $objectData * @return VCalendar */ protected function readCalendarData($objectData) { return Reader::read($objectData); } /** * delete all properties from a given calendar object * * @param int $calendarId * @param int $objectId */ protected function purgeProperties($calendarId, $objectId) { $query = $this->db->getQueryBuilder(); $query->delete($this->dbObjectPropertiesTable) ->where($query->expr()->eq('objectid', $query->createNamedParameter($objectId))) ->andWhere($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId))); $query->execute(); } /** * get ID from a given calendar object * * @param int $calendarId * @param string $uri * @return int */ protected function getCalendarObjectId($calendarId, $uri) { $query = $this->db->getQueryBuilder(); $query->select('id')->from('calendarobjects') ->where($query->expr()->eq('uri', $query->createNamedParameter($uri))) ->andWhere($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId))); $result = $query->execute(); $objectIds = $result->fetch(); $result->closeCursor(); if (!isset($objectIds['id'])) { throw new \InvalidArgumentException('Calendarobject does not exists: ' . $uri); } return (int)$objectIds['id']; } private function convertPrincipal($principalUri, $toV2) { if ($this->principalBackend->getPrincipalPrefix() === 'principals') { list(, $name) = URLUtil::splitPath($principalUri); if ($toV2 === true) { return "principals/users/$name"; } return "principals/$name"; } return $principalUri; } private function addOwnerPrincipal(&$calendarInfo) { $ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal'; $displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname'; if (isset($calendarInfo[$ownerPrincipalKey])) { $uri = $calendarInfo[$ownerPrincipalKey]; } else { $uri = $calendarInfo['principaluri']; } $principalInformation = $this->principalBackend->getPrincipalByPath($uri); if (isset($principalInformation['{DAV:}displayname'])) { $calendarInfo[$displaynameKey] = $principalInformation['{DAV:}displayname']; } } } Publishing/Xml/Publisher.php 0000604 00000004176 15247161531 0012067 0 ustar 00 <?php /** * @author Thomas Citharel <tcit@tcit.fr> * * @copyright Copyright (c) 2016 Thomas Citharel <tcit@tcit.fr> * @license GNU AGPL version 3 or any later version * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CalDAV\Publishing\Xml; use Sabre\Xml\Writer; use Sabre\Xml\XmlSerializable; class Publisher implements XmlSerializable { /** * @var string $publishUrl */ protected $publishUrl; /** * @var boolean $isPublished */ protected $isPublished; /** * @param string $publishUrl * @param boolean $isPublished */ function __construct($publishUrl, $isPublished) { $this->publishUrl = $publishUrl; $this->isPublished = $isPublished; } /** * @return string */ function getValue() { return $this->publishUrl; } /** * The xmlSerialize metod is called during xml writing. * * Use the $writer argument to write its own xml serialization. * * An important note: do _not_ create a parent element. Any element * implementing XmlSerializble should only ever write what's considered * its 'inner xml'. * * The parent of the current element is responsible for writing a * containing element. * * This allows serializers to be re-used for different element names. * * If you are opening new elements, you must also close them again. * * @param Writer $writer * @return void */ function xmlSerialize(Writer $writer) { if (!$this->isPublished) { // for pre-publish-url $writer->write($this->publishUrl); } else { // for publish-url $writer->writeElement('{DAV:}href', $this->publishUrl); } } } Publishing/PublishPlugin.php 0000604 00000014211 15247161531 0012146 0 ustar 00 <?php /** * @author Thomas Citharel <tcit@tcit.fr> * * @copyright Copyright (c) 2016 Thomas Citharel <tcit@tcit.fr> * @license GNU AGPL version 3 or any later version * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CalDAV\Publishing; use Sabre\DAV\PropFind; use Sabre\DAV\INode; use Sabre\DAV\Server; use Sabre\DAV\ServerPlugin; use Sabre\DAV\Exception\NotFound; use Sabre\HTTP\RequestInterface; use Sabre\HTTP\ResponseInterface; use Sabre\CalDAV\Xml\Property\AllowedSharingModes; use OCA\DAV\CalDAV\Publishing\Xml\Publisher; use OCA\DAV\CalDAV\Calendar; use OCP\IURLGenerator; use OCP\IConfig; class PublishPlugin extends ServerPlugin { const NS_CALENDARSERVER = 'http://calendarserver.org/ns/'; /** * Reference to SabreDAV server object. * * @var \Sabre\DAV\Server */ protected $server; /** * Config instance to get instance secret. * * @var IConfig */ protected $config; /** * URL Generator for absolute URLs. * * @var IURLGenerator */ protected $urlGenerator; /** * PublishPlugin constructor. * * @param IConfig $config * @param IURLGenerator $urlGenerator */ public function __construct(IConfig $config, IURLGenerator $urlGenerator) { $this->config = $config; $this->urlGenerator = $urlGenerator; } /** * This method should return a list of server-features. * * This is for example 'versioning' and is added to the DAV: header * in an OPTIONS response. * * @return string[] */ public function getFeatures() { // May have to be changed to be detected return ['oc-calendar-publishing', 'calendarserver-sharing']; } /** * Returns a plugin name. * * Using this name other plugins will be able to access other plugins * using Sabre\DAV\Server::getPlugin * * @return string */ public function getPluginName() { return 'oc-calendar-publishing'; } /** * This initializes the plugin. * * This function is called by Sabre\DAV\Server, after * addPlugin is called. * * This method should set up the required event subscriptions. * * @param Server $server */ public function initialize(Server $server) { $this->server = $server; $this->server->on('method:POST', [$this, 'httpPost']); $this->server->on('propFind', [$this, 'propFind']); } public function propFind(PropFind $propFind, INode $node) { if ($node instanceof Calendar) { $propFind->handle('{'.self::NS_CALENDARSERVER.'}publish-url', function () use ($node) { if ($node->getPublishStatus()) { // We return the publish-url only if the calendar is published. $token = $node->getPublishStatus(); $publishUrl = $this->urlGenerator->getAbsoluteURL($this->server->getBaseUri().'public-calendars/').$token; return new Publisher($publishUrl, true); } }); $propFind->handle('{'.self::NS_CALENDARSERVER.'}allowed-sharing-modes', function() use ($node) { return new AllowedSharingModes(!$node->isSubscription(), !$node->isSubscription()); }); } } /** * We intercept this to handle POST requests on calendars. * * @param RequestInterface $request * @param ResponseInterface $response * * @return void|bool */ public function httpPost(RequestInterface $request, ResponseInterface $response) { $path = $request->getPath(); // Only handling xml $contentType = $request->getHeader('Content-Type'); if (strpos($contentType, 'application/xml') === false && strpos($contentType, 'text/xml') === false) { return; } // Making sure the node exists try { $node = $this->server->tree->getNodeForPath($path); } catch (NotFound $e) { return; } $requestBody = $request->getBodyAsString(); // If this request handler could not deal with this POST request, it // will return 'null' and other plugins get a chance to handle the // request. // // However, we already requested the full body. This is a problem, // because a body can only be read once. This is why we preemptively // re-populated the request body with the existing data. $request->setBody($requestBody); $this->server->xml->parse($requestBody, $request->getUrl(), $documentType); switch ($documentType) { case '{'.self::NS_CALENDARSERVER.'}publish-calendar' : // We can only deal with IShareableCalendar objects if (!$node instanceof Calendar) { return; } $this->server->transactionType = 'post-publish-calendar'; // Getting ACL info $acl = $this->server->getPlugin('acl'); // If there's no ACL support, we allow everything if ($acl) { $acl->checkPrivileges($path, '{DAV:}write'); } $node->setPublishStatus(true); // iCloud sends back the 202, so we will too. $response->setStatus(202); // Adding this because sending a response body may cause issues, // and I wanted some type of indicator the response was handled. $response->setHeader('X-Sabre-Status', 'everything-went-well'); // Breaking the event chain return false; case '{'.self::NS_CALENDARSERVER.'}unpublish-calendar' : // We can only deal with IShareableCalendar objects if (!$node instanceof Calendar) { return; } $this->server->transactionType = 'post-unpublish-calendar'; // Getting ACL info $acl = $this->server->getPlugin('acl'); // If there's no ACL support, we allow everything if ($acl) { $acl->checkPrivileges($path, '{DAV:}write'); } $node->setPublishStatus(false); $response->setStatus(200); // Adding this because sending a response body may cause issues, // and I wanted some type of indicator the response was handled. $response->setHeader('X-Sabre-Status', 'everything-went-well'); // Breaking the event chain return false; } } } PublicCalendarObject.php 0000604 00000001666 15247161531 0011266 0 ustar 00 <?php /** * @copyright Copyright (c) 2017, Georg Ehrke * * @author Georg Ehrke <oc.list@georgehrke.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CalDAV; class PublicCalendarObject extends CalendarObject { /** * public calendars are always shared * @return bool */ protected function isShared() { return true; } } Search/Xml/Request/CalendarSearchReport.php 0000604 00000013255 15247161531 0014714 0 ustar 00 <?php /** * @author Georg Ehrke <oc.list@georgehrke.com> * * @copyright Copyright (c) 2017 Georg Ehrke <oc.list@georgehrke.com> * @license GNU AGPL version 3 or any later version * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CalDAV\Search\Xml\Request; use Sabre\DAV\Exception\BadRequest; use Sabre\Xml\Reader; use Sabre\Xml\XmlDeserializable; use OCA\DAV\CalDAV\Search\SearchPlugin; /** * CalendarSearchReport request parser. * * This class parses the {urn:ietf:params:xml:ns:caldav}calendar-query * REPORT, as defined in: * * https:// link to standard */ class CalendarSearchReport implements XmlDeserializable { /** * An array with requested properties. * * @var array */ public $properties; /** * List of property/component filters. * * @var array */ public $filters; /** * @var int */ public $limit; /** * @var int */ public $offset; /** * The deserialize method is called during xml parsing. * * This method is called statically, this is because in theory this method * may be used as a type of constructor, or factory method. * * Often you want to return an instance of the current class, but you are * free to return other data as well. * * You are responsible for advancing the reader to the next element. Not * doing anything will result in a never-ending loop. * * If you just want to skip parsing for this element altogether, you can * just call $reader->next(); * * $reader->parseInnerTree() will parse the entire sub-tree, and advance to * the next element. * * @param Reader $reader * @return mixed */ static function xmlDeserialize(Reader $reader) { $elems = $reader->parseInnerTree([ '{http://nextcloud.com/ns}comp-filter' => 'OCA\\DAV\\CalDAV\\Search\\Xml\\Filter\\CompFilter', '{http://nextcloud.com/ns}prop-filter' => 'OCA\\DAV\\CalDAV\\Search\\Xml\\Filter\\PropFilter', '{http://nextcloud.com/ns}param-filter' => 'OCA\\DAV\\CalDAV\\Search\\Xml\\Filter\\ParamFilter', '{http://nextcloud.com/ns}search-term' => 'OCA\\DAV\\CalDAV\\Search\\Xml\\Filter\\SearchTermFilter', '{http://nextcloud.com/ns}limit' => 'OCA\\DAV\\CalDAV\\Search\\Xml\\Filter\\LimitFilter', '{http://nextcloud.com/ns}offset' => 'OCA\\DAV\\CalDAV\\Search\\Xml\\Filter\\OffsetFilter', '{DAV:}prop' => 'Sabre\\Xml\\Element\\KeyValue', ]); $newProps = [ 'filters' => [], 'properties' => [], 'limit' => null, 'offset' => null ]; if (!is_array($elems)) { $elems = []; } foreach ($elems as $elem) { switch ($elem['name']) { case '{DAV:}prop': $newProps['properties'] = array_keys($elem['value']); break; case '{' . SearchPlugin::NS_Nextcloud . '}filter': foreach ($elem['value'] as $subElem) { if ($subElem['name'] === '{' . SearchPlugin::NS_Nextcloud . '}comp-filter') { if (!isset($newProps['filters']['comps']) || !is_array($newProps['filters']['comps'])) { $newProps['filters']['comps'] = []; } $newProps['filters']['comps'][] = $subElem['value']; } elseif ($subElem['name'] === '{' . SearchPlugin::NS_Nextcloud . '}prop-filter') { if (!isset($newProps['filters']['props']) || !is_array($newProps['filters']['props'])) { $newProps['filters']['props'] = []; } $newProps['filters']['props'][] = $subElem['value']; } elseif ($subElem['name'] === '{' . SearchPlugin::NS_Nextcloud . '}param-filter') { if (!isset($newProps['filters']['params']) || !is_array($newProps['filters']['params'])) { $newProps['filters']['params'] = []; } $newProps['filters']['params'][] = $subElem['value']; } elseif ($subElem['name'] === '{' . SearchPlugin::NS_Nextcloud . '}search-term') { $newProps['filters']['search-term'] = $subElem['value']; } } break; case '{' . SearchPlugin::NS_Nextcloud . '}limit': $newProps['limit'] = $elem['value']; break; case '{' . SearchPlugin::NS_Nextcloud . '}offset': $newProps['offset'] = $elem['value']; break; } } if (empty($newProps['filters'])) { throw new BadRequest('The {' . SearchPlugin::NS_Nextcloud . '}filter element is required for this request'); } $propsOrParamsDefined = (!empty($newProps['filters']['props']) || !empty($newProps['filters']['params'])); $noCompsDefined = empty($newProps['filters']['comps']); if ($propsOrParamsDefined && $noCompsDefined) { throw new BadRequest('{' . SearchPlugin::NS_Nextcloud . '}prop-filter or {' . SearchPlugin::NS_Nextcloud . '}param-filter given without any {' . SearchPlugin::NS_Nextcloud . '}comp-filter'); } if (!isset($newProps['filters']['search-term'])) { throw new BadRequest('{' . SearchPlugin::NS_Nextcloud . '}search-term is required for this request'); } if (empty($newProps['filters']['props']) && empty($newProps['filters']['params'])) { throw new BadRequest('At least one{' . SearchPlugin::NS_Nextcloud . '}prop-filter or {' . SearchPlugin::NS_Nextcloud . '}param-filter is required for this request'); } $obj = new self(); foreach ($newProps as $key => $value) { $obj->$key = $value; } return $obj; } } Search/Xml/Filter/OffsetFilter.php 0000604 00000002531 15247161531 0013045 0 ustar 00 <?php /** * @author Georg Ehrke <oc.list@georgehrke.com> * * @copyright Copyright (c) 2017 Georg Ehrke <oc.list@georgehrke.com> * @license GNU AGPL version 3 or any later version * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CalDAV\Search\Xml\Filter; use Sabre\DAV\Exception\BadRequest; use Sabre\Xml\Reader; use Sabre\Xml\XmlDeserializable; use OCA\DAV\CalDAV\Search\SearchPlugin; class OffsetFilter implements XmlDeserializable { /** * @param Reader $reader * @throws BadRequest * @return int */ static function xmlDeserialize(Reader $reader) { $value = $reader->parseInnerTree(); if (!is_int($value) && !is_string($value)) { throw new BadRequest('The {' . SearchPlugin::NS_Nextcloud . '}offset has illegal value'); } return intval($value); } } Search/Xml/Filter/PropFilter.php 0000604 00000002642 15247161531 0012542 0 ustar 00 <?php /** * @author Georg Ehrke <oc.list@georgehrke.com> * * @copyright Copyright (c) 2017 Georg Ehrke <oc.list@georgehrke.com> * @license GNU AGPL version 3 or any later version * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CalDAV\Search\Xml\Filter; use Sabre\DAV\Exception\BadRequest; use Sabre\Xml\Reader; use Sabre\Xml\XmlDeserializable; use OCA\DAV\CalDAV\Search\SearchPlugin; class PropFilter implements XmlDeserializable { /** * @param Reader $reader * @throws BadRequest * @return string */ static function xmlDeserialize(Reader $reader) { $att = $reader->parseAttributes(); $componentName = $att['name']; $reader->parseInnerTree(); if (!is_string($componentName)) { throw new BadRequest('The {' . SearchPlugin::NS_Nextcloud . '}prop-filter requires a valid name attribute'); } return $componentName; } } Search/Xml/Filter/SearchTermFilter.php 0000604 00000002512 15247161531 0013653 0 ustar 00 <?php /** * @author Georg Ehrke <oc.list@georgehrke.com> * * @copyright Copyright (c) 2017 Georg Ehrke <oc.list@georgehrke.com> * @license GNU AGPL version 3 or any later version * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CalDAV\Search\Xml\Filter; use Sabre\DAV\Exception\BadRequest; use Sabre\Xml\Reader; use Sabre\Xml\XmlDeserializable; use OCA\DAV\CalDAV\Search\SearchPlugin; class SearchTermFilter implements XmlDeserializable { /** * @param Reader $reader * @throws BadRequest * @return string */ static function xmlDeserialize(Reader $reader) { $value = $reader->parseInnerTree(); if (!is_string($value)) { throw new BadRequest('The {' . SearchPlugin::NS_Nextcloud . '}search-term has illegal value'); } return $value; } } Search/Xml/Filter/CompFilter.php 0000604 00000002642 15247161531 0012520 0 ustar 00 <?php /** * @author Georg Ehrke <oc.list@georgehrke.com> * * @copyright Copyright (c) 2017 Georg Ehrke <oc.list@georgehrke.com> * @license GNU AGPL version 3 or any later version * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CalDAV\Search\Xml\Filter; use Sabre\DAV\Exception\BadRequest; use Sabre\Xml\Reader; use Sabre\Xml\XmlDeserializable; use OCA\DAV\CalDAV\Search\SearchPlugin; class CompFilter implements XmlDeserializable { /** * @param Reader $reader * @throws BadRequest * @return string */ static function xmlDeserialize(Reader $reader) { $att = $reader->parseAttributes(); $componentName = $att['name']; $reader->parseInnerTree(); if (!is_string($componentName)) { throw new BadRequest('The {' . SearchPlugin::NS_Nextcloud . '}comp-filter requires a valid name attribute'); } return $componentName; } } Search/Xml/Filter/LimitFilter.php 0000604 00000002527 15247161531 0012702 0 ustar 00 <?php /** * @author Georg Ehrke <oc.list@georgehrke.com> * * @copyright Copyright (c) 2017 Georg Ehrke <oc.list@georgehrke.com> * @license GNU AGPL version 3 or any later version * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CalDAV\Search\Xml\Filter; use Sabre\DAV\Exception\BadRequest; use Sabre\Xml\Reader; use Sabre\Xml\XmlDeserializable; use OCA\DAV\CalDAV\Search\SearchPlugin; class LimitFilter implements XmlDeserializable { /** * @param Reader $reader * @throws BadRequest * @return int */ static function xmlDeserialize(Reader $reader) { $value = $reader->parseInnerTree(); if (!is_int($value) && !is_string($value)) { throw new BadRequest('The {' . SearchPlugin::NS_Nextcloud . '}limit has illegal value'); } return intval($value); } } Search/Xml/Filter/ParamFilter.php 0000604 00000003213 15247161531 0012655 0 ustar 00 <?php /** * @author Georg Ehrke <oc.list@georgehrke.com> * * @copyright Copyright (c) 2017 Georg Ehrke <oc.list@georgehrke.com> * @license GNU AGPL version 3 or any later version * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CalDAV\Search\Xml\Filter; use Sabre\DAV\Exception\BadRequest; use Sabre\Xml\Reader; use Sabre\Xml\XmlDeserializable; use OCA\DAV\CalDAV\Search\SearchPlugin; class ParamFilter implements XmlDeserializable { /** * @param Reader $reader * @throws BadRequest * @return string */ static function xmlDeserialize(Reader $reader) { $att = $reader->parseAttributes(); $property = $att['property']; $parameter = $att['name']; $reader->parseInnerTree(); if (!is_string($property)) { throw new BadRequest('The {' . SearchPlugin::NS_Nextcloud . '}param-filter requires a valid property attribute'); } if (!is_string($parameter)) { throw new BadRequest('The {' . SearchPlugin::NS_Nextcloud . '}param-filter requires a valid parameter attribute'); } return [ 'property' => $property, 'parameter' => $parameter, ]; } } Search/SearchPlugin.php 0000604 00000010514 15247161531 0011050 0 ustar 00 <?php /** * @author Georg Ehrke <oc.list@georgehrke.com> * * @copyright Copyright (c) 2017 Georg Ehrke <oc.list@georgehrke.com> * @license GNU AGPL version 3 or any later version * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CalDAV\Search; use Sabre\DAV\Server; use Sabre\DAV\ServerPlugin; use OCA\DAV\CalDAV\CalendarHome; class SearchPlugin extends ServerPlugin { const NS_Nextcloud = 'http://nextcloud.com/ns'; /** * Reference to SabreDAV server object. * * @var \Sabre\DAV\Server */ protected $server; /** * This method should return a list of server-features. * * This is for example 'versioning' and is added to the DAV: header * in an OPTIONS response. * * @return string[] */ public function getFeatures() { // May have to be changed to be detected return ['nc-calendar-search']; } /** * Returns a plugin name. * * Using this name other plugins will be able to access other plugins * using Sabre\DAV\Server::getPlugin * * @return string */ public function getPluginName() { return 'nc-calendar-search'; } /** * This initializes the plugin. * * This function is called by Sabre\DAV\Server, after * addPlugin is called. * * This method should set up the required event subscriptions. * * @param Server $server */ public function initialize(Server $server) { $this->server = $server; $server->on('report', [$this, 'report']); $server->xml->elementMap['{' . self::NS_Nextcloud . '}calendar-search'] = 'OCA\\DAV\\CalDAV\\Search\\Xml\\Request\\CalendarSearchReport'; } /** * This functions handles REPORT requests specific to CalDAV * * @param string $reportName * @param mixed $report * @param mixed $path * @return bool */ public function report($reportName, $report, $path) { switch ($reportName) { case '{' . self::NS_Nextcloud . '}calendar-search': $this->server->transactionType = 'report-nc-calendar-search'; $this->calendarSearch($report); return false; } } /** * Returns a list of reports this plugin supports. * * This will be used in the {DAV:}supported-report-set property. * Note that you still need to subscribe to the 'report' event to actually * implement them * * @param string $uri * @return array */ public function getSupportedReportSet($uri) { $node = $this->server->tree->getNodeForPath($uri); $reports = []; if ($node instanceof CalendarHome) { $reports[] = '{' . self::NS_Nextcloud . '}calendar-search'; } return $reports; } /** * This function handles the calendar-query REPORT * * This report is used by clients to request calendar objects based on * complex conditions. * * @param Xml\Request\CalendarSearchReport $report * @return void */ private function calendarSearch($report) { $node = $this->server->tree->getNodeForPath($this->server->getRequestUri()); $depth = $this->server->getHTTPDepth(2); // The default result is an empty array $result = []; // If we're dealing with the calendar home, the calendar home itself is // responsible for the calendar-query if ($node instanceof CalendarHome && $depth == 2) { $nodePaths = $node->calendarSearch($report->filters, $report->limit, $report->offset); foreach ($nodePaths as $path) { list($properties) = $this->server->getPropertiesForPath( $this->server->getRequestUri() . '/' . $path, $report->properties); $result[] = $properties; } } $prefer = $this->server->getHTTPPrefer(); $this->server->httpResponse->setStatus(207); $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); $this->server->httpResponse->setHeader('Vary', 'Brief,Prefer'); $this->server->httpResponse->setBody( $this->server->generateMultiStatus($result, $prefer['return'] === 'minimal')); } } Schedule/IMipPlugin.php 0000604 00000013520 15247161531 0011030 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * @copyright Copyright (c) 2017, Georg Ehrke * * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Georg Ehrke <oc.list@georgehrke.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CalDAV\Schedule; use OCP\AppFramework\Utility\ITimeFactory; use OCP\ILogger; use OCP\Mail\IMailer; use Sabre\VObject\Component\VCalendar; use Sabre\VObject\DateTimeParser; use Sabre\VObject\ITip; use Sabre\CalDAV\Schedule\IMipPlugin as SabreIMipPlugin; use Sabre\VObject\Recur\EventIterator; /** * iMIP handler. * * This class is responsible for sending out iMIP messages. iMIP is the * email-based transport for iTIP. iTIP deals with scheduling operations for * iCalendar objects. * * If you want to customize the email that gets sent out, you can do so by * extending this class and overriding the sendMessage method. * * @copyright Copyright (C) 2007-2015 fruux GmbH (https://fruux.com/). * @author Evert Pot (http://evertpot.com/) * @license http://sabre.io/license/ Modified BSD License */ class IMipPlugin extends SabreIMipPlugin { /** @var IMailer */ private $mailer; /** @var ILogger */ private $logger; /** @var ITimeFactory */ private $timeFactory; const MAX_DATE = '2038-01-01'; /** * Creates the email handler. * * @param IMailer $mailer * @param ILogger $logger * @param ITimeFactory $timeFactory */ function __construct(IMailer $mailer, ILogger $logger, ITimeFactory $timeFactory) { parent::__construct(''); $this->mailer = $mailer; $this->logger = $logger; $this->timeFactory = $timeFactory; } /** * Event handler for the 'schedule' event. * * @param ITip\Message $iTipMessage * @return void */ function schedule(ITip\Message $iTipMessage) { // Not sending any emails if the system considers the update // insignificant. if (!$iTipMessage->significantChange) { if (!$iTipMessage->scheduleStatus) { $iTipMessage->scheduleStatus = '1.0;We got the message, but it\'s not significant enough to warrant an email'; } return; } $summary = $iTipMessage->message->VEVENT->SUMMARY; if (parse_url($iTipMessage->sender, PHP_URL_SCHEME) !== 'mailto') { return; } if (parse_url($iTipMessage->recipient, PHP_URL_SCHEME) !== 'mailto') { return; } // don't send out mails for events that already took place if ($this->isEventInThePast($iTipMessage->message)) { return; } $sender = substr($iTipMessage->sender, 7); $recipient = substr($iTipMessage->recipient, 7); $senderName = ($iTipMessage->senderName) ? $iTipMessage->senderName : null; $recipientName = ($iTipMessage->recipientName) ? $iTipMessage->recipientName : null; $subject = 'SabreDAV iTIP message'; switch (strtoupper($iTipMessage->method)) { case 'REPLY' : $subject = 'Re: ' . $summary; break; case 'REQUEST' : $subject = $summary; break; case 'CANCEL' : $subject = 'Cancelled: ' . $summary; break; } $contentType = 'text/calendar; charset=UTF-8; method=' . $iTipMessage->method; $message = $this->mailer->createMessage(); $message->setReplyTo([$sender => $senderName]) ->setTo([$recipient => $recipientName]) ->setSubject($subject) ->setBody($iTipMessage->message->serialize(), $contentType); try { $failed = $this->mailer->send($message); if ($failed) { $this->logger->error('Unable to deliver message to {failed}', ['app' => 'dav', 'failed' => implode(', ', $failed)]); $iTipMessage->scheduleStatus = '5.0; EMail delivery failed'; } $iTipMessage->scheduleStatus = '1.1; Scheduling message is sent via iMip'; } catch(\Exception $ex) { $this->logger->logException($ex, ['app' => 'dav']); $iTipMessage->scheduleStatus = '5.0; EMail delivery failed'; } } /** * check if event took place in the past already * @param VCalendar $vObject * @return bool */ private function isEventInThePast(VCalendar $vObject) { $component = $vObject->VEVENT; $firstOccurrence = $component->DTSTART->getDateTime()->getTimeStamp(); // Finding the last occurrence is a bit harder if (!isset($component->RRULE)) { if (isset($component->DTEND)) { $lastOccurrence = $component->DTEND->getDateTime()->getTimeStamp(); } elseif (isset($component->DURATION)) { $endDate = clone $component->DTSTART->getDateTime(); // $component->DTEND->getDateTime() returns DateTimeImmutable $endDate = $endDate->add(DateTimeParser::parse($component->DURATION->getValue())); $lastOccurrence = $endDate->getTimeStamp(); } elseif (!$component->DTSTART->hasTime()) { $endDate = clone $component->DTSTART->getDateTime(); // $component->DTSTART->getDateTime() returns DateTimeImmutable $endDate = $endDate->modify('+1 day'); $lastOccurrence = $endDate->getTimeStamp(); } else { $lastOccurrence = $firstOccurrence; } } else { $it = new EventIterator($vObject, (string)$component->UID); $maxDate = new \DateTime(self::MAX_DATE); if ($it->isInfinite()) { $lastOccurrence = $maxDate->getTimestamp(); } else { $end = $it->getDtEnd(); while($it->valid() && $end < $maxDate) { $end = $it->getDtEnd(); $it->next(); } $lastOccurrence = $end->getTimestamp(); } } $currentTime = $this->timeFactory->getTime(); return $lastOccurrence < $currentTime; } } Schedule/Plugin.php 0000604 00000006053 15247161531 0010254 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, Roeland Jago Douma <roeland@famdouma.nl> * @copyright Copyright (c) 2016, Joas Schilling <coding@schilljs.com> * * @author Joas Schilling <coding@schilljs.com> * @author Roeland Jago Douma <roeland@famdouma.nl> * * @license GNU AGPL version 3 or any later version * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ namespace OCA\DAV\CalDAV\Schedule; use OCA\DAV\CalDAV\CalDavBackend; use OCA\DAV\CalDAV\CalendarHome; use Sabre\DAV\INode; use Sabre\DAV\PropFind; use Sabre\DAV\Server; use Sabre\DAV\Xml\Property\LocalHref; use Sabre\DAVACL\IPrincipal; class Plugin extends \Sabre\CalDAV\Schedule\Plugin { /** * Initializes the plugin * * @param Server $server * @return void */ function initialize(Server $server) { parent::initialize($server); $server->on('propFind', [$this, 'propFindDefaultCalendarUrl'], 90); } /** * Returns a list of addresses that are associated with a principal. * * @param string $principal * @return array */ protected function getAddressesForPrincipal($principal) { $result = parent::getAddressesForPrincipal($principal); if ($result === null) { $result = []; } return $result; } /** * Always use the personal calendar as target for scheduled events * * @param PropFind $propFind * @param INode $node * @return void */ function propFindDefaultCalendarUrl(PropFind $propFind, INode $node) { if ($node instanceof IPrincipal) { $propFind->handle('{' . self::NS_CALDAV . '}schedule-default-calendar-URL', function() use ($node) { /** @var \OCA\DAV\CalDAV\Plugin $caldavPlugin */ $caldavPlugin = $this->server->getPlugin('caldav'); $principalUrl = $node->getPrincipalUrl(); $calendarHomePath = $caldavPlugin->getCalendarHomeForPrincipal($principalUrl); if (!$calendarHomePath) { return null; } /** @var CalendarHome $calendarHome */ $calendarHome = $this->server->tree->getNodeForPath($calendarHomePath); if (!$calendarHome->childExists(CalDavBackend::PERSONAL_CALENDAR_URI)) { $calendarHome->getCalDAVBackend()->createCalendar($principalUrl, CalDavBackend::PERSONAL_CALENDAR_URI, [ '{DAV:}displayname' => CalDavBackend::PERSONAL_CALENDAR_NAME, ]); } $result = $this->server->getPropertiesForPath($calendarHomePath . '/' . CalDavBackend::PERSONAL_CALENDAR_URI, [], 1); if (empty($result)) { return null; } return new LocalHref($result[0]['href']); }); } } } PublicCalendarRoot.php 0000604 00000002645 15247161531 0011001 0 ustar 00 <?php /** * @author Thomas Müller <thomas.mueller@tmit.eu> * * @copyright Copyright (c) 2016, ownCloud, Inc. * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CalDAV; use Sabre\DAV\Collection; class PublicCalendarRoot extends Collection { /** @var CalDavBackend */ protected $caldavBackend; /** @var \OCP\IL10N */ protected $l10n; function __construct(CalDavBackend $caldavBackend) { $this->caldavBackend = $caldavBackend; $this->l10n = \OC::$server->getL10N('dav'); } /** * @inheritdoc */ function getName() { return 'public-calendars'; } /** * @inheritdoc */ function getChild($name) { $calendar = $this->caldavBackend->getPublicCalendar($name); return new PublicCalendar($this->caldavBackend, $calendar, $this->l10n); } /** * @inheritdoc */ function getChildren() { return []; } } Plugin.php 0000604 00000002202 15247161531 0006510 0 ustar 00 <?php /** * @author Thomas Müller <thomas.mueller@tmit.eu> * * @copyright Copyright (c) 2016, ownCloud GmbH. * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CalDAV; use Sabre\HTTP\URLUtil; class Plugin extends \Sabre\CalDAV\Plugin { /** * @inheritdoc */ function getCalendarHomeForPrincipal($principalUrl) { if (strrpos($principalUrl, 'principals/users', -strlen($principalUrl)) !== false) { list(, $principalId) = URLUtil::splitPath($principalUrl); return self::CALENDAR_ROOT .'/' . $principalId; } return; } } PublicCalendar.php 0000604 00000004664 15247161531 0010140 0 ustar 00 <?php /** * @copyright Copyright (c) 2017, Georg Ehrke * * @author Georg Ehrke <oc.list@georgehrke.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CalDAV; use Sabre\DAV\Exception\NotFound; class PublicCalendar extends Calendar { /** * @param string $name * @throws NotFound * @return PublicCalendarObject */ public function getChild($name) { $obj = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'], $name); if (!$obj) { throw new NotFound('Calendar object not found'); } if ($obj['classification'] === CalDavBackend::CLASSIFICATION_PRIVATE) { throw new NotFound('Calendar object not found'); } $obj['acl'] = $this->getChildACL(); return new PublicCalendarObject($this->caldavBackend, $this->calendarInfo, $obj); } /** * @return PublicCalendarObject[] */ public function getChildren() { $objs = $this->caldavBackend->getCalendarObjects($this->calendarInfo['id']); $children = []; foreach ($objs as $obj) { if ($obj['classification'] === CalDavBackend::CLASSIFICATION_PRIVATE) { continue; } $obj['acl'] = $this->getChildACL(); $children[] = new PublicCalendarObject($this->caldavBackend, $this->calendarInfo, $obj); } return $children; } /** * @param string[] $paths * @return PublicCalendarObject[] */ public function getMultipleChildren(array $paths) { $objs = $this->caldavBackend->getMultipleCalendarObjects($this->calendarInfo['id'], $paths); $children = []; foreach ($objs as $obj) { if ($obj['classification'] === CalDavBackend::CLASSIFICATION_PRIVATE) { continue; } $obj['acl'] = $this->getChildACL(); $children[] = new PublicCalendarObject($this->caldavBackend, $this->calendarInfo, $obj); } return $children; } /** * public calendars are always shared * @return bool */ protected function isShared() { return true; } } Activity/Filter/Todo.php 0000604 00000004475 15247161531 0011216 0 ustar 00 <?php /** * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com> * * @license GNU AGPL version 3 or any later version * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ namespace OCA\DAV\CalDAV\Activity\Filter; use OCP\Activity\IFilter; use OCP\IL10N; use OCP\IURLGenerator; class Todo implements IFilter { /** @var IL10N */ protected $l; /** @var IURLGenerator */ protected $url; public function __construct(IL10N $l, IURLGenerator $url) { $this->l = $l; $this->url = $url; } /** * @return string Lowercase a-z and underscore only identifier * @since 11.0.0 */ public function getIdentifier() { return 'calendar_todo'; } /** * @return string A translated string * @since 11.0.0 */ public function getName() { return $this->l->t('Todos'); } /** * @return int whether the filter should be rather on the top or bottom of * the admin section. The filters are arranged in ascending order of the * priority values. It is required to return a value between 0 and 100. * @since 11.0.0 */ public function getPriority() { return 40; } /** * @return string Full URL to an icon, empty string when none is given * @since 11.0.0 */ public function getIcon() { return $this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/checkmark.svg')); } /** * @param string[] $types * @return string[] An array of allowed apps from which activities should be displayed * @since 11.0.0 */ public function filterTypes(array $types) { return array_intersect(['calendar_todo'], $types); } /** * @return string[] An array of allowed apps from which activities should be displayed * @since 11.0.0 */ public function allowedApps() { return []; } } Activity/Filter/Calendar.php 0000604 00000004517 15247161531 0012017 0 ustar 00 <?php /** * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com> * * @license GNU AGPL version 3 or any later version * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ namespace OCA\DAV\CalDAV\Activity\Filter; use OCP\Activity\IFilter; use OCP\IL10N; use OCP\IURLGenerator; class Calendar implements IFilter { /** @var IL10N */ protected $l; /** @var IURLGenerator */ protected $url; public function __construct(IL10N $l, IURLGenerator $url) { $this->l = $l; $this->url = $url; } /** * @return string Lowercase a-z and underscore only identifier * @since 11.0.0 */ public function getIdentifier() { return 'calendar'; } /** * @return string A translated string * @since 11.0.0 */ public function getName() { return $this->l->t('Calendar'); } /** * @return int whether the filter should be rather on the top or bottom of * the admin section. The filters are arranged in ascending order of the * priority values. It is required to return a value between 0 and 100. * @since 11.0.0 */ public function getPriority() { return 40; } /** * @return string Full URL to an icon, empty string when none is given * @since 11.0.0 */ public function getIcon() { return $this->url->getAbsoluteURL($this->url->imagePath('core', 'places/calendar-dark.svg')); } /** * @param string[] $types * @return string[] An array of allowed apps from which activities should be displayed * @since 11.0.0 */ public function filterTypes(array $types) { return array_intersect(['calendar', 'calendar_event'], $types); } /** * @return string[] An array of allowed apps from which activities should be displayed * @since 11.0.0 */ public function allowedApps() { return []; } } Activity/Provider/Todo.php 0000604 00000011142 15247161531 0011550 0 ustar 00 <?php /** * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com> * * @license GNU AGPL version 3 or any later version * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ namespace OCA\DAV\CalDAV\Activity\Provider; use OCP\Activity\IEvent; class Todo extends Event { /** * @param string $language * @param IEvent $event * @param IEvent|null $previousEvent * @return IEvent * @throws \InvalidArgumentException * @since 11.0.0 */ public function parse($language, IEvent $event, IEvent $previousEvent = null) { if ($event->getApp() !== 'dav' || $event->getType() !== 'calendar_todo') { throw new \InvalidArgumentException(); } $this->l = $this->languageFactory->get('dav', $language); if ($this->activityManager->getRequirePNG()) { $event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/checkmark.png'))); } else { $event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'actions/checkmark.svg'))); } if ($event->getSubject() === self::SUBJECT_OBJECT_ADD . '_todo') { $subject = $this->l->t('{actor} created todo {todo} in list {calendar}'); } else if ($event->getSubject() === self::SUBJECT_OBJECT_ADD . '_todo_self') { $subject = $this->l->t('You created todo {todo} in list {calendar}'); } else if ($event->getSubject() === self::SUBJECT_OBJECT_DELETE . '_todo') { $subject = $this->l->t('{actor} deleted todo {todo} from list {calendar}'); } else if ($event->getSubject() === self::SUBJECT_OBJECT_DELETE . '_todo_self') { $subject = $this->l->t('You deleted todo {todo} from list {calendar}'); } else if ($event->getSubject() === self::SUBJECT_OBJECT_UPDATE . '_todo') { $subject = $this->l->t('{actor} updated todo {todo} in list {calendar}'); } else if ($event->getSubject() === self::SUBJECT_OBJECT_UPDATE . '_todo_self') { $subject = $this->l->t('You updated todo {todo} in list {calendar}'); } else if ($event->getSubject() === self::SUBJECT_OBJECT_UPDATE . '_todo_completed') { $subject = $this->l->t('{actor} solved todo {todo} in list {calendar}'); } else if ($event->getSubject() === self::SUBJECT_OBJECT_UPDATE . '_todo_completed_self') { $subject = $this->l->t('You solved todo {todo} in list {calendar}'); } else if ($event->getSubject() === self::SUBJECT_OBJECT_UPDATE . '_todo_needs_action') { $subject = $this->l->t('{actor} reopened todo {todo} in list {calendar}'); } else if ($event->getSubject() === self::SUBJECT_OBJECT_UPDATE . '_todo_needs_action_self') { $subject = $this->l->t('You reopened todo {todo} in list {calendar}'); } else { throw new \InvalidArgumentException(); } $parsedParameters = $this->getParameters($event); $this->setSubjects($event, $subject, $parsedParameters); $event = $this->eventMerger->mergeEvents('todo', $event, $previousEvent); return $event; } /** * @param IEvent $event * @return array */ protected function getParameters(IEvent $event) { $subject = $event->getSubject(); $parameters = $event->getSubjectParameters(); switch ($subject) { case self::SUBJECT_OBJECT_ADD . '_todo': case self::SUBJECT_OBJECT_DELETE . '_todo': case self::SUBJECT_OBJECT_UPDATE . '_todo': case self::SUBJECT_OBJECT_UPDATE . '_todo_completed': case self::SUBJECT_OBJECT_UPDATE . '_todo_needs_action': return [ 'actor' => $this->generateUserParameter($parameters[0]), 'calendar' => $this->generateCalendarParameter($event->getObjectId(), $parameters[1]), 'todo' => $this->generateObjectParameter($parameters[2]), ]; case self::SUBJECT_OBJECT_ADD . '_todo_self': case self::SUBJECT_OBJECT_DELETE . '_todo_self': case self::SUBJECT_OBJECT_UPDATE . '_todo_self': case self::SUBJECT_OBJECT_UPDATE . '_todo_completed_self': case self::SUBJECT_OBJECT_UPDATE . '_todo_needs_action_self': return [ 'calendar' => $this->generateCalendarParameter($event->getObjectId(), $parameters[1]), 'todo' => $this->generateObjectParameter($parameters[2]), ]; } throw new \InvalidArgumentException(); } } Activity/Provider/Calendar.php 0000604 00000017451 15247161531 0012365 0 ustar 00 <?php /** * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com> * * @license GNU AGPL version 3 or any later version * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ namespace OCA\DAV\CalDAV\Activity\Provider; use OCP\Activity\IEvent; use OCP\Activity\IEventMerger; use OCP\Activity\IManager; use OCP\IL10N; use OCP\IURLGenerator; use OCP\IUserManager; use OCP\L10N\IFactory; class Calendar extends Base { const SUBJECT_ADD = 'calendar_add'; const SUBJECT_UPDATE = 'calendar_update'; const SUBJECT_DELETE = 'calendar_delete'; const SUBJECT_SHARE_USER = 'calendar_user_share'; const SUBJECT_SHARE_GROUP = 'calendar_group_share'; const SUBJECT_UNSHARE_USER = 'calendar_user_unshare'; const SUBJECT_UNSHARE_GROUP = 'calendar_group_unshare'; /** @var IFactory */ protected $languageFactory; /** @var IL10N */ protected $l; /** @var IURLGenerator */ protected $url; /** @var IManager */ protected $activityManager; /** @var IEventMerger */ protected $eventMerger; /** * @param IFactory $languageFactory * @param IURLGenerator $url * @param IManager $activityManager * @param IUserManager $userManager * @param IEventMerger $eventMerger */ public function __construct(IFactory $languageFactory, IURLGenerator $url, IManager $activityManager, IUserManager $userManager, IEventMerger $eventMerger) { parent::__construct($userManager); $this->languageFactory = $languageFactory; $this->url = $url; $this->activityManager = $activityManager; $this->eventMerger = $eventMerger; } /** * @param string $language * @param IEvent $event * @param IEvent|null $previousEvent * @return IEvent * @throws \InvalidArgumentException * @since 11.0.0 */ public function parse($language, IEvent $event, IEvent $previousEvent = null) { if ($event->getApp() !== 'dav' || $event->getType() !== 'calendar') { throw new \InvalidArgumentException(); } $this->l = $this->languageFactory->get('dav', $language); if ($this->activityManager->getRequirePNG()) { $event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'places/calendar-dark.png'))); } else { $event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'places/calendar-dark.svg'))); } if ($event->getSubject() === self::SUBJECT_ADD) { $subject = $this->l->t('{actor} created calendar {calendar}'); } else if ($event->getSubject() === self::SUBJECT_ADD . '_self') { $subject = $this->l->t('You created calendar {calendar}'); } else if ($event->getSubject() === self::SUBJECT_DELETE) { $subject = $this->l->t('{actor} deleted calendar {calendar}'); } else if ($event->getSubject() === self::SUBJECT_DELETE . '_self') { $subject = $this->l->t('You deleted calendar {calendar}'); } else if ($event->getSubject() === self::SUBJECT_UPDATE) { $subject = $this->l->t('{actor} updated calendar {calendar}'); } else if ($event->getSubject() === self::SUBJECT_UPDATE . '_self') { $subject = $this->l->t('You updated calendar {calendar}'); } else if ($event->getSubject() === self::SUBJECT_SHARE_USER) { $subject = $this->l->t('{actor} shared calendar {calendar} with you'); } else if ($event->getSubject() === self::SUBJECT_SHARE_USER . '_you') { $subject = $this->l->t('You shared calendar {calendar} with {user}'); } else if ($event->getSubject() === self::SUBJECT_SHARE_USER . '_by') { $subject = $this->l->t('{actor} shared calendar {calendar} with {user}'); } else if ($event->getSubject() === self::SUBJECT_UNSHARE_USER) { $subject = $this->l->t('{actor} unshared calendar {calendar} from you'); } else if ($event->getSubject() === self::SUBJECT_UNSHARE_USER . '_you') { $subject = $this->l->t('You unshared calendar {calendar} from {user}'); } else if ($event->getSubject() === self::SUBJECT_UNSHARE_USER . '_by') { $subject = $this->l->t('{actor} unshared calendar {calendar} from {user}'); } else if ($event->getSubject() === self::SUBJECT_UNSHARE_USER . '_self') { $subject = $this->l->t('{actor} unshared calendar {calendar} from themselves'); } else if ($event->getSubject() === self::SUBJECT_SHARE_GROUP . '_you') { $subject = $this->l->t('You shared calendar {calendar} with group {group}'); } else if ($event->getSubject() === self::SUBJECT_SHARE_GROUP . '_by') { $subject = $this->l->t('{actor} shared calendar {calendar} with group {group}'); } else if ($event->getSubject() === self::SUBJECT_UNSHARE_GROUP . '_you') { $subject = $this->l->t('You unshared calendar {calendar} from group {group}'); } else if ($event->getSubject() === self::SUBJECT_UNSHARE_GROUP . '_by') { $subject = $this->l->t('{actor} unshared calendar {calendar} from group {group}'); } else { throw new \InvalidArgumentException(); } $parsedParameters = $this->getParameters($event); $this->setSubjects($event, $subject, $parsedParameters); $event = $this->eventMerger->mergeEvents('calendar', $event, $previousEvent); if ($event->getChildEvent() === null) { if (isset($parsedParameters['user'])) { // Couldn't group by calendar, maybe we can group by users $event = $this->eventMerger->mergeEvents('user', $event, $previousEvent); } else if (isset($parsedParameters['group'])) { // Couldn't group by calendar, maybe we can group by groups $event = $this->eventMerger->mergeEvents('group', $event, $previousEvent); } } return $event; } /** * @param IEvent $event * @return array */ protected function getParameters(IEvent $event) { $subject = $event->getSubject(); $parameters = $event->getSubjectParameters(); switch ($subject) { case self::SUBJECT_ADD: case self::SUBJECT_ADD . '_self': case self::SUBJECT_DELETE: case self::SUBJECT_DELETE . '_self': case self::SUBJECT_UPDATE: case self::SUBJECT_UPDATE . '_self': case self::SUBJECT_SHARE_USER: case self::SUBJECT_UNSHARE_USER: case self::SUBJECT_UNSHARE_USER . '_self': return [ 'actor' => $this->generateUserParameter($parameters[0]), 'calendar' => $this->generateCalendarParameter($event->getObjectId(), $parameters[1]), ]; case self::SUBJECT_SHARE_USER . '_you': case self::SUBJECT_UNSHARE_USER . '_you': return [ 'user' => $this->generateUserParameter($parameters[0]), 'calendar' => $this->generateCalendarParameter($event->getObjectId(), $parameters[1]), ]; case self::SUBJECT_SHARE_USER . '_by': case self::SUBJECT_UNSHARE_USER . '_by': return [ 'user' => $this->generateUserParameter($parameters[0]), 'calendar' => $this->generateCalendarParameter($event->getObjectId(), $parameters[1]), 'actor' => $this->generateUserParameter($parameters[2]), ]; case self::SUBJECT_SHARE_GROUP . '_you': case self::SUBJECT_UNSHARE_GROUP . '_you': return [ 'group' => $this->generateGroupParameter($parameters[0]), 'calendar' => $this->generateCalendarParameter($event->getObjectId(), $parameters[1]), ]; case self::SUBJECT_SHARE_GROUP . '_by': case self::SUBJECT_UNSHARE_GROUP . '_by': return [ 'group' => $this->generateGroupParameter($parameters[0]), 'calendar' => $this->generateCalendarParameter($event->getObjectId(), $parameters[1]), 'actor' => $this->generateUserParameter($parameters[2]), ]; } throw new \InvalidArgumentException(); } } Activity/Provider/Base.php 0000604 00000006022 15247161531 0011516 0 ustar 00 <?php /** * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com> * * @license GNU AGPL version 3 or any later version * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ namespace OCA\DAV\CalDAV\Activity\Provider; use OCP\Activity\IEvent; use OCP\Activity\IProvider; use OCP\IUser; use OCP\IUserManager; abstract class Base implements IProvider { /** @var IUserManager */ protected $userManager; /** @var string[] cached displayNames - key is the UID and value the displayname */ protected $displayNames = []; /** * @param IUserManager $userManager */ public function __construct(IUserManager $userManager) { $this->userManager = $userManager; } /** * @param IEvent $event * @param string $subject * @param array $parameters */ protected function setSubjects(IEvent $event, $subject, array $parameters) { $placeholders = $replacements = []; foreach ($parameters as $placeholder => $parameter) { $placeholders[] = '{' . $placeholder . '}'; $replacements[] = $parameter['name']; } $event->setParsedSubject(str_replace($placeholders, $replacements, $subject)) ->setRichSubject($subject, $parameters); } /** * @param array $eventData * @return array */ protected function generateObjectParameter($eventData) { if (!is_array($eventData) || !isset($eventData['id']) || !isset($eventData['name'])) { throw new \InvalidArgumentException(); }; return [ 'type' => 'calendar-event', 'id' => $eventData['id'], 'name' => $eventData['name'], ]; } /** * @param int $id * @param string $name * @return array */ protected function generateCalendarParameter($id, $name) { return [ 'type' => 'calendar', 'id' => $id, 'name' => $name, ]; } /** * @param string $id * @return array */ protected function generateGroupParameter($id) { return [ 'type' => 'group', 'id' => $id, 'name' => $id, ]; } /** * @param string $uid * @return array */ protected function generateUserParameter($uid) { if (!isset($this->displayNames[$uid])) { $this->displayNames[$uid] = $this->getDisplayName($uid); } return [ 'type' => 'user', 'id' => $uid, 'name' => $this->displayNames[$uid], ]; } /** * @param string $uid * @return string */ protected function getDisplayName($uid) { $user = $this->userManager->get($uid); if ($user instanceof IUser) { return $user->getDisplayName(); } else { return $uid; } } } Activity/Provider/Event.php 0000604 00000011443 15247161531 0011730 0 ustar 00 <?php /** * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com> * * @license GNU AGPL version 3 or any later version * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ namespace OCA\DAV\CalDAV\Activity\Provider; use OCP\Activity\IEvent; use OCP\Activity\IEventMerger; use OCP\Activity\IManager; use OCP\IL10N; use OCP\IURLGenerator; use OCP\IUserManager; use OCP\L10N\IFactory; class Event extends Base { const SUBJECT_OBJECT_ADD = 'object_add'; const SUBJECT_OBJECT_UPDATE = 'object_update'; const SUBJECT_OBJECT_DELETE = 'object_delete'; /** @var IFactory */ protected $languageFactory; /** @var IL10N */ protected $l; /** @var IURLGenerator */ protected $url; /** @var IManager */ protected $activityManager; /** @var IEventMerger */ protected $eventMerger; /** * @param IFactory $languageFactory * @param IURLGenerator $url * @param IManager $activityManager * @param IUserManager $userManager * @param IEventMerger $eventMerger */ public function __construct(IFactory $languageFactory, IURLGenerator $url, IManager $activityManager, IUserManager $userManager, IEventMerger $eventMerger) { parent::__construct($userManager); $this->languageFactory = $languageFactory; $this->url = $url; $this->activityManager = $activityManager; $this->eventMerger = $eventMerger; } /** * @param string $language * @param IEvent $event * @param IEvent|null $previousEvent * @return IEvent * @throws \InvalidArgumentException * @since 11.0.0 */ public function parse($language, IEvent $event, IEvent $previousEvent = null) { if ($event->getApp() !== 'dav' || $event->getType() !== 'calendar_event') { throw new \InvalidArgumentException(); } $this->l = $this->languageFactory->get('dav', $language); if ($this->activityManager->getRequirePNG()) { $event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'places/calendar-dark.png'))); } else { $event->setIcon($this->url->getAbsoluteURL($this->url->imagePath('core', 'places/calendar-dark.svg'))); } if ($event->getSubject() === self::SUBJECT_OBJECT_ADD . '_event') { $subject = $this->l->t('{actor} created event {event} in calendar {calendar}'); } else if ($event->getSubject() === self::SUBJECT_OBJECT_ADD . '_event_self') { $subject = $this->l->t('You created event {event} in calendar {calendar}'); } else if ($event->getSubject() === self::SUBJECT_OBJECT_DELETE . '_event') { $subject = $this->l->t('{actor} deleted event {event} from calendar {calendar}'); } else if ($event->getSubject() === self::SUBJECT_OBJECT_DELETE . '_event_self') { $subject = $this->l->t('You deleted event {event} from calendar {calendar}'); } else if ($event->getSubject() === self::SUBJECT_OBJECT_UPDATE . '_event') { $subject = $this->l->t('{actor} updated event {event} in calendar {calendar}'); } else if ($event->getSubject() === self::SUBJECT_OBJECT_UPDATE . '_event_self') { $subject = $this->l->t('You updated event {event} in calendar {calendar}'); } else { throw new \InvalidArgumentException(); } $parsedParameters = $this->getParameters($event); $this->setSubjects($event, $subject, $parsedParameters); $event = $this->eventMerger->mergeEvents('event', $event, $previousEvent); return $event; } /** * @param IEvent $event * @return array */ protected function getParameters(IEvent $event) { $subject = $event->getSubject(); $parameters = $event->getSubjectParameters(); switch ($subject) { case self::SUBJECT_OBJECT_ADD . '_event': case self::SUBJECT_OBJECT_DELETE . '_event': case self::SUBJECT_OBJECT_UPDATE . '_event': return [ 'actor' => $this->generateUserParameter($parameters[0]), 'calendar' => $this->generateCalendarParameter($event->getObjectId(), $parameters[1]), 'event' => $this->generateObjectParameter($parameters[2]), ]; case self::SUBJECT_OBJECT_ADD . '_event_self': case self::SUBJECT_OBJECT_DELETE . '_event_self': case self::SUBJECT_OBJECT_UPDATE . '_event_self': return [ 'calendar' => $this->generateCalendarParameter($event->getObjectId(), $parameters[1]), 'event' => $this->generateObjectParameter($parameters[2]), ]; } throw new \InvalidArgumentException(); } } Activity/Setting/Event.php 0000604 00000004411 15247161531 0011550 0 ustar 00 <?php /** * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com> * * @license GNU AGPL version 3 or any later version * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ namespace OCA\DAV\CalDAV\Activity\Setting; use OCP\Activity\ISetting; use OCP\IL10N; class Event implements ISetting { /** @var IL10N */ protected $l; /** * @param IL10N $l */ public function __construct(IL10N $l) { $this->l = $l; } /** * @return string Lowercase a-z and underscore only identifier * @since 11.0.0 */ public function getIdentifier() { return 'calendar_event'; } /** * @return string A translated string * @since 11.0.0 */ public function getName() { return $this->l->t('A calendar <strong>event</strong> was modified'); } /** * @return int whether the filter should be rather on the top or bottom of * the admin section. The filters are arranged in ascending order of the * priority values. It is required to return a value between 0 and 100. * @since 11.0.0 */ public function getPriority() { return 50; } /** * @return bool True when the option can be changed for the stream * @since 11.0.0 */ public function canChangeStream() { return true; } /** * @return bool True when the option can be changed for the stream * @since 11.0.0 */ public function isDefaultEnabledStream() { return true; } /** * @return bool True when the option can be changed for the mail * @since 11.0.0 */ public function canChangeMail() { return true; } /** * @return bool True when the option can be changed for the stream * @since 11.0.0 */ public function isDefaultEnabledMail() { return false; } } Activity/Setting/Todo.php 0000604 00000004406 15247161531 0011400 0 ustar 00 <?php /** * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com> * * @license GNU AGPL version 3 or any later version * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ namespace OCA\DAV\CalDAV\Activity\Setting; use OCP\Activity\ISetting; use OCP\IL10N; class Todo implements ISetting { /** @var IL10N */ protected $l; /** * @param IL10N $l */ public function __construct(IL10N $l) { $this->l = $l; } /** * @return string Lowercase a-z and underscore only identifier * @since 11.0.0 */ public function getIdentifier() { return 'calendar_todo'; } /** * @return string A translated string * @since 11.0.0 */ public function getName() { return $this->l->t('A calendar <strong>todo</strong> was modified'); } /** * @return int whether the filter should be rather on the top or bottom of * the admin section. The filters are arranged in ascending order of the * priority values. It is required to return a value between 0 and 100. * @since 11.0.0 */ public function getPriority() { return 50; } /** * @return bool True when the option can be changed for the stream * @since 11.0.0 */ public function canChangeStream() { return true; } /** * @return bool True when the option can be changed for the stream * @since 11.0.0 */ public function isDefaultEnabledStream() { return true; } /** * @return bool True when the option can be changed for the mail * @since 11.0.0 */ public function canChangeMail() { return true; } /** * @return bool True when the option can be changed for the stream * @since 11.0.0 */ public function isDefaultEnabledMail() { return false; } } Activity/Setting/Calendar.php 0000604 00000004400 15247161531 0012176 0 ustar 00 <?php /** * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com> * * @license GNU AGPL version 3 or any later version * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ namespace OCA\DAV\CalDAV\Activity\Setting; use OCP\Activity\ISetting; use OCP\IL10N; class Calendar implements ISetting { /** @var IL10N */ protected $l; /** * @param IL10N $l */ public function __construct(IL10N $l) { $this->l = $l; } /** * @return string Lowercase a-z and underscore only identifier * @since 11.0.0 */ public function getIdentifier() { return 'calendar'; } /** * @return string A translated string * @since 11.0.0 */ public function getName() { return $this->l->t('A <strong>calendar</strong> was modified'); } /** * @return int whether the filter should be rather on the top or bottom of * the admin section. The filters are arranged in ascending order of the * priority values. It is required to return a value between 0 and 100. * @since 11.0.0 */ public function getPriority() { return 50; } /** * @return bool True when the option can be changed for the stream * @since 11.0.0 */ public function canChangeStream() { return true; } /** * @return bool True when the option can be changed for the stream * @since 11.0.0 */ public function isDefaultEnabledStream() { return true; } /** * @return bool True when the option can be changed for the mail * @since 11.0.0 */ public function canChangeMail() { return true; } /** * @return bool True when the option can be changed for the stream * @since 11.0.0 */ public function isDefaultEnabledMail() { return false; } } Activity/Backend.php 0000604 00000032017 15247161531 0010404 0 ustar 00 <?php /** * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com> * * @license GNU AGPL version 3 or any later version * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ namespace OCA\DAV\CalDAV\Activity; use OCA\DAV\CalDAV\Activity\Provider\Calendar; use OCA\DAV\CalDAV\Activity\Provider\Event; use OCP\Activity\IEvent; use OCP\Activity\IManager as IActivityManager; use OCP\IGroup; use OCP\IGroupManager; use OCP\IUser; use OCP\IUserSession; use Sabre\VObject\Reader; /** * Class Backend * * @package OCA\DAV\CalDAV\Activity */ class Backend { /** @var IActivityManager */ protected $activityManager; /** @var IGroupManager */ protected $groupManager; /** @var IUserSession */ protected $userSession; /** * @param IActivityManager $activityManager * @param IGroupManager $groupManager * @param IUserSession $userSession */ public function __construct(IActivityManager $activityManager, IGroupManager $groupManager, IUserSession $userSession) { $this->activityManager = $activityManager; $this->groupManager = $groupManager; $this->userSession = $userSession; } /** * Creates activities when a calendar was creates * * @param array $calendarData */ public function onCalendarAdd(array $calendarData) { $this->triggerCalendarActivity(Calendar::SUBJECT_ADD, $calendarData); } /** * Creates activities when a calendar was updated * * @param array $calendarData * @param array $shares * @param array $properties */ public function onCalendarUpdate(array $calendarData, array $shares, array $properties) { $this->triggerCalendarActivity(Calendar::SUBJECT_UPDATE, $calendarData, $shares, $properties); } /** * Creates activities when a calendar was deleted * * @param array $calendarData * @param array $shares */ public function onCalendarDelete(array $calendarData, array $shares) { $this->triggerCalendarActivity(Calendar::SUBJECT_DELETE, $calendarData, $shares); } /** * Creates activities for all related users when a calendar was touched * * @param string $action * @param array $calendarData * @param array $shares * @param array $changedProperties */ protected function triggerCalendarActivity($action, array $calendarData, array $shares = [], array $changedProperties = []) { if (!isset($calendarData['principaluri'])) { return; } $principal = explode('/', $calendarData['principaluri']); $owner = array_pop($principal); $currentUser = $this->userSession->getUser(); if ($currentUser instanceof IUser) { $currentUser = $currentUser->getUID(); } else { $currentUser = $owner; } $event = $this->activityManager->generateEvent(); $event->setApp('dav') ->setObject('calendar', (int) $calendarData['id']) ->setType('calendar') ->setAuthor($currentUser); $changedVisibleInformation = array_intersect([ '{DAV:}displayname', '{http://apple.com/ns/ical/}calendar-color' ], array_keys($changedProperties)); if (empty($shares) || ($action === Calendar::SUBJECT_UPDATE && empty($changedVisibleInformation))) { $users = [$owner]; } else { $users = $this->getUsersForShares($shares); $users[] = $owner; } foreach ($users as $user) { $event->setAffectedUser($user) ->setSubject( $user === $currentUser ? $action . '_self' : $action, [ $currentUser, $calendarData['{DAV:}displayname'], ] ); $this->activityManager->publish($event); } } /** * Creates activities for all related users when a calendar was (un-)shared * * @param array $calendarData * @param array $shares * @param array $add * @param array $remove */ public function onCalendarUpdateShares(array $calendarData, array $shares, array $add, array $remove) { $principal = explode('/', $calendarData['principaluri']); $owner = $principal[2]; $currentUser = $this->userSession->getUser(); if ($currentUser instanceof IUser) { $currentUser = $currentUser->getUID(); } else { $currentUser = $owner; } $event = $this->activityManager->generateEvent(); $event->setApp('dav') ->setObject('calendar', (int) $calendarData['id']) ->setType('calendar') ->setAuthor($currentUser); foreach ($remove as $principal) { // principal:principals/users/test $parts = explode(':', $principal, 2); if ($parts[0] !== 'principal') { continue; } $principal = explode('/', $parts[1]); if ($principal[1] === 'users') { $this->triggerActivityUser( $principal[2], $event, $calendarData, Calendar::SUBJECT_UNSHARE_USER, Calendar::SUBJECT_DELETE . '_self' ); if ($owner !== $principal[2]) { $parameters = [ $principal[2], $calendarData['{DAV:}displayname'], ]; if ($owner === $event->getAuthor()) { $subject = Calendar::SUBJECT_UNSHARE_USER . '_you'; } else if ($principal[2] === $event->getAuthor()) { $subject = Calendar::SUBJECT_UNSHARE_USER . '_self'; } else { $event->setAffectedUser($event->getAuthor()) ->setSubject(Calendar::SUBJECT_UNSHARE_USER . '_you', $parameters); $this->activityManager->publish($event); $subject = Calendar::SUBJECT_UNSHARE_USER . '_by'; $parameters[] = $event->getAuthor(); } $event->setAffectedUser($owner) ->setSubject($subject, $parameters); $this->activityManager->publish($event); } } else if ($principal[1] === 'groups') { $this->triggerActivityGroup($principal[2], $event, $calendarData, Calendar::SUBJECT_UNSHARE_USER); $parameters = [ $principal[2], $calendarData['{DAV:}displayname'], ]; if ($owner === $event->getAuthor()) { $subject = Calendar::SUBJECT_UNSHARE_GROUP . '_you'; } else { $event->setAffectedUser($event->getAuthor()) ->setSubject(Calendar::SUBJECT_UNSHARE_GROUP . '_you', $parameters); $this->activityManager->publish($event); $subject = Calendar::SUBJECT_UNSHARE_GROUP . '_by'; $parameters[] = $event->getAuthor(); } $event->setAffectedUser($owner) ->setSubject($subject, $parameters); $this->activityManager->publish($event); } } foreach ($add as $share) { if ($this->isAlreadyShared($share['href'], $shares)) { continue; } // principal:principals/users/test $parts = explode(':', $share['href'], 2); if ($parts[0] !== 'principal') { continue; } $principal = explode('/', $parts[1]); if ($principal[1] === 'users') { $this->triggerActivityUser($principal[2], $event, $calendarData, Calendar::SUBJECT_SHARE_USER); if ($owner !== $principal[2]) { $parameters = [ $principal[2], $calendarData['{DAV:}displayname'], ]; if ($owner === $event->getAuthor()) { $subject = Calendar::SUBJECT_SHARE_USER . '_you'; } else { $event->setAffectedUser($event->getAuthor()) ->setSubject(Calendar::SUBJECT_SHARE_USER . '_you', $parameters); $this->activityManager->publish($event); $subject = Calendar::SUBJECT_SHARE_USER . '_by'; $parameters[] = $event->getAuthor(); } $event->setAffectedUser($owner) ->setSubject($subject, $parameters); $this->activityManager->publish($event); } } else if ($principal[1] === 'groups') { $this->triggerActivityGroup($principal[2], $event, $calendarData, Calendar::SUBJECT_SHARE_USER); $parameters = [ $principal[2], $calendarData['{DAV:}displayname'], ]; if ($owner === $event->getAuthor()) { $subject = Calendar::SUBJECT_SHARE_GROUP . '_you'; } else { $event->setAffectedUser($event->getAuthor()) ->setSubject(Calendar::SUBJECT_SHARE_GROUP . '_you', $parameters); $this->activityManager->publish($event); $subject = Calendar::SUBJECT_SHARE_GROUP . '_by'; $parameters[] = $event->getAuthor(); } $event->setAffectedUser($owner) ->setSubject($subject, $parameters); $this->activityManager->publish($event); } } } /** * Checks if a calendar is already shared with a principal * * @param string $principal * @param array[] $shares * @return bool */ protected function isAlreadyShared($principal, $shares) { foreach ($shares as $share) { if ($principal === $share['href']) { return true; } } return false; } /** * Creates the given activity for all members of the given group * * @param string $gid * @param IEvent $event * @param array $properties * @param string $subject */ protected function triggerActivityGroup($gid, IEvent $event, array $properties, $subject) { $group = $this->groupManager->get($gid); if ($group instanceof IGroup) { foreach ($group->getUsers() as $user) { // Exclude current user if ($user->getUID() !== $event->getAuthor()) { $this->triggerActivityUser($user->getUID(), $event, $properties, $subject); } } } } /** * Creates the given activity for the given user * * @param string $user * @param IEvent $event * @param array $properties * @param string $subject * @param string $subjectSelf */ protected function triggerActivityUser($user, IEvent $event, array $properties, $subject, $subjectSelf = '') { $event->setAffectedUser($user) ->setSubject( $user === $event->getAuthor() && $subjectSelf ? $subjectSelf : $subject, [ $event->getAuthor(), $properties['{DAV:}displayname'], ] ); $this->activityManager->publish($event); } /** * Creates activities when a calendar object was created/updated/deleted * * @param string $action * @param array $calendarData * @param array $shares * @param array $objectData */ public function onTouchCalendarObject($action, array $calendarData, array $shares, array $objectData) { if (!isset($calendarData['principaluri'])) { return; } $principal = explode('/', $calendarData['principaluri']); $owner = array_pop($principal); $currentUser = $this->userSession->getUser(); if ($currentUser instanceof IUser) { $currentUser = $currentUser->getUID(); } else { $currentUser = $owner; } $object = $this->getObjectNameAndType($objectData); $action = $action . '_' . $object['type']; if ($object['type'] === 'todo' && strpos($action, Event::SUBJECT_OBJECT_UPDATE) === 0 && $object['status'] === 'COMPLETED') { $action .= '_completed'; } else if ($object['type'] === 'todo' && strpos($action, Event::SUBJECT_OBJECT_UPDATE) === 0 && $object['status'] === 'NEEDS-ACTION') { $action .= '_needs_action'; } $event = $this->activityManager->generateEvent(); $event->setApp('dav') ->setObject('calendar', (int) $calendarData['id']) ->setType($object['type'] === 'event' ? 'calendar_event' : 'calendar_todo') ->setAuthor($currentUser); $users = $this->getUsersForShares($shares); $users[] = $owner; foreach ($users as $user) { $event->setAffectedUser($user) ->setSubject( $user === $currentUser ? $action . '_self' : $action, [ $currentUser, $calendarData['{DAV:}displayname'], [ 'id' => $object['id'], 'name' => $object['name'], ], ] ); $this->activityManager->publish($event); } } /** * @param array $objectData * @return string[]|bool */ protected function getObjectNameAndType(array $objectData) { $vObject = Reader::read($objectData['calendardata']); $component = $componentType = null; foreach($vObject->getComponents() as $component) { if (in_array($component->name, ['VEVENT', 'VTODO'])) { $componentType = $component->name; break; } } if (!$componentType) { // Calendar objects must have a VEVENT or VTODO component return false; } if ($componentType === 'VEVENT') { return ['id' => (string) $component->UID, 'name' => (string) $component->SUMMARY, 'type' => 'event']; } return ['id' => (string) $component->UID, 'name' => (string) $component->SUMMARY, 'type' => 'todo', 'status' => (string) $component->STATUS]; } /** * Get all users that have access to a given calendar * * @param array $shares * @return string[] */ protected function getUsersForShares(array $shares) { $users = $groups = []; foreach ($shares as $share) { $prinical = explode('/', $share['{http://owncloud.org/ns}principal']); if ($prinical[1] === 'users') { $users[] = $prinical[2]; } else if ($prinical[1] === 'groups') { $groups[] = $prinical[2]; } } if (!empty($groups)) { foreach ($groups as $gid) { $group = $this->groupManager->get($gid); if ($group instanceof IGroup) { foreach ($group->getUsers() as $user) { $users[] = $user->getUID(); } } } } return array_unique($users); } } CalendarRoot.php 0000604 00000001755 15247161531 0007643 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Lukas Reschke <lukas@statuscode.ch> * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CalDAV; class CalendarRoot extends \Sabre\CalDAV\CalendarRoot { function getChildForPrincipal(array $principal) { return new CalendarHome($this->caldavBackend, $principal); } } CalendarHome.php 0000604 00000010005 15247161531 0007574 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Lukas Reschke <lukas@statuscode.ch> * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\CalDAV; use Sabre\CalDAV\Backend\BackendInterface; use Sabre\CalDAV\Backend\NotificationSupport; use Sabre\CalDAV\Backend\SchedulingSupport; use Sabre\CalDAV\Backend\SubscriptionSupport; use Sabre\CalDAV\Schedule\Inbox; use Sabre\CalDAV\Schedule\Outbox; use Sabre\CalDAV\Subscriptions\Subscription; use Sabre\DAV\Exception\NotFound; class CalendarHome extends \Sabre\CalDAV\CalendarHome { /** @var \OCP\IL10N */ private $l10n; public function __construct(BackendInterface $caldavBackend, $principalInfo) { parent::__construct($caldavBackend, $principalInfo); $this->l10n = \OC::$server->getL10N('dav'); } /** * @return BackendInterface */ public function getCalDAVBackend() { return $this->caldavBackend; } /** * @inheritdoc */ function getChildren() { $calendars = $this->caldavBackend->getCalendarsForUser($this->principalInfo['uri']); $objects = []; foreach ($calendars as $calendar) { $objects[] = new Calendar($this->caldavBackend, $calendar, $this->l10n); } if ($this->caldavBackend instanceof SchedulingSupport) { $objects[] = new Inbox($this->caldavBackend, $this->principalInfo['uri']); $objects[] = new Outbox($this->principalInfo['uri']); } // We're adding a notifications node, if it's supported by the backend. if ($this->caldavBackend instanceof NotificationSupport) { $objects[] = new \Sabre\CalDAV\Notifications\Collection($this->caldavBackend, $this->principalInfo['uri']); } // If the backend supports subscriptions, we'll add those as well, if ($this->caldavBackend instanceof SubscriptionSupport) { foreach ($this->caldavBackend->getSubscriptionsForUser($this->principalInfo['uri']) as $subscription) { $objects[] = new Subscription($this->caldavBackend, $subscription); } } return $objects; } /** * @inheritdoc */ function getChild($name) { // Special nodes if ($name === 'inbox' && $this->caldavBackend instanceof SchedulingSupport) { return new Inbox($this->caldavBackend, $this->principalInfo['uri']); } if ($name === 'outbox' && $this->caldavBackend instanceof SchedulingSupport) { return new Outbox($this->principalInfo['uri']); } if ($name === 'notifications' && $this->caldavBackend instanceof NotificationSupport) { return new \Sabre\CalDAv\Notifications\Collection($this->caldavBackend, $this->principalInfo['uri']); } // Calendars foreach ($this->caldavBackend->getCalendarsForUser($this->principalInfo['uri']) as $calendar) { if ($calendar['uri'] === $name) { return new Calendar($this->caldavBackend, $calendar, $this->l10n); } } if ($this->caldavBackend instanceof SubscriptionSupport) { foreach ($this->caldavBackend->getSubscriptionsForUser($this->principalInfo['uri']) as $subscription) { if ($subscription['uri'] === $name) { return new Subscription($this->caldavBackend, $subscription); } } } throw new NotFound('Node with name \'' . $name . '\' could not be found'); } /** * @param array $filters * @param integer|null $limit * @param integer|null $offset */ function calendarSearch(array $filters, $limit=null, $offset=null) { $principalUri = $this->principalInfo['uri']; return $this->caldavBackend->calendarSearch($principalUri, $filters, $limit, $offset); } }
| ver. 1.4 |
Github
|
.
| PHP 7.4.33 | Generation time: 0 |
proxy
|
phpinfo
|
Settings