diff --git a/appinfo/app.php b/appinfo/app.php deleted file mode 100644 index bf0ac6c98..000000000 --- a/appinfo/app.php +++ /dev/null @@ -1,39 +0,0 @@ - - * - * @author Julius Härtl - * - * @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 . - * - */ - -use OCA\Deck\AppInfo\Application; -use OCP\AppFramework\QueryException; - -if ((@include_once __DIR__ . '/../vendor/autoload.php')=== false) { - throw new Exception('Cannot include autoload. Did you run install dependencies using composer?'); -} - -try { - /** @var Application $app */ - $app = \OC::$server->query(Application::class); - $app->register(); -} catch (QueryException $e) { -} - -/** Load activity style global so it is availabile in the activity app as well */ -\OC_Util::addStyle('deck', 'activity'); diff --git a/appinfo/info.xml b/appinfo/info.xml index 34c1ed4ed..95fd78a95 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -69,4 +69,13 @@ OCA\Deck\Provider\DeckProvider + + + Deck + deck.page.index + deck.svg + 10 + + + diff --git a/css/deck.scss b/css/deck.scss new file mode 100644 index 000000000..c142c3fbf --- /dev/null +++ b/css/deck.scss @@ -0,0 +1 @@ +@include icon-black-white('deck', 'deck', 1); diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index d4fd3a7a9..048dc5b3c 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -23,249 +23,11 @@ namespace OCA\Deck\AppInfo; -use Exception; -use OC_Util; -use OCA\Deck\Activity\CommentEventHandler; -use OCA\Deck\Capabilities; -use OCA\Deck\Collaboration\Resources\ResourceProvider; -use OCA\Deck\Collaboration\Resources\ResourceProviderCard; -use OCA\Deck\Dashboard\DeckWidget; -use OCA\Deck\Db\Acl; -use OCA\Deck\Db\AclMapper; -use OCA\Deck\Db\AssignedUsersMapper; -use OCA\Deck\Db\BoardMapper; -use OCA\Deck\Db\CardMapper; -use OCA\Deck\Middleware\DefaultBoardMiddleware; -use OCA\Deck\Middleware\ExceptionMiddleware; -use OCA\Deck\Notification\Notifier; -use OCA\Deck\Service\FullTextSearchService; -use OCA\Deck\Service\PermissionService; -use OCP\AppFramework\App; -use OCP\Collaboration\Resources\IManager; -use OCP\Collaboration\Resources\IProviderManager; -use OCP\Comments\CommentsEntityEvent; -use OCP\Dashboard\RegisterWidgetEvent; -use OCP\EventDispatcher\Event; -use OCP\EventDispatcher\IEventDispatcher; -use OCP\FullTextSearch\IFullTextSearchManager; -use OCP\IGroup; -use OCP\IServerContainer; -use OCP\IUser; -use OCP\IUserManager; -use OCP\IURLGenerator; -use OCP\Util; - -class Application extends App { - public const APP_ID = 'deck'; - - public const COMMENT_ENTITY_TYPE = 'deckCard'; - - /** @var IServerContainer */ - private $server; - - /** @var FullTextSearchService */ - private $fullTextSearchService; - - /** @var IFullTextSearchManager */ - private $fullTextSearchManager; - - public function __construct(array $urlParams = []) { - parent::__construct('deck', $urlParams); - - $container = $this->getContainer(); - $server = $this->getContainer()->getServer(); - - $this->server = $server; - - $container->registerCapability(Capabilities::class); - $container->registerMiddleWare(ExceptionMiddleware::class); - $container->registerMiddleWare(DefaultBoardMiddleware::class); - - $container->registerService('databaseType', static function () use ($server) { - return $server->getConfig()->getSystemValue('dbtype', 'sqlite'); - }); - $container->registerService('database4ByteSupport', static function () use ($server) { - return $server->getDatabaseConnection()->supports4ByteText(); - }); - - $version = OC_Util::getVersion()[0]; - if ($version >= 20) { - /** @var IEventDispatcher $dispatcher */ - $dispatcher = $container->getServer()->query(IEventDispatcher::class); - $dispatcher->addListener(RegisterWidgetEvent::class, function (RegisterWidgetEvent $event) use ($container) { - $event->registerWidget(DeckWidget::class); - }); - } - } - - public function register(): void { - $this->registerNavigationEntry(); - $this->registerUserGroupHooks(); - $this->registerNotifications(); - $this->registerCommentsEntity(); - $this->registerFullTextSearch(); - $this->registerCollaborationResources(); - } - - public function registerNavigationEntry(): void { - $container = $this->getContainer(); - $this->server->getNavigationManager()->add(static function () use ($container) { - $urlGenerator = $container->query(IURLGenerator::class); - return [ - 'id' => 'deck', - 'order' => 10, - 'href' => $urlGenerator->linkToRoute('deck.page.index'), - 'icon' => $urlGenerator->imagePath('deck', 'deck.svg'), - 'name' => 'Deck', - ]; - }); - } - - private function registerUserGroupHooks(): void { - $container = $this->getContainer(); - // Delete user/group acl entries when they get deleted - /** @var IUserManager $userManager */ - $userManager = $this->server->getUserManager(); - $userManager->listen('\OC\User', 'postDelete', static function (IUser $user) use ($container) { - // delete existing acl entries for deleted user - /** @var AclMapper $aclMapper */ - $aclMapper = $container->query(AclMapper::class); - $acls = $aclMapper->findByParticipant(Acl::PERMISSION_TYPE_USER, $user->getUID()); - foreach ($acls as $acl) { - $aclMapper->delete($acl); - } - // delete existing user assignments - $assignmentMapper = $container->query(AssignedUsersMapper::class); - $assignments = $assignmentMapper->findByUserId($user->getUID()); - foreach ($assignments as $assignment) { - $assignmentMapper->delete($assignment); - } - - /** @var BoardMapper $boardMapper */ - $boardMapper = $container->query(BoardMapper::class); - $boards = $boardMapper->findAllByOwner($user->getUID()); - foreach ($boards as $board) { - $boardMapper->delete($board); - } - }); - - /** @var IUserManager $userManager */ - $groupManager = $this->server->getGroupManager(); - $groupManager->listen('\OC\Group', 'postDelete', static function (IGroup $group) use ($container) { - /** @var AclMapper $aclMapper */ - $aclMapper = $container->query(AclMapper::class); - $aclMapper->findByParticipant(Acl::PERMISSION_TYPE_GROUP, $group->getGID()); - $acls = $aclMapper->findByParticipant(Acl::PERMISSION_TYPE_GROUP, $group->getGID()); - foreach ($acls as $acl) { - $aclMapper->delete($acl); - } - }); - } - - public function registerNotifications(): void { - $notificationManager = $this->server->getNotificationManager(); - $notificationManager->registerNotifierService(Notifier::class); - } - - public function registerCommentsEntity(): void { - $this->server->getEventDispatcher()->addListener(CommentsEntityEvent::EVENT_ENTITY, function (CommentsEntityEvent $event) { - $event->addEntityCollection(self::COMMENT_ENTITY_TYPE, function ($name) { - /** @var CardMapper */ - $cardMapper = $this->getContainer()->query(CardMapper::class); - $permissionService = $this->getContainer()->query(PermissionService::class); - - try { - return $permissionService->checkPermission($cardMapper, (int) $name, Acl::PERMISSION_READ); - } catch (\Exception $e) { - return false; - } - }); - }); - $this->registerCommentsEventHandler(); - } - - /** - */ - protected function registerCommentsEventHandler(): void { - $this->server->getCommentsManager()->registerEventHandler(function () { - return $this->getContainer()->query(CommentEventHandler::class); - }); +$version = \OC_Util::getVersion()[0]; +if ($version >= 20) { + class Application extends Application20 { } - - protected function registerCollaborationResources(): void { - $version = OC_Util::getVersion()[0]; - if ($version < 16) { - return; - } - - /** - * Register Collaboration ResourceProvider - * - * @Todo: Remove if min-version is 18 - */ - if ($version < 18) { - /** @var IManager $resourceManager */ - $resourceManager = $this->getContainer()->query(IManager::class); - } else { - /** @var IProviderManager $resourceManager */ - $resourceManager = $this->getContainer()->query(IProviderManager::class); - } - $resourceManager->registerResourceProvider(ResourceProvider::class); - $resourceManager->registerResourceProvider(ResourceProviderCard::class); - - $this->server->getEventDispatcher()->addListener('\OCP\Collaboration\Resources::loadAdditionalScripts', static function () { - Util::addScript('deck', 'collections'); - }); - } - - public function registerFullTextSearch(): void { - if (Util::getVersion()[0] < 16) { - return; - } - - $c = $this->getContainer(); - try { - $this->fullTextSearchService = $c->query(FullTextSearchService::class); - $this->fullTextSearchManager = $c->query(IFullTextSearchManager::class); - } catch (Exception $e) { - return; - } - - if (!$this->fullTextSearchManager->isAvailable()) { - return; - } - - /** @var IEventDispatcher $eventDispatcher */ - $eventDispatcher = $this->server->query(IEventDispatcher::class); - $eventDispatcher->addListener( - '\OCA\Deck\Card::onCreate', function (Event $e) { - $this->fullTextSearchService->onCardCreated($e); - } - ); - $eventDispatcher->addListener( - '\OCA\Deck\Card::onUpdate', function (Event $e) { - $this->fullTextSearchService->onCardUpdated($e); - } - ); - $eventDispatcher->addListener( - '\OCA\Deck\Card::onDelete', function (Event $e) { - $this->fullTextSearchService->onCardDeleted($e); - } - ); - $eventDispatcher->addListener( - '\OCA\Deck\Board::onShareNew', function (Event $e) { - $this->fullTextSearchService->onBoardShares($e); - } - ); - $eventDispatcher->addListener( - '\OCA\Deck\Board::onShareEdit', function (Event $e) { - $this->fullTextSearchService->onBoardShares($e); - } - ); - $eventDispatcher->addListener( - '\OCA\Deck\Board::onShareDelete', function (Event $e) { - $this->fullTextSearchService->onBoardShares($e); - } - ); +} else { + class Application extends ApplicationLegacy { } } diff --git a/lib/AppInfo/Application20.php b/lib/AppInfo/Application20.php new file mode 100644 index 000000000..91020d60c --- /dev/null +++ b/lib/AppInfo/Application20.php @@ -0,0 +1,252 @@ + + * + * @author Julius Härtl + * + * @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\Deck\AppInfo; + +use Exception; +use OC_Util; +use OCA\Deck\Activity\CommentEventHandler; +use OCA\Deck\Capabilities; +use OCA\Deck\Collaboration\Resources\ResourceProvider; +use OCA\Deck\Collaboration\Resources\ResourceProviderCard; +use OCA\Deck\Dashboard\DeckWidget; +use OCA\Deck\Db\Acl; +use OCA\Deck\Db\AclMapper; +use OCA\Deck\Db\AssignedUsersMapper; +use OCA\Deck\Db\BoardMapper; +use OCA\Deck\Db\CardMapper; +use OCA\Deck\Middleware\DefaultBoardMiddleware; +use OCA\Deck\Middleware\ExceptionMiddleware; +use OCA\Deck\Notification\Notifier; +use OCA\Deck\Search\DeckProvider; +use OCA\Deck\Service\FullTextSearchService; +use OCA\Deck\Service\PermissionService; +use OCP\AppFramework\App; +use OCP\AppFramework\Bootstrap\IBootContext; +use OCP\AppFramework\Bootstrap\IBootstrap; +use OCP\AppFramework\Bootstrap\IRegistrationContext; +use OCP\Collaboration\Resources\IManager; +use OCP\Collaboration\Resources\IProviderManager; +use OCP\Comments\CommentsEntityEvent; +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\FullTextSearch\IFullTextSearchManager; +use OCP\IConfig; +use OCP\IContainer; +use OCP\IDBConnection; +use OCP\IGroup; +use OCP\IServerContainer; +use OCP\IUser; +use OCP\IUserManager; +use OCP\Util; + +class Application20 extends App implements IBootstrap { + public const APP_ID = 'deck'; + + public const COMMENT_ENTITY_TYPE = 'deckCard'; + + /** @var IServerContainer */ + private $server; + + /** @var FullTextSearchService */ + private $fullTextSearchService; + + /** @var IFullTextSearchManager */ + private $fullTextSearchManager; + + public function __construct(array $urlParams = []) { + parent::__construct(self::APP_ID, $urlParams); + + $this->server = \OC::$server; + } + + public function boot(IBootContext $context): void { + $notificationManager = $context->getServerContainer()->get(\OCP\Notification\IManager::class); + $notificationManager->registerNotifierService(Notifier::class); + \OCP\Util::addStyle('deck', 'deck'); + } + + public function register(IRegistrationContext $context): void { + if ((@include_once __DIR__ . '/../../vendor/autoload.php') === false) { + throw new Exception('Cannot include autoload. Did you run install dependencies using composer?'); + } + + $context->registerCapability(Capabilities::class); + $context->registerMiddleWare(ExceptionMiddleware::class); + $context->registerMiddleWare(DefaultBoardMiddleware::class); + + $context->registerService('databaseType', static function (IContainer $c) { + return $c->get(IConfig::class)->getSystemValue('dbtype', 'sqlite'); + }); + $context->registerService('database4ByteSupport', static function (IContainer $c) { + return $c->get(IDBConnection::class)->supports4ByteText(); + }); + + $context->registerSearchProvider(DeckProvider::class); + + $context->registerDashboardWidget(DeckWidget::class); + + $this->registerUserGroupHooks(); + + $this->registerCommentsEntity(); + $this->registerFullTextSearch(); + $this->registerCollaborationResources(); + } + + private function registerUserGroupHooks(): void { + $container = $this->getContainer(); + // Delete user/group acl entries when they get deleted + /** @var IUserManager $userManager */ + $userManager = $this->server->getUserManager(); + $userManager->listen('\OC\User', 'postDelete', static function (IUser $user) use ($container) { + // delete existing acl entries for deleted user + /** @var AclMapper $aclMapper */ + $aclMapper = $container->query(AclMapper::class); + $acls = $aclMapper->findByParticipant(Acl::PERMISSION_TYPE_USER, $user->getUID()); + foreach ($acls as $acl) { + $aclMapper->delete($acl); + } + // delete existing user assignments + $assignmentMapper = $container->query(AssignedUsersMapper::class); + $assignments = $assignmentMapper->findByUserId($user->getUID()); + foreach ($assignments as $assignment) { + $assignmentMapper->delete($assignment); + } + + /** @var BoardMapper $boardMapper */ + $boardMapper = $container->query(BoardMapper::class); + $boards = $boardMapper->findAllByOwner($user->getUID()); + foreach ($boards as $board) { + $boardMapper->delete($board); + } + }); + + /** @var IUserManager $userManager */ + $groupManager = $this->server->getGroupManager(); + $groupManager->listen('\OC\Group', 'postDelete', static function (IGroup $group) use ($container) { + /** @var AclMapper $aclMapper */ + $aclMapper = $container->query(AclMapper::class); + $aclMapper->findByParticipant(Acl::PERMISSION_TYPE_GROUP, $group->getGID()); + $acls = $aclMapper->findByParticipant(Acl::PERMISSION_TYPE_GROUP, $group->getGID()); + foreach ($acls as $acl) { + $aclMapper->delete($acl); + } + }); + } + + public function registerCommentsEntity(): void { + $this->server->getEventDispatcher()->addListener(CommentsEntityEvent::EVENT_ENTITY, function (CommentsEntityEvent $event) { + $event->addEntityCollection(self::COMMENT_ENTITY_TYPE, function ($name) { + /** @var CardMapper */ + $cardMapper = $this->getContainer()->query(CardMapper::class); + $permissionService = $this->getContainer()->query(PermissionService::class); + + try { + return $permissionService->checkPermission($cardMapper, (int) $name, Acl::PERMISSION_READ); + } catch (\Exception $e) { + return false; + } + }); + }); + $this->registerCommentsEventHandler(); + } + + protected function registerCommentsEventHandler(): void { + $this->server->getCommentsManager()->registerEventHandler(function () { + return $this->getContainer()->query(CommentEventHandler::class); + }); + } + + protected function registerCollaborationResources(): void { + $version = OC_Util::getVersion()[0]; + /** + * Register Collaboration ResourceProvider + * + * @Todo: Remove if min-version is 18 + */ + if ($version < 18) { + /** @var IManager $resourceManager */ + $resourceManager = $this->getContainer()->query(IManager::class); + } else { + /** @var IProviderManager $resourceManager */ + $resourceManager = $this->getContainer()->query(IProviderManager::class); + } + $resourceManager->registerResourceProvider(ResourceProvider::class); + $resourceManager->registerResourceProvider(ResourceProviderCard::class); + + $this->server->getEventDispatcher()->addListener('\OCP\Collaboration\Resources::loadAdditionalScripts', static function () { + Util::addScript('deck', 'collections'); + }); + } + + public function registerFullTextSearch(): void { + if (Util::getVersion()[0] < 16) { + return; + } + + $c = $this->getContainer(); + try { + $this->fullTextSearchService = $c->query(FullTextSearchService::class); + $this->fullTextSearchManager = $c->query(IFullTextSearchManager::class); + } catch (Exception $e) { + return; + } + + if (!$this->fullTextSearchManager->isAvailable()) { + return; + } + + /** @var IEventDispatcher $eventDispatcher */ + $eventDispatcher = $this->server->query(IEventDispatcher::class); + $eventDispatcher->addListener( + '\OCA\Deck\Card::onCreate', function (Event $e) { + $this->fullTextSearchService->onCardCreated($e); + } + ); + $eventDispatcher->addListener( + '\OCA\Deck\Card::onUpdate', function (Event $e) { + $this->fullTextSearchService->onCardUpdated($e); + } + ); + $eventDispatcher->addListener( + '\OCA\Deck\Card::onDelete', function (Event $e) { + $this->fullTextSearchService->onCardDeleted($e); + } + ); + $eventDispatcher->addListener( + '\OCA\Deck\Board::onShareNew', function (Event $e) { + $this->fullTextSearchService->onBoardShares($e); + } + ); + $eventDispatcher->addListener( + '\OCA\Deck\Board::onShareEdit', function (Event $e) { + $this->fullTextSearchService->onBoardShares($e); + } + ); + $eventDispatcher->addListener( + '\OCA\Deck\Board::onShareDelete', function (Event $e) { + $this->fullTextSearchService->onBoardShares($e); + } + ); + } +} diff --git a/lib/AppInfo/ApplicationLegacy.php b/lib/AppInfo/ApplicationLegacy.php new file mode 100644 index 000000000..b194dd37f --- /dev/null +++ b/lib/AppInfo/ApplicationLegacy.php @@ -0,0 +1,248 @@ + + * + * @author Julius Härtl + * + * @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\Deck\AppInfo; + +use Exception; +use OC_Util; +use OCA\Deck\Activity\CommentEventHandler; +use OCA\Deck\Capabilities; +use OCA\Deck\Collaboration\Resources\ResourceProvider; +use OCA\Deck\Collaboration\Resources\ResourceProviderCard; +use OCA\Deck\Db\Acl; +use OCA\Deck\Db\AclMapper; +use OCA\Deck\Db\AssignedUsersMapper; +use OCA\Deck\Db\BoardMapper; +use OCA\Deck\Db\CardMapper; +use OCA\Deck\Middleware\DefaultBoardMiddleware; +use OCA\Deck\Middleware\ExceptionMiddleware; +use OCA\Deck\Notification\Notifier; +use OCA\Deck\Service\FullTextSearchService; +use OCA\Deck\Service\PermissionService; +use OCP\AppFramework\App; +use OCP\Collaboration\Resources\IManager; +use OCP\Collaboration\Resources\IProviderManager; +use OCP\Comments\CommentsEntityEvent; +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\FullTextSearch\IFullTextSearchManager; +use OCP\IGroup; +use OCP\IServerContainer; +use OCP\IUser; +use OCP\IUserManager; +use OCP\Util; + +if ((@include_once __DIR__ . '/../../vendor/autoload.php') === false) { + throw new Exception('Cannot include autoload. Did you run install dependencies using composer?'); +} + +class ApplicationLegacy extends App { + public const APP_ID = 'deck'; + + public const COMMENT_ENTITY_TYPE = 'deckCard'; + + /** @var IServerContainer */ + private $server; + + /** @var FullTextSearchService */ + private $fullTextSearchService; + + /** @var IFullTextSearchManager */ + private $fullTextSearchManager; + + public function __construct(array $urlParams = []) { + parent::__construct('deck', $urlParams); + + $container = $this->getContainer(); + $server = $this->getContainer()->getServer(); + + $this->server = $server; + + $container->registerCapability(Capabilities::class); + $container->registerMiddleWare(ExceptionMiddleware::class); + $container->registerMiddleWare(DefaultBoardMiddleware::class); + + $container->registerService('databaseType', static function () use ($server) { + return $server->getConfig()->getSystemValue('dbtype', 'sqlite'); + }); + $container->registerService('database4ByteSupport', static function () use ($server) { + return $server->getDatabaseConnection()->supports4ByteText(); + }); + } + + public function register(): void { + $this->registerUserGroupHooks(); + $this->registerNotifications(); + $this->registerCommentsEntity(); + $this->registerFullTextSearch(); + $this->registerCollaborationResources(); + } + + private function registerUserGroupHooks(): void { + $container = $this->getContainer(); + // Delete user/group acl entries when they get deleted + /** @var IUserManager $userManager */ + $userManager = $this->server->getUserManager(); + $userManager->listen('\OC\User', 'postDelete', static function (IUser $user) use ($container) { + // delete existing acl entries for deleted user + /** @var AclMapper $aclMapper */ + $aclMapper = $container->query(AclMapper::class); + $acls = $aclMapper->findByParticipant(Acl::PERMISSION_TYPE_USER, $user->getUID()); + foreach ($acls as $acl) { + $aclMapper->delete($acl); + } + // delete existing user assignments + $assignmentMapper = $container->query(AssignedUsersMapper::class); + $assignments = $assignmentMapper->findByUserId($user->getUID()); + foreach ($assignments as $assignment) { + $assignmentMapper->delete($assignment); + } + + /** @var BoardMapper $boardMapper */ + $boardMapper = $container->query(BoardMapper::class); + $boards = $boardMapper->findAllByOwner($user->getUID()); + foreach ($boards as $board) { + $boardMapper->delete($board); + } + }); + + /** @var IUserManager $userManager */ + $groupManager = $this->server->getGroupManager(); + $groupManager->listen('\OC\Group', 'postDelete', static function (IGroup $group) use ($container) { + /** @var AclMapper $aclMapper */ + $aclMapper = $container->query(AclMapper::class); + $aclMapper->findByParticipant(Acl::PERMISSION_TYPE_GROUP, $group->getGID()); + $acls = $aclMapper->findByParticipant(Acl::PERMISSION_TYPE_GROUP, $group->getGID()); + foreach ($acls as $acl) { + $aclMapper->delete($acl); + } + }); + } + + public function registerNotifications(): void { + $notificationManager = $this->server->getNotificationManager(); + $notificationManager->registerNotifierService(Notifier::class); + } + + public function registerCommentsEntity(): void { + $this->server->getEventDispatcher()->addListener(CommentsEntityEvent::EVENT_ENTITY, function (CommentsEntityEvent $event) { + $event->addEntityCollection(self::COMMENT_ENTITY_TYPE, function ($name) { + /** @var CardMapper */ + $cardMapper = $this->getContainer()->query(CardMapper::class); + $permissionService = $this->getContainer()->query(PermissionService::class); + + try { + return $permissionService->checkPermission($cardMapper, (int) $name, Acl::PERMISSION_READ); + } catch (\Exception $e) { + return false; + } + }); + }); + $this->registerCommentsEventHandler(); + } + + /** + */ + protected function registerCommentsEventHandler(): void { + $this->server->getCommentsManager()->registerEventHandler(function () { + return $this->getContainer()->query(CommentEventHandler::class); + }); + } + + protected function registerCollaborationResources(): void { + $version = OC_Util::getVersion()[0]; + if ($version < 16) { + return; + } + + /** + * Register Collaboration ResourceProvider + * + * @Todo: Remove if min-version is 18 + */ + if ($version < 18) { + /** @var IManager $resourceManager */ + $resourceManager = $this->getContainer()->query(IManager::class); + } else { + /** @var IProviderManager $resourceManager */ + $resourceManager = $this->getContainer()->query(IProviderManager::class); + } + $resourceManager->registerResourceProvider(ResourceProvider::class); + $resourceManager->registerResourceProvider(ResourceProviderCard::class); + + $this->server->getEventDispatcher()->addListener('\OCP\Collaboration\Resources::loadAdditionalScripts', static function () { + Util::addScript('deck', 'collections'); + }); + } + + public function registerFullTextSearch(): void { + if (Util::getVersion()[0] < 16) { + return; + } + + $c = $this->getContainer(); + try { + $this->fullTextSearchService = $c->query(FullTextSearchService::class); + $this->fullTextSearchManager = $c->query(IFullTextSearchManager::class); + } catch (Exception $e) { + return; + } + + if (!$this->fullTextSearchManager->isAvailable()) { + return; + } + + /** @var IEventDispatcher $eventDispatcher */ + $eventDispatcher = $this->server->query(IEventDispatcher::class); + $eventDispatcher->addListener( + '\OCA\Deck\Card::onCreate', function (Event $e) { + $this->fullTextSearchService->onCardCreated($e); + } + ); + $eventDispatcher->addListener( + '\OCA\Deck\Card::onUpdate', function (Event $e) { + $this->fullTextSearchService->onCardUpdated($e); + } + ); + $eventDispatcher->addListener( + '\OCA\Deck\Card::onDelete', function (Event $e) { + $this->fullTextSearchService->onCardDeleted($e); + } + ); + $eventDispatcher->addListener( + '\OCA\Deck\Board::onShareNew', function (Event $e) { + $this->fullTextSearchService->onBoardShares($e); + } + ); + $eventDispatcher->addListener( + '\OCA\Deck\Board::onShareEdit', function (Event $e) { + $this->fullTextSearchService->onBoardShares($e); + } + ); + $eventDispatcher->addListener( + '\OCA\Deck\Board::onShareDelete', function (Event $e) { + $this->fullTextSearchService->onBoardShares($e); + } + ); + } +} diff --git a/lib/Db/CardMapper.php b/lib/Db/CardMapper.php index bbbcdb560..1b3e73f46 100644 --- a/lib/Db/CardMapper.php +++ b/lib/Db/CardMapper.php @@ -24,11 +24,13 @@ namespace OCA\Deck\Db; use OCP\AppFramework\Db\Entity; +use OCP\AppFramework\Db\QBMapper; +use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; use OCP\IUserManager; use OCP\Notification\IManager; -class CardMapper extends DeckMapper implements IPermissionMapper { +class CardMapper extends QBMapper implements IPermissionMapper { /** @var LabelMapper */ private $labelMapper; @@ -55,7 +57,7 @@ public function __construct( $this->database4ByteSupport = $database4ByteSupport; } - public function insert(Entity $entity) { + public function insert(Entity $entity): Entity { $entity->setDatabaseType($this->databaseType); $entity->setCreatedAt(time()); $entity->setLastModified(time()); @@ -66,7 +68,7 @@ public function insert(Entity $entity) { return parent::insert($entity); } - public function update(Entity $entity, $updateModified = true) { + public function update(Entity $entity, $updateModified = true): Entity { if (!$this->database4ByteSupport) { $description = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $entity->getDescription()); $entity->setDescription($description); @@ -93,7 +95,7 @@ public function update(Entity $entity, $updateModified = true) { return parent::update($entity); } - public function markNotified(Card $card) { + public function markNotified(Card $card): Entity { $cardUpdate = new Card(); $cardUpdate->setId($card->getId()); $cardUpdate->setNotified(true); @@ -106,11 +108,15 @@ public function markNotified(Card $card) { * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException * @throws \OCP\AppFramework\Db\DoesNotExistException */ - public function find($id) { - $sql = 'SELECT * FROM `*PREFIX*deck_cards` ' . - 'WHERE `id` = ? ORDER BY `order`, `id`'; + public function find($id): Entity { + $qb = $this->db->getQueryBuilder(); + $qb->select('*')->from('deck_cards') + ->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT))) + ->orderBy('order') + ->addOrderBy('id'); + /** @var Card $card */ - $card = $this->findEntity($sql, [$id]); + $card = $this->findEntity($qb); $labels = $this->labelMapper->findAssignedLabelsForCard($card->id); $card->setLabels($labels); $this->mapOwner($card); @@ -118,57 +124,146 @@ public function find($id) { } public function findAll($stackId, $limit = null, $offset = null, $since = -1) { - $sql = 'SELECT * FROM `*PREFIX*deck_cards` - WHERE `stack_id` = ? AND NOT archived AND deleted_at = 0 AND last_modified > ? ORDER BY `order`, `id`'; - return $this->findEntities($sql, [$stackId, $since], $limit, $offset); + $qb = $this->db->getQueryBuilder(); + $qb->select('*') + ->from('deck_cards') + ->where($qb->expr()->eq('stack_id', $qb->createNamedParameter($stackId, IQueryBuilder::PARAM_INT))) + ->andWhere($qb->expr()->eq('archived', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL))) + ->andWhere($qb->expr()->eq('deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT))) + ->andWhere($qb->expr()->gt('last_modified', $qb->createNamedParameter($since, IQueryBuilder::PARAM_INT))) + ->setMaxResults($limit) + ->setFirstResult($offset) + ->orderBy('order') + ->addOrderBy('id'); + return $this->findEntities($qb); + } + + public function queryCardsByBoard(int $boardId): IQueryBuilder { + $qb = $this->db->getQueryBuilder(); + $qb->select('c.*') + ->from('deck_cards', 'c') + ->innerJoin('c', 'deck_stacks', 's', 'c.stack_id = s.id') + ->where($qb->expr()->eq('s.board_id', $qb->createNamedParameter($boardId, IQueryBuilder::PARAM_INT))); + return $qb; + } + + public function queryCardsByBoards(array $boardIds): IQueryBuilder { + $qb = $this->db->getQueryBuilder(); + $qb->select('c.*') + ->from('deck_cards', 'c') + ->innerJoin('c', 'deck_stacks', 's', $qb->expr()->eq('s.id', 'c.stack_id')) + ->andWhere($qb->expr()->in('s.board_id', $qb->createNamedParameter($boardIds, IQueryBuilder::PARAM_INT_ARRAY))); + + return $qb; } public function findDeleted($boardId, $limit = null, $offset = null) { - $sql = 'SELECT c.* FROM `*PREFIX*deck_cards` c - INNER JOIN `*PREFIX*deck_stacks` s ON s.id = c.stack_id - WHERE `s`.`board_id` = ? AND NOT c.archived AND NOT c.deleted_at = 0 ORDER BY `c`.`order`, `c`.`id`'; - return $this->findEntities($sql, [$boardId], $limit, $offset); + $qb = $this->queryCardsByBoard($boardId); + $qb->andWhere($qb->expr()->neq('c.deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT))) + ->setMaxResults($limit) + ->setFirstResult($offset) + ->orderBy('order') + ->addOrderBy('id'); + return $this->findEntities($qb); } public function findAllArchived($stackId, $limit = null, $offset = null) { - $sql = 'SELECT * FROM `*PREFIX*deck_cards` WHERE `stack_id`=? AND archived ORDER BY `last_modified`'; - return $this->findEntities($sql, [$stackId], $limit, $offset); + $qb = $this->db->getQueryBuilder(); + $qb->select('*') + ->from('deck_cards') + ->where($qb->expr()->eq('stack_id', $qb->createNamedParameter($stackId, IQueryBuilder::PARAM_INT))) + ->andWhere($qb->expr()->eq('archived', $qb->createNamedParameter(true, IQueryBuilder::PARAM_BOOL))) + ->andWhere($qb->expr()->eq('deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT))) + ->setMaxResults($limit) + ->setFirstResult($offset) + ->orderBy('last_modified'); + return $this->findEntities($qb); } public function findAllByStack($stackId, $limit = null, $offset = null) { - $sql = 'SELECT id FROM `*PREFIX*deck_cards` - WHERE `stack_id` = ? ORDER BY `order`, `id`'; - return $this->findEntities($sql, [$stackId], $limit, $offset); + $qb = $this->db->getQueryBuilder(); + $qb->select('*') + ->from('deck_cards') + ->where($qb->expr()->eq('stack_id', $qb->createNamedParameter($stackId, IQueryBuilder::PARAM_INT))) + ->andWhere($qb->expr()->eq('archived', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL))) + ->setMaxResults($limit) + ->setFirstResult($offset) + ->orderBy('order') + ->addOrderBy('id'); + return $this->findEntities($qb); } public function findAllWithDue($boardId) { - $sql = 'SELECT c.* FROM `*PREFIX*deck_cards` c - INNER JOIN `*PREFIX*deck_stacks` s ON s.id = c.stack_id - INNER JOIN `*PREFIX*deck_boards` b ON b.id = s.board_id - WHERE `s`.`board_id` = ? AND duedate IS NOT NULL AND NOT c.archived AND c.deleted_at = 0 AND s.deleted_at = 0 AND NOT b.archived AND b.deleted_at = 0'; - return $this->findEntities($sql, [$boardId]); + $qb = $this->db->getQueryBuilder(); + $qb->select('c.*') + ->from('deck_cards', 'c') + ->innerJoin('c', 'deck_stacks', 's', 's.id = c.stack_id') + ->innerJoin('s', 'deck_boards', 'b', 'b.id = s.board_id') + ->where($qb->expr()->eq('s.board_id', $qb->createNamedParameter($boardId, IQueryBuilder::PARAM_INT))) + ->andWhere($qb->expr()->isNotNull('c.duedate')) + ->andWhere($qb->expr()->eq('c.archived', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL))) + ->andWhere($qb->expr()->eq('c.deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT))) + ->andWhere($qb->expr()->eq('b.archived', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL))) + ->andWhere($qb->expr()->eq('b.deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT))); + return $this->findEntities($qb); } public function findAssignedCards($boardId, $username) { - $sql = 'SELECT c.* FROM `*PREFIX*deck_cards` c - INNER JOIN `*PREFIX*deck_stacks` s ON s.id = c.stack_id - INNER JOIN `*PREFIX*deck_boards` b ON b.id = s.board_id - INNER JOIN `*PREFIX*deck_assigned_users` u ON c.id = card_id - WHERE `s`.`board_id` = ? AND participant = ? AND NOT c.archived AND c.deleted_at = 0 AND s.deleted_at = 0 AND NOT b.archived AND b.deleted_at = 0'; - return $this->findEntities($sql, [$boardId, $username]); + $qb = $this->db->getQueryBuilder(); + $qb->select('c.*') + ->from('deck_cards', 'c') + ->innerJoin('c', 'deck_stacks', 's', 's.id = c.stack_id') + ->innerJoin('s', 'deck_boards', 'b', 'b.id = s.board_id') + ->innerJoin('c', 'deck_assigned_users', 'u', 'c.id = u.card_id') + ->where($qb->expr()->eq('s.board_id', $qb->createNamedParameter($boardId, IQueryBuilder::PARAM_INT))) + ->andWhere($qb->expr()->eq('u.participant', $qb->createNamedParameter($username, IQueryBuilder::PARAM_STR))) + ->andWhere($qb->expr()->eq('u.type', $qb->createNamedParameter(Acl::PERMISSION_TYPE_USER, IQueryBuilder::PARAM_INT))) + // Filter out archived/deleted cards and board + ->andWhere($qb->expr()->eq('c.archived', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL))) + ->andWhere($qb->expr()->eq('c.deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT))) + ->andWhere($qb->expr()->eq('b.archived', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL))) + ->andWhere($qb->expr()->eq('b.deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT))); + return $this->findEntities($qb); } public function findOverdue() { - $sql = 'SELECT id,title,duedate,notified from `*PREFIX*deck_cards` WHERE duedate < NOW() AND NOT archived AND deleted_at = 0'; - return $this->findEntities($sql); + $qb = $this->db->getQueryBuilder(); + $qb->select('id,title,duedate,notified') + ->from('deck_cards') + ->where($qb->expr()->lt('duedate', $qb->createFunction('NOW()'))) + ->andWhere($qb->expr()->eq('archived', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL))) + ->andWhere($qb->expr()->eq('deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT))); + return $this->findEntities($qb); } public function findUnexposedDescriptionChances() { - $sql = 'SELECT id,title,duedate,notified,description_prev,last_editor,description from `*PREFIX*deck_cards` WHERE last_editor IS NOT NULL AND description_prev IS NOT NULL'; - return $this->findEntities($sql); + $qb = $this->db->getQueryBuilder(); + $qb->select('id,title,duedate,notified,description_prev,last_editor,description') + ->from('deck_cards') + ->where($qb->expr()->isNotNull('last_editor')) + ->andWhere($qb->expr()->isNotNull('description_prev')); + return $this->findEntities($qb); + } + + public function search($boardIds, $term, $limit = null, $offset = null) { + $qb = $this->queryCardsByBoards($boardIds); + $qb->andWhere($qb->expr()->eq('c.deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT))); + $qb->andWhere( + $qb->expr()->orX( + $qb->expr()->iLike('c.title', $qb->createNamedParameter('%' . $this->db->escapeLikeParameter($term) . '%')), + $qb->expr()->iLike('c.description', $qb->createNamedParameter('%' . $this->db->escapeLikeParameter($term) . '%')) + ) + ); + if ($limit !== null) { + $qb->setMaxResults($limit); + } + if ($offset !== null) { + $qb->setFirstResult($offset); + } + return $this->findEntities($qb); } - public function delete(Entity $entity) { + public function delete(Entity $entity): Entity { // delete assigned labels $this->labelMapper->deleteLabelAssignmentsForCard($entity->getId()); // delete card @@ -200,14 +295,18 @@ public function removeLabel($card, $label) { public function isOwner($userId, $cardId) { $sql = 'SELECT owner FROM `*PREFIX*deck_boards` WHERE `id` IN (SELECT board_id FROM `*PREFIX*deck_stacks` WHERE id IN (SELECT stack_id FROM `*PREFIX*deck_cards` WHERE id = ?))'; - $stmt = $this->execute($sql, [$cardId]); + $stmt = $this->db->prepare($sql); + $stmt->bindParam(1, $cardId, \PDO::PARAM_INT); + $stmt->execute(); $row = $stmt->fetch(); return ($row['owner'] === $userId); } public function findBoardId($cardId) { $sql = 'SELECT id FROM `*PREFIX*deck_boards` WHERE `id` IN (SELECT board_id FROM `*PREFIX*deck_stacks` WHERE id IN (SELECT stack_id FROM `*PREFIX*deck_cards` WHERE id = ?))'; - $stmt = $this->execute($sql, [$cardId]); + $stmt = $this->db->prepare($sql); + $stmt->bindParam(1, $cardId, \PDO::PARAM_INT); + $stmt->execute(); $row = $stmt->fetch(); return $row['id']; } diff --git a/lib/Db/StackMapper.php b/lib/Db/StackMapper.php index 40d01929b..58b343929 100644 --- a/lib/Db/StackMapper.php +++ b/lib/Db/StackMapper.php @@ -41,7 +41,7 @@ public function __construct(IDBConnection $db, CardMapper $cardMapper) { * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException * @throws \OCP\AppFramework\Db\DoesNotExistException */ - public function find($id) { + public function find($id): Stack { $sql = 'SELECT * FROM `*PREFIX*deck_stacks` ' . 'WHERE `id` = ?'; return $this->findEntity($sql, [$id]); diff --git a/lib/Search/BoardSearchResultEntry.php b/lib/Search/BoardSearchResultEntry.php new file mode 100644 index 000000000..f66b7377d --- /dev/null +++ b/lib/Search/BoardSearchResultEntry.php @@ -0,0 +1,41 @@ + + * + * @author Julius Härtl + * + * @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 . + * + */ + +declare(strict_types=1); + + +namespace OCA\Deck\Search; + +use OCA\Deck\Db\Board; +use OCP\Search\SearchResultEntry; + +class BoardSearchResultEntry extends SearchResultEntry { + public function __construct(Board $board, $urlGenerator) { + parent::__construct( + '', + $board->getTitle(), + '', + $urlGenerator->linkToRoute('deck.page.index') . '#/board/' . $board->getId(), + 'icon-deck'); + } +} diff --git a/lib/Search/CardSearchResultEntry.php b/lib/Search/CardSearchResultEntry.php new file mode 100644 index 000000000..72b9c5f3a --- /dev/null +++ b/lib/Search/CardSearchResultEntry.php @@ -0,0 +1,38 @@ + + * + * @author Julius Härtl + * + * @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 . + * + */ + +declare(strict_types=1); + + +namespace OCA\Deck\Search; + +use OCA\Deck\Db\Board; +use OCA\Deck\Db\Card; +use OCA\Deck\Db\Stack; +use OCP\Search\SearchResultEntry; + +class CardSearchResultEntry extends SearchResultEntry { + public function __construct(Board $board, Stack $stack, Card $card, $urlGenerator) { + parent::__construct('', $card->getTitle(), $board->getTitle() . ' » ' . $stack->getTitle() , $urlGenerator->linkToRoute('deck.page.index') . '#/board/' . $board->getId() . '/card/' . $card->getId(), 'icon-deck'); + } +} diff --git a/lib/Search/DeckProvider.php b/lib/Search/DeckProvider.php new file mode 100644 index 000000000..fa961557c --- /dev/null +++ b/lib/Search/DeckProvider.php @@ -0,0 +1,115 @@ + + * + * @author Julius Härtl + * + * @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 . + * + */ + +declare(strict_types=1); + + +namespace OCA\Deck\Search; + +use OCA\Deck\Db\Board; +use OCA\Deck\Db\Card; +use OCA\Deck\Db\CardMapper; +use OCA\Deck\Db\StackMapper; +use OCA\Deck\Service\BoardService; +use OCP\IURLGenerator; +use OCP\IUser; +use OCP\Search\IProvider; +use OCP\Search\ISearchQuery; +use OCP\Search\SearchResult; + +class DeckProvider implements IProvider { + + /** + * @var BoardService + */ + private $boardService; + /** + * @var CardMapper + */ + private $cardMapper; + /** + * @var StackMapper + */ + private $stackMapper; + /** + * @var IURLGenerator + */ + private $urlGenerator; + + public function __construct( + BoardService $boardService, + StackMapper $stackMapper, + CardMapper $cardMapper, + IURLGenerator $urlGenerator + ) { + $this->boardService = $boardService; + $this->stackMapper = $stackMapper; + $this->cardMapper = $cardMapper; + $this->urlGenerator = $urlGenerator; + } + + public function getId(): string { + return 'deck'; + } + + public function getName(): string { + return 'Deck'; + } + + public function search(IUser $user, ISearchQuery $query): SearchResult { + $boards = $this->boardService->getUserBoards(); + + $matchedBoards = array_filter($this->boardService->getUserBoards(), static function (Board $board) use ($query) { + return mb_stripos($board->getTitle(), $query->getTerm()) > -1; + }); + + $matchedCards = $this->cardMapper->search(array_map(static function (Board $board) { + return $board->getId(); + }, $boards), $query->getTerm(), $query->getLimit(), $query->getCursor()); + + $self = $this; + $results = array_merge( + array_map(function (Board $board) { + return new BoardSearchResultEntry($board, $this->urlGenerator); + }, $matchedBoards), + + array_map(function (Card $card) use ($self) { + $board = $self->boardService->find($self->cardMapper->findBoardId($card->getId())); + $stack = $self->stackMapper->find($card->getStackId()); + return new CardSearchResultEntry($board, $stack, $card, $this->urlGenerator); + }, $matchedCards) + ); + + return SearchResult::complete( + 'Deck', + $results + ); + } + + public function getOrder(string $route, array $routeParameters): int { + if ($route === 'deck.page.index') { + return -5; + } + return 10; + } +} diff --git a/lib/Service/BoardService.php b/lib/Service/BoardService.php index 26472150a..df7ee1c4c 100644 --- a/lib/Service/BoardService.php +++ b/lib/Service/BoardService.php @@ -64,6 +64,8 @@ class BoardService { private $eventDispatcher; private $changeHelper; + private $boardsCache = null; + public function __construct( BoardMapper $boardMapper, StackMapper $stackMapper, @@ -105,42 +107,54 @@ public function setUserId(string $userId): void { $this->userId = $userId; } - /** - * @return array - */ - public function findAll($since = -1, $details = null) { + public function getUserBoards(int $since = -1): array { $userInfo = $this->getBoardPrerequisites(); $userBoards = $this->boardMapper->findAllByUser($userInfo['user'], null, null, $since); $groupBoards = $this->boardMapper->findAllByGroups($userInfo['user'], $userInfo['groups'],null, null, $since); $circleBoards = $this->boardMapper->findAllByCircles($userInfo['user'], null, null, $since); - $complete = array_merge($userBoards, $groupBoards, $circleBoards); + $mergedBoards = array_merge($userBoards, $groupBoards, $circleBoards); $result = []; /** @var Board $item */ - foreach ($complete as &$item) { + foreach ($mergedBoards as &$item) { if (!array_key_exists($item->getId(), $result)) { - $this->boardMapper->mapOwner($item); - if ($item->getAcl() !== null) { - foreach ($item->getAcl() as &$acl) { - $this->boardMapper->mapAcl($acl); - } - } - if ($details !== null) { - $this->enrichWithStacks($item); - $this->enrichWithLabels($item); - $this->enrichWithUsers($item); - } - $permissions = $this->permissionService->matchPermissions($item); - $item->setPermissions([ - 'PERMISSION_READ' => $permissions[Acl::PERMISSION_READ] ?? false, - 'PERMISSION_EDIT' => $permissions[Acl::PERMISSION_EDIT] ?? false, - 'PERMISSION_MANAGE' => $permissions[Acl::PERMISSION_MANAGE] ?? false, - 'PERMISSION_SHARE' => $permissions[Acl::PERMISSION_SHARE] ?? false - ]); $result[$item->getId()] = $item; } } return array_values($result); } + /** + * @return array + */ + public function findAll($since = -1, $details = null) { + if ($this->boardsCache) { + return $this->boardsCache; + } + $complete = $this->getUserBoards($since); + /** @var Board $item */ + foreach ($complete as &$item) { + $this->boardMapper->mapOwner($item); + if ($item->getAcl() !== null) { + foreach ($item->getAcl() as &$acl) { + $this->boardMapper->mapAcl($acl); + } + } + if ($details !== null) { + $this->enrichWithStacks($item); + $this->enrichWithLabels($item); + $this->enrichWithUsers($item); + } + $permissions = $this->permissionService->matchPermissions($item); + $item->setPermissions([ + 'PERMISSION_READ' => $permissions[Acl::PERMISSION_READ] ?? false, + 'PERMISSION_EDIT' => $permissions[Acl::PERMISSION_EDIT] ?? false, + 'PERMISSION_MANAGE' => $permissions[Acl::PERMISSION_MANAGE] ?? false, + 'PERMISSION_SHARE' => $permissions[Acl::PERMISSION_SHARE] ?? false + ]); + $result[$item->getId()] = $item; + } + $this->boardsCache = $complete; + return array_values($result); + } /** * @param $boardId @@ -151,6 +165,9 @@ public function findAll($since = -1, $details = null) { * @throws BadRequestException */ public function find($boardId) { + if ($this->boardsCache && isset($this->boardsCache[$boardId])) { + return $this->boardsCache[$boardId]; + } if (is_numeric($boardId) === false) { throw new BadRequestException('board id must be a number'); } diff --git a/lib/Service/CardService.php b/lib/Service/CardService.php index 9d4867705..50504bc07 100644 --- a/lib/Service/CardService.php +++ b/lib/Service/CardService.php @@ -116,6 +116,11 @@ public function fetchDeleted($boardId) { return $cards; } + public function search($boardIds, $term) { + $cards = $this->cardMapper->search($boardIds, $term); + return $cards; + } + /** * @param $cardId * @return \OCA\Deck\Db\RelationalEntity diff --git a/src/components/board/Stack.vue b/src/components/board/Stack.vue index 82b11f197..de79bb857 100644 --- a/src/components/board/Stack.vue +++ b/src/components/board/Stack.vue @@ -162,7 +162,12 @@ export default { showArchived: state => state.showArchived, }), cardsByStack() { - return this.$store.getters.cardsByStack(this.stack.id) + return this.$store.getters.cardsByStack(this.stack.id).filter((card) => { + if (this.showArchived) { + return card.archived + } + return !card.archived + }) }, dragHandleSelector() { return this.canEdit ? null : '.no-drag' diff --git a/tests/integration/app/AppTest.php b/tests/integration/app/AppTest.php index 3c7401d79..c25e0c513 100644 --- a/tests/integration/app/AppTest.php +++ b/tests/integration/app/AppTest.php @@ -42,9 +42,4 @@ public function testAppInstalled() { $appManager = $this->container->query('OCP\App\IAppManager'); $this->assertTrue($appManager->isInstalled('deck')); } - - public function testNavigationEntry() { - $this->app->registerNavigationEntry(); - $this->assertTrue(true); - } }