Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Allow apps to provide Calendars in user's calendarHome
Signed-off-by: Georg Ehrke <[email protected]>
  • Loading branch information
georgehrke committed Feb 18, 2020
commit b46e5cb270b1730a6e6d4a98e52ca672eafebd39
2 changes: 2 additions & 0 deletions apps/dav/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
'OCA\\DAV\\CalDAV\\CalendarObject' => $baseDir . '/../lib/CalDAV/CalendarObject.php',
'OCA\\DAV\\CalDAV\\CalendarRoot' => $baseDir . '/../lib/CalDAV/CalendarRoot.php',
'OCA\\DAV\\CalDAV\\ICSExportPlugin\\ICSExportPlugin' => $baseDir . '/../lib/CalDAV/ICSExportPlugin/ICSExportPlugin.php',
'OCA\\DAV\\CalDAV\\Integration\\ExternalCalendar' => $baseDir . '/../lib/CalDAV/Integration/ExternalCalendar.php',
'OCA\\DAV\\CalDAV\\Integration\\ICalendarProvider' => $baseDir . '/../lib/CalDAV/Integration/ICalendarProvider.php',
'OCA\\DAV\\CalDAV\\InvitationResponse\\InvitationResponseServer' => $baseDir . '/../lib/CalDAV/InvitationResponse/InvitationResponseServer.php',
'OCA\\DAV\\CalDAV\\Outbox' => $baseDir . '/../lib/CalDAV/Outbox.php',
'OCA\\DAV\\CalDAV\\Plugin' => $baseDir . '/../lib/CalDAV/Plugin.php',
Expand Down
2 changes: 2 additions & 0 deletions apps/dav/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ class ComposerStaticInitDAV
'OCA\\DAV\\CalDAV\\CalendarObject' => __DIR__ . '/..' . '/../lib/CalDAV/CalendarObject.php',
'OCA\\DAV\\CalDAV\\CalendarRoot' => __DIR__ . '/..' . '/../lib/CalDAV/CalendarRoot.php',
'OCA\\DAV\\CalDAV\\ICSExportPlugin\\ICSExportPlugin' => __DIR__ . '/..' . '/../lib/CalDAV/ICSExportPlugin/ICSExportPlugin.php',
'OCA\\DAV\\CalDAV\\Integration\\ExternalCalendar' => __DIR__ . '/..' . '/../lib/CalDAV/Integration/ExternalCalendar.php',
'OCA\\DAV\\CalDAV\\Integration\\ICalendarProvider' => __DIR__ . '/..' . '/../lib/CalDAV/Integration/ICalendarProvider.php',
'OCA\\DAV\\CalDAV\\InvitationResponse\\InvitationResponseServer' => __DIR__ . '/..' . '/../lib/CalDAV/InvitationResponse/InvitationResponseServer.php',
'OCA\\DAV\\CalDAV\\Outbox' => __DIR__ . '/..' . '/../lib/CalDAV/Outbox.php',
'OCA\\DAV\\CalDAV\\Plugin' => __DIR__ . '/..' . '/../lib/CalDAV/Plugin.php',
Expand Down
57 changes: 57 additions & 0 deletions apps/dav/lib/AppInfo/PluginManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
namespace OCA\DAV\AppInfo;

use OC\ServerContainer;
use OCA\DAV\CalDAV\Integration\ICalendarProvider;
use OCP\App\IAppManager;
use OCP\AppFramework\QueryException;

Expand Down Expand Up @@ -58,6 +59,13 @@ class PluginManager {
*/
private $collections = null;

/**
* Calendar plugins
*
* @var array
*/
private $calendarPlugins = null;

/**
* Contstruct a PluginManager
*
Expand Down Expand Up @@ -93,11 +101,24 @@ public function getAppCollections() {
return $this->collections;
}

/**
* Returns an array of app-registered calendar plugins
*
* @return array
*/
public function getCalendarPlugins():array {
if (null === $this->calendarPlugins) {
$this->populate();
}
return $this->calendarPlugins;
}

/**
* Retrieve plugin and collection list and populate attributes
*/
private function populate() {
$this->plugins = [];
$this->calendarPlugins = [];
$this->collections = [];
foreach ($this->appManager->getInstalledApps() as $app) {
// load plugins and collections from info.xml
Expand All @@ -107,6 +128,7 @@ private function populate() {
}
$this->loadSabrePluginsFromInfoXml($this->extractPluginList($info));
$this->loadSabreCollectionsFromInfoXml($this->extractCollectionList($info));
$this->loadSabreCalendarPluginsFromInfoXml($this->extractCalendarPluginList($info));
}
}

Expand Down Expand Up @@ -140,6 +162,21 @@ private function extractCollectionList(array $array) {
return [];
}

private function extractCalendarPluginList(array $array):array {
if (isset($array['sabre']) && is_array($array['sabre'])) {
if (isset($array['sabre']['calendar-plugins']) && is_array($array['sabre']['calendar-plugins'])) {
if (isset($array['sabre']['calendar-plugins']['plugin'])) {
$items = $array['sabre']['calendar-plugins']['plugin'];
if (!is_array($items)) {
$items = [$items];
}
return $items;
}
}
}
return [];
}

private function loadSabrePluginsFromInfoXml(array $plugins) {
foreach ($plugins as $plugin) {
try {
Expand Down Expand Up @@ -168,4 +205,24 @@ private function loadSabreCollectionsFromInfoXml(array $collections) {
}
}

private function loadSabreCalendarPluginsFromInfoXml(array $calendarPlugins):void {
foreach ($calendarPlugins as $calendarPlugin) {
try {
$instantiatedCalendarPlugin = $this->container->query($calendarPlugin);
} catch (QueryException $e) {
if (class_exists($calendarPlugin)) {
$instantiatedCalendarPlugin = new $calendarPlugin();
} else {
throw new \Exception("Sabre calendar-plugin class '$calendarPlugin' is unknown and could not be loaded");
}
}

if (!($instantiatedCalendarPlugin instanceof ICalendarProvider)) {
throw new \Exception("Sabre calendar-plugin class '$calendarPlugin' does not implement ICalendarProvider interface");
}

$this->calendarPlugins[] = $instantiatedCalendarPlugin;
}
}

}
36 changes: 34 additions & 2 deletions apps/dav/lib/CalDAV/CalendarHome.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@

namespace OCA\DAV\CalDAV;

use OCA\DAV\AppInfo\PluginManager;
use OCA\DAV\CalDAV\Integration\ExternalCalendar;
use OCA\DAV\CalDAV\Integration\ICalendarProvider;
use Sabre\CalDAV\Backend\BackendInterface;
use Sabre\CalDAV\Backend\NotificationSupport;
use Sabre\CalDAV\Backend\SchedulingSupport;
Expand All @@ -44,13 +47,20 @@ class CalendarHome extends \Sabre\CalDAV\CalendarHome {
/** @var \OCP\IConfig */
private $config;

/** @var PluginManager */
private $pluginManager;

/** @var bool */
private $returnCachedSubscriptions=false;

public function __construct(BackendInterface $caldavBackend, $principalInfo) {
parent::__construct($caldavBackend, $principalInfo);
$this->l10n = \OC::$server->getL10N('dav');
$this->config = \OC::$server->getConfig();
$this->pluginManager = new PluginManager(
\OC::$server,
\OC::$server->getAppManager()
);
}

/**
Expand All @@ -66,7 +76,7 @@ public function getCalDAVBackend() {
function createExtendedCollection($name, MkCol $mkCol) {
$reservedNames = [BirthdayService::BIRTHDAY_CALENDAR_URI];

if (in_array($name, $reservedNames)) {
if (\in_array($name, $reservedNames, true) || ExternalCalendar::doesViolateReservedName($name)) {
throw new MethodNotAllowed('The resource you tried to create has a reserved name');
}

Expand Down Expand Up @@ -104,6 +114,14 @@ function getChildren() {
}
}

foreach ($this->pluginManager->getCalendarPlugins() as $calendarPlugin) {
/** @var ICalendarProvider $calendarPlugin */
$calendars = $calendarPlugin->fetchAllForCalendarHome($this->principalInfo['uri']);
foreach ($calendars as $calendar) {
$objects[] = $calendar;
}
}

return $objects;
}

Expand Down Expand Up @@ -139,7 +157,21 @@ function getChild($name) {
return new Subscription($this->caldavBackend, $subscription);
}
}
}

if (ExternalCalendar::isAppGeneratedCalendar($name)) {
[$appId, $calendarUri] = ExternalCalendar::splitAppGeneratedCalendarUri($name);

foreach ($this->pluginManager->getCalendarPlugins() as $calendarPlugin) {
/** @var ICalendarProvider $calendarPlugin */
if ($calendarPlugin->getAppId() !== $appId) {
continue;
}

if ($calendarPlugin->hasCalendarInCalendarHome($this->principalInfo['uri'], $calendarUri)) {
return $calendarPlugin->getCalendarInCalendarHome($this->principalInfo['uri'], $calendarUri);
}
}
}

throw new NotFound('Node with name \'' . $name . '\' could not be found');
Expand All @@ -155,7 +187,7 @@ function calendarSearch(array $filters, $limit=null, $offset=null) {
return $this->caldavBackend->calendarSearch($principalUri, $filters, $limit, $offset);
}


public function enableCachedSubscriptionsForThisRequest() {
$this->returnCachedSubscriptions = true;
}
Expand Down
132 changes: 132 additions & 0 deletions apps/dav/lib/CalDAV/Integration/ExternalCalendar.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
<?php
/**
* @copyright 2020, Georg Ehrke <[email protected]>
*
* @author Georg Ehrke <[email protected]>
*
* @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\Integration;

use Sabre\CalDAV;
use Sabre\DAV;

/**
* Class ExternalCalendar
*
* @package OCA\DAV\CalDAV\Integration
* @since 19.0.0
*/
abstract class ExternalCalendar implements CalDAV\ICalendar, DAV\IProperties {

/** @var string */
private const PREFIX = 'app-generated';

/**
* @var string
*
* Double dash is a valid delimiter,
* because it will always split the calendarURIs correctly:
* - our prefix contains only one dash and won't be split
* - appIds are not allowed to contain dashes as per spec:
* > must contain only lowercase ASCII characters and underscore
* - explode has a limit of three, so even if the app-generated
* calendar uri has double dashes, it won't be split
*/
private const DELIMITER = '--';

/** @var string */
private $appId;

/** @var string */
private $calendarUri;

/**
* ExternalCalendar constructor.
*
* @param string $appId
* @param string $calendarUri
*/
public function __construct(string $appId, string $calendarUri) {
$this->appId = $appId;
$this->calendarUri = $calendarUri;
}

/**
* @inheritDoc
*/
final public function getName() {
return implode(self::DELIMITER, [
self::PREFIX,
$this->appId,
$this->calendarUri,
]);
}

/**
* @inheritDoc
*/
final public function setName($name) {
throw new DAV\Exception\MethodNotAllowed('Renaming calendars is not yet supported');
}

/**
* @inheritDoc
*/
final public function createDirectory($name) {
throw new DAV\Exception\MethodNotAllowed('Creating collections in calendar objects is not allowed');

}

/**
* Checks whether the calendar uri is app-generated
*
* @param string $calendarUri
* @return bool
*/
public static function isAppGeneratedCalendar(string $calendarUri):bool {
return strpos($calendarUri, self::PREFIX) === 0 && substr_count($calendarUri, self::DELIMITER) >= 2;
}

/**
* Splits an app-generated calendar-uri into appId and calendarUri
*
* @param string $calendarUri
* @return array
*/
public static function splitAppGeneratedCalendarUri(string $calendarUri):array {
$array = array_slice(explode(self::DELIMITER, $calendarUri, 3), 1);
// Check the array has expected amount of elements
// and none of them is an empty string
if (\count($array) !== 2 || \in_array('', $array, true)) {
throw new \InvalidArgumentException('Provided calendar uri was not app-generated');
}

return $array;
}

/**
* Checks whether a calendar-name, the user wants to create, violates
* the reserved name for calendar uris
*
* @param string $calendarUri
* @return bool
*/
public static function doesViolateReservedName(string $calendarUri):bool {
return strpos($calendarUri, self::PREFIX) === 0;
}
}
Loading