Skip to content

Commit 35b6315

Browse files
authored
Merge pull request #6243 from LibreSign/feat/unified-search
feat: unified search
2 parents cbe244d + a4a76d9 commit 35b6315

3 files changed

Lines changed: 197 additions & 12 deletions

File tree

lib/AppInfo/Application.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
use OCA\Libresign\Middleware\GlobalInjectionMiddleware;
2828
use OCA\Libresign\Middleware\InjectionMiddleware;
2929
use OCA\Libresign\Notification\Notifier;
30+
use OCA\Libresign\Search\FileSearchProvider;
3031
use OCP\AppFramework\App;
3132
use OCP\AppFramework\Bootstrap\IBootContext;
3233
use OCP\AppFramework\Bootstrap\IBootstrap;
@@ -57,6 +58,8 @@ public function register(IRegistrationContext $context): void {
5758

5859
$context->registerNotifierService(Notifier::class);
5960

61+
$context->registerSearchProvider(FileSearchProvider::class);
62+
6063
$context->registerEventListener(LoadSidebar::class, TemplateLoader::class);
6164
$context->registerEventListener(BeforeNodeDeletedEvent::class, BeforeNodeDeletedListener::class);
6265
$context->registerEventListener(CacheEntryRemovedEvent::class, BeforeNodeDeletedListener::class);

lib/Db/SignRequestMapper.php

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,38 @@ public function getFilesAssociatedFilesWithMe(
460460
];
461461
}
462462

463+
public function getFilesToSearchProvider(IUser $user, string $fileName, int $limit, int $offset): array {
464+
$filter = [
465+
'page' => ($offset / $limit) + 1,
466+
'length' => $limit,
467+
'fileName' => $fileName,
468+
];
469+
470+
$sort = [
471+
'sortBy' => 'created_at',
472+
'sortDirection' => 'desc',
473+
];
474+
475+
$qb = $this->getFilesAssociatedFilesWithMeQueryBuilder($user->getUID(), $filter, false, $sort);
476+
477+
$result = $qb->executeQuery();
478+
$files = [];
479+
480+
while ($row = $result->fetch()) {
481+
try {
482+
$file = File::fromRow($row);
483+
484+
$files[] = $file;
485+
} catch (\Exception $e) {
486+
continue;
487+
}
488+
}
489+
490+
$result->closeCursor();
491+
492+
return $files;
493+
}
494+
463495
/**
464496
* @param array<SignRequest> $signRequests
465497
* @return FileElement[][]
@@ -533,7 +565,12 @@ public function getMyLibresignFile(string $userId, ?array $filter = []): File {
533565
return $file->fromRow($row);
534566
}
535567

536-
private function getFilesAssociatedFilesWithMeQueryBuilder(string $userId, array $filter = [], bool $count = false): IQueryBuilder {
568+
private function getFilesAssociatedFilesWithMeQueryBuilder(
569+
string $userId,
570+
array $filter = [],
571+
bool $count = false,
572+
array $sort = [],
573+
): IQueryBuilder {
537574
$qb = $this->db->getQueryBuilder();
538575
$qb->from('libresign_file', 'f')
539576
->leftJoin('f', 'libresign_sign_request', 'sr', 'sr.file_id = f.id')
@@ -644,26 +681,21 @@ private function getFilesAssociatedFilesWithMeQueryBuilder(string $userId, array
644681
$qb->expr()->lte('f.created_at', $qb->createNamedParameter($end, IQueryBuilder::PARAM_STR))
645682
);
646683
}
684+
if (!empty($filter['fileName'])) {
685+
$qb->andWhere(
686+
$qb->expr()->like('f.name', $qb->createNamedParameter('%' . $this->db->escapeLikeParameter($filter['fileName']) . '%'))
687+
);
688+
}
647689
if (!empty($filter['parentFileId'])) {
648690
$qb->andWhere(
649691
$qb->expr()->eq('f.parent_file_id', $qb->createNamedParameter($filter['parentFileId'], IQueryBuilder::PARAM_INT))
650692
);
651693
} else {
652694
$qb->andWhere($qb->expr()->isNull('f.parent_file_id'));
653695
}
654-
} else {
655-
$qb->andWhere($qb->expr()->isNull('f.parent_file_id'));
656696
}
657-
return $qb;
658-
}
659697

660-
private function getFilesAssociatedFilesWithMeStmt(
661-
string $userId,
662-
?array $filter = [],
663-
?array $sort = [],
664-
): Pagination {
665-
$qb = $this->getFilesAssociatedFilesWithMeQueryBuilder($userId, $filter);
666-
if (!empty($sort['sortBy'])) {
698+
if (!empty($sort['sortBy']) && !empty($sort['sortDirection'])) {
667699
switch ($sort['sortBy']) {
668700
case 'name':
669701
case 'status':
@@ -680,6 +712,16 @@ private function getFilesAssociatedFilesWithMeStmt(
680712
}
681713
}
682714

715+
return $qb;
716+
}
717+
718+
private function getFilesAssociatedFilesWithMeStmt(
719+
string $userId,
720+
?array $filter = [],
721+
?array $sort = [],
722+
): Pagination {
723+
$qb = $this->getFilesAssociatedFilesWithMeQueryBuilder($userId, $filter, false, $sort);
724+
683725
$countQb = $this->getFilesAssociatedFilesWithMeQueryBuilder(
684726
userId: $userId,
685727
filter: $filter,

lib/Search/FileSearchProvider.php

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2025 LibreCode coop and contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace OCA\Libresign\Search;
11+
12+
use OCA\Libresign\AppInfo\Application;
13+
use OCA\Libresign\Db\File;
14+
use OCA\Libresign\Db\SignRequestMapper;
15+
use OCP\App\IAppManager;
16+
use OCP\Files\IMimeTypeDetector;
17+
use OCP\Files\IRootFolder;
18+
use OCP\IL10N;
19+
use OCP\IURLGenerator;
20+
use OCP\IUser;
21+
use OCP\Search\IProvider;
22+
use OCP\Search\ISearchQuery;
23+
use OCP\Search\SearchResult;
24+
use OCP\Search\SearchResultEntry;
25+
26+
class FileSearchProvider implements IProvider {
27+
public function __construct(
28+
private IL10N $l10n,
29+
private IURLGenerator $urlGenerator,
30+
private IRootFolder $rootFolder,
31+
private IAppManager $appManager,
32+
private IMimeTypeDetector $mimeTypeDetector,
33+
private SignRequestMapper $fileMapper,
34+
) {
35+
}
36+
37+
#[\Override]
38+
public function getId(): string {
39+
return 'libresign_files';
40+
}
41+
42+
#[\Override]
43+
public function getName(): string {
44+
return $this->l10n->t('LibreSign documents');
45+
}
46+
47+
#[\Override]
48+
public function getOrder(string $route, array $routeParameters): int {
49+
if (strpos($route, Application::APP_ID . '.') === 0) {
50+
return 0;
51+
}
52+
return 10;
53+
}
54+
55+
#[\Override]
56+
public function search(IUser $user, ISearchQuery $query): SearchResult {
57+
if (!$this->appManager->isEnabledForUser(Application::APP_ID, $user)) {
58+
return SearchResult::complete($this->l10n->t('LibreSign documents'), []);
59+
}
60+
61+
$term = $query->getTerm();
62+
$limit = $query->getLimit();
63+
$offset = $query->getCursor();
64+
65+
try {
66+
$files = $this->fileMapper->getFilesToSearchProvider($user, $term, $limit, (int)$offset);
67+
} catch (\Exception $e) {
68+
return SearchResult::complete($this->l10n->t('LibreSign documents'), []);
69+
}
70+
71+
$results = array_map(function (File $file) use ($user) {
72+
return $this->formatResult($file, $user);
73+
}, $files);
74+
75+
return SearchResult::paginated(
76+
$this->l10n->t('LibreSign documents'),
77+
$results,
78+
$offset + $limit
79+
);
80+
}
81+
82+
private function formatResult(File $file): SearchResultEntry {
83+
$userFolder = $this->rootFolder->getUserFolder($file->getUserId());
84+
$thumbnailUrl = '';
85+
$subline = '';
86+
$icon = '';
87+
$path = '';
88+
89+
try {
90+
$nodes = $userFolder->getById($file->getNodeId());
91+
if (!empty($nodes)) {
92+
$node = array_shift($nodes);
93+
94+
$icon = $this->mimeTypeDetector->mimeTypeIcon($node->getMimetype());
95+
96+
$thumbnailUrl = $this->urlGenerator->linkToRouteAbsolute(
97+
'core.Preview.getPreviewByFileId',
98+
[
99+
'x' => 32,
100+
'y' => 32,
101+
'fileId' => $node->getId()
102+
]
103+
);
104+
105+
$path = $userFolder->getRelativePath($node->getPath());
106+
$subline = $this->formatSubline($path);
107+
}
108+
} catch (\Exception $e) {
109+
}
110+
111+
$link = $this->urlGenerator->linkToRoute(
112+
'files.View.showFile',
113+
['fileid' => $file->getNodeId()]
114+
);
115+
116+
$searchResultEntry = new SearchResultEntry(
117+
$thumbnailUrl,
118+
$file->getName(),
119+
$subline,
120+
$this->urlGenerator->getAbsoluteURL($link),
121+
$icon,
122+
);
123+
124+
$searchResultEntry->addAttribute('fileId', (string)$file->getNodeId());
125+
$searchResultEntry->addAttribute('path', $path);
126+
127+
return $searchResultEntry;
128+
}
129+
130+
private function formatSubline(string $path): string {
131+
if (strrpos($path, '/') > 0) {
132+
$path = ltrim(dirname($path), '/');
133+
// TRANSLATORS This string indicates the location of a file in a given path.
134+
return $this->l10n->t('in %s', [$path]);
135+
} else {
136+
return '';
137+
}
138+
}
139+
140+
}

0 commit comments

Comments
 (0)