From 353bb31783d8170ab8ee9dc97af05535109de8d9 Mon Sep 17 00:00:00 2001 From: Crisciany Souza Date: Sat, 20 Dec 2025 09:51:35 -0400 Subject: [PATCH 1/2] 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 d0f9c282f5..2c671b6f06 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; @@ -64,6 +65,8 @@ public function register(IRegistrationContext $context): void { $context->registerNotifierService(Notifier::class); + $context->registerSearchProvider(FileSearchProvider::class); + $context->registerEventListener(LoadSidebar::class, LoadSidebarListener::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 9380c2380c..62b9c3b8f0 100644 --- a/lib/Db/SignRequestMapper.php +++ b/lib/Db/SignRequestMapper.php @@ -457,6 +457,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 608a30374c4978603633995eb33a4b05cef3c514 Mon Sep 17 00:00:00 2001 From: Crisciany Souza Date: Tue, 23 Dec 2025 14:55:43 -0400 Subject: [PATCH 2/2] 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 62b9c3b8f0..0e4b598290 100644 --- a/lib/Db/SignRequestMapper.php +++ b/lib/Db/SignRequestMapper.php @@ -457,19 +457,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'); @@ -478,16 +479,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) { @@ -573,7 +565,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') @@ -676,6 +668,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)) @@ -683,19 +680,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': @@ -712,6 +699,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 = '';