diff --git a/appinfo/info.xml b/appinfo/info.xml index c5a69243a..8826b338b 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -39,6 +39,7 @@ OCA\Polls\Command\Share\Add OCA\Polls\Command\Share\Remove OCA\Polls\Command\Db\Rebuild + OCA\Polls\Command\Poll\TransferOwnership OCA\Polls\Settings\AdminSection diff --git a/appinfo/routes.php b/appinfo/routes.php index b620c85e7..0d43de069 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -67,6 +67,8 @@ return [ ['name' => 'poll#clone', 'url' => '/poll/{pollId}/clone', 'verb' => 'GET'], ['name' => 'poll#getParticipantsEmailAddresses', 'url' => '/poll/{pollId}/addresses', 'verb' => 'GET'], + ['name' => 'poll#transfer_polls', 'url' => '/polls/transfer/{sourceUser}/{destinationUser}', 'verb' => 'PUT'], + ['name' => 'option#list', 'url' => '/poll/{pollId}/options', 'verb' => 'GET'], ['name' => 'option#add', 'url' => '/option', 'verb' => 'POST'], ['name' => 'option#addBulk', 'url' => '/option/bulk', 'verb' => 'POST'], @@ -118,6 +120,7 @@ return [ // REST-API calls ['name' => 'poll_api#list', 'url' => '/api/v1.0/polls', 'verb' => 'GET'], + ['name' => 'poll_api#transfer_polls', 'url' => '/api/v1.0/polls/transfer/{sourceUser}/{destinationUser}', 'verb' => 'PUT'], ['name' => 'poll_api#add', 'url' => '/api/v1.0/poll', 'verb' => 'POST'], ['name' => 'poll_api#get', 'url' => '/api/v1.0/poll/{pollId}', 'verb' => 'GET'], ['name' => 'poll_api#update', 'url' => '/api/v1.0/poll/{pollId}', 'verb' => 'PUT'], diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 0c6840024..73aff0ad2 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -47,6 +47,7 @@ use OCA\Polls\Event\PollArchivedEvent; use OCA\Polls\Event\PollCreatedEvent; use OCA\Polls\Event\PollDeletedEvent; use OCA\Polls\Event\PollExpiredEvent; +use OCA\Polls\Event\PollOwnerChangeEvent; use OCA\Polls\Event\PollRestoredEvent; use OCA\Polls\Event\PollTakeoverEvent; use OCA\Polls\Event\PollUpdatedEvent; @@ -98,6 +99,7 @@ class Application extends App implements IBootstrap { $context->registerEventListener(PollDeletedEvent::class, PollListener::class); $context->registerEventListener(PollExpiredEvent::class, PollListener::class); $context->registerEventListener(PollRestoredEvent::class, PollListener::class); + $context->registerEventListener(PollOwnerChangeEvent::class, PollListener::class); $context->registerEventListener(PollTakeoverEvent::class, PollListener::class); $context->registerEventListener(PollUpdatedEvent::class, PollListener::class); $context->registerEventListener(ShareChangedEmailEvent::class, ShareListener::class); diff --git a/lib/Command/Poll/TransferOwnership.php b/lib/Command/Poll/TransferOwnership.php new file mode 100644 index 000000000..93de1449f --- /dev/null +++ b/lib/Command/Poll/TransferOwnership.php @@ -0,0 +1,110 @@ + + * + * @author René Gieling + * + * @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\Polls\Command\Poll; + +use OCA\Polls\Service\PollService; +use OCP\IUser; +use OCP\IUserManager; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\ConfirmationQuestion; + +class TransferOwnership extends Command { + /** @var PollService */ + private $pollService; + + /** @var IUserManager */ + private $userManager; + + public function __construct( + IUserManager $userManager, + PollService $pollService + ) { + parent::__construct(); + $this->pollService = $pollService; + $this->userManager = $userManager; + } + + protected function configure(): void { + $this + ->setName('polls:poll:transfer-ownership') + ->setDescription('Transfer the ownership of one user\'s polls to another user.') + ->addArgument( + 'source-user', + InputArgument::REQUIRED, + 'User id to transfer the polls from' + ) + ->addArgument( + 'target-user', + InputArgument::REQUIRED, + 'User id to transfer the polls to' + ); + } + + protected function execute(InputInterface $input, OutputInterface $output): int { + if ($this->requestConfirmation($input, $output)) { + return 1; + } + + if (!$this->userManager->get($input->getArgument('target-user')) instanceof IUser) { + $output->writeln(' Unknown destination user ' . $input->getArgument('target-user') . ''); + return 1; + } + + $transferredPolls = $this->pollService->transferPolls($input->getArgument('source-user'), $input->getArgument('target-user')); + + if (sizeof($transferredPolls) < 1) { + $output->writeln('No polls were transferred from ' . $input->getArgument('source-user') . ''); + + } else if (sizeof($transferredPolls) === 1) { + $output->writeln('One poll was transferred from ' . $input->getArgument('source-user') . ' to ' . $input->getArgument('target-user') . ''); + $output->writeln(' * ' . $transferredPolls[0]->getId() . ' - ' . $transferredPolls[0]->getTitle() . ''); + + } else { + $output->writeln('' . sizeof($transferredPolls) . ' polls were transferred from ' . $input->getArgument('source-user') . ' to ' . $input->getArgument('target-user') . ''); + foreach ($transferredPolls as $poll) { + $output->writeln(' * ' . $poll->getId() . ' - ' . $poll->getTitle() . ''); + } + } + + return 0; + } + + private function requestConfirmation(InputInterface $input, OutputInterface $output): int { + if ($input->isInteractive()) { + $helper = $this->getHelper('question'); + $output->writeln('This command will change the ownership of all polls of ' . $input->getArgument('source-user') . ' to ' . $input->getArgument('target-user') . '.'); + $output->writeln('NO notifications will be sent to the users.'); + $output->writeln(''); + + $question = new ConfirmationQuestion('Continue with the transfer (y/n)? [n] ', false); + if (!$helper->ask($input, $output, $question)) { + return 1; + } + } + return 0; + } +} diff --git a/lib/Controller/PollApiController.php b/lib/Controller/PollApiController.php index b9d7afe54..9bd5f810d 100644 --- a/lib/Controller/PollApiController.php +++ b/lib/Controller/PollApiController.php @@ -168,6 +168,19 @@ class PollApiController extends ApiController { } } + /** + * Clone poll + * @CORS + * @NoCSRFRequired + */ + public function transferPolls(string $sourceUser, string $targetUser): JSONResponse { + try { + return new JSONResponse(['transferred' => $this->pollService->transferPolls($sourceUser, $targetUser)], Http::STATUS_CREATED); + } catch (Exception $e) { + return new JSONResponse(['message' => $e->getMessage()], $e->getStatus()); + } + } + /** * Collect email addresses from particitipants * @NoAdminRequired diff --git a/lib/Controller/PollController.php b/lib/Controller/PollController.php index 8ccd20afb..16c75a3c1 100644 --- a/lib/Controller/PollController.php +++ b/lib/Controller/PollController.php @@ -138,6 +138,13 @@ class PollController extends Controller { return $this->get($pollId); } + /** + * Transfer polls between users + */ + public function transferPolls(string $sourceUser, string $targetUser): JSONResponse { + return $this->response(fn () => $this->pollService->transferPolls($sourceUser, $targetUser)); + } + /** * Collect email addresses from particitipants * @NoAdminRequired diff --git a/lib/Db/PollMapper.php b/lib/Db/PollMapper.php index 4e9de1852..ff2c4ba26 100644 --- a/lib/Db/PollMapper.php +++ b/lib/Db/PollMapper.php @@ -94,6 +94,18 @@ class PollMapper extends QBMapper { return $this->findEntities($qb); } + /** + * @throws \OCP\AppFramework\Db\DoesNotExistException if not found + * @return Poll[] + */ + public function findOwner(string $userId): array { + $qb = $this->db->getQueryBuilder(); + $qb->select('*') + ->from($this->getTableName()) + ->where($qb->expr()->eq('owner', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR))); + return $this->findEntities($qb); + } + /** * @throws \OCP\AppFramework\Db\DoesNotExistException if not found * @return Poll[] diff --git a/lib/Event/PollOwnerChangeEvent.php b/lib/Event/PollOwnerChangeEvent.php new file mode 100644 index 000000000..01905f507 --- /dev/null +++ b/lib/Event/PollOwnerChangeEvent.php @@ -0,0 +1,35 @@ + + * + * @author René Gieling + * + * @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\Polls\Event; + +use OCA\Polls\Db\Poll; + +class PollOwnerChangeEvent extends PollEvent { + public function __construct( + Poll $poll + ) { + parent::__construct($poll); + $this->activitySubject = self::OWNER_CHANGE; + } +} diff --git a/lib/Event/PollTakeoverEvent.php b/lib/Event/PollTakeoverEvent.php index 23b945f50..819b5741d 100644 --- a/lib/Event/PollTakeoverEvent.php +++ b/lib/Event/PollTakeoverEvent.php @@ -26,12 +26,11 @@ namespace OCA\Polls\Event; use OCA\Polls\Notification\Notifier; use OCA\Polls\Db\Poll; -class PollTakeoverEvent extends PollEvent { +class PollTakeoverEvent extends PollOwnerChangeEvent { public function __construct( Poll $poll ) { parent::__construct($poll); - $this->activitySubject = self::OWNER_CHANGE; } public function getNotification(): array { diff --git a/lib/Listener/BaseListener.php b/lib/Listener/BaseListener.php index 915277f6e..5e2e20d1a 100644 --- a/lib/Listener/BaseListener.php +++ b/lib/Listener/BaseListener.php @@ -80,6 +80,7 @@ abstract class BaseListener implements IEventListener { public function handle(Event $event) : void { $this->event = $event; + try { $this->checkClass(); $this->addLog(); @@ -111,7 +112,6 @@ abstract class BaseListener implements IEventListener { throw $e; } } - // add a cron job, if necessary (i.e. for removed users and groups) $this->addCronJob(); diff --git a/lib/Service/PollService.php b/lib/Service/PollService.php index ce54116e0..662102346 100644 --- a/lib/Service/PollService.php +++ b/lib/Service/PollService.php @@ -41,11 +41,15 @@ use OCA\Polls\Db\Vote; use OCA\Polls\Event\PollArchivedEvent; use OCA\Polls\Event\PollCreatedEvent; use OCA\Polls\Event\PollDeletedEvent; +use OCA\Polls\Event\PollOwnerChangeEvent; use OCA\Polls\Event\PollRestoredEvent; use OCA\Polls\Event\PollTakeoverEvent; use OCA\Polls\Event\PollUpdatedEvent; +use OCA\Polls\Exceptions\InvalidUsernameException; use OCA\Polls\Model\Acl; use OCA\Polls\Model\Settings\AppSettings; +use OCP\IUser; +use OCP\IUserManager; class PollService { @@ -54,6 +58,9 @@ class PollService { /** @var IEventDispatcher */ private $eventDispatcher; + + /** @var IUserManager */ + private $userManager; /** @var IUserSession */ private $userSession; @@ -87,6 +94,7 @@ class PollService { AppSettings $appSettings, IEventDispatcher $eventDispatcher, IGroupManager $groupManager, + IUserManager $userManager, IUserSession $userSession, MailService $mailService, Poll $poll, @@ -103,6 +111,7 @@ class PollService { $this->poll = $poll; $this->pollMapper = $pollMapper; $this->userId = $UserId; + $this->userManager = $userManager; $this->userSession = $userSession; $this->voteMapper = $voteMapper; $this->vote = $vote; @@ -191,6 +200,20 @@ class PollService { return $this->poll; } + public function transferPolls(string $sourceUser, string $targetUser) { + if ($this->userManager->get($targetUser) instanceof IUser) { + $pollsToTransfer = $this->pollMapper->findOwner($sourceUser); + foreach ($pollsToTransfer as $poll) { + $poll->setOwner($targetUser); + $this->pollMapper->update($poll); + $this->eventDispatcher->dispatchTyped(new PollOwnerChangeEvent($poll)); + } + return $pollsToTransfer; + } + throw new InvalidUsernameException('The user id "' . $targetUser . '" is not valid.'); + } + + /** * get poll configuration */