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

LibreSign / libresign / 22048620506

16 Feb 2026 03:00AM UTC coverage: 51.605%. First build
22048620506

Pull #6891

github

web-flow
Merge 92e331da8 into 4cc0c7397
Pull Request #6891: feat: add id doc approver workflow

171 of 424 new or added lines in 24 files covered. (40.33%)

9145 of 17721 relevant lines covered (51.61%)

6.15 hits per line

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

39.85
/lib/Db/IdDocsMapper.php
1
<?php
2

3
declare(strict_types=1);
4
/**
5
 * SPDX-FileCopyrightText: 2020-2024 LibreCode coop and contributors
6
 * SPDX-License-Identifier: AGPL-3.0-or-later
7
 */
8

9
namespace OCA\Libresign\Db;
10

11
use OCA\Libresign\Enum\FileStatus;
12
use OCA\Libresign\Helper\Pagination;
13
use OCP\AppFramework\Db\QBMapper;
14
use OCP\DB\QueryBuilder\IQueryBuilder;
15
use OCP\DB\Types;
16
use OCP\IDBConnection;
17
use OCP\IL10N;
18
use OCP\IURLGenerator;
19

20
/**
21
 * Class FileMapper
22
 *
23
 * @package OCA\Libresign\DB
24
 * @template-extends QBMapper<IdDocs>
25
 */
26
class IdDocsMapper extends QBMapper {
27
        public function __construct(
28
                IDBConnection $db,
29
                private IURLGenerator $urlGenerator,
30
                private FileMapper $fileMapper,
31
                private SignRequestMapper $signRequestMapper,
32
                private FileTypeMapper $fileTypeMapper,
33
                private IL10N $l10n,
34
        ) {
35
                parent::__construct($db, 'libresign_id_docs');
57✔
36
        }
37

38
        public function save(int $fileId, ?int $signRequestId, ?string $userId, string $fileType): IdDocs {
39
                $idDocs = new IdDocs();
1✔
40
                $idDocs->setFileId($fileId);
1✔
41
                $idDocs->setSignRequestId($signRequestId);
1✔
42
                $idDocs->setUserId($userId);
1✔
43
                $idDocs->setFileType($fileType);
1✔
44
                return $this->insert($idDocs);
1✔
45
        }
46

47
        public function getByUserIdAndFileId(string $userId, int $fileId): IdDocs {
48
                $qb = $this->db->getQueryBuilder();
×
49
                $qb->select('*')
×
50
                        ->from($this->getTableName())
×
51
                        ->where($qb->expr()->eq('user_id', $qb->createNamedParameter($userId)))
×
52
                        ->andWhere($qb->expr()->eq('file_id', $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)));
×
53
                return $this->findEntity($qb);
×
54
        }
55

56
        /**
57
         * @return IdDocs[]
58
         */
59
        public function getByUserId(string $userId): array {
60
                $qb = $this->db->getQueryBuilder();
×
61
                $qb->select('*')
×
62
                        ->from($this->getTableName())
×
63
                        ->where($qb->expr()->eq('user_id', $qb->createNamedParameter($userId)));
×
64
                return $this->findEntities($qb);
×
65
        }
66

67
        public function getByUserIdAndNodeId(string $userId, int $nodeId): IdDocs {
68
                $qb = $this->db->getQueryBuilder();
×
69
                $qb->select('id.*')
×
70
                        ->from($this->getTableName(), 'id')
×
71
                        ->join('id', 'libresign_file', 'f', 'f.id = id.file_id')
×
72
                        ->where($qb->expr()->eq('id.user_id', $qb->createNamedParameter($userId)))
×
73
                        ->andWhere($qb->expr()->eq('f.node_id', $qb->createNamedParameter($nodeId, IQueryBuilder::PARAM_INT)));
×
74
                return $this->findEntity($qb);
×
75
        }
76

77
        public function getByNodeId(int $nodeId): IdDocs {
NEW
78
                $qb = $this->db->getQueryBuilder();
×
NEW
79
                $qb->select('id.*')
×
NEW
80
                        ->from($this->getTableName(), 'id')
×
NEW
81
                        ->join('id', 'libresign_file', 'f', 'f.id = id.file_id')
×
NEW
82
                        ->where(
×
NEW
83
                                $qb->expr()->orX(
×
NEW
84
                                        $qb->expr()->eq('f.node_id', $qb->createNamedParameter($nodeId, IQueryBuilder::PARAM_INT)),
×
NEW
85
                                        $qb->expr()->eq('f.signed_node_id', $qb->createNamedParameter($nodeId, IQueryBuilder::PARAM_INT))
×
NEW
86
                                )
×
NEW
87
                        );
×
NEW
88
                return $this->findEntity($qb);
×
89
        }
90

91
        public function getBySignRequestIdAndNodeId(int $signRequestId, int $nodeId): IdDocs {
NEW
92
                $qb = $this->db->getQueryBuilder();
×
NEW
93
                $qb->select('id.*')
×
NEW
94
                        ->from($this->getTableName(), 'id')
×
NEW
95
                        ->join('id', 'libresign_file', 'f', 'f.id = id.file_id')
×
NEW
96
                        ->where($qb->expr()->eq('id.sign_request_id', $qb->createNamedParameter($signRequestId, IQueryBuilder::PARAM_INT)))
×
NEW
97
                        ->andWhere($qb->expr()->eq('f.node_id', $qb->createNamedParameter($nodeId, IQueryBuilder::PARAM_INT)));
×
NEW
98
                return $this->findEntity($qb);
×
99
        }
100

101
        public function getByFileId(int $fileId): IdDocs {
102
                $qb = $this->db->getQueryBuilder();
×
103
                $qb->select('*')
×
104
                        ->from($this->getTableName())
×
105
                        ->where($qb->expr()->eq('file_id', $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)));
×
106
                return $this->findEntity($qb);
×
107
        }
108

109
        public function deleteByFileId(int $fileId): void {
110
                $qb = $this->db->getQueryBuilder();
×
111
                $qb->delete($this->getTableName())
×
112
                        ->where($qb->expr()->eq('file_id', $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)));
×
113
                $qb->executeStatement();
×
114
        }
115

116
        public function getByUserAndType(string $userId, string $fileType): ?IdDocs {
117
                $qb = $this->db->getQueryBuilder();
1✔
118
                $qb->select('*')
1✔
119
                        ->from($this->getTableName())
1✔
120
                        ->where($qb->expr()->eq('user_id', $qb->createNamedParameter($userId)))
1✔
121
                        ->andWhere($qb->expr()->eq('file_type', $qb->createNamedParameter($fileType)));
1✔
122
                try {
123
                        return $this->findEntity($qb);
1✔
124
                } catch (\Throwable) {
1✔
125
                        return null;
1✔
126
                }
127
        }
128

129
        public function list(array $filter, ?int $page = null, ?int $length = null, array $sort = []): array {
130
                $filter['length'] = $length;
1✔
131
                $filter['page'] = $page;
1✔
132
                $filter['sort'] = $sort;
1✔
133
                $pagination = $this->getDocs($filter);
1✔
134
                $pagination->setMaxPerPage($length);
1✔
135
                $pagination->setCurrentPage($page);
1✔
136
                $currentPageResults = $pagination->getCurrentPageResults();
1✔
137

138
                $url = $this->urlGenerator->linkToRoute('libresign.page.getPdf', ['uuid' => '_replace_']);
1✔
139
                $url = str_replace('_replace_', '', $url);
1✔
140

141
                $data = [];
1✔
142
                $fileIds = [];
1✔
143

144
                foreach ($currentPageResults as $row) {
1✔
145
                        $fileIds[] = $row['id'];
×
146
                        $data[] = $this->formatListRow($row, $url);
×
147
                }
148
                $signers = $this->signRequestMapper->getByMultipleFileId($fileIds);
1✔
149
                $return['data'] = $this->assocFileToSignRequestAndFormat($data, $signers);
1✔
150
                $return['pagination'] = $pagination;
1✔
151
                return $return;
1✔
152
        }
153

154
        private function getQueryBuilder(array $filter = [], bool $count = false): IQueryBuilder {
155
                $qb = $this->db->getQueryBuilder();
1✔
156

157
                $needsUserJoin = !$count || !empty($filter['userId']) || (!empty($filter['sort']) && isset($filter['sort']['owner']));
1✔
158

159
                if ($count) {
1✔
160
                        $qb->select($qb->func()->count());
1✔
161
                } else {
162
                        $qb->select(
1✔
163
                                'f.id',
1✔
164
                                'f.uuid',
1✔
165
                                'f.name',
1✔
166
                                'f.callback',
1✔
167
                                'f.status',
1✔
168
                                'f.node_id',
1✔
169
                                'f.user_id',
1✔
170
                                'id.file_type',
1✔
171
                                'id.sign_request_id',
1✔
172
                                'f.created_at',
1✔
173
                        );
1✔
174

175
                        $qb->selectAlias('sr.display_name', 'sign_request_display_name');
1✔
176

177
                        if ($needsUserJoin) {
1✔
178
                                $qb->selectAlias('u.uid_lower', 'account_uid')
1✔
179
                                        ->selectAlias('u.displayname', 'account_displayname');
1✔
180
                        }
181

182
                        $qb->selectAlias(
1✔
183
                                $qb->createFunction(
1✔
184
                                        'CASE WHEN u.displayname IS NOT NULL THEN u.displayname ELSE sr.display_name END'
1✔
185
                                ),
1✔
186
                                'owner_display_name'
1✔
187
                        );
1✔
188
                }
189

190
                $qb->from($this->getTableName(), 'id')
1✔
191
                        ->join('id', 'libresign_file', 'f', 'f.id = id.file_id')
1✔
192
                        ->leftJoin('id', 'libresign_sign_request', 'sr', 'id.sign_request_id = sr.id');
1✔
193

194
                if ($needsUserJoin) {
1✔
195
                        $joinType = !empty($filter['userId']) ? 'join' : 'leftJoin';
1✔
196
                        $qb->$joinType('id', 'users', 'u', 'id.user_id = u.uid');
1✔
197
                }
198

199
                if (!empty($filter['userId'])) {
1✔
200
                        $qb->where(
×
NEW
201
                                $qb->expr()->eq('id.user_id', $qb->createNamedParameter($filter['userId']))
×
NEW
202
                        );
×
203
                }
204

205
                if (!empty($filter['signRequestId'])) {
1✔
NEW
206
                        $qb->andWhere(
×
NEW
207
                                $qb->expr()->eq('id.sign_request_id', $qb->createNamedParameter($filter['signRequestId'], IQueryBuilder::PARAM_INT))
×
NEW
208
                        );
×
209
                }
210

211
                if (!empty($filter['approved']) && $filter['approved'] === 'yes') {
1✔
NEW
212
                        $qb->andWhere(
×
NEW
213
                                $qb->expr()->eq('f.status', $qb->createNamedParameter(FileStatus::SIGNED->value, Types::INTEGER))
×
NEW
214
                        );
×
215
                }
216

217
                if (!$count) {
1✔
218
                        $qb->groupBy(
1✔
219
                                'f.id',
1✔
220
                                'f.uuid',
1✔
221
                                'f.name',
1✔
222
                                'f.callback',
1✔
223
                                'f.status',
1✔
224
                                'f.node_id',
1✔
225
                                'f.user_id',
1✔
226
                                'id.sign_request_id',
1✔
227
                                'sr.display_name',
1✔
228
                                'f.created_at',
1✔
229
                                'id.file_type',
1✔
230
                        );
1✔
231

232
                        if ($needsUserJoin) {
1✔
233
                                $qb->addGroupBy('u.uid_lower')
1✔
234
                                        ->addGroupBy('u.displayname');
1✔
235
                        }
236

237
                        $qb->addGroupBy('owner_display_name');
1✔
238
                }
239

240
                if (!$count) {
1✔
241
                        if (!empty($filter['sort'])) {
1✔
NEW
242
                                foreach ($filter['sort'] as $field => $direction) {
×
NEW
243
                                        $direction = strtoupper($direction) === 'ASC' ? 'ASC' : 'DESC';
×
244

245
                                        switch ($field) {
NEW
246
                                                case 'owner':
×
NEW
247
                                                        $qb->addOrderBy('owner_display_name', $direction);
×
NEW
248
                                                        break;
×
NEW
249
                                                case 'file_type':
×
NEW
250
                                                        $qb->addOrderBy('id.file_type', $direction);
×
NEW
251
                                                        break;
×
NEW
252
                                                case 'status':
×
NEW
253
                                                        $qb->addOrderBy('f.status', $direction);
×
NEW
254
                                                        break;
×
NEW
255
                                                case 'created_at':
×
NEW
256
                                                        $qb->addOrderBy('f.created_at', $direction);
×
NEW
257
                                                        break;
×
258
                                        }
259
                                }
260
                        } else {
261
                                $qb->orderBy('f.created_at', 'DESC');
1✔
262
                        }
263
                }
264

265
                if ($count) {
1✔
266
                        $qb->setFirstResult(0)->setMaxResults(null);
1✔
267
                } elseif (isset($filter['length'], $filter['page'])) {
1✔
268
                        $qb->setFirstResult($filter['length'] * ($filter['page'] - 1))
1✔
269
                                ->setMaxResults($filter['length']);
1✔
270
                }
271

272
                return $qb;
1✔
273
        }
274

275
        private function getDocs(array $filter = []): Pagination {
276
                $qb = $this->getQueryBuilder(
1✔
277
                        filter: $filter,
1✔
278
                );
1✔
279
                $countQb = $this->getQueryBuilder(
1✔
280
                        filter: $filter,
1✔
281
                        count: true,
1✔
282
                );
1✔
283

284
                $pagination = new Pagination($qb, $this->urlGenerator, $countQb);
1✔
285
                return $pagination;
1✔
286
        }
287

288
        private function formatListRow(array $row, string $url): array {
NEW
289
                $createdAt = (new \DateTime())
×
290
                        ->setTimestamp((int)$row['created_at'])
×
291
                        ->format('Y-m-d H:i:s');
×
292

NEW
293
                $userId = $row['user_id'] ?? null;
×
NEW
294
                $displayName = $row['account_displayname'] ?? $row['sign_request_display_name'] ?? $userId ?? '';
×
295

NEW
296
                return [
×
NEW
297
                        'account' => [
×
NEW
298
                                'userId' => $userId,
×
NEW
299
                                'displayName' => $displayName,
×
NEW
300
                        ],
×
NEW
301
                        'file_type' => [
×
NEW
302
                                'type' => $row['file_type'],
×
NEW
303
                                'name' => $this->fileTypeMapper->getNameOfType($row['file_type']),
×
NEW
304
                                'description' => $this->fileTypeMapper->getDescriptionOfType($row['file_type']),
×
NEW
305
                        ],
×
NEW
306
                        'created_at' => $createdAt,
×
307
                        'file' => [
×
NEW
308
                                'name' => $row['name'],
×
NEW
309
                                'status' => (int)$row['status'],
×
NEW
310
                                'statusText' => $this->getIdDocStatusText((int)$row['status']),
×
NEW
311
                                'created_at' => $createdAt,
×
NEW
312
                                'user_id' => $row['user_id'],
×
NEW
313
                                'file' => [
×
NEW
314
                                        'type' => 'pdf',
×
NEW
315
                                        'nodeId' => (int)$row['node_id'],
×
NEW
316
                                        'signedNodeId' => (int)$row['node_id'],
×
NEW
317
                                        'url' => $url . $row['uuid'],
×
NEW
318
                                ],
×
NEW
319
                                'callback' => $row['callback'],
×
NEW
320
                                'uuid' => $row['uuid'],
×
NEW
321
                                'signers' => [],
×
322
                        ],
×
NEW
323
                        'id' => (int)$row['id'],
×
324
                ];
×
325
        }
326

327
        /**
328
         * @param array $files
329
         * @param SignRequest[] $signers
330
         */
331
        private function assocFileToSignRequestAndFormat(array $files, array $signers): array {
332
                foreach ($files as $key => $file) {
1✔
333
                        $totalSigned = 0;
×
334
                        $files[$key]['file']['signers'] = [];
×
335
                        foreach ($signers as $signerKey => $signer) {
×
336
                                if ($signer->getFileId() === $file['id']) {
×
337
                                        $data = [
×
338
                                                'description' => $signer->getDescription(),
×
339
                                                'displayName' => $signer->getDisplayName(),
×
NEW
340
                                                'uid' => $file['file']['user_id'] ?? null,
×
341
                                                'request_sign_date' => (new \DateTime())
×
342
                                                        ->setTimestamp($signer->getCreatedAt()->getTimestamp())
×
343
                                                        ->format('Y-m-d H:i:s'),
×
344
                                                'sign_date' => null,
×
345
                                                'signRequestId' => $signer->getId(),
×
346
                                                'status' => $signer->getStatus(),
×
347
                                                'statusText' => $this->signRequestMapper->getTextOfSignerStatus($signer->getStatus()),
×
348
                                        ];
×
349
                                        if ($signer->getSigned()) {
×
350
                                                $data['sign_date'] = (new \DateTime())
×
351
                                                        ->setTimestamp($signer->getSigned()->getTimestamp())
×
352
                                                        ->format('Y-m-d H:i:s');
×
353
                                                $totalSigned++;
×
354
                                        }
355
                                        $files[$key]['file']['signers'][] = $data;
×
356
                                        unset($signers[$signerKey]);
×
357
                                }
358
                        }
359
                        unset($files[$key]['id']);
×
360
                }
361
                return $files;
1✔
362
        }
363

364
        /**
365
         * Get all identification document files for a specific user account
366
         *
367
         * @param string $userId The user identifier
368
         * @return File[] Array of File entities associated with the user's identification documents
369
         */
370
        public function getFilesOfAccount(string $userId): array {
NEW
371
                $qb = $this->db->getQueryBuilder();
×
372

NEW
373
                $qb->select('lf.*')
×
NEW
374
                        ->from('libresign_file', 'lf')
×
NEW
375
                        ->join('lf', $this->getTableName(), 'lid', 'lid.file_id = lf.id')
×
NEW
376
                        ->where(
×
NEW
377
                                $qb->expr()->eq('lid.user_id', $qb->createNamedParameter($userId))
×
NEW
378
                        );
×
379

NEW
380
                $cursor = $qb->executeQuery();
×
NEW
381
                $return = [];
×
NEW
382
                while ($row = $cursor->fetch()) {
×
383
                        /** @var File */
NEW
384
                        $file = $this->fileMapper->mapRowToEntity($row);
×
NEW
385
                        $return[] = $file;
×
386
                }
NEW
387
                return $return;
×
388
        }
389

390
        /**
391
         * Get all identification document files for a specific sign request
392
         *
393
         * @param int $signRequestId The sign request identifier
394
         * @return File[] Array of File entities associated with the sign request's identification documents
395
         */
396
        public function getFilesOfSignRequest(int $signRequestId): array {
NEW
397
                $qb = $this->db->getQueryBuilder();
×
398

NEW
399
                $qb->select('lf.*')
×
NEW
400
                        ->from('libresign_file', 'lf')
×
NEW
401
                        ->join('lf', $this->getTableName(), 'lid', 'lid.file_id = lf.id')
×
NEW
402
                        ->where(
×
NEW
403
                                $qb->expr()->eq('lid.sign_request_id', $qb->createNamedParameter($signRequestId, IQueryBuilder::PARAM_INT))
×
NEW
404
                        );
×
405

NEW
406
                $cursor = $qb->executeQuery();
×
NEW
407
                $return = [];
×
NEW
408
                while ($row = $cursor->fetch()) {
×
409
                        /** @var File */
NEW
410
                        $file = $this->fileMapper->mapRowToEntity($row);
×
NEW
411
                        $return[] = $file;
×
412
                }
NEW
413
                return $return;
×
414
        }
415

416
        private function getIdDocStatusText(int $status): string {
417
                return match ($status) {
418
                        FileStatus::ABLE_TO_SIGN->value => $this->l10n->t('waiting for approval'),
×
419
                        FileStatus::SIGNED->value => $this->l10n->t('approved'),
×
420
                        default => $this->fileMapper->getTextOfStatus($status),
×
421
                };
422
        }
423
}
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