• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

LibreSign / libresign / 20395363831

20 Dec 2025 01:56PM UTC coverage: 43.411%. First build
20395363831

Pull #6243

github

web-flow
Merge 278e4ba5f into fae2922ee
Pull Request #6243: feat: unified search

0 of 99 new or added lines in 1 file covered. (0.0%)

5936 of 13674 relevant lines covered (43.41%)

5.11 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/lib/Search/FileSearchProvider.php
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 OC\Files\FileInfo;
13
use OCA\Libresign\AppInfo\Application;
14
use OCA\Libresign\Db\File;
15
use OCP\App\IAppManager;
16
use OCP\Files\IMimeTypeDetector;
17
use OCP\Files\IRootFolder;
18
use OCP\IDBConnection;
19
use OCP\IL10N;
20
use OCP\IURLGenerator;
21
use OCP\IUser;
22
use OCP\Search\IProvider;
23
use OCP\Search\ISearchQuery;
24
use OCP\Search\SearchResult;
25
use OCP\Search\SearchResultEntry;
26

27
class FileSearchProvider implements IProvider {
28
        public function __construct(
29
                private IL10N $l10n,
30
                private IURLGenerator $urlGenerator,
31
                private IRootFolder $rootFolder,
32
                private IAppManager $appManager,
33
                private IDBConnection $db,
34
                private IMimeTypeDetector $mimeTypeDetector,
35
        ) {
NEW
36
        }
×
37

38
        public function getId(): string {
NEW
39
                return 'libresign_files';
×
40
        }
41

42
        public function getName(): string {
NEW
43
                return $this->l10n->t('LibreSign documents');
×
44
        }
45

46
        public function getOrder(string $route, array $routeParameters): int {
NEW
47
                if (strpos($route, Application::APP_ID . '.') === 0) {
×
NEW
48
                        return 0;
×
49
                }
NEW
50
                return 10;
×
51
        }
52

53
        public function search(IUser $user, ISearchQuery $query): SearchResult {
NEW
54
                if (!$this->appManager->isEnabledForUser(Application::APP_ID, $user)) {
×
NEW
55
                        return SearchResult::complete($this->l10n->t('LibreSign documents'), []);
×
56
                }
57

NEW
58
                $term = $query->getTerm();
×
NEW
59
                $limit = $query->getLimit();
×
NEW
60
                $offset = $query->getCursor();
×
61

62
                try {
NEW
63
                        $files = $this->searchFiles($user, $term, $limit, (int)$offset);
×
NEW
64
                } catch (\Exception $e) {
×
NEW
65
                        return SearchResult::complete($this->l10n->t('LibreSign documents'), []);
×
66
                }
67

NEW
68
                $results = array_map(function (File $file) use ($user) {
×
NEW
69
                        return $this->formatResult($file, $user);
×
NEW
70
                }, $files);
×
71

NEW
72
                return SearchResult::paginated(
×
NEW
73
                        $this->l10n->t('LibreSign documents'),
×
NEW
74
                        $results,
×
NEW
75
                        $offset + $limit
×
NEW
76
                );
×
77
        }
78

79
        /**
80
         * Search for LibreSign files matching the search term
81
         *
82
         * @param IUser $user Current user
83
         * @param string $term Search term
84
         * @param int $limit Maximum number of results
85
         * @param int $offset Offset for pagination
86
         * @return File[] Array of File entities
87
         */
88
        private function searchFiles(IUser $user, string $term, int $limit, int $offset): array {
NEW
89
                $qb = $this->db->getQueryBuilder();
×
90

NEW
91
                $qb->select('*')
×
NEW
92
                        ->from('libresign_file')
×
NEW
93
                        ->where($qb->expr()->eq('user_id', $qb->createNamedParameter($user->getUID())))
×
NEW
94
                        ->setMaxResults($limit)
×
NEW
95
                        ->setFirstResult($offset);
×
96

NEW
97
                if (!empty($term)) {
×
NEW
98
                        $qb->andWhere(
×
NEW
99
                                $qb->expr()->like('name', $qb->createNamedParameter('%' . $this->db->escapeLikeParameter($term) . '%'))
×
NEW
100
                        );
×
101
                }
102

NEW
103
                $qb->orderBy('created_at', 'DESC');
×
104

NEW
105
                $result = $qb->executeQuery();
×
NEW
106
                $files = [];
×
107

NEW
108
                while ($row = $result->fetch()) {
×
109
                        try {
NEW
110
                                $file = new File();
×
NEW
111
                                $file->setId((int)$row['id']);
×
NEW
112
                                $file->setUserId($row['user_id']);
×
NEW
113
                                $file->setNodeId((int)($row['node_id'] ?? 0));
×
NEW
114
                                $file->setName($row['name'] ?? '');
×
NEW
115
                                $file->setStatus((int)($row['status'] ?? 0));
×
NEW
116
                                $file->setCreatedAt($row['created_at'] ?? '');
×
NEW
117
                                $files[] = $file;
×
NEW
118
                        } catch (\Exception $e) {
×
NEW
119
                                continue;
×
120
                        }
121
                }
122

NEW
123
                $result->closeCursor();
×
124

NEW
125
                return $files;
×
126
        }
127

128
        /**
129
         * Format a File entity as a SearchResultEntry
130
         *
131
         * @param File $file The file entity to format
132
         * @param IUser $user Current user
133
         * @return SearchResultEntry Formatted search result entry
134
         */
135
        private function formatResult(File $file, IUser $user): SearchResultEntry {
NEW
136
                $userFolder = $this->rootFolder->getUserFolder($user->getUID());
×
NEW
137
                $thumbnailUrl = '';
×
NEW
138
                $subline = '';
×
NEW
139
                $icon = '';
×
NEW
140
                $path = '';
×
141

142
                try {
NEW
143
                        $nodes = $userFolder->getById($file->getNodeId());
×
NEW
144
                        if (!empty($nodes)) {
×
NEW
145
                                $node = array_shift($nodes);
×
146

NEW
147
                                $icon = $node->getMimetype() === FileInfo::MIMETYPE_FOLDER
×
NEW
148
                                        ? 'icon-folder'
×
NEW
149
                                        : $this->mimeTypeDetector->mimeTypeIcon($node->getMimetype());
×
150

NEW
151
                                $thumbnailUrl = $this->urlGenerator->linkToRouteAbsolute(
×
NEW
152
                                        'core.Preview.getPreviewByFileId',
×
NEW
153
                                        [
×
NEW
154
                                                'x' => 32,
×
NEW
155
                                                'y' => 32,
×
NEW
156
                                                'fileId' => $node->getId()
×
NEW
157
                                        ]
×
NEW
158
                                );
×
159

NEW
160
                                $path = $userFolder->getRelativePath($node->getPath());
×
NEW
161
                                $subline = $this->formatSubline($path);
×
162
                        }
NEW
163
                } catch (\Exception $e) {
×
164
                }
165

NEW
166
                $status = $this->getStatusLabel($file->getStatus());
×
NEW
167
                if ($status) {
×
NEW
168
                        $subline .= $subline ? ' • ' . $status : $status;
×
169
                }
170

NEW
171
                $link = $this->urlGenerator->linkToRoute(
×
NEW
172
                        'files.View.showFile',
×
NEW
173
                        ['fileid' => $file->getNodeId()]
×
NEW
174
                );
×
175

NEW
176
                $searchResultEntry = new SearchResultEntry(
×
NEW
177
                        $thumbnailUrl,
×
NEW
178
                        $file->getName() ?? $this->l10n->t('Unnamed document'),
×
NEW
179
                        $subline,
×
NEW
180
                        $this->urlGenerator->getAbsoluteURL($link),
×
NEW
181
                        $icon,
×
NEW
182
                );
×
183

NEW
184
                $searchResultEntry->addAttribute('fileId', (string)$file->getNodeId());
×
NEW
185
                $searchResultEntry->addAttribute('path', $path);
×
186

NEW
187
                return $searchResultEntry;
×
188
        }
189

190
        /**
191
         * Format the subline showing file location
192
         *
193
         * @param string $path File path
194
         * @return string Formatted subline text
195
         */
196
        private function formatSubline(string $path): string {
NEW
197
                if (strrpos($path, '/') > 0) {
×
NEW
198
                        $path = ltrim(dirname($path), '/');
×
NEW
199
                        return $this->l10n->t('in %s', [$path]);
×
200
                } else {
NEW
201
                        return '';
×
202
                }
203
        }
204

205
        /**
206
         * Get the translated label for a file status
207
         *
208
         * @param int|null $status File status code
209
         * @return string Translated status label
210
         */
211
        private function getStatusLabel(?int $status): string {
NEW
212
                return match ($status) {
×
NEW
213
                        File::STATUS_DRAFT => $this->l10n->t('Draft'),
×
NEW
214
                        File::STATUS_ABLE_TO_SIGN => $this->l10n->t('Able to sign'),
×
NEW
215
                        File::STATUS_PARTIAL_SIGNED => $this->l10n->t('Partially signed'),
×
NEW
216
                        File::STATUS_SIGNED => $this->l10n->t('Signed'),
×
NEW
217
                        File::STATUS_DELETED => $this->l10n->t('Deleted'),
×
NEW
218
                        default => '',
×
NEW
219
                };
×
220
        }
221
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc