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

LibreSign / libresign / 21970093306

13 Feb 2026 12:43AM UTC coverage: 48.792%. First build
21970093306

Pull #6825

github

web-flow
Merge bab93e99a into 34f32c1fd
Pull Request #6825: chore: bump dependencies

39 of 69 new or added lines in 4 files covered. (56.52%)

8522 of 17466 relevant lines covered (48.79%)

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

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

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

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

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

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

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

282
        /**
283
         * Delete signature element
284
         *
285
         * @param int $nodeId Node id of a Nextcloud file
286
         * @return DataResponse<Http::STATUS_OK|Http::STATUS_NOT_FOUND, array{message: string}, array{}>
287
         *
288
         * 200: OK
289
         * 404: Not found
290
         */
291
        #[NoAdminRequired]
292
        #[NoCSRFRequired]
293
        #[PublicPage]
294
        #[RequireSignRequestUuid(skipIfAuthenticated: true)]
295
        #[ApiRoute(verb: 'DELETE', url: '/api/{apiVersion}/signature/elements/{nodeId}', requirements: ['apiVersion' => '(v1)'])]
296
        public function deleteSignatureElement(int $nodeId): DataResponse {
297
                try {
NEW
298
                        $user = $this->userSession->getUser();
×
NEW
299
                        $sessionId = $this->sessionService->getSessionId();
×
NEW
300
                        \OCP\Server::get(\Psr\Log\LoggerInterface::class)->debug('[WIP-DEBUG] deleteSignatureElement called', [
×
NEW
301
                                'nodeId' => $nodeId,
×
NEW
302
                                'userId' => $user?->getUID(),
×
NEW
303
                                'isIUser' => $user instanceof IUser,
×
NEW
304
                                'sessionId' => $sessionId,
×
NEW
305
                        ]);
×
306
                        $this->accountService->deleteSignatureElement(
×
NEW
307
                                user: $user,
×
308
                                nodeId: $nodeId,
×
NEW
309
                                sessionId: $sessionId,
×
310
                        );
×
NEW
311
                } catch (\Throwable $e) {
×
NEW
312
                        \OCP\Server::get(\Psr\Log\LoggerInterface::class)->error('[WIP-DEBUG] deleteSignatureElement exception', [
×
NEW
313
                                'nodeId' => $nodeId,
×
NEW
314
                                'userId' => $this->userSession->getUser()?->getUID(),
×
NEW
315
                                'sessionId' => $this->sessionService->getSessionId(),
×
NEW
316
                                'exception' => get_class($e),
×
NEW
317
                                'message' => $e->getMessage(),
×
NEW
318
                                'trace' => array_slice($e->getTrace(), 0, 5),
×
NEW
319
                        ]);
×
320
                        return new DataResponse(
×
321
                                [
×
322
                                        'message' => $this->l10n->t('Element not found')
×
323
                                ],
×
324
                                Http::STATUS_NOT_FOUND
×
325
                        );
×
326
                }
327
                return new DataResponse(
×
328
                        [
×
329
                                'message' => $this->l10n->t('Visible element deleted')
×
330
                        ],
×
331
                        Http::STATUS_OK
×
332
                );
×
333
        }
334
}
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