From 9adf7c82a4820a159f2504befed3696da16264b8 Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Thu, 26 Mar 2020 12:53:31 +0100 Subject: [PATCH 1/3] Add Write support to OCP/ICalendar Signed-off-by: Thomas Citharel --- .../composer/composer/autoload_classmap.php | 2 + .../dav/composer/composer/autoload_static.php | 6 +- apps/dav/lib/AppInfo/Application.php | 35 ++- apps/dav/lib/CalDAV/CalendarImplV2.php | 154 +++++++++++ apps/dav/lib/CalDAV/CalendarManager.php | 25 ++ apps/dav/lib/CalDAV/CalendarObjectImplV2.php | 94 +++++++ .../tests/unit/CalDAV/CalendarImplTest.php | 4 +- .../tests/unit/CalDAV/CalendarImplV2Test.php | 189 ++++++++++++++ .../tests/unit/CalDAV/CalendarManagerTest.php | 37 ++- .../unit/CalDAV/CalendarObjectImplV2Test.php | 110 ++++++++ lib/composer/composer/autoload_classmap.php | 4 + lib/composer/composer/autoload_static.php | 12 +- lib/private/Calendar/ManagerV2.php | 151 +++++++++++ lib/private/Server.php | 7 + lib/public/Calendar/ICalendar.php | 1 + lib/public/Calendar/ICalendarObjectV2.php | 67 +++++ lib/public/Calendar/ICalendarV2.php | 93 +++++++ lib/public/Calendar/IManager.php | 1 + lib/public/Calendar/IManagerV2.php | 112 ++++++++ tests/lib/Calendar/ManagerTest.php | 2 + tests/lib/Calendar/ManagerV2Test.php | 246 ++++++++++++++++++ 21 files changed, 1339 insertions(+), 13 deletions(-) create mode 100644 apps/dav/lib/CalDAV/CalendarImplV2.php create mode 100644 apps/dav/lib/CalDAV/CalendarObjectImplV2.php create mode 100644 apps/dav/tests/unit/CalDAV/CalendarImplV2Test.php create mode 100644 apps/dav/tests/unit/CalDAV/CalendarObjectImplV2Test.php create mode 100644 lib/private/Calendar/ManagerV2.php create mode 100644 lib/public/Calendar/ICalendarObjectV2.php create mode 100644 lib/public/Calendar/ICalendarV2.php create mode 100644 lib/public/Calendar/IManagerV2.php create mode 100644 tests/lib/Calendar/ManagerV2Test.php diff --git a/apps/dav/composer/composer/autoload_classmap.php b/apps/dav/composer/composer/autoload_classmap.php index ce98cece3a192..9caec5722198c 100644 --- a/apps/dav/composer/composer/autoload_classmap.php +++ b/apps/dav/composer/composer/autoload_classmap.php @@ -47,9 +47,11 @@ 'OCA\\DAV\\CalDAV\\Calendar' => $baseDir . '/../lib/CalDAV/Calendar.php', 'OCA\\DAV\\CalDAV\\CalendarHome' => $baseDir . '/../lib/CalDAV/CalendarHome.php', 'OCA\\DAV\\CalDAV\\CalendarImpl' => $baseDir . '/../lib/CalDAV/CalendarImpl.php', + 'OCA\\DAV\\CalDAV\\CalendarImplV2' => $baseDir . '/../lib/CalDAV/CalendarImplV2.php', 'OCA\\DAV\\CalDAV\\CalendarManager' => $baseDir . '/../lib/CalDAV/CalendarManager.php', 'OCA\\DAV\\CalDAV\\CalendarObject' => $baseDir . '/../lib/CalDAV/CalendarObject.php', 'OCA\\DAV\\CalDAV\\CalendarProvider' => $baseDir . '/../lib/CalDAV/CalendarProvider.php', + 'OCA\\DAV\\CalDAV\\CalendarObjectImplV2' => $baseDir . '/../lib/CalDAV/CalendarObjectImplV2.php', 'OCA\\DAV\\CalDAV\\CalendarRoot' => $baseDir . '/../lib/CalDAV/CalendarRoot.php', 'OCA\\DAV\\CalDAV\\ICSExportPlugin\\ICSExportPlugin' => $baseDir . '/../lib/CalDAV/ICSExportPlugin/ICSExportPlugin.php', 'OCA\\DAV\\CalDAV\\IRestorable' => $baseDir . '/../lib/CalDAV/IRestorable.php', diff --git a/apps/dav/composer/composer/autoload_static.php b/apps/dav/composer/composer/autoload_static.php index a5a7d34d128d4..c07b28f4cbcfa 100644 --- a/apps/dav/composer/composer/autoload_static.php +++ b/apps/dav/composer/composer/autoload_static.php @@ -7,14 +7,14 @@ class ComposerStaticInitDAV { public static $prefixLengthsPsr4 = array ( - 'O' => + 'O' => array ( 'OCA\\DAV\\' => 8, ), ); public static $prefixDirsPsr4 = array ( - 'OCA\\DAV\\' => + 'OCA\\DAV\\' => array ( 0 => __DIR__ . '/..' . '/../lib', ), @@ -62,9 +62,11 @@ class ComposerStaticInitDAV 'OCA\\DAV\\CalDAV\\Calendar' => __DIR__ . '/..' . '/../lib/CalDAV/Calendar.php', 'OCA\\DAV\\CalDAV\\CalendarHome' => __DIR__ . '/..' . '/../lib/CalDAV/CalendarHome.php', 'OCA\\DAV\\CalDAV\\CalendarImpl' => __DIR__ . '/..' . '/../lib/CalDAV/CalendarImpl.php', + 'OCA\\DAV\\CalDAV\\CalendarImplV2' => __DIR__ . '/..' . '/../lib/CalDAV/CalendarImplV2.php', 'OCA\\DAV\\CalDAV\\CalendarManager' => __DIR__ . '/..' . '/../lib/CalDAV/CalendarManager.php', 'OCA\\DAV\\CalDAV\\CalendarObject' => __DIR__ . '/..' . '/../lib/CalDAV/CalendarObject.php', 'OCA\\DAV\\CalDAV\\CalendarProvider' => __DIR__ . '/..' . '/../lib/CalDAV/CalendarProvider.php', + 'OCA\\DAV\\CalDAV\\CalendarObjectImplV2' => __DIR__ . '/..' . '/../lib/CalDAV/CalendarObjectImplV2.php', 'OCA\\DAV\\CalDAV\\CalendarRoot' => __DIR__ . '/..' . '/../lib/CalDAV/CalendarRoot.php', 'OCA\\DAV\\CalDAV\\ICSExportPlugin\\ICSExportPlugin' => __DIR__ . '/..' . '/../lib/CalDAV/ICSExportPlugin/ICSExportPlugin.php', 'OCA\\DAV\\CalDAV\\IRestorable' => __DIR__ . '/..' . '/../lib/CalDAV/IRestorable.php', diff --git a/apps/dav/lib/AppInfo/Application.php b/apps/dav/lib/AppInfo/Application.php index 8674986262623..6f0a26d54c7db 100644 --- a/apps/dav/lib/AppInfo/Application.php +++ b/apps/dav/lib/AppInfo/Application.php @@ -42,7 +42,6 @@ use OCA\DAV\CalDAV\Reminder\NotificationProvider\PushProvider; use OCA\DAV\CalDAV\Reminder\NotificationProviderManager; use OCA\DAV\CalDAV\Reminder\Notifier; - use OCA\DAV\Capabilities; use OCA\DAV\CardDAV\CardDavBackend; use OCA\DAV\CardDAV\ContactsManager; @@ -99,6 +98,7 @@ use OCP\Calendar\IManager as ICalendarManager; use OCP\Config\BeforePreferenceDeletedEvent; use OCP\Config\BeforePreferenceSetEvent; +use OCP\Calendar\IManagerV2 as ICalendarManagerV2; use OCP\Contacts\IManager as IContactsManager; use OCP\IServerContainer; use OCP\IUser; @@ -207,13 +207,14 @@ public function boot(IBootContext $context): void { $context->injectFn([$this, 'registerHooks']); $context->injectFn([$this, 'registerContactsManager']); $context->injectFn([$this, 'registerCalendarManager']); + $context->injectFn([$this, 'registerCalendarManagerV2']); + $context->injectFn([$this, 'registerNotifier']); $context->injectFn([$this, 'registerCalendarReminders']); } - public function registerHooks(HookManager $hm, - EventDispatcherInterface $dispatcher, - IAppContainer $container, - IServerContainer $serverContainer) { + public function registerHooks() { + /** @var HookManager $hm */ + $hm = $this->getContainer()->query(HookManager::class); $hm->setup(); // first time login event setup @@ -304,6 +305,30 @@ private function setupCalendarProvider(ICalendarManager $calendarManager, $cm->setupCalendarProvider($calendarManager, $userId); } + public function registerCalendarManagerV2(ICalendarManagerV2 $calendarManager, + IAppContainer $container): void { + $calendarManager->register(function () use ($container, $calendarManager) { + $user = \OC::$server->getUserSession()->getUser(); + if ($user !== null) { + $this->setupCalendarProviderV2($calendarManager, $container, $user->getUID()); + } + }); + } + + /** + * @param ICalendarManagerV2 $calendarManager + * @param string $userId + */ + public function setupCalendarProviderV2(ICalendarManagerV2 $calendarManager, $userId) { + /** @var CalendarManager $cm */ + $cm = $this->getContainer()->query(CalendarManager::class); + $cm->setupCalendarProviderV2($calendarManager, $userId); + } + + public function registerNotifier(INotificationManager $manager): void { + $manager->registerNotifierService(Notifier::class); + } + public function registerCalendarReminders(NotificationProviderManager $manager, LoggerInterface $logger): void { try { diff --git a/apps/dav/lib/CalDAV/CalendarImplV2.php b/apps/dav/lib/CalDAV/CalendarImplV2.php new file mode 100644 index 0000000000000..5a27f49c7915c --- /dev/null +++ b/apps/dav/lib/CalDAV/CalendarImplV2.php @@ -0,0 +1,154 @@ + + * + * @author Thomas Citharel + * + * @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 . + * + */ + +namespace OCA\DAV\CalDAV; + +use OCP\Calendar\ICalendarObjectV2; +use OCP\Calendar\ICalendarV2; +use OCP\Constants; +use Sabre\DAV\Exception\BadRequest; +use Sabre\VObject\Component\VCalendar; +use Sabre\VObject\Reader; +use Sabre\VObject\UUIDUtil; + +class CalendarImplV2 implements ICalendarV2 { + + /** @var CalDavBackend */ + private $backend; + + /** @var Calendar */ + private $calendar; + + /** @var array */ + private $calendarInfo; + + /** + * CalendarImpl constructor. + * + * @param Calendar $calendar + * @param array $calendarInfo + * @param CalDavBackend $backend + */ + public function __construct(Calendar $calendar, array $calendarInfo, + CalDavBackend $backend) { + $this->calendar = $calendar; + $this->calendarInfo = $calendarInfo; + $this->backend = $backend; + } + + /** + * @return string defining the technical unique key + * @since 19.0.0 + */ + public function getKey(): string { + return (string) $this->calendarInfo['id']; + } + + /** + * In comparison to getKey() this function returns a human readable (maybe translated) name + * @return null|string + * @since 19.0.0 + */ + public function getDisplayName(): string { + return $this->calendarInfo['{DAV:}displayname']; + } + + /** + * Calendar color + * @return null|string + * @since 19.0.0 + */ + public function getDisplayColor(): string { + return $this->calendarInfo['{http://apple.com/ns/ical/}calendar-color']; + } + + /** + * @param string $pattern which should match within the $searchProperties + * @param array $searchProperties defines the properties within the query pattern should match + * @param array $options - optional parameters: + * ['timerange' => ['start' => new DateTime(...), 'end' => new DateTime(...)]] + * @param integer|null $limit - limit number of search results + * @param integer|null $offset - offset for paging of search results + * @return array an array of events/journals/todos which are arrays of key-value-pairs + * @since 19.0.0 + */ + public function search($pattern, array $searchProperties=[], array $options=[], int $limit = null, int $offset = null): array { + return $this->backend->search($this->calendarInfo, $pattern, + $searchProperties, $options, $limit, $offset); + } + + /** + * @return bool + * @since 19.0.0 + */ + public function isWriteable(): bool { + $permissions = $this->calendar->getACL(); + $result = 0; + foreach ($permissions as $permission) { + switch ($permission['privilege']) { + case '{DAV:}read': + $result |= Constants::PERMISSION_READ; + break; + case '{DAV:}write': + $result |= Constants::PERMISSION_CREATE; + $result |= Constants::PERMISSION_UPDATE; + break; + case '{DAV:}all': + $result |= Constants::PERMISSION_ALL; + break; + } + } + + return $result > Constants::PERMISSION_READ; + } + + /** + * @param string $uri + * @return ICalendarObjectV2|null + * @since 19.0.0 + */ + public function getByUri(string $uri): ?ICalendarObjectV2 { + if ($calendarObjectData = $this->backend->getCalendarObject($this->getKey(), $uri)) { + if (isset($calendarObjectData['calendardata'])) { + $calendarData = Reader::read($calendarObjectData['calendardata']); + if ($calendarData instanceof VCalendar) { + return new CalendarObjectImplV2($calendarObjectData['calendarid'], $calendarObjectData['uri'], $calendarData, $this->backend); + } + } + } + return null; + } + + /** + * @param VCalendar $vObject + * @return ICalendarObjectV2 + * @throws BadRequest + * @since 19.0.0 + */ + public function create(VCalendar $vObject): ICalendarObjectV2 { + CalendarObjectImplV2::validateCalendarData($vObject); + $uuid = UUIDUtil::getUUID() . '.ics'; + $this->backend->createCalendarObject($this->getKey(), $uuid, $vObject->serialize()); + return $this->getByUri($uuid); + } +} diff --git a/apps/dav/lib/CalDAV/CalendarManager.php b/apps/dav/lib/CalDAV/CalendarManager.php index daa96a5139202..62e426058d65c 100644 --- a/apps/dav/lib/CalDAV/CalendarManager.php +++ b/apps/dav/lib/CalDAV/CalendarManager.php @@ -25,6 +25,7 @@ namespace OCA\DAV\CalDAV; use OCP\Calendar\IManager; +use OCP\Calendar\IManagerV2; use OCP\IConfig; use OCP\IL10N; use Psr\Log\LoggerInterface; @@ -66,6 +67,15 @@ public function setupCalendarProvider(IManager $cm, $userId) { $this->register($cm, $calendars); } + /** + * @param IManagerV2 $cm + * @param string $userId + */ + public function setupCalendarProviderV2(IManagerV2 $cm, $userId) { + $calendars = $this->backend->getCalendarsForUser("principals/users/$userId"); + $this->registerV2($cm, $calendars); + } + /** * @param IManager $cm * @param array $calendars @@ -80,4 +90,19 @@ private function register(IManager $cm, array $calendars) { )); } } + + /** + * @param IManagerV2 $cm + * @param array $calendars + */ + private function registerV2(IManagerV2 $cm, array $calendars) { + foreach ($calendars as $calendarInfo) { + $calendar = new Calendar($this->backend, $calendarInfo, $this->l10n, $this->config); + $cm->registerCalendar(new CalendarImplV2( + $calendar, + $calendarInfo, + $this->backend + )); + } + } } diff --git a/apps/dav/lib/CalDAV/CalendarObjectImplV2.php b/apps/dav/lib/CalDAV/CalendarObjectImplV2.php new file mode 100644 index 0000000000000..6a39a0b8e4c80 --- /dev/null +++ b/apps/dav/lib/CalDAV/CalendarObjectImplV2.php @@ -0,0 +1,94 @@ + + * + * @author Thomas Citharel + * + * @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 . + * + */ + +namespace OCA\DAV\CalDAV; + +use InvalidArgumentException; +use OCP\Calendar\ICalendarObjectV2; +use Sabre\VObject\Component\VCalendar; + +class CalendarObjectImplV2 implements ICalendarObjectV2 { + + /** @var int */ + private $calendarId; + + /** @var string */ + private $uri; + + /** @var VCalendar */ + private $data; + + /** @var CalDavBackend */ + private $backend; + + /** + * CalendarImpl constructor. + * + * @param int $calendarId + * @param string $uri + * @param VCalendar $data + * @param CalDavBackend $backend + */ + public function __construct(int $calendarId, string $uri, VCalendar $data, CalDavBackend $backend) { + $this->calendarId = $calendarId; + $this->uri = $uri; + $this->data = $data; + $this->backend = $backend; + } + + public function getCalendarKey(): string { + return (string) $this->calendarId; + } + + public function getUri(): string { + return $this->uri; + } + + public function getVObject(): VCalendar { + return $this->data; + } + + public function update(VCalendar $data): void { + self::validateCalendarData($data); + $serializedData = $data->serialize(); + $this->backend->updateCalendarObject($this->getCalendarKey(), $this->getUri(), $serializedData); + $this->data = $data; + } + + public function delete(): void { + $this->backend->deleteCalendarObject($this->getCalendarKey(), $this->getUri()); + } + + /** + * @param VCalendar $data + * @throws InvalidArgumentException + */ + public static function validateCalendarData(VCalendar $data): void { + $result = $data->validate(VCalendar::PROFILE_CALDAV); + foreach ($result as $warning) { + if ($warning['level'] === 3) { + throw new InvalidArgumentException($warning['message']); + } + } + } +} diff --git a/apps/dav/tests/unit/CalDAV/CalendarImplTest.php b/apps/dav/tests/unit/CalDAV/CalendarImplTest.php index 5adb7041e8b5f..631fc861ece0b 100644 --- a/apps/dav/tests/unit/CalDAV/CalendarImplTest.php +++ b/apps/dav/tests/unit/CalDAV/CalendarImplTest.php @@ -38,11 +38,13 @@ use Sabre\VObject\Component\VEvent; use Sabre\VObject\ITip\Message; use Sabre\VObject\Reader; +use PHPUnit\Framework\MockObject\MockObject; +use Test\TestCase; /** * @group DB */ -class CalendarImplTest extends \Test\TestCase { +class CalendarImplTest extends TestCase { /** @var CalendarImpl */ private $calendarImpl; diff --git a/apps/dav/tests/unit/CalDAV/CalendarImplV2Test.php b/apps/dav/tests/unit/CalDAV/CalendarImplV2Test.php new file mode 100644 index 0000000000000..d6bf181a7f9d8 --- /dev/null +++ b/apps/dav/tests/unit/CalDAV/CalendarImplV2Test.php @@ -0,0 +1,189 @@ + + * + * @author Georg Ehrke + * @author Roeland Jago Douma + * + * @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 . + * + */ + +namespace OCA\DAV\Tests\unit\CalDAV; + +use InvalidArgumentException; +use OCA\DAV\CalDAV\CalDavBackend; +use OCA\DAV\CalDAV\Calendar; +use OCA\DAV\CalDAV\CalendarImplV2; +use OCA\DAV\CalDAV\CalendarObjectImplV2; +use PHPUnit\Framework\MockObject\MockObject; +use Sabre\VObject\Component\VCalendar; +use Test\TestCase; + +class CalendarImplV2Test extends TestCase { + + /** @var CalendarImplV2 */ + private $calendarImpl; + + /** @var Calendar | MockObject */ + private $calendar; + + /** @var array */ + private $calendarInfo; + + /** @var CalDavBackend | MockObject */ + private $backend; + + protected function setUp(): void { + parent::setUp(); + + $this->calendar = $this->createMock(Calendar::class); + $this->calendarInfo = [ + 'id' => '4', + '{DAV:}displayname' => 'user readable name 123', + '{http://apple.com/ns/ical/}calendar-color' => '#AABBCC', + ]; + $this->backend = $this->createMock(CalDavBackend::class); + + $this->calendarImpl = new CalendarImplV2($this->calendar, + $this->calendarInfo, $this->backend); + } + + + public function testGetKey() { + $this->assertEquals($this->calendarImpl->getKey(), '4'); + } + + public function testGetDisplayname() { + $this->assertEquals($this->calendarImpl->getDisplayName(),'user readable name 123'); + } + + public function testGetDisplayColor() { + $this->assertEquals($this->calendarImpl->getDisplayColor(), '#AABBCC'); + } + + public function testSearch() { + $this->backend->expects($this->once()) + ->method('search') + ->with($this->calendarInfo, 'abc', ['def'], ['ghi'], 42, 1337) + ->willReturn(['SEARCHRESULTS']); + + $result = $this->calendarImpl->search('abc', ['def'], ['ghi'], 42, 1337); + $this->assertEquals($result, ['SEARCHRESULTS']); + } + + /** + * @dataProvider dataTestIsWritable + */ + public function testGetIsWritable(array $privileges, bool $result) { + $this->calendar->expects($this->once()) + ->method('getACL') + ->with() + ->willReturn($privileges); + + $this->assertEquals($result, $this->calendarImpl->isWriteable()); + } + + public function dataTestIsWritable(): array { + return [ + [ + [ + ['privilege' => '{DAV:}read'] + ], + false + ], + [ + [ + ['privilege' => '{DAV:}write'] + ], + true + ], + [ + [ + ['privilege' => '{DAV:}read'], + ['privilege' => '{DAV:}write'] + ], + true + ], + [ + [ + ['privilege' => '{DAV:}all'] + ], + true + ] + ]; + } + + public function testGetByUri() { + $vcalendar = new VCalendar(); + $calendarObjectData = ['id' => 'someid', 'calendarid' => '4', 'uri' => 'someuri.ics', 'calendardata' => $vcalendar->serialize()]; + $calendarObject = new CalendarObjectImplV2( + $calendarObjectData['calendarid'], + $calendarObjectData['uri'], + $vcalendar, + $this->backend); + + $this->backend->expects($this->once()) + ->method('getCalendarObject') + ->with($calendarObjectData['calendarid'], $calendarObjectData['uri']) + ->willReturn($calendarObjectData); + $foundObject = $this->calendarImpl->getByUri($calendarObjectData['uri']); + $this->assertEquals($calendarObject->getCalendarKey(), $foundObject->getCalendarKey()); + $this->assertEquals($calendarObject->getUri(), $foundObject->getUri()); + // Asserting equality for VObject PHP objects fails on parent references, so we serialize them + $this->assertEquals($calendarObject->getVObject()->serialize(), $foundObject->getVObject()->serialize()); + } + + public function testGetByUriNotFound() { + $this->backend->expects($this->once()) + ->method('getCalendarObject') + ->willReturn(null); + $this->assertEquals(null, $this->calendarImpl->getByUri('uri')); + } + + public function testCreateCalendarObject() { + $calendarObjectDataObject = new VCalendar([ + 'VEVENT' => [ + 'SUMMARY' => 'Birthday party!', + 'DTSTART' => new \DateTime('2016-07-04 21:00:00'), + 'DTEND' => new \DateTime('2016-07-05 03:00:00') + ] + ]); + $calendarObjectData = ['id' => 'someid', 'calendarid' => '4', 'uri' => 'someuri.ics', 'calendardata' => $calendarObjectDataObject->serialize()]; + $calendarObject = new CalendarObjectImplV2($calendarObjectData['calendarid'], $calendarObjectData['uri'], $calendarObjectDataObject, $this->backend); + $this->backend->expects($this->once()) + ->method('createCalendarObject') + ->with( + $calendarObjectData['calendarid'], + $this->anything(), + $calendarObjectDataObject->serialize() + ); + $this->backend->expects($this->once()) + ->method('getCalendarObject') + ->willReturn($calendarObjectData); + $createdCalendarObject = $this->calendarImpl->create($calendarObjectDataObject); + $this->assertEquals($calendarObject->getCalendarKey(), $createdCalendarObject->getCalendarKey()); + $this->assertEquals($calendarObject->getUri(), $createdCalendarObject->getUri()); + // Asserting equality for VObject PHP objects fails on parent references (and therefore on the whole CalendarImplV2 object), so we serialize them + $this->assertEquals($calendarObject->getVObject()->serialize(), $createdCalendarObject->getVObject()->serialize()); + } + + public function testCreateCalendarObjectWithBadCalendarData() { + $vobject = new VCalendar([]); + $this->expectException(InvalidArgumentException::class); + $this->calendarImpl->create($vobject); + } +} diff --git a/apps/dav/tests/unit/CalDAV/CalendarManagerTest.php b/apps/dav/tests/unit/CalDAV/CalendarManagerTest.php index f16a06f953dbb..059e5af8f1f23 100644 --- a/apps/dav/tests/unit/CalDAV/CalendarManagerTest.php +++ b/apps/dav/tests/unit/CalDAV/CalendarManagerTest.php @@ -6,6 +6,7 @@ * @author Georg Ehrke * @author Morris Jobke * @author Roeland Jago Douma + * @author Thomas Citharel * * @license GNU AGPL version 3 or any later version * @@ -26,16 +27,20 @@ namespace OCA\DAV\Tests\unit\CalDAV; use OC\Calendar\Manager; +use OC\Calendar\ManagerV2; use OCA\DAV\CalDAV\CalDavBackend; use OCA\DAV\CalDAV\CalendarImpl; +use OCA\DAV\CalDAV\CalendarImplV2; use OCA\DAV\CalDAV\CalendarManager; use OCP\Calendar\IManager; +use OCP\Calendar\IManagerV2; use OCP\IConfig; use OCP\IL10N; use PHPUnit\Framework\MockObject\MockObject; use Psr\Log\LoggerInterface; +use Test\TestCase; -class CalendarManagerTest extends \Test\TestCase { +class CalendarManagerTest extends TestCase { /** @var CalDavBackend | MockObject */ private $backend; @@ -95,4 +100,34 @@ public function testSetupCalendarProvider() { $this->manager->setupCalendarProvider($calendarManager, 'user123'); } + + public function testSetupCalendarProviderV2() { + $this->backend->expects($this->once()) + ->method('getCalendarsForUser') + ->with('principals/users/user123') + ->willReturn([ + ['id' => 123, 'uri' => 'blablub1'], + ['id' => 456, 'uri' => 'blablub2'], + ]); + + /** @var IManagerV2 | MockObject $calendarManager */ + $calendarManager = $this->createMock(ManagerV2::class); + $calendarManager->expects($this->at(0)) + ->method('registerCalendar') + ->willReturnCallback(function() { + $parameter = func_get_arg(0); + $this->assertInstanceOf(CalendarImplV2::class, $parameter); + $this->assertEquals(123, $parameter->getKey()); + }); + + $calendarManager->expects($this->at(1)) + ->method('registerCalendar') + ->willReturnCallback(function() { + $parameter = func_get_arg(0); + $this->assertInstanceOf(CalendarImplV2::class, $parameter); + $this->assertEquals(456, $parameter->getKey()); + }); + + $this->manager->setupCalendarProviderV2($calendarManager, 'user123'); + } } diff --git a/apps/dav/tests/unit/CalDAV/CalendarObjectImplV2Test.php b/apps/dav/tests/unit/CalDAV/CalendarObjectImplV2Test.php new file mode 100644 index 0000000000000..b39ba37ea0e65 --- /dev/null +++ b/apps/dav/tests/unit/CalDAV/CalendarObjectImplV2Test.php @@ -0,0 +1,110 @@ + + * + * @author Georg Ehrke + * @author Roeland Jago Douma + * + * @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 . + * + */ + +namespace OCA\DAV\Tests\unit\CalDAV; + +use InvalidArgumentException; +use OCA\DAV\CalDAV\CalDavBackend; +use OCA\DAV\CalDAV\CalendarObjectImplV2; +use PHPUnit\Framework\MockObject\MockObject; +use Sabre\VObject\Component\VCalendar; +use Test\TestCase; + +class CalendarObjectImplV2Test extends TestCase { + + /** @var array */ + private $calendarObject; + + /** @var CalDavBackend | MockObject */ + private $backend; + /** + * @var CalendarObjectImplV2 + */ + private $calendarObjectImpl; + + protected function setUp(): void { + parent::setUp(); + + $calendarObjectData = new VCalendar([ + 'VEVENT' => [ + 'SUMMARY' => 'Birthday party!', + 'DTSTART' => new \DateTime('2016-07-04 21:00:00'), + 'DTEND' => new \DateTime('2016-07-05 03:00:00') + ] + ]); + + $this->calendarObject = [ + 'calendarid' => '4', + 'uri' => 'something.ics', + 'calendardata' => $calendarObjectData->serialize() + ]; + $this->backend = $this->createMock(CalDavBackend::class); + + $this->calendarObjectImpl = new CalendarObjectImplV2($this->calendarObject['calendarid'], $this->calendarObject['uri'], $calendarObjectData, $this->backend); + } + + + public function testGetCalendarKey() { + $this->assertEquals($this->calendarObjectImpl->getCalendarKey(), $this->calendarObject['calendarid']); + } + + public function testGetUri() { + $this->assertEquals($this->calendarObjectImpl->getUri(),$this->calendarObject['uri']); + } + + public function testGetVObject() { + $this->assertEquals($this->calendarObjectImpl->getVObject()->serialize(), $this->calendarObject['calendardata']); + } + + public function testUpdate() { + $vobject = new VCalendar([ + 'VEVENT' => [ + 'SUMMARY' => 'Updated birthday party!', + 'DTSTART' => new \DateTime('2020-07-04 21:00:00'), + 'DTEND' => new \DateTime('2020-07-05 03:00:00') + ] + ]); + + $this->backend->expects($this->once()) + ->method('updateCalendarObject') + ->with($this->calendarObject['calendarid'], $this->calendarObject['uri'], $vobject->serialize()); + + $this->calendarObjectImpl->update($vobject); + $this->assertEquals($this->calendarObjectImpl->getVObject()->serialize(), $vobject->serialize()); + } + + public function testUpdateWithBadCalendarData() { + $vobject = new VCalendar([]); + $this->expectException(InvalidArgumentException::class); + $this->calendarObjectImpl->update($vobject); + } + + public function testDelete() { + $this->backend->expects($this->once()) + ->method('deleteCalendarObject') + ->with($this->calendarObject['calendarid'], $this->calendarObject['uri']); + + $this->calendarObjectImpl->delete(); + } +} diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index a92b208ba7a5f..548860d94add7 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -124,7 +124,10 @@ 'OCP\\Calendar\\ICalendarQuery' => $baseDir . '/lib/public/Calendar/ICalendarQuery.php', 'OCP\\Calendar\\ICreateFromString' => $baseDir . '/lib/public/Calendar/ICreateFromString.php', 'OCP\\Calendar\\IHandleImipMessage' => $baseDir . '/lib/public/Calendar/IHandleImipMessage.php', + 'OCP\\Calendar\\ICalendarObjectV2' => $baseDir . '/lib/public/Calendar/ICalendarObjectV2.php', + 'OCP\\Calendar\\ICalendarV2' => $baseDir . '/lib/public/Calendar/ICalendarV2.php', 'OCP\\Calendar\\IManager' => $baseDir . '/lib/public/Calendar/IManager.php', + 'OCP\\Calendar\\IManagerV2' => $baseDir . '/lib/public/Calendar/IManagerV2.php', 'OCP\\Calendar\\IMetadataProvider' => $baseDir . '/lib/public/Calendar/IMetadataProvider.php', 'OCP\\Calendar\\Resource\\IBackend' => $baseDir . '/lib/public/Calendar/Resource/IBackend.php', 'OCP\\Calendar\\Resource\\IManager' => $baseDir . '/lib/public/Calendar/Resource/IManager.php', @@ -820,6 +823,7 @@ 'OC\\Cache\\File' => $baseDir . '/lib/private/Cache/File.php', 'OC\\Calendar\\CalendarQuery' => $baseDir . '/lib/private/Calendar/CalendarQuery.php', 'OC\\Calendar\\Manager' => $baseDir . '/lib/private/Calendar/Manager.php', + 'OC\\Calendar\\ManagerV2' => $baseDir . '/lib/private/Calendar/ManagerV2.php', 'OC\\Calendar\\Resource\\Manager' => $baseDir . '/lib/private/Calendar/Resource/Manager.php', 'OC\\Calendar\\Room\\Manager' => $baseDir . '/lib/private/Calendar/Room/Manager.php', 'OC\\CapabilitiesManager' => $baseDir . '/lib/private/CapabilitiesManager.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index c0ff8e6d9a4b3..1efe80572c07c 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -11,7 +11,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 ); public static $prefixLengthsPsr4 = array ( - 'O' => + 'O' => array ( 'OC\\Core\\' => 8, 'OC\\' => 3, @@ -20,15 +20,15 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 ); public static $prefixDirsPsr4 = array ( - 'OC\\Core\\' => + 'OC\\Core\\' => array ( 0 => __DIR__ . '/../../..' . '/core', ), - 'OC\\' => + 'OC\\' => array ( 0 => __DIR__ . '/../../..' . '/lib/private', ), - 'OCP\\' => + 'OCP\\' => array ( 0 => __DIR__ . '/../../..' . '/lib/public', ), @@ -157,7 +157,10 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OCP\\Calendar\\ICalendarQuery' => __DIR__ . '/../../..' . '/lib/public/Calendar/ICalendarQuery.php', 'OCP\\Calendar\\ICreateFromString' => __DIR__ . '/../../..' . '/lib/public/Calendar/ICreateFromString.php', 'OCP\\Calendar\\IHandleImipMessage' => __DIR__ . '/../../..' . '/lib/public/Calendar/IHandleImipMessage.php', + 'OCP\\Calendar\\ICalendarObjectV2' => __DIR__ . '/../../..' . '/lib/public/Calendar/ICalendarObjectV2.php', + 'OCP\\Calendar\\ICalendarV2' => __DIR__ . '/../../..' . '/lib/public/Calendar/ICalendarV2.php', 'OCP\\Calendar\\IManager' => __DIR__ . '/../../..' . '/lib/public/Calendar/IManager.php', + 'OCP\\Calendar\\IManagerV2' => __DIR__ . '/../../..' . '/lib/public/Calendar/IManagerV2.php', 'OCP\\Calendar\\IMetadataProvider' => __DIR__ . '/../../..' . '/lib/public/Calendar/IMetadataProvider.php', 'OCP\\Calendar\\Resource\\IBackend' => __DIR__ . '/../../..' . '/lib/public/Calendar/Resource/IBackend.php', 'OCP\\Calendar\\Resource\\IManager' => __DIR__ . '/../../..' . '/lib/public/Calendar/Resource/IManager.php', @@ -853,6 +856,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OC\\Cache\\File' => __DIR__ . '/../../..' . '/lib/private/Cache/File.php', 'OC\\Calendar\\CalendarQuery' => __DIR__ . '/../../..' . '/lib/private/Calendar/CalendarQuery.php', 'OC\\Calendar\\Manager' => __DIR__ . '/../../..' . '/lib/private/Calendar/Manager.php', + 'OC\\Calendar\\ManagerV2' => __DIR__ . '/../../..' . '/lib/private/Calendar/ManagerV2.php', 'OC\\Calendar\\Resource\\Manager' => __DIR__ . '/../../..' . '/lib/private/Calendar/Resource/Manager.php', 'OC\\Calendar\\Room\\Manager' => __DIR__ . '/../../..' . '/lib/private/Calendar/Room/Manager.php', 'OC\\CapabilitiesManager' => __DIR__ . '/../../..' . '/lib/private/CapabilitiesManager.php', diff --git a/lib/private/Calendar/ManagerV2.php b/lib/private/Calendar/ManagerV2.php new file mode 100644 index 0000000000000..9e2fc492b5aa8 --- /dev/null +++ b/lib/private/Calendar/ManagerV2.php @@ -0,0 +1,151 @@ + + * + * @author Thomas Citharel + * + * @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 . + * + */ + +namespace OC\Calendar; + +use OCP\Calendar\ICalendarV2; +use OCP\Calendar\IManagerV2; + +class ManagerV2 implements IManagerV2 { + + /** + * @var ICalendarV2[] holds all registered calendars + */ + private $calendars=[]; + + /** + * @var \Closure[] to call to load/register calendar providers + */ + private $calendarLoaders=[]; + + /** + * This function is used to search and find objects within the user's calendars. + * In case $pattern is empty all events/journals/todos will be returned. + * + * @param string $pattern which should match within the $searchProperties + * @param array $searchProperties defines the properties within the query pattern should match + * @param array $options - optional parameters: + * ['timerange' => ['start' => new DateTime(...), 'end' => new DateTime(...)]] + * @param integer|null $limit - limit number of search results + * @param integer|null $offset - offset for paging of search results + * @return array an array of events/journals/todos which are arrays of arrays of key-value-pairs + * @since 13.0.0 + */ + public function search($pattern, array $searchProperties=[], array $options=[], int $limit = null, int $offset=null): array { + $this->loadCalendars(); + $result = []; + foreach($this->calendars as $calendar) { + $r = $calendar->search($pattern, $searchProperties, $options, $limit, $offset); + foreach($r as $o) { + $o['calendar-key'] = $calendar->getKey(); + $result[] = $o; + } + } + + return $result; + } + + /** + * Check if calendars are available + * + * @return bool true if enabled, false if not + * @since 13.0.0 + */ + public function isEnabled(): bool { + return !empty($this->calendars) || !empty($this->calendarLoaders); + } + + /** + * Registers a calendar + * + * @param ICalendarV2 $calendar + * @return void + * @since 13.0.0 + */ + public function registerCalendar(ICalendarV2 $calendar): void { + $this->calendars[$calendar->getKey()] = $calendar; + } + + /** + * Unregisters a calendar + * + * @param ICalendarV2 $calendar + * @return void + * @since 13.0.0 + */ + public function unregisterCalendar(ICalendarV2 $calendar): void { + unset($this->calendars[$calendar->getKey()]); + } + + /** + * In order to improve lazy loading a closure can be registered which will be called in case + * calendars are actually requested + * + * @param \Closure $callable + * @return void + * @since 13.0.0 + */ + public function register(\Closure $callable): void { + $this->calendarLoaders[] = $callable; + } + + /** + * @return ICalendarV2[] + * @since 13.0.0 + */ + public function getCalendars(): array { + $this->loadCalendars(); + + return array_values($this->calendars); + } + + /** + * removes all registered calendar instances + * @return void + * @since 13.0.0 + */ + public function clear(): void { + $this->calendars = []; + $this->calendarLoaders = []; + } + + /** + * loads all calendars + */ + private function loadCalendars() { + foreach($this->calendarLoaders as $callable) { + $callable($this); + } + $this->calendarLoaders = []; + } + + /** + * Get a calendar by it's key + * + * @param string $key + * @return ICalendarV2|null + */ + public function getCalendar(string $key): ?ICalendarV2 { + return isset($this->calendars[$key]) ? $this->calendars[$key] : null; + } +} diff --git a/lib/private/Server.php b/lib/private/Server.php index d8857b4efc6c1..2307c44657927 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -1468,6 +1468,13 @@ public function getCalendarManager() { return $this->get(\OC\Calendar\Manager::class); } + /** + * @return \OCP\Calendar\IManagerV2 + */ + public function getCalendarManagerV2() { + return $this->query(\OC\Calendar\ManagerV2::class); + } + /** * @return \OCP\Calendar\Resource\IManager * @deprecated 20.0.0 diff --git a/lib/public/Calendar/ICalendar.php b/lib/public/Calendar/ICalendar.php index c6037690f6572..1461027b7367b 100644 --- a/lib/public/Calendar/ICalendar.php +++ b/lib/public/Calendar/ICalendar.php @@ -30,6 +30,7 @@ * Interface ICalendar * * @since 13.0.0 + * @deprecated 20.0.0 */ interface ICalendar { /** diff --git a/lib/public/Calendar/ICalendarObjectV2.php b/lib/public/Calendar/ICalendarObjectV2.php new file mode 100644 index 0000000000000..6333044cce925 --- /dev/null +++ b/lib/public/Calendar/ICalendarObjectV2.php @@ -0,0 +1,67 @@ + + * + * @author Thomas Citharel + * + * @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 . + * + */ + +namespace OCP\Calendar; + +use Sabre\VObject\Component\VCalendar; + +/** + * Interface ICalendarObjectV2 + * + * @package OCP + * @since 20.0.0 + */ +interface ICalendarObjectV2 { + + /** + * @return string defining the technical unique key + * @since 20.0.0 + */ + public function getCalendarKey(): string; + + /** + * @return string calendar object unique URI + * @since 20.0.0 + */ + public function getUri(): string; + + /** + * @return VCalendar the calendar object data + * @since 20.0.0 + */ + public function getVObject(): VCalendar; + + /** + * Update calendar object data + * + * @param VCalendar $data + * @since 20.0.0 + */ + public function update(VCalendar $data): void; + + /** + * Delete calendar object + * @since 20.0.0 + */ + public function delete(): void; +} diff --git a/lib/public/Calendar/ICalendarV2.php b/lib/public/Calendar/ICalendarV2.php new file mode 100644 index 0000000000000..6265c1e811a1b --- /dev/null +++ b/lib/public/Calendar/ICalendarV2.php @@ -0,0 +1,93 @@ + + * + * @author Thomas Citharel + * + * @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 . + * + */ + +namespace OCP\Calendar; + +use Sabre\VObject\Component\VCalendar; + +/** + * Interface ICalendarV2 + * + * @package OCP + * @since 20.0.0 + */ +interface ICalendarV2 { + + /** + * @return string defining the technical unique key + * @since 20.0.0 + */ + public function getKey(): string; + + /** + * In comparison to getKey() this function returns a human readable (maybe translated) name + * @return null|string + * @since 20.0.0 + */ + public function getDisplayName(): ?string; + + /** + * Calendar color + * @return null|string + * @since 20.0.0 + */ + public function getDisplayColor(): ?string; + + /** + * Whether the calendar is writeable + * + * @return bool + * @since 20.0.0 + */ + public function isWriteable(): bool; + + /** + * Get a calendar object by it's URI + * + * @param string $uri + * @return ICalendarObjectV2|null + * @since 20.0.0 + */ + public function getByUri(string $uri): ?ICalendarObjectV2; + + /** + * @param string $pattern which should match within the $searchProperties + * @param array $searchProperties defines the properties within the query pattern should match + * @param array $options - optional parameters: + * ['timerange' => ['start' => new DateTime(...), 'end' => new DateTime(...)]] + * @param integer|null $limit - limit number of search results + * @param integer|null $offset - offset for paging of search results + * @return ICalendarObjectV2[] + * @since 20.0.0 + */ + public function search(string $pattern, array $searchProperties=[], array $options=[], int $limit=null, int $offset=null): ?array; + + /** + * Create a new calendar object into a calendar. Accepts a VCalendar object for calendar data. + * + * @param VCalendar $vObject + * @return ICalendarObjectV2 + * @since 20.0.0 + */ + public function create(VCalendar $vObject): ICalendarObjectV2; +} diff --git a/lib/public/Calendar/IManager.php b/lib/public/Calendar/IManager.php index bd23e67bebc93..f0f76179099bb 100644 --- a/lib/public/Calendar/IManager.php +++ b/lib/public/Calendar/IManager.php @@ -56,6 +56,7 @@ * ] * * @since 13.0.0 + * @deprecated 20.0.0 */ interface IManager { /** diff --git a/lib/public/Calendar/IManagerV2.php b/lib/public/Calendar/IManagerV2.php new file mode 100644 index 0000000000000..6e43961577ce1 --- /dev/null +++ b/lib/public/Calendar/IManagerV2.php @@ -0,0 +1,112 @@ + + * + * @author Thomas Citharel + * + * @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 . + * + */ + +namespace OCP\Calendar; + +use Closure; + +/** + * This class provides access to the Nextcloud CalDAV backend. + * Use this class exclusively if you want to access and manage calendars. + * + * Events/Journals/Todos are expressed as instances of ICalendarObjectV2 + * + * @since 20.0.0 + */ +interface IManagerV2 { + + /** + * Check if calendars are available + * + * @return bool true if enabled, false if not + * @since 20.0.0 + */ + public function isEnabled(): bool; + + /** + * Get all registered calendars + * + * @return ICalendarV2[] + * @since 20.0.0 + */ + public function getCalendars(): array; + + /** + * Get a calendar by it's key + * + * @param string $key + * @return ICalendarV2|null + * @since 20.0.0 + */ + public function getCalendar(string $key): ?ICalendarV2; + + /** + * This function is used to search and find objects within the user's calendars. + * In case $pattern is empty all events/journals/todos will be returned. + * + * @param string $pattern which should match within the $searchProperties + * @param array $searchProperties defines the properties within the query pattern should match + * @param array $options - optional parameters: + * ['timerange' => ['start' => new DateTime(...), 'end' => new DateTime(...)]] + * @param integer|null $limit - limit number of search results + * @param integer|null $offset - offset for paging of search results + * @return ICalendarObjectV2[] + * @since 20.0.0 + */ + public function search(string $pattern, array $searchProperties=[], array $options=[], int $limit=null, int $offset=null): ?array; + + /** + * Registers a calendar + * + * @param ICalendarV2 $calendar + * @return void + * @since 20.0.0 + */ + public function registerCalendar(ICalendarV2 $calendar): void; + + /** + * Unregisters a calendar + * + * @param ICalendarV2 $calendar + * @return void + * @since 20.0.0 + */ + public function unregisterCalendar(ICalendarV2 $calendar): void; + + /** + * In order to improve lazy loading a closure can be registered which will be called in case + * calendars are actually requested + * + * @param Closure $callable + * @return void + * @since 20.0.0 + */ + public function register(Closure $callable): void; + + /** + * removes all registered calendar instances + * @return void + * @since 20.0.0 + */ + public function clear(): void; +} diff --git a/tests/lib/Calendar/ManagerTest.php b/tests/lib/Calendar/ManagerTest.php index fcf6ab3a58894..f2b82a38a0678 100644 --- a/tests/lib/Calendar/ManagerTest.php +++ b/tests/lib/Calendar/ManagerTest.php @@ -3,6 +3,7 @@ * @copyright 2017, Georg Ehrke * * @author Georg Ehrke + * @author Thomas Citharel * * @license GNU AGPL version 3 or any later version * @@ -34,6 +35,7 @@ use Psr\Log\LoggerInterface; use Sabre\VObject\Document; use Sabre\VObject\Reader; +use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; /* diff --git a/tests/lib/Calendar/ManagerV2Test.php b/tests/lib/Calendar/ManagerV2Test.php new file mode 100644 index 0000000000000..f313265a1b85b --- /dev/null +++ b/tests/lib/Calendar/ManagerV2Test.php @@ -0,0 +1,246 @@ + + * + * @author Georg Ehrke + * @author Thomas Citharel + * + * @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 . + * + */ + +namespace Test\Calendar; + +use OC\Calendar\ManagerV2; +use OCP\Calendar\ICalendarV2; +use PHPUnit\Framework\MockObject\MockObject; +use Test\TestCase; + +class ManagerV2Test extends TestCase { + + /** @var ManagerV2 */ + private $manager; + + protected function setUp(): void { + parent::setUp(); + + $this->manager = new ManagerV2(); + } + + /** + * @dataProvider searchProvider + */ + public function testSearch($search1, $search2, $expected) { + /** @var ICalendarV2 | MockObject $calendar1 */ + $calendar1 = $this->createMock(ICalendarV2::class); + $calendar1->method('getKey')->willReturn('simple:1'); + $calendar1->expects($this->once()) + ->method('search') + ->with('', [], [], null, null) + ->willReturn($search1); + + /** @var ICalendarV2 | MockObject $calendar2 */ + $calendar2 = $this->createMock(ICalendarV2::class); + $calendar2->method('getKey')->willReturn('simple:2'); + $calendar2->expects($this->once()) + ->method('search') + ->with('', [], [], null, null) + ->willReturn($search2); + + $this->manager->registerCalendar($calendar1); + $this->manager->registerCalendar($calendar2); + + $result = $this->manager->search(''); + $this->assertEquals($expected, $result); + } + + /** + * @dataProvider searchProvider + */ + public function testSearchOptions($search1, $search2, $expected) { + /** @var ICalendarV2 | MockObject $calendar1 */ + $calendar1 = $this->createMock(ICalendarV2::class); + $calendar1->method('getKey')->willReturn('simple:1'); + $calendar1->expects($this->once()) + ->method('search') + ->with('searchTerm', ['SUMMARY', 'DESCRIPTION'], + ['timerange' => ['start' => null, 'end' => null]], 5, 20) + ->willReturn($search1); + + /** @var ICalendarV2 | MockObject $calendar2 */ + $calendar2 = $this->createMock(ICalendarV2::class); + $calendar2->method('getKey')->willReturn('simple:2'); + $calendar2->expects($this->once()) + ->method('search') + ->with('searchTerm', ['SUMMARY', 'DESCRIPTION'], + ['timerange' => ['start' => null, 'end' => null]], 5, 20) + ->willReturn($search2); + + $this->manager->registerCalendar($calendar1); + $this->manager->registerCalendar($calendar2); + + $result = $this->manager->search('searchTerm', ['SUMMARY', 'DESCRIPTION'], + ['timerange' => ['start' => null, 'end' => null]], 5, 20); + $this->assertEquals($expected, $result); + } + + public function searchProvider() { + $search1 = [ + [ + 'id' => 1, + 'data' => 'foobar', + ], + [ + 'id' => 2, + 'data' => 'barfoo', + ] + ]; + $search2 = [ + [ + 'id' => 3, + 'data' => 'blablub', + ], + [ + 'id' => 4, + 'data' => 'blubbla', + ] + ]; + + $expected = [ + [ + 'id' => 1, + 'data' => 'foobar', + 'calendar-key' => 'simple:1', + ], + [ + 'id' => 2, + 'data' => 'barfoo', + 'calendar-key' => 'simple:1', + ], + [ + 'id' => 3, + 'data' => 'blablub', + 'calendar-key' => 'simple:2', + ], + [ + 'id' => 4, + 'data' => 'blubbla', + 'calendar-key' => 'simple:2', + ] + ]; + + return [ + [ + $search1, + $search2, + $expected + ] + ]; + } + + public function testRegisterUnregister() { + /** @var ICalendarV2 | MockObject $calendar1 */ + $calendar1 = $this->createMock(ICalendarV2::class); + $calendar1->method('getKey')->willReturn('key1'); + + /** @var ICalendarV2 | MockObject $calendar2 */ + $calendar2 = $this->createMock(ICalendarV2::class); + $calendar2->method('getKey')->willReturn('key2'); + + $this->manager->registerCalendar($calendar1); + $this->manager->registerCalendar($calendar2); + + $result = $this->manager->getCalendars(); + $this->assertCount(2, $result); + $this->assertContains($calendar1, $result); + $this->assertContains($calendar2, $result); + + $this->manager->unregisterCalendar($calendar1); + + $result = $this->manager->getCalendars(); + $this->assertCount(1, $result); + $this->assertContains($calendar2, $result); + } + + public function testGetCalendars() { + /** @var ICalendarV2 | MockObject $calendar1 */ + $calendar1 = $this->createMock(ICalendarV2::class); + $calendar1->method('getKey')->willReturn('key1'); + + /** @var ICalendarV2 | MockObject $calendar2 */ + $calendar2 = $this->createMock(ICalendarV2::class); + $calendar2->method('getKey')->willReturn('key2'); + + $this->manager->registerCalendar($calendar1); + $this->manager->registerCalendar($calendar2); + + $result = $this->manager->getCalendars(); + $this->assertCount(2, $result); + $this->assertContainsOnlyInstancesOf(ICalendarV2::class, $result); + $this->assertContains($calendar1, $result); + $this->assertContains($calendar2, $result); + + $this->manager->clear(); + + $result = $this->manager->getCalendars(); + + $this->assertCount(0, $result); + } + + public function testGetCalendar() { + /** @var ICalendarV2 | MockObject $calendar1 */ + $calendar1 = $this->createMock(ICalendarV2::class); + $calendar1->method('getKey')->willReturn('key1'); + + /** @var ICalendarV2 | MockObject $calendar2 */ + $calendar2 = $this->createMock(ICalendarV2::class); + $calendar2->method('getKey')->willReturn('key2'); + + $this->manager->registerCalendar($calendar1); + $this->manager->registerCalendar($calendar2); + + $result = $this->manager->getCalendar('key1'); + $this->assertEquals($calendar1, $result); + $this->assertInstanceOf(ICalendarV2::class, $result); + + $result = $this->manager->getCalendar('key2'); + $this->assertEquals($calendar1, $result); + $this->assertInstanceOf(ICalendarV2::class, $result); + + $result = $this->manager->getCalendar('key3'); + $this->assertNull($result); + + $this->manager->clear(); + + $result = $this->manager->getCalendar('key1'); + + $this->assertNull($result); + } + + public function testEnabledIfNot() { + $isEnabled = $this->manager->isEnabled(); + $this->assertFalse($isEnabled); + } + + public function testIfEnabledIfSo() { + /** @var ICalendarV2 | MockObject $calendar */ + $calendar = $this->createMock(ICalendarV2::class); + $this->manager->registerCalendar($calendar); + + $isEnabled = $this->manager->isEnabled(); + $this->assertTrue($isEnabled); + } +} From 1ef937c8094bb0b43562bcbd554507a4956aa2d1 Mon Sep 17 00:00:00 2001 From: Claus-Justus Heine Date: Tue, 6 Oct 2020 18:55:08 +0200 Subject: [PATCH 2/3] Polish s.t. the pre-merge checks pass. Signed-off-by: Claus-Justus Heine --- apps/dav/lib/AppInfo/Application.php | 13 ++++++---- apps/dav/lib/CalDAV/CalendarImplV2.php | 24 +++++++++---------- .../tests/unit/CalDAV/CalendarImplTest.php | 1 - .../tests/unit/CalDAV/CalendarManagerTest.php | 4 ++-- lib/private/Calendar/ManagerV2.php | 10 ++++---- lib/public/Calendar/ICalendarObjectV2.php | 12 +++++----- lib/public/Calendar/ICalendarV2.php | 10 ++++---- lib/public/Calendar/IManagerV2.php | 20 ++++++++-------- tests/lib/Calendar/ManagerTest.php | 1 - 9 files changed, 50 insertions(+), 45 deletions(-) diff --git a/apps/dav/lib/AppInfo/Application.php b/apps/dav/lib/AppInfo/Application.php index 6f0a26d54c7db..847f1069adb64 100644 --- a/apps/dav/lib/AppInfo/Application.php +++ b/apps/dav/lib/AppInfo/Application.php @@ -212,7 +212,10 @@ public function boot(IBootContext $context): void { $context->injectFn([$this, 'registerCalendarReminders']); } - public function registerHooks() { + public function registerHooks(HookManager $hm, + EventDispatcherInterface $dispatcher, + IAppContainer $container, + IServerContainer $serverContainer) { /** @var HookManager $hm */ $hm = $this->getContainer()->query(HookManager::class); $hm->setup(); @@ -306,7 +309,7 @@ private function setupCalendarProvider(ICalendarManager $calendarManager, } public function registerCalendarManagerV2(ICalendarManagerV2 $calendarManager, - IAppContainer $container): void { + IAppContainer $container): void { $calendarManager->register(function () use ($container, $calendarManager) { $user = \OC::$server->getUserSession()->getUser(); if ($user !== null) { @@ -319,9 +322,11 @@ public function registerCalendarManagerV2(ICalendarManagerV2 $calendarManager, * @param ICalendarManagerV2 $calendarManager * @param string $userId */ - public function setupCalendarProviderV2(ICalendarManagerV2 $calendarManager, $userId) { + public function setupCalendarProviderV2(ICalendarManagerV2 $calendarManager, + IAppContainer $container, + $userId) { /** @var CalendarManager $cm */ - $cm = $this->getContainer()->query(CalendarManager::class); + $cm = $container->query(CalendarManager::class); $cm->setupCalendarProviderV2($calendarManager, $userId); } diff --git a/apps/dav/lib/CalDAV/CalendarImplV2.php b/apps/dav/lib/CalDAV/CalendarImplV2.php index 5a27f49c7915c..afa72db88e33e 100644 --- a/apps/dav/lib/CalDAV/CalendarImplV2.php +++ b/apps/dav/lib/CalDAV/CalendarImplV2.php @@ -58,27 +58,27 @@ public function __construct(Calendar $calendar, array $calendarInfo, /** * @return string defining the technical unique key - * @since 19.0.0 + * @since 21.0.0 */ public function getKey(): string { - return (string) $this->calendarInfo['id']; + return $this->calendarInfo['id']; } /** * In comparison to getKey() this function returns a human readable (maybe translated) name * @return null|string - * @since 19.0.0 + * @since 21.0.0 */ - public function getDisplayName(): string { + public function getDisplayName(): ?string { return $this->calendarInfo['{DAV:}displayname']; } /** * Calendar color * @return null|string - * @since 19.0.0 + * @since 21.0.0 */ - public function getDisplayColor(): string { + public function getDisplayColor(): ?string { return $this->calendarInfo['{http://apple.com/ns/ical/}calendar-color']; } @@ -90,7 +90,7 @@ public function getDisplayColor(): string { * @param integer|null $limit - limit number of search results * @param integer|null $offset - offset for paging of search results * @return array an array of events/journals/todos which are arrays of key-value-pairs - * @since 19.0.0 + * @since 21.0.0 */ public function search($pattern, array $searchProperties=[], array $options=[], int $limit = null, int $offset = null): array { return $this->backend->search($this->calendarInfo, $pattern, @@ -99,7 +99,7 @@ public function search($pattern, array $searchProperties=[], array $options=[], /** * @return bool - * @since 19.0.0 + * @since 21.0.0 */ public function isWriteable(): bool { $permissions = $this->calendar->getACL(); @@ -125,7 +125,7 @@ public function isWriteable(): bool { /** * @param string $uri * @return ICalendarObjectV2|null - * @since 19.0.0 + * @since 21.0.0 */ public function getByUri(string $uri): ?ICalendarObjectV2 { if ($calendarObjectData = $this->backend->getCalendarObject($this->getKey(), $uri)) { @@ -141,11 +141,11 @@ public function getByUri(string $uri): ?ICalendarObjectV2 { /** * @param VCalendar $vObject - * @return ICalendarObjectV2 + * @return ICalendarObjectV2|null * @throws BadRequest - * @since 19.0.0 + * @since 21.0.0 */ - public function create(VCalendar $vObject): ICalendarObjectV2 { + public function create(VCalendar $vObject): ?ICalendarObjectV2 { CalendarObjectImplV2::validateCalendarData($vObject); $uuid = UUIDUtil::getUUID() . '.ics'; $this->backend->createCalendarObject($this->getKey(), $uuid, $vObject->serialize()); diff --git a/apps/dav/tests/unit/CalDAV/CalendarImplTest.php b/apps/dav/tests/unit/CalDAV/CalendarImplTest.php index 631fc861ece0b..a423673a0504a 100644 --- a/apps/dav/tests/unit/CalDAV/CalendarImplTest.php +++ b/apps/dav/tests/unit/CalDAV/CalendarImplTest.php @@ -38,7 +38,6 @@ use Sabre\VObject\Component\VEvent; use Sabre\VObject\ITip\Message; use Sabre\VObject\Reader; -use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; /** diff --git a/apps/dav/tests/unit/CalDAV/CalendarManagerTest.php b/apps/dav/tests/unit/CalDAV/CalendarManagerTest.php index 059e5af8f1f23..168708f471028 100644 --- a/apps/dav/tests/unit/CalDAV/CalendarManagerTest.php +++ b/apps/dav/tests/unit/CalDAV/CalendarManagerTest.php @@ -114,7 +114,7 @@ public function testSetupCalendarProviderV2() { $calendarManager = $this->createMock(ManagerV2::class); $calendarManager->expects($this->at(0)) ->method('registerCalendar') - ->willReturnCallback(function() { + ->willReturnCallback(function () { $parameter = func_get_arg(0); $this->assertInstanceOf(CalendarImplV2::class, $parameter); $this->assertEquals(123, $parameter->getKey()); @@ -122,7 +122,7 @@ public function testSetupCalendarProviderV2() { $calendarManager->expects($this->at(1)) ->method('registerCalendar') - ->willReturnCallback(function() { + ->willReturnCallback(function () { $parameter = func_get_arg(0); $this->assertInstanceOf(CalendarImplV2::class, $parameter); $this->assertEquals(456, $parameter->getKey()); diff --git a/lib/private/Calendar/ManagerV2.php b/lib/private/Calendar/ManagerV2.php index 9e2fc492b5aa8..cd6eb2d3b67f6 100644 --- a/lib/private/Calendar/ManagerV2.php +++ b/lib/private/Calendar/ManagerV2.php @@ -25,6 +25,7 @@ use OCP\Calendar\ICalendarV2; use OCP\Calendar\IManagerV2; +use OCP\Calendar\ICalendarObjectV2; class ManagerV2 implements IManagerV2 { @@ -48,15 +49,15 @@ class ManagerV2 implements IManagerV2 { * ['timerange' => ['start' => new DateTime(...), 'end' => new DateTime(...)]] * @param integer|null $limit - limit number of search results * @param integer|null $offset - offset for paging of search results - * @return array an array of events/journals/todos which are arrays of arrays of key-value-pairs + * @return ICalendarObjectV2[] an array of events/journals/todos which are arrays of arrays of key-value-pairs * @since 13.0.0 */ public function search($pattern, array $searchProperties=[], array $options=[], int $limit = null, int $offset=null): array { $this->loadCalendars(); $result = []; - foreach($this->calendars as $calendar) { + foreach ($this->calendars as $calendar) { $r = $calendar->search($pattern, $searchProperties, $options, $limit, $offset); - foreach($r as $o) { + foreach ($r as $o) { $o['calendar-key'] = $calendar->getKey(); $result[] = $o; } @@ -133,7 +134,7 @@ public function clear(): void { * loads all calendars */ private function loadCalendars() { - foreach($this->calendarLoaders as $callable) { + foreach ($this->calendarLoaders as $callable) { $callable($this); } $this->calendarLoaders = []; @@ -144,6 +145,7 @@ private function loadCalendars() { * * @param string $key * @return ICalendarV2|null + * @since 21.0.0 */ public function getCalendar(string $key): ?ICalendarV2 { return isset($this->calendars[$key]) ? $this->calendars[$key] : null; diff --git a/lib/public/Calendar/ICalendarObjectV2.php b/lib/public/Calendar/ICalendarObjectV2.php index 6333044cce925..2a3c8b7ab450b 100644 --- a/lib/public/Calendar/ICalendarObjectV2.php +++ b/lib/public/Calendar/ICalendarObjectV2.php @@ -29,25 +29,25 @@ * Interface ICalendarObjectV2 * * @package OCP - * @since 20.0.0 + * @since 21.0.0 */ interface ICalendarObjectV2 { /** * @return string defining the technical unique key - * @since 20.0.0 + * @since 21.0.0 */ public function getCalendarKey(): string; /** * @return string calendar object unique URI - * @since 20.0.0 + * @since 21.0.0 */ public function getUri(): string; /** * @return VCalendar the calendar object data - * @since 20.0.0 + * @since 21.0.0 */ public function getVObject(): VCalendar; @@ -55,13 +55,13 @@ public function getVObject(): VCalendar; * Update calendar object data * * @param VCalendar $data - * @since 20.0.0 + * @since 21.0.0 */ public function update(VCalendar $data): void; /** * Delete calendar object - * @since 20.0.0 + * @since 21.0.0 */ public function delete(): void; } diff --git a/lib/public/Calendar/ICalendarV2.php b/lib/public/Calendar/ICalendarV2.php index 6265c1e811a1b..5f2425c6794ed 100644 --- a/lib/public/Calendar/ICalendarV2.php +++ b/lib/public/Calendar/ICalendarV2.php @@ -77,17 +77,17 @@ public function getByUri(string $uri): ?ICalendarObjectV2; * ['timerange' => ['start' => new DateTime(...), 'end' => new DateTime(...)]] * @param integer|null $limit - limit number of search results * @param integer|null $offset - offset for paging of search results - * @return ICalendarObjectV2[] - * @since 20.0.0 + * @return array an array of events/journals/todos which are arrays of key-value-pairs + * @since 19.0.0 */ - public function search(string $pattern, array $searchProperties=[], array $options=[], int $limit=null, int $offset=null): ?array; + public function search(string $pattern, array $searchProperties=[], array $options=[], int $limit=null, int $offset=null): array; /** * Create a new calendar object into a calendar. Accepts a VCalendar object for calendar data. * * @param VCalendar $vObject - * @return ICalendarObjectV2 + * @return ICalendarObjectV2|null * @since 20.0.0 */ - public function create(VCalendar $vObject): ICalendarObjectV2; + public function create(VCalendar $vObject): ?ICalendarObjectV2; } diff --git a/lib/public/Calendar/IManagerV2.php b/lib/public/Calendar/IManagerV2.php index 6e43961577ce1..6fc5d8589a034 100644 --- a/lib/public/Calendar/IManagerV2.php +++ b/lib/public/Calendar/IManagerV2.php @@ -31,7 +31,7 @@ * * Events/Journals/Todos are expressed as instances of ICalendarObjectV2 * - * @since 20.0.0 + * @since 21.0.0 */ interface IManagerV2 { @@ -39,7 +39,7 @@ interface IManagerV2 { * Check if calendars are available * * @return bool true if enabled, false if not - * @since 20.0.0 + * @since 21.0.0 */ public function isEnabled(): bool; @@ -47,7 +47,7 @@ public function isEnabled(): bool; * Get all registered calendars * * @return ICalendarV2[] - * @since 20.0.0 + * @since 21.0.0 */ public function getCalendars(): array; @@ -56,7 +56,7 @@ public function getCalendars(): array; * * @param string $key * @return ICalendarV2|null - * @since 20.0.0 + * @since 21.0.0 */ public function getCalendar(string $key): ?ICalendarV2; @@ -71,16 +71,16 @@ public function getCalendar(string $key): ?ICalendarV2; * @param integer|null $limit - limit number of search results * @param integer|null $offset - offset for paging of search results * @return ICalendarObjectV2[] - * @since 20.0.0 + * @since 21.0.0 */ - public function search(string $pattern, array $searchProperties=[], array $options=[], int $limit=null, int $offset=null): ?array; + public function search(string $pattern, array $searchProperties=[], array $options=[], int $limit=null, int $offset=null): array; /** * Registers a calendar * * @param ICalendarV2 $calendar * @return void - * @since 20.0.0 + * @since 21.0.0 */ public function registerCalendar(ICalendarV2 $calendar): void; @@ -89,7 +89,7 @@ public function registerCalendar(ICalendarV2 $calendar): void; * * @param ICalendarV2 $calendar * @return void - * @since 20.0.0 + * @since 21.0.0 */ public function unregisterCalendar(ICalendarV2 $calendar): void; @@ -99,14 +99,14 @@ public function unregisterCalendar(ICalendarV2 $calendar): void; * * @param Closure $callable * @return void - * @since 20.0.0 + * @since 21.0.0 */ public function register(Closure $callable): void; /** * removes all registered calendar instances * @return void - * @since 20.0.0 + * @since 21.0.0 */ public function clear(): void; } diff --git a/tests/lib/Calendar/ManagerTest.php b/tests/lib/Calendar/ManagerTest.php index f2b82a38a0678..bdaa5c9326091 100644 --- a/tests/lib/Calendar/ManagerTest.php +++ b/tests/lib/Calendar/ManagerTest.php @@ -35,7 +35,6 @@ use Psr\Log\LoggerInterface; use Sabre\VObject\Document; use Sabre\VObject\Reader; -use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; /* From 887af373f5a79331d8ae9776c8f0f4c8ee1d24e9 Mon Sep 17 00:00:00 2001 From: Claus-Justus Heine Date: Wed, 7 Oct 2020 17:13:00 +0200 Subject: [PATCH 3/3] Remove explicit hookmanager-query, already passed as argument Signed-off-by: Claus-Justus Heine --- apps/dav/lib/AppInfo/Application.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/apps/dav/lib/AppInfo/Application.php b/apps/dav/lib/AppInfo/Application.php index 847f1069adb64..3b9e5d96de992 100644 --- a/apps/dav/lib/AppInfo/Application.php +++ b/apps/dav/lib/AppInfo/Application.php @@ -216,10 +216,6 @@ public function registerHooks(HookManager $hm, EventDispatcherInterface $dispatcher, IAppContainer $container, IServerContainer $serverContainer) { - /** @var HookManager $hm */ - $hm = $this->getContainer()->query(HookManager::class); - $hm->setup(); - // first time login event setup $dispatcher->addListener(IUser::class . '::firstLogin', function ($event) use ($hm) { if ($event instanceof GenericEvent) {