Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
94.12% covered (success)
94.12%
32 / 34
83.33% covered (warning)
83.33%
5 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
GatewayNotificationComposer
94.12% covered (success)
94.12%
32 / 34
83.33% covered (warning)
83.33%
5 / 6
11.02
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 compose
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 composePayload
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 render
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 signRequest
87.50% covered (warning)
87.50%
14 / 16
0.00% covered (danger)
0.00%
0 / 1
5.05
 signedFile
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
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\Notification;
10
11use OCA\Libresign\Db\File;
12use OCA\Libresign\Db\SignRequest;
13use OCP\IL10N;
14use OCP\IURLGenerator;
15
16/** Builds fully localized notification content before a transport submission is claimed. */
17final class GatewayNotificationComposer {
18    public function __construct(
19        private NotificationHistoryService $history,
20        private IL10N $l10n,
21        private IURLGenerator $urlGenerator,
22        private WhatsAppBusinessTemplateOptions $templates,
23    ) {
24    }
25
26    public function compose(string $eventType, SignRequest $request, File $file, string $method): string {
27        return $this->render($eventType, $request, $file, $method)[0];
28    }
29
30    public function composePayload(string $eventType, SignRequest $request, File $file, string $method, string $gatewayName): GatewayPayload {
31        [$message, $text, $url] = $this->render($eventType, $request, $file, $method);
32        $extra = $gatewayName === 'whatsappbusiness'
33            ? $this->templates->forNotification($text, $url)
34            : [];
35        return new GatewayPayload($gatewayName, $message, $extra);
36    }
37
38    /** @return array{string, string, string} Full message, independent template text, action URL. */
39    private function render(string $eventType, SignRequest $request, File $file, string $method): array {
40        return match ($eventType) {
41            'sign_request' => $this->signRequest($request, $file, $method),
42            'file_signed' => $this->signedFile($request, $file),
43            default => throw new \InvalidArgumentException('Unsupported gateway notification event'),
44        };
45    }
46
47    /** @return array{string, string, string} */
48    private function signRequest(SignRequest $request, File $file, string $method): array {
49        $url = $this->urlGenerator->linkToRouteAbsolute('libresign.page.sign', ['uuid' => $request->getUuid()]);
50        $description = $request->getDescription();
51        $hasDescription = is_string($description) && trim($description) !== '';
52        $name = $file->getName();
53
54        if ($this->history->isFirst($request, $method)) {
55            if ($hasDescription) {
56                // TRANSLATORS Initial signature invitation with a sender's description.
57                // %1$s is that description, %2$s is the document name, and %3$s is the signing URL.
58                // Example with "Please review this agreement", "Agreement.pdf", and "https://example.invalid/sign":
59                //   Please review this agreement
60                //
61                //   There is a document for you to sign. Access the link below:
62                //   Agreement.pdf: https://example.invalid/sign
63                // Keep the whole message translatable. Preserve the intended paragraph break; placeholders may be reordered for the target language.
64                $message = $this->l10n->t("%1\$s\n\nThere is a document for you to sign. Access the link below:\n%2\$s: %3\$s", [$description, $name, $url]);
65                // TRANSLATORS Initial invitation sent through a WhatsApp Business UTILITY template.
66                // %1$s is the sender's description; %2$s is the document name. The URL is not in this string.
67                // The translated text has paragraph breaks, but the first WhatsApp template value is normalized to one line.
68                // Example values sent to the approved template:
69                //   {{1}} = Please review this agreement There is a document for you to sign. Access the link below: Agreement.pdf:
70                //   {{2}} = https://example.invalid/sign
71                // Do not add the URL to this translated text or alter the positional template variables.
72                $text = $this->l10n->t("%1\$s\n\nThere is a document for you to sign. Access the link below:\n%2\$s:", [$description, $name]);
73            } else {
74                // TRANSLATORS Initial signature invitation without a sender's description.
75                // %1$s is the document name; %2$s is the signing URL.
76                // Example with "Agreement.pdf" and "https://example.invalid/sign":
77                //   There is a document for you to sign. Access the link below:
78                //   Agreement.pdf: https://example.invalid/sign
79                // The entire message is translatable; placeholders may be reordered for the target language.
80                $message = $this->l10n->t("There is a document for you to sign. Access the link below:\n%1\$s: %2\$s", [$name, $url]);
81                // TRANSLATORS Initial invitation without a description, for a WhatsApp Business UTILITY template.
82                // %1$s is the document name. The signing URL is a separate template variable, not part of this text.
83                // Example values sent to the approved template (the first one is a single line):
84                //   {{1}} = There is a document for you to sign. Access the link below: Agreement.pdf:
85                //   {{2}} = https://example.invalid/sign
86                // Translate the whole text while keeping the URL out of the first template value.
87                $text = $this->l10n->t("There is a document for you to sign. Access the link below:\n%1\$s:", [$name]);
88            }
89        } elseif ($hasDescription) {
90            // TRANSLATORS Updated signature invitation with a sender's description.
91            // %1$s is the description, %2$s is the document name, and %3$s is the signing URL.
92            // Example with "Please check the new version", "Agreement.pdf", and "https://example.invalid/sign":
93            //   Please check the new version
94            //
95            //   Changes have been made in a file that you have to sign. Access the link below:
96            //   Agreement.pdf: https://example.invalid/sign
97            // Keep the paragraph break. The placeholders can be reordered to suit the language.
98            $message = $this->l10n->t("%1\$s\n\nChanges have been made in a file that you have to sign. Access the link below:\n%2\$s: %3\$s", [$description, $name, $url]);
99            // TRANSLATORS Updated invitation with a description, for a WhatsApp Business UTILITY template.
100            // %1$s is the sender's description; %2$s is the document name. The URL is supplied separately.
101            // Example values sent to the template after the translated text is normalized to one line:
102            //   {{1}} = Please check the new version Changes have been made in a file that you have to sign. Access the link below: Agreement.pdf:
103            //   {{2}} = https://example.invalid/sign
104            // Do not insert the separate URL into this translated text.
105            $text = $this->l10n->t("%1\$s\n\nChanges have been made in a file that you have to sign. Access the link below:\n%2\$s:", [$description, $name]);
106        } else {
107            // TRANSLATORS Updated signature invitation without a sender's description.
108            // %1$s is the document name and %2$s is the signing URL.
109            // Example with "Agreement.pdf" and "https://example.invalid/sign":
110            //   Changes have been made in a file that you have to sign. Access the link below:
111            //   Agreement.pdf: https://example.invalid/sign
112            // The full notification can be rearranged for the target language.
113            $message = $this->l10n->t("Changes have been made in a file that you have to sign. Access the link below:\n%1\$s: %2\$s", [$name, $url]);
114            // TRANSLATORS Updated invitation without a description, for a WhatsApp Business UTILITY template.
115            // %1$s is the document name, while the signing URL is supplied as the next template variable.
116            // Example values sent to the template (the first one is a single line):
117            //   {{1}} = Changes have been made in a file that you have to sign. Access the link below: Agreement.pdf:
118            //   {{2}} = https://example.invalid/sign
119            // The translated text must not include the separate signing URL.
120            $text = $this->l10n->t("Changes have been made in a file that you have to sign. Access the link below:\n%1\$s:", [$name]);
121        }
122
123        return [$message, $text, $url];
124    }
125
126    /** @return array{string, string, string} */
127    private function signedFile(SignRequest $request, File $file): array {
128        $url = $this->urlGenerator->linkToRouteAbsolute('libresign.page.validationFilePublic', ['uuid' => $file->getUuid()]);
129        $name = $file->getName();
130        $signer = $request->getDisplayName();
131        // TRANSLATORS Signature completion notification.
132        // %1$s is the signer's display name, %2$s is the document name, and %3$s is the public validation URL.
133        // Example with "Alex Example", "Agreement.pdf", and "https://example.invalid/validate":
134        //   LibreSign: A file has been signed
135        //   Alex Example signed the document. You can access it using the link below:
136        //   Agreement.pdf: https://example.invalid/validate
137        // Keep the full notification and its line breaks translatable; the placeholders may be reordered.
138        $message = $this->l10n->t("LibreSign: A file has been signed\n%1\$s signed the document. You can access it using the link below:\n%2\$s: %3\$s", [$signer, $name, $url]);
139        // TRANSLATORS Signature completion for a WhatsApp Business UTILITY template.
140        // %1$s is the signer's display name, and %2$s is the document name. The validation URL is separate.
141        // Example values sent to the approved template (the first one is a single line):
142        //   {{1}} = LibreSign: A file has been signed Alex Example signed the document. You can access it using the link below: Agreement.pdf:
143        //   {{2}} = https://example.invalid/validate
144        // Do not include the separate validation URL in the translated text.
145        $text = $this->l10n->t("LibreSign: A file has been signed\n%1\$s signed the document. You can access it using the link below:\n%2\$s:", [$signer, $name]);
146        return [$message, $text, $url];
147    }
148}