From 9bf8d2bdc40eda8a33fa7e2e6f7861684327e8c9 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sun, 1 Feb 2026 11:21:03 +0100 Subject: [PATCH 01/18] fix: Refactor SQL queries and improve performance Signed-off-by: Marcel Klehr --- lib/Controller/BookmarkController.php | 2 +- lib/Db/BookmarkMapper.php | 157 +++++++++++++++----------- 2 files changed, 95 insertions(+), 64 deletions(-) diff --git a/lib/Controller/BookmarkController.php b/lib/Controller/BookmarkController.php index 40f2558b9..2c77de5ec 100644 --- a/lib/Controller/BookmarkController.php +++ b/lib/Controller/BookmarkController.php @@ -170,7 +170,7 @@ private function _returnBookmarkAsArray(Bookmark $bookmark): array { if (!isset($array['tags'])) { $array['tags'] = $this->tagMapper->findByBookmark($bookmark->getId()); } - if ($array['archivedFile'] !== 0) { + if ($array['archivedFile'] !== 0 && $this->authorizer->getUserId() === $bookmark->getUserId()) { $results = $this->rootFolder->getById($array['archivedFile']); if (count($results)) { $array['archivedFilePath'] = $results[0]->getPath(); diff --git a/lib/Db/BookmarkMapper.php b/lib/Db/BookmarkMapper.php index 8646ab51b..985358bc9 100644 --- a/lib/Db/BookmarkMapper.php +++ b/lib/Db/BookmarkMapper.php @@ -265,8 +265,6 @@ protected function findEntitiesWithRawQuery(string $query, array $params, array protected function getIteratorWithRawQuery(string $query, array $params, array $types): \Generator { $cursor = $this->db->executeQuery($query, $params, $types); - $entities = []; - while ($row = $cursor->fetch()) { yield $this->mapRowToEntity($row); } @@ -535,23 +533,28 @@ private function _filterTags(IQueryBuilder $qb, QueryParameters $params): void { public function countArchived(string $userId): int { $qb = $this->db->getQueryBuilder(); $qb->selectAlias($qb->func()->count('b.id'), 'count'); + $qb + ->from('bookmarks', 'b') + ->innerJoin('b', 'bookmarks_tree', 'tr', 'b.id = tr.id AND tr.type = ' . $qb->createPositionalParameter(TreeMapper::TYPE_BOOKMARK) . ' AND tr.soft_deleted_at is NULL') + ->where($qb->expr()->eq('b.user_id', $qb->createPositionalParameter($userId))) + ->andWhere($qb->expr()->isNotNull('b.archived_file')); + $result = $qb->executeQuery(); + $userOwnerArchivedCount = $result->fetch(PDO::FETCH_COLUMN); + $result->closeCursor(); + $qb = $this->db->getQueryBuilder(); + $qb->selectAlias($qb->func()->count('b.id'), 'count'); $qb ->from('bookmarks', 'b') - ->leftJoin('b', 'bookmarks_tree', 'tr', 'b.id = tr.id AND tr.type = ' . $qb->createPositionalParameter(TreeMapper::TYPE_BOOKMARK) . ' AND tr.soft_deleted_at is NULL') - ->leftJoin('tr', 'bookmarks_shared_folders', 'sf', $qb->expr()->eq('tr.parent_folder', 'sf.folder_id')) - ->where( - $qb->expr()->andX( - $qb->expr()->orX( - $qb->expr()->eq('b.user_id', $qb->createPositionalParameter($userId)), - $qb->expr()->eq('sf.user_id', $qb->createPositionalParameter($userId)) - ), - $qb->expr()->in('b.user_id', array_map([$qb, 'createPositionalParameter'], array_merge($this->_findSharersFor($userId), [$userId]))) - ) - ) + ->innerJoin('b', 'bookmarks_tree', 'tr', 'b.id = tr.id AND tr.type = ' . $qb->createPositionalParameter(TreeMapper::TYPE_BOOKMARK) . ' AND tr.soft_deleted_at is NULL') + ->innerJoin('tr', 'bookmarks_shared_folders', 'sf', $qb->expr()->eq('tr.parent_folder', 'sf.folder_id')) + ->where($qb->expr()->eq('sf.user_id', $qb->createPositionalParameter($userId))) ->andWhere($qb->expr()->isNotNull('b.archived_file')); + $result = $qb->executeQuery(); + $foreignArchivedCount = $result->fetch(PDO::FETCH_COLUMN); + $result->closeCursor(); - return $qb->execute()->fetch(PDO::FETCH_COLUMN); + return $userOwnerArchivedCount + $foreignArchivedCount; } /** @@ -562,22 +565,28 @@ public function countArchived(string $userId): int { public function countAllClicks(string $userId): int { $qb = $this->db->getQueryBuilder(); $qb->selectAlias($qb->func()->sum('b.clickcount'), 'count'); + $qb + ->from('bookmarks', 'b') + ->innerJoin('b', 'bookmarks_tree', 'tr', 'b.id = tr.id AND tr.type = ' . $qb->createPositionalParameter(TreeMapper::TYPE_BOOKMARK) . ' AND tr.soft_deleted_at is NULL') + ->where($qb->expr()->eq('b.user_id', $qb->createPositionalParameter($userId))) + ->andWhere($qb->expr()->neq('b.clickcount', $qb->createPositionalParameter(0, IQueryBuilder::PARAM_INT))); + $result = $qb->executeQuery(); + $userOwnerClickCount = $result->fetch(PDO::FETCH_COLUMN); + $result->closeCursor(); + $qb = $this->db->getQueryBuilder(); + $qb->selectAlias($qb->func()->sum('b.clickcount'), 'count'); $qb ->from('bookmarks', 'b') - ->leftJoin('b', 'bookmarks_tree', 'tr', 'b.id = tr.id AND tr.type = ' . $qb->createPositionalParameter(TreeMapper::TYPE_BOOKMARK) . ' AND tr.soft_deleted_at is NULL') - ->leftJoin('tr', 'bookmarks_shared_folders', 'sf', $qb->expr()->eq('tr.parent_folder', 'sf.folder_id')) - ->where( - $qb->expr()->andX( - $qb->expr()->orX( - $qb->expr()->eq('b.user_id', $qb->createPositionalParameter($userId)), - $qb->expr()->eq('sf.user_id', $qb->createPositionalParameter($userId)) - ), - $qb->expr()->in('b.user_id', array_map([$qb, 'createPositionalParameter'], array_merge($this->_findSharersFor($userId), [$userId]))) - ) - ); + ->innerJoin('b', 'bookmarks_tree', 'tr', 'b.id = tr.id AND tr.type = ' . $qb->createPositionalParameter(TreeMapper::TYPE_BOOKMARK) . ' AND tr.soft_deleted_at is NULL') + ->innerJoin('tr', 'bookmarks_shared_folders', 'sf', $qb->expr()->eq('tr.parent_folder', 'sf.folder_id')) + ->where($qb->expr()->eq('sf.user_id', $qb->createPositionalParameter($userId))) + ->andWhere($qb->expr()->neq('b.clickcount', $qb->createPositionalParameter(0, IQueryBuilder::PARAM_INT))); + $result = $qb->executeQuery(); + $foreignClickCount = $result->fetch(PDO::FETCH_COLUMN); + $result->closeCursor(); - return $qb->execute()->fetch(PDO::FETCH_COLUMN) ?? 0; + return $userOwnerClickCount + $foreignClickCount; } /** @@ -588,23 +597,28 @@ public function countAllClicks(string $userId): int { public function countWithClicks(string $userId): int { $qb = $this->db->getQueryBuilder(); $qb->selectAlias($qb->func()->count('b.id'), 'count'); + $qb + ->from('bookmarks', 'b') + ->innerJoin('b', 'bookmarks_tree', 'tr', 'b.id = tr.id AND tr.type = ' . $qb->createPositionalParameter(TreeMapper::TYPE_BOOKMARK) . ' AND tr.soft_deleted_at is NULL') + ->where($qb->expr()->eq('b.user_id', $qb->createPositionalParameter($userId))) + ->andWhere($qb->expr()->neq('b.clickcount', $qb->createPositionalParameter(0, IQueryBuilder::PARAM_INT))); + $result = $qb->executeQuery(); + $userOwnerWithClicksCount = $result->fetch(PDO::FETCH_COLUMN); + $result->closeCursor(); + $qb = $this->db->getQueryBuilder(); + $qb->selectAlias($qb->func()->count('b.id'), 'count'); $qb ->from('bookmarks', 'b') - ->leftJoin('b', 'bookmarks_tree', 'tr', 'b.id = tr.id AND tr.type = ' . $qb->createPositionalParameter(TreeMapper::TYPE_BOOKMARK) . ' AND tr.soft_deleted_at is NULL') - ->leftJoin('tr', 'bookmarks_shared_folders', 'sf', $qb->expr()->eq('tr.parent_folder', 'sf.folder_id')) - ->where( - $qb->expr()->andX( - $qb->expr()->orX( - $qb->expr()->eq('b.user_id', $qb->createPositionalParameter($userId)), - $qb->expr()->eq('sf.user_id', $qb->createPositionalParameter($userId)) - ), - $qb->expr()->in('b.user_id', array_map([$qb, 'createPositionalParameter'], array_merge($this->_findSharersFor($userId), [$userId]))) - ) - ) + ->innerJoin('b', 'bookmarks_tree', 'tr', 'b.id = tr.id AND tr.type = ' . $qb->createPositionalParameter(TreeMapper::TYPE_BOOKMARK) . ' AND tr.soft_deleted_at is NULL') + ->innerJoin('tr', 'bookmarks_shared_folders', 'sf', $qb->expr()->eq('tr.parent_folder', 'sf.folder_id')) + ->where($qb->expr()->eq('sf.user_id', $qb->createPositionalParameter($userId))) ->andWhere($qb->expr()->neq('b.clickcount', $qb->createPositionalParameter(0, IQueryBuilder::PARAM_INT))); + $result = $qb->executeQuery(); + $foreignWithClicksCount = $result->fetch(PDO::FETCH_COLUMN); + $result->closeCursor(); - return $qb->execute()->fetch(PDO::FETCH_COLUMN); + return $userOwnerWithClicksCount + $foreignWithClicksCount; } /** @@ -615,46 +629,60 @@ public function countWithClicks(string $userId): int { public function countUnavailable(string $userId): int { $qb = $this->db->getQueryBuilder(); $qb->selectAlias($qb->func()->count('b.id'), 'count'); + $qb + ->from('bookmarks', 'b') + ->innerJoin('b', 'bookmarks_tree', 'tr', 'b.id = tr.id AND tr.type = ' . $qb->createPositionalParameter(TreeMapper::TYPE_BOOKMARK) . ' AND tr.soft_deleted_at is NULL') + ->where($qb->expr()->eq('b.user_id', $qb->createPositionalParameter($userId))) + ->andWhere($qb->expr()->eq('b.available', $qb->createPositionalParameter(false, IQueryBuilder::PARAM_BOOL))); + $result = $qb->executeQuery(); + $userOwnerUnavailableCount = $result->fetch(PDO::FETCH_COLUMN); + $result->closeCursor(); + $qb = $this->db->getQueryBuilder(); + $qb->selectAlias($qb->func()->count('b.id'), 'count'); $qb ->from('bookmarks', 'b') - ->leftJoin('b', 'bookmarks_tree', 'tr', 'b.id = tr.id AND tr.type = ' . $qb->createPositionalParameter(TreeMapper::TYPE_BOOKMARK) . ' AND tr.soft_deleted_at is NULL') - ->leftJoin('tr', 'bookmarks_shared_folders', 'sf', $qb->expr()->eq('tr.parent_folder', 'sf.folder_id')) - ->where( - $qb->expr()->andX( - $qb->expr()->orX( - $qb->expr()->eq('b.user_id', $qb->createPositionalParameter($userId)), - $qb->expr()->eq('sf.user_id', $qb->createPositionalParameter($userId)) - ), - $qb->expr()->in('b.user_id', array_map([$qb, 'createPositionalParameter'], array_merge($this->_findSharersFor($userId), [$userId]))) - ) - ) + ->innerJoin('b', 'bookmarks_tree', 'tr', 'b.id = tr.id AND tr.type = ' . $qb->createPositionalParameter(TreeMapper::TYPE_BOOKMARK) . ' AND tr.soft_deleted_at is NULL') + ->innerJoin('tr', 'bookmarks_shared_folders', 'sf', $qb->expr()->eq('tr.parent_folder', 'sf.folder_id')) + ->where($qb->expr()->eq('sf.user_id', $qb->createPositionalParameter($userId))) ->andWhere($qb->expr()->eq('b.available', $qb->createPositionalParameter(false, IQueryBuilder::PARAM_BOOL))); + $result = $qb->executeQuery(); + $foreignUnavailableCount = $result->fetch(PDO::FETCH_COLUMN); + $result->closeCursor(); - return $qb->execute()->fetch(PDO::FETCH_COLUMN); + return $userOwnerUnavailableCount + $foreignUnavailableCount; } /** * @param string $userId * @return int + * @throws Exception */ public function countDuplicated(string $userId): int { $qb = $this->db->getQueryBuilder(); $qb->selectDistinct($qb->func()->count('b.id')); + $qb + ->from('bookmarks', 'b') + ->innerJoin('b', 'bookmarks_tree', 'tr', 'b.id = tr.id AND tr.type = ' . $qb->createPositionalParameter(TreeMapper::TYPE_BOOKMARK) . ' AND tr.soft_deleted_at is NULL') + ->where($qb->expr()->eq('b.user_id', $qb->createPositionalParameter($userId))); + $subQuery = $this->db->getQueryBuilder(); + $subQuery->select('trdup.parent_folder') + ->from('bookmarks_tree', 'trdup') + ->where($subQuery->expr()->eq('b.id', 'trdup.id')) + ->andWhere($subQuery->expr()->neq('trdup.parent_folder', 'tr.parent_folder')) + ->andWhere($subQuery->expr()->eq('trdup.type', $qb->createPositionalParameter(TreeMapper::TYPE_BOOKMARK))); + $qb->andWhere($qb->createFunction('EXISTS(' . $subQuery->getSQL() . ')')); + $result = $qb->executeQuery(); + $userOwnerDuplicatesCount = $result->fetch(PDO::FETCH_COLUMN); + $result->closeCursor(); + $qb = $this->db->getQueryBuilder(); + $qb->selectDistinct($qb->func()->count('b.id')); $qb ->from('bookmarks', 'b') - ->leftJoin('b', 'bookmarks_tree', 'tr', 'b.id = tr.id AND tr.type = ' . $qb->createPositionalParameter(TreeMapper::TYPE_BOOKMARK) . ' AND tr.soft_deleted_at is NULL') - ->leftJoin('tr', 'bookmarks_shared_folders', 'sf', $qb->expr()->eq('tr.parent_folder', 'sf.folder_id')) - ->where( - $qb->expr()->andX( - $qb->expr()->orX( - $qb->expr()->eq('b.user_id', $qb->createPositionalParameter($userId)), - $qb->expr()->eq('sf.user_id', $qb->createPositionalParameter($userId)) - ), - $qb->expr()->in('b.user_id', array_map([$qb, 'createPositionalParameter'], array_merge($this->_findSharersFor($userId), [$userId]))), - ) - ); + ->innerJoin('b', 'bookmarks_tree', 'tr', 'b.id = tr.id AND tr.type = ' . $qb->createPositionalParameter(TreeMapper::TYPE_BOOKMARK) . ' AND tr.soft_deleted_at is NULL') + ->innerJoin('tr', 'bookmarks_shared_folders', 'sf', $qb->expr()->eq('tr.parent_folder', 'sf.folder_id')) + ->where($qb->expr()->eq('sf.user_id', $qb->createPositionalParameter($userId))); $subQuery = $this->db->getQueryBuilder(); $subQuery->select('trdup.parent_folder') ->from('bookmarks_tree', 'trdup') @@ -662,8 +690,11 @@ public function countDuplicated(string $userId): int { ->andWhere($subQuery->expr()->neq('trdup.parent_folder', 'tr.parent_folder')) ->andWhere($subQuery->expr()->eq('trdup.type', $qb->createPositionalParameter(TreeMapper::TYPE_BOOKMARK))); $qb->andWhere($qb->createFunction('EXISTS(' . $subQuery->getSQL() . ')')); + $result = $qb->executeQuery(); + $foreignDuplicatesCount = $result->fetch(PDO::FETCH_COLUMN); + $result->closeCursor(); - return $qb->execute()->fetch(PDO::FETCH_COLUMN); + return $userOwnerDuplicatesCount + $foreignDuplicatesCount; } /** @@ -704,7 +735,7 @@ public function findAllInPublicFolder(string $token, QueryParameters $queryParam $qb ->from('*PREFIX*bookmarks', 'b') - ->join('b', 'folder_tree', 'tree', 'tree.item_id = b.id AND tree.type = ' . $qb->createPositionalParameter(TreeMapper::TYPE_BOOKMARK) . ' AND tree.soft_deleted_at is NULL'); + ->innerJoin('b', 'folder_tree', 'tree', 'tree.item_id = b.id AND tree.type = ' . $qb->createPositionalParameter(TreeMapper::TYPE_BOOKMARK) . ' AND tree.soft_deleted_at is NULL'); $this->_filterUrl($qb, $queryParams); From d61df103019a3048ca0e27bef844613348e0be46 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sun, 1 Feb 2026 11:39:49 +0100 Subject: [PATCH 02/18] perf(HtmlImporter): Use transactions of size 10k Signed-off-by: Marcel Klehr --- lib/Controller/BookmarkController.php | 2 +- lib/Service/FolderService.php | 6 +- lib/Service/HtmlImporter.php | 101 +++++++++++--------------- 3 files changed, 49 insertions(+), 60 deletions(-) diff --git a/lib/Controller/BookmarkController.php b/lib/Controller/BookmarkController.php index 2c77de5ec..de9bfe05a 100644 --- a/lib/Controller/BookmarkController.php +++ b/lib/Controller/BookmarkController.php @@ -744,7 +744,7 @@ public function importBookmark($folder = null): JSONResponse { return new JSONResponse(['status' => 'error', 'data' => ['Could not import all bookmarks: User limit Exceeded']], Http::STATUS_BAD_REQUEST); } catch (AlreadyExistsError $e) { return new JSONResponse(['status' => 'error', 'data' => ['Could not import all bookmarks: Already exists']], Http::STATUS_BAD_REQUEST); - } catch (UnsupportedOperation $e) { + } catch (UnsupportedOperation|\OCP\DB\Exception $e) { return new JSONResponse(['status' => 'error', 'data' => ['Internal server error']], Http::STATUS_INTERNAL_SERVER_ERROR); } if (count($result['errors']) !== 0) { diff --git a/lib/Service/FolderService.php b/lib/Service/FolderService.php index 37ff115e2..b8d005e73 100644 --- a/lib/Service/FolderService.php +++ b/lib/Service/FolderService.php @@ -421,11 +421,13 @@ public function addSharedFolder(Share $share, Folder $folder, string $userId): v * @param $file * @param int $folder * @return array - * @throws DoesNotExistException - * @throws MultipleObjectsReturnedException * @throws AlreadyExistsError + * @throws DoesNotExistException + * @throws Exception * @throws HtmlParseError + * @throws MultipleObjectsReturnedException * @throws UnauthorizedAccessError + * @throws UnsupportedOperation * @throws UserLimitExceededError */ public function importFile(string $userId, $file, $folder): array { diff --git a/lib/Service/HtmlImporter.php b/lib/Service/HtmlImporter.php index 188b11df2..edfbe9f81 100644 --- a/lib/Service/HtmlImporter.php +++ b/lib/Service/HtmlImporter.php @@ -23,6 +23,8 @@ use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\Entity; use OCP\AppFramework\Db\MultipleObjectsReturnedException; +use OCP\DB\Exception; +use OCP\IDBConnection; /** * Class HtmlImporter @@ -32,50 +34,19 @@ class HtmlImporter { // Taken from https://stackoverflow.com/questions/33126595/what-is-the-actual-range-of-a-mysql-int-column-in-this-situation public const DB_MAX_INT = 2147483647; - - /** - * @var BookmarkMapper - */ - protected $bookmarkMapper; - - /** - * @var FolderMapper - */ - protected $folderMapper; - - /** - * @var TagMapper - */ - protected $tagMapper; - - /** @var BookmarksParser */ - private $bookmarksParser; - /** - * @var TreeMapper - */ - private $treeMapper; - /** - * @var TreeCacheManager - */ - private $hashManager; - - /** - * ImportService constructor. - * - * @param BookmarkMapper $bookmarkMapper - * @param FolderMapper $folderMapper - * @param TagMapper $tagMapper - * @param TreeMapper $treeMapper - * @param BookmarksParser $bookmarksParser - * @param TreeCacheManager $hashManager - */ - public function __construct(BookmarkMapper $bookmarkMapper, FolderMapper $folderMapper, TagMapper $tagMapper, TreeMapper $treeMapper, BookmarksParser $bookmarksParser, \OCA\Bookmarks\Service\TreeCacheManager $hashManager) { - $this->bookmarkMapper = $bookmarkMapper; - $this->folderMapper = $folderMapper; - $this->tagMapper = $tagMapper; - $this->treeMapper = $treeMapper; - $this->bookmarksParser = $bookmarksParser; - $this->hashManager = $hashManager; + + private int $transactionCounter = 0; + + + public function __construct( + private BookmarkMapper $bookmarkMapper, + private FolderMapper $folderMapper, + private TagMapper $tagMapper, + private TreeMapper $treeMapper, + private BookmarksParser $bookmarksParser, + private \OCA\Bookmarks\Service\TreeCacheManager $hashManager, + private IDBConnection $connection, + ) { } /** @@ -87,12 +58,14 @@ public function __construct(BookmarkMapper $bookmarkMapper, FolderMapper $folder * * @return array * + * @throws AlreadyExistsError * @throws DoesNotExistException + * @throws Exception + * @throws HtmlParseError * @throws MultipleObjectsReturnedException * @throws UnauthorizedAccessError - * @throws AlreadyExistsError + * @throws UnsupportedOperation * @throws UserLimitExceededError - * @throws HtmlParseError */ public function importFile(string $userId, string $file, ?int $rootFolder = null): array { $content = file_get_contents($file); @@ -114,6 +87,7 @@ public function importFile(string $userId, string $file, ?int $rootFolder = null * @throws MultipleObjectsReturnedException * @throws UnauthorizedAccessError * @throws UserLimitExceededError|UnsupportedOperation + * @throws Exception * * @psalm-return array{imported: list, errors: array} */ @@ -135,17 +109,23 @@ public function import(string $userId, string $content, ?int $rootFolderId = nul // Disable invalidation, since we're going to add a bunch of new data to the tree at a single point $this->hashManager->setInvalidationEnabled(false); - foreach ($this->bookmarksParser->currentFolder['children'] as $folder) { - $imported[] = $this->importFolder($userId, $folder, $rootFolder->getId(), $errors); - } - foreach ($this->bookmarksParser->currentFolder['bookmarks'] as $bookmark) { - try { - $bm = $this->importBookmark($userId, $rootFolder->getId(), $bookmark); - } catch (UrlParseError $e) { - $errors[] = 'Failed to parse URL: ' . $bookmark['href']; - continue; + $this->connection->beginTransaction(); + $this->transactionCounter = 0; + try { + foreach ($this->bookmarksParser->currentFolder['children'] as $folder) { + $imported[] = $this->importFolder($userId, $folder, $rootFolder->getId(), $errors); } - $imported[] = ['type' => 'bookmark', 'id' => $bm->getId(), 'title' => $bookmark['title'], 'url' => $bookmark['href']]; + foreach ($this->bookmarksParser->currentFolder['bookmarks'] as $bookmark) { + try { + $bm = $this->importBookmark($userId, $rootFolder->getId(), $bookmark); + } catch (UrlParseError $e) { + $errors[] = 'Failed to parse URL: ' . $bookmark['href']; + continue; + } + $imported[] = ['type' => 'bookmark', 'id' => $bm->getId(), 'title' => $bookmark['title'], 'url' => $bookmark['href']]; + } + } finally { + $this->connection->commit(); } $this->hashManager->setInvalidationEnabled(true); @@ -201,7 +181,7 @@ private function importFolder(string $userId, array $folderParams, int $parentId * @param array $bookmark * @param null|int $index * @return Bookmark|Entity - * @throws UrlParseError|AlreadyExistsError|UnsupportedOperation|UserLimitExceededError|MultipleObjectsReturnedException + * @throws UrlParseError|AlreadyExistsError|UnsupportedOperation|UserLimitExceededError|MultipleObjectsReturnedException|Exception */ private function importBookmark(string $userId, int $folderId, array $bookmark, $index = null) { $bm = new Bookmark(); @@ -224,6 +204,13 @@ private function importBookmark(string $userId, int $folderId, array $bookmark, // add tags $this->tagMapper->addTo($bookmark['tags'], $bm->getId()); + $this->transactionCounter++; + if ($this->transactionCounter >= 10_000) { + $this->transactionCounter = 0; + $this->connection->commit(); + $this->connection->beginTransaction(); + } + return $bm; } } From 4795606ab87b813dcb2c59c9de87e1756fd07d4b Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sun, 1 Feb 2026 11:55:14 +0100 Subject: [PATCH 03/18] perf(BookmarksParser): Do not parse folder tags anymore Signed-off-by: Marcel Klehr --- lib/Service/BookmarksParser.php | 32 ++------------------------------ 1 file changed, 2 insertions(+), 30 deletions(-) diff --git a/lib/Service/BookmarksParser.php b/lib/Service/BookmarksParser.php index 98e61c9e6..5c97aa876 100644 --- a/lib/Service/BookmarksParser.php +++ b/lib/Service/BookmarksParser.php @@ -73,12 +73,7 @@ class BookmarksParser { * @var bool */ private $ignorePersonalToolbarFolder = true; - /** - * If tags should be included - * - * @var bool - */ - private $includeFolderTags = true; + /** * Constructor @@ -105,14 +100,13 @@ public static function isValid($doctype): bool { * * @param string $input A Netscape Bookmark File Format HTML string * @param bool $ignorePersonalToolbarFolder If we should ignore the personal toolbar bookmark folder - * @param bool $includeFolderTags If we should include folter tags * @param bool $useDateTimeObjects If we should return \DateTime objects * * @return mixed A PHP value * * @throws HtmlParseError */ - public function parse($input, $ignorePersonalToolbarFolder = true, $includeFolderTags = true, $useDateTimeObjects = true) { + public function parse($input, $ignorePersonalToolbarFolder = true, $useDateTimeObjects = true) { $document = new DOMDocument(); $document->preserveWhiteSpace = false; if (empty($input)) { @@ -123,7 +117,6 @@ public function parse($input, $ignorePersonalToolbarFolder = true, $includeFolde } $this->xpath = new DOMXPath($document); $this->ignorePersonalToolbarFolder = $ignorePersonalToolbarFolder; - $this->includeFolderTags = $includeFolderTags; $this->useDateTimeObjects = $useDateTimeObjects; // set root folder @@ -217,12 +210,6 @@ private function addBookmark(DOMNode $node): void { 'tags' => [], ]; $bookmark = array_merge($bookmark, $this->getAttributes($node)); - if ($this->includeFolderTags) { - $tags = $this->getCurrentFolderTags(); - if (!empty($tags)) { - $bookmark['tags'] = $tags; - } - } $this->currentFolder['bookmarks'][] = & $bookmark; $this->bookmarks[] = & $bookmark; } @@ -289,19 +276,4 @@ private function getAttributes(DOMNode $node): array { } return $attributes; } - - /** - * Get current folder tags - * - * @return array - */ - private function getCurrentFolderTags(): array { - $tags = []; - array_walk_recursive($this->currentFolder, static function ($tag, $key) use (&$tags) { - if ($key === 'name') { - $tags[] = $tag; - } - }); - return $tags; - } } From dd78e2947ec9450f0d469ac3da6b580d830bd273 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sun, 1 Feb 2026 11:57:40 +0100 Subject: [PATCH 04/18] chore: Run cs:fix Signed-off-by: Marcel Klehr --- lib/Service/HtmlImporter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Service/HtmlImporter.php b/lib/Service/HtmlImporter.php index edfbe9f81..7ce6f9ea9 100644 --- a/lib/Service/HtmlImporter.php +++ b/lib/Service/HtmlImporter.php @@ -34,7 +34,7 @@ class HtmlImporter { // Taken from https://stackoverflow.com/questions/33126595/what-is-the-actual-range-of-a-mysql-int-column-in-this-situation public const DB_MAX_INT = 2147483647; - + private int $transactionCounter = 0; From d788efca1303414a9fa27a265fa81dfed4aa1e18 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sun, 1 Feb 2026 12:02:03 +0100 Subject: [PATCH 05/18] chore: Set psalm level to 5 Signed-off-by: Marcel Klehr --- psalm.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/psalm.xml b/psalm.xml index c6ff140cd..8778c7014 100644 --- a/psalm.xml +++ b/psalm.xml @@ -5,7 +5,7 @@ ~ This file is licensed under the Affero General Public License version 3 or later. See the COPYING file. --> Date: Sun, 1 Feb 2026 14:23:29 +0100 Subject: [PATCH 06/18] perf: Improve html import shaves off 80% in my test case Signed-off-by: Marcel Klehr --- lib/Db/BookmarkMapper.php | 21 +++++---- .../Version016002000Date20260201124721.php | 38 +++++++++++++++ lib/Service/BookmarksParser.php | 46 ++++++++++++------- lib/Service/HtmlImporter.php | 10 ++-- 4 files changed, 86 insertions(+), 29 deletions(-) create mode 100644 lib/Migration/Version016002000Date20260201124721.php diff --git a/lib/Db/BookmarkMapper.php b/lib/Db/BookmarkMapper.php index 985358bc9..4a5ce27a6 100644 --- a/lib/Db/BookmarkMapper.php +++ b/lib/Db/BookmarkMapper.php @@ -853,16 +853,15 @@ public function insert(Entity $entity): Bookmark { $entity->setClickcount(0); try { - $this->findByUrl($entity->getUserId(), $entity->getUrl()); - } catch (DoesNotExistException $e) { parent::insert($entity); $this->eventDispatcher->dispatchTyped(new InsertEvent('bookmark', $entity->getId())); return $entity; - } catch (MultipleObjectsReturnedException $e) { - // noop + } catch (Exception $e) { + if ($e->getReason() === Exception::REASON_UNIQUE_CONSTRAINT_VIOLATION) { + throw new AlreadyExistsError('A bookmark with this URL already exists'); + } + throw $e; } - - throw new AlreadyExistsError('A bookmark with this URL already exists'); } /** @@ -877,9 +876,13 @@ public function insertOrUpdate(Entity $entity): Bookmark { try { $newEntity = $this->insert($entity); } catch (AlreadyExistsError $e) { - $bookmark = $this->findByUrl($entity->getUserId(), $entity->getUrl()); - $entity->setId($bookmark->getId()); - $newEntity = $this->update($entity); + try { + $bookmark = $this->findByUrl($entity->getUserId(), $entity->getUrl()); + $entity->setId($bookmark->getId()); + $newEntity = $this->update($entity); + } catch (DoesNotExistException $e) { + $newEntity = $this->insert($entity); + } } return $newEntity; diff --git a/lib/Migration/Version016002000Date20260201124721.php b/lib/Migration/Version016002000Date20260201124721.php new file mode 100644 index 000000000..f9b39f2f4 --- /dev/null +++ b/lib/Migration/Version016002000Date20260201124721.php @@ -0,0 +1,38 @@ +hasTable('bookmarks')) { + $table = $schema->getTable('bookmarks'); + $table->addUniqueConstraint(['user_id', 'url']); + } + + return $schema; + } + +} diff --git a/lib/Service/BookmarksParser.php b/lib/Service/BookmarksParser.php index 5c97aa876..9f56573bc 100644 --- a/lib/Service/BookmarksParser.php +++ b/lib/Service/BookmarksParser.php @@ -127,19 +127,18 @@ public function parse($input, $ignorePersonalToolbarFolder = true, $useDateTimeO return empty($this->bookmarks) ? null : $this->bookmarks; } - /** - * Traverses a DOMNode - * - * @param DOMNode|null $node - */ private function traverse(?DOMNode $node = null): void { - $query = './*'; - $entries = $this->xpath->query($query, $node ?: null); + if ($node !== null) { + $entries = $node->childNodes; + } else { + $query = './*'; + $entries = $this->xpath->query($query, null); + } if (!$entries) { return; } - for ($i = 0; $i < $entries->length; $i++) { - $entry = $entries->item($i); + + foreach ($entries as $entry) { if ($entry === null) { continue; } @@ -240,13 +239,8 @@ private function addDescription(DOMNode $node): void { private function getAttributes(DOMNode $node): array { $attributes = []; if ($node->attributes) { - $length = $node->attributes->length; - for ($i = 0; $i < $length; ++$i) { - $item = $node->attributes->item($i); - if ($item === null) { - continue; - } - $attributes[strtolower($item->nodeName)] = $item->nodeValue; + foreach ($node->attributes as $attribute) { + $attributes[strtolower($attribute->nodeName)] = $attribute->nodeValue; } } $lastModified = null; @@ -270,6 +264,26 @@ private function getAttributes(DOMNode $node): array { $modified->setTimestamp($attributes['last_modified'] instanceof DateTime ? $attributes['last_modified']->getTimestamp() : (int)$attributes['last_modified']); $attributes['last_modified'] = $modified; } + } else { + if (isset($attributes['add_date'])) { + if ((int)$attributes['add_date'] > self::THOUSAND_YEARS) { + // Google exports dates in miliseconds. This way we only lose the first year of UNIX Epoch. + // This is invalid once we hit 2970. So, quite a long time. + $attributes['add_date'] = ((int)($attributes['add_date']) / 1000); + } else { + $attributes['add_date'] = (int)$attributes['add_date']; + } + } + if (isset($attributes['last_modified'])) { + $attributes['last_modified'] = $attributes['last_modified'] instanceof DateTime ? $attributes['last_modified']->getTimestamp() : (int)$attributes['last_modified']; + if ((int)$attributes['last_modified'] > self::THOUSAND_YEARS) { + // Google exports dates in miliseconds. This way we only lose the first year of UNIX Epoch. + // This is invalid once we hit 2970. So, quite a long time. + $attributes['last_modified'] = ((int)($attributes['last_modified']) / 1000); + } else { + $attributes['last_modified'] = (int)$attributes['last_modified']; + } + } } if (isset($attributes['tags'])) { $attributes['tags'] = explode(',', $attributes['tags']); diff --git a/lib/Service/HtmlImporter.php b/lib/Service/HtmlImporter.php index 7ce6f9ea9..fbd202d4d 100644 --- a/lib/Service/HtmlImporter.php +++ b/lib/Service/HtmlImporter.php @@ -104,7 +104,7 @@ public function import(string $userId, string $content, ?int $rootFolderId = nul } } - $this->bookmarksParser->parse($content, false); + $this->bookmarksParser->parse($content, false, useDateTimeObjects: false); // Disable invalidation, since we're going to add a bunch of new data to the tree at a single point $this->hashManager->setInvalidationEnabled(false); @@ -115,9 +115,11 @@ public function import(string $userId, string $content, ?int $rootFolderId = nul foreach ($this->bookmarksParser->currentFolder['children'] as $folder) { $imported[] = $this->importFolder($userId, $folder, $rootFolder->getId(), $errors); } + // Might not start at 0 because this folder already exists + $index = $this->treeMapper->countChildren($rootFolder->getId()); foreach ($this->bookmarksParser->currentFolder['bookmarks'] as $bookmark) { try { - $bm = $this->importBookmark($userId, $rootFolder->getId(), $bookmark); + $bm = $this->importBookmark($userId, $rootFolder->getId(), $bookmark, $index++); } catch (UrlParseError $e) { $errors[] = 'Failed to parse URL: ' . $bookmark['href']; continue; @@ -190,8 +192,8 @@ private function importBookmark(string $userId, int $folderId, array $bookmark, $bm->setTitle($bookmark['title']); $bm->setDescription($bookmark['description']); if (isset($bookmark['add_date'])) { - if ($bookmark['add_date']->getTimestamp() < self::DB_MAX_INT && $bookmark['add_date']->getTimestamp() > -self::DB_MAX_INT) { - $bm->setAdded($bookmark['add_date']->getTimestamp()); + if ($bookmark['add_date'] < self::DB_MAX_INT && $bookmark['add_date'] > -self::DB_MAX_INT) { + $bm->setAdded($bookmark['add_date']); } else { $bm->setAdded(time()); } From a8e97b4732c6e000c74c2e0c62176f1f8eac56c3 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sun, 1 Feb 2026 15:12:22 +0100 Subject: [PATCH 07/18] fix: Add url_hash column for unique index Signed-off-by: Marcel Klehr --- lib/Db/Bookmark.php | 11 ++- lib/Db/BookmarkMapper.php | 7 +- lib/Db/BookmarkWithTagsAndParent.php | 7 +- .../Version016002000Date20260201124721.php | 38 ---------- .../Version016002000Date20260201124723.php | 75 +++++++++++++++++++ 5 files changed, 93 insertions(+), 45 deletions(-) delete mode 100644 lib/Migration/Version016002000Date20260201124721.php create mode 100644 lib/Migration/Version016002000Date20260201124723.php diff --git a/lib/Db/Bookmark.php b/lib/Db/Bookmark.php index 1f1bd693b..888c26e48 100644 --- a/lib/Db/Bookmark.php +++ b/lib/Db/Bookmark.php @@ -32,6 +32,8 @@ * @method setArchivedFile(int $fileId) * @method getUserId(): string * @method setUserId(string $userId) + * @method setUrlHash(string $urlHash) + * @method getUrlHash():string */ class Bookmark extends Entity { protected $url; @@ -47,9 +49,10 @@ class Bookmark extends Entity { protected $archivedFile; protected $textContent; protected $htmlContent; + protected $urlHash; - public static $columns = ['id', 'url', 'title', 'description', 'lastmodified', 'added', 'clickcount', 'last_preview', 'available', 'archived_file', 'user_id', 'text_content', 'html_content']; - public static $fields = ['id', 'url', 'title', 'description', 'lastmodified', 'added', 'clickcount', 'lastPreview', 'available', 'archivedFile', 'userId', 'textContent','htmlContent']; + public static $columns = ['id', 'url', 'title', 'description', 'lastmodified', 'added', 'clickcount', 'last_preview', 'available', 'archived_file', 'user_id', 'text_content', 'html_content', 'url_hash']; + public static $fields = ['id', 'url', 'title', 'description', 'lastmodified', 'added', 'clickcount', 'lastPreview', 'available', 'archivedFile', 'userId', 'textContent','htmlContent', 'urlHash']; public static function fromArray($props): self { $bookmark = new Bookmark(); @@ -75,6 +78,7 @@ public function __construct() { $this->addType('lastPreview', 'integer'); $this->addType('available', 'boolean'); $this->addType('archivedFile', 'integer'); + $this->addType('urlHash', 'string'); } public function toArray(): array { @@ -89,6 +93,9 @@ public function toArray(): array { $array['target'] = $this->url; continue; } + if ($field === 'urlHash') { + continue; + } $array[$field] = $this->{'get' . $field}(); } return $array; diff --git a/lib/Db/BookmarkMapper.php b/lib/Db/BookmarkMapper.php index 4a5ce27a6..b2a3a1e7d 100644 --- a/lib/Db/BookmarkMapper.php +++ b/lib/Db/BookmarkMapper.php @@ -109,7 +109,7 @@ protected function getFindByUrlQuery(): IQueryBuilder { ->select('*') ->from('bookmarks') ->where($qb->expr()->eq('user_id', $qb->createParameter('user_id'))) - ->andWhere($qb->expr()->eq('url', $qb->createParameter('url'))); + ->andWhere($qb->expr()->eq('url_hash', $qb->createParameter('url_hash'))); return $qb; } @@ -132,7 +132,7 @@ public function findByUrl($userId, $url): Bookmark { $qb = $this->findByUrlQuery; $qb->setParameters([ 'user_id' => $userId, - 'url' => $url + 'url_hash' => md5($url), ]); return $this->findEntity($qb); } @@ -399,7 +399,7 @@ private function _sortAndPaginate(IQueryBuilder $qb, QueryParameters $params): v private function _filterUrl(IQueryBuilder $qb, QueryParameters $params): void { if (($url = $params->getUrl()) !== null) { $normalized = $this->urlNormalizer->normalize($url); - $qb->andWhere($qb->expr()->eq('b.url', $qb->createPositionalParameter($normalized))); + $qb->andWhere($qb->expr()->eq('b.url_hash', $qb->createPositionalParameter(md5($normalized)))); } } @@ -844,6 +844,7 @@ public function insert(Entity $entity): Bookmark { if ($entity->isWebLink()) { $entity->setUrl($this->urlNormalizer->normalize($entity->getUrl())); } + $entity->setUrlHash(md5($entity->getUrl())); if ($entity->getAdded() === null) { $entity->setAdded(time()); diff --git a/lib/Db/BookmarkWithTagsAndParent.php b/lib/Db/BookmarkWithTagsAndParent.php index 35ae1bed8..edd53b58b 100644 --- a/lib/Db/BookmarkWithTagsAndParent.php +++ b/lib/Db/BookmarkWithTagsAndParent.php @@ -18,8 +18,8 @@ class BookmarkWithTagsAndParent extends Bookmark { protected $tags; protected $folders; - public static $columns = ['id', 'url', 'title', 'description', 'lastmodified', 'added', 'clickcount', 'last_preview', 'available', 'archived_file', 'user_id', 'tags', 'folders', 'text_content', 'html_content']; - public static $fields = ['id', 'url', 'title', 'description', 'lastmodified', 'added', 'clickcount', 'lastPreview', 'available', 'archivedFile', 'userId', 'tags', 'folders', 'textContent', 'htmlContent']; + public static $columns = ['id', 'url', 'title', 'description', 'lastmodified', 'added', 'clickcount', 'last_preview', 'available', 'archived_file', 'user_id', 'tags', 'folders', 'text_content', 'html_content', 'url_hash']; + public static $fields = ['id', 'url', 'title', 'description', 'lastmodified', 'added', 'clickcount', 'lastPreview', 'available', 'archivedFile', 'userId', 'tags', 'folders', 'textContent', 'htmlContent', 'urlHash']; public function toArray(): array { $array = []; @@ -49,6 +49,9 @@ public function toArray(): array { $array['target'] = $this->url; continue; } + if ($field === 'urlHash') { + continue; + } $array[$field] = $this->{$field}; } return $array; diff --git a/lib/Migration/Version016002000Date20260201124721.php b/lib/Migration/Version016002000Date20260201124721.php deleted file mode 100644 index f9b39f2f4..000000000 --- a/lib/Migration/Version016002000Date20260201124721.php +++ /dev/null @@ -1,38 +0,0 @@ -hasTable('bookmarks')) { - $table = $schema->getTable('bookmarks'); - $table->addUniqueConstraint(['user_id', 'url']); - } - - return $schema; - } - -} diff --git a/lib/Migration/Version016002000Date20260201124723.php b/lib/Migration/Version016002000Date20260201124723.php new file mode 100644 index 000000000..04c1d0ea2 --- /dev/null +++ b/lib/Migration/Version016002000Date20260201124723.php @@ -0,0 +1,75 @@ +hasTable('bookmarks')) { + $table = $schema->getTable('bookmarks'); + if (!$table->hasColumn('url_hash')) { + $table->addColumn('url_hash', 'string', [ + 'notnull' => false, + 'length' => 32, + ]); + $table->addUniqueIndex(['user_id', 'url_hash'], 'bookmarks_uniq_url'); + } + } + + return $schema; + } + + public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options) { + $countQb = $this->db->getQueryBuilder(); + $countQb->select($countQb->func()->count('id'))->from('bookmarks')->where($countQb->expr()->isNull('url_hash')); + $result = $countQb->executeQuery(); + $count = $result->fetchOne(); + $output->info('Hashing URLs of n=' . $count . ' bookmarks'); + $output->startProgress($count); + + $qb = $this->db->getQueryBuilder(); + $qb->select('id', 'url')->from('bookmarks')->where($qb->expr()->isNull('url_hash')); + $result = $qb->executeQuery(); + $setQb = $this->db->getQueryBuilder(); + $setQb->update('bookmarks') + ->set('url_hash', $qb->createParameter('url_hash')) + ->where($qb->expr()->eq('id', $qb->createParameter('id'))); + while ($row = $result->fetch()) { + $setQb->setParameter('url_hash', md5($row['url'])); + $setQb->setParameter('id', $row['id']); + $setQb->executeStatement(); + $output->advance(); + } + $output->finishProgress(); + $result->closeCursor(); + } + +} From 9d0275abc4502ab1ce42b8955222d8b2e0b5747e Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sun, 1 Feb 2026 15:59:30 +0100 Subject: [PATCH 08/18] chore: Modernize QueryBuilder usage Signed-off-by: Marcel Klehr --- lib/Db/BookmarkMapper.php | 18 +++---- lib/Db/FolderMapper.php | 3 +- lib/Db/SharedFolderMapper.php | 4 +- lib/Db/TagMapper.php | 36 +++++++++----- lib/Db/TreeMapper.php | 47 ++++++++++--------- .../DeduplicateSharedFoldersRepairStep.php | 8 ++-- lib/Migration/GroupSharesUpdateRepairStep.php | 4 +- lib/Migration/OrphanedSharesRepairStep.php | 14 +++--- lib/Migration/OrphanedTreeItemsRepairStep.php | 42 ++++++++--------- .../SuperfluousSharedFoldersRepairStep.php | 8 ++-- .../Version000014000Date20181002094721.php | 6 +-- .../Version000014000Date20181029094721.php | 10 ++-- .../Version003000000Date20191123094721.php | 28 +++++------ .../Version003001000Date20200526094721.php | 10 ++-- .../Version004002000Date20210208124721.php | 2 +- .../Version010002000Date20220319124721.php | 4 +- lib/Service/LockManager.php | 6 ++- 17 files changed, 135 insertions(+), 115 deletions(-) diff --git a/lib/Db/BookmarkMapper.php b/lib/Db/BookmarkMapper.php index b2a3a1e7d..2d424cf98 100644 --- a/lib/Db/BookmarkMapper.php +++ b/lib/Db/BookmarkMapper.php @@ -141,17 +141,19 @@ public function findByUrl($userId, $url): Bookmark { * @param string $userId * @throws DoesNotExistException * @throws MultipleObjectsReturnedException + * @throws Exception */ public function deleteAll(string $userId): void { $qb = $this->db->getQueryBuilder(); $qb->select('b.id') ->from('bookmarks', 'b') ->where($qb->expr()->eq('b.user_id', $qb->createPositionalParameter($userId))); - $orphanedBookmarks = $qb->execute(); - while ($bookmark = $orphanedBookmarks->fetchColumn()) { + $result = $qb->executeQuery(); + while ($bookmark = $result->fetchOne()) { $bm = $this->find($bookmark); $this->delete($bm); } + $result->closeCursor(); } /** @@ -804,7 +806,7 @@ public function delete(Entity $entity): Bookmark { $qb = $this->deleteTagsQuery; $qb->setParameter('id', $id); - $qb->execute(); + $qb->executeStatement(); return $returnedEntity; } @@ -890,10 +892,7 @@ public function insertOrUpdate(Entity $entity): Bookmark { } /** - * @param $userId - * @param string $userId - * - * @return int + * @throws Exception */ public function countBookmarksOfUser(string $userId) : int { $qb = $this->db->getQueryBuilder(); @@ -901,7 +900,10 @@ public function countBookmarksOfUser(string $userId) : int { ->select($qb->func()->count('id')) ->from('bookmarks') ->where($qb->expr()->eq('user_id', $qb->createPositionalParameter($userId))); - return $qb->execute()->fetch(PDO::FETCH_COLUMN); + $result = $qb->executeQuery(); + $count = $result->fetchOne(); + $result->closeCursor(); + return $count; } /** diff --git a/lib/Db/FolderMapper.php b/lib/Db/FolderMapper.php index d8d3c67e5..2d2b89d47 100644 --- a/lib/Db/FolderMapper.php +++ b/lib/Db/FolderMapper.php @@ -80,6 +80,7 @@ public function find(int $id): Folder { * @param string $userId * * @return Folder + * @throws Exception */ public function findRootFolder(string $userId): Folder { $qb = $this->db->getQueryBuilder(); @@ -105,7 +106,7 @@ public function findRootFolder(string $userId): Folder { 'user_id' => $qb->createPositionalParameter($userId), 'folder_id' => $qb->createPositionalParameter($rootFolder->getId(), IQueryBuilder::PARAM_INT), ]) - ->execute(); + ->executeStatement(); } catch (MultipleObjectsReturnedException $e) { $rootFolders = $this->findEntities($qb); return $rootFolders[0]; diff --git a/lib/Db/SharedFolderMapper.php b/lib/Db/SharedFolderMapper.php index 118f478eb..2cc976faa 100644 --- a/lib/Db/SharedFolderMapper.php +++ b/lib/Db/SharedFolderMapper.php @@ -228,7 +228,7 @@ public function delete(Entity $entity): SharedFolder { $qb = $this->db->getQueryBuilder(); $qb->delete('bookmarks_shared_to_shares') ->where($qb->expr()->eq('shared_folder_id', $qb->createPositionalParameter($entity->getId(), IQueryBuilder::PARAM_INT))) - ->execute(); + ->executeStatement(); return parent::delete($entity); } @@ -237,7 +237,7 @@ public function mount(int $id, int $share_id): void { $qb->insert('bookmarks_shared_to_shares')->values([ 'shared_folder_id' => $qb->createPositionalParameter($id), 'share_id' => $qb->createPositionalParameter($share_id, IQueryBuilder::PARAM_INT) - ])->execute(); + ])->executeStatement(); $this->eventDispatcher->dispatch(CreateEvent::class, new CreateEvent( TreeMapper::TYPE_SHARE, $id diff --git a/lib/Db/TagMapper.php b/lib/Db/TagMapper.php index 3634be612..6bc9eeb0d 100644 --- a/lib/Db/TagMapper.php +++ b/lib/Db/TagMapper.php @@ -8,6 +8,7 @@ namespace OCA\Bookmarks\Db; +use OCP\DB\Exception; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; use PDO; @@ -35,6 +36,7 @@ public function __construct(IDBConnection $db) { /** * @param $userId * @return array + * @throws Exception */ public function findAllWithCount($userId): array { $qb = $this->db->getQueryBuilder(); @@ -53,12 +55,13 @@ public function findAllWithCount($userId): array { ->groupBy('t.tag') ->orderBy('count', 'DESC'); - return $qb->execute()->fetchAll(); + return $qb->executeQuery()->fetchAll(); } /** * @param $userId * @return array + * @throws Exception */ public function findAll($userId): array { $qb = $this->db->getQueryBuilder(); @@ -74,12 +77,13 @@ public function findAll($userId): array { $qb->expr()->eq('tr.type', $qb->createPositionalParameter(TreeMapper::TYPE_SHARE))) ) ->groupBy('t.tag'); - return $qb->execute()->fetchAll(PDO::FETCH_COLUMN); + return $qb->executeQuery()->fetchAll(PDO::FETCH_COLUMN); } /** * @param int $bookmarkId * @return array + * @throws Exception */ public function findByBookmark(int $bookmarkId): array { $qb = $this->db->getQueryBuilder(); @@ -89,12 +93,13 @@ public function findByBookmark(int $bookmarkId): array { ->from('bookmarks_tags', 't') ->where($qb->expr()->eq('t.bookmark_id', $qb->createPositionalParameter($bookmarkId, IQueryBuilder::PARAM_INT))); - return $qb->execute()->fetchAll(PDO::FETCH_COLUMN); + return $qb->executeQuery()->fetchAll(PDO::FETCH_COLUMN); } /** * @param $userId * @param string $tag + * @throws Exception */ public function delete($userId, string $tag) { $qb = $this->db->getQueryBuilder(); @@ -103,11 +108,12 @@ public function delete($userId, string $tag) { ->innerJoin('tgs', 'bookmarks', 'bm', $qb->expr()->eq('tgs.bookmark_id', 'bm.id')) ->where($qb->expr()->eq('tgs.tag', $qb->createNamedParameter($tag))) ->andWhere($qb->expr()->eq('bm.user_id', $qb->createNamedParameter($userId))); - return $qb->execute(); + return $qb->executeStatement(); } /** * @param $userId + * @throws Exception */ public function deleteAll(int $userId) { $qb = $this->db->getQueryBuilder(); @@ -115,12 +121,13 @@ public function deleteAll(int $userId) { ->delete('bookmarks_tags', 'tgs') ->innerJoin('tgs', 'bookmarks', 'bm', $qb->expr()->eq('tgs.bookmark_id', 'bm.id')) ->where($qb->expr()->eq('bm.user_id', $qb->createNamedParameter($userId))); - return $qb->execute(); + return $qb->executeStatement(); } /** * @param $tags * @param int $bookmarkId + * @throws Exception */ public function addTo(array $tags, int $bookmarkId): void { if (count($tags) === 0) { @@ -144,12 +151,13 @@ public function addTo(array $tags, int $bookmarkId): void { 'tag' => $qb->createNamedParameter($tag), 'bookmark_id' => $qb->createNamedParameter($bookmarkId), ]); - $qb->execute(); + $qb->executeStatement(); } } /** * @param int $bookmarkId + * @throws Exception */ public function removeAllFrom(int $bookmarkId): void { // Remove old tags @@ -157,7 +165,7 @@ public function removeAllFrom(int $bookmarkId): void { $qb ->delete('bookmarks_tags') ->where($qb->expr()->eq('bookmark_id', $qb->createNamedParameter($bookmarkId))); - $qb->execute(); + $qb->executeStatement(); } /** @@ -174,6 +182,7 @@ public function setOn(array $tags, int $bookmarkId): void { * @param int $userId UserId * @param string $old Old Tag Name * @param string $new New Tag Name + * @throws Exception */ public function renameTag($userId, string $old, string $new): void { // Remove about-to-be duplicated tags @@ -186,14 +195,14 @@ public function renameTag($userId, string $old, string $new): void { ->where($qb->expr()->eq('tgs.tag', $qb->createNamedParameter($new))) ->andWhere($qb->expr()->eq('bm.user_id', $qb->createNamedParameter($userId))) ->andWhere($qb->expr()->eq('t.tag', $qb->createNamedParameter($old))); - $duplicates = $qb->execute()->fetchAll(PDO::FETCH_COLUMN); + $duplicates = $qb->executeQuery()->fetchAll(PDO::FETCH_COLUMN); if (count($duplicates) !== 0) { $qb = $this->db->getQueryBuilder(); $qb ->delete('bookmarks_tags') ->where($qb->expr()->in('bookmark_id', array_map([$qb, 'createNamedParameter'], $duplicates))) ->andWhere($qb->expr()->eq('tag', $qb->createNamedParameter($old))); - $qb->execute(); + $qb->executeStatement(); } // Update tags to the new label @@ -204,7 +213,7 @@ public function renameTag($userId, string $old, string $new): void { ->innerJoin('tgs', 'bookmarks', 'bm', $qb->expr()->eq('tgs.bookmark_id', 'bm.id')) ->where($qb->expr()->eq('tgs.tag', $qb->createNamedParameter($old))) ->andWhere($qb->expr()->eq('bm.user_id', $qb->createNamedParameter($userId))); - $bookmarks = $qb->execute()->fetchAll(PDO::FETCH_COLUMN); + $bookmarks = $qb->executeQuery()->fetchAll(PDO::FETCH_COLUMN); if (count($bookmarks) !== 0) { $qb = $this->db->getQueryBuilder(); $qb @@ -212,13 +221,14 @@ public function renameTag($userId, string $old, string $new): void { ->set('tag', $qb->createNamedParameter($new)) ->where($qb->expr()->eq('tag', $qb->createNamedParameter($old))) ->andWhere($qb->expr()->in('bookmark_id', array_map([$qb, 'createNamedParameter'], $bookmarks))); - $qb->execute(); + $qb->executeStatement(); } } /** * @param $userId * @param string $old + * @throws Exception */ public function deleteTag($userId, string $old): void { $qb = $this->db->getQueryBuilder(); @@ -228,14 +238,14 @@ public function deleteTag($userId, string $old): void { ->innerJoin('t', 'bookmarks', 'bm', $qb->expr()->eq('t.bookmark_id', 'bm.id')) ->where($qb->expr()->eq('t.tag', $qb->createNamedParameter($old))) ->andWhere($qb->expr()->eq('bm.user_id', $qb->createNamedParameter($userId))); - $affectedBookmarks = $qb->execute()->fetchAll(PDO::FETCH_COLUMN); + $affectedBookmarks = $qb->executeQuery()->fetchAll(PDO::FETCH_COLUMN); if (count($affectedBookmarks) !== 0) { $qb = $this->db->getQueryBuilder(); $qb ->delete('bookmarks_tags') ->where($qb->expr()->in('bookmark_id', array_map([$qb, 'createNamedParameter'], $affectedBookmarks))) ->andWhere($qb->expr()->eq('tag', $qb->createNamedParameter($old))); - $qb->execute(); + $qb->executeStatement(); } } } diff --git a/lib/Db/TreeMapper.php b/lib/Db/TreeMapper.php index d8296424a..82aad3c90 100644 --- a/lib/Db/TreeMapper.php +++ b/lib/Db/TreeMapper.php @@ -141,7 +141,7 @@ protected function mapRowToEntityWithClass(array $row, string $entityClass): Ent * @psalm-return list */ protected function findEntitiesWithType(IQueryBuilder $query, string $type): array { - $cursor = $query->execute(); + $cursor = $query->executeQuery(); $entities = []; @@ -381,6 +381,7 @@ public function hasDescendant(int $folderId, string $type, int $descendantId): b * @throws DoesNotExistException * @throws MultipleObjectsReturnedException * @throws UnsupportedOperation + * @throws Exception */ public function deleteEntry(string $type, int $id, ?int $folderId = null): void { $this->eventDispatcher->dispatch(BeforeDeleteEvent::class, new BeforeDeleteEvent($type, $id)); @@ -405,7 +406,7 @@ public function deleteEntry(string $type, int $id, ?int $folderId = null): void ->andWhere($qb->expr()->in('parent_folder', $qb->createPositionalParameter(array_map(static function ($folder) { return $folder->getId(); }, $descendantFolders), IQueryBuilder::PARAM_INT_ARRAY))); - $qb->execute(); + $qb->executeStatement(); // remove all folders entries from this subtree foreach ($descendantFolders as $descendantFolder) { @@ -420,12 +421,12 @@ public function deleteEntry(string $type, int $id, ?int $folderId = null): void ->from('bookmarks', 'b') ->leftJoin('b', 'bookmarks_tree', 't', 'b.id = t.id AND t.type = ' . $qb->createPositionalParameter(TreeMapper::TYPE_BOOKMARK)) ->where($qb->expr()->isNull('t.id')); - $orphanedBookmarks = $qb->execute(); - while ($bookmark = $orphanedBookmarks->fetchColumn()) { + $orphanedBookmarks = $qb->executeQuery(); + while ($bookmark = $orphanedBookmarks->fetchOne()) { $qb = $this->db->getQueryBuilder(); $qb->delete('bookmarks') ->where($qb->expr()->eq('id', $qb->createPositionalParameter($bookmark))) - ->execute(); + ->executeStatement(); } return; @@ -483,7 +484,7 @@ public function softDeleteEntry(string $type, int $id, ?int $folderId = null): v ->andWhere($qb->expr()->in('parent_folder', $qb->createNamedParameter(array_map(static function ($folder) { return $folder->getId(); }, $descendantFoldersPlusThisFolder), IQueryBuilder::PARAM_INT_ARRAY))); - $qb->execute(); + $qb->executeStatement(); // soft delete all folder entries from this subtree foreach ($descendantFoldersPlusThisFolder as $descendantFolder) { @@ -598,7 +599,7 @@ public function remove(string $type, int $itemId): void { ->delete('bookmarks_tree') ->where($qb->expr()->eq('type', $qb->createPositionalParameter($type))) ->andWhere($qb->expr()->eq('id', $qb->createPositionalParameter($itemId, IQueryBuilder::PARAM_INT))); - $qb->execute(); + $qb->executeStatement(); } /** @@ -626,6 +627,7 @@ public function deleteShare(int $shareId): void { * @psalm-param 0|positive-int|null $index * @throws MultipleObjectsReturnedException * @throws UnsupportedOperation + * @throws Exception */ public function move(string $type, int $itemId, int $newParentFolderId, ?int $index = null): void { if ($type === TreeMapper::TYPE_BOOKMARK) { @@ -667,7 +669,7 @@ public function move(string $type, int $itemId, int $newParentFolderId, ?int $in ->set('index', $qb->createPositionalParameter($index ?? $this->countChildren($newParentFolderId), IQueryBuilder::PARAM_INT)) ->where($qb->expr()->eq('id', $qb->createPositionalParameter($itemId, IQueryBuilder::PARAM_INT))) ->andWhere($qb->expr()->eq('type', $qb->createPositionalParameter($type))); - $qb->execute(); + $qb->executeStatement(); } else { // Item currently has no parent => insert into tree. $qb = $this->insertQuery; @@ -677,7 +679,7 @@ public function move(string $type, int $itemId, int $newParentFolderId, ?int $in 'type' => $type, 'index' => $index ?? $this->countChildren($newParentFolderId), ]); - $qb->execute(); + $qb->executeStatement(); } $this->eventDispatcher->dispatch(MoveEvent::class, new MoveEvent( @@ -745,7 +747,7 @@ public function addToFolders(string $type, int $itemId, array $folders, ?int $in 'id' => $itemId, 'index' => $index ?? $this->countChildren($folderId), ]); - $qb->execute(); + $qb->executeStatement(); $this->eventDispatcher->dispatch(MoveEvent::class, new MoveEvent($type, $itemId, null, $folderId)); } @@ -774,7 +776,7 @@ public function removeFromFolders(string $type, int $itemId, array $folders): vo ->where($qb->expr()->eq('parent_folder', $qb->createPositionalParameter($folderId))) ->andWhere($qb->expr()->eq('id', $qb->createPositionalParameter($itemId))) ->andWhere($qb->expr()->eq('type', $qb->createPositionalParameter($type))); - $qb->execute(); + $qb->executeStatement(); $this->eventDispatcher->dispatch(MoveEvent::class, new MoveEvent($type, $itemId, $folderId)); @@ -792,6 +794,7 @@ public function removeFromFolders(string $type, int $itemId, array $folders): vo * @param $newChildrenOrder * @return void * @throws ChildrenOrderValidationError + * @throws Exception */ public function setChildrenOrder(int $folderId, array $newChildrenOrder): void { $existingChildren = $this->getChildrenOrder($folderId); @@ -815,7 +818,7 @@ public function setChildrenOrder(int $folderId, array $newChildrenOrder): void { ->where($qb->expr()->eq('t.parent_folder', $qb->createPositionalParameter($folderId))) ->andWhere($qb->expr()->eq('t.type', $qb->createPositionalParameter(TreeMapper::TYPE_SHARE))) ->orderBy('t.index', 'ASC'); - $childShares = $qb->execute()->fetchAll(); + $childShares = $qb->executeQuery()->fetchAll(); $foldersToShares = array_reduce($childShares, static function ($dict, $shareRec) { $dict[$shareRec['folder_id']] = $shareRec['id']; @@ -839,7 +842,7 @@ public function setChildrenOrder(int $folderId, array $newChildrenOrder): void { ->where($qb->expr()->eq('id', $qb->createPositionalParameter($child['id']))) ->andWhere($qb->expr()->eq('parent_folder', $qb->createPositionalParameter($folderId))) ->andWhere($qb->expr()->eq('type', $qb->createPositionalParameter($child['type']))); - $qb->execute(); + $qb->executeStatement(); } $this->eventDispatcher->dispatch(UpdateEvent::class, new UpdateEvent(TreeMapper::TYPE_FOLDER, $folderId)); @@ -863,12 +866,12 @@ public function getChildrenOrder(int $folderId, int $layers = 0): array { } $qb = $this->getChildrenOrderQuery; $qb->setParameter('parent_folder', $folderId); - $children = $qb->execute()->fetchAll(); + $children = $qb->executeQuery()->fetchAll(); $qb = $this->getChildrenQuery[TreeMapper::TYPE_SHARE]; $this->selectFromType(TreeMapper::TYPE_SHARE, ['t.index'], $qb); $qb->setParameter('parent_folder', $folderId); - $childShares = $qb->execute()->fetchAll(); + $childShares = $qb->executeQuery()->fetchAll(); $children = array_map(function ($child) use ($layers, $childShares) { $item = ['type' => $child['type'], 'id' => (int)$child['id']]; @@ -1009,6 +1012,7 @@ public function getSoftDeletedRootItems(string $userId, string $type): array { * @brief Count the children in the given folder * @param int $folderId * @return int + * @throws Exception */ public function countChildren(int $folderId): int { $qb = $this->db->getQueryBuilder(); @@ -1017,13 +1021,14 @@ public function countChildren(int $folderId): int { ->from('bookmarks_tree') ->where($qb->expr()->eq('parent_folder', $qb->createPositionalParameter($folderId))) ->andWhere($qb->expr()->isNull('soft_deleted_at')); - return $qb->execute()->fetch(PDO::FETCH_COLUMN); + return $qb->executeQuery()->fetch(PDO::FETCH_COLUMN); } /** * @brief Count the descendant bookmarks in the given folder * @param int $folderId * @return int + * @throws Exception */ public function countBookmarksInFolder(int $folderId): int { $count = $this->treeCache->get(TreeCacheManager::CATEGORY_FOLDERCOUNT, TreeMapper::TYPE_FOLDER, $folderId); @@ -1038,7 +1043,7 @@ public function countBookmarksInFolder(int $folderId): int { ->where($qb->expr()->eq('t.parent_folder', $qb->createPositionalParameter($folderId))) ->andWhere($qb->expr()->eq('t.type', $qb->createPositionalParameter(TreeMapper::TYPE_BOOKMARK))) ->andWhere($qb->expr()->isNull('t.soft_deleted_at')); - $countChildren = $qb->execute()->fetch(PDO::FETCH_COLUMN); + $countChildren = $qb->executeQuery()->fetch(PDO::FETCH_COLUMN); $qb = $this->db->getQueryBuilder(); $qb @@ -1049,7 +1054,7 @@ public function countBookmarksInFolder(int $folderId): int { ->andWhere($qb->expr()->eq('t.type', $qb->createPositionalParameter(TreeMapper::TYPE_FOLDER))) ->andWhere($qb->expr()->isNull('t.soft_deleted_at')); ; - $childFolders = $qb->execute()->fetchAll(PDO::FETCH_COLUMN); + $childFolders = $qb->executeQuery()->fetchAll(PDO::FETCH_COLUMN); foreach ($childFolders as $subFolderId) { $countChildren += $this->countBookmarksInFolder($subFolderId); @@ -1075,17 +1080,17 @@ public function getChildren(int $folderId, int $layers = 0): array { $qb = $this->getChildrenQuery[TreeMapper::TYPE_BOOKMARK]; $this->selectFromType(TreeMapper::TYPE_BOOKMARK, ['t.index', 't.type'], $qb); $qb->setParameter('parent_folder', $folderId); - $childBookmarks = $qb->execute()->fetchAll(); + $childBookmarks = $qb->executeQuery()->fetchAll(); $qb = $this->getChildrenQuery[TreeMapper::TYPE_FOLDER]; $this->selectFromType(TreeMapper::TYPE_FOLDER, ['t.index', 't.type'], $qb); $qb->setParameter('parent_folder', $folderId); - $childFolders = $qb->execute()->fetchAll(); + $childFolders = $qb->executeQuery()->fetchAll(); $qb = $this->getChildrenQuery[TreeMapper::TYPE_SHARE]; $this->selectFromType(TreeMapper::TYPE_SHARE, ['t.index', 't.type'], $qb); $qb->setParameter('parent_folder', $folderId); - $childShares = $qb->execute()->fetchAll(); + $childShares = $qb->executeQuery()->fetchAll(); $children = array_merge($childBookmarks, $childFolders, $childShares); $indices = array_column($children, 'index'); diff --git a/lib/Migration/DeduplicateSharedFoldersRepairStep.php b/lib/Migration/DeduplicateSharedFoldersRepairStep.php index 99fdbf820..781ef9988 100644 --- a/lib/Migration/DeduplicateSharedFoldersRepairStep.php +++ b/lib/Migration/DeduplicateSharedFoldersRepairStep.php @@ -42,18 +42,18 @@ public function run(IOutput $output) { ->from('bookmarks_shared_folders', 'p1') ->leftJoin('p1', 'bookmarks_shared_folders', 'p2', 'p1.folder_id = p2.folder_id AND p1.user_id = p2.user_id') ->where($qb->expr()->lt('p2.id', 'p1.id')); - $duplicateSharedFolders = $qb->execute(); + $duplicateSharedFolders = $qb->executeQuery(); $i = 0; - while ($sharedFolder = $duplicateSharedFolders->fetchColumn()) { + while ($sharedFolder = $duplicateSharedFolders->fetch(\PDO::FETCH_COLUMN)) { $qb = $this->db->getQueryBuilder(); $qb->delete('bookmarks_tree') ->where($qb->expr()->eq('id', $qb->createPositionalParameter($sharedFolder))) ->andWhere($qb->expr()->eq('type', $qb->createPositionalParameter('share'))) - ->execute(); + ->executeStatement(); $qb = $this->db->getQueryBuilder(); $qb->delete('bookmarks_shared_folders') ->where($qb->expr()->eq('id', $qb->createPositionalParameter($sharedFolder))) - ->execute(); + ->executeStatement(); $i++; } $output->info("Removed $i duplicate shares"); diff --git a/lib/Migration/GroupSharesUpdateRepairStep.php b/lib/Migration/GroupSharesUpdateRepairStep.php index f8c413651..c9c5ee08a 100644 --- a/lib/Migration/GroupSharesUpdateRepairStep.php +++ b/lib/Migration/GroupSharesUpdateRepairStep.php @@ -88,7 +88,7 @@ public function run(IOutput $output) { $qb->select('s.id', 's.participant', 's.folder_id', 's.owner') ->from('bookmarks_shares', 's') ->where($qb->expr()->eq('s.type', $qb->createPositionalParameter(IShare::TYPE_GROUP))); - $groupShares = $qb->execute(); + $groupShares = $qb->executeQuery(); while ($groupShare = $groupShares->fetch()) { // find users in share @@ -97,7 +97,7 @@ public function run(IOutput $output) { ->from('bookmarks_shared_folders', 'sf') ->join('sf', 'bookmarks_shared_to_shares', 't', $qb->expr()->eq('t.shared_folder_id', 'sf.id')) ->where($qb->expr()->eq('t.share_id', $qb->createPositionalParameter($groupShare['id']))); - $usersInShare = $qb->execute()->fetchAll(PDO::FETCH_COLUMN); + $usersInShare = $qb->executeQuery()->fetchAll(PDO::FETCH_COLUMN); $group = $this->groupManager->get($groupShare['participant']); if ($group === null) { diff --git a/lib/Migration/OrphanedSharesRepairStep.php b/lib/Migration/OrphanedSharesRepairStep.php index f30457578..889665a39 100644 --- a/lib/Migration/OrphanedSharesRepairStep.php +++ b/lib/Migration/OrphanedSharesRepairStep.php @@ -44,28 +44,28 @@ public function run(IOutput $output) { ->from('bookmarks_shares', 's') ->leftJoin('s', 'bookmarks_folders', 'f', $qb->expr()->eq('f.id', 's.folder_id')) ->where($qb->expr()->isNull('f.id')); - $shares = $qb->execute(); + $shares = $qb->executeQuery(); $i = 0; - while ($share = $shares->fetchColumn()) { + while ($share = $shares->fetch(\PDO::FETCH_COLUMN)) { $qb = $this->db->getQueryBuilder(); $folders = $qb->select('f.id') ->from('bookmarks_shared_folders', 'f') ->join('f', 'bookmarks_shared_to_shares', 't', $qb->expr()->eq('f.id', 't.shared_folder_id')) ->where($qb->expr()->eq('t.share_id', $qb->createPositionalParameter($share, IQueryBuilder::PARAM_INT))) - ->execute() + ->executeQuery() ->fetchAll(PDO::FETCH_COLUMN); foreach ($folders as $folderId) { $qb = $this->db->getQueryBuilder(); $qb->delete('bookmarks_tree') ->where($qb->expr()->eq('type', $qb->createPositionalParameter('share'))) ->andWhere($qb->expr()->eq('id', $qb->createPositionalParameter($folderId, IQueryBuilder::PARAM_INT))) - ->execute(); + ->executeStatement(); } $this->db->executeQuery('DELETE sf FROM *PREFIX*bookmarks_shared_folders sf JOIN *PREFIX*bookmarks_shared_to_shares t ON sf.id = t.shared_folder_id WHERE t.share_id = ?', [$share]); $qb = $this->db->getQueryBuilder(); $qb->delete('bookmarks_shares') ->where($qb->expr()->eq('id', $qb->createPositionalParameter($share))) - ->execute(); + ->executeStatement(); $i++; } $output->info("Removed $i orphaned shares"); @@ -75,14 +75,14 @@ public function run(IOutput $output) { ->from('bookmarks_folders_public', 'p') ->leftJoin('p', 'bookmarks_folders', 'f', $qb->expr()->eq('f.id', 'p.folder_id')) ->where($qb->expr()->isNull('f.id')) - ->execute() + ->executeQuery() ->fetchAll(PDO::FETCH_COLUMN); $i = 0; foreach ($publics as $publicId) { $qb = $this->db->getQueryBuilder(); $qb->delete('bookmarks_folders_public') ->where($qb->expr()->eq('id', $qb->createPositionalParameter($publicId))) - ->execute(); + ->executeStatement(); $i++; } $output->info("Removed $i orphaned public links"); diff --git a/lib/Migration/OrphanedTreeItemsRepairStep.php b/lib/Migration/OrphanedTreeItemsRepairStep.php index 5cf0d491d..ad230d7dd 100644 --- a/lib/Migration/OrphanedTreeItemsRepairStep.php +++ b/lib/Migration/OrphanedTreeItemsRepairStep.php @@ -44,14 +44,14 @@ public function run(IOutput $output) { ->leftJoin('t', 'bookmarks', 'b', $qb->expr()->eq('b.id', 't.id')) ->where($qb->expr()->isNull('b.id')) ->andWhere($qb->expr()->eq('t.type', $qb->createPositionalParameter('bookmark'))); - $orphanedBookmarks = $qb->execute(); + $orphanedBookmarks = $qb->executeQuery(); $i = 0; - while ($bookmark = $orphanedBookmarks->fetchColumn()) { + while ($bookmark = $orphanedBookmarks->fetch(\PDO::FETCH_COLUMN)) { $qb = $this->db->getQueryBuilder(); $qb->delete('bookmarks_tree') ->where($qb->expr()->eq('id', $qb->createPositionalParameter($bookmark, IQueryBuilder::PARAM_INT))) ->andWhere($qb->expr()->eq('type', $qb->createPositionalParameter('bookmark'))) - ->execute(); + ->executeStatement(); $i++; } $output->info("Removed $i orphaned bookmarks entries"); @@ -64,14 +64,14 @@ public function run(IOutput $output) { ->where($qb->expr()->isNull('f.id')) ->andWhere($qb->expr()->isNull('r.folder_id')) ->andWhere($qb->expr()->eq('t.type', $qb->createPositionalParameter('folder'))); - $orphanedFolders = $qb->execute(); + $orphanedFolders = $qb->executeQuery(); $i = 0; - while ($folder = $orphanedFolders->fetchColumn()) { + while ($folder = $orphanedFolders->fetch(\PDO::FETCH_COLUMN)) { $qb = $this->db->getQueryBuilder(); $qb->delete('bookmarks_tree') ->where($qb->expr()->eq('id', $qb->createPositionalParameter($folder, IQueryBuilder::PARAM_INT))) ->andWhere($qb->expr()->eq('type', $qb->createPositionalParameter('folder'))) - ->execute(); + ->executeStatement(); $i++; } $output->info("Removed $i orphaned folders entries"); @@ -81,7 +81,7 @@ public function run(IOutput $output) { ->from('bookmarks_tree', 't') ->leftJoin('t', 'bookmarks_folders', 'f', $qb->expr()->eq('t.parent_folder', 'f.id')) ->where($qb->expr()->isNull('f.id')); - $orphanedTreeItems = $qb->execute(); + $orphanedTreeItems = $qb->executeQuery(); $i = 0; while ($treeItem = $orphanedTreeItems->fetch()) { if ($treeItem['type'] === 'bookmark') { @@ -89,7 +89,7 @@ public function run(IOutput $output) { $bookmark = $qb->select('user_id') ->from('bookmarks') ->where($qb->expr()->eq('id', $qb->createNamedParameter($treeItem['id']))) - ->execute() + ->executeQuery() ->fetch(); $userId = $bookmark['user_id']; } elseif ($treeItem['type'] === 'folder') { @@ -97,7 +97,7 @@ public function run(IOutput $output) { $folder = $qb->select('user_id') ->from('bookmarks_folders') ->where($qb->expr()->eq('id', $qb->createNamedParameter($treeItem['id']))) - ->execute() + ->executeQuery() ->fetch(); $userId = $folder['user_id']; } elseif ($treeItem['type'] === 'share') { @@ -105,7 +105,7 @@ public function run(IOutput $output) { $folder = $qb->select('user_id') ->from('bookmarks_shared_folders') ->where($qb->expr()->eq('id', $qb->createNamedParameter($treeItem['id']))) - ->execute() + ->executeQuery() ->fetch(); $userId = $folder['user_id']; } @@ -117,7 +117,7 @@ public function run(IOutput $output) { ->set('index', $qb->createNamedParameter($rootFolder['count'])) ->where($qb->expr()->eq('id', $qb->createNamedParameter($treeItem['id'], IQueryBuilder::PARAM_INT))) ->andWhere($qb->expr()->eq('type', $qb->createNamedParameter($treeItem['type']))) - ->execute(); + ->executeStatement(); $i++; } $output->info("Reinserted $i orphaned children entries"); @@ -130,13 +130,13 @@ public function run(IOutput $output) { ->where($qb->expr()->isNull('t.id')) ->andWhere($qb->expr()->isNull('r.folder_id')) ->andWhere($qb->expr()->eq('t.type', $qb->createPositionalParameter('folder'))); - $orphanedFolders = $qb->execute(); + $orphanedFolders = $qb->executeQuery(); $i = 0; - while ($folder = $orphanedFolders->fetchColumn()) { + while ($folder = $orphanedFolders->fetch(\PDO::FETCH_COLUMN)) { $qb = $this->db->getQueryBuilder(); $qb->delete('bookmarks_folders') ->where($qb->expr()->eq('id', $qb->createPositionalParameter($folder, IQueryBuilder::PARAM_INT))) - ->execute(); + ->executeStatement(); $i++; } $output->info("Removed $i orphaned bookmark folders"); @@ -146,7 +146,7 @@ public function run(IOutput $output) { ->from('bookmarks', 'b') ->leftJoin('b', 'bookmarks_tree', 't', 'b.id = t.id AND t.type = ' . $qb->createPositionalParameter('bookmark')) ->where($qb->expr()->isNull('t.id')); - $orphanedBookmarks = $qb->execute(); + $orphanedBookmarks = $qb->executeQuery(); $i = 0; while ($bookmark = $orphanedBookmarks->fetch()) { $rootFolder = $this->ensureRootFolder($bookmark['user_id']); @@ -156,7 +156,7 @@ public function run(IOutput $output) { 'type' => $qb->createPositionalParameter('bookmark'), 'parent_folder' => $qb->createPositionalParameter($rootFolder['folder_id']), 'index' => $qb->createPositionalParameter($rootFolder['count']), - ])->execute(); + ])->executeStatement(); $i++; } $output->info("Reinserted $i orphaned bookmarks"); @@ -170,7 +170,7 @@ private function ensureRootFolder($userId) { ->leftJoin('r', 'bookmarks_tree', 't', 't.parent_folder = r.folder_id') ->where($qb->expr()->eq('r.user_id', $qb->createNamedParameter($userId))) ->groupBy(['r.folder_id']) - ->execute() + ->executeQuery() ->fetch(); if ($rootFolder === null || $rootFolder === false || $rootFolder['folder_id'] === null) { $qb = $this->db->getQueryBuilder(); @@ -179,7 +179,7 @@ private function ensureRootFolder($userId) { 'user_id' => $qb->createNamedParameter($userId), 'title' => $qb->createNamedParameter('') ]) - ->execute(); + ->executeStatement(); $rootFolder = [ 'folder_id' => $qb->getLastInsertId(), 'count' => 0 @@ -188,14 +188,14 @@ private function ensureRootFolder($userId) { $oldRootFolder = $qb->select('folder_id') ->from('bookmarks_root_folders') ->where($qb->expr()->eq('user_id', $qb->createNamedParameter($userId))) - ->execute() + ->executeQuery() ->fetch(); if ($oldRootFolder) { $qb = $this->db->getQueryBuilder(); $qb->update('bookmarks_root_folders') ->set('folder_id', $qb->createNamedParameter($rootFolder['folder_id'])) ->where($qb->expr()->eq('user_id', $qb->createNamedParameter($userId))) - ->execute(); + ->executeStatement(); } else { $qb = $this->db->getQueryBuilder(); $qb->insert('bookmarks_root_folders') @@ -203,7 +203,7 @@ private function ensureRootFolder($userId) { 'folder_id' => $qb->createNamedParameter($rootFolder['folder_id']), 'user_id' => $qb->createNamedParameter($userId) ]) - ->execute(); + ->executeStatement(); } } return $rootFolder; diff --git a/lib/Migration/SuperfluousSharedFoldersRepairStep.php b/lib/Migration/SuperfluousSharedFoldersRepairStep.php index e84ea1b8a..e466d2e6b 100644 --- a/lib/Migration/SuperfluousSharedFoldersRepairStep.php +++ b/lib/Migration/SuperfluousSharedFoldersRepairStep.php @@ -45,18 +45,18 @@ public function run(IOutput $output) { ->join('to', 'bookmarks_shares', 's', $qb->expr()->eq('to.share_id', 's.id')) ->where($qb->expr()->eq('t.type', $qb->createPositionalParameter('share'))) ->andWhere($qb->expr()->eq('s.owner', 'sf.user_id')); - $superfluousSharedFolders = $qb->execute(); + $superfluousSharedFolders = $qb->executeQuery(); $i = 0; - while ($sharedFolder = $superfluousSharedFolders->fetchColumn()) { + while ($sharedFolder = $superfluousSharedFolders->fetch(\PDO::FETCH_COLUMN)) { $qb = $this->db->getQueryBuilder(); $qb->delete('bookmarks_tree') ->where($qb->expr()->eq('id', $qb->createPositionalParameter($sharedFolder))) ->andWhere($qb->expr()->eq('type', $qb->createPositionalParameter('share'))) - ->execute(); + ->executeStatement(); $qb = $this->db->getQueryBuilder(); $qb->delete('bookmarks_shared_folders') ->where($qb->expr()->eq('id', $qb->createPositionalParameter($sharedFolder))) - ->execute(); + ->executeStatement(); $i++; } $output->info("Removed $i superfluous shares"); diff --git a/lib/Migration/Version000014000Date20181002094721.php b/lib/Migration/Version000014000Date20181002094721.php index d4adc90c8..1c0dcadb5 100644 --- a/lib/Migration/Version000014000Date20181002094721.php +++ b/lib/Migration/Version000014000Date20181002094721.php @@ -160,7 +160,7 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options) { $query = $this->db->getQueryBuilder(); $query->select('id', 'user_id')->from('bookmarks'); - $bookmarks = $query->execute()->fetchAll(); + $bookmarks = $query->executeQuery()->fetchAll(); foreach ($bookmarks as $i => $bookmark) { $query = $this->db->getQueryBuilder(); $query->select('bookmark_id') @@ -168,7 +168,7 @@ public function postSchemaChange(IOutput $output, Closure $schemaClosure, array ->where( $query->expr()->eq('bookmark_id', $query->createPositionalParameter($bookmark['id'])) ); - if (count($query->execute()->fetchAll()) > 0) { + if (count($query->executeQuery()->fetchAll()) > 0) { continue; } $query = $this->db->getQueryBuilder(); @@ -178,7 +178,7 @@ public function postSchemaChange(IOutput $output, Closure $schemaClosure, array 'folder_id' => $query->createNamedParameter(-1), 'bookmark_id' => $query->createNamedParameter($bookmark['id']), ]); - $query->execute(); + $query->executeStatement(); } } } diff --git a/lib/Migration/Version000014000Date20181029094721.php b/lib/Migration/Version000014000Date20181029094721.php index 7fdfbc92e..6e975c82e 100644 --- a/lib/Migration/Version000014000Date20181029094721.php +++ b/lib/Migration/Version000014000Date20181029094721.php @@ -71,7 +71,7 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options) { $query = $this->db->getQueryBuilder(); $query->select('id')->from('bookmarks_folders'); - $folders = $query->execute()->fetchAll(PDO::FETCH_COLUMN); + $folders = $query->executeQuery()->fetchAll(PDO::FETCH_COLUMN); $folders[] = -1; foreach ($folders as $folder) { $qb = $this->db->getQueryBuilder(); @@ -80,14 +80,14 @@ public function postSchemaChange(IOutput $output, Closure $schemaClosure, array ->from('bookmarks_folders') ->where($qb->expr()->eq('parent_folder', $qb->createPositionalParameter($folder, IQueryBuilder::PARAM_INT))) ->orderBy('title', 'DESC'); - $childFolders = $qb->execute()->fetchAll(); + $childFolders = $qb->executeQuery()->fetchAll(); $qb = $this->db->getQueryBuilder(); $qb ->select('bookmark_id') ->from('bookmarks_folders_bookmarks') ->where($qb->expr()->eq('folder_id', $qb->createPositionalParameter($folder, IQueryBuilder::PARAM_INT))); - $childBookmarks = $qb->execute()->fetchAll(); + $childBookmarks = $qb->executeQuery()->fetchAll(); $children = array_merge($childFolders, $childBookmarks); $children = array_map(static function ($child) { @@ -107,7 +107,7 @@ public function postSchemaChange(IOutput $output, Closure $schemaClosure, array ->set('index', $qb->createPositionalParameter($i, IQueryBuilder::PARAM_INT)) ->where($qb->expr()->eq('bookmark_id', $qb->createPositionalParameter($child['id']))) ->andWhere($qb->expr()->eq('folder_id', $qb->createPositionalParameter($folder))); - $qb->execute(); + $qb->executeStatement(); } else { $qb = $this->db->getQueryBuilder(); $qb @@ -115,7 +115,7 @@ public function postSchemaChange(IOutput $output, Closure $schemaClosure, array ->set('index', $qb->createPositionalParameter($i)) ->where($qb->expr()->eq('id', $qb->createPositionalParameter($child['id']))) ->andWhere($qb->expr()->eq('parent_folder', $qb->createPositionalParameter($folder))); - $qb->execute(); + $qb->executeStatement(); } } } diff --git a/lib/Migration/Version003000000Date20191123094721.php b/lib/Migration/Version003000000Date20191123094721.php index 8d8233825..42a901b2a 100644 --- a/lib/Migration/Version003000000Date20191123094721.php +++ b/lib/Migration/Version003000000Date20191123094721.php @@ -190,14 +190,14 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options) { $qb = $this->db->getQueryBuilder(); $qb->selectDistinct('user_id')->from('bookmarks'); - $usersQuery = $qb->execute(); - while ($user = $usersQuery->fetchColumn()) { + $usersQuery = $qb->executeQuery(); + while ($user = $usersQuery->fetch(\PDO::FETCH_COLUMN)) { $qb = $this->db->getQueryBuilder(); $rootFolderId = $qb->select('folder_id') ->from('bookmarks_root_folders') ->where($qb->expr()->eq('user_id', $qb->createPositionalParameter($user))) - ->execute() - ->fetchColumn(); + ->executeQuery() + ->fetchOne(); if ($rootFolderId === false) { // Create root folders $qb = $this->db->getQueryBuilder(); @@ -205,7 +205,7 @@ public function postSchemaChange(IOutput $output, Closure $schemaClosure, array 'title' => $qb->createPositionalParameter(''), 'user_id' => $qb->createPositionalParameter($user), ]); - $qb->execute(); + $qb->executeStatement(); $rootFolderId = $qb->getLastInsertId(); $qb = $this->db->getQueryBuilder(); @@ -213,14 +213,14 @@ public function postSchemaChange(IOutput $output, Closure $schemaClosure, array 'folder_id' => $qb->createPositionalParameter($rootFolderId, IQueryBuilder::PARAM_INT), 'user_id' => $qb->createPositionalParameter($user), ]); - $qb->execute(); + $qb->executeStatement(); } $qb = $this->db->getQueryBuilder(); $qb->select('id', 'parent_folder', 'index') ->from('bookmarks_folders') ->where($qb->expr()->eq('user_id', $qb->createPositionalParameter($user))); - $foldersQuery = $qb->execute(); + $foldersQuery = $qb->executeQuery(); while ($folder = $foldersQuery->fetch()) { if ((string)$folder['id'] === (string)$rootFolderId || $folder['parent_folder'] === null) { continue; @@ -232,8 +232,8 @@ public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $qb->expr()->eq('id', $qb->createPositionalParameter($folder['id'], IQueryBuilder::PARAM_INT)), $qb->expr()->eq('type', $qb->createPositionalParameter('folder')) ) - ->execute() - ->fetchColumn(); + ->executeQuery() + ->fetchOne(); if ($folderId === false) { $qb = $this->db->getQueryBuilder(); $qb->insert('bookmarks_tree') @@ -242,7 +242,7 @@ public function postSchemaChange(IOutput $output, Closure $schemaClosure, array 'type' => $qb->createPositionalParameter('folder'), 'parent_folder' => $qb->createPositionalParameter(($folder['parent_folder'] === '-1' || $folder['parent_folder'] === -1) ? $rootFolderId : $folder['parent_folder'], IQueryBuilder::PARAM_INT), 'index' => $qb->createPositionalParameter($folder['index'], IQueryBuilder::PARAM_INT), - ])->execute(); + ])->executeStatement(); } } $qb = $this->db->getQueryBuilder(); @@ -250,7 +250,7 @@ public function postSchemaChange(IOutput $output, Closure $schemaClosure, array ->from('bookmarks_folders_bookmarks', 'f') ->leftJoin('f', 'bookmarks', 'b', $qb->expr()->eq('b.id', 'f.bookmark_id')) ->where($qb->expr()->eq('b.user_id', $qb->createPositionalParameter($user))); - $bookmarksQuery = $qb->execute(); + $bookmarksQuery = $qb->executeQuery(); while ($bookmark = $bookmarksQuery->fetch()) { $qb = $this->db->getQueryBuilder(); $parentFolder = ($bookmark['folder_id'] === '-1' || $bookmark['folder_id'] === -1) ? $rootFolderId : $bookmark['folder_id']; @@ -261,8 +261,8 @@ public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $qb->expr()->eq('type', $qb->createPositionalParameter('bookmark')), $qb->expr()->eq('parent_folder', $qb->createPositionalParameter($parentFolder, IQueryBuilder::PARAM_INT)) ) - ->execute() - ->fetchColumn(); + ->executeQuery() + ->fetchOne(); if ($bookmarkId === false) { $qb = $this->db->getQueryBuilder(); $qb->insert('bookmarks_tree')->values([ @@ -270,7 +270,7 @@ public function postSchemaChange(IOutput $output, Closure $schemaClosure, array 'type' => $qb->createPositionalParameter('bookmark'), 'parent_folder' => $qb->createPositionalParameter($parentFolder, IQueryBuilder::PARAM_INT), 'index' => $qb->createPositionalParameter($bookmark['index']), - ])->execute(); + ])->executeStatement(); } } } diff --git a/lib/Migration/Version003001000Date20200526094721.php b/lib/Migration/Version003001000Date20200526094721.php index 4f5ea3936..bf518ec1f 100644 --- a/lib/Migration/Version003001000Date20200526094721.php +++ b/lib/Migration/Version003001000Date20200526094721.php @@ -85,7 +85,7 @@ public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $sharedFolders = $qb->select('sf.share_id', 'sf.id', 's.folder_id', 'sf.user_id') ->from('bookmarks_shared_folders', 'sf') ->leftJoin('sf', 'bookmarks_shares', 's', $qb->expr()->eq('sf.share_id', 's.id')) - ->execute(); + ->executeQuery(); while ($sharedFolder = $sharedFolders->fetch()) { // Find a shared folder with folder_id already set. This is gonna be the only one we will have for this folder from now on. $qb = $this->db->getQueryBuilder(); @@ -93,7 +93,7 @@ public function postSchemaChange(IOutput $output, Closure $schemaClosure, array ->from('bookmarks_shared_folders', 'sf') ->where($qb->expr()->eq('sf.folder_id', $qb->createPositionalParameter($sharedFolder['folder_id']))) ->andWhere($qb->expr()->eq('sf.user_id', $qb->createPositionalParameter($sharedFolder['user_id']))) - ->execute() + ->executeQuery() ->fetch(); if (!$canonicalSharedFolder) { // If there's no canonical shared folder, we make this one it. @@ -101,14 +101,14 @@ public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $qb->update('bookmarks_shared_folders') ->set('folder_id', $qb->createPositionalParameter($sharedFolder['folder_id'])) ->where($qb->expr()->eq('id', $qb->createPositionalParameter($sharedFolder['id']))) - ->execute(); + ->executeStatement(); $canonicalSharedFolder = $sharedFolder; } else { // ...otherwise delete this shared folder. $qb = $this->db->getQueryBuilder(); $qb->delete('bookmarks_shared_folders') ->where($qb->expr()->eq('id', $qb->createPositionalParameter($sharedFolder['id']))) - ->execute(); + ->executeStatement(); } // Insert into pivot table @@ -118,7 +118,7 @@ public function postSchemaChange(IOutput $output, Closure $schemaClosure, array 'shared_folder_id' => $qb->createPositionalParameter($canonicalSharedFolder['id']), 'share_id' => $qb->createPositionalParameter($sharedFolder['share_id']) ]) - ->execute(); + ->executeStatement(); } } } diff --git a/lib/Migration/Version004002000Date20210208124721.php b/lib/Migration/Version004002000Date20210208124721.php index b9b26d398..ce1d6c866 100644 --- a/lib/Migration/Version004002000Date20210208124721.php +++ b/lib/Migration/Version004002000Date20210208124721.php @@ -93,7 +93,7 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options) { // Reset last_preview of all bookmarks to trigger re-visiting them $qb = $this->db->getQueryBuilder(); - $qb->update('bookmarks')->set('last_preview', $qb->createPositionalParameter(0, IQueryBuilder::PARAM_INT))->execute(); + $qb->update('bookmarks')->set('last_preview', $qb->createPositionalParameter(0, IQueryBuilder::PARAM_INT))->executeStatement(); } protected function ensureColumnIsNullable(ISchemaWrapper $schema, string $tableName, string $columnName): bool { diff --git a/lib/Migration/Version010002000Date20220319124721.php b/lib/Migration/Version010002000Date20220319124721.php index d696c2271..737396245 100644 --- a/lib/Migration/Version010002000Date20220319124721.php +++ b/lib/Migration/Version010002000Date20220319124721.php @@ -40,7 +40,7 @@ public function __construct(IDBConnection $db) { */ public function preSchemaChange(IOutput $output, Closure $schemaClosure, array $options) { $qb = $this->db->getQueryBuilder(); - $this->rootFolders = $qb->select('user_id', 'locked')->from('bookmarks_root_folders')->execute()->fetchAll(); + $this->rootFolders = $qb->select('user_id', 'locked')->from('bookmarks_root_folders')->executeQuery()->fetchAll(); } /** @@ -80,7 +80,7 @@ public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $qb->update('bookmarks_root_folders') ->set('locked_time', $qb->createPositionalParameter(new DateTime(), Types::DATETIME)) ->where($qb->expr()->eq('user_id', $qb->createPositionalParameter($rootFolder['user_id']))) - ->execute(); + ->executeStatement(); } } } diff --git a/lib/Service/LockManager.php b/lib/Service/LockManager.php index 63e75ccc3..b0c6afcc3 100644 --- a/lib/Service/LockManager.php +++ b/lib/Service/LockManager.php @@ -11,6 +11,7 @@ use OCA\Bookmarks\Db\FolderMapper; use OCA\Bookmarks\Db\Types; use OCP\AppFramework\Utility\ITimeFactory; +use OCP\DB\Exception; use OCP\IDBConnection; class LockManager { @@ -43,12 +44,13 @@ public function setLock(string $userId, bool $locked): void { $qb->update('bookmarks_root_folders') ->set('locked_time', $qb->createNamedParameter($value, Types::DATETIME)) ->where($qb->expr()->eq('user_id', $qb->createNamedParameter($userId))) - ->execute(); + ->executeStatement(); } /** * @param string $userId * @return bool + * @throws Exception */ public function getLock(string $userId): bool { $this->folderMapper->findRootFolder($userId); @@ -56,7 +58,7 @@ public function getLock(string $userId): bool { $lockedAt = $qb->select('locked_time') ->from('bookmarks_root_folders') ->where($qb->expr()->eq('user_id', $qb->createNamedParameter($userId))) - ->execute() + ->executeQuery() ->fetch(\PDO::FETCH_COLUMN); if ($lockedAt === null) { return false; From 08e67ba5db6d56964fd044856af78e085fb23ee6 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sun, 1 Feb 2026 16:01:48 +0100 Subject: [PATCH 09/18] chore: Support nc 33 Signed-off-by: Marcel Klehr --- appinfo/info.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appinfo/info.xml b/appinfo/info.xml index 0e783daae..b7986913f 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -23,7 +23,7 @@ Requirements: - mbstring: * - when using MySQL, use at least v8.0 ]]> - 16.0.1 + 16.1.0-dev.0 agpl Marcel Klehr Arthur Schiwon @@ -42,7 +42,7 @@ Requirements: pgsql intl mbstring - + OCA\Bookmarks\BackgroundJobs\CrawlJob From 766bb71a1c7e0fc2c018976ffd1e7e7d10afb420 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Fri, 6 Feb 2026 16:34:09 +0100 Subject: [PATCH 10/18] fix: Don't call Job#execute() as it's deprecated Signed-off-by: Marcel Klehr --- tests/BackgroundJobTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/BackgroundJobTest.php b/tests/BackgroundJobTest.php index 056c90e61..2253e96aa 100644 --- a/tests/BackgroundJobTest.php +++ b/tests/BackgroundJobTest.php @@ -129,7 +129,7 @@ public function testPreviewsJob() : void { // generate cached previews $this->previewsJob->setId(1); $this->previewsJob->setLastRun(0); - $this->previewsJob->execute($this->jobList); + $this->previewsJob->start($this->jobList); $folder = $this->appData->getFolder('cache'); $newCacheSize = count($folder->getDirectoryListing()); @@ -151,7 +151,7 @@ public function testGCJob() : void { // generate cached previews $this->previewsJob->setId(1); $this->previewsJob->setLastRun(0); - $this->previewsJob->execute($this->jobList); + $this->previewsJob->start($this->jobList); $folder = $this->appData->getFolder('cache'); $cacheSize = count($folder->getDirectoryListing()); @@ -169,7 +169,7 @@ public function testGCJob() : void { // run GC job $this->gcJob->setId(3); $this->gcJob->setLastRun(0); - $this->gcJob->execute($this->jobList); + $this->gcJob->start($this->jobList); $newCacheSize = count($folder->getDirectoryListing()); // should have cleaned up the pending cache entries From 28eab23fbc4c98097e5fff82d7175a53dfc70769 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Fri, 6 Feb 2026 16:53:29 +0100 Subject: [PATCH 11/18] fix: Fix psalm issues Signed-off-by: Marcel Klehr --- lib/Db/BookmarkMapper.php | 5 ++- psalm-baseline.xml | 76 +-------------------------------------- 2 files changed, 5 insertions(+), 76 deletions(-) diff --git a/lib/Db/BookmarkMapper.php b/lib/Db/BookmarkMapper.php index 2d424cf98..3d5e68a9e 100644 --- a/lib/Db/BookmarkMapper.php +++ b/lib/Db/BookmarkMapper.php @@ -902,8 +902,11 @@ public function countBookmarksOfUser(string $userId) : int { ->where($qb->expr()->eq('user_id', $qb->createPositionalParameter($userId))); $result = $qb->executeQuery(); $count = $result->fetchOne(); + if ($count === false) { + $count = 0; + } $result->closeCursor(); - return $count; + return (int)$count; } /** diff --git a/psalm-baseline.xml b/psalm-baseline.xml index bc42557f2..a9e5750ea 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1,5 +1,5 @@ - + @@ -25,53 +25,7 @@ folderController->getFolders()->getData()]]> - - - createPositionalParameter($folderId, IQueryBuilder::PARAM_INT)]]> - createPositionalParameter($folderId, IQueryBuilder::PARAM_INT)]]> - createPositionalParameter(TreeMapper::TYPE_FOLDER)]]> - createPositionalParameter(TreeMapper::TYPE_FOLDER)]]> - createFunction($qb->getColumnName($col))]]> - createPositionalParameter(TreeMapper::TYPE_BOOKMARK)]]> - createPositionalParameter(TreeMapper::TYPE_BOOKMARK)]]> - createPositionalParameter(TreeMapper::TYPE_BOOKMARK)]]> - createPositionalParameter(TreeMapper::TYPE_BOOKMARK)]]> - createPositionalParameter(TreeMapper::TYPE_BOOKMARK)]]> - createPositionalParameter(TreeMapper::TYPE_BOOKMARK)]]> - createPositionalParameter(TreeMapper::TYPE_BOOKMARK)]]> - createPositionalParameter(TreeMapper::TYPE_BOOKMARK)]]> - createPositionalParameter(TreeMapper::TYPE_BOOKMARK)]]> - createPositionalParameter(TreeMapper::TYPE_FOLDER)]]> - createPositionalParameter(TreeMapper::TYPE_FOLDER)]]> - createPositionalParameter(TreeMapper::TYPE_SHARE)]]> - createPositionalParameter(TreeMapper::TYPE_FOLDER)]]> - - - - - - - - - - - - - - getId()]]> - getId()]]> - - - - - createPositionalParameter(TreeMapper::TYPE_BOOKMARK)]]> - createPositionalParameter(TreeMapper::TYPE_BOOKMARK)]]> - - - - createPositionalParameter(TreeMapper::TYPE_BOOKMARK)]]> - bookmarkMapper->findAll($userId, $params)]]> @@ -133,19 +87,6 @@ - - - createPositionalParameter('bookmark')]]> - - - - - server]]> - - - - - @@ -161,11 +102,6 @@ - - - - - @@ -182,11 +118,6 @@ - - - - - getId()]]> @@ -206,9 +137,4 @@ - - - - - From 663b64e68e300f0a067fc37049d6c225b90751e5 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Fri, 6 Feb 2026 16:59:58 +0100 Subject: [PATCH 12/18] chore: Don't test nc 33 on php8.1 Signed-off-by: Marcel Klehr --- .github/workflows/upgrade.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/upgrade.yml b/.github/workflows/upgrade.yml index 812a9b896..734240b70 100644 --- a/.github/workflows/upgrade.yml +++ b/.github/workflows/upgrade.yml @@ -24,10 +24,15 @@ jobs: # do not stop on another job's failure fail-fast: false matrix: - php-versions: ['8.1', '8.2', '8.3', '8.4'] + php-versions: ['8.2', '8.3', '8.4'] databases: ['sqlite', 'mysql', 'pgsql'] - server-versions: ['stable32', 'master'] + server-versions: ['stable32', 'stable33', 'master'] prev-version: ['stable'] + include: + - databases: 'sqlite' + server-versions: 'stable32' + php-versions: '8.1' + prev-version: 'stable' name: Update from ${{ matrix.prev-version }} on ${{ matrix.databases }}-${{ matrix.server-versions }} From 838435528ab3f78bb1ba87b7a874a3177e2c8c82 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sat, 7 Feb 2026 09:40:15 +0100 Subject: [PATCH 13/18] fix(Migration): Wrap md5 updates in a transcation Signed-off-by: Marcel Klehr --- lib/Migration/Version016002000Date20260201124723.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/Migration/Version016002000Date20260201124723.php b/lib/Migration/Version016002000Date20260201124723.php index 04c1d0ea2..16987aa71 100644 --- a/lib/Migration/Version016002000Date20260201124723.php +++ b/lib/Migration/Version016002000Date20260201124723.php @@ -62,7 +62,14 @@ public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $setQb->update('bookmarks') ->set('url_hash', $qb->createParameter('url_hash')) ->where($qb->expr()->eq('id', $qb->createParameter('id'))); + $i = 0; while ($row = $result->fetch()) { + if ($i++ % 1000 === 0) { + if ($i > 0) { + $this->db->commit(); + } + $this->db->beginTransaction(); + } $setQb->setParameter('url_hash', md5($row['url'])); $setQb->setParameter('id', $row['id']); $setQb->executeStatement(); From 6a048fe6ffc966c82319b6e2aedbd5e9f8a785e7 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sat, 7 Feb 2026 10:11:04 +0100 Subject: [PATCH 14/18] fix(UI): Update nc/vue and make it work with nc 33 Signed-off-by: Marcel Klehr --- package-lock.json | 1122 ++++++++++++++++-------------- src/components/BookmarksList.vue | 12 +- src/components/ViewPrivate.vue | 3 + 3 files changed, 607 insertions(+), 530 deletions(-) diff --git a/package-lock.json b/package-lock.json index 730f671ef..384707372 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,20 +1,12 @@ { "name": "bookmarks", -<<<<<<< HEAD - "version": "15.2.0", -======= - "version": "16.0.0", ->>>>>>> 1493a1ee (enh: Support nextcloud 32) + "version": "16.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "bookmarks", -<<<<<<< HEAD - "version": "15.2.0", -======= - "version": "16.0.0", ->>>>>>> 1493a1ee (enh: Support nextcloud 32) + "version": "16.0.1", "license": "AGPL-3.0-or-later", "dependencies": { "@nextcloud/auth": "^2.1.0", @@ -70,16 +62,16 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", + "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" @@ -386,18 +378,18 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -431,27 +423,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.26.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.7.tgz", - "integrity": "sha512-8NHiL98vsi0mbPQmYAGWwfcFaOy4j2HY49fXJCfuDcdE7fMIsH9a7GdaeXpIBsbT7307WU8KCMp5pUVDNL4f9A==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@babel/template": "^7.25.9", - "@babel/types": "^7.26.7" + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.26.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.7.tgz", - "integrity": "sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", "license": "MIT", "dependencies": { - "@babel/types": "^7.26.7" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -1627,16 +1619,16 @@ } }, "node_modules/@babel/template": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", - "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@babel/code-frame": "^7.25.9", - "@babel/parser": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1663,13 +1655,13 @@ } }, "node_modules/@babel/types": { - "version": "7.26.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.7.tgz", - "integrity": "sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -1947,19 +1939,6 @@ "version": "0.4.4", "resolved": "https://registry.npmjs.org/@file-type/xml/-/xml-0.4.4.tgz", "integrity": "sha512-NhCyXoHlVZ8TqM476hyzwGJ24+D5IPSaZhmrPj7qXnEVb3q6jrFzA3mM9TBpknKSI9EuQeGTKRg2DXGUwvBBoQ==", -<<<<<<< HEAD - "license": "MIT", - "dependencies": { - "sax": "^1.4.1", - "strtok3": "^10.3.4" - } - }, - "node_modules/@floating-ui/core": { - "version": "1.6.9", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz", - "integrity": "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==", -======= ->>>>>>> 1493a1ee (enh: Support nextcloud 32) "license": "MIT", "dependencies": { "sax": "^1.4.1", @@ -1967,21 +1946,21 @@ } }, "node_modules/@floating-ui/core": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", - "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.4.tgz", + "integrity": "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==", "license": "MIT", "dependencies": { "@floating-ui/utils": "^0.2.10" } }, "node_modules/@floating-ui/dom": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", - "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.5.tgz", + "integrity": "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==", "license": "MIT", "dependencies": { - "@floating-ui/core": "^1.7.3", + "@floating-ui/core": "^1.7.4", "@floating-ui/utils": "^0.2.10" } }, @@ -2141,9 +2120,9 @@ } }, "node_modules/@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", "license": "MIT", "peer": true, "dependencies": { @@ -2298,12 +2277,12 @@ "license": "Apache-2.0" }, "node_modules/@nextcloud/auth": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/@nextcloud/auth/-/auth-2.5.2.tgz", - "integrity": "sha512-1dr+Xhvg2QtsFC23KFXJSkU524EVgI/+WFBGTZ8tFa+9hmcZ3CqZIHjtXm3KxUvezwAY5023Ml0n2vpdYkpBXQ==", + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/@nextcloud/auth/-/auth-2.5.3.tgz", + "integrity": "sha512-KIhWLk0BKcP4hvypE4o11YqKOPeFMfEFjRrhUUF+h7Fry+dhTBIEIxuQPVCKXMIpjTDd8791y8V6UdRZ2feKAQ==", "license": "GPL-3.0-or-later", "dependencies": { - "@nextcloud/browser-storage": "^0.4.0", + "@nextcloud/browser-storage": "^0.5.0", "@nextcloud/event-bus": "^3.3.2" }, "engines": { @@ -2311,18 +2290,17 @@ } }, "node_modules/@nextcloud/axios": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@nextcloud/axios/-/axios-2.5.1.tgz", - "integrity": "sha512-AA7BPF/rsOZWAiVxqlobGSdD67AEwjOnymZCKUIwEIBArKxYK7OQEqcILDjQwgj6G0e/Vg9Y8zTVsPZp+mlvwA==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/@nextcloud/axios/-/axios-2.5.2.tgz", + "integrity": "sha512-8frJb77jNMbz00TjsSqs1PymY0nIEbNM4mVmwen2tXY7wNgRai6uXilIlXKOYB9jR/F/HKRj6B4vUwVwZbhdbw==", "license": "GPL-3.0-or-later", "dependencies": { - "@nextcloud/auth": "^2.3.0", + "@nextcloud/auth": "^2.5.1", "@nextcloud/router": "^3.0.1", - "axios": "^1.6.8" + "axios": "^1.12.2" }, "engines": { - "node": "^20.0.0", - "npm": "^10.0.0" + "node": "^20.0.0 || ^22.0.0 || ^24.0.0" } }, "node_modules/@nextcloud/axios/node_modules/@nextcloud/router": { @@ -2355,16 +2333,12 @@ } }, "node_modules/@nextcloud/browser-storage": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@nextcloud/browser-storage/-/browser-storage-0.4.0.tgz", - "integrity": "sha512-D6XxznxCYmJ3oBCC3p0JB6GZJ2RZ9dgbB1UqtTePXrIvHUMBAeF/YkiGKYxLAVZCZb+NSNZXgAYHm/3LnIUbDg==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@nextcloud/browser-storage/-/browser-storage-0.5.0.tgz", + "integrity": "sha512-usYr4GlJQlK3hgZURvklqWb9ivi7sgsSuFqXrs7s4hl1LTS4enzPrnkQumm6nRsQruf0ITS+OBsK+oELEbvYPA==", "license": "GPL-3.0-or-later", - "dependencies": { - "core-js": "3.37.0" - }, "engines": { - "node": "^20.0.0", - "npm": "^10.0.0" + "node": "^24 || ^22 || ^20" } }, "node_modules/@nextcloud/browserslist-config": { @@ -2379,56 +2353,72 @@ } }, "node_modules/@nextcloud/capabilities": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@nextcloud/capabilities/-/capabilities-1.2.0.tgz", - "integrity": "sha512-L1NQtOfHWzkfj0Ple1MEJt6HmOHWAi3y4qs+OnwSWexqJT0DtXTVPyRxi7ADyITwRxS5H9R/HMl6USAj4Nr1nQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@nextcloud/capabilities/-/capabilities-1.2.1.tgz", + "integrity": "sha512-snZ0/910zzwN6PDsIlx2Uvktr1S5x0ClhDUnfPlCj7ntNvECzuVHNY5wzby22LIkc+9ZjaDKtCwuCt2ye+9p/Q==", "license": "GPL-3.0-or-later", "dependencies": { - "@nextcloud/initial-state": "^2.1.0" + "@nextcloud/initial-state": "^3.0.0" }, "engines": { - "node": "^20.0.0", - "npm": "^10.0.0" + "node": "^20.0.0 || ^22.0.0 || ^24.0.0" + } + }, + "node_modules/@nextcloud/capabilities/node_modules/@nextcloud/initial-state": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@nextcloud/initial-state/-/initial-state-3.0.0.tgz", + "integrity": "sha512-cV+HBdkQJGm8FxkBI5rFT/FbMNWNBvpbj6OPrg4Ae4YOOsQ15CL8InPOAw1t4XkOkQK2NEdUGQLVUz/19wXbdQ==", + "license": "GPL-3.0-or-later", + "engines": { + "node": "^20.0.0 || ^22.0.0 || ^24.0.0" } }, "node_modules/@nextcloud/dialogs": { - "version": "6.3.2", - "resolved": "https://registry.npmjs.org/@nextcloud/dialogs/-/dialogs-6.3.2.tgz", - "integrity": "sha512-ioZ483wmKdNX1HdSJ1EG7ewTSyQAqlmbBALkhT4guZdR9JG8VIdnijX15qwKgAWITG2y36PWoi9Rimb3dDf+7A==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/@nextcloud/dialogs/-/dialogs-6.4.2.tgz", + "integrity": "sha512-xj6fyUdb56StWt1DWeDHYnqK0Ck7EWQLUEyWtXHnneknoOV3x/Y0HPRL5L2PTJaDuVQB8TcVW53JjR38JG9W6Q==", "license": "AGPL-3.0-or-later", "dependencies": { "@mdi/js": "^7.4.47", - "@nextcloud/auth": "^2.5.1", - "@nextcloud/axios": "^2.5.1", - "@nextcloud/browser-storage": "^0.4.0", - "@nextcloud/event-bus": "^3.3.2", - "@nextcloud/files": "^3.10.2", - "@nextcloud/initial-state": "^2.2.0", - "@nextcloud/l10n": "^3.3.0", - "@nextcloud/router": "^3.0.1", - "@nextcloud/sharing": "^0.2.4", - "@nextcloud/typings": "^1.9.1", + "@nextcloud/auth": "^2.5.3", + "@nextcloud/axios": "^2.5.2", + "@nextcloud/browser-storage": "^0.5.0", + "@nextcloud/event-bus": "^3.3.3", + "@nextcloud/files": "^3.12.2", + "@nextcloud/initial-state": "^3.0.0", + "@nextcloud/l10n": "^3.4.1", + "@nextcloud/paths": "^3.0.0", + "@nextcloud/router": "^3.1.0", + "@nextcloud/sharing": "^0.3.0", "@types/toastify-js": "^1.12.4", "@vueuse/core": "^11.3.0", "cancelable-promise": "^4.3.1", - "p-queue": "^8.1.0", + "p-queue": "^9.0.1", "toastify-js": "^1.12.0", "vue-frag": "^1.4.3", "webdav": "^5.8.0" }, "engines": { - "node": "^20.0.0 || ^22.0.0 || ^24.0.0", - "npm": "^10.5.1" + "node": "^20.0.0 || ^22.0.0 || ^24.0.0" }, "peerDependencies": { "@nextcloud/vue": "^8.24.0", "vue": "^2.7.16" } }, + "node_modules/@nextcloud/dialogs/node_modules/@nextcloud/initial-state": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@nextcloud/initial-state/-/initial-state-3.0.0.tgz", + "integrity": "sha512-cV+HBdkQJGm8FxkBI5rFT/FbMNWNBvpbj6OPrg4Ae4YOOsQ15CL8InPOAw1t4XkOkQK2NEdUGQLVUz/19wXbdQ==", + "license": "GPL-3.0-or-later", + "engines": { + "node": "^20.0.0 || ^22.0.0 || ^24.0.0" + } + }, "node_modules/@nextcloud/dialogs/node_modules/@nextcloud/l10n": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@nextcloud/l10n/-/l10n-3.4.0.tgz", - "integrity": "sha512-K4UBSl0Ou6sXXLxyjuhktRf2FyTCjyvHxJyBLmS2z3YEYcRkpf8ib3XneRwEQIEpzBPQjul2/ZdFlt7umd8Gaw==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@nextcloud/l10n/-/l10n-3.4.1.tgz", + "integrity": "sha512-aTFinTcKiK2gEXwLgutXekpZZ8/v/4QiC8C3QCLH5m0o+WtxsBC+fqV142ebC/rfDnzCLhY4ZtswSu8bFbZocg==", "license": "GPL-3.0-or-later", "dependencies": { "@nextcloud/router": "^3.0.1", @@ -2442,16 +2432,43 @@ } }, "node_modules/@nextcloud/dialogs/node_modules/@nextcloud/router": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@nextcloud/router/-/router-3.0.1.tgz", - "integrity": "sha512-Ci/uD3x8OKHdxSqXL6gRJ+mGJOEXjeiHjj7hqsZqVTsT7kOrCjDf0/J8z5RyLlokKZ0IpSe+hGxgi3YB7Gpw3Q==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@nextcloud/router/-/router-3.1.0.tgz", + "integrity": "sha512-e4dkIaxRSwdZJlZFpn9x03QgBn/Sa2hN1hp/BA7+AbzykmSAlKuWfdmX8j/8ewrLpQwYmZR23IZO9XwpJXq2Uw==", "license": "GPL-3.0-or-later", "dependencies": { - "@nextcloud/typings": "^1.7.0" + "@nextcloud/typings": "^1.10.0" }, "engines": { - "node": "^20.0.0", - "npm": "^10.0.0" + "node": "^20.0.0 || ^22.0.0 || ^24.0.0" + } + }, + "node_modules/@nextcloud/dialogs/node_modules/p-queue": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.1.0.tgz", + "integrity": "sha512-O/ZPaXuQV29uSLbxWBGGZO1mCQXV2BLIwUr59JUU9SoH76mnYvtms7aafH/isNSNGwuEfP6W/4xD0/TJXxrizw==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^5.0.1", + "p-timeout": "^7.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@nextcloud/dialogs/node_modules/p-timeout": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-7.0.1.tgz", + "integrity": "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/@nextcloud/eslint-config": { @@ -2516,23 +2533,22 @@ } }, "node_modules/@nextcloud/event-bus": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/@nextcloud/event-bus/-/event-bus-3.3.2.tgz", - "integrity": "sha512-1Qfs6i7Tz2qd1A33NpBQOt810ydHIRjhyXMFwSEkYX2yUI80lAk/sWO8HIB2Fqp+iffhyviPPcQYoytMDRyDNw==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@nextcloud/event-bus/-/event-bus-3.3.3.tgz", + "integrity": "sha512-zIfvKmUGkXpVzRKoXrcO9hkoiKDm65fqNxy/XIbIxrQhZByPq3gDkjBpnu3V5Gs8JdYwa73R8DjzV9oH8HYhIg==", "license": "GPL-3.0-or-later", "dependencies": { - "@types/semver": "^7.5.8", - "semver": "^7.6.3" + "@types/semver": "^7.7.0", + "semver": "^7.7.2" }, "engines": { - "node": "^20.0.0", - "npm": "^10.0.0" + "node": "^20 || ^22 || ^24" } }, "node_modules/@nextcloud/event-bus/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -2542,20 +2558,20 @@ } }, "node_modules/@nextcloud/files": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/@nextcloud/files/-/files-3.12.0.tgz", - "integrity": "sha512-LVZklgooZzBj2jkbPRZO4jnnvW5+RvOn7wN5weyOZltF6i2wVMbg1Y/Czl2pi/UNMjUm5ENqc0j7FgxMBo8bwA==", + "version": "3.12.2", + "resolved": "https://registry.npmjs.org/@nextcloud/files/-/files-3.12.2.tgz", + "integrity": "sha512-vBo8tf3Xh6efiF8CrEo3pKj9AtvAF6RdDGO1XKL65IxV8+UUd9Uxl2lUExHlzoDRRczCqfGfaWfRRaFhYqce5Q==", "license": "AGPL-3.0-or-later", "dependencies": { - "@nextcloud/auth": "^2.5.1", - "@nextcloud/capabilities": "^1.2.0", - "@nextcloud/l10n": "^3.3.0", - "@nextcloud/logger": "^3.0.2", - "@nextcloud/paths": "^2.2.1", - "@nextcloud/router": "^3.0.1", - "@nextcloud/sharing": "^0.2.4", + "@nextcloud/auth": "^2.5.3", + "@nextcloud/capabilities": "^1.2.1", + "@nextcloud/l10n": "^3.4.1", + "@nextcloud/logger": "^3.0.3", + "@nextcloud/paths": "^3.0.0", + "@nextcloud/router": "^3.1.0", + "@nextcloud/sharing": "^0.3.0", "cancelable-promise": "^4.3.1", - "is-svg": "^6.0.0", + "is-svg": "^6.1.0", "typescript-event-target": "^1.1.1", "webdav": "^5.8.0" }, @@ -2564,9 +2580,9 @@ } }, "node_modules/@nextcloud/files/node_modules/@nextcloud/l10n": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@nextcloud/l10n/-/l10n-3.4.0.tgz", - "integrity": "sha512-K4UBSl0Ou6sXXLxyjuhktRf2FyTCjyvHxJyBLmS2z3YEYcRkpf8ib3XneRwEQIEpzBPQjul2/ZdFlt7umd8Gaw==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@nextcloud/l10n/-/l10n-3.4.1.tgz", + "integrity": "sha512-aTFinTcKiK2gEXwLgutXekpZZ8/v/4QiC8C3QCLH5m0o+WtxsBC+fqV142ebC/rfDnzCLhY4ZtswSu8bFbZocg==", "license": "GPL-3.0-or-later", "dependencies": { "@nextcloud/router": "^3.0.1", @@ -2580,16 +2596,15 @@ } }, "node_modules/@nextcloud/files/node_modules/@nextcloud/router": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@nextcloud/router/-/router-3.0.1.tgz", - "integrity": "sha512-Ci/uD3x8OKHdxSqXL6gRJ+mGJOEXjeiHjj7hqsZqVTsT7kOrCjDf0/J8z5RyLlokKZ0IpSe+hGxgi3YB7Gpw3Q==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@nextcloud/router/-/router-3.1.0.tgz", + "integrity": "sha512-e4dkIaxRSwdZJlZFpn9x03QgBn/Sa2hN1hp/BA7+AbzykmSAlKuWfdmX8j/8ewrLpQwYmZR23IZO9XwpJXq2Uw==", "license": "GPL-3.0-or-later", "dependencies": { - "@nextcloud/typings": "^1.7.0" + "@nextcloud/typings": "^1.10.0" }, "engines": { - "node": "^20.0.0", - "npm": "^10.0.0" + "node": "^20.0.0 || ^22.0.0 || ^24.0.0" } }, "node_modules/@nextcloud/files/node_modules/is-svg": { @@ -2635,26 +2650,24 @@ } }, "node_modules/@nextcloud/logger": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@nextcloud/logger/-/logger-3.0.2.tgz", - "integrity": "sha512-wByt0R0/6QC44RBpaJr1MWghjjOxk/pRbACHo/ZWWKht1qYbJRHB4GtEi+35KEIHY07ZpqxiDk6dIRuN7sXYWQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@nextcloud/logger/-/logger-3.0.3.tgz", + "integrity": "sha512-TcbVRL4/O5ffI1RXFmQAFD3gwwT15AAdr1770x+RNqVvfBdoGVyhzOwCIyA5Vfc3fA1iJXFa+rE6buJZSoqlcw==", "license": "GPL-3.0-or-later", "dependencies": { - "@nextcloud/auth": "^2.3.0" + "@nextcloud/auth": "^2.5.3" }, "engines": { - "node": "^20.0.0", - "npm": "^10.0.0" + "node": "^20.0.0 || ^22.0.0 || ^24.0.0" } }, "node_modules/@nextcloud/paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@nextcloud/paths/-/paths-2.2.1.tgz", - "integrity": "sha512-M3ShLjrxR7B48eKThLMoqbxTqTKyQXcwf9TgeXQGbCIhiHoXU6as5j8l5qNv/uZlANokVdowpuWHBi3b2+YNNA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@nextcloud/paths/-/paths-3.0.0.tgz", + "integrity": "sha512-+sTfTkIbVUa2Ue3bkz3R7F1mhddvHPOWUxkSNg7Q5dAsimVFBaTRgiBAJmsAag3JPsxyuS8kUgeb0zdEssRdTA==", "license": "GPL-3.0-or-later", "engines": { - "node": "^20.0.0", - "npm": "^10.0.0" + "node": "^20.0.0 || ^22.0.0 || ^24.0.0" } }, "node_modules/@nextcloud/router": { @@ -2672,16 +2685,43 @@ } }, "node_modules/@nextcloud/sharing": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@nextcloud/sharing/-/sharing-0.2.4.tgz", - "integrity": "sha512-kOLAr0w4NDUGPF42L22i9iSs6Z3ylTsE0RudAGDBzw/pnxGY8PEwZI2j0IMAFRfQ7XFNcpV/EVHI5YCMxtxGMQ==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@nextcloud/sharing/-/sharing-0.3.0.tgz", + "integrity": "sha512-kV7qeUZvd1fTKeFyH+W5Qq5rNOqG9rLATZM3U9MBxWXHJs3OxMqYQb8UQ3NYONzsX3zDGJmdQECIGHm1ei2sCA==", "license": "GPL-3.0-or-later", "dependencies": { - "@nextcloud/initial-state": "^2.2.0" + "@nextcloud/initial-state": "^3.0.0", + "is-svg": "^6.1.0" }, "engines": { - "node": "^20.0.0", - "npm": "^10.0.0" + "node": "^20.0.0 || ^22.0.0 || ^24.0.0" + }, + "optionalDependencies": { + "@nextcloud/files": "^3.12.0" + } + }, + "node_modules/@nextcloud/sharing/node_modules/@nextcloud/initial-state": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@nextcloud/initial-state/-/initial-state-3.0.0.tgz", + "integrity": "sha512-cV+HBdkQJGm8FxkBI5rFT/FbMNWNBvpbj6OPrg4Ae4YOOsQ15CL8InPOAw1t4XkOkQK2NEdUGQLVUz/19wXbdQ==", + "license": "GPL-3.0-or-later", + "engines": { + "node": "^20.0.0 || ^22.0.0 || ^24.0.0" + } + }, + "node_modules/@nextcloud/sharing/node_modules/is-svg": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-6.1.0.tgz", + "integrity": "sha512-i7YPdvYuSCYcaLQrKwt8cvKTlwHcdA6Hp8N9SO3Q5jIzo8x6kH3N47W0BvPP7NdxVBmIHx7X9DK36czYYW7lHg==", + "license": "MIT", + "dependencies": { + "@file-type/xml": "^0.4.3" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/@nextcloud/stylelint-config": { @@ -2700,61 +2740,47 @@ "stylelint-config-recommended-vue": "^1.1.0" } }, - "node_modules/@nextcloud/timezones": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@nextcloud/timezones/-/timezones-0.2.0.tgz", - "integrity": "sha512-1mwQ+asTFOgv9rxPoAMEbDF8JfnenIa2EGNS+8MATCyi6WXxYh0Lhkaq1d3l2+xNbUPHgMnk4cRYsvIo319lkA==", - "license": "AGPL-3.0-or-later", - "dependencies": { - "ical.js": "^2.1.0" - }, - "engines": { - "node": "^20 || ^22" - } - }, "node_modules/@nextcloud/typings": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@nextcloud/typings/-/typings-1.9.1.tgz", - "integrity": "sha512-i0l/L5gKW8EACbXHVxXM6wn3sUhY2qmnL2OijppzU4dENC7/hqySMQDer7/+cJbNSNG7uHF/Z+9JmHtDfRfuGg==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@nextcloud/typings/-/typings-1.10.0.tgz", + "integrity": "sha512-SMC42rDjOH3SspPTLMZRv76ZliHpj2JJkF8pGLP8l1QrVTZxE47Qz5qeKmbj2VL+dRv2e/NgixlAFmzVnxkhqg==", "license": "GPL-3.0-or-later", "dependencies": { "@types/jquery": "3.5.16" }, "engines": { - "node": "^20.0.0", - "npm": "^10.0.0" + "node": "^20.0.0 || ^22.0.0 || ^24.0.0" } }, "node_modules/@nextcloud/vue": { - "version": "8.31.0", - "resolved": "https://registry.npmjs.org/@nextcloud/vue/-/vue-8.31.0.tgz", - "integrity": "sha512-P5m3Odfw4m0siu7qs88WJutu2C8mEknqMS1ijlqYtQvc8qZwmpQshgCV5GhyyBTTK9Baicthm+ULglIf/Eq/sg==", + "version": "8.36.0", + "resolved": "https://registry.npmjs.org/@nextcloud/vue/-/vue-8.36.0.tgz", + "integrity": "sha512-x1MEh4nvCrD1zoJ3ybhtbSDox+1wyHRP7st95Uu14Wm630quRrfXGaQ1bxqaZ7en/wqaihR0NPyQKfmjrPmseg==", "license": "AGPL-3.0-or-later", "dependencies": { - "@floating-ui/dom": "^1.7.4", + "@floating-ui/dom": "^1.7.5", "@linusborg/vue-simple-portal": "^0.1.5", - "@nextcloud/auth": "^2.5.2", - "@nextcloud/axios": "^2.5.0", - "@nextcloud/browser-storage": "^0.4.0", - "@nextcloud/capabilities": "^1.2.0", - "@nextcloud/event-bus": "^3.3.2", + "@nextcloud/auth": "^2.5.3", + "@nextcloud/axios": "^2.5.2", + "@nextcloud/browser-storage": "^0.5.0", + "@nextcloud/capabilities": "^1.2.1", + "@nextcloud/event-bus": "^3.3.3", "@nextcloud/initial-state": "^2.2.0", - "@nextcloud/l10n": "^3.4.0", - "@nextcloud/logger": "^3.0.2", - "@nextcloud/router": "^3.0.1", + "@nextcloud/l10n": "^3.4.1", + "@nextcloud/logger": "^3.0.3", + "@nextcloud/router": "^3.1.0", "@nextcloud/sharing": "^0.3.0", - "@nextcloud/timezones": "^0.2.0", "@nextcloud/vue-select": "^3.26.0", "@vueuse/components": "^11.0.0", "@vueuse/core": "^11.0.0", "blurhash": "^2.0.5", "clone": "^2.1.2", "debounce": "^2.2.0", - "dompurify": "^3.2.4", + "dompurify": "^3.3.1", "emoji-mart-vue-fast": "^15.0.5", "escape-html": "^1.0.3", "floating-vue": "^1.0.0-beta.19", - "focus-trap": "^7.4.3", + "focus-trap": "^7.8.0", "linkify-string": "^4.3.2", "md5": "^2.3.0", "p-queue": "^8.1.1", @@ -2768,11 +2794,11 @@ "splitpanes": "^2.4.1", "string-length": "^5.0.1", "striptags": "^3.2.0", - "tabbable": "^6.2.0", + "tabbable": "^6.4.0", "tributejs": "^5.1.3", "unified": "^11.0.1", "unist-builder": "^4.0.0", - "unist-util-visit": "^5.0.0", + "unist-util-visit": "^5.1.0", "vue": "^2.7.16", "vue-color": "^2.8.1", "vue-frag": "^1.4.3", @@ -2796,9 +2822,9 @@ } }, "node_modules/@nextcloud/vue/node_modules/@nextcloud/l10n": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@nextcloud/l10n/-/l10n-3.4.0.tgz", - "integrity": "sha512-K4UBSl0Ou6sXXLxyjuhktRf2FyTCjyvHxJyBLmS2z3YEYcRkpf8ib3XneRwEQIEpzBPQjul2/ZdFlt7umd8Gaw==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@nextcloud/l10n/-/l10n-3.4.1.tgz", + "integrity": "sha512-aTFinTcKiK2gEXwLgutXekpZZ8/v/4QiC8C3QCLH5m0o+WtxsBC+fqV142ebC/rfDnzCLhY4ZtswSu8bFbZocg==", "license": "GPL-3.0-or-later", "dependencies": { "@nextcloud/router": "^3.0.1", @@ -2812,56 +2838,15 @@ } }, "node_modules/@nextcloud/vue/node_modules/@nextcloud/router": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@nextcloud/router/-/router-3.0.1.tgz", - "integrity": "sha512-Ci/uD3x8OKHdxSqXL6gRJ+mGJOEXjeiHjj7hqsZqVTsT7kOrCjDf0/J8z5RyLlokKZ0IpSe+hGxgi3YB7Gpw3Q==", - "license": "GPL-3.0-or-later", - "dependencies": { - "@nextcloud/typings": "^1.7.0" - }, - "engines": { - "node": "^20.0.0", - "npm": "^10.0.0" - } - }, - "node_modules/@nextcloud/vue/node_modules/@nextcloud/sharing": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@nextcloud/sharing/-/sharing-0.3.0.tgz", - "integrity": "sha512-kV7qeUZvd1fTKeFyH+W5Qq5rNOqG9rLATZM3U9MBxWXHJs3OxMqYQb8UQ3NYONzsX3zDGJmdQECIGHm1ei2sCA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@nextcloud/router/-/router-3.1.0.tgz", + "integrity": "sha512-e4dkIaxRSwdZJlZFpn9x03QgBn/Sa2hN1hp/BA7+AbzykmSAlKuWfdmX8j/8ewrLpQwYmZR23IZO9XwpJXq2Uw==", "license": "GPL-3.0-or-later", "dependencies": { - "@nextcloud/initial-state": "^3.0.0", - "is-svg": "^6.1.0" + "@nextcloud/typings": "^1.10.0" }, "engines": { "node": "^20.0.0 || ^22.0.0 || ^24.0.0" - }, - "optionalDependencies": { - "@nextcloud/files": "^3.12.0" - } - }, - "node_modules/@nextcloud/vue/node_modules/@nextcloud/sharing/node_modules/@nextcloud/initial-state": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@nextcloud/initial-state/-/initial-state-3.0.0.tgz", - "integrity": "sha512-cV+HBdkQJGm8FxkBI5rFT/FbMNWNBvpbj6OPrg4Ae4YOOsQ15CL8InPOAw1t4XkOkQK2NEdUGQLVUz/19wXbdQ==", - "license": "GPL-3.0-or-later", - "engines": { - "node": "^20.0.0 || ^22.0.0 || ^24.0.0" - } - }, - "node_modules/@nextcloud/vue/node_modules/is-svg": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-6.1.0.tgz", - "integrity": "sha512-i7YPdvYuSCYcaLQrKwt8cvKTlwHcdA6Hp8N9SO3Q5jIzo8x6kH3N47W0BvPP7NdxVBmIHx7X9DK36czYYW7lHg==", - "license": "MIT", - "dependencies": { - "@file-type/xml": "^0.4.3" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/@nextcloud/webpack-vue-config": { @@ -3423,9 +3408,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "license": "MIT", "peer": true }, @@ -4489,9 +4474,9 @@ } }, "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", "peer": true, "bin": { @@ -4501,6 +4486,19 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "acorn": "^8.14.0" + } + }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", @@ -4516,6 +4514,7 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, "license": "MIT", "peer": true, "dependencies": { @@ -4571,16 +4570,6 @@ "license": "MIT", "peer": true }, - "node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "license": "MIT", - "peer": true, - "peerDependencies": { - "ajv": "^6.9.1" - } - }, "node_modules/ansi-html-community": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", @@ -5006,6 +4995,16 @@ ], "license": "MIT" }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "license": "Apache-2.0", + "peer": true, + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, "node_modules/batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", @@ -5054,31 +5053,31 @@ "license": "MIT" }, "node_modules/bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.2.tgz", + "integrity": "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==", "license": "MIT" }, "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "bytes": "3.1.2", + "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", "type-is": "~1.6.18", - "unpipe": "1.0.0" + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8", @@ -5096,6 +5095,28 @@ "ms": "2.0.0" } }, + "node_modules/body-parser/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -5104,21 +5125,15 @@ "license": "MIT", "peer": true }, - "node_modules/body-parser/node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "node_modules/body-parser/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "peer": true, - "dependencies": { - "side-channel": "^1.0.6" - }, "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 0.8" } }, "node_modules/bonjour-service": { @@ -5222,24 +5237,23 @@ } }, "node_modules/browserify-sign": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.3.tgz", - "integrity": "sha512-JWCZW6SKhfhjJxO8Tyiiy+XYB7cqd2S5/+WeYHsKdNKFlCBhKbblba1A/HN/90YwtxKc8tCErjffZl++UNmGiw==", + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.5.tgz", + "integrity": "sha512-C2AUdAJg6rlM2W5QMp2Q4KGQMVBwR1lIimTsUnutJ8bMpW5B52pGpR2gEnNBNwijumDo5FojQ0L9JrXA8m4YEw==", "license": "ISC", "dependencies": { - "bn.js": "^5.2.1", - "browserify-rsa": "^4.1.0", + "bn.js": "^5.2.2", + "browserify-rsa": "^4.1.1", "create-hash": "^1.2.0", "create-hmac": "^1.1.7", - "elliptic": "^6.5.5", - "hash-base": "~3.0", + "elliptic": "^6.6.1", "inherits": "^2.0.4", - "parse-asn1": "^5.1.7", + "parse-asn1": "^5.1.9", "readable-stream": "^2.3.8", "safe-buffer": "^5.2.1" }, "engines": { - "node": ">= 0.12" + "node": ">= 0.10" } }, "node_modules/browserify-sign/node_modules/isarray": { @@ -5294,9 +5308,9 @@ } }, "node_modules/browserslist": { - "version": "4.24.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", - "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "funding": [ { "type": "opencollective", @@ -5314,10 +5328,11 @@ "license": "MIT", "peer": true, "dependencies": { - "caniuse-lite": "^1.0.30001688", - "electron-to-chromium": "^1.5.73", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.1" + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" @@ -5555,9 +5570,9 @@ "license": "MIT" }, "node_modules/caniuse-lite": { - "version": "1.0.30001695", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001695.tgz", - "integrity": "sha512-vHyLade6wTgI2u1ec3WQBxv+2BrTERV28UXQu9LO6lZ9pYeMk34vjXFLOxo1A4UBA8XTL4njRQZdno/yYaSmWw==", + "version": "1.0.30001769", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz", + "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==", "funding": [ { "type": "opencollective", @@ -5792,9 +5807,9 @@ } }, "node_modules/compression": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.5.tgz", - "integrity": "sha512-bQJ0YRck5ak3LgtnpKkiabX5pNF7tMUh1BSy2ZBOTh0Dim0BUu6aPPwByIns6/A5Prh8PufSPerMDUklpzes2Q==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", "dev": true, "license": "MIT", "peer": true, @@ -5803,7 +5818,7 @@ "compressible": "~2.0.18", "debug": "2.6.9", "negotiator": "~0.6.4", - "on-headers": "~1.0.2", + "on-headers": "~1.1.0", "safe-buffer": "5.2.1", "vary": "~1.1.2" }, @@ -6575,9 +6590,9 @@ } }, "node_modules/diff": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", - "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.2.tgz", + "integrity": "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==", "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" @@ -6705,9 +6720,9 @@ } }, "node_modules/dompurify": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz", - "integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.1.tgz", + "integrity": "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" @@ -6805,9 +6820,9 @@ "peer": true }, "node_modules/electron-to-chromium": { - "version": "1.5.88", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.88.tgz", - "integrity": "sha512-K3C2qf1o+bGzbilTDCTBhTQcMS9KW60yTAaTeeXsfvQuTDDwlokLam/AdqlqcSy9u4UainDgsHV23ksXAOgamw==", + "version": "1.5.286", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", + "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", "license": "ISC", "peer": true }, @@ -6875,14 +6890,14 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.18.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz", - "integrity": "sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==", + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", + "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==", "license": "MIT", "peer": true, "dependencies": { "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" + "tapable": "^2.3.0" }, "engines": { "node": ">=10.13.0" @@ -7011,9 +7026,9 @@ } }, "node_modules/es-module-lexer": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", - "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", "license": "MIT", "peer": true }, @@ -7936,41 +7951,41 @@ } }, "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", "dev": true, "license": "MIT", "peer": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", + "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.13.0", + "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", + "send": "~0.19.0", + "serve-static": "~1.16.2", "setprototypeof": "1.2.0", - "statuses": "2.0.1", + "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -8002,23 +8017,6 @@ "license": "MIT", "peer": true }, - "node_modules/express/node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true, - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -8081,6 +8079,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, "license": "MIT", "peer": true }, @@ -8366,12 +8365,12 @@ } }, "node_modules/focus-trap": { - "version": "7.6.4", - "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.6.4.tgz", - "integrity": "sha512-xx560wGBk7seZ6y933idtjJQc1l+ck+pI3sKvhKozdBV1dRZoKhkW5xoCaFv9tQiX5RH1xfSxjuNu6g+lmN/gw==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.8.0.tgz", + "integrity": "sha512-/yNdlIkpWbM0ptxno3ONTuf+2g318kh2ez3KSeZN5dZ8YC6AAmgeWz+GasYYiBJPFaYcSAPeu4GfhUaChzIJXA==", "license": "MIT", "dependencies": { - "tabbable": "^6.2.0" + "tabbable": "^6.4.0" } }, "node_modules/follow-redirects": { @@ -8624,9 +8623,10 @@ } }, "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { @@ -9292,12 +9292,6 @@ "node": ">=10.18" } }, - "node_modules/ical.js": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ical.js/-/ical.js-2.2.0.tgz", - "integrity": "sha512-P8gjWkTEd5M/SEEvBVPPO/KC+V+HRNRZh3xfCDTVWmUTEfVbL8JaK5GTWS2MJ55aLMhfXhbh7kYzd0nrBARjsA==", - "license": "MPL-2.0" - }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -10349,9 +10343,9 @@ "peer": true }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "license": "MIT", "peer": true, @@ -10406,6 +10400,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, "license": "MIT", "peer": true }, @@ -10535,13 +10530,17 @@ "peer": true }, "node_modules/loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", "license": "MIT", "peer": true, "engines": { "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/loader-utils": { @@ -10592,15 +10591,15 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "license": "MIT" }, "node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", + "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", "license": "MIT" }, "node_modules/lodash.debounce": { @@ -10882,9 +10881,9 @@ } }, "node_modules/mdast-util-to-hast": { - "version": "13.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", - "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -12947,9 +12946,9 @@ } }, "node_modules/node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.3.tgz", + "integrity": "sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==", "dev": true, "license": "(BSD-3-Clause OR GPL-2.0)", "peer": true, @@ -12958,9 +12957,9 @@ } }, "node_modules/node-gettext": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/node-gettext/-/node-gettext-3.0.0.tgz", - "integrity": "sha512-/VRYibXmVoN6tnSAY2JWhNRhWYJ8Cd844jrZU/DwLVoI4vBI6ceYbd8i42sYZ9uOgDH3S7vslIKOWV/ZrT2YBA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/node-gettext/-/node-gettext-3.0.1.tgz", + "integrity": "sha512-foI20qQAV22OlCeFIUZiXZutPbe23nGuDp7hLvK6PanhOYlUJIgLAUZa4MB5pX0J62dubOM/MYVhQ2aYTUPbhA==", "dependencies": { "lodash.get": "^4.4.2" } @@ -13006,9 +13005,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "license": "MIT", "peer": true }, @@ -13220,9 +13219,9 @@ } }, "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", "dev": true, "license": "MIT", "peer": true, @@ -13425,16 +13424,15 @@ } }, "node_modules/parse-asn1": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.7.tgz", - "integrity": "sha512-CTM5kuWR3sx9IFamcl5ErfPl6ea/N8IYwiJ+vpeB2g+1iknv7zBl5uPwbMbRVznRVbrNY6lGuDoE5b30grmbqg==", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.9.tgz", + "integrity": "sha512-fIYNuZ/HastSb80baGOuPRo1O9cf4baWw5WsAp7dBuUzeTD/BoaG8sVTdlPFksBE2lF21dN+A1AnrpIjSWqHHg==", "license": "ISC", "dependencies": { "asn1.js": "^4.10.1", "browserify-aes": "^1.2.0", "evp_bytestokey": "^1.0.3", - "hash-base": "~3.0", - "pbkdf2": "^3.1.2", + "pbkdf2": "^3.1.5", "safe-buffer": "^5.2.1" }, "engines": { @@ -13574,51 +13572,20 @@ } }, "node_modules/pbkdf2": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.3.tgz", - "integrity": "sha512-wfRLBZ0feWRhCIkoMB6ete7czJcnNnqRpcoWQBLqatqXXmelSRqfdDK4F3u9T2s2cXas/hQJcryI/4lAL+XTlA==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.5.tgz", + "integrity": "sha512-Q3CG/cYvCO1ye4QKkuH7EXxs3VC/rI1/trd+qX2+PolbaKG0H+bgcZzrTt96mMyRtejk+JMCiLUn3y29W8qmFQ==", "license": "MIT", "dependencies": { - "create-hash": "~1.1.3", + "create-hash": "^1.2.0", "create-hmac": "^1.1.7", - "ripemd160": "=2.0.1", + "ripemd160": "^2.0.3", "safe-buffer": "^5.2.1", - "sha.js": "^2.4.11", - "to-buffer": "^1.2.0" + "sha.js": "^2.4.12", + "to-buffer": "^1.2.1" }, "engines": { - "node": ">=0.12" - } - }, - "node_modules/pbkdf2/node_modules/create-hash": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz", - "integrity": "sha512-snRpch/kwQhcdlnZKYanNF1m0RDlrCdSKQaH87w1FCFPVPNCQ/Il9QJKAX2jVBZddRdaHBMC+zXa9Gw9tmkNUA==", - "license": "MIT", - "dependencies": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "sha.js": "^2.4.0" - } - }, - "node_modules/pbkdf2/node_modules/hash-base": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-2.0.2.tgz", - "integrity": "sha512-0TROgQ1/SxE6KmxWSvXHvRj90/Xo1JvZShofnYF+f6ZsGtR4eES7WfrQzPalmyagfKZCXpVnitiRebZulWsbiw==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.1" - } - }, - "node_modules/pbkdf2/node_modules/ripemd160": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.1.tgz", - "integrity": "sha512-J7f4wutN8mdbV08MJnXibYpCOPHR+yzy+iQ/AsjMv2j8cLavQ8VGagDFUwwTAdF8FmRKVeNpbTTEwNHCW1g94w==", - "license": "MIT", - "dependencies": { - "hash-base": "^2.0.0", - "inherits": "^2.0.1" + "node": ">= 0.10" } }, "node_modules/picocolors": { @@ -14140,9 +14107,9 @@ } }, "node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -14235,22 +14202,55 @@ } }, "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8" } }, + "node_modules/raw-body/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/raw-body/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/read-pkg": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-6.0.0.tgz", @@ -14939,15 +14939,75 @@ } }, "node_modules/ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.3.tgz", + "integrity": "sha512-5Di9UC0+8h1L6ZD2d7awM7E/T4uA1fJRlx6zk/NvdCCVEoAnFqvHmCuNeIKoCeIixBX/q8uM+6ycDvF8woqosA==", "license": "MIT", "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" + "hash-base": "^3.1.2", + "inherits": "^2.0.4" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ripemd160/node_modules/hash-base": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.2.tgz", + "integrity": "sha512-Bb33KbowVTIj5s7Ked1OsqHUeCpz//tPwR+E2zJgJKo9Z5XolZ9b6bdUgjmYlwnWhoOQKoTd1TYToZGn5mAYOg==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "^2.3.8", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ripemd160/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/ripemd160/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/ripemd160/node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/ripemd160/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" } }, + "node_modules/ripemd160/node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, "node_modules/run-applescript": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", @@ -15177,9 +15237,9 @@ "license": "ISC" }, "node_modules/schema-utils": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", - "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", "license": "MIT", "peer": true, "dependencies": { @@ -16540,9 +16600,9 @@ "peer": true }, "node_modules/tabbable": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", - "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.4.0.tgz", + "integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==", "license": "MIT" }, "node_modules/table": { @@ -16614,24 +16674,28 @@ } }, "node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", "license": "MIT", "peer": true, "engines": { "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/terser": { - "version": "5.37.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.37.0.tgz", - "integrity": "sha512-B8wRRkmre4ERucLM/uXx4MOV5cbnOlVAqUst+1+iLKPI0dOgFO28f84ptoQt9HEI537PMzfYa/d+GEPKTRXmYA==", + "version": "5.46.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz", + "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", "license": "BSD-2-Clause", "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", + "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, @@ -16643,9 +16707,9 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.11", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.11.tgz", - "integrity": "sha512-RVCsMfuD0+cTt3EwX8hSl2Ks56EbFHWmhluwcqoPKtBnfjiT6olaq7PRIRfhyU8nnC2MrnDrBLfrD/RGE+cVXQ==", + "version": "5.3.16", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz", + "integrity": "sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==", "license": "MIT", "peer": true, "dependencies": { @@ -17260,9 +17324,9 @@ } }, "node_modules/unist-util-visit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", - "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -17300,9 +17364,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz", - "integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "funding": [ { "type": "opencollective", @@ -17334,6 +17398,7 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, "license": "BSD-2-Clause", "peer": true, "dependencies": { @@ -17770,9 +17835,9 @@ } }, "node_modules/watchpack": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", - "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", + "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", "license": "MIT", "peer": true, "dependencies": { @@ -17814,16 +17879,16 @@ } }, "node_modules/webdav": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/webdav/-/webdav-5.8.0.tgz", - "integrity": "sha512-iuFG7NamJ41Oshg4930iQgfIpRrUiatPWIekeznYgEf2EOraTRcDPTjy7gIOMtkdpKTaqPk1E68NO5PAGtJahA==", + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/webdav/-/webdav-5.9.0.tgz", + "integrity": "sha512-OMJ6wtK1WvCO++aOLoQgE96S8KT4e5aaClWHmHXfFU369r4eyELN569B7EqT4OOUb99mmO58GkyuiCv/Ag6J0Q==", "license": "MIT", "dependencies": { "@buttercup/fetch": "^0.2.1", "base-64": "^1.0.0", "byte-length": "^1.0.2", - "entities": "^6.0.0", - "fast-xml-parser": "^4.5.1", + "entities": "^6.0.1", + "fast-xml-parser": "^5.3.4", "hot-patcher": "^2.0.1", "layerr": "^3.0.0", "md5": "^2.3.0", @@ -17850,36 +17915,68 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/webdav/node_modules/fast-xml-parser": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.4.tgz", + "integrity": "sha512-EFd6afGmXlCx8H8WTZHhAoDaWaGyuIBoZJ2mknrNxug+aZKjkp0a0dlars9Izl+jF+7Gu1/5f/2h68cQpe0IiA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^2.1.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/webdav/node_modules/strnum": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.2.tgz", + "integrity": "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, "node_modules/webpack": { - "version": "5.97.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.97.1.tgz", - "integrity": "sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==", + "version": "5.105.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.0.tgz", + "integrity": "sha512-gX/dMkRQc7QOMzgTe6KsYFM7DxeIONQSui1s0n/0xht36HvrgbxtM1xBlgx596NbpHuQU8P7QpKwrZYwUX48nw==", "license": "MIT", "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", - "@types/estree": "^1.0.6", + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", "@webassemblyjs/ast": "^1.14.1", "@webassemblyjs/wasm-edit": "^1.14.1", "@webassemblyjs/wasm-parser": "^1.14.1", - "acorn": "^8.14.0", - "browserslist": "^4.24.0", + "acorn": "^8.15.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.1", - "es-module-lexer": "^1.2.1", + "enhanced-resolve": "^5.19.0", + "es-module-lexer": "^2.0.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", + "loader-runner": "^4.3.1", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^3.2.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.10", - "watchpack": "^2.4.1", - "webpack-sources": "^3.2.3" + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.3.16", + "watchpack": "^2.5.1", + "webpack-sources": "^3.3.3" }, "bin": { "webpack": "bin/webpack.js" @@ -18119,34 +18216,15 @@ } }, "node_modules/webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", + "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", "license": "MIT", "peer": true, "engines": { "node": ">=10.13.0" } }, - "node_modules/webpack/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, "node_modules/websocket-driver": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", diff --git a/src/components/BookmarksList.vue b/src/components/BookmarksList.vue index a017f442f..9d32212da 100644 --- a/src/components/BookmarksList.vue +++ b/src/components/BookmarksList.vue @@ -72,10 +72,6 @@ :key="'nosort' + item.type + item.id" :bookmark="getBookmark(item.id)" /> - - + + diff --git a/src/components/ViewPrivate.vue b/src/components/ViewPrivate.vue index e6878609a..719c60e51 100644 --- a/src/components/ViewPrivate.vue +++ b/src/components/ViewPrivate.vue @@ -234,6 +234,9 @@ export default { error: reject, }), ) + if (typeof resDocument.data !== 'undefined') { + return resDocument.data + } if (resDocument.querySelector('status').textContent !== 'ok') { console.error('Failed request', resDocument) return From e95158717506b86e4fdd5f40b11026bf2ab61600 Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sat, 7 Feb 2026 10:15:13 +0100 Subject: [PATCH 15/18] fix(SidebarBookmark): Do not display background if no preview was done Signed-off-by: Marcel Klehr --- src/components/SidebarBookmark.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/SidebarBookmark.vue b/src/components/SidebarBookmark.vue index 633628a5b..163817ca4 100644 --- a/src/components/SidebarBookmark.vue +++ b/src/components/SidebarBookmark.vue @@ -158,7 +158,7 @@ export default { return this.$store.getters.getBookmark(this.$store.state.sidebar.id) }, background() { - return generateUrl(`/apps/bookmarks/bookmark/${this.bookmark.id}/image`) + return this.bookmark.lastPreview ? generateUrl(`/apps/bookmarks/bookmark/${this.bookmark.id}/image`) : undefined }, addedDate() { const date = new Date(Number(this.bookmark.added) * 1000) From c9243e55949e294dc1cf96532c184755d7e6121d Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Sat, 7 Feb 2026 10:33:04 +0100 Subject: [PATCH 16/18] feat(Trashbin): Show trashbin count in Navigation Signed-off-by: Marcel Klehr --- appinfo/routes.php | 1 + lib/Controller/BookmarkController.php | 19 ++ lib/Controller/InternalBookmarkController.php | 9 + lib/Controller/WebViewController.php | 1 + lib/Db/BookmarkMapper.php | 32 +++ src/components/Navigation.vue | 6 + src/components/ViewPrivate.vue | 1 + src/store/actions.js | 34 +++ src/store/index.js | 1 + src/store/mutations.js | 211 ++++++++++-------- 10 files changed, 221 insertions(+), 94 deletions(-) diff --git a/appinfo/routes.php b/appinfo/routes.php index f14ff69b2..c0e2e2b13 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -45,6 +45,7 @@ ['name' => 'internal_bookmark#import_bookmark', 'url' => '/bookmark/import', 'verb' => 'POST'], ['name' => 'internal_bookmark#count_unavailable', 'url' => '/bookmark/unavailable', 'verb' => 'GET'], ['name' => 'internal_bookmark#count_archived', 'url' => '/bookmark/archived', 'verb' => 'GET'], + ['name' => 'internal_bookmark#count_deleted', 'url' => '/bookmark/deletedCount', 'verb' => 'GET'], ['name' => 'internal_bookmark#count_duplicated', 'url' => '/bookmark/duplicated', 'verb' => 'GET'], ['name' => 'internal_bookmark#get_deleted_bookmarks', 'url' => '/bookmark/deleted', 'verb' => 'GET'], ['name' => 'internal_bookmark#edit_bookmark', 'url' => '/bookmark/{id}', 'verb' => 'PUT'], diff --git a/lib/Controller/BookmarkController.php b/lib/Controller/BookmarkController.php index 508eb95c5..8764a6d59 100644 --- a/lib/Controller/BookmarkController.php +++ b/lib/Controller/BookmarkController.php @@ -874,6 +874,25 @@ public function countDuplicated(): JSONResponse { return new JSONResponse(['status' => 'success', 'item' => $count]); } + /** + * @return JSONResponse + * @NoAdminRequired + * @NoCSRFRequired + * @BruteForceProtection + * @PublicPage + * @throws UnauthenticatedError + */ + public function countDeleted(): JSONResponse { + if (!Authorizer::hasPermission(Authorizer::PERM_READ, $this->authorizer->getPermissionsForFolder(-1, $this->request))) { + $res = new JSONResponse(['status' => 'error', 'data' => ['Unauthorized']], Http::STATUS_FORBIDDEN); + $res->throttle(); + return $res; + } + + $count = $this->bookmarkMapper->countDeleted($this->authorizer->getUserId()); + return new JSONResponse(['status' => 'success', 'item' => $count]); + } + /** * @return JSONResponse * @NoAdminRequired diff --git a/lib/Controller/InternalBookmarkController.php b/lib/Controller/InternalBookmarkController.php index 695e955ed..5709d8e0e 100644 --- a/lib/Controller/InternalBookmarkController.php +++ b/lib/Controller/InternalBookmarkController.php @@ -286,4 +286,13 @@ public function countAllClicks(): DataResponse { public function countWithClicks(): DataResponse { return $this->publicController->countWithClicks(); } + + /** + * @return JSONResponse + * @throws \OCA\Bookmarks\Exception\UnauthenticatedError + * @NoAdminRequired + */ + public function countDeleted() { + return $this->publicController->countDeleted(); + } } diff --git a/lib/Controller/WebViewController.php b/lib/Controller/WebViewController.php index fe6ac1184..a44db041a 100644 --- a/lib/Controller/WebViewController.php +++ b/lib/Controller/WebViewController.php @@ -76,6 +76,7 @@ public function index(): AugmentedTemplateResponse { $this->initialState->provideInitialState($this->appName, 'folders', $this->folderController->getFolders()->getData()['data']); $this->initialState->provideInitialState($this->appName, 'deletedFolders', $this->folderController->getDeletedFolders()->getData()['data']); $this->initialState->provideInitialState($this->appName, 'archivedCount', $this->bookmarkController->countArchived()->getData()['item']); + $this->initialState->provideInitialState($this->appName, 'deletedCount', $this->bookmarkController->countDeleted()->getData()['item']); $this->initialState->provideInitialState($this->appName, 'duplicatedCount', $this->bookmarkController->countDuplicated()->getData()['item']); $this->initialState->provideInitialState($this->appName, 'unavailableCount', $this->bookmarkController->countUnavailable()->getData()['item']); $this->initialState->provideInitialState($this->appName, 'allCount', $this->bookmarkController->countBookmarks(-1)->getData()['item']); diff --git a/lib/Db/BookmarkMapper.php b/lib/Db/BookmarkMapper.php index 3d5e68a9e..bf493ed56 100644 --- a/lib/Db/BookmarkMapper.php +++ b/lib/Db/BookmarkMapper.php @@ -528,6 +528,38 @@ private function _filterTags(IQueryBuilder $qb, QueryParameters $params): void { } } + /** + * @param string $userId + * @return int + * @throws Exception + */ + public function countDeleted(string $userId): int { + $qb = $this->db->getQueryBuilder(); + $qb->selectAlias($qb->func()->count('b.id'), 'count'); + $qb + ->from('bookmarks', 'b') + ->innerJoin('b', 'bookmarks_tree', 'tr', 'b.id = tr.id AND tr.type = ' . $qb->createPositionalParameter(TreeMapper::TYPE_BOOKMARK)) + ->where($qb->expr()->eq('b.user_id', $qb->createPositionalParameter($userId))) + ->andWhere($qb->expr()->isNotNull('tr.soft_deleted_at')); + $result = $qb->executeQuery(); + $userOwnerDeletedCount = $result->fetch(PDO::FETCH_COLUMN); + $result->closeCursor(); + + $qb = $this->db->getQueryBuilder(); + $qb->selectAlias($qb->func()->count('b.id'), 'count'); + $qb + ->from('bookmarks', 'b') + ->innerJoin('b', 'bookmarks_tree', 'tr', 'b.id = tr.id AND tr.type = ' . $qb->createPositionalParameter(TreeMapper::TYPE_BOOKMARK)) + ->innerJoin('tr', 'bookmarks_shared_folders', 'sf', $qb->expr()->eq('tr.parent_folder', 'sf.folder_id')) + ->where($qb->expr()->eq('sf.user_id', $qb->createPositionalParameter($userId))) + ->andWhere($qb->expr()->isNotNull('tr.soft_deleted_at')); + $result = $qb->executeQuery(); + $foreignDeletedCount = $result->fetch(PDO::FETCH_COLUMN); + $result->closeCursor(); + + return $userOwnerDeletedCount + $foreignDeletedCount; + } + /** * @param string $userId * @return int diff --git a/src/components/Navigation.vue b/src/components/Navigation.vue index ef2bd85c0..6c23d1b42 100644 --- a/src/components/Navigation.vue +++ b/src/components/Navigation.vue @@ -108,6 +108,9 @@ :to="{ name: routes.TRASHBIN }" :name="t('bookmarks', 'Trash Bin')"> + + {{ deletedBookmarksCount | largeNumbers }} +