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

LibreSign / libresign / 19867129312

02 Dec 2025 05:10PM UTC coverage: 41.519%. First build
19867129312

Pull #4464

github

web-flow
Merge 4521e389d into 198609762
Pull Request #4464: refactor: attach document

85 of 242 new or added lines in 14 files covered. (35.12%)

5099 of 12281 relevant lines covered (41.52%)

4.18 hits per line

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

52.42
/lib/Service/FileService.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\Service;
10

11
use DateTime;
12
use DateTimeInterface;
13
use InvalidArgumentException;
14
use OC\Files\Filesystem;
15
use OCA\Libresign\AppInfo\Application;
16
use OCA\Libresign\Db\File;
17
use OCA\Libresign\Db\FileElement;
18
use OCA\Libresign\Db\FileElementMapper;
19
use OCA\Libresign\Db\FileMapper;
20
use OCA\Libresign\Db\IdentifyMethod;
21
use OCA\Libresign\Db\SignRequest;
22
use OCA\Libresign\Db\SignRequestMapper;
23
use OCA\Libresign\Exception\LibresignException;
24
use OCA\Libresign\Handler\SignEngine\Pkcs12Handler;
25
use OCA\Libresign\Helper\ValidateHelper;
26
use OCA\Libresign\ResponseDefinitions;
27
use OCA\Libresign\Service\IdentifyMethod\IIdentifyMethod;
28
use OCP\Accounts\IAccountManager;
29
use OCP\AppFramework\Db\DoesNotExistException;
30
use OCP\Files\IMimeTypeDetector;
31
use OCP\Files\IRootFolder;
32
use OCP\Files\NotFoundException;
33
use OCP\Http\Client\IClientService;
34
use OCP\IAppConfig;
35
use OCP\IDateTimeFormatter;
36
use OCP\IL10N;
37
use OCP\IURLGenerator;
38
use OCP\IUser;
39
use OCP\IUserManager;
40
use OCP\IUserSession;
41
use Psr\Log\LoggerInterface;
42
use stdClass;
43

44
/**
45
 * @psalm-import-type LibresignValidateFile from ResponseDefinitions
46
 */
47
class FileService {
48
        use TFile;
49

50
        private bool $showSigners = false;
51
        private bool $showSettings = false;
52
        private bool $showVisibleElements = false;
53
        private bool $showMessages = false;
54
        private bool $validateFile = false;
55
        private bool $signersLibreSignLoaded = false;
56
        private bool $signerIdentified = false;
57
        private string $fileContent = '';
58
        private string $host = '';
59
        private ?File $file = null;
60
        private ?SignRequest $signRequest = null;
61
        private ?IUser $me = null;
62
        private ?int $identifyMethodId = null;
63
        private array $certData = [];
64
        private stdClass $fileData;
65
        public const IDENTIFICATION_DOCUMENTS_DISABLED = 0;
66
        public const IDENTIFICATION_DOCUMENTS_NEED_SEND = 1;
67
        public const IDENTIFICATION_DOCUMENTS_NEED_APPROVAL = 2;
68
        public const IDENTIFICATION_DOCUMENTS_APPROVED = 3;
69
        public function __construct(
70
                protected FileMapper $fileMapper,
71
                protected SignRequestMapper $signRequestMapper,
72
                protected FileElementMapper $fileElementMapper,
73
                protected FileElementService $fileElementService,
74
                protected FolderService $folderService,
75
                protected ValidateHelper $validateHelper,
76
                protected PdfParserService $pdfParserService,
77
                private AccountService $accountService,
78
                private IdentifyMethodService $identifyMethodService,
79
                private IUserSession $userSession,
80
                private IUserManager $userManager,
81
                private IAccountManager $accountManager,
82
                protected IClientService $client,
83
                private IDateTimeFormatter $dateTimeFormatter,
84
                private IAppConfig $appConfig,
85
                private IURLGenerator $urlGenerator,
86
                protected IMimeTypeDetector $mimeTypeDetector,
87
                protected Pkcs12Handler $pkcs12Handler,
88
                private IRootFolder $root,
89
                protected LoggerInterface $logger,
90
                protected IL10N $l10n,
91
        ) {
92
                $this->fileData = new stdClass();
30✔
93
        }
94

95
        /**
96
         * @return static
97
         */
98
        public function showSigners(bool $show = true): self {
99
                $this->showSigners = $show;
5✔
100
                return $this;
5✔
101
        }
102

103
        /**
104
         * @return static
105
         */
106
        public function showSettings(bool $show = true): self {
107
                $this->showSettings = $show;
4✔
108
                if ($show) {
4✔
109
                        $this->fileData->settings = [
4✔
110
                                'canSign' => false,
4✔
111
                                'canRequestSign' => false,
4✔
112
                                'signerFileUuid' => null,
4✔
113
                                'phoneNumber' => '',
4✔
114
                        ];
4✔
115
                } else {
116
                        unset($this->fileData->settings);
×
117
                }
118
                return $this;
4✔
119
        }
120

121
        /**
122
         * @return static
123
         */
124
        public function showVisibleElements(bool $show = true): self {
125
                $this->showVisibleElements = $show;
4✔
126
                return $this;
4✔
127
        }
128

129
        /**
130
         * @return static
131
         */
132
        public function showMessages(bool $show = true): self {
133
                $this->showMessages = $show;
4✔
134
                return $this;
4✔
135
        }
136

137
        /**
138
         * @return static
139
         */
140
        public function setMe(?IUser $user): self {
141
                $this->me = $user;
5✔
142
                return $this;
5✔
143
        }
144

145
        public function setSignerIdentified(bool $identified = true): self {
NEW
146
                $this->signerIdentified = $identified;
×
NEW
147
                return $this;
×
148
        }
149

150
        public function setIdentifyMethodId(?int $id): self {
151
                $this->identifyMethodId = $id;
2✔
152
                return $this;
2✔
153
        }
154

155
        public function setHost(string $host): self {
156
                $this->host = $host;
4✔
157
                return $this;
4✔
158
        }
159

160
        /**
161
         * @return static
162
         */
163
        public function setFile(File $file): self {
164
                $this->file = $file;
4✔
165
                $this->fileData->status = $this->file->getStatus();
4✔
166
                return $this;
4✔
167
        }
168

169
        public function setSignRequest(SignRequest $signRequest): self {
170
                $this->signRequest = $signRequest;
×
171
                return $this;
×
172
        }
173

174
        public function showValidateFile(bool $validateFile = true): self {
175
                $this->validateFile = $validateFile;
2✔
176
                return $this;
2✔
177
        }
178

179
        /**
180
         * @return static
181
         */
182
        public function setFileByType(string $type, $identifier): self {
183
                try {
184
                        /** @var File */
185
                        $file = call_user_func(
4✔
186
                                [$this->fileMapper, 'getBy' . $type],
4✔
187
                                $identifier
4✔
188
                        );
4✔
189
                } catch (\Throwable) {
2✔
190
                        throw new LibresignException($this->l10n->t('Invalid data to validate file'), 404);
2✔
191
                }
192
                if (!$file) {
2✔
193
                        throw new LibresignException($this->l10n->t('Invalid file identifier'), 404);
×
194
                }
195
                $this->setFile($file);
2✔
196
                return $this;
2✔
197
        }
198

199
        public function setFileFromRequest(?array $file): self {
200
                if ($file === null) {
8✔
201
                        throw new InvalidArgumentException($this->l10n->t('No file provided'));
1✔
202
                }
203
                if (
204
                        $file['error'] !== 0
7✔
205
                        || !is_uploaded_file($file['tmp_name'])
6✔
206
                        || Filesystem::isFileBlacklisted($file['tmp_name'])
7✔
207
                ) {
208
                        unlink($file['tmp_name']);
2✔
209
                        throw new InvalidArgumentException($this->l10n->t('Invalid file provided'));
2✔
210
                }
211
                if ($file['size'] > \OCP\Util::uploadLimit()) {
5✔
212
                        unlink($file['tmp_name']);
1✔
213
                        throw new InvalidArgumentException($this->l10n->t('File is too big'));
1✔
214
                }
215

216
                $this->fileContent = file_get_contents($file['tmp_name']);
4✔
217
                $mimeType = $this->mimeTypeDetector->detectString($this->fileContent);
4✔
218
                if ($mimeType !== 'application/pdf') {
4✔
219
                        $this->fileContent = '';
1✔
220
                        unlink($file['tmp_name']);
1✔
221
                        throw new InvalidArgumentException($this->l10n->t('Invalid file provided'));
1✔
222
                }
223
                $this->fileData->size = $file['size'];
3✔
224

225
                $memoryFile = fopen($file['tmp_name'], 'rb');
3✔
226
                try {
227
                        $this->certData = $this->pkcs12Handler->getCertificateChain($memoryFile);
3✔
228
                        $this->fileData->status = File::STATUS_SIGNED;
2✔
229
                        // Ignore when isnt a signed file
230
                } catch (LibresignException) {
1✔
231
                        $this->fileData->status = File::STATUS_DRAFT;
1✔
232
                }
233
                fclose($memoryFile);
3✔
234
                unlink($file['tmp_name']);
3✔
235
                $this->fileData->hash = hash('sha256', $this->fileContent);
3✔
236
                try {
237
                        $libresignFile = $this->fileMapper->getBySignedHash($this->fileData->hash);
3✔
238
                        $this->setFile($libresignFile);
×
239
                } catch (DoesNotExistException) {
3✔
240
                        $this->fileData->status = File::STATUS_NOT_LIBRESIGN_FILE;
3✔
241
                }
242
                $this->fileData->name = $file['name'];
3✔
243
                return $this;
3✔
244
        }
245

246
        private function getFile(): \OCP\Files\File {
247
                $nodeId = $this->file->getSignedNodeId();
4✔
248
                if (!$nodeId) {
4✔
249
                        $nodeId = $this->file->getNodeId();
4✔
250
                }
251
                $fileToValidate = $this->root->getUserFolder($this->file->getUserId())->getFirstNodeById($nodeId);
4✔
252
                if (!$fileToValidate instanceof \OCP\Files\File) {
4✔
253
                        throw new LibresignException($this->l10n->t('File not found'), 404);
×
254
                }
255
                return $fileToValidate;
4✔
256
        }
257

258
        private function getFileContent(): string {
259
                if ($this->fileContent) {
8✔
260
                        return $this->fileContent;
3✔
261
                } elseif ($this->file) {
5✔
262
                        try {
263
                                return $this->fileContent = $this->getFile()->getContent();
4✔
264
                        } catch (LibresignException $e) {
×
265
                                throw new LibresignException($e->getMessage(), 404);
×
266
                        } catch (\Throwable) {
×
267
                                throw new LibresignException($this->l10n->t('Invalid data to validate file'), 404);
×
268
                        }
269
                }
270
                return '';
1✔
271
        }
272

273
        private function loadFileMetadata(): void {
274
                if (!$content = $this->getFileContent()) {
8✔
275
                        return;
1✔
276
                }
277
                $pdfParserService = $this->pdfParserService->setFile($content);
7✔
278
                if ($this->file) {
7✔
279
                        $metadata = $this->file->getMetadata();
4✔
280
                }
281
                if (isset($metadata) && isset($metadata['p'])) {
7✔
282
                        $dimensions = $metadata;
4✔
283
                } else {
284
                        $dimensions = $pdfParserService->getPageDimensions();
3✔
285
                }
286
                $this->fileData->totalPages = $dimensions['p'];
7✔
287
                $this->fileData->size = strlen($content);
7✔
288
                $this->fileData->pdfVersion = $pdfParserService->getPdfVersion();
7✔
289
        }
290

291
        private function loadCertDataFromLibreSignFile(): void {
292
                if (!empty($this->certData) || !$this->validateFile || !$this->file || !$this->file->getSignedNodeId()) {
5✔
293
                        return;
5✔
294
                }
295
                $file = $this->getFile();
×
296

297
                $resource = $file->fopen('rb');
×
298
                $sha256 = $this->getSha256FromResource($resource);
×
299
                if ($sha256 === $this->file->getSignedHash()) {
×
300
                        $this->pkcs12Handler->setIsLibreSignFile();
×
301
                }
302
                $this->certData = $this->pkcs12Handler->getCertificateChain($resource);
×
303
                fclose($resource);
×
304
        }
305

306
        private function getSha256FromResource($resource): string {
307
                $hashContext = hash_init('sha256');
×
308
                while (!feof($resource)) {
×
309
                        $buffer = fread($resource, 8192); // 8192 bytes = 8 KB
×
310
                        hash_update($hashContext, $buffer);
×
311
                }
312
                return hash_final($hashContext);
×
313
        }
314

315
        private function loadLibreSignSigners(): void {
316
                if ($this->signersLibreSignLoaded || !$this->file) {
5✔
317
                        return;
3✔
318
                }
319
                $signers = $this->signRequestMapper->getByFileId($this->file->getId());
4✔
320
                foreach ($signers as $signer) {
4✔
321
                        $identifyMethods = $this->identifyMethodService
4✔
322
                                ->setIsRequest(false)
4✔
323
                                ->getIdentifyMethodsFromSignRequestId($signer->getId());
4✔
324
                        if (!empty($this->fileData->signers)) {
4✔
325
                                $found = array_filter($this->fileData->signers, function ($found) use ($identifyMethods) {
×
326
                                        if (!isset($found['uid'])) {
×
327
                                                return false;
×
328
                                        }
329
                                        [$key, $value] = explode(':', (string)$found['uid']);
×
330
                                        foreach ($identifyMethods as $methods) {
×
331
                                                foreach ($methods as $identifyMethod) {
×
332
                                                        $entity = $identifyMethod->getEntity();
×
333
                                                        if ($key === $entity->getIdentifierKey() && $value === $entity->getIdentifierValue()) {
×
334
                                                                return true;
×
335
                                                        }
336
                                                }
337
                                        }
338
                                        return false;
×
339
                                });
×
340
                                if (!empty($found)) {
×
341
                                        $index = key($found);
×
342
                                } else {
343
                                        $totalSigners = count($signers);
×
344
                                        $totalCert = count($this->certData);
×
345
                                        // When only have a signature, consider that who signed is who need to sign
346
                                        if ($totalCert === 1 && $totalSigners === $totalCert) {
×
347
                                                $index = 0;
×
348
                                        } else {
349
                                                $index = count($this->fileData->signers);
×
350
                                        }
351
                                }
352
                        } else {
353
                                $index = 0;
4✔
354
                        }
355
                        $this->fileData->signers[$index]['identifyMethods'] = $identifyMethods;
4✔
356
                        $this->fileData->signers[$index]['displayName'] = $signer->getDisplayName();
4✔
357
                        $this->fileData->signers[$index]['me'] = false;
4✔
358
                        $this->fileData->signers[$index]['signRequestId'] = $signer->getId();
4✔
359
                        $this->fileData->signers[$index]['description'] = $signer->getDescription();
4✔
360
                        $this->fileData->signers[$index]['visibleElements'] = $this->getVisibleElements($signer->getId());
4✔
361
                        $this->fileData->signers[$index]['request_sign_date'] = $signer->getCreatedAt()->format(DateTimeInterface::ATOM);
4✔
362
                        if (empty($this->fileData->signers[$index]['signed'])) {
4✔
363
                                if ($signer->getSigned()) {
4✔
364
                                        $this->fileData->signers[$index]['signed'] = $signer->getSigned()->format(DateTimeInterface::ATOM);
×
365
                                } else {
366
                                        $this->fileData->signers[$index]['signed'] = null;
4✔
367
                                }
368
                        }
369
                        $metadata = $signer->getMetadata();
4✔
370
                        if (!empty($metadata['remote-address'])) {
4✔
371
                                $this->fileData->signers[$index]['remote_address'] = $metadata['remote-address'];
×
372
                        }
373
                        if (!empty($metadata['user-agent'])) {
4✔
374
                                $this->fileData->signers[$index]['user_agent'] = $metadata['user-agent'];
×
375
                        }
376
                        if (!empty($metadata['notify'])) {
4✔
377
                                foreach ($metadata['notify'] as $notify) {
4✔
378
                                        $this->fileData->signers[$index]['notify'][] = [
4✔
379
                                                'method' => $notify['method'],
4✔
380
                                                'date' => (new \DateTime('@' . $notify['date'], new \DateTimeZone('UTC')))->format(DateTimeInterface::ATOM),
4✔
381
                                        ];
4✔
382
                                }
383
                        }
384
                        if ($signer->getSigned() && empty($this->fileData->signers[$index]['signed'])) {
4✔
385
                                if ($signer->getSigned()) {
×
386
                                        $this->fileData->signers[$index]['signed'] = $signer->getSigned()->format(DateTimeInterface::ATOM);
×
387
                                } else {
388
                                        $this->fileData->signers[$index]['signed'] = null;
×
389
                                }
390
                        }
391
                        // @todo refactor this code
392
                        if ($this->me || $this->identifyMethodId) {
4✔
393
                                $this->fileData->signers[$index]['sign_uuid'] = $signer->getUuid();
3✔
394
                                // Identifi if I'm file owner
395
                                if ($this->me?->getUID() === $this->file->getUserId()) {
3✔
396
                                        $email = array_reduce($identifyMethods[IdentifyMethodService::IDENTIFY_EMAIL] ?? [], function (?string $carry, IIdentifyMethod $identifyMethod): ?string {
3✔
397
                                                if ($identifyMethod->getEntity()->getIdentifierKey() === IdentifyMethodService::IDENTIFY_EMAIL) {
3✔
398
                                                        $carry = $identifyMethod->getEntity()->getIdentifierValue();
3✔
399
                                                }
400
                                                return $carry;
3✔
401
                                        }, '');
3✔
402
                                        $this->fileData->signers[$index]['email'] = $email;
3✔
403
                                        $user = $this->userManager->getByEmail($email);
3✔
404
                                        if ($user && count($user) === 1) {
3✔
405
                                                $this->fileData->signers[$index]['userId'] = $user[0]->getUID();
1✔
406
                                        }
407
                                }
408
                                // Identify if I'm signer
409
                                foreach ($identifyMethods as $methods) {
3✔
410
                                        foreach ($methods as $identifyMethod) {
3✔
411
                                                $entity = $identifyMethod->getEntity();
3✔
412
                                                if ($this->identifyMethodId === $entity->getId()
3✔
413
                                                        || $this->me?->getUID() === $entity->getIdentifierValue()
3✔
414
                                                        || $this->me?->getEMailAddress() === $entity->getIdentifierValue()
3✔
415
                                                ) {
416
                                                        $this->fileData->signers[$index]['me'] = true;
1✔
417
                                                        if (!$signer->getSigned()) {
1✔
418
                                                                $this->fileData->settings['canSign'] = true;
1✔
419
                                                                $this->fileData->settings['signerFileUuid'] = $signer->getUuid();
1✔
420
                                                        }
421
                                                }
422
                                        }
423
                                }
424
                        }
425
                        if ($this->fileData->signers[$index]['me']) {
4✔
426
                                $this->fileData->url = $this->urlGenerator->linkToRoute('libresign.page.getPdfFile', ['uuid' => $this->fileData->signers[$index]['sign_uuid']]);
1✔
427
                                $this->fileData->signers[$index]['signatureMethods'] = $this->identifyMethodService->getSignMethodsOfIdentifiedFactors($signer->getId());
1✔
428
                        }
429
                        $this->fileData->signers[$index]['identifyMethods'] = array_reduce($this->fileData->signers[$index]['identifyMethods'], function ($carry, $list) {
4✔
430
                                foreach ($list as $identifyMethod) {
4✔
431
                                        $carry[] = [
4✔
432
                                                'method' => $identifyMethod->getEntity()->getIdentifierKey(),
4✔
433
                                                'value' => $identifyMethod->getEntity()->getIdentifierValue(),
4✔
434
                                                'mandatory' => $identifyMethod->getEntity()->getMandatory(),
4✔
435
                                        ];
4✔
436
                                }
437
                                return $carry;
4✔
438
                        }, []);
4✔
439
                        ksort($this->fileData->signers[$index]);
4✔
440
                }
441
                $this->signersLibreSignLoaded = true;
4✔
442
        }
443

444
        private function loadSignersFromCertData(): void {
445
                $this->loadCertDataFromLibreSignFile();
5✔
446
                foreach ($this->certData as $index => $signer) {
5✔
447
                        if (isset($signer['timestamp'])) {
1✔
448
                                $this->fileData->signers[$index]['timestamp'] = $signer['timestamp'];
×
449
                                if (isset($signer['timestamp']['genTime']) && $signer['timestamp']['genTime'] instanceof DateTimeInterface) {
×
450
                                        $this->fileData->signers[$index]['timestamp']['genTime'] = $signer['timestamp']['genTime']->format(DateTimeInterface::ATOM);
×
451
                                }
452
                        }
453
                        if (isset($signer['signingTime']) && $signer['signingTime'] instanceof DateTimeInterface) {
1✔
454
                                $this->fileData->signers[$index]['signingTime'] = $signer['signingTime'];
1✔
455
                                $this->fileData->signers[$index]['signed'] = $signer['signingTime']->format(DateTimeInterface::ATOM);
1✔
456
                        }
457
                        foreach ($signer['chain'] as $chainIndex => $chainItem) {
1✔
458
                                $chainArr = $chainItem;
1✔
459
                                if (isset($chainItem['validFrom_time_t']) && is_numeric($chainItem['validFrom_time_t'])) {
1✔
460
                                        $chainArr['valid_from'] = (new DateTime('@' . $chainItem['validFrom_time_t'], new \DateTimeZone('UTC')))->format(DateTimeInterface::ATOM);
1✔
461
                                }
462
                                if (isset($chainItem['validTo_time_t']) && is_numeric($chainItem['validTo_time_t'])) {
1✔
463
                                        $chainArr['valid_to'] = (new DateTime('@' . $chainItem['validTo_time_t'], new \DateTimeZone('UTC')))->format(DateTimeInterface::ATOM);
1✔
464
                                }
465
                                $chainArr['displayName'] = $chainArr['name'] ?? ($chainArr['subject']['CN'] ?? '');
1✔
466
                                $this->fileData->signers[$index]['chain'][$chainIndex] = $chainArr;
1✔
467
                                if ($chainIndex === 0) {
1✔
468
                                        $this->fileData->signers[$index] = array_merge($chainArr, $this->fileData->signers[$index] ?? []);
1✔
469
                                        $this->fileData->signers[$index]['uid'] = $this->resolveUid($chainArr);
1✔
470
                                }
471
                        }
472
                }
473
        }
474

475
        private function resolveUid(array $chainArr): ?string {
476
                if (!empty($chainArr['subject']['UID'])) {
1✔
477
                        return $chainArr['subject']['UID'];
×
478
                }
479
                if (!empty($chainArr['subject']['CN'])) {
1✔
480
                        $cn = $chainArr['subject']['CN'];
1✔
481
                        if (is_array($cn)) {
1✔
482
                                $cn = $cn[0];
1✔
483
                        }
484
                        if (preg_match('/^(?<key>.*):(?<value>.*), /', (string)$cn, $matches)) {
1✔
485
                                return $matches['key'] . ':' . $matches['value'];
×
486
                        }
487
                }
488
                if (!empty($chainArr['extensions']['subjectAltName'])) {
1✔
489
                        $subjectAltName = $chainArr['extensions']['subjectAltName'];
1✔
490
                        if (is_array($subjectAltName)) {
1✔
491
                                $subjectAltName = $subjectAltName[0];
×
492
                        }
493
                        preg_match('/^(?<key>(email|account)):(?<value>.*)$/', (string)$subjectAltName, $matches);
1✔
494
                        if ($matches) {
1✔
495
                                if (str_ends_with($matches['value'], $this->host)) {
1✔
496
                                        $uid = str_replace('@' . $this->host, '', $matches['value']);
1✔
497
                                        $userFound = $this->userManager->get($uid);
1✔
498
                                        if ($userFound) {
1✔
499
                                                return 'account:' . $uid;
×
500
                                        } else {
501
                                                $userFound = $this->userManager->getByEmail($matches['value']);
1✔
502
                                                if ($userFound) {
1✔
503
                                                        $userFound = current($userFound);
×
504
                                                        return 'account:' . $userFound->getUID();
×
505
                                                } else {
506
                                                        return 'email:' . $matches['value'];
1✔
507
                                                }
508
                                        }
509
                                } else {
510
                                        $userFound = $this->userManager->getByEmail($matches['value']);
×
511
                                        if ($userFound) {
×
512
                                                $userFound = current($userFound);
×
513
                                                return 'account:' . $userFound->getUID();
×
514
                                        } else {
515
                                                $userFound = $this->userManager->get($matches['value']);
×
516
                                                if ($userFound) {
×
517
                                                        return 'account:' . $userFound->getUID();
×
518
                                                } else {
519
                                                        return $matches['key'] . ':' . $matches['value'];
×
520
                                                }
521
                                        }
522
                                }
523
                        }
524
                }
525
                return null;
×
526
        }
527

528
        private function loadSigners(): void {
529
                if (!$this->showSigners) {
8✔
530
                        return;
3✔
531
                }
532
                $this->loadSignersFromCertData();
5✔
533
                $this->loadLibreSignSigners();
5✔
534
        }
535

536
        /**
537
         * @return (mixed|string)[][]
538
         *
539
         * @psalm-return list<array{url: string, resolution: mixed}>
540
         */
541
        private function getPages(): array {
542
                $return = [];
×
543

544
                $metadata = $this->file->getMetadata();
×
545
                for ($page = 1; $page <= $metadata['p']; $page++) {
×
546
                        $return[] = [
×
547
                                'url' => $this->urlGenerator->linkToRoute('ocs.libresign.File.getPage', [
×
548
                                        'apiVersion' => 'v1',
×
549
                                        'uuid' => $this->file->getUuid(),
×
550
                                        'page' => $page,
×
551
                                ]),
×
552
                                'resolution' => $metadata['d'][$page - 1]
×
553
                        ];
×
554
                }
555
                return $return;
×
556
        }
557

558
        private function getVisibleElements(int $signRequestId): array {
559
                $return = [];
4✔
560
                if (!$this->showVisibleElements) {
4✔
561
                        return $return;
×
562
                }
563
                try {
564
                        $visibleElements = $this->fileElementMapper->getByFileIdAndSignRequestId($this->file->getId(), $signRequestId);
4✔
565
                        foreach ($visibleElements as $visibleElement) {
4✔
566
                                $element = [
×
567
                                        'elementId' => $visibleElement->getId(),
×
568
                                        'signRequestId' => $visibleElement->getSignRequestId(),
×
569
                                        'type' => $visibleElement->getType(),
×
570
                                        'coordinates' => [
×
571
                                                'page' => $visibleElement->getPage(),
×
572
                                                'urx' => $visibleElement->getUrx(),
×
573
                                                'ury' => $visibleElement->getUry(),
×
574
                                                'llx' => $visibleElement->getLlx(),
×
575
                                                'lly' => $visibleElement->getLly()
×
576
                                        ]
×
577
                                ];
×
578
                                $element['coordinates'] = array_merge(
×
579
                                        $element['coordinates'],
×
580
                                        $this->fileElementService->translateCoordinatesFromInternalNotation($element, $this->file)
×
581
                                );
×
582
                                $return[] = $element;
×
583
                        }
584
                } catch (\Throwable) {
×
585
                }
586
                return $return;
4✔
587
        }
588

589
        private function getPhoneNumber(): string {
590
                if (!$this->me) {
3✔
591
                        return '';
×
592
                }
593
                $userAccount = $this->accountManager->getAccount($this->me);
3✔
594
                return $userAccount->getProperty(IAccountManager::PROPERTY_PHONE)->getValue();
3✔
595
        }
596

597
        private function loadSettings(): void {
598
                if (!$this->showSettings) {
8✔
599
                        return;
4✔
600
                }
601
                if ($this->me) {
4✔
602
                        $this->fileData->settings = array_merge($this->fileData->settings, $this->accountService->getSettings($this->me));
3✔
603
                        $this->fileData->settings['phoneNumber'] = $this->getPhoneNumber();
3✔
604
                }
605
                if ($this->signerIdentified || $this->me) {
4✔
606
                        $status = $this->getIdentificationDocumentsStatus();
3✔
607
                        if ($status === self::IDENTIFICATION_DOCUMENTS_NEED_SEND) {
3✔
608
                                $this->fileData->settings['needIdentificationDocuments'] = true;
×
609
                                $this->fileData->settings['identificationDocumentsWaitingApproval'] = false;
×
610
                        } elseif ($status === self::IDENTIFICATION_DOCUMENTS_NEED_APPROVAL) {
3✔
611
                                $this->fileData->settings['needIdentificationDocuments'] = true;
×
612
                                $this->fileData->settings['identificationDocumentsWaitingApproval'] = true;
×
613
                        }
614
                }
615
        }
616

617
        public function getIdentificationDocumentsStatus(string $userId = ''): int {
618
                if (!$this->appConfig->getValueBool(Application::APP_ID, 'identification_documents', false)) {
6✔
619
                        return self::IDENTIFICATION_DOCUMENTS_DISABLED;
6✔
620
                }
621

NEW
622
                if (!$userId && $this->me instanceof IUser) {
×
NEW
623
                        $userId = $this->me->getUID();
×
624
                }
625
                if (!empty($userId)) {
×
626
                        $files = $this->fileMapper->getFilesOfAccount($userId);
×
627
                }
628

629
                if (empty($files) || !count($files)) {
×
630
                        return self::IDENTIFICATION_DOCUMENTS_NEED_SEND;
×
631
                }
632
                $deleted = array_filter($files, fn (File $file) => $file->getStatus() === File::STATUS_DELETED);
×
633
                if (count($deleted) === count($files)) {
×
634
                        return self::IDENTIFICATION_DOCUMENTS_NEED_SEND;
×
635
                }
636

637
                $signed = array_filter($files, fn (File $file) => $file->getStatus() === File::STATUS_SIGNED);
×
638
                if (count($signed) !== count($files)) {
×
639
                        return self::IDENTIFICATION_DOCUMENTS_NEED_APPROVAL;
×
640
                }
641

642
                return self::IDENTIFICATION_DOCUMENTS_APPROVED;
×
643
        }
644

645
        private function loadLibreSignData(): void {
646
                if (!$this->file) {
8✔
647
                        return;
4✔
648
                }
649
                $this->fileData->uuid = $this->file->getUuid();
4✔
650
                $this->fileData->name = $this->file->getName();
4✔
651
                $this->fileData->status = $this->file->getStatus();
4✔
652
                $this->fileData->created_at = $this->file->getCreatedAt()->format(DateTimeInterface::ATOM);
4✔
653
                $this->fileData->statusText = $this->fileMapper->getTextOfStatus($this->file->getStatus());
4✔
654
                $this->fileData->nodeId = $this->file->getNodeId();
4✔
655

656
                $this->fileData->requested_by = [
4✔
657
                        'userId' => $this->file->getUserId(),
4✔
658
                        'displayName' => $this->userManager->get($this->file->getUserId())->getDisplayName(),
4✔
659
                ];
4✔
660
                $this->fileData->file = $this->urlGenerator->linkToRoute('libresign.page.getPdf', ['uuid' => $this->file->getUuid()]);
4✔
661
                if ($this->showVisibleElements) {
4✔
662
                        $signers = $this->signRequestMapper->getByMultipleFileId([$this->file->getId()]);
4✔
663
                        $this->fileData->visibleElements = [];
4✔
664
                        foreach ($this->signRequestMapper->getVisibleElementsFromSigners($signers) as $visibleElements) {
4✔
665
                                $this->fileData->visibleElements = array_merge(
×
666
                                        $this->formatVisibleElementsToArray(
×
667
                                                $visibleElements,
×
668
                                                $this->file->getMetadata()
×
669
                                        ),
×
670
                                        $this->fileData->visibleElements
×
671
                                );
×
672
                        }
673
                }
674
        }
675

676
        private function loadMessages(): void {
677
                if (!$this->showMessages) {
8✔
678
                        return;
4✔
679
                }
680
                $messages = [];
4✔
681
                if ($this->fileData->settings['canSign']) {
4✔
682
                        $messages[] = [
1✔
683
                                'type' => 'info',
1✔
684
                                'message' => $this->l10n->t('You need to sign this document')
1✔
685
                        ];
1✔
686
                }
687
                if ($this->fileData->settings['canRequestSign']) {
4✔
688
                        $this->loadLibreSignSigners();
2✔
689
                        if (empty($this->fileData->signers)) {
2✔
690
                                $messages[] = [
×
691
                                        'type' => 'info',
×
692
                                        'message' => $this->l10n->t('You cannot request signature for this document, please contact your administrator')
×
693
                                ];
×
694
                        }
695
                }
696
                if ($messages) {
4✔
697
                        $this->fileData->messages = $messages;
1✔
698
                }
699
        }
700

701
        /**
702
         * @return LibresignValidateFile
703
         */
704
        public function toArray(): array {
705
                $this->loadLibreSignData();
8✔
706
                $this->loadFileMetadata();
8✔
707
                $this->loadSettings();
8✔
708
                $this->loadSigners();
8✔
709
                $this->loadMessages();
8✔
710
                $return = json_decode(json_encode($this->fileData), true);
8✔
711
                ksort($return);
8✔
712
                return $return;
8✔
713
        }
714

715
        public function setFileByPath(string $path): self {
716
                $node = $this->folderService->getFileByPath($path);
×
717
                $this->setFileByType('FileId', $node->getId());
×
718
                return $this;
×
719
        }
720

721
        /**
722
         * @return array[]
723
         *
724
         * @psalm-return array{data: array, pagination: array}
725
         */
726
        public function listAssociatedFilesOfSignFlow(
727
                $page = null,
728
                $length = null,
729
                array $filter = [],
730
                array $sort = [],
731
        ): array {
732
                $page ??= 1;
1✔
733
                $length ??= (int)$this->appConfig->getValueInt(Application::APP_ID, 'length_of_page', 100);
1✔
734

735
                $return = $this->signRequestMapper->getFilesAssociatedFilesWithMeFormatted(
1✔
736
                        $this->me,
1✔
737
                        $filter,
1✔
738
                        $page,
1✔
739
                        $length,
1✔
740
                        $sort,
1✔
741
                );
1✔
742

743
                $signers = $this->signRequestMapper->getByMultipleFileId(array_column($return['data'], 'id'));
1✔
744
                $identifyMethods = $this->signRequestMapper->getIdentifyMethodsFromSigners($signers);
1✔
745
                $visibleElements = $this->signRequestMapper->getVisibleElementsFromSigners($signers);
1✔
746
                $return['data'] = $this->associateAllAndFormat($this->me, $return['data'], $signers, $identifyMethods, $visibleElements);
1✔
747

748
                $return['pagination']->setRouteName('ocs.libresign.File.list');
1✔
749
                return [
1✔
750
                        'data' => $return['data'],
1✔
751
                        'pagination' => $return['pagination']->getPagination($page, $length, $filter)
1✔
752
                ];
1✔
753
        }
754

755
        private function associateAllAndFormat(IUser $user, array $files, array $signers, array $identifyMethods, array $visibleElements): array {
756
                foreach ($files as $key => $file) {
1✔
757
                        $totalSigned = 0;
×
758
                        foreach ($signers as $signerKey => $signer) {
×
759
                                if ($signer->getFileId() === $file['id']) {
×
760
                                        /** @var array<IdentifyMethod> */
761
                                        $identifyMethodsOfSigner = $identifyMethods[$signer->getId()] ?? [];
×
762
                                        $data = [
×
763
                                                'email' => array_reduce($identifyMethodsOfSigner, function (string $carry, IdentifyMethod $identifyMethod): string {
×
764
                                                        if ($identifyMethod->getIdentifierKey() === IdentifyMethodService::IDENTIFY_EMAIL) {
×
765
                                                                return $identifyMethod->getIdentifierValue();
×
766
                                                        }
767
                                                        if (filter_var($identifyMethod->getIdentifierValue(), FILTER_VALIDATE_EMAIL)) {
×
768
                                                                return $identifyMethod->getIdentifierValue();
×
769
                                                        }
770
                                                        return $carry;
×
771
                                                }, ''),
×
772
                                                'description' => $signer->getDescription(),
×
773
                                                'displayName'
×
774
                                                        => array_reduce($identifyMethodsOfSigner, function (string $carry, IdentifyMethod $identifyMethod): string {
×
775
                                                                if (!$carry && $identifyMethod->getMandatory()) {
×
776
                                                                        return $identifyMethod->getIdentifierValue();
×
777
                                                                }
778
                                                                return $carry;
×
779
                                                        }, $signer->getDisplayName()),
×
780
                                                'request_sign_date' => $signer->getCreatedAt()->format(DateTimeInterface::ATOM),
×
781
                                                'signed' => null,
×
782
                                                'signRequestId' => $signer->getId(),
×
783
                                                'me' => array_reduce($identifyMethodsOfSigner, function (bool $carry, IdentifyMethod $identifyMethod) use ($user): bool {
×
784
                                                        if ($identifyMethod->getIdentifierKey() === IdentifyMethodService::IDENTIFY_ACCOUNT) {
×
785
                                                                if ($user->getUID() === $identifyMethod->getIdentifierValue()) {
×
786
                                                                        return true;
×
787
                                                                }
788
                                                        } elseif ($identifyMethod->getIdentifierKey() === IdentifyMethodService::IDENTIFY_EMAIL) {
×
789
                                                                if (!$user->getEMailAddress()) {
×
790
                                                                        return false;
×
791
                                                                }
792
                                                                if ($user->getEMailAddress() === $identifyMethod->getIdentifierValue()) {
×
793
                                                                        return true;
×
794
                                                                }
795
                                                        }
796
                                                        return $carry;
×
797
                                                }, false),
×
798
                                                'visibleElements' => $this->formatVisibleElementsToArray(
×
799
                                                        $visibleElements[$signer->getId()] ?? [],
×
800
                                                        !empty($file['metadata'])?json_decode((string)$file['metadata'], true):[]
×
801
                                                ),
×
802
                                                'identifyMethods' => array_map(fn (IdentifyMethod $identifyMethod): array => [
×
803
                                                        'method' => $identifyMethod->getIdentifierKey(),
×
804
                                                        'value' => $identifyMethod->getIdentifierValue(),
×
805
                                                        'mandatory' => $identifyMethod->getMandatory(),
×
806
                                                ], array_values($identifyMethodsOfSigner)),
×
807
                                        ];
×
808

809
                                        if ($data['me']) {
×
810
                                                $temp = array_map(function (IdentifyMethod $identifyMethodEntity) use ($signer): array {
×
811
                                                        $this->identifyMethodService->setCurrentIdentifyMethod($identifyMethodEntity);
×
812
                                                        $identifyMethod = $this->identifyMethodService
×
813
                                                                ->setIsRequest(false)
×
814
                                                                ->getInstanceOfIdentifyMethod(
×
815
                                                                        $identifyMethodEntity->getIdentifierKey(),
×
816
                                                                        $identifyMethodEntity->getIdentifierValue(),
×
817
                                                                );
×
818
                                                        $signatureMethods = $identifyMethod->getSignatureMethods();
×
819
                                                        $return = [];
×
820
                                                        foreach ($signatureMethods as $signatureMethod) {
×
821
                                                                if (!$signatureMethod->isEnabled()) {
×
822
                                                                        continue;
×
823
                                                                }
824
                                                                $signatureMethod->setEntity($identifyMethod->getEntity());
×
825
                                                                $return[$signatureMethod->getName()] = $signatureMethod->toArray();
×
826
                                                        }
827
                                                        return $return;
×
828
                                                }, array_values($identifyMethodsOfSigner));
×
829
                                                $data['signatureMethods'] = [];
×
830
                                                foreach ($temp as $methods) {
×
831
                                                        $data['signatureMethods'] = array_merge($data['signatureMethods'], $methods);
×
832
                                                }
833
                                                $data['sign_uuid'] = $signer->getUuid();
×
834
                                                $files[$key]['url'] = $this->urlGenerator->linkToRoute('libresign.page.getPdfFile', ['uuid' => $signer->getuuid()]);
×
835
                                        }
836

837
                                        if ($signer->getSigned()) {
×
838
                                                $data['signed'] = $signer->getSigned()->format(DateTimeInterface::ATOM);
×
839
                                                $totalSigned++;
×
840
                                        }
841
                                        ksort($data);
×
842
                                        $files[$key]['signers'][] = $data;
×
843
                                        unset($signers[$signerKey]);
×
844
                                }
845
                        }
846
                        if (empty($files[$key]['signers'])) {
×
847
                                $files[$key]['signers'] = [];
×
848
                                $files[$key]['statusText'] = $this->l10n->t('no signers');
×
849
                        } else {
850
                                $files[$key]['statusText'] = $this->fileMapper->getTextOfStatus((int)$files[$key]['status']);
×
851
                        }
852
                        unset($files[$key]['id']);
×
853
                        ksort($files[$key]);
×
854
                }
855
                return $files;
1✔
856
        }
857

858
        /**
859
         * @param FileElement[] $visibleElements
860
         * @param array
861
         * @return array
862
         */
863
        private function formatVisibleElementsToArray(array $visibleElements, array $metadata): array {
864
                return array_map(function (FileElement $visibleElement) use ($metadata) {
×
865
                        $element = [
×
866
                                'elementId' => $visibleElement->getId(),
×
867
                                'signRequestId' => $visibleElement->getSignRequestId(),
×
868
                                'type' => $visibleElement->getType(),
×
869
                                'coordinates' => [
×
870
                                        'page' => $visibleElement->getPage(),
×
871
                                        'urx' => $visibleElement->getUrx(),
×
872
                                        'ury' => $visibleElement->getUry(),
×
873
                                        'llx' => $visibleElement->getLlx(),
×
874
                                        'lly' => $visibleElement->getLly()
×
875
                                ]
×
876
                        ];
×
877
                        $dimension = $metadata['d'][$element['coordinates']['page'] - 1];
×
878

879
                        $element['coordinates']['left'] = $element['coordinates']['llx'];
×
880
                        $element['coordinates']['height'] = abs($element['coordinates']['ury'] - $element['coordinates']['lly']);
×
881
                        $element['coordinates']['top'] = $dimension['h'] - $element['coordinates']['ury'];
×
882
                        $element['coordinates']['width'] = $element['coordinates']['urx'] - $element['coordinates']['llx'];
×
883

884
                        return $element;
×
885
                }, $visibleElements);
×
886
        }
887

888
        public function getMyLibresignFile(int $nodeId): File {
889
                return $this->signRequestMapper->getMyLibresignFile(
×
890
                        userId: $this->me->getUID(),
×
891
                        filter: [
×
892
                                'email' => $this->me->getEMailAddress(),
×
893
                                'nodeId' => $nodeId,
×
894
                        ],
×
895
                );
×
896
        }
897

898
        public function delete(int $fileId): void {
899
                $file = $this->fileMapper->getByFileId($fileId);
×
900
                $this->fileElementService->deleteVisibleElements($file->getId());
×
901
                $list = $this->signRequestMapper->getByFileId($file->getId());
×
902
                foreach ($list as $signRequest) {
×
903
                        $this->signRequestMapper->delete($signRequest);
×
904
                }
905
                $this->fileMapper->delete($file);
×
906
                if ($file->getSignedNodeId()) {
×
907
                        $signedNextcloudFile = $this->folderService->getFileById($file->getSignedNodeId());
×
908
                        $signedNextcloudFile->delete();
×
909
                }
910
                try {
911
                        $nextcloudFile = $this->folderService->getFileById($fileId);
×
912
                        $nextcloudFile->delete();
×
913
                } catch (NotFoundException) {
×
914
                }
915
        }
916
}
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