From 9191054d956e0841a22fac3e4128164d4383ee00 Mon Sep 17 00:00:00 2001 From: Crisciany Souza Date: Sat, 20 Dec 2025 09:51:35 -0400 Subject: [PATCH 1/5] feat: unified search Signed-off-by: Crisciany Souza --- lib/AppInfo/Application.php | 3 + lib/Db/SignRequestMapper.php | 43 +++++++++ lib/Search/FileSearchProvider.php | 148 ++++++++++++++++++++++++++++++ 3 files changed, 194 insertions(+) create mode 100644 lib/Search/FileSearchProvider.php diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 4d504083eb..2bccafe461 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -27,6 +27,7 @@ use OCA\Libresign\Middleware\GlobalInjectionMiddleware; use OCA\Libresign\Middleware\InjectionMiddleware; use OCA\Libresign\Notification\Notifier; +use OCA\Libresign\Search\FileSearchProvider; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootContext; use OCP\AppFramework\Bootstrap\IBootstrap; @@ -57,6 +58,8 @@ public function register(IRegistrationContext $context): void { $context->registerNotifierService(Notifier::class); + $context->registerSearchProvider(FileSearchProvider::class); + $context->registerEventListener(LoadSidebar::class, TemplateLoader::class); $context->registerEventListener(BeforeNodeDeletedEvent::class, BeforeNodeDeletedListener::class); $context->registerEventListener(CacheEntryRemovedEvent::class, BeforeNodeDeletedListener::class); diff --git a/lib/Db/SignRequestMapper.php b/lib/Db/SignRequestMapper.php index 83dd9839e9..113ee82323 100644 --- a/lib/Db/SignRequestMapper.php +++ b/lib/Db/SignRequestMapper.php @@ -460,6 +460,49 @@ public function getFilesAssociatedFilesWithMe( ]; } + public function getFilesToSearchProvider(IUser $user, string $term, int $limit, int $offset): array { + $filter = [ + 'page' => ($offset / $limit) + 1, + 'length' => $limit, + ]; + + $qb = $this->getFilesAssociatedFilesWithMeQueryBuilder($user->getUID(), $filter); + + if (!empty($term)) { + $qb->andWhere( + $qb->expr()->like('f.name', $qb->createNamedParameter('%' . $this->db->escapeLikeParameter($term) . '%')) + ); + } + + $qb->orderBy('f.created_at', 'DESC'); + + $result = $qb->executeQuery(); + $files = []; + + while ($row = $result->fetch()) { + try { + $file = new File(); + $file->setId((int)$row['id']); + $file->setUserId($row['user_id']); + $file->setNodeId((int)($row['node_id'] ?? 0)); + $file->setSignedNodeId($row['signed_node_id'] ? (int)$row['signed_node_id'] : null); + $file->setName($row['name'] ?? ''); + $file->setStatus((int)($row['status'] ?? 0)); + $file->setUuid($row['uuid'] ?? ''); + $file->setCreatedAt($row['created_at'] ?? ''); + + + $files[] = $file; + } catch (\Exception $e) { + continue; + } + } + + $result->closeCursor(); + + return $files; + } + /** * @param array $signRequests * @return FileElement[][] diff --git a/lib/Search/FileSearchProvider.php b/lib/Search/FileSearchProvider.php new file mode 100644 index 0000000000..c5c05f395f --- /dev/null +++ b/lib/Search/FileSearchProvider.php @@ -0,0 +1,148 @@ +l10n->t('LibreSign documents'); + } + + #[\Override] + public function getOrder(string $route, array $routeParameters): int { + if (strpos($route, Application::APP_ID . '.') === 0) { + return 0; + } + return 10; + } + + #[\Override] + public function search(IUser $user, ISearchQuery $query): SearchResult { + if (!$this->appManager->isEnabledForUser(Application::APP_ID, $user)) { + return SearchResult::complete($this->l10n->t('LibreSign documents'), []); + } + + $term = $query->getTerm(); + $limit = $query->getLimit(); + $offset = $query->getCursor(); + + try { + $files = $this->fileMapper->getFilesToSearchProvider($user, $term, $limit, (int)$offset); + } catch (\Exception $e) { + return SearchResult::complete($this->l10n->t('LibreSign documents'), []); + } + + $results = array_map(function (File $file) use ($user) { + return $this->formatResult($file, $user); + }, $files); + + return SearchResult::paginated( + $this->l10n->t('LibreSign documents'), + $results, + $offset + $limit + ); + } + + /** + * Format a File entity as a SearchResultEntry + * + * @param File $file The file entity to format + * @param IUser $user Current user + * @return SearchResultEntry Formatted search result entry + */ + private function formatResult(File $file, IUser $user): SearchResultEntry { + $userFolder = $this->rootFolder->getUserFolder($user->getUID()); + $thumbnailUrl = ''; + $subline = ''; + $icon = ''; + $path = ''; + + try { + $nodes = $userFolder->getById($file->getNodeId()); + if (!empty($nodes)) { + $node = array_shift($nodes); + + $icon = $this->mimeTypeDetector->mimeTypeIcon($node->getMimetype()); + + $thumbnailUrl = $this->urlGenerator->linkToRouteAbsolute( + 'core.Preview.getPreviewByFileId', + [ + 'x' => 32, + 'y' => 32, + 'fileId' => $node->getId() + ] + ); + + $path = $userFolder->getRelativePath($node->getPath()); + $subline = $this->formatSubline($path); + } + } catch (\Exception $e) { + } + + $link = $this->urlGenerator->linkToRoute( + 'files.View.showFile', + ['fileid' => $file->getNodeId()] + ); + + $searchResultEntry = new SearchResultEntry( + $thumbnailUrl, + $file->getName() ?? $this->l10n->t('Unnamed document'), + $subline, + $this->urlGenerator->getAbsoluteURL($link), + $icon, + ); + + $searchResultEntry->addAttribute('fileId', (string)$file->getNodeId()); + $searchResultEntry->addAttribute('path', $path); + + return $searchResultEntry; + } + + private function formatSubline(string $path): string { + if (strrpos($path, '/') > 0) { + $path = ltrim(dirname($path), '/'); + return $this->l10n->t('in %s', [$path]); + } else { + return ''; + } + } + +} From 8280ee843be37d5d1998d428971765123f1af122 Mon Sep 17 00:00:00 2001 From: Crisciany Souza Date: Tue, 23 Dec 2025 14:55:43 -0400 Subject: [PATCH 2/5] feat: unified search Signed-off-by: Crisciany Souza --- lib/Db/SignRequestMapper.php | 55 +++++++++++++++---------------- lib/Search/FileSearchProvider.php | 9 ----- 2 files changed, 26 insertions(+), 38 deletions(-) diff --git a/lib/Db/SignRequestMapper.php b/lib/Db/SignRequestMapper.php index 113ee82323..de674855bc 100644 --- a/lib/Db/SignRequestMapper.php +++ b/lib/Db/SignRequestMapper.php @@ -460,19 +460,20 @@ public function getFilesAssociatedFilesWithMe( ]; } - public function getFilesToSearchProvider(IUser $user, string $term, int $limit, int $offset): array { + public function getFilesToSearchProvider(IUser $user, string $fileName, int $limit, int $offset): array { $filter = [ 'page' => ($offset / $limit) + 1, 'length' => $limit, + 'fileName' => $fileName, ]; - $qb = $this->getFilesAssociatedFilesWithMeQueryBuilder($user->getUID(), $filter); + $sort = [ + 'sortBy' => 'created_at', + 'sortDirection' => 'desc', + ]; + + $qb = $this->getFilesAssociatedFilesWithMeQueryBuilder($user->getUID(), $filter, false, $sort); - if (!empty($term)) { - $qb->andWhere( - $qb->expr()->like('f.name', $qb->createNamedParameter('%' . $this->db->escapeLikeParameter($term) . '%')) - ); - } $qb->orderBy('f.created_at', 'DESC'); @@ -481,16 +482,7 @@ public function getFilesToSearchProvider(IUser $user, string $term, int $limit, while ($row = $result->fetch()) { try { - $file = new File(); - $file->setId((int)$row['id']); - $file->setUserId($row['user_id']); - $file->setNodeId((int)($row['node_id'] ?? 0)); - $file->setSignedNodeId($row['signed_node_id'] ? (int)$row['signed_node_id'] : null); - $file->setName($row['name'] ?? ''); - $file->setStatus((int)($row['status'] ?? 0)); - $file->setUuid($row['uuid'] ?? ''); - $file->setCreatedAt($row['created_at'] ?? ''); - + $file = File::fromRow($row); $files[] = $file; } catch (\Exception $e) { @@ -576,7 +568,7 @@ public function getMyLibresignFile(string $userId, ?array $filter = []): File { return $file->fromRow($row); } - private function getFilesAssociatedFilesWithMeQueryBuilder(string $userId, array $filter = [], bool $count = false): IQueryBuilder { + private function getFilesAssociatedFilesWithMeQueryBuilder(string $userId, array $filter = [], bool $count = false, array $sort = []): IQueryBuilder { $qb = $this->db->getQueryBuilder(); $qb->from('libresign_file', 'f') ->leftJoin('f', 'libresign_sign_request', 'sr', 'sr.file_id = f.id') @@ -687,6 +679,11 @@ private function getFilesAssociatedFilesWithMeQueryBuilder(string $userId, array $qb->expr()->lte('f.created_at', $qb->createNamedParameter($end, IQueryBuilder::PARAM_STR)) ); } + if (!empty($filter['fileName'])) { + $qb->andWhere( + $qb->expr()->like('f.name', $qb->createNamedParameter('%' . $this->db->escapeLikeParameter($filter['fileName']) . '%')) + ); + } if (!empty($filter['parentFileId'])) { $qb->andWhere( $qb->expr()->eq('f.parent_file_id', $qb->createNamedParameter($filter['parentFileId'], IQueryBuilder::PARAM_INT)) @@ -694,19 +691,9 @@ private function getFilesAssociatedFilesWithMeQueryBuilder(string $userId, array } else { $qb->andWhere($qb->expr()->isNull('f.parent_file_id')); } - } else { - $qb->andWhere($qb->expr()->isNull('f.parent_file_id')); } - return $qb; - } - private function getFilesAssociatedFilesWithMeStmt( - string $userId, - ?array $filter = [], - ?array $sort = [], - ): Pagination { - $qb = $this->getFilesAssociatedFilesWithMeQueryBuilder($userId, $filter); - if (!empty($sort['sortBy'])) { + if (!empty($sort['sortBy']) && !empty($sort['sortDirection'])) { switch ($sort['sortBy']) { case 'name': case 'status': @@ -723,6 +710,16 @@ private function getFilesAssociatedFilesWithMeStmt( } } + return $qb; + } + + private function getFilesAssociatedFilesWithMeStmt( + string $userId, + ?array $filter = [], + ?array $sort = [], + ): Pagination { + $qb = $this->getFilesAssociatedFilesWithMeQueryBuilder($userId, $filter, false, $sort); + $countQb = $this->getFilesAssociatedFilesWithMeQueryBuilder( userId: $userId, filter: $filter, diff --git a/lib/Search/FileSearchProvider.php b/lib/Search/FileSearchProvider.php index c5c05f395f..3f010ad822 100644 --- a/lib/Search/FileSearchProvider.php +++ b/lib/Search/FileSearchProvider.php @@ -15,7 +15,6 @@ use OCP\App\IAppManager; use OCP\Files\IMimeTypeDetector; use OCP\Files\IRootFolder; -use OCP\IDBConnection; use OCP\IL10N; use OCP\IURLGenerator; use OCP\IUser; @@ -30,7 +29,6 @@ public function __construct( private IURLGenerator $urlGenerator, private IRootFolder $rootFolder, private IAppManager $appManager, - private IDBConnection $db, private IMimeTypeDetector $mimeTypeDetector, private SignRequestMapper $fileMapper, ) { @@ -81,13 +79,6 @@ public function search(IUser $user, ISearchQuery $query): SearchResult { ); } - /** - * Format a File entity as a SearchResultEntry - * - * @param File $file The file entity to format - * @param IUser $user Current user - * @return SearchResultEntry Formatted search result entry - */ private function formatResult(File $file, IUser $user): SearchResultEntry { $userFolder = $this->rootFolder->getUserFolder($user->getUID()); $thumbnailUrl = ''; From d82ee30546d84f0129385abb7540226cdaf5ec3b Mon Sep 17 00:00:00 2001 From: Crisciany Souza Date: Mon, 19 Jan 2026 14:17:37 -0400 Subject: [PATCH 3/5] feat: remove orderBy line Signed-off-by: Crisciany Souza --- lib/Db/SignRequestMapper.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/Db/SignRequestMapper.php b/lib/Db/SignRequestMapper.php index de674855bc..d2eccd9c62 100644 --- a/lib/Db/SignRequestMapper.php +++ b/lib/Db/SignRequestMapper.php @@ -474,9 +474,6 @@ public function getFilesToSearchProvider(IUser $user, string $fileName, int $lim $qb = $this->getFilesAssociatedFilesWithMeQueryBuilder($user->getUID(), $filter, false, $sort); - - $qb->orderBy('f.created_at', 'DESC'); - $result = $qb->executeQuery(); $files = []; From 6e43c9a0564e4e0f1adaae8b2cc5ae10ad290d6a Mon Sep 17 00:00:00 2001 From: Crisciany Souza Date: Tue, 20 Jan 2026 14:20:51 -0400 Subject: [PATCH 4/5] feat: some adjustments Signed-off-by: Crisciany Souza --- lib/Db/SignRequestMapper.php | 7 ++++++- lib/Search/FileSearchProvider.php | 7 ++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/Db/SignRequestMapper.php b/lib/Db/SignRequestMapper.php index d2eccd9c62..7bf507f8c0 100644 --- a/lib/Db/SignRequestMapper.php +++ b/lib/Db/SignRequestMapper.php @@ -565,7 +565,12 @@ public function getMyLibresignFile(string $userId, ?array $filter = []): File { return $file->fromRow($row); } - private function getFilesAssociatedFilesWithMeQueryBuilder(string $userId, array $filter = [], bool $count = false, array $sort = []): IQueryBuilder { + private function getFilesAssociatedFilesWithMeQueryBuilder( + string $userId, + array $filter = [], + bool $count = false, + array $sort = [] + ): IQueryBuilder { $qb = $this->db->getQueryBuilder(); $qb->from('libresign_file', 'f') ->leftJoin('f', 'libresign_sign_request', 'sr', 'sr.file_id = f.id') diff --git a/lib/Search/FileSearchProvider.php b/lib/Search/FileSearchProvider.php index 3f010ad822..aeabcb9dce 100644 --- a/lib/Search/FileSearchProvider.php +++ b/lib/Search/FileSearchProvider.php @@ -79,8 +79,8 @@ public function search(IUser $user, ISearchQuery $query): SearchResult { ); } - private function formatResult(File $file, IUser $user): SearchResultEntry { - $userFolder = $this->rootFolder->getUserFolder($user->getUID()); + private function formatResult(File $file): SearchResultEntry { + $userFolder = $this->rootFolder->getUserFolder($file->getUserId()); $thumbnailUrl = ''; $subline = ''; $icon = ''; @@ -115,7 +115,7 @@ private function formatResult(File $file, IUser $user): SearchResultEntry { $searchResultEntry = new SearchResultEntry( $thumbnailUrl, - $file->getName() ?? $this->l10n->t('Unnamed document'), + $file->getName(), $subline, $this->urlGenerator->getAbsoluteURL($link), $icon, @@ -130,6 +130,7 @@ private function formatResult(File $file, IUser $user): SearchResultEntry { private function formatSubline(string $path): string { if (strrpos($path, '/') > 0) { $path = ltrim(dirname($path), '/'); + // TRANSLATORS This string indicates the location of a file in a given path. return $this->l10n->t('in %s', [$path]); } else { return ''; From a4a76d9d59aa3b6a16259ba79d71b19ed9a6a4c0 Mon Sep 17 00:00:00 2001 From: Crisciany Souza Date: Tue, 20 Jan 2026 14:49:40 -0400 Subject: [PATCH 5/5] fix: correcting cs:php error Signed-off-by: Crisciany Souza --- lib/Db/SignRequestMapper.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/Db/SignRequestMapper.php b/lib/Db/SignRequestMapper.php index 7bf507f8c0..63e5ec4744 100644 --- a/lib/Db/SignRequestMapper.php +++ b/lib/Db/SignRequestMapper.php @@ -566,11 +566,11 @@ public function getMyLibresignFile(string $userId, ?array $filter = []): File { } private function getFilesAssociatedFilesWithMeQueryBuilder( - string $userId, - array $filter = [], - bool $count = false, - array $sort = [] - ): IQueryBuilder { + string $userId, + array $filter = [], + bool $count = false, + array $sort = [], + ): IQueryBuilder { $qb = $this->db->getQueryBuilder(); $qb->from('libresign_file', 'f') ->leftJoin('f', 'libresign_sign_request', 'sr', 'sr.file_id = f.id')