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

LibreSign / libresign / 19878831952

03 Dec 2025 01:08AM UTC coverage: 41.576%. First build
19878831952

Pull #4464

github

web-flow
Merge a843e05d5 into da0973853
Pull Request #4464: refactor: attach document

117 of 302 new or added lines in 16 files covered. (38.74%)

5123 of 12322 relevant lines covered (41.58%)

4.17 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

644
                return self::IDENTIFICATION_DOCUMENTS_APPROVED;
×
645
        }
646

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

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

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

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

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

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

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

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

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

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

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

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

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

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

886
                        return $element;
×
887
                }, $visibleElements);
×
888
        }
889

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

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