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

LibreSign / libresign / 19684837155

25 Nov 2025 09:36PM UTC coverage: 40.413%. First build
19684837155

Pull #5793

github

web-flow
Merge 538f52e31 into 3df8e8f6d
Pull Request #5793: chore: unify parser

11 of 13 new or added lines in 3 files covered. (84.62%)

4772 of 11808 relevant lines covered (40.41%)

3.51 hits per line

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

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

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

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

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

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

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

144
        public function setIdentifyMethodId(?int $id): self {
145
                $this->identifyMethodId = $id;
2✔
146
                return $this;
2✔
147
        }
148

149
        public function setHost(string $host): self {
150
                $this->host = $host;
4✔
151
                return $this;
4✔
152
        }
153

154
        /**
155
         * @return static
156
         */
157
        public function setFile(File $file): self {
158
                $this->file = $file;
4✔
159
                $this->fileData->status = $this->file->getStatus();
4✔
160
                return $this;
4✔
161
        }
162

163
        public function setSignRequest(SignRequest $signRequest): self {
164
                $this->signRequest = $signRequest;
×
165
                return $this;
×
166
        }
167

168
        public function showValidateFile(bool $validateFile = true): self {
169
                $this->validateFile = $validateFile;
2✔
170
                return $this;
2✔
171
        }
172

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

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

210
                $this->fileContent = file_get_contents($file['tmp_name']);
4✔
211
                $mimeType = $this->mimeTypeDetector->detectString($this->fileContent);
4✔
212
                if ($mimeType !== 'application/pdf') {
4✔
213
                        $this->fileContent = '';
1✔
214
                        unlink($file['tmp_name']);
1✔
215
                        throw new InvalidArgumentException($this->l10n->t('Invalid file provided'));
1✔
216
                }
217
                $this->fileData->size = $file['size'];
3✔
218

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

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

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

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

285
        private function loadCertDataFromLibreSignFile(): void {
286
                if (!empty($this->certData) || !$this->validateFile || !$this->file || !$this->file->getSignedNodeId()) {
5✔
287
                        return;
5✔
288
                }
289
                $file = $this->getFile();
×
290

291
                $resource = $file->fopen('rb');
×
292
                $sha256 = $this->getSha256FromResource($resource);
×
293
                if ($sha256 === $this->file->getSignedHash()) {
×
294
                        $this->pkcs12Handler->setIsLibreSignFile();
×
295
                }
296
                $this->certData = $this->pkcs12Handler->getCertificateChain($resource);
×
297
                fclose($resource);
×
298
        }
299

300
        private function getSha256FromResource($resource): string {
301
                $hashContext = hash_init('sha256');
×
302
                while (!feof($resource)) {
×
303
                        $buffer = fread($resource, 8192); // 8192 bytes = 8 KB
×
304
                        hash_update($hashContext, $buffer);
×
305
                }
306
                return hash_final($hashContext);
×
307
        }
308

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

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

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

522
        private function loadSigners(): void {
523
                if (!$this->showSigners) {
8✔
524
                        return;
3✔
525
                }
526
                $this->loadSignersFromCertData();
5✔
527
                $this->loadLibreSignSigners();
5✔
528
        }
529

530
        /**
531
         * @return (mixed|string)[][]
532
         *
533
         * @psalm-return list<array{url: string, resolution: mixed}>
534
         */
535
        private function getPages(): array {
536
                $return = [];
×
537

538
                $metadata = $this->file->getMetadata();
×
539
                for ($page = 1; $page <= $metadata['p']; $page++) {
×
540
                        $return[] = [
×
541
                                'url' => $this->urlGenerator->linkToRoute('ocs.libresign.File.getPage', [
×
542
                                        'apiVersion' => 'v1',
×
543
                                        'uuid' => $this->file->getUuid(),
×
544
                                        'page' => $page,
×
545
                                ]),
×
546
                                'resolution' => $metadata['d'][$page - 1]
×
547
                        ];
×
548
                }
549
                return $return;
×
550
        }
551

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

583
        private function getPhoneNumber(): string {
584
                if (!$this->me) {
3✔
585
                        return '';
×
586
                }
587
                $userAccount = $this->accountManager->getAccount($this->me);
3✔
588
                return $userAccount->getProperty(IAccountManager::PROPERTY_PHONE)->getValue();
3✔
589
        }
590

591
        private function loadSettings(): void {
592
                if (!$this->showSettings) {
8✔
593
                        return;
4✔
594
                }
595
                if ($this->me) {
4✔
596
                        $this->fileData->settings = array_merge($this->fileData->settings, $this->accountService->getSettings($this->me));
3✔
597
                        $this->fileData->settings['phoneNumber'] = $this->getPhoneNumber();
3✔
598
                        $status = $this->getIdentificationDocumentsStatus($this->me->getUID());
3✔
599
                        if ($status === self::IDENTIFICATION_DOCUMENTS_NEED_SEND) {
3✔
600
                                $this->fileData->settings['needIdentificationDocuments'] = true;
×
601
                                $this->fileData->settings['identificationDocumentsWaitingApproval'] = false;
×
602
                        } elseif ($status === self::IDENTIFICATION_DOCUMENTS_NEED_APPROVAL) {
3✔
603
                                $this->fileData->settings['needIdentificationDocuments'] = true;
×
604
                                $this->fileData->settings['identificationDocumentsWaitingApproval'] = true;
×
605
                        }
606
                }
607
        }
608

609
        public function getIdentificationDocumentsStatus(?string $userId): int {
610
                if (!$this->appConfig->getValueBool(Application::APP_ID, 'identification_documents', false)) {
4✔
611
                        return self::IDENTIFICATION_DOCUMENTS_DISABLED;
4✔
612
                }
613

614
                if (!empty($userId)) {
×
615
                        $files = $this->fileMapper->getFilesOfAccount($userId);
×
616
                }
617
                if (empty($files) || !count($files)) {
×
618
                        return self::IDENTIFICATION_DOCUMENTS_NEED_SEND;
×
619
                }
620
                $deleted = array_filter($files, fn (File $file) => $file->getStatus() === File::STATUS_DELETED);
×
621
                if (count($deleted) === count($files)) {
×
622
                        return self::IDENTIFICATION_DOCUMENTS_NEED_SEND;
×
623
                }
624

625
                $signed = array_filter($files, fn (File $file) => $file->getStatus() === File::STATUS_SIGNED);
×
626
                if (count($signed) !== count($files)) {
×
627
                        return self::IDENTIFICATION_DOCUMENTS_NEED_APPROVAL;
×
628
                }
629

630
                return self::IDENTIFICATION_DOCUMENTS_APPROVED;
×
631
        }
632

633
        private function loadLibreSignData(): void {
634
                if (!$this->file) {
8✔
635
                        return;
4✔
636
                }
637
                $this->fileData->uuid = $this->file->getUuid();
4✔
638
                $this->fileData->name = $this->file->getName();
4✔
639
                $this->fileData->status = $this->file->getStatus();
4✔
640
                $this->fileData->created_at = $this->file->getCreatedAt()->format(DateTimeInterface::ATOM);
4✔
641
                $this->fileData->statusText = $this->fileMapper->getTextOfStatus($this->file->getStatus());
4✔
642
                $this->fileData->nodeId = $this->file->getNodeId();
4✔
643

644
                $this->fileData->requested_by = [
4✔
645
                        'userId' => $this->file->getUserId(),
4✔
646
                        'displayName' => $this->userManager->get($this->file->getUserId())->getDisplayName(),
4✔
647
                ];
4✔
648
                $this->fileData->file = $this->urlGenerator->linkToRoute('libresign.page.getPdf', ['uuid' => $this->file->getUuid()]);
4✔
649
                if ($this->showVisibleElements) {
4✔
650
                        $signers = $this->signRequestMapper->getByMultipleFileId([$this->file->getId()]);
4✔
651
                        $this->fileData->visibleElements = [];
4✔
652
                        foreach ($this->signRequestMapper->getVisibleElementsFromSigners($signers) as $visibleElements) {
4✔
653
                                $this->fileData->visibleElements = array_merge(
×
654
                                        $this->formatVisibleElementsToArray(
×
655
                                                $visibleElements,
×
656
                                                $this->file->getMetadata()
×
657
                                        ),
×
658
                                        $this->fileData->visibleElements
×
659
                                );
×
660
                        }
661
                }
662
        }
663

664
        private function loadMessages(): void {
665
                if (!$this->showMessages) {
8✔
666
                        return;
4✔
667
                }
668
                $messages = [];
4✔
669
                if ($this->fileData->settings['canSign']) {
4✔
670
                        $messages[] = [
1✔
671
                                'type' => 'info',
1✔
672
                                'message' => $this->l10n->t('You need to sign this document')
1✔
673
                        ];
1✔
674
                }
675
                if ($this->fileData->settings['canRequestSign']) {
4✔
676
                        $this->loadLibreSignSigners();
2✔
677
                        if (empty($this->fileData->signers)) {
2✔
678
                                $messages[] = [
×
679
                                        'type' => 'info',
×
680
                                        'message' => $this->l10n->t('You cannot request signature for this document, please contact your administrator')
×
681
                                ];
×
682
                        }
683
                }
684
                if ($messages) {
4✔
685
                        $this->fileData->messages = $messages;
1✔
686
                }
687
        }
688

689
        /**
690
         * @return LibresignValidateFile
691
         */
692
        public function toArray(): array {
693
                $this->loadLibreSignData();
8✔
694
                $this->loadFileMetadata();
8✔
695
                $this->loadSettings();
8✔
696
                $this->loadSigners();
8✔
697
                $this->loadMessages();
8✔
698
                $return = json_decode(json_encode($this->fileData), true);
8✔
699
                ksort($return);
8✔
700
                return $return;
8✔
701
        }
702

703
        public function setFileByPath(string $path): self {
704
                $node = $this->folderService->getFileByPath($path);
×
705
                $this->setFileByType('FileId', $node->getId());
×
706
                return $this;
×
707
        }
708

709
        /**
710
         * @return array[]
711
         *
712
         * @psalm-return array{data: array, pagination: array}
713
         */
714
        public function listAssociatedFilesOfSignFlow(
715
                $page = null,
716
                $length = null,
717
                array $filter = [],
718
                array $sort = [],
719
        ): array {
720
                $page ??= 1;
1✔
721
                $length ??= (int)$this->appConfig->getValueInt(Application::APP_ID, 'length_of_page', 100);
1✔
722

723
                $return = $this->signRequestMapper->getFilesAssociatedFilesWithMeFormatted(
1✔
724
                        $this->me,
1✔
725
                        $filter,
1✔
726
                        $page,
1✔
727
                        $length,
1✔
728
                        $sort,
1✔
729
                );
1✔
730

731
                $signers = $this->signRequestMapper->getByMultipleFileId(array_column($return['data'], 'id'));
1✔
732
                $identifyMethods = $this->signRequestMapper->getIdentifyMethodsFromSigners($signers);
1✔
733
                $visibleElements = $this->signRequestMapper->getVisibleElementsFromSigners($signers);
1✔
734
                $return['data'] = $this->associateAllAndFormat($this->me, $return['data'], $signers, $identifyMethods, $visibleElements);
1✔
735

736
                $return['pagination']->setRouteName('ocs.libresign.File.list');
1✔
737
                return [
1✔
738
                        'data' => $return['data'],
1✔
739
                        'pagination' => $return['pagination']->getPagination($page, $length, $filter)
1✔
740
                ];
1✔
741
        }
742

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

797
                                        if ($data['me']) {
×
798
                                                $temp = array_map(function (IdentifyMethod $identifyMethodEntity) use ($signer): array {
×
799
                                                        $this->identifyMethodService->setCurrentIdentifyMethod($identifyMethodEntity);
×
800
                                                        $identifyMethod = $this->identifyMethodService
×
801
                                                                ->setIsRequest(false)
×
802
                                                                ->getInstanceOfIdentifyMethod(
×
803
                                                                        $identifyMethodEntity->getIdentifierKey(),
×
804
                                                                        $identifyMethodEntity->getIdentifierValue(),
×
805
                                                                );
×
806
                                                        $signatureMethods = $identifyMethod->getSignatureMethods();
×
807
                                                        $return = [];
×
808
                                                        foreach ($signatureMethods as $signatureMethod) {
×
809
                                                                if (!$signatureMethod->isEnabled()) {
×
810
                                                                        continue;
×
811
                                                                }
812
                                                                $signatureMethod->setEntity($identifyMethod->getEntity());
×
813
                                                                $return[$signatureMethod->getName()] = $signatureMethod->toArray();
×
814
                                                        }
815
                                                        return $return;
×
816
                                                }, array_values($identifyMethodsOfSigner));
×
817
                                                $data['signatureMethods'] = [];
×
818
                                                foreach ($temp as $methods) {
×
819
                                                        $data['signatureMethods'] = array_merge($data['signatureMethods'], $methods);
×
820
                                                }
821
                                                $data['sign_uuid'] = $signer->getUuid();
×
822
                                                $files[$key]['url'] = $this->urlGenerator->linkToRoute('libresign.page.getPdfFile', ['uuid' => $signer->getuuid()]);
×
823
                                        }
824

825
                                        if ($signer->getSigned()) {
×
826
                                                $data['signed'] = $signer->getSigned()->format(DateTimeInterface::ATOM);
×
827
                                                $totalSigned++;
×
828
                                        }
829
                                        ksort($data);
×
830
                                        $files[$key]['signers'][] = $data;
×
831
                                        unset($signers[$signerKey]);
×
832
                                }
833
                        }
834
                        if (empty($files[$key]['signers'])) {
×
835
                                $files[$key]['signers'] = [];
×
836
                                $files[$key]['statusText'] = $this->l10n->t('no signers');
×
837
                        } else {
838
                                $files[$key]['statusText'] = $this->fileMapper->getTextOfStatus((int)$files[$key]['status']);
×
839
                        }
840
                        unset($files[$key]['id']);
×
841
                        ksort($files[$key]);
×
842
                }
843
                return $files;
1✔
844
        }
845

846
        /**
847
         * @param FileElement[] $visibleElements
848
         * @param array
849
         * @return array
850
         */
851
        private function formatVisibleElementsToArray(array $visibleElements, array $metadata): array {
852
                return array_map(function (FileElement $visibleElement) use ($metadata) {
×
853
                        $element = [
×
854
                                'elementId' => $visibleElement->getId(),
×
855
                                'signRequestId' => $visibleElement->getSignRequestId(),
×
856
                                'type' => $visibleElement->getType(),
×
857
                                'coordinates' => [
×
858
                                        'page' => $visibleElement->getPage(),
×
859
                                        'urx' => $visibleElement->getUrx(),
×
860
                                        'ury' => $visibleElement->getUry(),
×
861
                                        'llx' => $visibleElement->getLlx(),
×
862
                                        'lly' => $visibleElement->getLly()
×
863
                                ]
×
864
                        ];
×
865
                        $dimension = $metadata['d'][$element['coordinates']['page'] - 1];
×
866

867
                        $element['coordinates']['left'] = $element['coordinates']['llx'];
×
868
                        $element['coordinates']['height'] = abs($element['coordinates']['ury'] - $element['coordinates']['lly']);
×
869
                        $element['coordinates']['top'] = $dimension['h'] - $element['coordinates']['ury'];
×
870
                        $element['coordinates']['width'] = $element['coordinates']['urx'] - $element['coordinates']['llx'];
×
871

872
                        return $element;
×
873
                }, $visibleElements);
×
874
        }
875

876
        public function getMyLibresignFile(int $nodeId): File {
877
                return $this->signRequestMapper->getMyLibresignFile(
×
878
                        userId: $this->me->getUID(),
×
879
                        filter: [
×
880
                                'email' => $this->me->getEMailAddress(),
×
881
                                'nodeId' => $nodeId,
×
882
                        ],
×
883
                );
×
884
        }
885

886
        public function delete(int $fileId): void {
887
                $file = $this->fileMapper->getByFileId($fileId);
×
888
                $this->fileElementService->deleteVisibleElements($file->getId());
×
889
                $list = $this->signRequestMapper->getByFileId($file->getId());
×
890
                foreach ($list as $signRequest) {
×
891
                        $this->signRequestMapper->delete($signRequest);
×
892
                }
893
                $this->fileMapper->delete($file);
×
894
                if ($file->getSignedNodeId()) {
×
895
                        $signedNextcloudFile = $this->folderService->getFileById($file->getSignedNodeId());
×
896
                        $signedNextcloudFile->delete();
×
897
                }
898
                try {
899
                        $nextcloudFile = $this->folderService->getFileById($fileId);
×
900
                        $nextcloudFile->delete();
×
901
                } catch (NotFoundException) {
×
902
                }
903
        }
904
}
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