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

LibreSign / libresign / 20735882561

06 Jan 2026 02:27AM UTC coverage: 44.781%. First build
20735882561

Pull #6335

github

web-flow
Merge 160d4b249 into 7e27d60b2
Pull Request #6335: feat: refactor file list service

160 of 243 new or added lines in 5 files covered. (65.84%)

6692 of 14944 relevant lines covered (44.78%)

5.05 hits per line

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

63.64
/lib/Controller/RequestSignatureController.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\Controller;
10

11
use OCA\Libresign\AppInfo\Application;
12
use OCA\Libresign\Exception\LibresignException;
13
use OCA\Libresign\Helper\ValidateHelper;
14
use OCA\Libresign\Middleware\Attribute\RequireManager;
15
use OCA\Libresign\ResponseDefinitions;
16
use OCA\Libresign\Service\File\FileListService;
17
use OCA\Libresign\Service\FileService;
18
use OCA\Libresign\Service\RequestSignatureService;
19
use OCP\AppFramework\Http;
20
use OCP\AppFramework\Http\Attribute\ApiRoute;
21
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
22
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
23
use OCP\AppFramework\Http\DataResponse;
24
use OCP\IL10N;
25
use OCP\IRequest;
26
use OCP\IUserSession;
27

28
/**
29
 * @psalm-import-type LibresignNewFile from ResponseDefinitions
30
 * @psalm-import-type LibresignNewSigner from ResponseDefinitions
31
 * @psalm-import-type LibresignValidateFile from ResponseDefinitions
32
 * @psalm-import-type LibresignFileDetail from ResponseDefinitions
33
 * @psalm-import-type LibresignNextcloudFile from ResponseDefinitions
34
 * @psalm-import-type LibresignFolderSettings from ResponseDefinitions
35
 * @psalm-import-type LibresignSettings from ResponseDefinitions
36
 * @psalm-import-type LibresignSigner from ResponseDefinitions
37
 * @psalm-import-type LibresignVisibleElement from ResponseDefinitions
38
 */
39
class RequestSignatureController extends AEnvironmentAwareController {
40
        public function __construct(
41
                IRequest $request,
42
                protected IL10N $l10n,
43
                protected IUserSession $userSession,
44
                protected FileService $fileService,
45
                protected FileListService $fileListService,
46
                protected ValidateHelper $validateHelper,
47
                protected RequestSignatureService $requestSignatureService,
48
        ) {
49
                parent::__construct(Application::APP_ID, $request);
9✔
50
        }
51

52
        /**
53
         * Request signature
54
         *
55
         * Request that a file be signed by a group of people.
56
         * Each user in the users array can optionally include a 'signing_order' field
57
         * to control the order of signatures when ordered signing flow is enabled.
58
         * When the created entity is an envelope (`nodeType` = `envelope`),
59
         * the returned `data` includes `filesCount` and `files` as a list of
60
         * envelope child files.
61
         *
62
         * @param LibresignNewSigner[] $users Collection of users who must sign the document. Each user can have: identify, displayName, description, notify, signing_order
63
         * @param string $name The name of file to sign
64
         * @param LibresignFolderSettings $settings Settings to define how and where the file should be stored
65
         * @param LibresignNewFile $file File object.
66
         * @param list<LibresignNewFile> $files Multiple files to create an envelope (optional, use either file or files)
67
         * @param string|null $callback URL that will receive a POST after the document is signed
68
         * @param integer|null $status Numeric code of status * 0 - no signers * 1 - signed * 2 - pending
69
         * @param string|null $signatureFlow Signature flow mode: 'parallel' or 'ordered_numeric'. If not provided, uses global configuration
70
         * @return DataResponse<Http::STATUS_OK, LibresignNextcloudFile, array{}>|DataResponse<Http::STATUS_UNPROCESSABLE_ENTITY, array{message?: string, action?: integer, errors?: list<array{message: string, title?: string}>}, array{}>
71
         *
72
         * 200: OK
73
         * 422: Unauthorized
74
         */
75
        #[NoAdminRequired]
76
        #[NoCSRFRequired]
77
        #[RequireManager]
78
        #[ApiRoute(verb: 'POST', url: '/api/{apiVersion}/request-signature', requirements: ['apiVersion' => '(v1)'])]
79
        public function request(
80
                array $users,
81
                string $name,
82
                array $settings = [],
83
                array $file = [],
84
                array $files = [],
85
                ?string $callback = null,
86
                ?int $status = 1,
87
                ?string $signatureFlow = null,
88
        ): DataResponse {
89
                try {
90
                        $user = $this->userSession->getUser();
1✔
91
                        return $this->createSignatureRequest(
1✔
92
                                $user,
1✔
93
                                $file,
1✔
94
                                $files,
1✔
95
                                $name,
1✔
96
                                $settings,
1✔
97
                                $users,
1✔
98
                                $status,
1✔
99
                                $callback,
1✔
100
                                $signatureFlow
1✔
101
                        );
1✔
102
                } catch (LibresignException $e) {
×
103
                        $errorMessage = $e->getMessage();
×
104
                        $decoded = json_decode($errorMessage, true);
×
105
                        if (json_last_error() === JSON_ERROR_NONE && isset($decoded['errors'])) {
×
106
                                $errorMessage = $decoded['errors'][0]['message'] ?? $errorMessage;
×
107
                        }
108
                        return new DataResponse(
×
109
                                [
×
110
                                        'message' => $errorMessage,
×
111
                                ],
×
112
                                Http::STATUS_UNPROCESSABLE_ENTITY
×
113
                        );
×
114
                } catch (\Throwable $th) {
×
115
                        $errorMessage = $th->getMessage();
×
116
                        return new DataResponse(
×
117
                                [
×
118
                                        'message' => $errorMessage,
×
119
                                ],
×
120
                                Http::STATUS_UNPROCESSABLE_ENTITY
×
121
                        );
×
122
                }
123
        }
124

125
        /**
126
         * Updates signatures data
127
         *
128
         * Is necessary to inform the UUID of the file and a list of people
129
         *
130
         * @param LibresignNewSigner[]|null $users Collection of users who must sign the document
131
         * @param string|null $uuid UUID of sign request. The signer UUID is what the person receives via email when asked to sign. This is not the file UUID.
132
         * @param LibresignVisibleElement[]|null $visibleElements Visible elements on document
133
         * @param LibresignNewFile|array<empty>|null $file File object.
134
         * @param integer|null $status Numeric code of status * 0 - no signers * 1 - signed * 2 - pending
135
         * @param string|null $signatureFlow Signature flow mode: 'parallel' or 'ordered_numeric'. If not provided, uses global configuration
136
         * @param string|null $name The name of file to sign
137
         * @param LibresignFolderSettings $settings Settings to define how and where the file should be stored
138
         * @param list<LibresignNewFile> $files Multiple files to create an envelope (optional, use either file or files)
139
         * @return DataResponse<Http::STATUS_OK, LibresignNextcloudFile, array{}>|DataResponse<Http::STATUS_UNPROCESSABLE_ENTITY, array{message?: string, action?: integer, errors?: list<array{message: string, title?: string}>}, array{}>
140
         *
141
         * 200: OK
142
         * 422: Unauthorized
143
         */
144
        #[NoAdminRequired]
145
        #[NoCSRFRequired]
146
        #[RequireManager]
147
        #[ApiRoute(verb: 'PATCH', url: '/api/{apiVersion}/request-signature', requirements: ['apiVersion' => '(v1)'])]
148
        public function updateSign(
149
                ?array $users = [],
150
                ?string $uuid = null,
151
                ?array $visibleElements = null,
152
                ?array $file = [],
153
                ?int $status = null,
154
                ?string $signatureFlow = null,
155
                ?string $name = null,
156
                array $settings = [],
157
                array $files = [],
158
        ): DataResponse {
159
                try {
160
                        $user = $this->userSession->getUser();
1✔
161

162
                        if (empty($uuid)) {
1✔
NEW
163
                                return $this->createSignatureRequest(
×
NEW
164
                                        $user,
×
NEW
165
                                        $file,
×
NEW
166
                                        $files,
×
NEW
167
                                        $name,
×
NEW
168
                                        $settings,
×
NEW
169
                                        $users,
×
NEW
170
                                        $status,
×
NEW
171
                                        null,
×
NEW
172
                                        $signatureFlow,
×
NEW
173
                                        $visibleElements
×
NEW
174
                                );
×
175
                        }
176

177
                        $data = [
1✔
178
                                'uuid' => $uuid,
1✔
179
                                'file' => $file,
1✔
180
                                'users' => $users,
1✔
181
                                'userManager' => $user,
1✔
182
                                'status' => $status,
1✔
183
                                'visibleElements' => $visibleElements,
1✔
184
                                'signatureFlow' => $signatureFlow,
1✔
185
                                'name' => $name,
1✔
186
                                'settings' => $settings,
1✔
187
                        ];
1✔
188
                        $this->validateHelper->validateExistingFile($data);
1✔
189
                        $this->validateHelper->validateFileStatus($data);
1✔
190
                        $this->validateHelper->validateIdentifySigners($data);
1✔
191
                        if (!empty($visibleElements)) {
1✔
NEW
192
                                $this->validateHelper->validateVisibleElements($visibleElements, $this->validateHelper::TYPE_VISIBLE_ELEMENT_PDF);
×
193
                        }
194
                        $fileEntity = $this->requestSignatureService->save($data);
1✔
195
                        $childFiles = [];
1✔
196

197
                        $response = $this->fileListService->formatFileWithChildren($fileEntity, $childFiles, $user);
1✔
198
                        return new DataResponse($response, Http::STATUS_OK);
1✔
199
                } catch (\Throwable $th) {
×
200
                        return new DataResponse(
×
201
                                [
×
202
                                        'message' => $th->getMessage(),
×
203
                                ],
×
204
                                Http::STATUS_UNPROCESSABLE_ENTITY
×
205
                        );
×
206
                }
207
        }
208

209
        /**
210
         * Internal method to handle signature request creation logic
211
         * Used by both request() and updateSign() when creating new requests
212
         *
213
         * @return DataResponse<Http::STATUS_OK, LibresignNextcloudFile, array{}>
214
         * @throws LibresignException
215
         */
216
        private function createSignatureRequest(
217
                $user,
218
                array $file,
219
                array $files,
220
                string $name,
221
                array $settings,
222
                array $users,
223
                ?int $status,
224
                ?string $callback,
225
                ?string $signatureFlow,
226
                ?array $visibleElements = null,
227
        ): DataResponse {
228
                $isEnvelope = !empty($files);
1✔
229

230
                $filesToSave = $isEnvelope ? $files : null;
1✔
231

232
                if (empty($file) && empty($files)) {
1✔
NEW
233
                        throw new LibresignException($this->l10n->t('File or files parameter is required'));
×
234
                }
235

236
                $data = [
1✔
237
                        'file' => $file,
1✔
238
                        'name' => $name,
1✔
239
                        'users' => $users,
1✔
240
                        'status' => $status,
1✔
241
                        'callback' => $callback,
1✔
242
                        'userManager' => $user,
1✔
243
                        'signatureFlow' => $signatureFlow,
1✔
244
                        'settings' => !empty($settings) ? $settings : ($file['settings'] ?? []),
1✔
245
                ];
1✔
246

247
                if ($isEnvelope) {
1✔
NEW
248
                        $data['files'] = $filesToSave;
×
249
                }
250

251
                if ($visibleElements !== null) {
1✔
NEW
252
                        $data['visibleElements'] = $visibleElements;
×
253
                }
254
                $this->requestSignatureService->validateNewRequestToFile($data);
1✔
255

256
                if ($isEnvelope) {
1✔
NEW
257
                        $result = $this->requestSignatureService->saveFiles($data);
×
NEW
258
                        $fileEntity = $result['file'];
×
NEW
259
                        $childFiles = $result['children'] ?? [];
×
260
                } else {
261
                        $fileEntity = $this->requestSignatureService->save($data);
1✔
262
                        $childFiles = [];
1✔
263
                }
264

265
                $response = $this->fileListService->formatFileWithChildren($fileEntity, $childFiles, $user);
1✔
266
                return new DataResponse($response, Http::STATUS_OK);
1✔
267
        }
268

269
        /**
270
         * Delete sign request
271
         *
272
         * You can only request exclusion as any sign
273
         *
274
         * @param integer $fileId LibreSign file ID
275
         * @param integer $signRequestId The sign request id
276
         * @return DataResponse<Http::STATUS_OK, array{message: string}, array{}>|DataResponse<Http::STATUS_UNAUTHORIZED, array{message: string}, array{}>|DataResponse<Http::STATUS_UNPROCESSABLE_ENTITY, array{action: integer, errors: list<array{message: string, title?: string}>}, array{}>
277
         *
278
         * 200: OK
279
         * 401: Failed
280
         * 422: Failed
281
         */
282
        #[NoAdminRequired]
283
        #[NoCSRFRequired]
284
        #[RequireManager]
285
        #[ApiRoute(verb: 'DELETE', url: '/api/{apiVersion}/sign/file_id/{fileId}/{signRequestId}', requirements: ['apiVersion' => '(v1)'])]
286
        public function deleteOneRequestSignatureUsingFileId(int $fileId, int $signRequestId): DataResponse {
287
                try {
288
                        $data = [
1✔
289
                                'userManager' => $this->userSession->getUser(),
1✔
290
                                'file' => [
1✔
291
                                        'fileId' => $fileId
1✔
292
                                ]
1✔
293
                        ];
1✔
294
                        $this->validateHelper->validateExistingFile($data);
1✔
295
                        $this->validateHelper->validateIsSignerOfFile($signRequestId, $fileId);
1✔
296
                        $this->requestSignatureService->unassociateToUser($fileId, $signRequestId);
1✔
297
                } catch (\Throwable $th) {
×
298
                        return new DataResponse(
×
299
                                [
×
300
                                        'message' => $th->getMessage(),
×
301
                                ],
×
302
                                Http::STATUS_UNAUTHORIZED
×
303
                        );
×
304
                }
305
                return new DataResponse(
1✔
306
                        [
1✔
307
                                'message' => $this->l10n->t('Success')
1✔
308
                        ],
1✔
309
                        Http::STATUS_OK
1✔
310
                );
1✔
311
        }
312

313
        /**
314
         * Delete sign request
315
         *
316
         * You can only request exclusion as any sign
317
         *
318
         * @param integer $fileId Node id of a Nextcloud file
319
         * @return DataResponse<Http::STATUS_OK, array{message: string}, array{}>|DataResponse<Http::STATUS_UNAUTHORIZED, array{message: string}, array{}>|DataResponse<Http::STATUS_UNPROCESSABLE_ENTITY, array{action: integer, errors: list<array{message: string, title?: string}>}, array{}>
320
         *
321
         * 200: OK
322
         * 401: Failed
323
         * 422: Failed
324
         */
325
        #[NoAdminRequired]
326
        #[NoCSRFRequired]
327
        #[RequireManager]
328
        #[ApiRoute(verb: 'DELETE', url: '/api/{apiVersion}/sign/file_id/{fileId}', requirements: ['apiVersion' => '(v1)'])]
329
        public function deleteAllRequestSignatureUsingFileId(int $fileId): DataResponse {
330
                try {
331
                        $data = [
2✔
332
                                'userManager' => $this->userSession->getUser(),
2✔
333
                                'file' => [
2✔
334
                                        'fileId' => $fileId
2✔
335
                                ]
2✔
336
                        ];
2✔
337
                        $this->validateHelper->validateExistingFile($data);
2✔
338
                        $this->requestSignatureService->deleteRequestSignature($data);
1✔
339
                } catch (\Throwable $th) {
1✔
340
                        return new DataResponse(
1✔
341
                                [
1✔
342
                                        'message' => $th->getMessage(),
1✔
343
                                ],
1✔
344
                                Http::STATUS_UNAUTHORIZED
1✔
345
                        );
1✔
346
                }
347
                return new DataResponse(
1✔
348
                        [
1✔
349
                                'message' => $this->l10n->t('Success')
1✔
350
                        ],
1✔
351
                        Http::STATUS_OK
1✔
352
                );
1✔
353
        }
354
}
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