Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
75 / 75
100.00% covered (success)
100.00%
8 / 8
CRAP
100.00% covered (success)
100.00%
1 / 1
RequestSignatureWorkflowService
100.00% covered (success)
100.00%
75 / 75
100.00% covered (success)
100.00%
8 / 8
22
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 resolvePolicyPayload
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 createRequest
100.00% covered (success)
100.00%
33 / 33
100.00% covered (success)
100.00%
1 / 1
8
 updateExistingRequest
100.00% covered (success)
100.00%
26 / 26
100.00% covered (success)
100.00%
1 / 1
3
 normalizeNodeIds
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 loadChildFilesIfEnvelope
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 extractPolicyOverrides
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 extractPolicyActiveContext
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3declare(strict_types=1);
4/**
5 * SPDX-FileCopyrightText: 2026 LibreCode coop and contributors
6 * SPDX-License-Identifier: AGPL-3.0-or-later
7 */
8
9namespace OCA\Libresign\Service;
10
11use OCA\Libresign\Db\File as FileEntity;
12use OCA\Libresign\Db\FileMapper;
13use OCA\Libresign\Exception\LibresignException;
14use OCA\Libresign\Service\Validation\FileInputValidator;
15use OCA\Libresign\Service\Validation\SignerValidator;
16use OCA\Libresign\Service\Validation\SigningRequestValidator;
17use OCA\Libresign\Service\Validation\VisibleElementValidator;
18use OCP\IL10N;
19use OCP\IUser;
20
21final class RequestSignatureWorkflowService {
22    public function __construct(
23        private IL10N $l10n,
24        private RequestSignatureService $requestSignatureService,
25        private SigningRequestValidator $signingRequestValidator,
26        private SignerValidator $signerValidator,
27        private VisibleElementValidator $visibleElementValidator,
28        private FileMapper $fileMapper,
29        private FileInputValidator $fileInputValidator,
30    ) {
31    }
32
33    /**
34     * @param array<string, mixed>|null $policy
35     * @return array{policyOverrides: array<string, mixed>, policyActiveContext: array<string, mixed>|null}
36     */
37    public function resolvePolicyPayload(?array $policy): array {
38        return [
39            'policyOverrides' => $this->extractPolicyOverrides($policy),
40            'policyActiveContext' => $this->extractPolicyActiveContext($policy),
41        ];
42    }
43
44    /**
45     * @param array<string, mixed> $file
46     * @param list<array<string, mixed>> $files
47     * @param list<array<string, mixed>> $signers
48     * @param array<string, mixed> $settings
49     * @param array<string, mixed>|null $policy
50     * @param list<array<string, mixed>>|null $visibleElements
51     * @return array{file: FileEntity, children: list<FileEntity>}
52     * @throws LibresignException
53     */
54    public function createRequest(
55        ?IUser $user,
56        array $file,
57        array $files,
58        string $name,
59        array $settings,
60        array $signers,
61        ?int $status,
62        ?string $callback,
63        ?array $policy = null,
64        ?array $visibleElements = null,
65    ): array {
66        if ($file === [] && $files === []) {
67            // TRANSLATORS Error shown when starting a signature request without providing any documents.
68            throw new LibresignException($this->l10n->t('File or files parameter is required'));
69        }
70
71        $file = $this->fileInputValidator->normalizeNodeId($file);
72        $files = $this->normalizeNodeIds($files);
73        $resolvedPolicy = $this->resolvePolicyPayload($policy);
74        $data = [
75            'file' => $file,
76            'name' => $name,
77            'signers' => $signers,
78            'callback' => $callback,
79            'userManager' => $user,
80            'policyOverrides' => $resolvedPolicy['policyOverrides'],
81            'policyActiveContext' => $resolvedPolicy['policyActiveContext'],
82            'settings' => $settings !== [] ? $settings : ($file['settings'] ?? []),
83        ];
84
85        if ($status !== null) {
86            $data['status'] = $status;
87        }
88
89        if ($files !== []) {
90            $data['files'] = $files;
91        }
92
93        if ($visibleElements !== null) {
94            $data['visibleElements'] = $visibleElements;
95        }
96
97        $this->requestSignatureService->validateNewRequestToFile($data);
98
99        if ($files !== []) {
100            $result = $this->requestSignatureService->saveFiles($data);
101            return [
102                'file' => $result['file'],
103                'children' => $result['children'] ?? [],
104            ];
105        }
106
107        $fileEntity = $this->requestSignatureService->save($data);
108
109        return [
110            'file' => $fileEntity,
111            'children' => $this->loadChildFilesIfEnvelope($fileEntity),
112        ];
113    }
114
115    /**
116     * @param list<array<string, mixed>> $signers
117     * @param array<string, mixed> $file
118     * @param array<string, mixed>|null $policy
119     * @param list<array<string, mixed>>|null $visibleElements
120     * @param array<string, mixed> $settings
121     * @return array{file: FileEntity, children: list<FileEntity>}
122     */
123    public function updateExistingRequest(
124        ?IUser $user,
125        array $signers,
126        string $uuid,
127        ?array $visibleElements,
128        array $file,
129        ?int $status,
130        ?array $policy = null,
131        ?string $name = null,
132        array $settings = [],
133    ): array {
134        $file = $this->fileInputValidator->normalizeNodeId($file);
135        $resolvedPolicy = $this->resolvePolicyPayload($policy);
136        $data = [
137            'uuid' => $uuid,
138            'file' => $file,
139            'signers' => $signers,
140            'userManager' => $user,
141            'visibleElements' => $visibleElements,
142            'policyOverrides' => $resolvedPolicy['policyOverrides'],
143            'policyActiveContext' => $resolvedPolicy['policyActiveContext'],
144            'name' => $name,
145            'settings' => $settings,
146        ];
147        if ($status !== null) {
148            $data['status'] = $status;
149        }
150
151        $this->signingRequestValidator->validateExistingFile($data);
152        $this->signingRequestValidator->validateWorkflowIsNotClosedByUuid($uuid);
153        $this->signingRequestValidator->validateFileStatus($data);
154        $this->signerValidator->validateIdentifySigners($data);
155        if (!empty($visibleElements)) {
156            $this->visibleElementValidator->validateVisibleElements($visibleElements, FileInputValidator::TYPE_VISIBLE_ELEMENT_PDF);
157        }
158        $fileEntity = $this->requestSignatureService->save($data);
159
160        return [
161            'file' => $fileEntity,
162            'children' => $this->loadChildFilesIfEnvelope($fileEntity),
163        ];
164    }
165
166    /**
167     * @param list<array<string, mixed>> $files
168     * @return list<array<string, mixed>>
169     */
170    private function normalizeNodeIds(array $files): array {
171        return array_map(
172            fn (mixed $file): mixed => is_array($file) ? $this->fileInputValidator->normalizeNodeId($file) : $file,
173            $files,
174        );
175    }
176
177    /** @return list<FileEntity> */
178    private function loadChildFilesIfEnvelope(FileEntity $fileEntity): array {
179        return $fileEntity->getParentFileId() === null || $fileEntity->isEnvelope()
180            ? $this->fileMapper->getChildrenFiles($fileEntity->getId())
181            : [];
182    }
183
184    /** @param array<string, mixed>|null $policy
185     * @return array<string, mixed>
186     */
187    private function extractPolicyOverrides(?array $policy): array {
188        $overrides = $policy['overrides'] ?? null;
189
190        return is_array($overrides) ? $overrides : [];
191    }
192
193    /** @param array<string, mixed>|null $policy
194     * @return array<string, mixed>|null
195     */
196    private function extractPolicyActiveContext(?array $policy): ?array {
197        $activeContext = $policy['activeContext'] ?? null;
198
199        return is_array($activeContext) ? $activeContext : null;
200    }
201}