From fa0ac60b14cd9c08d9bc7e4366d0d48aa8a7e2ce Mon Sep 17 00:00:00 2001 From: samin-z Date: Mon, 9 Mar 2026 12:04:18 +0100 Subject: [PATCH] add function for import/export Signed-off-by: samin-z --- lib/Db/BoardMapper.php | 5 +++ lib/Service/AssignmentService.php | 28 +++++++++++++ lib/Service/BoardService.php | 50 +++++++++++++++++++++++ lib/Service/CardService.php | 54 +++++++++++++++++++++++++ lib/Service/CommentService.php | 47 ++++++++++++++++++++++ lib/Service/FilesAppService.php | 66 +++++++++++++++++++++++++++++++ lib/Service/LabelService.php | 29 ++++++++++++++ lib/Service/StackService.php | 26 ++++++++++++ 8 files changed, 305 insertions(+) diff --git a/lib/Db/BoardMapper.php b/lib/Db/BoardMapper.php index 8d2706d9c4..5b379d1c44 100644 --- a/lib/Db/BoardMapper.php +++ b/lib/Db/BoardMapper.php @@ -583,4 +583,9 @@ public function flushCache(?int $boardId = null, ?string $userId = null) { $this->userBoardCache = new CappedMemoryCache(); } } + + public function getDbConnection() { + + return $this->db; + } } diff --git a/lib/Service/AssignmentService.php b/lib/Service/AssignmentService.php index 4d9a97486d..a4b663c90e 100644 --- a/lib/Service/AssignmentService.php +++ b/lib/Service/AssignmentService.php @@ -23,6 +23,8 @@ use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\MultipleObjectsReturnedException; use OCP\EventDispatcher\IEventDispatcher; +use Psr\Log\LoggerInterface; +use Symfony\Component\CssSelector\Exception\InternalErrorException; class AssignmentService { @@ -64,6 +66,10 @@ class AssignmentService { * @var AssignmentServiceValidator */ private $assignmentServiceValidator; + /** + * @var LoggerInterface + */ + private $logger; public function __construct( @@ -77,6 +83,7 @@ public function __construct( IEventDispatcher $eventDispatcher, AssignmentServiceValidator $assignmentServiceValidator, $userId, + LoggerInterface $logger, ) { $this->assignmentServiceValidator = $assignmentServiceValidator; $this->permissionService = $permissionService; @@ -88,6 +95,7 @@ public function __construct( $this->activityManager = $activityManager; $this->eventDispatcher = $eventDispatcher; $this->currentUser = $userId; + $this->logger = $logger; } /** @@ -169,4 +177,24 @@ public function unassignUser(int $cardId, string $userId, int $type = 0): Assign } throw new NotFoundException('No assignment for ' . $userId . 'found.'); } + + /** + * @param int $cardId + * @param array $assignedUser + * + * @return void + */ + public function importAssignedUser(int $cardId, array $assignedUser): void { + $newAssignedUser = new Assignment(); + $newAssignedUser->setCardId($cardId); + $newAssignedUser->setParticipant($assignedUser['participant']['uid']); + $newAssignedUser->setType($assignedUser['type']); + + try { + $this->assignedUsersMapper->insert($newAssignedUser); + } catch (\Exception $e) { + $this->logger->error('importAssignedUser insert error: ' . $e->getMessage()); + throw new InternalErrorException('importAssignedUser insert error: ' . $e->getMessage()); + } + } } diff --git a/lib/Service/BoardService.php b/lib/Service/BoardService.php index b91ec6e880..8cc7a72479 100644 --- a/lib/Service/BoardService.php +++ b/lib/Service/BoardService.php @@ -52,6 +52,8 @@ use OCP\Server; use Psr\Container\ContainerExceptionInterface; use Psr\Container\NotFoundExceptionInterface; +use Psr\Log\LoggerInterface; +use Symfony\Component\CssSelector\Exception\InternalErrorException; class BoardService { private ?array $boardsCacheFull = null; @@ -83,6 +85,7 @@ public function __construct( private ISecureRandom $random, private ConfigService $configService, private ?string $userId, + private LoggerInterface $logger, ) { } @@ -836,4 +839,51 @@ private function enrichWithCards(Board $board): void { $board->setStacks($stacks); } + + /** + * @param array $board + * @param string $userId + * + * @return Board + * + * @throws InternalErrorException + */ + public function importBoard(array $board, string $userId): Board { + $item = new Board(); + $item->setTitle($board['title']); + $item->setOwner($userId); + $item->setColor($board['color']); + $item->setArchived((bool)$board['archived']); + $item->setDeletedAt($board['deletedAt']); + $item->setLastModified($board['lastModified']); + try { + $newBoard = $this->boardMapper->insert($item); + } catch (\Exception $e) { + $this->logger->error('importBoard insert error: ' . $e->getMessage()); + throw new InternalErrorException('importBoard insert error: ' . $e->getMessage()); + } + return $newBoard; + } + + /** + * @param Board $board + * @param array $acl + * + * @return void + */ + public function importAcl(Board $board, array $acl): void { + $aclEntity = new Acl(); + $aclEntity->setBoardId($board->getId()); + $aclEntity->setType((int)$acl['type']); + $aclEntity->setParticipant($acl['participant']); + $aclEntity->setPermissionEdit((bool)$acl['permissionEdit']); + $aclEntity->setPermissionShare((bool)$acl['permissionShare']); + $aclEntity->setPermissionManage((bool)$acl['permissionManage']); + try { + $this->aclMapper->insert($aclEntity); + } catch (\Exception $e) { + $this->logger->error('importAcl insert error: ' . $e->getMessage()); + throw new InternalErrorException('importAcl insert error: ' . $e->getMessage()); + } + } } diff --git a/lib/Service/CardService.php b/lib/Service/CardService.php index 3b9f97d42b..81c0a2d066 100644 --- a/lib/Service/CardService.php +++ b/lib/Service/CardService.php @@ -36,6 +36,7 @@ use OCP\IURLGenerator; use OCP\IUserManager; use Psr\Log\LoggerInterface; +use Symfony\Component\CssSelector\Exception\InternalErrorException; class CardService { public function __construct( @@ -638,4 +639,57 @@ public function getCardUrl(int $cardId): string { public function getRedirectUrlForCard(int $cardId): string { return $this->urlGenerator->linkToRouteAbsolute('deck.page.redirectToCard', ['cardId' => $cardId]); } + + /** + * @param int $stackId + * @param array $card + * + * @return Card + * + * @throws InternalErrorException + */ + public function importCard (int $stackId, array $card): Card { + $item = new Card(); + $item->setStackId($stackId); + $item->setTitle($card['title']); + $item->setType($card['type']); + $item->setOrder($card['order']); + $item->setOwner($card['owner']); + $item->setDescription($card['description']); + $item->setDuedate($card['duedate']); + $item->setLastModified($card['lastModified']); + $item->setLastEditor($card['lastEditor']); + $item->setCreatedAt($card['createdAt']); + $item->isArchived($card['archived']); + $item->setDeletedAt($card['deletedAt']); + $item->setDone($card['done']); + $item->setNotified($card['notified']); + + try { + $newCard = $this->cardMapper->insert($item); + } catch (\Exception $e) { + $this->logger->error('importCard insert error: ' . $e->getMessage()); + throw new InternalErrorException('importCard insert error: ' . $e->getMessage()); + } + + return $newCard; + } + + /** + * @param int $cardId + * @param int $boardId + * @param array $importedLabel + * + * @return void + */ + public function importLabels(int $cardId, int $boardId, array $importedLabel): void { + $labels = $this->labelMapper->findAll($boardId); + + foreach ($labels as $label) { + if ($label->getTitle() === $importedLabel['title'] && $label->getColor() === $importedLabel['color']) { + $this->cardMapper->assignLabel($cardId, $label->getId()); + } + } + $this->changeHelper->cardChanged($cardId); + } } diff --git a/lib/Service/CommentService.php b/lib/Service/CommentService.php index 72a48c66c8..550ee56da1 100644 --- a/lib/Service/CommentService.php +++ b/lib/Service/CommentService.php @@ -7,6 +7,7 @@ namespace OCA\Deck\Service; +use OC\Comments\Comment; use OCA\Deck\AppInfo\Application; use OCA\Deck\BadRequestException; use OCA\Deck\Db\Acl; @@ -21,6 +22,7 @@ use OCP\IUserManager; use OutOfBoundsException; use Psr\Log\LoggerInterface; +use Symfony\Component\CssSelector\Exception\InternalErrorException; class CommentService { @@ -188,4 +190,49 @@ private function formatComment(IComment $comment, bool $addReplyTo = false): arr } return $formattedComment; } + + public function exportAllForCard(int $cardId, int $limit = 1000, int $offset = 0): array { + $comments = $this->commentsManager->getForObject( + Application::COMMENT_ENTITY_TYPE, + (string)$cardId, + $limit, + $offset + ); + $allComments = []; + foreach ($comments as $comment) { + $formattedComment = $this->formatComment($comment); + try { + if ($comment->getParentId() !== '0' && $replyTo = $this->commentsManager->get($comment->getParentId())) { + $formattedComment['replyTo'] = $this->formatComment($replyTo); + } + } catch (CommentNotFoundException $e) { + } + $allComments[] = $formattedComment; + } + return $allComments; + } + + /** + * @param int $cardId + * @param array $comment + * @param string $parentId + * + * @return int + */ + public function importComment(int $cardId, array $comment, string $parentId = '0'): int { + try { + $newComment = new Comment(); + $newComment->setMessage($comment['message']); + $newComment->setObject(Application::COMMENT_ENTITY_TYPE, (string)$cardId); + $newComment->setVerb('comment'); + $newComment->setParentId($parentId); + $newComment->setActor($comment['actorType'], $comment['actorId']); + $newComment->setCreationDateTime(new \DateTime($comment['creationDateTime'])); + $this->commentsManager->save($newComment); + } catch (\Exception $e) { + $this->logger->error('importComment insert error: ' . $e->getMessage()); + throw new InternalErrorException('importComment insert error: ' . $e->getMessage()); + } + return (int)$newComment->getId(); + } } diff --git a/lib/Service/FilesAppService.php b/lib/Service/FilesAppService.php index 931668d037..76d0a45785 100644 --- a/lib/Service/FilesAppService.php +++ b/lib/Service/FilesAppService.php @@ -33,6 +33,7 @@ use OCP\Share\IManager; use OCP\Share\IShare; use Psr\Log\LoggerInterface; +use Symfony\Component\CssSelector\Exception\InternalErrorException; class FilesAppService implements IAttachmentService, ICustomAttachmentService { /** @@ -339,4 +340,69 @@ private function validateFilename(string $fileName): void { throw new BadRequestException($errorMessage); } } + + /** + * @param int[] $cardIds + * @return array + */ + public function getAllDeckSharesForCards(array $cardIds): array { + $qb = $this->connection->getQueryBuilder(); + $qb->select('*') + ->from('share') + ->where($qb->expr()->eq('share_type', $qb->createNamedParameter(12))) + ->andWhere($qb->expr()->in('share_with', $qb->createParameter('cardIds'))); + $qb->setParameter('cardIds', $cardIds, Connection::PARAM_STR_ARRAY); + + $sql = $qb->getSQL(); + $params = $qb->getParameters(); + $shares = $qb->executeQuery()->fetchAllAssociative(); + return $shares; + } + + /** + * @param int $cardId + * @param array $shareData + * @param string $userId + * @return bool + */ + public function importDeckSharesForCard(int $cardId, array $shareData, int $fileId, string $userId): void { + try { + $insert = [ + 'share_type' => $shareData['share_type'] ?? 12, + 'share_with' => (string)$cardId, + 'uid_owner' => $userId, + 'uid_initiator' => $userId, + 'file_source' => $fileId, + 'file_target' => $shareData['file_target'], + 'permissions' => $shareData['permissions'] ?? 1, + 'stime' => $shareData['stime'] ?? time(), + 'parent' => $shareData['parent'], + 'item_type' => $shareData['item_type'] ?? 'file', + 'item_source' => $fileId, + 'item_target' => $shareData['item_target'], + 'token' => $shareData['token'], + 'mail_send' => $shareData['mail_send'], + 'share_name' => $shareData['share_name'], + 'note' => $shareData['note'], + 'label' => $shareData['label'], + 'hide_download' => $shareData['hide_download'], + 'expiration' => $shareData['expiration'], + 'accepted' => $shareData['accepted'], + 'password' => $shareData['password'], + 'password_by_talk' => $shareData['password_by_talk'], + 'attributes' => $shareData['attributes'], + 'password_expiration_time' => $shareData['password_expiration_time'], + 'reminder_sent' => $shareData['reminder_sent'], + ]; + $qb = $this->connection->getQueryBuilder(); + $qb->insert('share'); + foreach ($insert as $key => $value) { + $qb->setValue($key, $qb->createNamedParameter($value)); + } + $qb->executeStatement(); + } catch (\Throwable $e) { + $this->logger->error('importDeckSharesForCard insert error: ' . $e->getMessage()); + throw new InternalErrorException('importDeckSharesForCard insert error: ' . $e->getMessage()); + } + } } diff --git a/lib/Service/LabelService.php b/lib/Service/LabelService.php index bec844f9f5..852f5fef7d 100644 --- a/lib/Service/LabelService.php +++ b/lib/Service/LabelService.php @@ -9,11 +9,14 @@ use OCA\Deck\BadRequestException; use OCA\Deck\Db\Acl; +use OCA\Deck\Db\Board; use OCA\Deck\Db\ChangeHelper; use OCA\Deck\Db\Label; use OCA\Deck\Db\LabelMapper; use OCA\Deck\StatusException; use OCA\Deck\Validators\LabelServiceValidator; +use Psr\Log\LoggerInterface; +use Symfony\Component\CssSelector\Exception\InternalErrorException; class LabelService { @@ -27,6 +30,10 @@ class LabelService { private $changeHelper; /** @var LabelServiceValidator */ private LabelServiceValidator $labelServiceValidator; + /** + * @var LoggerInterface + */ + private $logger; public function __construct( LabelMapper $labelMapper, @@ -34,12 +41,14 @@ public function __construct( BoardService $boardService, ChangeHelper $changeHelper, LabelServiceValidator $labelServiceValidator, + LoggerInterface $logger, ) { $this->labelMapper = $labelMapper; $this->permissionService = $permissionService; $this->boardService = $boardService; $this->changeHelper = $changeHelper; $this->labelServiceValidator = $labelServiceValidator; + $this->logger = $logger; } /** @@ -149,4 +158,24 @@ public function update(int $id, string $title, string $color): Label { $this->changeHelper->boardChanged($label->getBoardId()); return $this->labelMapper->update($label); } + + /** + * @param Board $board + * @param array $$label + * + * @return void + */ + public function importBoardLabel(Board $board, array $label): void { + $labelEntity = new Label(); + $labelEntity->setBoardId($board->getId()); + $labelEntity->setTitle($label['title']); + $labelEntity->setColor($label['color']); + $labelEntity->setLastModified($label['lastModified']); + try { + $this->labelMapper->insert($labelEntity); + } catch (\Throwable $e) { + $this->logger->error('importBoardLabel insert error: ' . $e->getMessage()); + throw new InternalErrorException('importBoardLabel insert error: ' . $e->getMessage()); + } + } } diff --git a/lib/Service/StackService.php b/lib/Service/StackService.php index c2aed66e57..dc5ff4ee16 100644 --- a/lib/Service/StackService.php +++ b/lib/Service/StackService.php @@ -26,6 +26,7 @@ use OCA\Deck\Validators\StackServiceValidator; use OCP\EventDispatcher\IEventDispatcher; use Psr\Log\LoggerInterface; +use Symfony\Component\CssSelector\Exception\InternalErrorException; class StackService { private StackMapper $stackMapper; @@ -304,4 +305,29 @@ public function reorder(int $id, int $order): array { return $result; } + + /** + * @param int $boardId + * @param array $stack + * + * @return Stack + * + * @throws InternalErrorException + */ + public function importStack(int $boardId, array $stack): Stack { + $item = new Stack(); + $item->setBoardId($boardId); + $item->setTitle($stack['title']); + $item->setDeletedAt($stack['deletedAt']); + $item->setLastModified($stack['lastModified']); + $item->setOrder($stack['order']); + try { + $newStack = $this->stackMapper->insert($item); + } catch (\Exception $e) { + $this->logger->error('importStack insert error: ' . $e->getMessage()); + throw new InternalErrorException('importStack insert error: ' . $e->getMessage()); + } + + return $newStack; + } }