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

LibreSign / libresign / 24083305764

07 Apr 2026 01:14PM UTC coverage: 55.599%. First build
24083305764

Pull #7450

github

web-flow
Merge 99a97f498 into 1112b1165
Pull Request #7450: chore(rector): apply safe test-only batch and php82 baseline

5 of 20 new or added lines in 15 files covered. (25.0%)

10233 of 18405 relevant lines covered (55.6%)

6.61 hits per line

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

71.27
/lib/Service/RequestSignatureService.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 OCA\Libresign\AppInfo\Application;
12
use OCA\Libresign\Db\File as FileEntity;
13
use OCA\Libresign\Db\FileElementMapper;
14
use OCA\Libresign\Db\FileMapper;
15
use OCA\Libresign\Db\IdentifyMethodMapper;
16
use OCA\Libresign\Db\SignRequest as SignRequestEntity;
17
use OCA\Libresign\Db\SignRequestMapper;
18
use OCA\Libresign\Enum\FileStatus;
19
use OCA\Libresign\Enum\SignatureFlow;
20
use OCA\Libresign\Events\SignRequestCanceledEvent;
21
use OCA\Libresign\Exception\LibresignException;
22
use OCA\Libresign\Handler\DocMdpHandler;
23
use OCA\Libresign\Helper\FileUploadHelper;
24
use OCA\Libresign\Helper\ValidateHelper;
25
use OCA\Libresign\Service\DocMdp\ConfigService as DocMdpConfigService;
26
use OCA\Libresign\Service\Envelope\EnvelopeFileRelocator;
27
use OCA\Libresign\Service\Envelope\EnvelopeService;
28
use OCA\Libresign\Service\File\Pdf\PdfMetadataExtractor;
29
use OCA\Libresign\Service\IdentifyMethod\IIdentifyMethod;
30
use OCA\Libresign\Service\SignRequest\SignRequestService;
31
use OCP\EventDispatcher\IEventDispatcher;
32
use OCP\Files\IMimeTypeDetector;
33
use OCP\Files\Node;
34
use OCP\Http\Client\IClientService;
35
use OCP\IAppConfig;
36
use OCP\IL10N;
37
use OCP\IUser;
38
use OCP\IUserManager;
39
use Psr\Log\LoggerInterface;
40
use Sabre\DAV\UUIDUtil;
41

42
class RequestSignatureService {
43

44
        public function __construct(
45
                protected FileService $fileService,
46
                protected IL10N $l10n,
47
                protected IdentifyMethodService $identifyMethod,
48
                protected SignRequestMapper $signRequestMapper,
49
                protected IUserManager $userManager,
50
                protected FileMapper $fileMapper,
51
                protected IdentifyMethodMapper $identifyMethodMapper,
52
                protected PdfMetadataExtractor $pdfMetadataExtractor,
53
                protected FileElementService $fileElementService,
54
                protected FileElementMapper $fileElementMapper,
55
                protected FolderService $folderService,
56
                protected IMimeTypeDetector $mimeTypeDetector,
57
                protected ValidateHelper $validateHelper,
58
                protected IClientService $client,
59
                protected DocMdpHandler $docMdpHandler,
60
                protected LoggerInterface $logger,
61
                protected SequentialSigningService $sequentialSigningService,
62
                protected IAppConfig $appConfig,
63
                protected IEventDispatcher $eventDispatcher,
64
                protected FileStatusService $fileStatusService,
65
                protected DocMdpConfigService $docMdpConfigService,
66
                protected EnvelopeService $envelopeService,
67
                protected EnvelopeFileRelocator $envelopeFileRelocator,
68
                protected FileUploadHelper $uploadHelper,
69
                protected SignRequestService $signRequestService,
70
        ) {
71
        }
52✔
72

73
        /**
74
         * Save files - creates single file or envelope based on files count
75
         *
76
         * @return array{file: FileEntity, children: list<FileEntity>}
77
         */
78
        public function saveFiles(array $data): array {
79
                if (empty($data['files'])) {
3✔
80
                        throw new LibresignException('Files parameter is required');
×
81
                }
82

83
                if (count($data['files']) === 1) {
3✔
84
                        $fileData = $data['files'][0];
2✔
85

86
                        $saveData = [
2✔
87
                                'name' => $data['name'] ?? $fileData['name'] ?? '',
2✔
88
                                'userManager' => $data['userManager'],
2✔
89
                                'status' => FileStatus::DRAFT->value,
2✔
90
                                'settings' => $data['settings'],
2✔
91
                        ];
2✔
92

93
                        if (isset($fileData['uploadedFile'])) {
2✔
94
                                $saveData['uploadedFile'] = $fileData['uploadedFile'];
1✔
95
                        } elseif (isset($fileData['fileNode'])) {
1✔
96
                                $saveData['file'] = ['fileNode' => $fileData['fileNode']];
×
97
                        } else {
98
                                $saveData['file'] = $fileData;
1✔
99
                        }
100

101
                        $savedFile = $this->save($saveData);
2✔
102

103
                        return [
2✔
104
                                'file' => $savedFile,
2✔
105
                                'children' => [$savedFile],
2✔
106
                        ];
2✔
107
                }
108

109
                $result = $this->saveEnvelope([
1✔
110
                        'files' => $data['files'],
1✔
111
                        'name' => $data['name'],
1✔
112
                        'userManager' => $data['userManager'],
1✔
113
                        'settings' => $data['settings'],
1✔
114
                        'signers' => $data['signers'] ?? [],
1✔
115
                        'status' => $data['status'] ?? FileStatus::DRAFT->value,
1✔
116
                        'visibleElements' => $data['visibleElements'] ?? [],
1✔
117
                        'signatureFlow' => $data['signatureFlow'] ?? null,
1✔
118
                ]);
1✔
119

120
                return [
1✔
121
                        'file' => $result['envelope'],
1✔
122
                        'children' => $result['files'],
1✔
123
                ];
1✔
124
        }
125

126
        public function save(array $data): FileEntity {
127
                $file = $this->saveFile($data);
14✔
128
                if (!isset($data['status'])) {
14✔
129
                        $data['status'] = $file->getStatus();
12✔
130
                }
131
                $this->sequentialSigningService->setFile($file);
14✔
132
                $this->associateToSigners($data, $file);
14✔
133
                $this->propagateSignersToChildren($file, $data);
14✔
134
                $this->saveVisibleElements($data, $file);
14✔
135

136
                return $file;
14✔
137
        }
138

139
        private function propagateSignersToChildren(FileEntity $envelope, array $data): void {
140
                if ($envelope->getNodeType() !== 'envelope' || empty($data['signers'])) {
14✔
141
                        return;
14✔
142
                }
143

144
                $children = $this->fileMapper->getChildrenFiles($envelope->getId());
×
145

146
                $dataWithoutNotification = $data;
×
147
                foreach ($dataWithoutNotification['signers'] as &$signer) {
×
148
                        $signer['notify'] = 0;
×
149
                }
150

151
                foreach ($children as $child) {
×
152
                        $this->identifyMethod->clearCache();
×
153
                        $this->sequentialSigningService->setFile($child);
×
154
                        $this->associateToSigners($dataWithoutNotification, $child);
×
155
                }
156

157
                if ($envelope->getStatus() > FileStatus::DRAFT->value) {
×
158
                        $this->fileStatusService->propagateStatusToChildren($envelope->getId(), $envelope->getStatus());
×
159
                }
160
        }
161

162
        public function saveEnvelope(array $data): array {
163
                $this->envelopeService->validateEnvelopeConstraints(count($data['files']));
1✔
164

165
                $envelopeName = $data['name'] ?: $this->l10n->t('Envelope %s', [date('Y-m-d H:i:s')]);
1✔
166
                $userManager = $data['userManager'] ?? null;
1✔
167
                $userId = $userManager instanceof IUser ? $userManager->getUID() : null;
1✔
168
                $filesCount = count($data['files']);
1✔
169

170
                $envelope = null;
1✔
171
                $files = [];
1✔
172
                $createdNodes = [];
1✔
173

174
                try {
175
                        $envelopePath = $data['settings']['path'] ?? null;
1✔
176
                        $envelope = $this->envelopeService->createEnvelope($envelopeName, $userId, $filesCount, $envelopePath);
1✔
177

178
                        $envelopeFolder = $this->envelopeService->getEnvelopeFolder($envelope);
1✔
179
                        $envelopeSettings = array_merge($data['settings'] ?? [], [
1✔
180
                                'envelopeFolderId' => $envelopeFolder->getId(),
1✔
181
                        ]);
1✔
182

183
                        foreach ($data['files'] as $fileData) {
1✔
184
                                $node = $this->processFileData($fileData, $userManager, $envelopeSettings);
1✔
185
                                $createdNodes[] = $node;
1✔
186

187
                                $fileData['node'] = $node;
1✔
188
                                $fileEntity = $this->createFileForEnvelope($fileData, $userManager, $envelopeSettings);
1✔
189
                                $this->envelopeService->addFileToEnvelope($envelope->getId(), $fileEntity);
1✔
190
                                $files[] = $fileEntity;
1✔
191
                        }
192

193
                        if (!empty($data['signers'])) {
1✔
194
                                $this->sequentialSigningService->setFile($envelope);
×
195
                                $this->associateToSigners($data, $envelope);
×
196
                                $this->propagateSignersToChildren($envelope, $data);
×
197
                        }
198

199
                        return [
1✔
200
                                'envelope' => $envelope,
1✔
201
                                'files' => $files,
1✔
202
                        ];
1✔
203
                } catch (\Throwable $e) {
×
204
                        $this->rollbackEnvelopeCreation($envelope, $files, $createdNodes);
×
205
                        throw $e;
×
206
                }
207
        }
208

209
        private function processFileData(array $fileData, ?IUser $userManager, array $settings): Node {
210
                $name = $this->requireFileName($fileData);
1✔
211

212
                if (isset($fileData['uploadedFile'])) {
1✔
213
                        $sourceNode = $this->fileService->getNodeFromData([
×
214
                                'userManager' => $userManager,
×
215
                                'name' => $name,
×
216
                                'uploadedFile' => $fileData['uploadedFile'],
×
217
                                'settings' => $settings,
×
218
                        ]);
×
219
                } else {
220
                        $sourceNode = $this->fileService->getNodeFromData([
1✔
221
                                'userManager' => $userManager,
1✔
222
                                'name' => $name,
1✔
223
                                'file' => $fileData['file'] ?? $fileData,
1✔
224
                                'settings' => $settings,
1✔
225
                        ]);
1✔
226
                }
227

228
                if (isset($settings['envelopeFolderId'])) {
1✔
229
                        return $this->envelopeFileRelocator->ensureFileInEnvelopeFolder(
1✔
230
                                $sourceNode,
1✔
231
                                $settings['envelopeFolderId'],
1✔
232
                                $userManager,
1✔
233
                        );
1✔
234
                }
235

236
                return $sourceNode;
×
237
        }
238

239
        private function requireFileName(array $fileData): string {
240
                $name = trim((string)($fileData['name'] ?? ''));
1✔
241
                if ($name === '') {
1✔
242
                        throw new LibresignException($this->l10n->t('File name is required'));
×
243
                }
244
                return $name;
1✔
245
        }
246

247
        private function rollbackEnvelopeCreation(?FileEntity $envelope, array $files, array $createdNodes): void {
248
                $this->rollbackCreatedNodes($createdNodes);
×
249
                $this->rollbackCreatedFiles($files);
×
250
                $this->rollbackEnvelope($envelope);
×
251
        }
252

253
        private function rollbackCreatedNodes(array $nodes): void {
254
                foreach ($nodes as $node) {
×
255
                        try {
256
                                $node->delete();
×
257
                        } catch (\Throwable $deleteError) {
×
258
                                $this->logger->error('Failed to rollback created node in envelope', [
×
259
                                        'nodeId' => $node->getId(),
×
260
                                        'error' => $deleteError->getMessage(),
×
261
                                ]);
×
262
                        }
263
                }
264
        }
265

266
        private function rollbackCreatedFiles(array $files): void {
267
                foreach ($files as $file) {
×
268
                        try {
269
                                $this->fileMapper->delete($file);
×
270
                        } catch (\Throwable $deleteError) {
×
271
                                $this->logger->error('Failed to rollback created file entity in envelope', [
×
272
                                        'fileId' => $file->getId(),
×
273
                                        'error' => $deleteError->getMessage(),
×
274
                                ]);
×
275
                        }
276
                }
277
        }
278

279
        private function rollbackEnvelope(?FileEntity $envelope): void {
280
                if ($envelope === null) {
×
281
                        return;
×
282
                }
283

284
                try {
285
                        $this->fileMapper->delete($envelope);
×
286
                } catch (\Throwable $deleteError) {
×
287
                        $this->logger->error('Failed to rollback created envelope', [
×
288
                                'envelopeId' => $envelope->getId(),
×
289
                                'error' => $deleteError->getMessage(),
×
290
                        ]);
×
291
                }
292
        }
293

294
        private function createFileForEnvelope(array $fileData, ?IUser $userManager, array $settings): FileEntity {
295
                if (!isset($fileData['node'])) {
1✔
296
                        throw new \InvalidArgumentException('Node not provided in file data');
×
297
                }
298

299
                $node = $fileData['node'];
1✔
300
                $fileName = $fileData['name'] ?? $node->getName();
1✔
301

302
                return $this->saveFile([
1✔
303
                        'file' => ['fileNode' => $node],
1✔
304
                        'name' => $fileName,
1✔
305
                        'userManager' => $userManager,
1✔
306
                        'status' => FileStatus::DRAFT->value,
1✔
307
                        'settings' => $settings,
1✔
308
                ]);
1✔
309
        }
310

311
        /**
312
         * Save file data
313
         *
314
         * @param array{?userManager: IUser, ?signRequest: SignRequestEntity, name: string, callback: string, uuid?: ?string, status: int, file?: array{fileId?: int, fileNode?: Node}} $data
315
         */
316
        public function saveFile(array $data): FileEntity {
317
                if (!empty($data['uuid'])) {
15✔
318
                        $file = $this->fileMapper->getByUuid($data['uuid']);
1✔
319
                        $this->updateSignatureFlowIfAllowed($file, $data);
1✔
320
                        if (!empty($data['name'])) {
1✔
321
                                $file->setName($data['name']);
×
322
                                $this->fileService->update($file);
×
323
                        }
324
                        return $this->fileStatusService->updateFileStatusIfUpgrade($file, $data['status'] ?? 0);
1✔
325
                }
326
                $fileId = null;
15✔
327
                if (isset($data['file']['fileNode']) && $data['file']['fileNode'] instanceof Node) {
15✔
328
                        $fileId = $data['file']['fileNode']->getId();
×
329
                } elseif (!empty($data['file']['nodeId'])) {
15✔
330
                        $fileId = $data['file']['nodeId'];
×
331
                }
332
                if (!is_null($fileId)) {
15✔
333
                        try {
334
                                $file = $this->fileMapper->getByNodeId($fileId);
×
335
                                $this->updateSignatureFlowIfAllowed($file, $data);
×
336
                                return $this->fileStatusService->updateFileStatusIfUpgrade($file, $data['status'] ?? 0);
×
337
                        } catch (\Throwable) {
×
338
                        }
339
                }
340

341
                $node = $this->fileService->getNodeFromData($data);
15✔
342

343
                $file = new FileEntity();
15✔
344
                $file->setNodeId($node->getId());
15✔
345
                if (isset($data['userManager']) && $data['userManager'] instanceof IUser) {
15✔
346
                        $file->setUserId($data['userManager']->getUID());
15✔
347
                } elseif (isset($data['signRequest']) && $data['signRequest'] instanceof SignRequestEntity) {
×
348
                        $signRequestFileId = $data['signRequest']->getFileId();
×
349
                        if ($signRequestFileId) {
×
350
                                $signRequestFile = $this->fileMapper->getById($signRequestFileId);
×
351
                                $file->setUserId($signRequestFile->getUserId());
×
352
                        }
353
                }
354
                $file->setUuid(UUIDUtil::getUUID());
15✔
355
                $file->setCreatedAt(new \DateTime('now', new \DateTimeZone('UTC')));
15✔
356
                $metadata = $this->getFileMetadata($node);
15✔
357
                $name = trim((string)($data['name'] ?? ''));
15✔
358
                if ($name === '') {
15✔
359
                        $name = $node->getName();
×
360
                }
361
                $file->setName($this->removeExtensionFromName($name, $metadata));
15✔
362
                $file->setMetadata($metadata);
15✔
363
                if (!empty($data['callback'])) {
15✔
364
                        $file->setCallback($data['callback']);
×
365
                }
366
                if (isset($data['status'])) {
15✔
367
                        $file->setStatus($data['status']);
2✔
368
                } else {
369
                        $file->setStatus(FileStatus::ABLE_TO_SIGN->value);
13✔
370
                }
371

372
                if (isset($data['parentFileId'])) {
15✔
373
                        $file->setParentFileId($data['parentFileId']);
×
374
                }
375

376
                $this->setSignatureFlow($file, $data);
15✔
377
                $this->setDocMdpLevelFromGlobalConfig($file);
15✔
378

379
                $this->fileMapper->insert($file);
15✔
380
                return $file;
15✔
381
        }
382

383
        private function updateSignatureFlowIfAllowed(FileEntity $file, array $data): void {
384
                $adminFlow = $this->appConfig->getValueString(Application::APP_ID, 'signature_flow', SignatureFlow::NONE->value);
1✔
385
                $adminForcedConfig = $adminFlow !== SignatureFlow::NONE->value;
1✔
386

387
                if ($adminForcedConfig) {
1✔
388
                        $adminFlowEnum = SignatureFlow::from($adminFlow);
×
389
                        if ($file->getSignatureFlowEnum() !== $adminFlowEnum) {
×
390
                                $file->setSignatureFlowEnum($adminFlowEnum);
×
391
                                $this->fileService->update($file);
×
392
                        }
393
                        return;
×
394
                }
395

396
                if (isset($data['signatureFlow']) && !empty($data['signatureFlow'])) {
1✔
397
                        $newFlow = SignatureFlow::from($data['signatureFlow']);
×
398
                        if ($file->getSignatureFlowEnum() !== $newFlow) {
×
399
                                $file->setSignatureFlowEnum($newFlow);
×
400
                                $this->fileService->update($file);
×
401
                        }
402
                }
403
        }
404

405
        private function setSignatureFlow(FileEntity $file, array $data): void {
406
                $adminFlow = $this->appConfig->getValueString(Application::APP_ID, 'signature_flow', SignatureFlow::NONE->value);
15✔
407

408
                if (isset($data['signatureFlow']) && !empty($data['signatureFlow'])) {
15✔
409
                        $file->setSignatureFlowEnum(SignatureFlow::from($data['signatureFlow']));
×
410
                } elseif ($adminFlow !== SignatureFlow::NONE->value) {
15✔
411
                        $file->setSignatureFlowEnum(SignatureFlow::from($adminFlow));
×
412
                } else {
413
                        $file->setSignatureFlowEnum(SignatureFlow::NONE);
15✔
414
                }
415
        }
416

417
        private function setDocMdpLevelFromGlobalConfig(FileEntity $file): void {
418
                if ($this->docMdpConfigService->isEnabled()) {
15✔
419
                        $docmdpLevel = $this->docMdpConfigService->getLevel();
15✔
420
                        $file->setDocmdpLevelEnum($docmdpLevel);
15✔
421
                }
422
        }
423

424
        private function getFileMetadata(\OCP\Files\Node $node): array {
425
                $metadata = [];
18✔
426
                if ($extension = strtolower($node->getExtension())) {
18✔
427
                        $metadata = [
17✔
428
                                'extension' => $extension,
17✔
429
                        ];
17✔
430
                        if ($metadata['extension'] === 'pdf') {
17✔
431
                                $this->pdfMetadataExtractor->setFile($node);
16✔
432
                                $metadata = array_merge(
16✔
433
                                        $metadata,
16✔
434
                                        $this->pdfMetadataExtractor->getPageDimensions()
16✔
435
                                );
16✔
436
                                $metadata['pdfVersion'] = $this->pdfMetadataExtractor->getPdfVersion();
16✔
437
                        }
438
                }
439
                return $metadata;
18✔
440
        }
441

442
        private function removeExtensionFromName(string $name, array $metadata): string {
443
                if (!isset($metadata['extension'])) {
15✔
444
                        return $name;
×
445
                }
446
                $extensionPattern = '/\.' . preg_quote($metadata['extension'], '/') . '$/i';
15✔
447
                $result = preg_replace($extensionPattern, '', $name);
15✔
448
                return $result ?? $name;
15✔
449
        }
450

451
        private function deleteIdentifyMethodIfNotExits(array $signers, FileEntity $file): void {
452
                $normalizedSigners = $this->validateHelper->normalizeRequestSigners($signers);
16✔
453
                $signRequests = $this->signRequestMapper->getByFileId($file->getId());
16✔
454
                foreach ($signRequests as $key => $signRequest) {
16✔
455
                        $identifyMethods = $this->identifyMethod->getIdentifyMethodsFromSignRequestId($signRequest->getId());
3✔
456
                        if (empty($identifyMethods)) {
3✔
457
                                $this->unassociateToUser($file->getId(), $signRequest->getId());
×
458
                                continue;
×
459
                        }
460
                        foreach ($identifyMethods as $methodName => $list) {
3✔
461
                                foreach ($list as $method) {
3✔
462
                                        if (!$this->identifyMethodExists($normalizedSigners, $method)) {
3✔
463
                                                $this->unassociateToUser($file->getId(), $signRequest->getId());
2✔
464
                                                continue 3;
2✔
465
                                        }
466
                                }
467
                        }
468
                }
469
        }
470

471
        private function identifyMethodExists(array $signers, IIdentifyMethod $identifyMethod): bool {
472
                foreach ($signers as $signer) {
3✔
473
                        foreach (($signer['identifyMethods'] ?? []) as $data) {
3✔
474
                                if ($identifyMethod->getEntity()->getIdentifierKey() !== $data['method']) {
3✔
475
                                        continue;
×
476
                                }
477
                                if ($identifyMethod->getEntity()->getIdentifierValue() === $data['value']) {
3✔
478
                                        return true;
1✔
479
                                }
480
                        }
481
                }
482
                return false;
2✔
483
        }
484

485
        /**
486
         * @return SignRequestEntity[]
487
         *
488
         * @psalm-return list<SignRequestEntity>
489
         */
490
        private function associateToSigners(array $data, FileEntity $file): array {
491
                $return = [];
15✔
492
                if (!empty($data['signers'])) {
15✔
493
                        $normalizedSigners = $this->validateHelper->normalizeRequestSigners($data['signers']);
14✔
494
                        $this->deleteIdentifyMethodIfNotExits($normalizedSigners, $file);
14✔
495
                        $this->identifyMethod->clearCache();
14✔
496

497
                        $this->sequentialSigningService->resetOrderCounter();
14✔
498
                        $fileStatus = $data['status'] ?? null;
14✔
499

500
                        foreach ($normalizedSigners as $signer) {
14✔
501
                                $userProvidedOrder = isset($signer['signingOrder']) ? (int)$signer['signingOrder'] : null;
14✔
502
                                $signingOrder = $this->sequentialSigningService->determineSigningOrder($userProvidedOrder);
14✔
503
                                $signerStatus = $signer['status'] ?? null;
14✔
504
                                $shouldNotify = !isset($signer['notify']) || $signer['notify'] !== 0;
14✔
505

506
                                foreach ($signer['identifyMethods'] as $identifyMethod) {
14✔
507
                                        $return[] = $this->signRequestService->createOrUpdateSignRequest(
14✔
508
                                                identifyMethods: [
14✔
509
                                                        $identifyMethod['method'] => $identifyMethod['value'],
14✔
510
                                                ],
14✔
511
                                                displayName: $signer['displayName'] ?? '',
14✔
512
                                                description: $signer['description'] ?? '',
14✔
513
                                                notify: $shouldNotify,
14✔
514
                                                fileId: $file->getId(),
14✔
515
                                                signingOrder: $signingOrder,
14✔
516
                                                fileStatus: $fileStatus,
14✔
517
                                                signerStatus: $signerStatus,
14✔
518
                                        );
14✔
519
                                }
520
                        }
521
                }
522
                return $return;
15✔
523
        }
524

525

526

527
        private function saveVisibleElements(array $data, FileEntity $file): array {
528
                if (empty($data['visibleElements'])) {
19✔
529
                        return [];
15✔
530
                }
531
                $persisted = [];
4✔
532
                foreach ($data['visibleElements'] as $element) {
4✔
533
                        if ($file->isEnvelope() && !empty($element['signRequestId'])) {
4✔
534
                                $envelopeSignRequest = $this->signRequestMapper->getById((int)$element['signRequestId']);
2✔
535
                                // Only translate if the provided SR belongs to the envelope itself
536
                                if ($envelopeSignRequest && $envelopeSignRequest->getFileId() === $file->getId()) {
2✔
537
                                        $childrenSrs = $this->signRequestMapper->getByEnvelopeChildrenAndIdentifyMethod($file->getId(), (int)$element['signRequestId']);
1✔
538
                                        foreach ($childrenSrs as $childSr) {
1✔
539
                                                if ($childSr->getFileId() === (int)$element['fileId']) {
1✔
540
                                                        $element['signRequestId'] = $childSr->getId();
1✔
541
                                                        break;
1✔
542
                                                }
543
                                        }
544
                                }
545
                        }
546

547
                        $persisted[] = $this->fileElementService->saveVisibleElement($element);
4✔
548
                }
549
                return $persisted;
4✔
550
        }
551

552
        public function validateNewRequestToFile(array $data): void {
553
                $this->validateNewFile($data);
7✔
554
                $this->validateSigners($data);
6✔
555
                $this->validateHelper->validateFileStatus($data);
2✔
556
        }
557

558
        public function validateNewFile(array $data): void {
559
                if (empty($data['name'])) {
7✔
560
                        throw new \Exception($this->l10n->t('File name is required'));
1✔
561
                }
562
                $this->validateHelper->validateNewFile($data);
6✔
563
        }
564

565
        public function validateSigners(array $data): void {
566
                if (empty($data['signers'])) {
8✔
567
                        if (($data['status'] ?? FileStatus::ABLE_TO_SIGN->value) === FileStatus::DRAFT->value) {
3✔
568
                                return;
×
569
                        }
570
                        throw new \Exception($this->l10n->t('Empty signers list'));
3✔
571
                }
572
                if (!is_array($data['signers'])) {
5✔
573
                        // TRANSLATION This message will be displayed when the request to API with the key signers has a value that is not an array
574
                        throw new \Exception($this->l10n->t('Signers list needs to be an array'));
1✔
575
                }
576

577
                $this->validateHelper->validateIdentifySigners($data);
4✔
578
                $normalizedSigners = $this->validateHelper->normalizeRequestSigners($data['signers']);
3✔
579

580
                foreach ($normalizedSigners as $signer) {
3✔
581
                        $this->identifyMethod->setAllEntityData($signer);
1✔
582
                }
583
        }
584

585

586

587
        public function unassociateToUser(int $fileId, int $signRequestId): void {
588
                $file = $this->fileMapper->getById($fileId);
2✔
589
                $signRequest = $this->signRequestMapper->getByFileIdAndSignRequestId($fileId, $signRequestId);
2✔
590
                $deletedOrder = $signRequest->getSigningOrder();
2✔
591
                $groupedIdentifyMethods = $this->identifyMethod->getIdentifyMethodsFromSignRequestId($signRequestId);
2✔
592

593
                $this->dispatchCancellationEventIfNeeded($signRequest, $file, $groupedIdentifyMethods);
2✔
594

595
                try {
596
                        $this->signRequestMapper->delete($signRequest);
2✔
597
                        $this->identifyMethod->deleteBySignRequestId($signRequestId);
2✔
598
                        $visibleElements = $this->fileElementMapper->getByFileIdAndSignRequestId($fileId, $signRequestId);
2✔
599
                        foreach ($visibleElements as $visibleElement) {
2✔
600
                                $this->fileElementMapper->delete($visibleElement);
×
601
                        }
602

603
                        $this->sequentialSigningService
2✔
604
                                ->setFile($file)
2✔
605
                                ->reorderAfterDeletion($file->getId(), $deletedOrder);
2✔
606

607
                        $this->propagateSignerDeletionToChildren($file, $signRequest);
2✔
608
                        $this->revertStatusToDraftIfNoSignersRemain($file);
2✔
609
                } catch (\Throwable) {
×
610
                }
611
        }
612

613
        private function revertStatusToDraftIfNoSignersRemain(FileEntity $file): void {
614
                $remaining = $this->signRequestMapper->getByFileId($file->getId());
2✔
615
                if (empty($remaining)) {
2✔
616
                        $file->setStatus(FileStatus::DRAFT->value);
2✔
617
                        $this->fileStatusService->update($file);
2✔
618
                }
619
        }
620

621
        private function propagateSignerDeletionToChildren(FileEntity $envelope, SignRequestEntity $deletedSignRequest): void {
622
                if ($envelope->getNodeType() !== 'envelope') {
2✔
623
                        return;
2✔
624
                }
625

626
                $children = $this->fileMapper->getChildrenFiles($envelope->getId());
×
627

628
                $identifyMethods = $this->identifyMethod->getIdentifyMethodsFromSignRequestId($deletedSignRequest->getId());
×
629
                if (empty($identifyMethods)) {
×
630
                        return;
×
631
                }
632

633
                foreach ($children as $child) {
×
634
                        try {
635
                                $this->identifyMethod->clearCache();
×
636
                                $childSignRequest = $this->signRequestService->getSignRequestByIdentifyMethod(
×
637
                                        current(reset($identifyMethods)),
×
638
                                        $child->getId()
×
639
                                );
×
640

641
                                if ($childSignRequest->getId()) {
×
642
                                        $this->unassociateToUser($child->getId(), $childSignRequest->getId());
×
643
                                }
NEW
644
                        } catch (\Throwable $e) {
×
645
                                continue;
×
646
                        }
647
                }
648
        }
649

650
        private function dispatchCancellationEventIfNeeded(
651
                SignRequestEntity $signRequest,
652
                FileEntity $file,
653
                array $groupedIdentifyMethods,
654
        ): void {
655
                if ($signRequest->getStatus() !== \OCA\Libresign\Enum\SignRequestStatus::ABLE_TO_SIGN->value) {
2✔
656
                        return;
×
657
                }
658

659
                try {
660
                        foreach ($groupedIdentifyMethods as $identifyMethods) {
2✔
661
                                foreach ($identifyMethods as $identifyMethod) {
2✔
662
                                        $event = new SignRequestCanceledEvent(
2✔
663
                                                $signRequest,
2✔
664
                                                $file,
2✔
665
                                                $identifyMethod,
2✔
666
                                        );
2✔
667
                                        $this->eventDispatcher->dispatchTyped($event);
2✔
668
                                }
669
                        }
670
                } catch (\Throwable $e) {
×
671
                        $this->logger->error('Error dispatching SignRequestCanceledEvent: ' . $e->getMessage(), ['exception' => $e]);
×
672
                }
673
        }
674

675
        public function deleteRequestSignature(array $data): void {
676
                if (!empty($data['uuid'])) {
1✔
677
                        $signatures = $this->signRequestMapper->getByFileUuid($data['uuid']);
×
678
                        $fileData = $this->fileMapper->getByUuid($data['uuid']);
×
679
                } elseif (!empty($data['file']['fileId'])) {
1✔
680
                        $fileData = $this->fileMapper->getById($data['file']['fileId']);
1✔
681
                        $signatures = $this->signRequestMapper->getByFileId($fileData->getId());
1✔
682
                } else {
683
                        throw new \Exception($this->l10n->t('Please provide either UUID or File object'));
×
684
                }
685
                foreach ($signatures as $signRequest) {
1✔
686
                        $this->identifyMethod->deleteBySignRequestId($signRequest->getId());
1✔
687
                        $this->signRequestMapper->delete($signRequest);
1✔
688
                }
689
                $this->fileMapper->delete($fileData);
1✔
690
                $this->fileElementService->deleteVisibleElements($fileData->getId());
1✔
691
        }
692
}
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