Skip to content

Commit f8139f6

Browse files
authored
Merge pull request #6514 from LibreSign/backport/6243/stable32
[stable32] feat: unified search
2 parents 43ad72b + 1efcb8e commit f8139f6

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;
@@ -55,6 +56,8 @@ public function register(IRegistrationContext $context): void {
5556

5657
$context->registerNotifierService(Notifier::class);
5758

59+
$context->registerSearchProvider(FileSearchProvider::class);
60+
5861
$context->registerEventListener(LoadSidebar::class, TemplateLoader::class);
5962
$context->registerEventListener(BeforeNodeDeletedEvent::class, BeforeNodeDeletedListener::class);
6063
$context->registerEventListener(CacheEntryRemovedEvent::class, BeforeNodeDeletedListener::class);

lib/Db/SignRequestMapper.php

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

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

535-
private function getFilesAssociatedFilesWithMeQueryBuilder(string $userId, array $filter = [], bool $count = false): IQueryBuilder {
567+
private function getFilesAssociatedFilesWithMeQueryBuilder(
568+
string $userId,
569+
array $filter = [],
570+
bool $count = false,
571+
array $sort = [],
572+
): IQueryBuilder {
536573
$qb = $this->db->getQueryBuilder();
537574
$qb->from('libresign_file', 'f')
538575
->leftJoin('f', 'libresign_sign_request', 'sr', 'sr.file_id = f.id')
@@ -643,26 +680,21 @@ private function getFilesAssociatedFilesWithMeQueryBuilder(string $userId, array
643680
$qb->expr()->lte('f.created_at', $qb->createNamedParameter($end, IQueryBuilder::PARAM_STR))
644681
);
645682
}
683+
if (!empty($filter['fileName'])) {
684+
$qb->andWhere(
685+
$qb->expr()->like('f.name', $qb->createNamedParameter('%' . $this->db->escapeLikeParameter($filter['fileName']) . '%'))
686+
);
687+
}
646688
if (!empty($filter['parentFileId'])) {
647689
$qb->andWhere(
648690
$qb->expr()->eq('f.parent_file_id', $qb->createNamedParameter($filter['parentFileId'], IQueryBuilder::PARAM_INT))
649691
);
650692
} else {
651693
$qb->andWhere($qb->expr()->isNull('f.parent_file_id'));
652694
}
653-
} else {
654-
$qb->andWhere($qb->expr()->isNull('f.parent_file_id'));
655695
}
656-
return $qb;
657-
}
658696

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

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