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

LibreSign / libresign / 20035138649

08 Dec 2025 04:26PM UTC coverage: 44.146%. First build
20035138649

Pull #6021

github

web-flow
Merge 55b80f632 into 8b3afd9dd
Pull Request #6021: feat: docmdp implementation

64 of 111 new or added lines in 10 files covered. (57.66%)

5678 of 12862 relevant lines covered (44.15%)

5.1 hits per line

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

51.67
/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\IdDocsMapper;
21
use OCA\Libresign\Db\IdentifyMethod;
22
use OCA\Libresign\Db\SignRequest;
23
use OCA\Libresign\Db\SignRequestMapper;
24
use OCA\Libresign\Exception\LibresignException;
25
use OCA\Libresign\Handler\DocMdpHandler;
26
use OCA\Libresign\Handler\SignEngine\Pkcs12Handler;
27
use OCA\Libresign\Helper\ValidateHelper;
28
use OCA\Libresign\ResponseDefinitions;
29
use OCA\Libresign\Service\IdentifyMethod\IIdentifyMethod;
30
use OCP\Accounts\IAccountManager;
31
use OCP\AppFramework\Db\DoesNotExistException;
32
use OCP\Files\IMimeTypeDetector;
33
use OCP\Files\IRootFolder;
34
use OCP\Files\NotFoundException;
35
use OCP\Http\Client\IClientService;
36
use OCP\IAppConfig;
37
use OCP\IDateTimeFormatter;
38
use OCP\IL10N;
39
use OCP\IURLGenerator;
40
use OCP\IUser;
41
use OCP\IUserManager;
42
use OCP\IUserSession;
43
use Psr\Log\LoggerInterface;
44
use stdClass;
45

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

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

100
        /**
101
         * @return static
102
         */
103
        public function showSigners(bool $show = true): self {
104
                $this->showSigners = $show;
5✔
105
                return $this;
5✔
106
        }
107

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

126
        /**
127
         * @return static
128
         */
129
        public function showVisibleElements(bool $show = true): self {
130
                $this->showVisibleElements = $show;
4✔
131
                return $this;
4✔
132
        }
133

134
        /**
135
         * @return static
136
         */
137
        public function showMessages(bool $show = true): self {
138
                $this->showMessages = $show;
4✔
139
                return $this;
4✔
140
        }
141

142
        /**
143
         * @return static
144
         */
145
        public function setMe(?IUser $user): self {
146
                $this->me = $user;
5✔
147
                return $this;
5✔
148
        }
149

150
        public function setSignerIdentified(bool $identified = true): self {
151
                $this->signerIdentified = $identified;
×
152
                return $this;
×
153
        }
154

155
        public function setIdentifyMethodId(?int $id): self {
156
                $this->identifyMethodId = $id;
2✔
157
                return $this;
2✔
158
        }
159

160
        public function setHost(string $host): self {
161
                $this->host = $host;
4✔
162
                return $this;
4✔
163
        }
164

165
        /**
166
         * @return static
167
         */
168
        public function setFile(File $file): self {
169
                $this->file = $file;
4✔
170
                $this->fileData->status = $this->file->getStatus();
4✔
171
                return $this;
4✔
172
        }
173

174
        public function setSignRequest(SignRequest $signRequest): self {
175
                $this->signRequest = $signRequest;
×
176
                return $this;
×
177
        }
178

179
        public function showValidateFile(bool $validateFile = true): self {
180
                $this->validateFile = $validateFile;
2✔
181
                return $this;
2✔
182
        }
183

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

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

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

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

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

263
        public function getStatus(): int {
264
                return $this->file->getStatus();
×
265
        }
266

267
        public function getSignedNodeId(): ?int {
268
                $status = $this->file->getStatus();
×
269

270
                if (!in_array($status, [File::STATUS_PARTIAL_SIGNED, File::STATUS_SIGNED])) {
×
271
                        return null;
×
272
                }
273
                return $this->file->getSignedNodeId();
×
274
        }
275

276
        private function getFileContent(): string {
277
                if ($this->fileContent) {
8✔
278
                        return $this->fileContent;
3✔
279
                } elseif ($this->file) {
5✔
280
                        try {
281
                                return $this->fileContent = $this->getFile()->getContent();
4✔
282
                        } catch (LibresignException $e) {
×
NEW
283
                                throw $e;
×
NEW
284
                        } catch (\Throwable $e) {
×
NEW
285
                                $this->logger->error('Failed to get file content: ' . $e->getMessage(), [
×
NEW
286
                                        'fileId' => $this->file->getId(),
×
NEW
287
                                        'exception' => $e,
×
NEW
288
                                ]);
×
NEW
289
                                throw new LibresignException($this->l10n->t('Invalid data to validate file'), 404, $e);
×
290
                        }
291
                }
292
                return '';
1✔
293
        }
294

295
        public function isLibresignFile(int $nodeId): bool {
296
                try {
297
                        return $this->fileMapper->fileIdExists($nodeId);
×
298
                } catch (\Throwable) {
×
299
                        throw new LibresignException($this->l10n->t('Invalid data to validate file'), 404);
×
300
                }
301
        }
302

303
        private function loadFileMetadata(): void {
304
                if (!$content = $this->getFileContent()) {
8✔
305
                        return;
1✔
306
                }
307
                $pdfParserService = $this->pdfParserService->setFile($content);
7✔
308
                if ($this->file) {
7✔
309
                        $metadata = $this->file->getMetadata();
4✔
310
                }
311
                if (isset($metadata) && isset($metadata['p'])) {
7✔
312
                        $dimensions = $metadata;
4✔
313
                } else {
314
                        $dimensions = $pdfParserService->getPageDimensions();
3✔
315
                }
316
                $this->fileData->totalPages = $dimensions['p'];
7✔
317
                $this->fileData->size = strlen($content);
7✔
318
                $this->fileData->pdfVersion = $pdfParserService->getPdfVersion();
7✔
319
        }
320

321
        private function loadCertDataFromLibreSignFile(): void {
322
                if (!empty($this->certData) || !$this->validateFile || !$this->file || !$this->file->getSignedNodeId()) {
5✔
323
                        return;
5✔
324
                }
325
                $file = $this->getFile();
×
326

327
                $resource = $file->fopen('rb');
×
328
                $sha256 = $this->getSha256FromResource($resource);
×
329
                if ($sha256 === $this->file->getSignedHash()) {
×
330
                        $this->pkcs12Handler->setIsLibreSignFile();
×
331
                }
332
                $this->certData = $this->pkcs12Handler->getCertificateChain($resource);
×
333
                fclose($resource);
×
334
        }
335

336
        private function getSha256FromResource($resource): string {
337
                $hashContext = hash_init('sha256');
×
338
                while (!feof($resource)) {
×
339
                        $buffer = fread($resource, 8192); // 8192 bytes = 8 KB
×
340
                        hash_update($hashContext, $buffer);
×
341
                }
342
                return hash_final($hashContext);
×
343
        }
344

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

474
        private function loadSignersFromCertData(): void {
475
                $this->loadCertDataFromLibreSignFile();
5✔
476
                foreach ($this->certData as $index => $signer) {
5✔
477
                        if (isset($signer['timestamp'])) {
1✔
478
                                $this->fileData->signers[$index]['timestamp'] = $signer['timestamp'];
×
479
                                if (isset($signer['timestamp']['genTime']) && $signer['timestamp']['genTime'] instanceof DateTimeInterface) {
×
480
                                        $this->fileData->signers[$index]['timestamp']['genTime'] = $signer['timestamp']['genTime']->format(DateTimeInterface::ATOM);
×
481
                                }
482
                        }
483
                        if (isset($signer['signingTime']) && $signer['signingTime'] instanceof DateTimeInterface) {
1✔
484
                                $this->fileData->signers[$index]['signingTime'] = $signer['signingTime'];
1✔
485
                                $this->fileData->signers[$index]['signed'] = $signer['signingTime']->format(DateTimeInterface::ATOM);
1✔
486
                        }
487
                        if (isset($signer['docmdp'])) {
1✔
488
                                $this->fileData->signers[$index]['docmdp'] = $signer['docmdp'];
1✔
489
                        }
490
                        if (isset($signer['modifications'])) {
1✔
491
                                $this->fileData->signers[$index]['modifications'] = $signer['modifications'];
1✔
492
                        }
493
                        if (isset($signer['modification_validation'])) {
1✔
NEW
494
                                $this->fileData->signers[$index]['modification_validation'] = $signer['modification_validation'];
×
495
                        }
496
                        if (isset($signer['chain'])) {
1✔
497
                                foreach ($signer['chain'] as $chainIndex => $chainItem) {
1✔
498
                                        $chainArr = $chainItem;
1✔
499
                                        if (isset($chainItem['validFrom_time_t']) && is_numeric($chainItem['validFrom_time_t'])) {
1✔
500
                                                $chainArr['valid_from'] = (new DateTime('@' . $chainItem['validFrom_time_t'], new \DateTimeZone('UTC')))->format(DateTimeInterface::ATOM);
1✔
501
                                        }
502
                                        if (isset($chainItem['validTo_time_t']) && is_numeric($chainItem['validTo_time_t'])) {
1✔
503
                                                $chainArr['valid_to'] = (new DateTime('@' . $chainItem['validTo_time_t'], new \DateTimeZone('UTC')))->format(DateTimeInterface::ATOM);
1✔
504
                                        }
505
                                        $chainArr['displayName'] = $chainArr['name'] ?? ($chainArr['subject']['CN'] ?? '');
1✔
506
                                        $this->fileData->signers[$index]['chain'][$chainIndex] = $chainArr;
1✔
507
                                        if ($chainIndex === 0) {
1✔
508
                                                $this->fileData->signers[$index] = array_merge($chainArr, $this->fileData->signers[$index] ?? []);
1✔
509
                                                $this->fileData->signers[$index]['uid'] = $this->resolveUid($chainArr);
1✔
510
                                        }
511
                                }
512
                        }
513
                }
514
        }
515

516
        private function resolveUid(array $chainArr): ?string {
517
                if (!empty($chainArr['subject']['UID'])) {
1✔
518
                        return $chainArr['subject']['UID'];
×
519
                }
520
                if (!empty($chainArr['subject']['CN'])) {
1✔
521
                        $cn = $chainArr['subject']['CN'];
1✔
522
                        if (is_array($cn)) {
1✔
523
                                $cn = $cn[0];
1✔
524
                        }
525
                        if (preg_match('/^(?<key>.*):(?<value>.*), /', (string)$cn, $matches)) {
1✔
526
                                return $matches['key'] . ':' . $matches['value'];
×
527
                        }
528
                }
529
                if (!empty($chainArr['extensions']['subjectAltName'])) {
1✔
530
                        $subjectAltName = $chainArr['extensions']['subjectAltName'];
1✔
531
                        if (is_array($subjectAltName)) {
1✔
532
                                $subjectAltName = $subjectAltName[0];
×
533
                        }
534
                        preg_match('/^(?<key>(email|account)):(?<value>.*)$/', (string)$subjectAltName, $matches);
1✔
535
                        if ($matches) {
1✔
536
                                if (str_ends_with($matches['value'], $this->host)) {
1✔
537
                                        $uid = str_replace('@' . $this->host, '', $matches['value']);
1✔
538
                                        $userFound = $this->userManager->get($uid);
1✔
539
                                        if ($userFound) {
1✔
540
                                                return 'account:' . $uid;
×
541
                                        } else {
542
                                                $userFound = $this->userManager->getByEmail($matches['value']);
1✔
543
                                                if ($userFound) {
1✔
544
                                                        $userFound = current($userFound);
×
545
                                                        return 'account:' . $userFound->getUID();
×
546
                                                } else {
547
                                                        return 'email:' . $matches['value'];
1✔
548
                                                }
549
                                        }
550
                                } else {
551
                                        $userFound = $this->userManager->getByEmail($matches['value']);
×
552
                                        if ($userFound) {
×
553
                                                $userFound = current($userFound);
×
554
                                                return 'account:' . $userFound->getUID();
×
555
                                        } else {
556
                                                $userFound = $this->userManager->get($matches['value']);
×
557
                                                if ($userFound) {
×
558
                                                        return 'account:' . $userFound->getUID();
×
559
                                                } else {
560
                                                        return $matches['key'] . ':' . $matches['value'];
×
561
                                                }
562
                                        }
563
                                }
564
                        }
565
                }
566
                return null;
×
567
        }
568

569
        private function loadSigners(): void {
570
                if (!$this->showSigners) {
8✔
571
                        return;
3✔
572
                }
573
                $this->loadSignersFromCertData();
5✔
574
                $this->loadLibreSignSigners();
5✔
575
        }
576

577
        /**
578
         * @return (mixed|string)[][]
579
         *
580
         * @psalm-return list<array{url: string, resolution: mixed}>
581
         */
582
        private function getPages(): array {
583
                $return = [];
×
584

585
                $metadata = $this->file->getMetadata();
×
586
                for ($page = 1; $page <= $metadata['p']; $page++) {
×
587
                        $return[] = [
×
588
                                'url' => $this->urlGenerator->linkToRoute('ocs.libresign.File.getPage', [
×
589
                                        'apiVersion' => 'v1',
×
590
                                        'uuid' => $this->file->getUuid(),
×
591
                                        'page' => $page,
×
592
                                ]),
×
593
                                'resolution' => $metadata['d'][$page - 1]
×
594
                        ];
×
595
                }
596
                return $return;
×
597
        }
598

599
        private function getVisibleElements(int $signRequestId): array {
600
                $return = [];
4✔
601
                if (!$this->showVisibleElements) {
4✔
602
                        return $return;
×
603
                }
604
                try {
605
                        $visibleElements = $this->fileElementMapper->getByFileIdAndSignRequestId($this->file->getId(), $signRequestId);
4✔
606
                        foreach ($visibleElements as $visibleElement) {
4✔
607
                                $element = [
×
608
                                        'elementId' => $visibleElement->getId(),
×
609
                                        'signRequestId' => $visibleElement->getSignRequestId(),
×
610
                                        'type' => $visibleElement->getType(),
×
611
                                        'coordinates' => [
×
612
                                                'page' => $visibleElement->getPage(),
×
613
                                                'urx' => $visibleElement->getUrx(),
×
614
                                                'ury' => $visibleElement->getUry(),
×
615
                                                'llx' => $visibleElement->getLlx(),
×
616
                                                'lly' => $visibleElement->getLly()
×
617
                                        ]
×
618
                                ];
×
619
                                $element['coordinates'] = array_merge(
×
620
                                        $element['coordinates'],
×
621
                                        $this->fileElementService->translateCoordinatesFromInternalNotation($element, $this->file)
×
622
                                );
×
623
                                $return[] = $element;
×
624
                        }
625
                } catch (\Throwable) {
×
626
                }
627
                return $return;
4✔
628
        }
629

630
        private function getPhoneNumber(): string {
631
                if (!$this->me) {
3✔
632
                        return '';
×
633
                }
634
                $userAccount = $this->accountManager->getAccount($this->me);
3✔
635
                return $userAccount->getProperty(IAccountManager::PROPERTY_PHONE)->getValue();
3✔
636
        }
637

638
        private function loadSettings(): void {
639
                if (!$this->showSettings) {
8✔
640
                        return;
4✔
641
                }
642
                if ($this->me) {
4✔
643
                        $this->fileData->settings = array_merge($this->fileData->settings, $this->accountService->getSettings($this->me));
3✔
644
                        $this->fileData->settings['phoneNumber'] = $this->getPhoneNumber();
3✔
645
                }
646
                if ($this->signerIdentified || $this->me) {
4✔
647
                        $status = $this->getIdentificationDocumentsStatus();
3✔
648
                        if ($status === self::IDENTIFICATION_DOCUMENTS_NEED_SEND) {
3✔
649
                                $this->fileData->settings['needIdentificationDocuments'] = true;
×
650
                                $this->fileData->settings['identificationDocumentsWaitingApproval'] = false;
×
651
                        } elseif ($status === self::IDENTIFICATION_DOCUMENTS_NEED_APPROVAL) {
3✔
652
                                $this->fileData->settings['needIdentificationDocuments'] = true;
×
653
                                $this->fileData->settings['identificationDocumentsWaitingApproval'] = true;
×
654
                        }
655
                }
656
        }
657

658
        public function getIdentificationDocumentsStatus(string $userId = ''): int {
659
                if (!$this->appConfig->getValueBool(Application::APP_ID, 'identification_documents', false)) {
6✔
660
                        return self::IDENTIFICATION_DOCUMENTS_DISABLED;
6✔
661
                }
662

663
                if (!$userId && $this->me instanceof IUser) {
×
664
                        $userId = $this->me->getUID();
×
665
                }
666
                if (!empty($userId)) {
×
667
                        $files = $this->fileMapper->getFilesOfAccount($userId);
×
668
                }
669

670
                if (empty($files) || !count($files)) {
×
671
                        return self::IDENTIFICATION_DOCUMENTS_NEED_SEND;
×
672
                }
673
                $deleted = array_filter($files, fn (File $file) => $file->getStatus() === File::STATUS_DELETED);
×
674
                if (count($deleted) === count($files)) {
×
675
                        return self::IDENTIFICATION_DOCUMENTS_NEED_SEND;
×
676
                }
677

678
                $signed = array_filter($files, fn (File $file) => $file->getStatus() === File::STATUS_SIGNED);
×
679
                if (count($signed) !== count($files)) {
×
680
                        return self::IDENTIFICATION_DOCUMENTS_NEED_APPROVAL;
×
681
                }
682

683
                return self::IDENTIFICATION_DOCUMENTS_APPROVED;
×
684
        }
685

686
        private function loadLibreSignData(): void {
687
                if (!$this->file) {
8✔
688
                        return;
4✔
689
                }
690
                $this->fileData->uuid = $this->file->getUuid();
4✔
691
                $this->fileData->name = $this->file->getName();
4✔
692
                $this->fileData->status = $this->file->getStatus();
4✔
693
                $this->fileData->created_at = $this->file->getCreatedAt()->format(DateTimeInterface::ATOM);
4✔
694
                $this->fileData->statusText = $this->fileMapper->getTextOfStatus($this->file->getStatus());
4✔
695
                $this->fileData->nodeId = $this->file->getNodeId();
4✔
696

697
                $this->fileData->requested_by = [
4✔
698
                        'userId' => $this->file->getUserId(),
4✔
699
                        'displayName' => $this->userManager->get($this->file->getUserId())->getDisplayName(),
4✔
700
                ];
4✔
701
                $this->fileData->file = $this->urlGenerator->linkToRoute('libresign.page.getPdf', ['uuid' => $this->file->getUuid()]);
4✔
702
                if ($this->showVisibleElements) {
4✔
703
                        $signers = $this->signRequestMapper->getByMultipleFileId([$this->file->getId()]);
4✔
704
                        $this->fileData->visibleElements = [];
4✔
705
                        foreach ($this->signRequestMapper->getVisibleElementsFromSigners($signers) as $visibleElements) {
4✔
706
                                $this->fileData->visibleElements = array_merge(
×
707
                                        $this->formatVisibleElementsToArray(
×
708
                                                $visibleElements,
×
709
                                                $this->file->getMetadata()
×
710
                                        ),
×
711
                                        $this->fileData->visibleElements
×
712
                                );
×
713
                        }
714
                }
715
        }
716

717
        private function loadMessages(): void {
718
                if (!$this->showMessages) {
8✔
719
                        return;
4✔
720
                }
721
                $messages = [];
4✔
722
                if ($this->fileData->settings['canSign']) {
4✔
723
                        $messages[] = [
1✔
724
                                'type' => 'info',
1✔
725
                                'message' => $this->l10n->t('You need to sign this document')
1✔
726
                        ];
1✔
727
                }
728
                if ($this->fileData->settings['canRequestSign']) {
4✔
729
                        $this->loadLibreSignSigners();
2✔
730
                        if (empty($this->fileData->signers)) {
2✔
731
                                $messages[] = [
×
732
                                        'type' => 'info',
×
733
                                        'message' => $this->l10n->t('You cannot request signature for this document, please contact your administrator')
×
734
                                ];
×
735
                        }
736
                }
737
                if ($messages) {
4✔
738
                        $this->fileData->messages = $messages;
1✔
739
                }
740
        }
741

742
        /**
743
         * @return LibresignValidateFile
744
         */
745
        public function toArray(): array {
746
                $this->loadLibreSignData();
8✔
747
                $this->loadFileMetadata();
8✔
748
                $this->loadSettings();
8✔
749
                $this->loadSigners();
8✔
750
                $this->loadMessages();
8✔
751
                $return = json_decode(json_encode($this->fileData), true);
8✔
752
                ksort($return);
8✔
753
                return $return;
8✔
754
        }
755

756
        public function setFileByPath(string $path): self {
757
                $node = $this->folderService->getFileByPath($path);
×
758
                $this->setFileByType('FileId', $node->getId());
×
759
                return $this;
×
760
        }
761

762
        /**
763
         * @return array[]
764
         *
765
         * @psalm-return array{data: array, pagination: array}
766
         */
767
        public function listAssociatedFilesOfSignFlow(
768
                $page = null,
769
                $length = null,
770
                array $filter = [],
771
                array $sort = [],
772
        ): array {
773
                $page ??= 1;
1✔
774
                $length ??= (int)$this->appConfig->getValueInt(Application::APP_ID, 'length_of_page', 100);
1✔
775

776
                $return = $this->signRequestMapper->getFilesAssociatedFilesWithMeFormatted(
1✔
777
                        $this->me,
1✔
778
                        $filter,
1✔
779
                        $page,
1✔
780
                        $length,
1✔
781
                        $sort,
1✔
782
                );
1✔
783

784
                $signers = $this->signRequestMapper->getByMultipleFileId(array_column($return['data'], 'id'));
1✔
785
                $identifyMethods = $this->signRequestMapper->getIdentifyMethodsFromSigners($signers);
1✔
786
                $visibleElements = $this->signRequestMapper->getVisibleElementsFromSigners($signers);
1✔
787
                $return['data'] = $this->associateAllAndFormat($this->me, $return['data'], $signers, $identifyMethods, $visibleElements);
1✔
788

789
                $return['pagination']->setRouteName('ocs.libresign.File.list');
1✔
790
                return [
1✔
791
                        'data' => $return['data'],
1✔
792
                        'pagination' => $return['pagination']->getPagination($page, $length, $filter)
1✔
793
                ];
1✔
794
        }
795

796
        private function associateAllAndFormat(IUser $user, array $files, array $signers, array $identifyMethods, array $visibleElements): array {
797
                foreach ($files as $key => $file) {
1✔
798
                        $totalSigned = 0;
×
799
                        foreach ($signers as $signerKey => $signer) {
×
800
                                if ($signer->getFileId() === $file['id']) {
×
801
                                        /** @var array<IdentifyMethod> */
802
                                        $identifyMethodsOfSigner = $identifyMethods[$signer->getId()] ?? [];
×
803
                                        $data = [
×
804
                                                'email' => array_reduce($identifyMethodsOfSigner, function (string $carry, IdentifyMethod $identifyMethod): string {
×
805
                                                        if ($identifyMethod->getIdentifierKey() === IdentifyMethodService::IDENTIFY_EMAIL) {
×
806
                                                                return $identifyMethod->getIdentifierValue();
×
807
                                                        }
808
                                                        if (filter_var($identifyMethod->getIdentifierValue(), FILTER_VALIDATE_EMAIL)) {
×
809
                                                                return $identifyMethod->getIdentifierValue();
×
810
                                                        }
811
                                                        return $carry;
×
812
                                                }, ''),
×
813
                                                'description' => $signer->getDescription(),
×
814
                                                'displayName'
×
815
                                                        => array_reduce($identifyMethodsOfSigner, function (string $carry, IdentifyMethod $identifyMethod): string {
×
816
                                                                if (!$carry && $identifyMethod->getMandatory()) {
×
817
                                                                        return $identifyMethod->getIdentifierValue();
×
818
                                                                }
819
                                                                return $carry;
×
820
                                                        }, $signer->getDisplayName()),
×
821
                                                'request_sign_date' => $signer->getCreatedAt()->format(DateTimeInterface::ATOM),
×
822
                                                'signed' => null,
×
823
                                                'signRequestId' => $signer->getId(),
×
824
                                                'me' => array_reduce($identifyMethodsOfSigner, function (bool $carry, IdentifyMethod $identifyMethod) use ($user): bool {
×
825
                                                        if ($identifyMethod->getIdentifierKey() === IdentifyMethodService::IDENTIFY_ACCOUNT) {
×
826
                                                                if ($user->getUID() === $identifyMethod->getIdentifierValue()) {
×
827
                                                                        return true;
×
828
                                                                }
829
                                                        } elseif ($identifyMethod->getIdentifierKey() === IdentifyMethodService::IDENTIFY_EMAIL) {
×
830
                                                                if (!$user->getEMailAddress()) {
×
831
                                                                        return false;
×
832
                                                                }
833
                                                                if ($user->getEMailAddress() === $identifyMethod->getIdentifierValue()) {
×
834
                                                                        return true;
×
835
                                                                }
836
                                                        }
837
                                                        return $carry;
×
838
                                                }, false),
×
839
                                                'visibleElements' => $this->formatVisibleElementsToArray(
×
840
                                                        $visibleElements[$signer->getId()] ?? [],
×
841
                                                        !empty($file['metadata'])?json_decode((string)$file['metadata'], true):[]
×
842
                                                ),
×
843
                                                'identifyMethods' => array_map(fn (IdentifyMethod $identifyMethod): array => [
×
844
                                                        'method' => $identifyMethod->getIdentifierKey(),
×
845
                                                        'value' => $identifyMethod->getIdentifierValue(),
×
846
                                                        'mandatory' => $identifyMethod->getMandatory(),
×
847
                                                ], array_values($identifyMethodsOfSigner)),
×
848
                                        ];
×
849

850
                                        if ($data['me']) {
×
851
                                                $temp = array_map(function (IdentifyMethod $identifyMethodEntity) use ($signer): array {
×
852
                                                        $this->identifyMethodService->setCurrentIdentifyMethod($identifyMethodEntity);
×
853
                                                        $identifyMethod = $this->identifyMethodService
×
854
                                                                ->setIsRequest(false)
×
855
                                                                ->getInstanceOfIdentifyMethod(
×
856
                                                                        $identifyMethodEntity->getIdentifierKey(),
×
857
                                                                        $identifyMethodEntity->getIdentifierValue(),
×
858
                                                                );
×
859
                                                        $signatureMethods = $identifyMethod->getSignatureMethods();
×
860
                                                        $return = [];
×
861
                                                        foreach ($signatureMethods as $signatureMethod) {
×
862
                                                                if (!$signatureMethod->isEnabled()) {
×
863
                                                                        continue;
×
864
                                                                }
865
                                                                $signatureMethod->setEntity($identifyMethod->getEntity());
×
866
                                                                $return[$signatureMethod->getName()] = $signatureMethod->toArray();
×
867
                                                        }
868
                                                        return $return;
×
869
                                                }, array_values($identifyMethodsOfSigner));
×
870
                                                $data['signatureMethods'] = [];
×
871
                                                foreach ($temp as $methods) {
×
872
                                                        $data['signatureMethods'] = array_merge($data['signatureMethods'], $methods);
×
873
                                                }
874
                                                $data['sign_uuid'] = $signer->getUuid();
×
875
                                                $files[$key]['url'] = $this->urlGenerator->linkToRoute('libresign.page.getPdfFile', ['uuid' => $signer->getuuid()]);
×
876
                                        }
877

878
                                        if ($signer->getSigned()) {
×
879
                                                $data['signed'] = $signer->getSigned()->format(DateTimeInterface::ATOM);
×
880
                                                $totalSigned++;
×
881
                                        }
882
                                        ksort($data);
×
883
                                        $files[$key]['signers'][] = $data;
×
884
                                        unset($signers[$signerKey]);
×
885
                                }
886
                        }
887
                        if (empty($files[$key]['signers'])) {
×
888
                                $files[$key]['signers'] = [];
×
889
                                $files[$key]['statusText'] = $this->l10n->t('no signers');
×
890
                        } else {
891
                                $files[$key]['statusText'] = $this->fileMapper->getTextOfStatus((int)$files[$key]['status']);
×
892
                        }
893
                        unset($files[$key]['id']);
×
894
                        ksort($files[$key]);
×
895
                }
896
                return $files;
1✔
897
        }
898

899
        /**
900
         * @param FileElement[] $visibleElements
901
         * @param array
902
         * @return array
903
         */
904
        private function formatVisibleElementsToArray(array $visibleElements, array $metadata): array {
905
                return array_map(function (FileElement $visibleElement) use ($metadata) {
×
906
                        $element = [
×
907
                                'elementId' => $visibleElement->getId(),
×
908
                                'signRequestId' => $visibleElement->getSignRequestId(),
×
909
                                'type' => $visibleElement->getType(),
×
910
                                'coordinates' => [
×
911
                                        'page' => $visibleElement->getPage(),
×
912
                                        'urx' => $visibleElement->getUrx(),
×
913
                                        'ury' => $visibleElement->getUry(),
×
914
                                        'llx' => $visibleElement->getLlx(),
×
915
                                        'lly' => $visibleElement->getLly()
×
916
                                ]
×
917
                        ];
×
918
                        $dimension = $metadata['d'][$element['coordinates']['page'] - 1];
×
919

920
                        $element['coordinates']['left'] = $element['coordinates']['llx'];
×
921
                        $element['coordinates']['height'] = abs($element['coordinates']['ury'] - $element['coordinates']['lly']);
×
922
                        $element['coordinates']['top'] = $dimension['h'] - $element['coordinates']['ury'];
×
923
                        $element['coordinates']['width'] = $element['coordinates']['urx'] - $element['coordinates']['llx'];
×
924

925
                        return $element;
×
926
                }, $visibleElements);
×
927
        }
928

929
        public function getMyLibresignFile(int $nodeId): File {
930
                return $this->signRequestMapper->getMyLibresignFile(
×
931
                        userId: $this->me->getUID(),
×
932
                        filter: [
×
933
                                'email' => $this->me->getEMailAddress(),
×
934
                                'nodeId' => $nodeId,
×
935
                        ],
×
936
                );
×
937
        }
938

939
        public function delete(int $fileId): void {
940
                $file = $this->fileMapper->getByFileId($fileId);
×
941
                $this->fileElementService->deleteVisibleElements($file->getId());
×
942
                $list = $this->signRequestMapper->getByFileId($file->getId());
×
943
                foreach ($list as $signRequest) {
×
944
                        $this->signRequestMapper->delete($signRequest);
×
945
                }
946
                $this->idDocsMapper->deleteByFileId($file->getId());
×
947
                $this->fileMapper->delete($file);
×
948
                if ($file->getSignedNodeId()) {
×
949
                        $signedNextcloudFile = $this->folderService->getFileById($file->getSignedNodeId());
×
950
                        $signedNextcloudFile->delete();
×
951
                }
952
                try {
953
                        $nextcloudFile = $this->folderService->getFileById($fileId);
×
954
                        $nextcloudFile->delete();
×
955
                } catch (NotFoundException) {
×
956
                }
957
        }
958
}
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