diff options
author | Morris Jobke <hey@morrisjobke.de> | 2017-12-12 08:29:26 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-12-12 08:29:26 +0100 |
commit | d98dea1eb139ae33dfce28f5c61b140518ef4d1d (patch) | |
tree | 6bd1527425761a5b9c6657874e573f9a4ae8a38d /apps | |
parent | e1740c92421d9f6e05210c1cd69811e429f88410 (diff) | |
parent | 2b51d84b98a5a44c2a42a8498164a35b6822e760 (diff) | |
download | nextcloud-server-d98dea1eb139ae33dfce28f5c61b140518ef4d1d.tar.gz nextcloud-server-d98dea1eb139ae33dfce28f5c61b140518ef4d1d.zip |
Merge pull request #6884 from nextcloud/feature/3003/opt_out_of_birthday_calendar
Opt out of birthday calendar
Diffstat (limited to 'apps')
26 files changed, 1204 insertions, 35 deletions
diff --git a/apps/dav/appinfo/routes.php b/apps/dav/appinfo/routes.php new file mode 100644 index 00000000000..e6785bfd4e6 --- /dev/null +++ b/apps/dav/appinfo/routes.php @@ -0,0 +1,29 @@ +<?php +/** + * @copyright 2017, Georg Ehrke <oc.list@georgehrke.com> + * + * @author Georg Ehrke <oc.list@georgehrke.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/>. + * + */ + +return [ + 'routes' => [ + ['name' => 'birthday_calendar#enable', 'url' => '/enableBirthdayCalendar', 'verb' => 'POST'], + ['name' => 'birthday_calendar#disable', 'url' => '/disableBirthdayCalendar', 'verb' => 'POST'], + ] +]; diff --git a/apps/dav/composer/composer/autoload_classmap.php b/apps/dav/composer/composer/autoload_classmap.php index 137b77dbd35..ddb8d7ca8d2 100644 --- a/apps/dav/composer/composer/autoload_classmap.php +++ b/apps/dav/composer/composer/autoload_classmap.php @@ -11,6 +11,7 @@ return array( 'OCA\\DAV\\Avatars\\AvatarHome' => $baseDir . '/../lib/Avatars/AvatarHome.php', 'OCA\\DAV\\Avatars\\AvatarNode' => $baseDir . '/../lib/Avatars/AvatarNode.php', 'OCA\\DAV\\Avatars\\RootCollection' => $baseDir . '/../lib/Avatars/RootCollection.php', + 'OCA\\DAV\\BackgroundJob\\GenerateBirthdayCalendarBackgroundJob' => $baseDir . '/../lib/BackgroundJob/GenerateBirthdayCalendarBackgroundJob.php', 'OCA\\DAV\\CalDAV\\Activity\\Backend' => $baseDir . '/../lib/CalDAV/Activity/Backend.php', 'OCA\\DAV\\CalDAV\\Activity\\Filter\\Calendar' => $baseDir . '/../lib/CalDAV/Activity/Filter/Calendar.php', 'OCA\\DAV\\CalDAV\\Activity\\Filter\\Todo' => $baseDir . '/../lib/CalDAV/Activity/Filter/Todo.php', @@ -21,6 +22,7 @@ return array( 'OCA\\DAV\\CalDAV\\Activity\\Setting\\Calendar' => $baseDir . '/../lib/CalDAV/Activity/Setting/Calendar.php', 'OCA\\DAV\\CalDAV\\Activity\\Setting\\Event' => $baseDir . '/../lib/CalDAV/Activity/Setting/Event.php', 'OCA\\DAV\\CalDAV\\Activity\\Setting\\Todo' => $baseDir . '/../lib/CalDAV/Activity/Setting/Todo.php', + 'OCA\\DAV\\CalDAV\\BirthdayCalendar\\EnablePlugin' => $baseDir . '/../lib/CalDAV/BirthdayCalendar/EnablePlugin.php', 'OCA\\DAV\\CalDAV\\BirthdayService' => $baseDir . '/../lib/CalDAV/BirthdayService.php', 'OCA\\DAV\\CalDAV\\CalDavBackend' => $baseDir . '/../lib/CalDAV/CalDavBackend.php', 'OCA\\DAV\\CalDAV\\Calendar' => $baseDir . '/../lib/CalDAV/Calendar.php', @@ -107,6 +109,7 @@ return array( 'OCA\\DAV\\Connector\\Sabre\\SharesPlugin' => $baseDir . '/../lib/Connector/Sabre/SharesPlugin.php', 'OCA\\DAV\\Connector\\Sabre\\TagList' => $baseDir . '/../lib/Connector/Sabre/TagList.php', 'OCA\\DAV\\Connector\\Sabre\\TagsPlugin' => $baseDir . '/../lib/Connector/Sabre/TagsPlugin.php', + 'OCA\\DAV\\Controller\\BirthdayCalendarController' => $baseDir . '/../lib/Controller/BirthdayCalendarController.php', 'OCA\\DAV\\DAV\\CustomPropertiesBackend' => $baseDir . '/../lib/DAV/CustomPropertiesBackend.php', 'OCA\\DAV\\DAV\\GroupPrincipalBackend' => $baseDir . '/../lib/DAV/GroupPrincipalBackend.php', 'OCA\\DAV\\DAV\\PublicAuth' => $baseDir . '/../lib/DAV/PublicAuth.php', diff --git a/apps/dav/composer/composer/autoload_static.php b/apps/dav/composer/composer/autoload_static.php index 772eba41349..46707a93912 100644 --- a/apps/dav/composer/composer/autoload_static.php +++ b/apps/dav/composer/composer/autoload_static.php @@ -26,6 +26,7 @@ class ComposerStaticInitDAV 'OCA\\DAV\\Avatars\\AvatarHome' => __DIR__ . '/..' . '/../lib/Avatars/AvatarHome.php', 'OCA\\DAV\\Avatars\\AvatarNode' => __DIR__ . '/..' . '/../lib/Avatars/AvatarNode.php', 'OCA\\DAV\\Avatars\\RootCollection' => __DIR__ . '/..' . '/../lib/Avatars/RootCollection.php', + 'OCA\\DAV\\BackgroundJob\\GenerateBirthdayCalendarBackgroundJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/GenerateBirthdayCalendarBackgroundJob.php', 'OCA\\DAV\\CalDAV\\Activity\\Backend' => __DIR__ . '/..' . '/../lib/CalDAV/Activity/Backend.php', 'OCA\\DAV\\CalDAV\\Activity\\Filter\\Calendar' => __DIR__ . '/..' . '/../lib/CalDAV/Activity/Filter/Calendar.php', 'OCA\\DAV\\CalDAV\\Activity\\Filter\\Todo' => __DIR__ . '/..' . '/../lib/CalDAV/Activity/Filter/Todo.php', @@ -36,6 +37,7 @@ class ComposerStaticInitDAV 'OCA\\DAV\\CalDAV\\Activity\\Setting\\Calendar' => __DIR__ . '/..' . '/../lib/CalDAV/Activity/Setting/Calendar.php', 'OCA\\DAV\\CalDAV\\Activity\\Setting\\Event' => __DIR__ . '/..' . '/../lib/CalDAV/Activity/Setting/Event.php', 'OCA\\DAV\\CalDAV\\Activity\\Setting\\Todo' => __DIR__ . '/..' . '/../lib/CalDAV/Activity/Setting/Todo.php', + 'OCA\\DAV\\CalDAV\\BirthdayCalendar\\EnablePlugin' => __DIR__ . '/..' . '/../lib/CalDAV/BirthdayCalendar/EnablePlugin.php', 'OCA\\DAV\\CalDAV\\BirthdayService' => __DIR__ . '/..' . '/../lib/CalDAV/BirthdayService.php', 'OCA\\DAV\\CalDAV\\CalDavBackend' => __DIR__ . '/..' . '/../lib/CalDAV/CalDavBackend.php', 'OCA\\DAV\\CalDAV\\Calendar' => __DIR__ . '/..' . '/../lib/CalDAV/Calendar.php', @@ -122,6 +124,7 @@ class ComposerStaticInitDAV 'OCA\\DAV\\Connector\\Sabre\\SharesPlugin' => __DIR__ . '/..' . '/../lib/Connector/Sabre/SharesPlugin.php', 'OCA\\DAV\\Connector\\Sabre\\TagList' => __DIR__ . '/..' . '/../lib/Connector/Sabre/TagList.php', 'OCA\\DAV\\Connector\\Sabre\\TagsPlugin' => __DIR__ . '/..' . '/../lib/Connector/Sabre/TagsPlugin.php', + 'OCA\\DAV\\Controller\\BirthdayCalendarController' => __DIR__ . '/..' . '/../lib/Controller/BirthdayCalendarController.php', 'OCA\\DAV\\DAV\\CustomPropertiesBackend' => __DIR__ . '/..' . '/../lib/DAV/CustomPropertiesBackend.php', 'OCA\\DAV\\DAV\\GroupPrincipalBackend' => __DIR__ . '/..' . '/../lib/DAV/GroupPrincipalBackend.php', 'OCA\\DAV\\DAV\\PublicAuth' => __DIR__ . '/..' . '/../lib/DAV/PublicAuth.php', diff --git a/apps/dav/js/settings-admin-caldav.js b/apps/dav/js/settings-admin-caldav.js index 10eb89ab61d..cf1a2006f69 100644 --- a/apps/dav/js/settings-admin-caldav.js +++ b/apps/dav/js/settings-admin-caldav.js @@ -26,3 +26,13 @@ $('#caldavSendInvitations').change(function() { OCP.AppConfig.setValue('dav', 'sendInvitations', val ? 'yes' : 'no'); }); + +$('#caldavGenerateBirthdayCalendar').change(function() { + var val = $(this)[0].checked; + + if (val) { + $.post(OC.generateUrl(OC.linkTo("dav", "enableBirthdayCalendar"))); + } else { + $.post(OC.generateUrl(OC.linkTo("dav", "disableBirthdayCalendar"))); + } +}); diff --git a/apps/dav/lib/BackgroundJob/GenerateBirthdayCalendarBackgroundJob.php b/apps/dav/lib/BackgroundJob/GenerateBirthdayCalendarBackgroundJob.php new file mode 100644 index 00000000000..c4279c5108c --- /dev/null +++ b/apps/dav/lib/BackgroundJob/GenerateBirthdayCalendarBackgroundJob.php @@ -0,0 +1,69 @@ +<?php +/** + * @copyright 2017 Georg Ehrke <oc.list@georgehrke.com> + * + * @author Georg Ehrke <oc.list@georgehrke.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\BackgroundJob; + +use OC\BackgroundJob\QueuedJob; +use OCA\DAV\CalDAV\BirthdayService; +use OCP\IConfig; + +class GenerateBirthdayCalendarBackgroundJob extends QueuedJob { + + /** @var BirthdayService */ + private $birthdayService; + + /** @var IConfig */ + private $config; + + /** + * GenerateAllBirthdayCalendarsBackgroundJob constructor. + * + * @param BirthdayService $birthdayService + * @param IConfig $config + */ + public function __construct(BirthdayService $birthdayService, + IConfig $config) { + $this->birthdayService = $birthdayService; + $this->config = $config; + } + + /** + * @param array $arguments + */ + public function run($arguments) { + $userId = $arguments['userId']; + + // make sure admin didn't change his mind + $isGloballyEnabled = $this->config->getAppValue('dav', 'generateBirthdayCalendar', 'yes'); + if ($isGloballyEnabled !== 'yes') { + return; + } + + // did the user opt out? + $isUserEnabled = $this->config->getUserValue($userId, 'dav', 'generateBirthdayCalendar', 'yes'); + if ($isUserEnabled !== 'yes') { + return; + } + + $this->birthdayService->syncUser($userId); + } +} diff --git a/apps/dav/lib/CalDAV/BirthdayCalendar/EnablePlugin.php b/apps/dav/lib/CalDAV/BirthdayCalendar/EnablePlugin.php new file mode 100644 index 00000000000..497d7112b3c --- /dev/null +++ b/apps/dav/lib/CalDAV/BirthdayCalendar/EnablePlugin.php @@ -0,0 +1,139 @@ +<?php +/** + * @copyright 2017, Georg Ehrke <oc.list@georgehrke.com> + * + * @author Georg Ehrke <oc.list@georgehrke.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\BirthdayCalendar; + +use OCA\DAV\CalDAV\BirthdayService; +use OCA\DAV\CalDAV\CalendarHome; +use Sabre\DAV\Server; +use Sabre\DAV\ServerPlugin; +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; +use OCP\IConfig; + +/** + * Class EnablePlugin + * allows users to re-enable the birthday calendar via CalDAV + * + * @package OCA\DAV\CalDAV\BirthdayCalendar + */ +class EnablePlugin extends ServerPlugin { + const NS_Nextcloud = 'http://nextcloud.com/ns'; + + /** + * @var IConfig + */ + protected $config; + + /** + * @var BirthdayService + */ + protected $birthdayService; + + /** + * @var Server + */ + protected $server; + + /** + * PublishPlugin constructor. + * + * @param IConfig $config + * @param BirthdayService $birthdayService + */ + public function __construct(IConfig $config, BirthdayService $birthdayService) { + $this->config = $config; + $this->birthdayService = $birthdayService; + } + + /** + * 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() { + return ['nc-enable-birthday-calendar']; + } + + /** + * 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-enable-birthday-calendar'; + } + + /** + * 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']); + } + + /** + * We intercept this to handle POST requests on calendar homes. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * + * @return bool|void + */ + public function httpPost(RequestInterface $request, ResponseInterface $response) { + $node = $this->server->tree->getNodeForPath($this->server->getRequestUri()); + if (!($node instanceof CalendarHome)) { + return; + } + + $requestBody = $request->getBodyAsString(); + $this->server->xml->parse($requestBody, $request->getUrl(), $documentType); + if ($documentType !== '{'.self::NS_Nextcloud.'}enable-birthday-calendar') { + return; + } + + $principalUri = $node->getOwner(); + $userId = substr($principalUri, 17); + + $this->config->setUserValue($userId, 'dav', 'generateBirthdayCalendar', 'yes'); + $this->birthdayService->syncUser($userId); + + $this->server->httpResponse->setStatus(204); + + return false; + } +} diff --git a/apps/dav/lib/CalDAV/BirthdayService.php b/apps/dav/lib/CalDAV/BirthdayService.php index aa902bacc53..62d218f0a2a 100644 --- a/apps/dav/lib/CalDAV/BirthdayService.php +++ b/apps/dav/lib/CalDAV/BirthdayService.php @@ -31,6 +31,7 @@ namespace OCA\DAV\CalDAV; use Exception; use OCA\DAV\CardDAV\CardDavBackend; use OCA\DAV\DAV\GroupPrincipalBackend; +use OCP\IConfig; use Sabre\VObject\Component\VCalendar; use Sabre\VObject\Component\VCard; use Sabre\VObject\DateTimeParser; @@ -52,17 +53,22 @@ class BirthdayService { /** @var CardDavBackend */ private $cardDavBackEnd; + /** @var IConfig */ + private $config; + /** * BirthdayService constructor. * * @param CalDavBackend $calDavBackEnd * @param CardDavBackend $cardDavBackEnd * @param GroupPrincipalBackend $principalBackend + * @param IConfig $config; */ - public function __construct(CalDavBackend $calDavBackEnd, CardDavBackend $cardDavBackEnd, GroupPrincipalBackend $principalBackend) { + public function __construct(CalDavBackend $calDavBackEnd, CardDavBackend $cardDavBackEnd, GroupPrincipalBackend $principalBackend, IConfig $config) { $this->calDavBackEnd = $calDavBackEnd; $this->cardDavBackEnd = $cardDavBackEnd; $this->principalBackend = $principalBackend; + $this->config = $config; } /** @@ -71,8 +77,11 @@ class BirthdayService { * @param string $cardData */ public function onCardChanged($addressBookId, $cardUri, $cardData) { + if (!$this->isGloballyEnabled()) { + return; + } + $targetPrincipals = $this->getAllAffectedPrincipals($addressBookId); - $book = $this->cardDavBackEnd->getAddressBookById($addressBookId); $targetPrincipals[] = $book['principaluri']; $datesToSync = [ @@ -81,6 +90,10 @@ class BirthdayService { ['postfix' => '-anniversary', 'field' => 'ANNIVERSARY', 'symbol' => "âš"], ]; foreach ($targetPrincipals as $principalUri) { + if (!$this->isUserEnabled($principalUri)) { + continue; + } + $calendar = $this->ensureCalendarExists($principalUri); foreach ($datesToSync as $type) { $this->updateCalendar($cardUri, $cardData, $book, $calendar['id'], $type); @@ -93,10 +106,18 @@ class BirthdayService { * @param string $cardUri */ public function onCardDeleted($addressBookId, $cardUri) { + if (!$this->isGloballyEnabled()) { + return; + } + $targetPrincipals = $this->getAllAffectedPrincipals($addressBookId); $book = $this->cardDavBackEnd->getAddressBookById($addressBookId); $targetPrincipals[] = $book['principaluri']; foreach ($targetPrincipals as $principalUri) { + if (!$this->isUserEnabled($principalUri)) { + continue; + } + $calendar = $this->ensureCalendarExists($principalUri); foreach (['', '-death', '-anniversary'] as $tag) { $objectUri = $book['uri'] . '-' . $cardUri . $tag .'.ics'; @@ -293,4 +314,31 @@ class BirthdayService { } } + /** + * checks if the admin opted-out of birthday calendars + * + * @return bool + */ + private function isGloballyEnabled() { + $isGloballyEnabled = $this->config->getAppValue('dav', 'generateBirthdayCalendar', 'yes'); + return $isGloballyEnabled === 'yes'; + } + + /** + * checks if the user opted-out of birthday calendars + * + * @param $userPrincipal + * @return bool + */ + private function isUserEnabled($userPrincipal) { + if (strpos($userPrincipal, 'principals/users/') === 0) { + $userId = substr($userPrincipal, 17); + $isEnabled = $this->config->getUserValue($userId, 'dav', 'generateBirthdayCalendar', 'yes'); + return $isEnabled === 'yes'; + } + + // not sure how we got here, just be on the safe side and return true + return true; + } + } diff --git a/apps/dav/lib/CalDAV/CalDavBackend.php b/apps/dav/lib/CalDAV/CalDavBackend.php index 8ecd64723a2..169bf6ff6a5 100644 --- a/apps/dav/lib/CalDAV/CalDavBackend.php +++ b/apps/dav/lib/CalDAV/CalDavBackend.php @@ -2285,6 +2285,22 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription } /** + * deletes all birthday calendars + */ + public function deleteAllBirthdayCalendars() { + $query = $this->db->getQueryBuilder(); + $result = $query->select(['id'])->from('calendars') + ->where($query->expr()->eq('uri', + $query->createNamedParameter(BirthdayService::BIRTHDAY_CALENDAR_URI))) + ->execute(); + + $ids = $result->fetchAll(); + foreach($ids as $id) { + $this->deleteCalendar($id['id']); + } + } + + /** * read VCalendar data into a VCalendar object * * @param string $objectData diff --git a/apps/dav/lib/CalDAV/Calendar.php b/apps/dav/lib/CalDAV/Calendar.php index ac3bcec6173..02808ab5662 100644 --- a/apps/dav/lib/CalDAV/Calendar.php +++ b/apps/dav/lib/CalDAV/Calendar.php @@ -27,6 +27,7 @@ namespace OCA\DAV\CalDAV; use OCA\DAV\DAV\Sharing\IShareable; +use OCP\IConfig; use OCP\IL10N; use Sabre\CalDAV\Backend\BackendInterface; use Sabre\DAV\Exception\Forbidden; @@ -41,7 +42,10 @@ use Sabre\DAV\PropPatch; */ class Calendar extends \Sabre\CalDAV\Calendar implements IShareable { - public function __construct(BackendInterface $caldavBackend, $calendarInfo, IL10N $l10n) { + /** @var IConfig */ + private $config; + + public function __construct(BackendInterface $caldavBackend, $calendarInfo, IL10N $l10n, IConfig $config) { parent::__construct($caldavBackend, $calendarInfo); if ($this->getName() === BirthdayService::BIRTHDAY_CALENDAR_URI) { @@ -51,6 +55,8 @@ class Calendar extends \Sabre\CalDAV\Calendar implements IShareable { $this->calendarInfo['{DAV:}displayname'] === CalDavBackend::PERSONAL_CALENDAR_NAME) { $this->calendarInfo['{DAV:}displayname'] = $l10n->t('Personal'); } + + $this->config = $config; } /** @@ -201,6 +207,16 @@ class Calendar extends \Sabre\CalDAV\Calendar implements IShareable { ]); return; } + + // Remember when a user deleted their birthday calendar + // in order to not regenerate it on the next contacts change + if ($this->getName() === BirthdayService::BIRTHDAY_CALENDAR_URI) { + $principalURI = $this->getPrincipalURI(); + $userId = substr($principalURI, 17); + + $this->config->setUserValue($userId, 'dav', 'generateBirthdayCalendar', 'no'); + } + parent::delete(); } diff --git a/apps/dav/lib/CalDAV/CalendarHome.php b/apps/dav/lib/CalDAV/CalendarHome.php index 3429c24705d..3e645db459f 100644 --- a/apps/dav/lib/CalDAV/CalendarHome.php +++ b/apps/dav/lib/CalDAV/CalendarHome.php @@ -32,15 +32,21 @@ use Sabre\CalDAV\Schedule\Inbox; use Sabre\CalDAV\Schedule\Outbox; use Sabre\CalDAV\Subscriptions\Subscription; use Sabre\DAV\Exception\NotFound; +use Sabre\DAV\Exception\MethodNotAllowed; +use Sabre\DAV\MkCol; class CalendarHome extends \Sabre\CalDAV\CalendarHome { /** @var \OCP\IL10N */ private $l10n; + /** @var \OCP\IConfig */ + private $config; + public function __construct(BackendInterface $caldavBackend, $principalInfo) { parent::__construct($caldavBackend, $principalInfo); $this->l10n = \OC::$server->getL10N('dav'); + $this->config = \OC::$server->getConfig(); } /** @@ -53,11 +59,24 @@ class CalendarHome extends \Sabre\CalDAV\CalendarHome { /** * @inheritdoc */ + function createExtendedCollection($name, MkCol $mkCol) { + $reservedNames = [BirthdayService::BIRTHDAY_CALENDAR_URI]; + + if (in_array($name, $reservedNames)) { + throw new MethodNotAllowed('The resource you tried to create has a reserved name'); + } + + parent::createExtendedCollection($name, $mkCol); + } + + /** + * @inheritdoc + */ function getChildren() { $calendars = $this->caldavBackend->getCalendarsForUser($this->principalInfo['uri']); $objects = []; foreach ($calendars as $calendar) { - $objects[] = new Calendar($this->caldavBackend, $calendar, $this->l10n); + $objects[] = new Calendar($this->caldavBackend, $calendar, $this->l10n, $this->config); } if ($this->caldavBackend instanceof SchedulingSupport) { @@ -98,7 +117,7 @@ class CalendarHome extends \Sabre\CalDAV\CalendarHome { // Calendars foreach ($this->caldavBackend->getCalendarsForUser($this->principalInfo['uri']) as $calendar) { if ($calendar['uri'] === $name) { - return new Calendar($this->caldavBackend, $calendar, $this->l10n); + return new Calendar($this->caldavBackend, $calendar, $this->l10n, $this->config); } } diff --git a/apps/dav/lib/CalDAV/PublicCalendarRoot.php b/apps/dav/lib/CalDAV/PublicCalendarRoot.php index 55f969e2167..9385f487bda 100644 --- a/apps/dav/lib/CalDAV/PublicCalendarRoot.php +++ b/apps/dav/lib/CalDAV/PublicCalendarRoot.php @@ -24,6 +24,8 @@ */ namespace OCA\DAV\CalDAV; +use OCP\IConfig; +use OCP\IL10N; use Sabre\DAV\Collection; class PublicCalendarRoot extends Collection { @@ -34,9 +36,22 @@ class PublicCalendarRoot extends Collection { /** @var \OCP\IL10N */ protected $l10n; - function __construct(CalDavBackend $caldavBackend) { + /** @var \OCP\IConfig */ + protected $config; + + /** + * PublicCalendarRoot constructor. + * + * @param CalDavBackend $caldavBackend + * @param IL10N $l10n + * @param IConfig $config + */ + function __construct(CalDavBackend $caldavBackend, IL10N $l10n, + IConfig $config) { $this->caldavBackend = $caldavBackend; - $this->l10n = \OC::$server->getL10N('dav'); + $this->l10n = $l10n; + $this->config = $config; + } /** @@ -51,7 +66,7 @@ class PublicCalendarRoot extends Collection { */ function getChild($name) { $calendar = $this->caldavBackend->getPublicCalendar($name); - return new PublicCalendar($this->caldavBackend, $calendar, $this->l10n); + return new PublicCalendar($this->caldavBackend, $calendar, $this->l10n, $this->config); } /** diff --git a/apps/dav/lib/Command/SyncBirthdayCalendar.php b/apps/dav/lib/Command/SyncBirthdayCalendar.php index a99e2ea4b89..88f85a98812 100644 --- a/apps/dav/lib/Command/SyncBirthdayCalendar.php +++ b/apps/dav/lib/Command/SyncBirthdayCalendar.php @@ -23,6 +23,7 @@ namespace OCA\DAV\Command; use OCA\DAV\CalDAV\BirthdayService; +use OCP\IConfig; use OCP\IUser; use OCP\IUserManager; use Symfony\Component\Console\Command\Command; @@ -36,16 +37,22 @@ class SyncBirthdayCalendar extends Command { /** @var BirthdayService */ private $birthdayService; + /** @var IConfig */ + private $config; + /** @var IUserManager */ private $userManager; /** * @param IUserManager $userManager + * @param IConfig $config * @param BirthdayService $birthdayService */ - function __construct(IUserManager $userManager, BirthdayService $birthdayService) { + function __construct(IUserManager $userManager, IConfig $config, + BirthdayService $birthdayService) { parent::__construct(); $this->birthdayService = $birthdayService; + $this->config = $config; $this->userManager = $userManager; } @@ -63,11 +70,21 @@ class SyncBirthdayCalendar extends Command { * @param OutputInterface $output */ protected function execute(InputInterface $input, OutputInterface $output) { + $this->verifyEnabled(); + $user = $input->getArgument('user'); if (!is_null($user)) { if (!$this->userManager->userExists($user)) { throw new \InvalidArgumentException("User <$user> in unknown."); } + + // re-enable the birthday calendar in case it's called directly with a user name + $isEnabled = $this->config->getUserValue($user, 'dav', 'generateBirthdayCalendar', 'yes'); + if ($isEnabled !== 'yes') { + $this->config->setUserValue($user, 'dav', 'generateBirthdayCalendar', 'yes'); + $output->writeln("Re-enabling birthday calendar for $user"); + } + $output->writeln("Start birthday calendar sync for $user"); $this->birthdayService->syncUser($user); return; @@ -77,6 +94,13 @@ class SyncBirthdayCalendar extends Command { $p->start(); $this->userManager->callForAllUsers(function($user) use ($p) { $p->advance(); + + $userId = $user->getUID(); + $isEnabled = $this->config->getUserValue($userId, 'dav', 'generateBirthdayCalendar', 'yes'); + if ($isEnabled !== 'yes') { + return; + } + /** @var IUser $user */ $this->birthdayService->syncUser($user->getUID()); }); @@ -84,4 +108,12 @@ class SyncBirthdayCalendar extends Command { $p->finish(); $output->writeln(''); } + + protected function verifyEnabled () { + $isEnabled = $this->config->getAppValue('dav', 'generateBirthdayCalendar', 'yes'); + + if ($isEnabled !== 'yes') { + throw new \InvalidArgumentException('Birthday calendars are disabled'); + } + } } diff --git a/apps/dav/lib/Controller/BirthdayCalendarController.php b/apps/dav/lib/Controller/BirthdayCalendarController.php new file mode 100644 index 00000000000..244111e3aec --- /dev/null +++ b/apps/dav/lib/Controller/BirthdayCalendarController.php @@ -0,0 +1,116 @@ +<?php +/** + * @copyright 2017, Georg Ehrke <oc.list@georgehrke.com> + * + * @author Georg Ehrke <oc.list@georgehrke.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\Controller; + +use OCA\DAV\BackgroundJob\GenerateBirthdayCalendarBackgroundJob; +use OCA\DAV\CalDAV\CalDavBackend; +use OCP\AppFramework\Controller; +use OCP\AppFramework\Http\JSONResponse; +use OCP\AppFramework\Http\Response; +use OCP\BackgroundJob\IJobList; +use OCP\IConfig; +use OCP\IDBConnection; +use OCP\IRequest; +use OCP\IUser; +use OCP\IUserManager; + +class BirthdayCalendarController extends Controller { + + /** + * @var IDBConnection + */ + protected $db; + + /** + * @var IConfig + */ + protected $config; + + /** + * @var IUserManager + */ + protected $userManager; + + /** + * @var CalDavBackend + */ + protected $caldavBackend; + + /** + * @var IJobList + */ + protected $jobList; + + /** + * BirthdayCalendar constructor. + * + * @param string $appName + * @param IRequest $request + * @param IDBConnection $db + * @param IConfig $config + * @param IJobList $jobList + * @param IUserManager $userManager + * @param CalDavBackend $calDavBackend + */ + public function __construct($appName, IRequest $request, + IDBConnection $db, IConfig $config, + IJobList $jobList, + IUserManager $userManager, + CalDavBackend $calDavBackend){ + parent::__construct($appName, $request); + $this->db = $db; + $this->config = $config; + $this->userManager = $userManager; + $this->jobList = $jobList; + $this->caldavBackend = $calDavBackend; + } + + /** + * @return Response + */ + public function enable() { + $this->config->setAppValue($this->appName, 'generateBirthdayCalendar', 'yes'); + + // add background job for each user + $this->userManager->callForAllUsers(function(IUser $user) { + $this->jobList->add(GenerateBirthdayCalendarBackgroundJob::class, [ + 'userId' => $user->getUID(), + ]); + }); + + return new JSONResponse([]); + } + + /** + * @return Response + */ + public function disable() { + $this->config->setAppValue($this->appName, 'generateBirthdayCalendar', 'no'); + + $this->jobList->remove(GenerateBirthdayCalendarBackgroundJob::class); + $this->caldavBackend->deleteAllBirthdayCalendars(); + + return new JSONResponse([]); + } +} diff --git a/apps/dav/lib/RootCollection.php b/apps/dav/lib/RootCollection.php index f2e9350c101..a39b8716110 100644 --- a/apps/dav/lib/RootCollection.php +++ b/apps/dav/lib/RootCollection.php @@ -39,6 +39,7 @@ class RootCollection extends SimpleCollection { public function __construct() { $config = \OC::$server->getConfig(); + $l10n = \OC::$server->getL10N('dav'); $random = \OC::$server->getSecureRandom(); $logger = \OC::$server->getLogger(); $userManager = \OC::$server->getUserManager(); @@ -68,7 +69,7 @@ class RootCollection extends SimpleCollection { $caldavBackend = new CalDavBackend($db, $userPrincipalBackend, $userManager, $groupManager, $random, $logger, $dispatcher); $calendarRoot = new CalendarRoot($userPrincipalBackend, $caldavBackend, 'principals/users'); $calendarRoot->disableListing = $disableListing; - $publicCalendarRoot = new PublicCalendarRoot($caldavBackend); + $publicCalendarRoot = new PublicCalendarRoot($caldavBackend, $l10n, $config); $publicCalendarRoot->disableListing = $disableListing; $systemTagCollection = new SystemTag\SystemTagsByIdCollection( diff --git a/apps/dav/lib/Server.php b/apps/dav/lib/Server.php index 8267c656988..4e8a9fd0a51 100644 --- a/apps/dav/lib/Server.php +++ b/apps/dav/lib/Server.php @@ -33,6 +33,7 @@ namespace OCA\DAV; use OC\AppFramework\Utility\TimeFactory; +use OCA\DAV\CalDAV\BirthdayService; use OCA\DAV\CalDAV\Schedule\IMipPlugin; use OCA\DAV\CardDAV\ImageExportPlugin; use OCA\DAV\CardDAV\PhotoCache; @@ -256,6 +257,10 @@ class Server { $view ))); } + $this->server->addPlugin(new \OCA\DAV\CalDAV\BirthdayCalendar\EnablePlugin( + \OC::$server->getConfig(), + \OC::$server->query(BirthdayService::class) + )); } // register plugins from apps diff --git a/apps/dav/lib/Settings/CalDAVSettings.php b/apps/dav/lib/Settings/CalDAVSettings.php index 1c85d19432c..a419afa1c55 100644 --- a/apps/dav/lib/Settings/CalDAVSettings.php +++ b/apps/dav/lib/Settings/CalDAVSettings.php @@ -47,6 +47,7 @@ class CalDAVSettings implements ISettings { public function getForm() { $parameters = [ 'send_invitations' => $this->config->getAppValue('dav', 'sendInvitations', 'yes'), + 'generate_birthday_calendar' => $this->config->getAppValue('dav', 'generateBirthdayCalendar', 'yes'), ]; return new TemplateResponse('dav', 'settings-admin-caldav', $parameters); diff --git a/apps/dav/templates/settings-admin-caldav.php b/apps/dav/templates/settings-admin-caldav.php index 8dbe5b38412..a3e3188fb4f 100644 --- a/apps/dav/templates/settings-admin-caldav.php +++ b/apps/dav/templates/settings-admin-caldav.php @@ -37,4 +37,12 @@ script('dav', [ <br> <em><?php p($l->t('Please make sure to properly set up the email settings above.')); ?></em> </p> + <p> + <input type="checkbox" name="caldav_generate_birthday_calendar" id="caldavGenerateBirthdayCalendar" class="checkbox" + <?php ($_['generate_birthday_calendar'] === 'yes') ? print_unescaped('checked="checked"') : null ?>/> + <label for="caldavGenerateBirthdayCalendar"><?php p($l->t('Automatically generate a birthday calendar')); ?></label> + <br> + <em><?php p($l->t('Birthday calendars will be generated by a background job.')); ?></em><br> + <em><?php p($l->t('Hence they will not be available immediately after enabling but will show up after some time.')); ?></em> + </p> </form> diff --git a/apps/dav/tests/unit/BackgroundJob/GenerateBirthdayCalendarBackgroundJobTest.php b/apps/dav/tests/unit/BackgroundJob/GenerateBirthdayCalendarBackgroundJobTest.php new file mode 100644 index 00000000000..010289a745a --- /dev/null +++ b/apps/dav/tests/unit/BackgroundJob/GenerateBirthdayCalendarBackgroundJobTest.php @@ -0,0 +1,103 @@ +<?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\Tests\unit\BackgroundJob; + +use OCA\DAV\BackgroundJob\GenerateBirthdayCalendarBackgroundJob; +use OCA\DAV\CalDAV\BirthdayService; +use OCA\DAV\CalDAV\CalDavBackend; +use OCA\DAV\CalDAV\CalendarHome; +use OCP\IConfig; +use Sabre\DAV\MkCol; +use Test\TestCase; + +class GenerateBirthdayCalendarBackgroundJobTest extends TestCase { + + /** @var BirthdayService | \PHPUnit_Framework_MockObject_MockObject */ + private $birthdayService; + + /** @var IConfig | \PHPUnit_Framework_MockObject_MockObject */ + private $config; + + /** @var \OCA\DAV\BackgroundJob\GenerateBirthdayCalendarBackgroundJob */ + private $backgroundJob; + + protected function setUp() { + parent::setUp(); + + $this->birthdayService = $this->createMock(BirthdayService::class); + $this->config = $this->createMock(IConfig::class); + + $this->backgroundJob = new GenerateBirthdayCalendarBackgroundJob( + $this->birthdayService, $this->config); + } + + public function testRun() { + $this->config->expects($this->once()) + ->method('getAppValue') + ->with('dav', 'generateBirthdayCalendar', 'yes') + ->will($this->returnValue('yes')); + + $this->config->expects($this->once()) + ->method('getUserValue') + ->with('user123', 'dav', 'generateBirthdayCalendar', 'yes') + ->will($this->returnValue('yes')); + + $this->birthdayService->expects($this->once()) + ->method('syncUser') + ->with('user123'); + + $this->backgroundJob->run(['userId' => 'user123']); + } + + public function testRunGloballyDisabled() { + $this->config->expects($this->once()) + ->method('getAppValue') + ->with('dav', 'generateBirthdayCalendar', 'yes') + ->will($this->returnValue('no')); + + $this->config->expects($this->never()) + ->method('getUserValue'); + + $this->birthdayService->expects($this->never()) + ->method('syncUser'); + + $this->backgroundJob->run(['userId' => 'user123']); + } + + public function testRunUserDisabled() { + $this->config->expects($this->once()) + ->method('getAppValue') + ->with('dav', 'generateBirthdayCalendar', 'yes') + ->will($this->returnValue('yes')); + + $this->config->expects($this->once()) + ->method('getUserValue') + ->with('user123', 'dav', 'generateBirthdayCalendar', 'yes') + ->will($this->returnValue('no')); + + $this->birthdayService->expects($this->never()) + ->method('syncUser'); + + $this->backgroundJob->run(['userId' => 'user123']); + } +} diff --git a/apps/dav/tests/unit/CalDAV/BirthdayCalendar/EnablePluginTest.php b/apps/dav/tests/unit/CalDAV/BirthdayCalendar/EnablePluginTest.php new file mode 100644 index 00000000000..44bf9237b2f --- /dev/null +++ b/apps/dav/tests/unit/CalDAV/BirthdayCalendar/EnablePluginTest.php @@ -0,0 +1,186 @@ +<?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\Tests\unit\CalDAV\BirthdayCalendar; + +use OCA\DAV\CalDAV\BirthdayCalendar\EnablePlugin; +use OCA\DAV\CalDAV\BirthdayService; +use OCA\DAV\CalDAV\Calendar; +use OCA\DAV\CalDAV\CalendarHome; +use OCP\IConfig; +use Test\TestCase; + +class EnablePluginTest extends TestCase { + + /** @var \Sabre\DAV\Server|\PHPUnit_Framework_MockObject_MockObject */ + protected $server; + + /** @var \OCP\IConfig|\PHPUnit_Framework_MockObject_MockObject */ + protected $config; + + /** @var BirthdayService |\PHPUnit_Framework_MockObject_MockObject */ + protected $birthdayService; + + /** @var \OCA\DAV\CalDAV\BirthdayCalendar\EnablePlugin $plugin */ + protected $plugin; + + protected $request; + + protected $response; + + public function setUp() { + parent::setUp(); + + $this->server = $this->createMock(\Sabre\DAV\Server::class); + $this->server->tree = $this->createMock(\Sabre\DAV\Tree::class); + $this->server->httpResponse = $this->createMock(\Sabre\HTTP\Response::class); + $this->server->xml = $this->createMock(\Sabre\DAV\Xml\Service::class); + + $this->config = $this->createMock(IConfig::class); + $this->birthdayService = $this->createMock(BirthdayService::class); + + $this->plugin = new EnablePlugin($this->config, $this->birthdayService); + $this->plugin->initialize($this->server); + + $this->request = $this->createMock(\Sabre\HTTP\RequestInterface::class); + $this->response = $this->createMock(\Sabre\HTTP\ResponseInterface::class); + } + + public function testGetFeatures() { + $this->assertEquals(['nc-enable-birthday-calendar'], $this->plugin->getFeatures()); + } + + public function testGetName() { + $this->assertEquals('nc-enable-birthday-calendar', $this->plugin->getPluginName()); + } + + public function testInitialize() { + $server = $this->createMock(\Sabre\DAV\Server::class); + + $plugin = new EnablePlugin($this->config, $this->birthdayService); + + $server->expects($this->at(0)) + ->method('on') + ->with('method:POST', [$plugin, 'httpPost']); + + $plugin->initialize($server); + } + + public function testHttpPostNoCalendarHome() { + $calendar = $this->createMock(Calendar::class); + + $this->server->expects($this->once()) + ->method('getRequestUri') + ->will($this->returnValue('/bar/foo')); + $this->server->tree->expects($this->once()) + ->method('getNodeForPath') + ->with('/bar/foo') + ->will($this->returnValue($calendar)); + + $this->config->expects($this->never()) + ->method('setUserValue'); + + $this->birthdayService->expects($this->never()) + ->method('syncUser'); + + $this->plugin->httpPost($this->request, $this->response); + } + + public function testHttpPostWrongRequest() { + $calendarHome = $this->createMock(CalendarHome::class); + + $this->server->expects($this->once()) + ->method('getRequestUri') + ->will($this->returnValue('/bar/foo')); + $this->server->tree->expects($this->once()) + ->method('getNodeForPath') + ->with('/bar/foo') + ->will($this->returnValue($calendarHome)); + + $this->request->expects($this->at(0)) + ->method('getBodyAsString') + ->will($this->returnValue('<nc:disable-birthday-calendar xmlns:nc="http://nextcloud.com/ns"/>')); + + $this->request->expects($this->at(1)) + ->method('getUrl') + ->will($this->returnValue('url_abc')); + + $this->server->xml->expects($this->once()) + ->method('parse') + ->will($this->returnCallback(function($requestBody, $url, &$documentType) { + $documentType = '{http://nextcloud.com/ns}disable-birthday-calendar'; + })); + + $this->config->expects($this->never()) + ->method('setUserValue'); + + $this->birthdayService->expects($this->never()) + ->method('syncUser'); + + $this->plugin->httpPost($this->request, $this->response); + } + + public function testHttpPost() { + $calendarHome = $this->createMock(CalendarHome::class); + + $this->server->expects($this->once()) + ->method('getRequestUri') + ->will($this->returnValue('/bar/foo')); + $this->server->tree->expects($this->once()) + ->method('getNodeForPath') + ->with('/bar/foo') + ->will($this->returnValue($calendarHome)); + + $calendarHome->expects($this->once()) + ->method('getOwner') + ->will($this->returnValue('principals/users/BlaBlub')); + + $this->request->expects($this->at(0)) + ->method('getBodyAsString') + ->will($this->returnValue('<nc:enable-birthday-calendar xmlns:nc="http://nextcloud.com/ns"/>')); + + $this->request->expects($this->at(1)) + ->method('getUrl') + ->will($this->returnValue('url_abc')); + + $this->server->xml->expects($this->once()) + ->method('parse') + ->will($this->returnCallback(function($requestBody, $url, &$documentType) { + $documentType = '{http://nextcloud.com/ns}enable-birthday-calendar'; + })); + + $this->config->expects($this->once()) + ->method('setUserValue') + ->with('BlaBlub', 'dav', 'generateBirthdayCalendar', 'yes'); + + $this->birthdayService->expects($this->once()) + ->method('syncUser') + ->with('BlaBlub'); + + $this->server->httpResponse->expects($this->once()) + ->method('setStatus') + ->with(204); + + $result = $this->plugin->httpPost($this->request, $this->response); + + $this->assertEquals(false, $result); + } +} diff --git a/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php b/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php index fc34a7af952..0b8978a0409 100644 --- a/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php +++ b/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php @@ -31,6 +31,7 @@ use DateTime; use DateTimeZone; use OCA\DAV\CalDAV\CalDavBackend; use OCA\DAV\CalDAV\Calendar; +use OCP\IConfig; use OCP\IL10N; use Sabre\DAV\PropPatch; use Sabre\DAV\Xml\Property\Href; @@ -131,6 +132,8 @@ class CalDavBackendTest extends AbstractCalDavBackend { return vsprintf($text, $parameters); })); + $config = $this->createMock(IConfig::class); + $this->userManager->expects($this->any()) ->method('userExists') ->willReturn(true); @@ -142,14 +145,14 @@ class CalDavBackendTest extends AbstractCalDavBackend { $calendarId = $this->createTestCalendar(); $calendars = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER); $this->assertCount(1, $calendars); - $calendar = new Calendar($this->backend, $calendars[0], $l10n); + $calendar = new Calendar($this->backend, $calendars[0], $l10n, $config); $this->dispatcher->expects($this->at(0)) ->method('dispatch') ->with('\OCA\DAV\CalDAV\CalDavBackend::updateShares'); $this->backend->updateShares($calendar, $add, []); $calendars = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER1); $this->assertCount(1, $calendars); - $calendar = new Calendar($this->backend, $calendars[0], $l10n); + $calendar = new Calendar($this->backend, $calendars[0], $l10n, $config); $acl = $calendar->getACL(); $this->assertAcl(self::UNIT_TEST_USER, '{DAV:}read', $acl); $this->assertAcl(self::UNIT_TEST_USER, '{DAV:}write', $acl); @@ -505,8 +508,9 @@ EOD; /** @var IL10N|\PHPUnit_Framework_MockObject_MockObject $l10n */ $l10n = $this->createMock(IL10N::class); + $config = $this->createMock(IConfig::class); - $calendar = new Calendar($this->backend, $calendarInfo, $l10n); + $calendar = new Calendar($this->backend, $calendarInfo, $l10n, $config); $calendar->setPublishStatus(true); $this->assertNotEquals(false, $calendar->getPublishStatus()); diff --git a/apps/dav/tests/unit/CalDAV/CalendarHomeTest.php b/apps/dav/tests/unit/CalDAV/CalendarHomeTest.php new file mode 100644 index 00000000000..a7981cfa159 --- /dev/null +++ b/apps/dav/tests/unit/CalDAV/CalendarHomeTest.php @@ -0,0 +1,81 @@ +<?php +/** + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @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\Tests\unit\CalDAV; + +use OCA\DAV\CalDAV\CalDavBackend; +use OCA\DAV\CalDAV\CalendarHome; +use Sabre\DAV\MkCol; +use Test\TestCase; + +class CalendarHomeTest extends TestCase { + + /** @var CalDavBackend | \PHPUnit_Framework_MockObject_MockObject */ + private $backend; + + /** @var array */ + private $principalInfo = []; + + /** @var CalendarHome */ + private $calendarHome; + + protected function setUp() { + parent::setUp(); + + $this->backend = $this->createMock(CalDavBackend::class); + $this->principalInfo = [ + 'uri' => 'user-principal-123', + ]; + + $this->calendarHome = new CalendarHome($this->backend, + $this->principalInfo); + } + + public function testCreateCalendarValidName() { + /** @var MkCol | \PHPUnit_Framework_MockObject_MockObject $mkCol */ + $mkCol = $this->createMock(MkCol::class); + + $mkCol->method('getResourceType') + ->will($this->returnValue(['{DAV:}collection', + '{urn:ietf:params:xml:ns:caldav}calendar'])); + $mkCol->method('getRemainingValues') + ->will($this->returnValue(['... properties ...'])); + + $this->backend->expects($this->once()) + ->method('createCalendar') + ->with('user-principal-123', 'name123', ['... properties ...']); + + $this->calendarHome->createExtendedCollection('name123', $mkCol); + } + + /** + * @expectedException \Sabre\DAV\Exception\MethodNotAllowed + * @expectedExceptionMessage The resource you tried to create has a reserved name + */ + public function testCreateCalendarReservedName() { + /** @var MkCol | \PHPUnit_Framework_MockObject_MockObject $mkCol */ + $mkCol = $this->createMock(MkCol::class); + + $this->calendarHome->createExtendedCollection('contact_birthdays', $mkCol); + } +} diff --git a/apps/dav/tests/unit/CalDAV/CalendarTest.php b/apps/dav/tests/unit/CalDAV/CalendarTest.php index 99ad640c447..dbdbf0dbafd 100644 --- a/apps/dav/tests/unit/CalDAV/CalendarTest.php +++ b/apps/dav/tests/unit/CalDAV/CalendarTest.php @@ -29,6 +29,7 @@ namespace OCA\DAV\Tests\unit\CalDAV; use OCA\DAV\CalDAV\BirthdayService; use OCA\DAV\CalDAV\CalDavBackend; use OCA\DAV\CalDAV\Calendar; +use OCP\IConfig; use OCP\IL10N; use Sabre\DAV\PropPatch; use Sabre\VObject\Reader; @@ -39,10 +40,14 @@ class CalendarTest extends TestCase { /** @var IL10N */ protected $l10n; + /** @var IConfig */ + protected $config; + public function setUp() { parent::setUp(); $this->l10n = $this->getMockBuilder(IL10N::class) ->disableOriginalConstructor()->getMock(); + $this->config = $this->createMock(IConfig::class); $this->l10n ->expects($this->any()) ->method('t') @@ -64,7 +69,7 @@ class CalendarTest extends TestCase { 'id' => 666, 'uri' => 'cal', ]; - $c = new Calendar($backend, $calendarInfo, $this->l10n); + $c = new Calendar($backend, $calendarInfo, $this->l10n, $this->config); $c->delete(); } @@ -84,7 +89,7 @@ class CalendarTest extends TestCase { 'id' => 666, 'uri' => 'cal', ]; - $c = new Calendar($backend, $calendarInfo, $this->l10n); + $c = new Calendar($backend, $calendarInfo, $this->l10n, $this->config); $c->delete(); } @@ -94,6 +99,8 @@ class CalendarTest extends TestCase { $backend->expects($this->never())->method('updateShares'); $backend->expects($this->never())->method('getShares'); + $this->config->expects($this->never())->method('setUserValue'); + $backend->expects($this->once())->method('deleteCalendar') ->with(666); @@ -103,7 +110,28 @@ class CalendarTest extends TestCase { 'id' => 666, 'uri' => 'cal', ]; - $c = new Calendar($backend, $calendarInfo, $this->l10n); + $c = new Calendar($backend, $calendarInfo, $this->l10n, $this->config); + $c->delete(); + } + + public function testDeleteBirthdayCalendar() { + /** @var \PHPUnit_Framework_MockObject_MockObject | CalDavBackend $backend */ + $backend = $this->createMock(CalDavBackend::class); + $backend->expects($this->once())->method('deleteCalendar') + ->with(666); + + $this->config->expects($this->once()) + ->method('setUserValue') + ->with('user1', 'dav', 'generateBirthdayCalendar', 'no'); + + $calendarInfo = [ + '{http://owncloud.org/ns}owner-principal' => 'principals/users/user1', + 'principaluri' => 'principals/users/user1', + 'id' => 666, + 'uri' => 'contact_birthdays', + ]; + + $c = new Calendar($backend, $calendarInfo, $this->l10n, $this->config); $c->delete(); } @@ -146,7 +174,7 @@ class CalendarTest extends TestCase { 'id' => 666, 'uri' => 'default' ]; - $c = new Calendar($backend, $calendarInfo, $this->l10n); + $c = new Calendar($backend, $calendarInfo, $this->l10n, $this->config); $propPatch = new PropPatch($mutations); if (!$shared) { @@ -176,7 +204,7 @@ class CalendarTest extends TestCase { if ($hasOwnerSet) { $calendarInfo['{http://owncloud.org/ns}owner-principal'] = 'user1'; } - $c = new Calendar($backend, $calendarInfo, $this->l10n); + $c = new Calendar($backend, $calendarInfo, $this->l10n, $this->config); $acl = $c->getACL(); $childAcl = $c->getChildACL(); @@ -271,7 +299,7 @@ class CalendarTest extends TestCase { $calendarInfo['{http://owncloud.org/ns}owner-principal'] = 'user1'; } - $c = new Calendar($backend, $calendarInfo, $this->l10n); + $c = new Calendar($backend, $calendarInfo, $this->l10n, $this->config); $children = $c->getChildren(); $this->assertEquals($expectedChildren, count($children)); $children = $c->getMultipleChildren(['event-0', 'event-1', 'event-2']); @@ -355,7 +383,7 @@ EOD; 'id' => 666, 'uri' => 'cal', ]; - $c = new Calendar($backend, $calendarInfo, $this->l10n); + $c = new Calendar($backend, $calendarInfo, $this->l10n, $this->config); $this->assertEquals(count($c->getChildren()), $expectedChildren); @@ -531,9 +559,9 @@ EOD; 'uri' => 'cal', ]; - $ownerCalendar = new Calendar($backend, $calendarInfoOwner, $this->l10n); - $rwCalendar = new Calendar($backend, $calendarInfoSharedRW, $this->l10n); - $roCalendar = new Calendar($backend, $calendarInfoSharedRO, $this->l10n); + $ownerCalendar = new Calendar($backend, $calendarInfoOwner, $this->l10n, $this->config); + $rwCalendar = new Calendar($backend, $calendarInfoSharedRW, $this->l10n, $this->config); + $roCalendar = new Calendar($backend, $calendarInfoSharedRO, $this->l10n, $this->config); $this->assertEquals(count($ownerCalendar->getChildren()), 2); $this->assertEquals(count($rwCalendar->getChildren()), 2); diff --git a/apps/dav/tests/unit/CalDAV/PublicCalendarRootTest.php b/apps/dav/tests/unit/CalDAV/PublicCalendarRootTest.php index 57707c6c0a4..c10b333e28d 100644 --- a/apps/dav/tests/unit/CalDAV/PublicCalendarRootTest.php +++ b/apps/dav/tests/unit/CalDAV/PublicCalendarRootTest.php @@ -30,6 +30,7 @@ namespace OCA\DAV\Tests\unit\CalDAV; use OCA\DAV\CalDAV\Calendar; use OCA\DAV\CalDAV\PublicCalendar; use OCA\DAV\Connector\Sabre\Principal; +use OCP\IConfig; use OCP\IGroupManager; use OCP\IL10N; use OCA\DAV\CalDAV\CalDavBackend; @@ -62,6 +63,8 @@ class PublicCalendarRootTest extends TestCase { protected $userManager; /** @var IGroupManager|\PHPUnit_Framework_MockObject_MockObject */ protected $groupManager; + /** @var IConfig */ + protected $config; /** @var ISecureRandom */ private $random; @@ -92,11 +95,12 @@ class PublicCalendarRootTest extends TestCase { $this->logger, $dispatcher ); - - $this->publicCalendarRoot = new PublicCalendarRoot($this->backend); - $this->l10n = $this->getMockBuilder(IL10N::class) ->disableOriginalConstructor()->getMock(); + $this->config = $this->createMock(IConfig::class); + + $this->publicCalendarRoot = new PublicCalendarRoot($this->backend, + $this->l10n, $this->config); } public function tearDown() { @@ -146,11 +150,11 @@ class PublicCalendarRootTest extends TestCase { $this->backend->createCalendar(self::UNIT_TEST_USER, 'Example', []); $calendarInfo = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER)[0]; - $calendar = new PublicCalendar($this->backend, $calendarInfo, $this->l10n); + $calendar = new PublicCalendar($this->backend, $calendarInfo, $this->l10n, $this->config); $publicUri = $calendar->setPublishStatus(true); $calendarInfo = $this->backend->getPublicCalendar($publicUri); - $calendar = new PublicCalendar($this->backend, $calendarInfo, $this->l10n); + $calendar = new PublicCalendar($this->backend, $calendarInfo, $this->l10n, $this->config); return $calendar; } diff --git a/apps/dav/tests/unit/CalDAV/PublicCalendarTest.php b/apps/dav/tests/unit/CalDAV/PublicCalendarTest.php index 9783d1a6267..98dd330f427 100644 --- a/apps/dav/tests/unit/CalDAV/PublicCalendarTest.php +++ b/apps/dav/tests/unit/CalDAV/PublicCalendarTest.php @@ -26,6 +26,7 @@ namespace OCA\DAV\Tests\unit\CalDAV; use OCA\DAV\CalDAV\PublicCalendar; use OCA\DAV\CalDAV\CalDavBackend; +use OCP\IConfig; use Sabre\VObject\Reader; class PublicCalendarTest extends CalendarTest { @@ -61,8 +62,10 @@ class PublicCalendarTest extends CalendarTest { 'id' => 666, 'uri' => 'cal', ]; + /** @var \PHPUnit_Framework_MockObject_MockObject | IConfig $config */ + $config = $this->createMock(IConfig::class); - $c = new PublicCalendar($backend, $calendarInfo, $this->l10n); + $c = new PublicCalendar($backend, $calendarInfo, $this->l10n, $config); $children = $c->getChildren(); $this->assertEquals(2, count($children)); $children = $c->getMultipleChildren(['event-0', 'event-1', 'event-2']); @@ -146,7 +149,9 @@ EOD; 'id' => 666, 'uri' => 'cal', ]; - $c = new PublicCalendar($backend, $calendarInfo, $this->l10n); + /** @var \PHPUnit_Framework_MockObject_MockObject | IConfig $config */ + $config = $this->createMock(IConfig::class); + $c = new PublicCalendar($backend, $calendarInfo, $this->l10n, $config); $this->assertEquals(count($c->getChildren()), 2); diff --git a/apps/dav/tests/unit/CardDAV/BirthdayServiceTest.php b/apps/dav/tests/unit/CardDAV/BirthdayServiceTest.php index 72b3c57bea6..867168033a4 100644 --- a/apps/dav/tests/unit/CardDAV/BirthdayServiceTest.php +++ b/apps/dav/tests/unit/CardDAV/BirthdayServiceTest.php @@ -28,6 +28,7 @@ use OCA\DAV\CalDAV\BirthdayService; use OCA\DAV\CalDAV\CalDavBackend; use OCA\DAV\CardDAV\CardDavBackend; use OCA\DAV\DAV\GroupPrincipalBackend; +use OCP\IConfig; use Sabre\VObject\Component\VCalendar; use Sabre\VObject\Reader; use Test\TestCase; @@ -42,15 +43,19 @@ class BirthdayServiceTest extends TestCase { private $cardDav; /** @var GroupPrincipalBackend | \PHPUnit_Framework_MockObject_MockObject */ private $groupPrincipalBackend; + /** @var IConfig | \PHPUnit_Framework_MockObject_MockObject */ + private $config; public function setUp() { parent::setUp(); - $this->calDav = $this->getMockBuilder(CalDavBackend::class)->disableOriginalConstructor()->getMock(); - $this->cardDav = $this->getMockBuilder(CardDavBackend::class)->disableOriginalConstructor()->getMock(); - $this->groupPrincipalBackend = $this->getMockBuilder(GroupPrincipalBackend::class)->disableOriginalConstructor()->getMock(); + $this->calDav = $this->createMock(CalDavBackend::class); + $this->cardDav = $this->createMock(CardDavBackend::class); + $this->groupPrincipalBackend = $this->createMock(GroupPrincipalBackend::class); + $this->config = $this->createMock(IConfig::class); - $this->service = new BirthdayService($this->calDav, $this->cardDav, $this->groupPrincipalBackend); + $this->service = new BirthdayService($this->calDav, $this->cardDav, + $this->groupPrincipalBackend, $this->config); } /** @@ -71,7 +76,52 @@ class BirthdayServiceTest extends TestCase { } } + public function testOnCardDeleteGloballyDisabled() { + $this->config->expects($this->once()) + ->method('getAppValue') + ->with('dav', 'generateBirthdayCalendar', 'yes') + ->will($this->returnValue('no')); + + $this->cardDav->expects($this->never())->method('getAddressBookById'); + + $this->service->onCardDeleted(666, 'gump.vcf'); + } + + public function testOnCardDeleteUserDisabled() { + $this->config->expects($this->once()) + ->method('getAppValue') + ->with('dav', 'generateBirthdayCalendar', 'yes') + ->will($this->returnValue('yes')); + + $this->config->expects($this->once()) + ->method('getUserValue') + ->with('user01', 'dav', 'generateBirthdayCalendar', 'yes') + ->will($this->returnValue('no')); + + $this->cardDav->expects($this->once())->method('getAddressBookById') + ->with(666) + ->willReturn([ + 'principaluri' => 'principals/users/user01', + 'uri' => 'default' + ]); + $this->cardDav->expects($this->once())->method('getShares')->willReturn([]); + $this->calDav->expects($this->never())->method('getCalendarByUri'); + $this->calDav->expects($this->never())->method('deleteCalendarObject'); + + $this->service->onCardDeleted(666, 'gump.vcf'); + } + public function testOnCardDeleted() { + $this->config->expects($this->once()) + ->method('getAppValue') + ->with('dav', 'generateBirthdayCalendar', 'yes') + ->will($this->returnValue('yes')); + + $this->config->expects($this->once()) + ->method('getUserValue') + ->with('user01', 'dav', 'generateBirthdayCalendar', 'yes') + ->will($this->returnValue('yes')); + $this->cardDav->expects($this->once())->method('getAddressBookById') ->with(666) ->willReturn([ @@ -91,10 +141,65 @@ class BirthdayServiceTest extends TestCase { $this->service->onCardDeleted(666, 'gump.vcf'); } + public function testOnCardChangedGloballyDisabled() { + $this->config->expects($this->once()) + ->method('getAppValue') + ->with('dav', 'generateBirthdayCalendar', 'yes') + ->will($this->returnValue('no')); + + $this->cardDav->expects($this->never())->method('getAddressBookById'); + + $service = $this->getMockBuilder(BirthdayService::class) + ->setMethods(['buildDateFromContact', 'birthdayEvenChanged']) + ->setConstructorArgs([$this->calDav, $this->cardDav, $this->groupPrincipalBackend, $this->config]) + ->getMock(); + + $service->onCardChanged(666, 'gump.vcf', ''); + } + + public function testOnCardChangedUserDisabled() { + $this->config->expects($this->once()) + ->method('getAppValue') + ->with('dav', 'generateBirthdayCalendar', 'yes') + ->will($this->returnValue('yes')); + + $this->config->expects($this->once()) + ->method('getUserValue') + ->with('user01', 'dav', 'generateBirthdayCalendar', 'yes') + ->will($this->returnValue('no')); + + $this->cardDav->expects($this->once())->method('getAddressBookById') + ->with(666) + ->willReturn([ + 'principaluri' => 'principals/users/user01', + 'uri' => 'default' + ]); + $this->cardDav->expects($this->once())->method('getShares')->willReturn([]); + $this->calDav->expects($this->never())->method('getCalendarByUri'); + + /** @var BirthdayService | \PHPUnit_Framework_MockObject_MockObject $service */ + $service = $this->getMockBuilder(BirthdayService::class) + ->setMethods(['buildDateFromContact', 'birthdayEvenChanged']) + ->setConstructorArgs([$this->calDav, $this->cardDav, $this->groupPrincipalBackend, $this->config]) + ->getMock(); + + $service->onCardChanged(666, 'gump.vcf', ''); + } + /** * @dataProvider providesCardChanges */ public function testOnCardChanged($expectedOp) { + $this->config->expects($this->once()) + ->method('getAppValue') + ->with('dav', 'generateBirthdayCalendar', 'yes') + ->will($this->returnValue('yes')); + + $this->config->expects($this->once()) + ->method('getUserValue') + ->with('user01', 'dav', 'generateBirthdayCalendar', 'yes') + ->will($this->returnValue('yes')); + $this->cardDav->expects($this->once())->method('getAddressBookById') ->with(666) ->willReturn([ @@ -111,7 +216,7 @@ class BirthdayServiceTest extends TestCase { /** @var BirthdayService | \PHPUnit_Framework_MockObject_MockObject $service */ $service = $this->getMockBuilder(BirthdayService::class) ->setMethods(['buildDateFromContact', 'birthdayEvenChanged']) - ->setConstructorArgs([$this->calDav, $this->cardDav, $this->groupPrincipalBackend]) + ->setConstructorArgs([$this->calDav, $this->cardDav, $this->groupPrincipalBackend, $this->config]) ->getMock(); if ($expectedOp === 'delete') { diff --git a/apps/dav/tests/unit/Controller/BirthdayCalendarControllerTest.php b/apps/dav/tests/unit/Controller/BirthdayCalendarControllerTest.php new file mode 100644 index 00000000000..46ed58df4f9 --- /dev/null +++ b/apps/dav/tests/unit/Controller/BirthdayCalendarControllerTest.php @@ -0,0 +1,123 @@ +<?php +/** + * @copyright 2017, Georg Ehrke <oc.list@georgehrke.com> + * + * @author Georg Ehrke <oc.list@georgehrke.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\Tests\Unit\DAV\Controller; + +use OCA\DAV\BackgroundJob\GenerateBirthdayCalendarBackgroundJob; +use OCA\DAV\CalDAV\CalDavBackend; +use OCA\DAV\Controller\BirthdayCalendarController; +use OCP\BackgroundJob\IJobList; +use OCP\IConfig; +use OCP\IDBConnection; +use OCP\IRequest; +use OCP\IUser; +use OCP\IUserManager; +use Test\TestCase; + +class BirthdayCalendarControllerTest extends TestCase { + + /** @var IConfig|\PHPUnit_Framework_MockObject_MockObject */ + private $config; + + /** @var IRequest|\PHPUnit_Framework_MockObject_MockObject */ + private $request; + + /** @var IDBConnection|\PHPUnit_Framework_MockObject_MockObject */ + private $db; + + /** @var IJobList|\PHPUnit_Framework_MockObject_MockObject */ + private $jobList; + + /** @var IUserManager|\PHPUnit_Framework_MockObject_MockObject */ + private $userManager; + + /** @var CalDavBackend|\PHPUnit_Framework_MockObject_MockObject */ + private $caldav; + + /** @var BirthdayCalendarController|\PHPUnit_Framework_MockObject_MockObject */ + private $controller; + + public function setUp() { + parent::setUp(); + + $this->config = $this->createMock(IConfig::class); + $this->request = $this->createMock(IRequest::class); + $this->db = $this->createMock(IDBConnection::class); + $this->jobList = $this->createMock(IJobList::class); + $this->userManager = $this->createMock(IUserManager::class); + $this->caldav = $this->createMock(CalDavBackend::class); + + $this->controller = new BirthdayCalendarController('dav', + $this->request, $this->db, $this->config, $this->jobList, + $this->userManager, $this->caldav); + } + + public function testEnable() { + $this->config->expects($this->once()) + ->method('setAppValue') + ->with('dav', 'generateBirthdayCalendar', 'yes'); + + $this->userManager->expects($this->once()) + ->method('callForAllUsers') + ->will($this->returnCallback(function($closure) { + $user1 = $this->createMock(IUser::class); + $user1->method('getUID')->will($this->returnValue('uid1')); + $user2 = $this->createMock(IUser::class); + $user2->method('getUID')->will($this->returnValue('uid2')); + $user3 = $this->createMock(IUser::class); + $user3->method('getUID')->will($this->returnValue('uid3')); + + $closure($user1); + $closure($user2); + $closure($user3); + })); + + $this->jobList->expects($this->at(0)) + ->method('add') + ->with(GenerateBirthdayCalendarBackgroundJob::class, ['userId' => 'uid1']); + $this->jobList->expects($this->at(1)) + ->method('add') + ->with(GenerateBirthdayCalendarBackgroundJob::class, ['userId' => 'uid2']); + $this->jobList->expects($this->at(2)) + ->method('add') + ->with(GenerateBirthdayCalendarBackgroundJob::class, ['userId' => 'uid3']); + + $response = $this->controller->enable(); + $this->assertInstanceOf('OCP\AppFramework\Http\JSONResponse', $response); + } + + public function testDisable() { + $this->config->expects($this->once()) + ->method('setAppValue') + ->with('dav', 'generateBirthdayCalendar', 'no'); + $this->jobList->expects($this->once()) + ->method('remove') + ->with(GenerateBirthdayCalendarBackgroundJob::class); + $this->caldav->expects($this->once()) + ->method('deleteAllBirthdayCalendars'); + + $response = $this->controller->disable(); + $this->assertInstanceOf('OCP\AppFramework\Http\JSONResponse', $response); + } + +} |