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

LibreSign / libresign / 25521310733

07 May 2026 08:49PM UTC coverage: 56.843%. First build
25521310733

Pull #7668

github

web-flow
Merge 97a720789 into eb2d0b4fe
Pull Request #7668: fix: align OpenAPI contract and frontend to string-only signatureFlow

16 of 29 new or added lines in 9 files covered. (55.17%)

10741 of 18896 relevant lines covered (56.84%)

6.97 hits per line

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

0.0
/lib/Controller/SignatureElementsController.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\Helper\ValidateHelper;
13
use OCA\Libresign\Middleware\Attribute\RequireSignRequestUuid;
14
use OCA\Libresign\Service\AccountService;
15
use OCA\Libresign\Service\SessionService;
16
use OCA\Libresign\Service\SignatureTextService;
17
use OCA\Libresign\Service\SignerElementsService;
18
use OCA\Libresign\Service\SignFileService;
19
use OCP\AppFramework\Db\DoesNotExistException;
20
use OCP\AppFramework\Http;
21
use OCP\AppFramework\Http\Attribute\ApiRoute;
22
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
23
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
24
use OCP\AppFramework\Http\Attribute\PublicPage;
25
use OCP\AppFramework\Http\Attribute\RequestHeader;
26
use OCP\AppFramework\Http\DataResponse;
27
use OCP\AppFramework\Http\FileDisplayResponse;
28
use OCP\Files\SimpleFS\InMemoryFile;
29
use OCP\IL10N;
30
use OCP\IPreview;
31
use OCP\IRequest;
32
use OCP\IURLGenerator;
33
use OCP\IUser;
34
use OCP\IUserSession;
35
use OCP\Preview\IMimeIconProvider;
36

37
/**
38
 * @psalm-import-type LibresignMessageResponse from \OCA\Libresign\ResponseDefinitions
39
 * @psalm-import-type LibresignUserElement from \OCA\Libresign\ResponseDefinitions
40
 * @psalm-import-type LibresignUserElementsMessageResponse from \OCA\Libresign\ResponseDefinitions
41
 * @psalm-import-type LibresignUserElementsResponse from \OCA\Libresign\ResponseDefinitions
42
 */
43
class SignatureElementsController extends AEnvironmentAwareController implements ISignatureUuid {
44
        use LibresignTrait;
45
        public function __construct(
46
                IRequest $request,
47
                protected IL10N $l10n,
48
                private AccountService $accountService,
49
                private SignerElementsService $signerElementsService,
50
                protected SignatureTextService $signatureTextService,
51
                protected IUserSession $userSession,
52
                protected SessionService $sessionService,
53
                protected SignFileService $signFileService,
54
                private IPreview $preview,
55
                protected IMimeIconProvider $mimeIconProvider,
56
                protected IURLGenerator $urlGenerator,
57
                private ValidateHelper $validateHelper,
58
        ) {
59
                parent::__construct(Application::APP_ID, $request);
×
60
        }
61

62
        /**
63
         * Create signature element
64
         *
65
         * @param array<string, mixed> $elements Element object
66
         * @return DataResponse<Http::STATUS_OK, LibresignUserElementsMessageResponse, array{}>|DataResponse<Http::STATUS_UNPROCESSABLE_ENTITY, LibresignMessageResponse, array{}>
67
         *
68
         * 200: OK
69
         * 422: Invalid data
70
         */
71
        #[NoAdminRequired]
72
        #[NoCSRFRequired]
73
        #[PublicPage]
74
        #[RequestHeader(name: 'libresign-sign-request-uuid', description: 'The UUID of the sign request, used to identify the request', indirect: true)]
75
        #[RequireSignRequestUuid(skipIfAuthenticated: true)]
76
        #[ApiRoute(verb: 'POST', url: '/api/{apiVersion}/signature/elements', requirements: ['apiVersion' => '(v1)'])]
77
        public function createSignatureElement(array $elements): DataResponse {
78
                try {
79
                        $this->validateHelper->validateVisibleElements($elements, $this->validateHelper::TYPE_VISIBLE_ELEMENT_USER);
×
80
                        $this->accountService->saveVisibleElements(
×
81
                                elements: $elements,
×
82
                                sessionId: $this->sessionService->getSessionId(),
×
83
                                user: $this->userSession->getUser(),
×
84
                        );
×
85
                } catch (\Throwable $th) {
×
86
                        return new DataResponse(
×
87
                                [
×
88
                                        'message' => $th->getMessage(),
×
89
                                ],
×
90
                                Http::STATUS_UNPROCESSABLE_ENTITY
×
91
                        );
×
92
                }
93
                if (count($elements) === 1) {
×
94
                        $message = $this->l10n->t('Element created with success');
×
95
                } else {
96
                        $message = $this->l10n->t('Elements created with success');
×
97
                }
98
                return new DataResponse(
×
99
                        [
×
100
                                'message' => $message,
×
101
                                'elements'
×
102
                                        => (
×
103
                                                $this->userSession->getUser() instanceof IUser
×
104
                                                ? $this->signerElementsService->getUserElements($this->userSession->getUser()->getUID())
×
105
                                                : $this->signerElementsService->getElementsFromSessionAsArray()
×
106
                                        ),
×
107
                        ],
×
108
                        Http::STATUS_OK
×
109
                );
×
110
        }
111

112
        /**
113
         * Get signature elements
114
         *
115
         * @return DataResponse<Http::STATUS_OK, LibresignUserElementsResponse, array{}>|DataResponse<Http::STATUS_NOT_FOUND, LibresignMessageResponse, array{}>
116
         *
117
         * 200: OK
118
         * 404: Invalid data
119
         */
120
        #[NoAdminRequired]
121
        #[NoCSRFRequired]
122
        #[PublicPage]
123
        #[RequestHeader(name: 'libresign-sign-request-uuid', description: 'The UUID of the sign request, used to identify the request', indirect: true)]
124
        #[RequireSignRequestUuid(skipIfAuthenticated: true)]
125
        #[ApiRoute(verb: 'GET', url: '/api/{apiVersion}/signature/elements', requirements: ['apiVersion' => '(v1)'])]
126
        public function getSignatureElements(): DataResponse {
127
                $userId = $this->userSession->getUser()?->getUID();
×
128
                try {
129
                        $elements = (
×
130
                                $userId
×
131
                                ? $this->signerElementsService->getUserElements($userId)
×
132
                                : $this->signerElementsService->getElementsFromSessionAsArray()
×
133
                        );
×
134
                        return new DataResponse(
×
135
                                [
×
136
                                        'elements' => $elements,
×
137
                                ],
×
138
                                Http::STATUS_OK
×
139
                        );
×
140
                } catch (\Throwable) {
×
141
                        return new DataResponse(
×
142
                                [
×
143
                                        'message' => $this->l10n->t('Elements not found')
×
144
                                ],
×
145
                                Http::STATUS_NOT_FOUND
×
146
                        );
×
147
                }
148
        }
149

150
        /**
151
         * Get preview of signature elements of
152
         *
153
         * @param int $nodeId Node id of a Nextcloud file
154
         * @return FileDisplayResponse<Http::STATUS_OK, array{Content-Type: string}>|DataResponse<Http::STATUS_NOT_FOUND, array{}, array{}>
155
         *
156
         * 200: OK
157
         * 404: Invalid data
158
         */
159
        #[NoAdminRequired]
160
        #[PublicPage]
161
        #[NoCSRFRequired]
162
        #[RequireSignRequestUuid(skipIfAuthenticated: true)]
163
        #[ApiRoute(verb: 'GET', url: '/api/{apiVersion}/signature/elements/preview/{nodeId}', requirements: ['apiVersion' => '(v1)'])]
164
        public function previewSignatureElement(int $nodeId) {
165
                try {
166
                        $node = $this->accountService->getFileByNodeId($nodeId);
×
167
                        if ($this->preview->isAvailable($node)) {
×
168
                                $preview = $this->preview->getPreview(
×
169
                                        file: $node,
×
170
                                        width: (int)$this->signatureTextService->getSignatureWidth(),
×
171
                                        height: (int)$this->signatureTextService->getSignatureHeight(),
×
172
                                );
×
173
                        } else {
174
                                // When the preview is disabled, use the icon image of mimetype
175
                                // as fallback
176
                                $url = $this->mimeIconProvider->getMimeIconUrl($node->getMimeType());
×
177
                                $baseUrl = $this->urlGenerator->getBaseUrl();
×
178
                                if (!str_starts_with((string)$url, $baseUrl)) {
×
179
                                        throw new DoesNotExistException('Preview disabled');
×
180
                                }
181
                                $path = \OC::$SERVERROOT . str_replace($baseUrl, '', $url);
×
182
                                if (!file_exists($path)) {
×
183
                                        throw new DoesNotExistException('Preview disabled');
×
184
                                }
185
                                $extension = pathinfo($path, PATHINFO_EXTENSION);
×
186
                                $preview = new InMemoryFile(implode('.', ['signature', $extension]), file_get_contents($path));
×
187
                        }
188
                } catch (DoesNotExistException) {
×
189
                        return new DataResponse([], Http::STATUS_NOT_FOUND);
×
190
                }
191
                $response = new FileDisplayResponse($preview, Http::STATUS_OK, [
×
192
                        'Content-Type' => $preview->getMimeType(),
×
193
                ]);
×
194
                return $response;
×
195
        }
196

197
        public function getSignatureElementPreview(int $nodeId) {
NEW
198
                return $this->previewSignatureElement($nodeId);
×
199
        }
200

201
        /**
202
         * Get signature element of signer
203
         *
204
         * @param int $nodeId Node id of a Nextcloud file
205
         * @return DataResponse<Http::STATUS_OK, LibresignUserElement, array{}>|DataResponse<Http::STATUS_NOT_FOUND, LibresignMessageResponse, array{}>
206
         *
207
         * 200: OK
208
         * 404: Invalid data
209
         */
210
        #[NoAdminRequired]
211
        #[NoCSRFRequired]
212
        #[ApiRoute(verb: 'GET', url: '/api/{apiVersion}/signature/elements/{nodeId}', requirements: ['apiVersion' => '(v1)'])]
213
        public function getSignatureElement(int $nodeId): DataResponse {
214
                $userId = $this->userSession->getUser()->getUID();
×
215
                try {
216
                        return new DataResponse(
×
217
                                $this->signerElementsService->getUserElementByNodeId($userId, $nodeId),
×
218
                                Http::STATUS_OK
×
219
                        );
×
220
                } catch (\Throwable) {
×
221
                        return new DataResponse(
×
222
                                [
×
223
                                        'message' => $this->l10n->t('Element not found')
×
224
                                ],
×
225
                                Http::STATUS_NOT_FOUND
×
226
                        );
×
227
                }
228
        }
229

230
        /**
231
         * Update signature element
232
         *
233
         * @param int $nodeId Node id of a Nextcloud file
234
         * @param string $type The type of signature element
235
         * @param array<string, mixed> $file Element object
236
         * @return DataResponse<Http::STATUS_OK, LibresignUserElementsMessageResponse, array{}>|DataResponse<Http::STATUS_UNPROCESSABLE_ENTITY, LibresignMessageResponse, array{}>
237
         *
238
         * 200: OK
239
         * 422: Error
240
         */
241
        #[NoAdminRequired]
242
        #[PublicPage]
243
        #[NoCSRFRequired]
244
        #[RequireSignRequestUuid(skipIfAuthenticated: true)]
245
        #[ApiRoute(verb: 'PATCH', url: '/api/{apiVersion}/signature/elements/{nodeId}', requirements: ['apiVersion' => '(v1)'])]
246
        public function patchSignatureElement(int $nodeId, string $type = '', array $file = []): DataResponse {
247
                try {
248
                        $element['nodeId'] = $nodeId;
×
249
                        if ($type) {
×
250
                                $element['type'] = $type;
×
251
                        }
252
                        if ($file) {
×
253
                                $element['file'] = $file;
×
254
                        }
255
                        $this->validateHelper->validateVisibleElement($element, $this->validateHelper::TYPE_VISIBLE_ELEMENT_USER);
×
256
                        $user = $this->userSession->getUser();
×
257
                        if ($user instanceof IUser) {
×
258
                                $userElement = $this->signerElementsService->getUserElementByNodeId(
×
259
                                        $user->getUID(),
×
260
                                        $nodeId,
×
261
                                );
×
262
                                $element['elementId'] = $userElement['id'];
×
263
                        }
264
                        $this->accountService->saveVisibleElement($element, $this->sessionService->getSessionId(), $user);
×
265
                        /** @var LibresignUserElement[] $elements */
266
                        $elements = (
×
267
                                $this->userSession->getUser() instanceof IUser
×
268
                                ? $this->signerElementsService->getUserElements($this->userSession->getUser()->getUID())
×
269
                                : $this->signerElementsService->getElementsFromSessionAsArray()
×
270
                        );
×
271
                        return new DataResponse(
×
272
                                [
×
273
                                        'message' => $this->l10n->t('Element updated with success'),
×
274
                                        'elements' => $elements,
×
275
                                ],
×
276
                                Http::STATUS_OK
×
277
                        );
×
278
                } catch (\Throwable $th) {
×
279
                        return new DataResponse(
×
280
                                [
×
281
                                        'message' => $th->getMessage()
×
282
                                ],
×
283
                                Http::STATUS_UNPROCESSABLE_ENTITY
×
284
                        );
×
285
                }
286
        }
287

288
        /**
289
         * Delete signature element
290
         *
291
         * @param int $nodeId Node id of a Nextcloud file
292
         * @return DataResponse<Http::STATUS_OK|Http::STATUS_NOT_FOUND, LibresignMessageResponse, array{}>
293
         *
294
         * 200: OK
295
         * 404: Not found
296
         */
297
        #[NoAdminRequired]
298
        #[NoCSRFRequired]
299
        #[PublicPage]
300
        #[RequireSignRequestUuid(skipIfAuthenticated: true)]
301
        #[ApiRoute(verb: 'DELETE', url: '/api/{apiVersion}/signature/elements/{nodeId}', requirements: ['apiVersion' => '(v1)'])]
302
        public function deleteSignatureElement(int $nodeId): DataResponse {
303
                try {
304
                        $this->accountService->deleteSignatureElement(
×
305
                                user: $this->userSession->getUser(),
×
306
                                nodeId: $nodeId,
×
307
                                sessionId: $this->sessionService->getSessionId(),
×
308
                        );
×
309
                } catch (\Throwable) {
×
310
                        return new DataResponse(
×
311
                                [
×
312
                                        'message' => $this->l10n->t('Element not found')
×
313
                                ],
×
314
                                Http::STATUS_NOT_FOUND
×
315
                        );
×
316
                }
317
                return new DataResponse(
×
318
                        [
×
319
                                'message' => $this->l10n->t('Visible element deleted')
×
320
                        ],
×
321
                        Http::STATUS_OK
×
322
                );
×
323
        }
324
}
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