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

LibreSign / libresign / 20621495436

31 Dec 2025 02:59PM UTC coverage: 44.647%. First build
20621495436

Pull #6256

github

web-flow
Merge 4343635f1 into 27812ed76
Pull Request #6256: feat: add dashboard pending signatures widget

57 of 239 new or added lines in 16 files covered. (23.85%)

6618 of 14823 relevant lines covered (44.65%)

5.05 hits per line

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

49.58
/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\SignatureFlow;
19
use OCA\Libresign\Events\SignRequestCanceledEvent;
20
use OCA\Libresign\Exception\LibresignException;
21
use OCA\Libresign\Handler\DocMdpHandler;
22
use OCA\Libresign\Helper\FileUploadHelper;
23
use OCA\Libresign\Helper\ValidateHelper;
24
use OCA\Libresign\Service\IdentifyMethod\IIdentifyMethod;
25
use OCP\EventDispatcher\IEventDispatcher;
26
use OCP\Files\IMimeTypeDetector;
27
use OCP\Files\Node;
28
use OCP\Http\Client\IClientService;
29
use OCP\IAppConfig;
30
use OCP\IL10N;
31
use OCP\IUser;
32
use OCP\IUserManager;
33
use Psr\Log\LoggerInterface;
34
use Sabre\DAV\UUIDUtil;
35

36
class RequestSignatureService {
37

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

66
        /**
67
         * Save files - creates single file or envelope based on files count
68
         *
69
         * @param array{files: array, name: string, settings: array, userManager: IUser} $data
70
         * @return array{file: FileEntity, children: FileEntity[]}
71
         */
72
        public function saveFiles(array $data): array {
73
                if (empty($data['files'])) {
1✔
74
                        throw new LibresignException('Files parameter is required');
×
75
                }
76

77
                if (count($data['files']) === 1) {
1✔
78
                        $fileData = $data['files'][0];
1✔
79

80
                        $saveData = [
1✔
81
                                'name' => $data['name'] ?? $fileData['name'] ?? '',
1✔
82
                                'userManager' => $data['userManager'],
1✔
83
                                'status' => FileEntity::STATUS_DRAFT,
1✔
84
                                'settings' => $data['settings'],
1✔
85
                        ];
1✔
86

87
                        if (isset($fileData['uploadedFile'])) {
1✔
88
                                $saveData['uploadedFile'] = $fileData['uploadedFile'];
×
89
                        } elseif (isset($fileData['fileNode'])) {
1✔
90
                                $saveData['file'] = ['fileNode' => $fileData['fileNode']];
×
91
                        } else {
92
                                $saveData['file'] = $fileData;
1✔
93
                        }
94

95
                        $savedFile = $this->save($saveData);
1✔
96

97
                        return [
1✔
98
                                'file' => $savedFile,
1✔
99
                                'children' => [$savedFile],
1✔
100
                        ];
1✔
101
                }
102

103
                $result = $this->saveEnvelope([
×
104
                        'files' => $data['files'],
×
105
                        'name' => $data['name'],
×
106
                        'userManager' => $data['userManager'],
×
107
                        'settings' => $data['settings'],
×
108
                ]);
×
109

110
                return [
×
111
                        'file' => $result['envelope'],
×
112
                        'children' => $result['files'],
×
113
                ];
×
114
        }
115

116
        public function save(array $data): FileEntity {
117
                $file = $this->saveFile($data);
14✔
118
                if (!isset($data['status'])) {
14✔
119
                        $data['status'] = $file->getStatus();
12✔
120
                }
121
                $this->sequentialSigningService->setFile($file);
14✔
122
                $this->associateToSigners($data, $file);
14✔
123
                $this->propagateSignersToChildren($file, $data);
14✔
124
                $this->saveVisibleElements($data, $file);
14✔
125

126
                return $file;
14✔
127
        }
128

129
        private function propagateSignersToChildren(FileEntity $envelope, array $data): void {
130
                if ($envelope->getNodeType() !== 'envelope' || empty($data['users'])) {
14✔
131
                        return;
14✔
132
                }
133

134
                $children = $this->fileMapper->getChildrenFiles($envelope->getId());
×
135

136
                $dataWithoutNotification = $data;
×
137
                foreach ($dataWithoutNotification['users'] as &$user) {
×
138
                        $user['notify'] = 0;
×
139
                }
140

141
                foreach ($children as $child) {
×
142
                        $this->identifyMethod->clearCache();
×
143
                        $this->sequentialSigningService->setFile($child);
×
144
                        $this->associateToSigners($dataWithoutNotification, $child);
×
145
                }
146

147
                if ($envelope->getStatus() > FileEntity::STATUS_DRAFT) {
×
148
                        $this->fileStatusService->propagateStatusToChildren($envelope->getId(), $envelope->getStatus());
×
149
                }
150
        }
151

152
        public function saveEnvelope(array $data): array {
153
                $this->envelopeService->validateEnvelopeConstraints(count($data['files']));
×
154

155
                $envelopeName = $data['name'] ?: $this->l10n->t('Envelope %s', [date('Y-m-d H:i:s')]);
×
156
                $userManager = $data['userManager'] ?? null;
×
157
                $userId = $userManager instanceof IUser ? $userManager->getUID() : null;
×
158
                $filesCount = count($data['files']);
×
159

160
                $envelope = null;
×
161
                $files = [];
×
162
                $createdNodes = [];
×
163

164
                try {
165
                        $envelope = $this->envelopeService->createEnvelope($envelopeName, $userId, $filesCount);
×
166

NEW
167
                        $envelopeFolder = $this->envelopeService->getEnvelopeFolder($envelope);
×
168
                        $envelopeSettings = array_merge($data['settings'] ?? [], [
×
NEW
169
                                'envelopeFolderId' => $envelopeFolder->getId(),
×
170
                        ]);
×
171

172
                        foreach ($data['files'] as $fileData) {
×
173
                                $node = $this->processFileData($fileData, $userManager, $envelopeSettings);
×
174
                                $createdNodes[] = $node;
×
175

176
                                $fileData['node'] = $node;
×
177
                                $fileEntity = $this->createFileForEnvelope($fileData, $userManager, $envelopeSettings);
×
178
                                $this->envelopeService->addFileToEnvelope($envelope->getId(), $fileEntity);
×
179
                                $files[] = $fileEntity;
×
180
                        }
181

182
                        return [
×
183
                                'envelope' => $envelope,
×
184
                                'files' => $files,
×
185
                        ];
×
186
                } catch (\Throwable $e) {
×
187
                        $this->rollbackEnvelopeCreation($envelope, $files, $createdNodes);
×
188
                        throw $e;
×
189
                }
190
        }
191

192
        private function processFileData(array $fileData, ?IUser $userManager, array $settings): Node {
193
                if (isset($fileData['uploadedFile'])) {
×
194
                        return $this->fileService->getNodeFromData([
×
195
                                'userManager' => $userManager,
×
196
                                'name' => $fileData['name'] ?? '',
×
197
                                'uploadedFile' => $fileData['uploadedFile'],
×
198
                                'settings' => $settings,
×
199
                        ]);
×
200
                }
201

202
                return $this->fileService->getNodeFromData([
×
203
                        'userManager' => $userManager,
×
204
                        'name' => $fileData['name'] ?? '',
×
205
                        'file' => $fileData,
×
206
                        'settings' => $settings,
×
207
                ]);
×
208
        }
209

210
        private function rollbackEnvelopeCreation(?FileEntity $envelope, array $files, array $createdNodes): void {
211
                $this->rollbackCreatedNodes($createdNodes);
×
212
                $this->rollbackCreatedFiles($files);
×
213
                $this->rollbackEnvelope($envelope);
×
214
        }
215

216
        private function rollbackCreatedNodes(array $nodes): void {
217
                foreach ($nodes as $node) {
×
218
                        try {
219
                                $node->delete();
×
220
                        } catch (\Throwable $deleteError) {
×
221
                                $this->logger->error('Failed to rollback created node in envelope', [
×
222
                                        'nodeId' => $node->getId(),
×
223
                                        'error' => $deleteError->getMessage(),
×
224
                                ]);
×
225
                        }
226
                }
227
        }
228

229
        private function rollbackCreatedFiles(array $files): void {
230
                foreach ($files as $file) {
×
231
                        try {
232
                                $this->fileMapper->delete($file);
×
233
                        } catch (\Throwable $deleteError) {
×
234
                                $this->logger->error('Failed to rollback created file entity in envelope', [
×
235
                                        'fileId' => $file->getId(),
×
236
                                        'error' => $deleteError->getMessage(),
×
237
                                ]);
×
238
                        }
239
                }
240
        }
241

242
        private function rollbackEnvelope(?FileEntity $envelope): void {
243
                if ($envelope === null) {
×
244
                        return;
×
245
                }
246

247
                try {
248
                        $this->fileMapper->delete($envelope);
×
249
                } catch (\Throwable $deleteError) {
×
250
                        $this->logger->error('Failed to rollback created envelope', [
×
251
                                'envelopeId' => $envelope->getId(),
×
252
                                'error' => $deleteError->getMessage(),
×
253
                        ]);
×
254
                }
255
        }
256

257
        private function createFileForEnvelope(array $fileData, ?IUser $userManager, array $settings): FileEntity {
258
                if (!isset($fileData['node'])) {
×
259
                        throw new \InvalidArgumentException('Node not provided in file data');
×
260
                }
261

262
                $node = $fileData['node'];
×
263
                $fileName = $fileData['name'] ?? $node->getName();
×
264

265
                return $this->saveFile([
×
266
                        'file' => ['fileNode' => $node],
×
267
                        'name' => $fileName,
×
268
                        'userManager' => $userManager,
×
269
                        'status' => FileEntity::STATUS_DRAFT,
×
270
                        'settings' => $settings,
×
271
                ]);
×
272
        }
273

274
        /**
275
         * Save file data
276
         *
277
         * @param array{?userManager: IUser, ?signRequest: SignRequest, name: string, callback: string, uuid?: ?string, status: int, file?: array{fileId?: int, fileNode?: Node}} $data
278
         */
279
        public function saveFile(array $data): FileEntity {
280
                if (!empty($data['uuid'])) {
15✔
281
                        $file = $this->fileMapper->getByUuid($data['uuid']);
1✔
282
                        $this->updateSignatureFlowIfAllowed($file, $data);
1✔
283
                        if (!empty($data['name'])) {
1✔
284
                                $file->setName($data['name']);
×
285
                                $this->fileMapper->update($file);
×
286
                        }
287
                        return $this->fileStatusService->updateFileStatusIfUpgrade($file, $data['status'] ?? 0);
1✔
288
                }
289
                $fileId = null;
15✔
290
                if (isset($data['file']['fileNode']) && $data['file']['fileNode'] instanceof Node) {
15✔
291
                        $fileId = $data['file']['fileNode']->getId();
×
292
                } elseif (!empty($data['file']['fileId'])) {
15✔
293
                        $fileId = $data['file']['fileId'];
×
294
                }
295
                if (!is_null($fileId)) {
15✔
296
                        try {
297
                                $file = $this->fileMapper->getByNodeId($fileId);
×
298
                                $this->updateSignatureFlowIfAllowed($file, $data);
×
299
                                return $this->fileStatusService->updateFileStatusIfUpgrade($file, $data['status'] ?? 0);
×
300
                        } catch (\Throwable) {
×
301
                        }
302
                }
303

304
                $node = $this->fileService->getNodeFromData($data);
15✔
305

306
                $file = new FileEntity();
15✔
307
                $file->setNodeId($node->getId());
15✔
308
                if ($data['userManager'] instanceof IUser) {
15✔
309
                        $file->setUserId($data['userManager']->getUID());
15✔
310
                } elseif ($data['signRequest'] instanceof SignRequestEntity) {
×
311
                        $file->setSignRequestId($data['signRequest']->getId());
×
312
                }
313
                $file->setUuid(UUIDUtil::getUUID());
15✔
314
                $file->setCreatedAt(new \DateTime('now', new \DateTimeZone('UTC')));
15✔
315
                $metadata = $this->getFileMetadata($node);
15✔
316
                $name = trim((string)($data['name'] ?? ''));
15✔
317
                if ($name === '') {
15✔
318
                        $name = $node->getName();
×
319
                }
320
                $file->setName($this->removeExtensionFromName($name, $metadata));
15✔
321
                $file->setMetadata($metadata);
15✔
322
                if (!empty($data['callback'])) {
15✔
323
                        $file->setCallback($data['callback']);
×
324
                }
325
                if (isset($data['status'])) {
15✔
326
                        $file->setStatus($data['status']);
2✔
327
                } else {
328
                        $file->setStatus(FileEntity::STATUS_ABLE_TO_SIGN);
13✔
329
                }
330

331
                if (isset($data['parentFileId'])) {
15✔
332
                        $file->setParentFileId($data['parentFileId']);
×
333
                }
334

335
                $this->setSignatureFlow($file, $data);
15✔
336
                $this->setDocMdpLevelFromGlobalConfig($file);
15✔
337

338
                $this->fileMapper->insert($file);
15✔
339
                return $file;
15✔
340
        }
341

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

346
                if ($adminForcedConfig) {
1✔
347
                        $adminFlowEnum = SignatureFlow::from($adminFlow);
×
348
                        if ($file->getSignatureFlowEnum() !== $adminFlowEnum) {
×
349
                                $file->setSignatureFlowEnum($adminFlowEnum);
×
350
                                $this->fileMapper->update($file);
×
351
                        }
352
                        return;
×
353
                }
354

355
                if (isset($data['signatureFlow']) && !empty($data['signatureFlow'])) {
1✔
356
                        $newFlow = SignatureFlow::from($data['signatureFlow']);
×
357
                        if ($file->getSignatureFlowEnum() !== $newFlow) {
×
358
                                $file->setSignatureFlowEnum($newFlow);
×
359
                                $this->fileMapper->update($file);
×
360
                        }
361
                }
362
        }
363

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

367
                if (isset($data['signatureFlow']) && !empty($data['signatureFlow'])) {
15✔
368
                        $file->setSignatureFlowEnum(SignatureFlow::from($data['signatureFlow']));
×
369
                } elseif ($adminFlow !== SignatureFlow::NONE->value) {
15✔
370
                        $file->setSignatureFlowEnum(SignatureFlow::from($adminFlow));
×
371
                } else {
372
                        $file->setSignatureFlowEnum(SignatureFlow::NONE);
15✔
373
                }
374
        }
375

376
        private function setDocMdpLevelFromGlobalConfig(FileEntity $file): void {
377
                if ($this->docMdpConfigService->isEnabled()) {
15✔
378
                        $docmdpLevel = $this->docMdpConfigService->getLevel();
×
379
                        $file->setDocmdpLevelEnum($docmdpLevel);
×
380
                }
381
        }
382

383
        private function getFileMetadata(\OCP\Files\Node $node): array {
384
                $metadata = [];
18✔
385
                if ($extension = strtolower($node->getExtension())) {
18✔
386
                        $metadata = [
17✔
387
                                'extension' => $extension,
17✔
388
                        ];
17✔
389
                        if ($metadata['extension'] === 'pdf') {
17✔
390
                                $pdfParser = $this->pdfParserService->setFile($node);
16✔
391
                                $metadata = array_merge(
16✔
392
                                        $metadata,
16✔
393
                                        $pdfParser->getPageDimensions()
16✔
394
                                );
16✔
395
                                $metadata['pdfVersion'] = $pdfParser->getPdfVersion();
16✔
396
                        }
397
                }
398
                return $metadata;
18✔
399
        }
400

401
        private function removeExtensionFromName(string $name, array $metadata): string {
402
                if (!isset($metadata['extension'])) {
15✔
403
                        return $name;
×
404
                }
405
                $extensionPattern = '/\.' . preg_quote($metadata['extension'], '/') . '$/i';
15✔
406
                $result = preg_replace($extensionPattern, '', $name);
15✔
407
                return $result ?? $name;
15✔
408
        }
409

410
        private function deleteIdentifyMethodIfNotExits(array $users, FileEntity $file): void {
411
                $signRequests = $this->signRequestMapper->getByFileId($file->getId());
13✔
412
                foreach ($signRequests as $key => $signRequest) {
13✔
413
                        $identifyMethods = $this->identifyMethod->getIdentifyMethodsFromSignRequestId($signRequest->getId());
1✔
414
                        if (empty($identifyMethods)) {
1✔
415
                                $this->unassociateToUser($file->getId(), $signRequest->getId());
×
416
                                continue;
×
417
                        }
418
                        foreach ($identifyMethods as $methodName => $list) {
1✔
419
                                foreach ($list as $method) {
1✔
420
                                        $exists[$key]['identify'][$methodName] = $method->getEntity()->getIdentifierValue();
1✔
421
                                        if (!$this->identifyMethodExists($users, $method)) {
1✔
422
                                                $this->unassociateToUser($file->getId(), $signRequest->getId());
1✔
423
                                                continue 3;
1✔
424
                                        }
425
                                }
426
                        }
427
                }
428
        }
429

430
        private function identifyMethodExists(array $users, IIdentifyMethod $identifyMethod): bool {
431
                foreach ($users as $user) {
1✔
432
                        if (!empty($user['identifyMethods'])) {
1✔
433
                                foreach ($user['identifyMethods'] as $data) {
×
434
                                        if ($identifyMethod->getEntity()->getIdentifierKey() !== $data['method']) {
×
435
                                                continue;
×
436
                                        }
437
                                        if ($identifyMethod->getEntity()->getIdentifierValue() === $data['value']) {
×
438
                                                return true;
×
439
                                        }
440
                                }
441
                        } else {
442
                                foreach ($user['identify'] as $method => $value) {
1✔
443
                                        if ($identifyMethod->getEntity()->getIdentifierKey() !== $method) {
1✔
444
                                                continue;
×
445
                                        }
446
                                        if ($identifyMethod->getEntity()->getIdentifierValue() === $value) {
1✔
447
                                                return true;
×
448
                                        }
449
                                }
450
                        }
451
                }
452
                return false;
1✔
453
        }
454

455
        /**
456
         * @return SignRequestEntity[]
457
         *
458
         * @psalm-return list<SignRequestEntity>
459
         */
460
        private function associateToSigners(array $data, FileEntity $file): array {
461
                $return = [];
14✔
462
                if (!empty($data['users'])) {
14✔
463
                        $this->deleteIdentifyMethodIfNotExits($data['users'], $file);
13✔
464

465
                        $this->sequentialSigningService->resetOrderCounter();
13✔
466
                        $fileStatus = $data['status'] ?? null;
13✔
467

468
                        foreach ($data['users'] as $user) {
13✔
469
                                $userProvidedOrder = isset($user['signingOrder']) ? (int)$user['signingOrder'] : null;
13✔
470
                                $signingOrder = $this->sequentialSigningService->determineSigningOrder($userProvidedOrder);
13✔
471
                                $signerStatus = $user['status'] ?? null;
13✔
472
                                $shouldNotify = !isset($user['notify']) || $user['notify'] !== 0;
13✔
473

474
                                if (isset($user['identifyMethods'])) {
13✔
475
                                        foreach ($user['identifyMethods'] as $identifyMethod) {
×
476
                                                $return[] = $this->signRequestService->createOrUpdateSignRequest(
×
477
                                                        identifyMethods: [
×
478
                                                                $identifyMethod['method'] => $identifyMethod['value'],
×
479
                                                        ],
×
480
                                                        displayName: $user['displayName'] ?? '',
×
481
                                                        description: $user['description'] ?? '',
×
482
                                                        notify: $shouldNotify,
×
483
                                                        fileId: $file->getId(),
×
484
                                                        signingOrder: $signingOrder,
×
485
                                                        fileStatus: $fileStatus,
×
486
                                                        signerStatus: $signerStatus,
×
487
                                                );
×
488
                                        }
489
                                } else {
490
                                        $return[] = $this->signRequestService->createOrUpdateSignRequest(
13✔
491
                                                identifyMethods: $user['identify'],
13✔
492
                                                displayName: $user['displayName'] ?? '',
13✔
493
                                                description: $user['description'] ?? '',
13✔
494
                                                notify: $shouldNotify,
13✔
495
                                                fileId: $file->getId(),
13✔
496
                                                signingOrder: $signingOrder,
13✔
497
                                                fileStatus: $fileStatus,
13✔
498
                                                signerStatus: $signerStatus,
13✔
499
                                        );
13✔
500
                                }
501
                        }
502
                }
503
                return $return;
14✔
504
        }
505

506

507

508
        private function saveVisibleElements(array $data, FileEntity $file): array {
509
                if (empty($data['visibleElements'])) {
17✔
510
                        return [];
15✔
511
                }
512
                $persisted = [];
2✔
513
                foreach ($data['visibleElements'] as $element) {
2✔
514
                        if ($file->isEnvelope() && !empty($element['signRequestId'])) {
2✔
515
                                $envelopeSignRequest = $this->signRequestMapper->getById((int)$element['signRequestId']);
×
516
                                // Only translate if the provided SR belongs to the envelope itself
517
                                if ($envelopeSignRequest && $envelopeSignRequest->getFileId() === $file->getId()) {
×
518
                                        $childrenSrs = $this->signRequestMapper->getByEnvelopeChildrenAndIdentifyMethod($file->getId(), (int)$element['signRequestId']);
×
519
                                        foreach ($childrenSrs as $childSr) {
×
520
                                                if ($childSr->getFileId() === (int)$element['fileId']) {
×
521
                                                        $element['signRequestId'] = $childSr->getId();
×
522
                                                        break;
×
523
                                                }
524
                                        }
525
                                }
526
                        }
527

528
                        $persisted[] = $this->fileElementService->saveVisibleElement($element);
2✔
529
                }
530
                return $persisted;
2✔
531
        }
532

533

534
        public function validateNewRequestToFile(array $data): void {
535
                $this->validateNewFile($data);
7✔
536
                $this->validateUsers($data);
6✔
537
                $this->validateHelper->validateFileStatus($data);
2✔
538
        }
539

540
        public function validateNewFile(array $data): void {
541
                if (empty($data['name'])) {
7✔
542
                        throw new \Exception($this->l10n->t('Name is mandatory'));
1✔
543
                }
544
                $this->validateHelper->validateNewFile($data);
6✔
545
        }
546

547
        public function validateUsers(array $data): void {
548
                if (empty($data['users'])) {
6✔
549
                        throw new \Exception($this->l10n->t('Empty users list'));
3✔
550
                }
551
                if (!is_array($data['users'])) {
3✔
552
                        // TRANSLATION This message will be displayed when the request to API with the key users has a value that is not an array
553
                        throw new \Exception($this->l10n->t('User list needs to be an array'));
1✔
554
                }
555
                foreach ($data['users'] as $user) {
2✔
556
                        if (!array_key_exists('identify', $user)) {
2✔
557
                                throw new \Exception('Identify key not found');
×
558
                        }
559
                        $this->identifyMethod->setAllEntityData($user);
2✔
560
                }
561
        }
562

563

564

565
        public function unassociateToUser(int $fileId, int $signRequestId): void {
566
                $file = $this->fileMapper->getById($fileId);
2✔
567
                $signRequest = $this->signRequestMapper->getByFileIdAndSignRequestId($fileId, $signRequestId);
2✔
568
                $deletedOrder = $signRequest->getSigningOrder();
2✔
569
                $groupedIdentifyMethods = $this->identifyMethod->getIdentifyMethodsFromSignRequestId($signRequestId);
2✔
570

571
                $this->dispatchCancellationEventIfNeeded($signRequest, $file, $groupedIdentifyMethods);
2✔
572

573
                try {
574
                        $this->signRequestMapper->delete($signRequest);
2✔
575
                        $this->identifyMethod->deleteBySignRequestId($signRequestId);
2✔
576
                        $visibleElements = $this->fileElementMapper->getByFileIdAndSignRequestId($fileId, $signRequestId);
2✔
577
                        foreach ($visibleElements as $visibleElement) {
2✔
578
                                $this->fileElementMapper->delete($visibleElement);
×
579
                        }
580

581
                        $this->sequentialSigningService
2✔
582
                                ->setFile($file)
2✔
583
                                ->reorderAfterDeletion($file->getId(), $deletedOrder);
2✔
584

585
                        $this->propagateSignerDeletionToChildren($file, $signRequest);
2✔
586
                } catch (\Throwable) {
×
587
                }
588
        }
589

590
        private function propagateSignerDeletionToChildren(FileEntity $envelope, SignRequestEntity $deletedSignRequest): void {
591
                if ($envelope->getNodeType() !== 'envelope') {
2✔
592
                        return;
2✔
593
                }
594

595
                $children = $this->fileMapper->getChildrenFiles($envelope->getId());
×
596

597
                $identifyMethods = $this->identifyMethod->getIdentifyMethodsFromSignRequestId($deletedSignRequest->getId());
×
598
                if (empty($identifyMethods)) {
×
599
                        return;
×
600
                }
601

602
                foreach ($children as $child) {
×
603
                        try {
604
                                $this->identifyMethod->clearCache();
×
605
                                $childSignRequest = $this->signRequestService->getSignRequestByIdentifyMethod(
×
606
                                        current(reset($identifyMethods)),
×
607
                                        $child->getId()
×
608
                                );
×
609

610
                                if ($childSignRequest->getId()) {
×
611
                                        $this->unassociateToUser($child->getId(), $childSignRequest->getId());
×
612
                                }
613
                        } catch (\Throwable $e) {
×
614
                                continue;
×
615
                        }
616
                }
617
        }
618

619
        private function dispatchCancellationEventIfNeeded(
620
                SignRequestEntity $signRequest,
621
                FileEntity $file,
622
                array $groupedIdentifyMethods,
623
        ): void {
624
                if ($signRequest->getStatus() !== \OCA\Libresign\Enum\SignRequestStatus::ABLE_TO_SIGN->value) {
2✔
625
                        return;
×
626
                }
627

628
                try {
629
                        foreach ($groupedIdentifyMethods as $identifyMethods) {
2✔
630
                                foreach ($identifyMethods as $identifyMethod) {
2✔
631
                                        $event = new SignRequestCanceledEvent(
2✔
632
                                                $signRequest,
2✔
633
                                                $file,
2✔
634
                                                $identifyMethod,
2✔
635
                                        );
2✔
636
                                        $this->eventDispatcher->dispatchTyped($event);
2✔
637
                                }
638
                        }
639
                } catch (\Throwable $e) {
×
640
                        $this->logger->error('Error dispatching SignRequestCanceledEvent: ' . $e->getMessage(), ['exception' => $e]);
×
641
                }
642
        }
643

644
        public function deleteRequestSignature(array $data): void {
645
                if (!empty($data['uuid'])) {
2✔
646
                        $signatures = $this->signRequestMapper->getByFileUuid($data['uuid']);
×
647
                        $fileData = $this->fileMapper->getByUuid($data['uuid']);
×
648
                } elseif (!empty($data['file']['fileId'])) {
2✔
649
                        $fileData = $this->fileMapper->getById($data['file']['fileId']);
2✔
650
                        $signatures = $this->signRequestMapper->getByFileId($fileData->getId());
2✔
651
                } else {
652
                        throw new \Exception($this->l10n->t('Please provide either UUID or File object'));
×
653
                }
654
                foreach ($signatures as $signRequest) {
2✔
655
                        $this->identifyMethod->deleteBySignRequestId($signRequest->getId());
2✔
656
                        $this->signRequestMapper->delete($signRequest);
2✔
657
                }
658
                $this->fileMapper->delete($fileData);
2✔
659
                $this->fileElementService->deleteVisibleElements($fileData->getId());
2✔
660
        }
661
}
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