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

LibreSign / libresign / 19876874942

02 Dec 2025 11:26PM UTC coverage: 41.669%. First build
19876874942

Pull #4464

github

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

113 of 268 new or added lines in 15 files covered. (42.16%)

5122 of 12292 relevant lines covered (41.67%)

4.18 hits per line

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

80.95
/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\Db\File as FileEntity;
12
use OCA\Libresign\Db\FileElementMapper;
13
use OCA\Libresign\Db\FileMapper;
14
use OCA\Libresign\Db\IdentifyMethodMapper;
15
use OCA\Libresign\Db\SignRequest as SignRequestEntity;
16
use OCA\Libresign\Db\SignRequestMapper;
17
use OCA\Libresign\Helper\ValidateHelper;
18
use OCA\Libresign\Service\IdentifyMethod\IIdentifyMethod;
19
use OCP\AppFramework\Db\DoesNotExistException;
20
use OCP\Files\IMimeTypeDetector;
21
use OCP\Files\Node;
22
use OCP\Http\Client\IClientService;
23
use OCP\IL10N;
24
use OCP\IUser;
25
use OCP\IUserManager;
26
use Psr\Log\LoggerInterface;
27
use Sabre\DAV\UUIDUtil;
28

29
class RequestSignatureService {
30
        use TFile;
31

32
        public function __construct(
33
                protected IL10N $l10n,
34
                protected IdentifyMethodService $identifyMethod,
35
                protected SignRequestMapper $signRequestMapper,
36
                protected IUserManager $userManager,
37
                protected FileMapper $fileMapper,
38
                protected IdentifyMethodMapper $identifyMethodMapper,
39
                protected PdfParserService $pdfParserService,
40
                protected FileElementService $fileElementService,
41
                protected FileElementMapper $fileElementMapper,
42
                protected FolderService $folderService,
43
                protected IMimeTypeDetector $mimeTypeDetector,
44
                protected ValidateHelper $validateHelper,
45
                protected IClientService $client,
46
                protected LoggerInterface $logger,
47
        ) {
48
        }
54✔
49

50
        public function save(array $data): FileEntity {
51
                $file = $this->saveFile($data);
14✔
52
                $this->saveVisibleElements($data, $file);
14✔
53
                if (empty($data['status'])) {
14✔
54
                        $data['status'] = $file->getStatus();
13✔
55
                }
56
                $this->associateToSigners($data, $file->getId());
14✔
57
                return $file;
14✔
58
        }
59

60
        /**
61
         * Save file data
62
         *
63
         * @param array{?userManager: IUser, ?signRequest: SignRequest, name: string, callback: string, uuid?: ?string, status: int, file?: array{fileId?: int, fileNode?: Node}} $data
64
         */
65
        public function saveFile(array $data): FileEntity {
66
                if (!empty($data['uuid'])) {
15✔
67
                        $file = $this->fileMapper->getByUuid($data['uuid']);
1✔
68
                        return $this->updateStatus($file, $data['status'] ?? 0);
1✔
69
                }
70
                $fileId = null;
15✔
71
                if (isset($data['file']['fileNode']) && $data['file']['fileNode'] instanceof Node) {
15✔
72
                        $fileId = $data['file']['fileNode']->getId();
1✔
73
                } elseif (!empty($data['file']['fileId'])) {
14✔
74
                        $fileId = $data['file']['fileId'];
×
75
                }
76
                if (!is_null($fileId)) {
15✔
77
                        try {
78
                                $file = $this->fileMapper->getByFileId($fileId);
1✔
79
                                return $this->updateStatus($file, $data['status'] ?? 0);
×
80
                        } catch (\Throwable) {
1✔
81
                        }
82
                }
83

84
                $node = $this->getNodeFromData($data);
15✔
85

86
                $file = new FileEntity();
15✔
87
                $file->setNodeId($node->getId());
15✔
88
                if ($data['userManager'] instanceof IUser) {
15✔
89
                        $file->setUserId($data['userManager']->getUID());
15✔
NEW
90
                } elseif ($data['signRequest'] instanceof SignRequestEntity) {
×
NEW
91
                        $file->setSignRequestId($data['signRequest']->getId());
×
92
                }
93
                $file->setUuid(UUIDUtil::getUUID());
15✔
94
                $file->setCreatedAt(new \DateTime('now', new \DateTimeZone('UTC')));
15✔
95
                $file->setName($data['name']);
15✔
96
                $file->setMetadata($this->getFileMetadata($node));
15✔
97
                if (!empty($data['callback'])) {
15✔
98
                        $file->setCallback($data['callback']);
×
99
                }
100
                if (isset($data['status'])) {
15✔
101
                        $file->setStatus($data['status']);
2✔
102
                } else {
103
                        $file->setStatus(FileEntity::STATUS_ABLE_TO_SIGN);
13✔
104
                }
105
                $this->fileMapper->insert($file);
15✔
106
                return $file;
15✔
107
        }
108

109
        private function updateStatus(FileEntity $file, int $status): FileEntity {
110
                if ($status > $file->getStatus()) {
1✔
111
                        $file->setStatus($status);
×
112
                        /** @var FileEntity */
113
                        return $this->fileMapper->update($file);
×
114
                }
115
                return $file;
1✔
116
        }
117

118
        private function getFileMetadata(\OCP\Files\Node $node): array {
119
                $metadata = [];
18✔
120
                if ($extension = strtolower($node->getExtension())) {
18✔
121
                        $metadata = [
17✔
122
                                'extension' => $extension,
17✔
123
                        ];
17✔
124
                        if ($metadata['extension'] === 'pdf') {
17✔
125
                                $metadata = array_merge(
16✔
126
                                        $metadata,
16✔
127
                                        $this->pdfParserService
16✔
128
                                                ->setFile($node)
16✔
129
                                                ->getPageDimensions()
16✔
130
                                );
16✔
131
                        }
132
                }
133
                return $metadata;
18✔
134
        }
135

136
        private function deleteIdentifyMethodIfNotExits(array $users, int $fileId): void {
137
                $file = $this->fileMapper->getById($fileId);
13✔
138
                $signRequests = $this->signRequestMapper->getByFileId($fileId);
13✔
139
                foreach ($signRequests as $key => $signRequest) {
13✔
140
                        $identifyMethods = $this->identifyMethod->getIdentifyMethodsFromSignRequestId($signRequest->getId());
1✔
141
                        if (empty($identifyMethods)) {
1✔
142
                                $this->unassociateToUser($file->getNodeId(), $signRequest->getId());
×
143
                                continue;
×
144
                        }
145
                        foreach ($identifyMethods as $methodName => $list) {
1✔
146
                                foreach ($list as $method) {
1✔
147
                                        $exists[$key]['identify'][$methodName] = $method->getEntity()->getIdentifierValue();
1✔
148
                                        if (!$this->identifyMethodExists($users, $method)) {
1✔
149
                                                $this->unassociateToUser($file->getNodeId(), $signRequest->getId());
1✔
150
                                                continue 3;
1✔
151
                                        }
152
                                }
153
                        }
154
                }
155
        }
156

157
        private function identifyMethodExists(array $users, IIdentifyMethod $identifyMethod): bool {
158
                foreach ($users as $user) {
1✔
159
                        if (!empty($user['identifyMethods'])) {
1✔
160
                                foreach ($user['identifyMethods'] as $data) {
×
161
                                        if ($identifyMethod->getEntity()->getIdentifierKey() !== $data['method']) {
×
162
                                                continue;
×
163
                                        }
164
                                        if ($identifyMethod->getEntity()->getIdentifierValue() === $data['value']) {
×
165
                                                return true;
×
166
                                        }
167
                                }
168
                        } else {
169
                                foreach ($user['identify'] as $method => $value) {
1✔
170
                                        if ($identifyMethod->getEntity()->getIdentifierKey() !== $method) {
1✔
171
                                                continue;
×
172
                                        }
173
                                        if ($identifyMethod->getEntity()->getIdentifierValue() === $value) {
1✔
174
                                                return true;
×
175
                                        }
176
                                }
177
                        }
178
                }
179
                return false;
1✔
180
        }
181

182
        /**
183
         * @return SignRequestEntity[]
184
         *
185
         * @psalm-return list<SignRequestEntity>
186
         */
187
        private function associateToSigners(array $data, int $fileId): array {
188
                $return = [];
14✔
189
                if (!empty($data['users'])) {
14✔
190
                        $this->deleteIdentifyMethodIfNotExits($data['users'], $fileId);
13✔
191
                        foreach ($data['users'] as $user) {
13✔
192
                                if (isset($user['identifyMethods'])) {
13✔
193
                                        foreach ($user['identifyMethods'] as $identifyMethod) {
×
194
                                                $return[] = $this->associateToSigner(
×
195
                                                        identifyMethods: [
×
196
                                                                $identifyMethod['method'] => $identifyMethod['value'],
×
197
                                                        ],
×
198
                                                        displayName: $user['displayName'] ?? '',
×
199
                                                        description: $user['description'] ?? '',
×
200
                                                        notify: empty($user['notify']) && $this->isStatusAbleToNotify($data['status'] ?? null),
×
201
                                                        fileId: $fileId,
×
202
                                                );
×
203
                                        }
204
                                } else {
205
                                        $return[] = $this->associateToSigner(
13✔
206
                                                identifyMethods: $user['identify'],
13✔
207
                                                displayName: $user['displayName'] ?? '',
13✔
208
                                                description: $user['description'] ?? '',
13✔
209
                                                notify: empty($user['notify']) && $this->isStatusAbleToNotify($data['status'] ?? null),
13✔
210
                                                fileId: $fileId,
13✔
211
                                        );
13✔
212
                                }
213
                        }
214
                }
215
                return $return;
14✔
216
        }
217

218
        private function isStatusAbleToNotify(?int $status): bool {
219
                return in_array($status, [
13✔
220
                        FileEntity::STATUS_ABLE_TO_SIGN,
13✔
221
                        FileEntity::STATUS_PARTIAL_SIGNED,
13✔
222
                ]);
13✔
223
        }
224

225
        private function associateToSigner(array $identifyMethods, string $displayName, string $description, bool $notify, int $fileId): SignRequestEntity {
226
                $identifyMethodsIncances = $this->identifyMethod->getByUserData($identifyMethods);
13✔
227
                if (empty($identifyMethodsIncances)) {
13✔
228
                        throw new \Exception($this->l10n->t('Invalid identification method'));
×
229
                }
230
                $signRequest = $this->getSignRequestByIdentifyMethod(
13✔
231
                        current($identifyMethodsIncances),
13✔
232
                        $fileId
13✔
233
                );
13✔
234
                $displayName = $this->getDisplayNameFromIdentifyMethodIfEmpty($identifyMethodsIncances, $displayName);
13✔
235
                $this->setDataToUser($signRequest, $displayName, $description, $fileId);
13✔
236
                $this->saveSignRequest($signRequest);
13✔
237
                foreach ($identifyMethodsIncances as $identifyMethod) {
13✔
238
                        $identifyMethod->getEntity()->setSignRequestId($signRequest->getId());
13✔
239
                        $identifyMethod->willNotifyUser($notify);
13✔
240
                        $identifyMethod->save();
13✔
241
                }
242
                return $signRequest;
13✔
243
        }
244

245
        /**
246
         * @param IIdentifyMethod[] $identifyMethodsIncances
247
         * @param string $displayName
248
         * @return string
249
         */
250
        private function getDisplayNameFromIdentifyMethodIfEmpty(array $identifyMethodsIncances, string $displayName): string {
251
                if (!empty($displayName)) {
13✔
252
                        return $displayName;
×
253
                }
254
                foreach ($identifyMethodsIncances as $identifyMethod) {
13✔
255
                        if ($identifyMethod->getName() === 'account') {
13✔
256
                                return $this->userManager->get($identifyMethod->getEntity()->getIdentifierValue())->getDisplayName();
2✔
257
                        }
258
                }
259
                foreach ($identifyMethodsIncances as $identifyMethod) {
11✔
260
                        if ($identifyMethod->getName() !== 'account') {
11✔
261
                                return $identifyMethod->getEntity()->getIdentifierValue();
11✔
262
                        }
263
                }
264
                return '';
×
265
        }
266

267
        private function saveVisibleElements(array $data, FileEntity $file): array {
268
                if (empty($data['visibleElements'])) {
17✔
269
                        return [];
15✔
270
                }
271
                $elements = $data['visibleElements'];
2✔
272
                foreach ($elements as $key => $element) {
2✔
273
                        $element['fileId'] = $file->getId();
2✔
274
                        $elements[$key] = $this->fileElementService->saveVisibleElement($element);
2✔
275
                }
276
                return $elements;
2✔
277
        }
278

279
        public function validateNewRequestToFile(array $data): void {
280
                $this->validateNewFile($data);
7✔
281
                $this->validateUsers($data);
6✔
282
                $this->validateHelper->validateFileStatus($data);
2✔
283
        }
284

285
        public function validateNewFile(array $data): void {
286
                if (empty($data['name'])) {
7✔
287
                        throw new \Exception($this->l10n->t('Name is mandatory'));
1✔
288
                }
289
                $this->validateHelper->validateNewFile($data);
6✔
290
        }
291

292
        public function validateUsers(array $data): void {
293
                if (empty($data['users'])) {
6✔
294
                        throw new \Exception($this->l10n->t('Empty users list'));
3✔
295
                }
296
                if (!is_array($data['users'])) {
3✔
297
                        // TRANSLATION This message will be displayed when the request to API with the key users has a value that is not an array
298
                        throw new \Exception($this->l10n->t('User list needs to be an array'));
1✔
299
                }
300
                foreach ($data['users'] as $user) {
2✔
301
                        if (!array_key_exists('identify', $user)) {
2✔
302
                                throw new \Exception('Identify key not found');
×
303
                        }
304
                        $this->identifyMethod->setAllEntityData($user);
2✔
305
                }
306
        }
307

308
        public function saveSignRequest(SignRequestEntity $signRequest): void {
309
                if ($signRequest->getId()) {
15✔
310
                        $this->signRequestMapper->update($signRequest);
1✔
311
                } else {
312
                        $this->signRequestMapper->insert($signRequest);
14✔
313
                }
314
        }
315

316
        /**
317
         * @psalm-suppress MixedMethodCall
318
         */
319
        private function setDataToUser(SignRequestEntity $signRequest, string $displayName, string $description, int $fileId): void {
320
                $signRequest->setFileId($fileId);
13✔
321
                if (!$signRequest->getUuid()) {
13✔
322
                        $signRequest->setUuid(UUIDUtil::getUUID());
13✔
323
                }
324
                if (!empty($displayName)) {
13✔
325
                        $signRequest->setDisplayName($displayName);
13✔
326
                }
327
                if (!empty($description)) {
13✔
328
                        $signRequest->setDescription($description);
×
329
                }
330
                if (!$signRequest->getId()) {
13✔
331
                        $signRequest->setCreatedAt(new \DateTime('now', new \DateTimeZone('UTC')));
13✔
332
                }
333
        }
334

335
        private function getSignRequestByIdentifyMethod(IIdentifyMethod $identifyMethod, int $fileId): SignRequestEntity {
336
                try {
337
                        $signRequest = $this->signRequestMapper->getByIdentifyMethodAndFileId($identifyMethod, $fileId);
13✔
338
                } catch (DoesNotExistException) {
13✔
339
                        $signRequest = new SignRequestEntity();
13✔
340
                }
341
                return $signRequest;
13✔
342
        }
343

344
        public function unassociateToUser(int $fileId, int $signRequestId): void {
345
                $signRequest = $this->signRequestMapper->getByFileIdAndSignRequestId($fileId, $signRequestId);
2✔
346
                try {
347
                        $this->signRequestMapper->delete($signRequest);
2✔
348
                        $groupedIdentifyMethods = $this->identifyMethod->getIdentifyMethodsFromSignRequestId($signRequestId);
2✔
349
                        foreach ($groupedIdentifyMethods as $identifyMethods) {
2✔
350
                                foreach ($identifyMethods as $identifyMethod) {
2✔
351
                                        $identifyMethod->delete();
2✔
352
                                }
353
                        }
354
                        $visibleElements = $this->fileElementMapper->getByFileIdAndSignRequestId($fileId, $signRequestId);
2✔
355
                        foreach ($visibleElements as $visibleElement) {
2✔
356
                                $this->fileElementMapper->delete($visibleElement);
×
357
                        }
358
                } catch (\Throwable) {
×
359
                }
360
        }
361

362
        public function deleteRequestSignature(array $data): void {
363
                if (!empty($data['uuid'])) {
2✔
364
                        $signatures = $this->signRequestMapper->getByFileUuid($data['uuid']);
×
365
                        $fileData = $this->fileMapper->getByUuid($data['uuid']);
×
366
                } elseif (!empty($data['file']['fileId'])) {
2✔
367
                        $signatures = $this->signRequestMapper->getByNodeId($data['file']['fileId']);
2✔
368
                        $fileData = $this->fileMapper->getByFileId($data['file']['fileId']);
2✔
369
                } else {
370
                        throw new \Exception($this->l10n->t('Please provide either UUID or File object'));
×
371
                }
372
                foreach ($signatures as $signRequest) {
2✔
373
                        $this->signRequestMapper->delete($signRequest);
2✔
374
                }
375
                $this->fileMapper->delete($fileData);
2✔
376
                $this->fileElementService->deleteVisibleElements($fileData->getId());
2✔
377
        }
378
}
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