Skip to content

Commit 8ef6026

Browse files
authored
Merge pull request #6008 from LibreSign/backport/6006/stable31
[stable31] feat: footer template editor
2 parents fc276a2 + c03b875 commit 8ef6026

File tree

20 files changed

+884
-65
lines changed

20 files changed

+884
-65
lines changed

REUSE.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ path = [
3333
"jsconfig.json",
3434
"l10n/*.js",
3535
"l10n/*.json",
36+
"lib/Handler/Templates/footer.twig",
3637
"Makefile",
3738
"lib/Vendor/.gitkeep",
3839
"openapi-administration.json",

lib/Controller/AdminController.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -791,7 +791,7 @@ public function deleteTsaConfig(): DataResponse {
791791
*
792792
* Returns the current footer template if set, otherwise returns the default template.
793793
*
794-
* @return DataResponse<Http::STATUS_OK, array{template: string, isDefault: bool}, array{}>
794+
* @return DataResponse<Http::STATUS_OK, array{template: string, isDefault: bool, preview_width: int, preview_height: int}, array{}>
795795
*
796796
* 200: OK
797797
*/
@@ -800,6 +800,8 @@ public function getFooterTemplate(): DataResponse {
800800
return new DataResponse([
801801
'template' => $this->footerService->getTemplate(),
802802
'isDefault' => $this->footerService->isDefaultTemplate(),
803+
'preview_width' => $this->appConfig->getValueInt(Application::APP_ID, 'footer_preview_width', 595),
804+
'preview_height' => $this->appConfig->getValueInt(Application::APP_ID, 'footer_preview_height', 100),
803805
]);
804806
}
805807

lib/Handler/FooterHandler.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,15 @@ class FooterHandler {
3636
private const MIN_QRCODE_SIZE = 100;
3737
private const POINT_TO_MILIMETER = 0.3527777778;
3838

39-
private TemplateVariables $templateVars;
40-
4139
public function __construct(
4240
private IAppConfig $appConfig,
4341
private PdfParserService $pdfParserService,
4442
private IURLGenerator $urlGenerator,
4543
private IL10N $l10n,
4644
private IFactory $l10nFactory,
4745
private ITempManager $tempManager,
46+
private TemplateVariables $templateVars,
4847
) {
49-
$this->templateVars = new TemplateVariables();
5048
}
5149

5250
public function getFooter(array $dimensions): string {
@@ -203,6 +201,10 @@ private function getQrCodeImageBase64(string $text): string {
203201
return $qrcode;
204202
}
205203

204+
public function getTemplateVariablesMetadata(): array {
205+
return $this->templateVars->getVariablesMetadata();
206+
}
207+
206208
private function setQrCodeSize(): void {
207209
$blockValues = $this->getQrCodeBlocks();
208210
$this->qrCode->setSize(self::MIN_QRCODE_SIZE);

lib/Handler/TemplateVariables.php

Lines changed: 72 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
namespace OCA\Libresign\Handler;
1111

12+
use OCP\IL10N;
13+
1214
/**
1315
* @method self setDirection(string $value)
1416
* @method self setLinkToSite(string $value)
@@ -31,21 +33,66 @@
3133
*/
3234
class TemplateVariables {
3335
private array $variables = [];
36+
private array $variablesMetadata = [];
3437

35-
/**
36-
* Allowed template variable names with their expected types
37-
*/
38-
private const ALLOWED_VARIABLES = [
39-
'direction' => 'string',
40-
'linkToSite' => 'string',
41-
'qrcode' => 'string',
42-
'qrcodeSize' => 'integer',
43-
'signedBy' => 'string',
44-
'signers' => 'array',
45-
'uuid' => 'string',
46-
'validateIn' => 'string',
47-
'validationSite' => 'string',
48-
];
38+
public function __construct(
39+
private IL10N $l10n,
40+
) {
41+
$this->initializeVariablesMetadata();
42+
}
43+
44+
private function initializeVariablesMetadata(): void {
45+
$this->variablesMetadata = [
46+
'direction' => [
47+
'type' => 'string',
48+
'description' => $this->l10n->t('Text direction for the footer (ltr or rtl based on language)'),
49+
'example' => 'ltr',
50+
],
51+
'linkToSite' => [
52+
'type' => 'string',
53+
'description' => $this->l10n->t('Link to LibreSign or custom website'),
54+
'example' => 'https://libresign.coop',
55+
'default' => 'https://libresign.coop',
56+
],
57+
'qrcode' => [
58+
'type' => 'string',
59+
'description' => $this->l10n->t('QR Code image in base64 format for document validation'),
60+
'example' => 'iVBORw0KGgoAAAANSUhEUgAA...',
61+
],
62+
'qrcodeSize' => [
63+
'type' => 'integer',
64+
'description' => $this->l10n->t('QR Code size in pixels (includes margin)'),
65+
'example' => 108,
66+
],
67+
'signedBy' => [
68+
'type' => 'string',
69+
'description' => $this->l10n->t('Message indicating the document was digitally signed'),
70+
'example' => 'Digitally signed by LibreSign.',
71+
'default' => $this->l10n->t('Digitally signed by LibreSign.'),
72+
],
73+
'signers' => [
74+
'type' => 'array',
75+
'description' => $this->l10n->t('Array of signers with displayName and signed timestamp'),
76+
'example' => '[{"displayName": "John Doe", "signed": "2025-01-01T10:00:00Z"}]',
77+
],
78+
'uuid' => [
79+
'type' => 'string',
80+
'description' => $this->l10n->t('Document unique identifier (UUID format)'),
81+
'example' => 'de0a18d4-fe65-4abc-bdd1-84e819700260',
82+
],
83+
'validateIn' => [
84+
'type' => 'string',
85+
'description' => $this->l10n->t('Validation message template with placeholder'),
86+
'example' => 'Validate in %s.',
87+
'default' => $this->l10n->t('Validate in %s.', ['%s']),
88+
],
89+
'validationSite' => [
90+
'type' => 'string',
91+
'description' => $this->l10n->t('Complete URL for document validation with UUID'),
92+
'example' => 'https://example.com/validation/de0a18d4-fe65-4abc-bdd1-84e819700260',
93+
],
94+
];
95+
}
4996

5097
/**
5198
* @throws \InvalidArgumentException if trying to access non-whitelisted variable or wrong type
@@ -70,13 +117,13 @@ public function __call(string $method, array $args): mixed {
70117
}
71118

72119
private function ensureAllowed(string $key): void {
73-
if (!array_key_exists($key, self::ALLOWED_VARIABLES)) {
120+
if (!array_key_exists($key, $this->variablesMetadata)) {
74121
throw new \InvalidArgumentException("Template variable '{$key}' is not allowed");
75122
}
76123
}
77124

78125
private function ensureType(string $key, mixed $value): void {
79-
$expected = self::ALLOWED_VARIABLES[$key];
126+
$expected = $this->variablesMetadata[$key]['type'];
80127
$actual = gettype($value);
81128

82129
if ($actual !== $expected) {
@@ -92,6 +139,15 @@ public function toArray(): array {
92139
return $this->variables;
93140
}
94141

142+
/**
143+
* Get metadata for all available template variables
144+
*
145+
* @return array Associative array of variable metadata (name => config)
146+
*/
147+
public function getVariablesMetadata(): array {
148+
return $this->variablesMetadata;
149+
}
150+
95151
/**
96152
* Merge additional variables, validating against whitelist and types
97153
*

lib/Handler/Templates/footer.twig

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
{#
2-
SPDX-FileCopyrightText: 2024 LibreCode coop and contributors
3-
SPDX-License-Identifier: AGPL-3.0-or-later
4-
#}
51
<table style="width:100%; border:0; {% if not qrcode %}padding-left:15px;{% endif %} font-size:8px;">
62
<tr>
73
{% if qrcode %}

lib/Service/FooterService.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,18 @@ public function renderPreviewPdf(string $template = '', int $width = 595, int $h
4646
$this->saveTemplate($template);
4747
}
4848

49+
// Generate a realistic UUID format for preview (36 chars with hyphens, same as real UUIDs)
50+
// This ensures QR code size matches the final document
51+
$previewUuid = sprintf(
52+
'preview-%04x-%04x-%04x-%012x',
53+
random_int(0, 0xffff),
54+
random_int(0, 0xffff),
55+
random_int(0, 0xffff),
56+
random_int(0, 0xffffffffffff)
57+
);
58+
4959
return $this->footerHandler
50-
->setTemplateVar('uuid', 'preview-' . bin2hex(random_bytes(8)))
60+
->setTemplateVar('uuid', $previewUuid)
5161
->setTemplateVar('signers', [
5262
[
5363
'displayName' => 'Preview Signer',
@@ -56,4 +66,8 @@ public function renderPreviewPdf(string $template = '', int $width = 595, int $h
5666
])
5767
->getFooter([['w' => $width, 'h' => $height]]);
5868
}
69+
70+
public function getTemplateVariablesMetadata(): array {
71+
return $this->footerHandler->getTemplateVariablesMetadata();
72+
}
5973
}

lib/Settings/Admin.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use OCA\Libresign\Exception\LibresignException;
1313
use OCA\Libresign\Handler\CertificateEngine\CertificateEngineFactory;
1414
use OCA\Libresign\Service\CertificatePolicyService;
15+
use OCA\Libresign\Service\FooterService;
1516
use OCA\Libresign\Service\IdentifyMethodService;
1617
use OCA\Libresign\Service\SignatureBackgroundService;
1718
use OCA\Libresign\Service\SignatureTextService;
@@ -32,6 +33,7 @@ public function __construct(
3233
private IAppConfig $appConfig,
3334
private SignatureTextService $signatureTextService,
3435
private SignatureBackgroundService $signatureBackgroundService,
36+
private FooterService $footerService,
3537
) {
3638
}
3739
public function getForm(): TemplateResponse {
@@ -58,6 +60,12 @@ public function getForm(): TemplateResponse {
5860
$this->initialState->provideInitialState('signature_font_size', $this->signatureTextService->getSignatureFontSize());
5961
$this->initialState->provideInitialState('signature_height', $this->signatureTextService->getFullSignatureHeight());
6062
$this->initialState->provideInitialState('signature_preview_zoom_level', $this->appConfig->getValueFloat(Application::APP_ID, 'signature_preview_zoom_level', 100));
63+
$this->initialState->provideInitialState('footer_preview_zoom_level', $this->appConfig->getValueFloat(Application::APP_ID, 'footer_preview_zoom_level', 100));
64+
$this->initialState->provideInitialState('footer_preview_width', $this->appConfig->getValueInt(Application::APP_ID, 'footer_preview_width', 595));
65+
$this->initialState->provideInitialState('footer_preview_height', $this->appConfig->getValueInt(Application::APP_ID, 'footer_preview_height', 100));
66+
$this->initialState->provideInitialState('footer_template_variables', $this->footerService->getTemplateVariablesMetadata());
67+
$this->initialState->provideInitialState('footer_template', $this->footerService->getTemplate());
68+
$this->initialState->provideInitialState('footer_template_is_default', $this->footerService->isDefaultTemplate());
6169
$this->initialState->provideInitialState('signature_render_mode', $this->signatureTextService->getRenderMode());
6270
$this->initialState->provideInitialState('signature_text_template', $this->signatureTextService->getTemplate());
6371
$this->initialState->provideInitialState('signature_width', $this->signatureTextService->getFullSignatureWidth());

openapi-administration.json

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2664,14 +2664,24 @@
26642664
"type": "object",
26652665
"required": [
26662666
"template",
2667-
"isDefault"
2667+
"isDefault",
2668+
"preview_width",
2669+
"preview_height"
26682670
],
26692671
"properties": {
26702672
"template": {
26712673
"type": "string"
26722674
},
26732675
"isDefault": {
26742676
"type": "boolean"
2677+
},
2678+
"preview_width": {
2679+
"type": "integer",
2680+
"format": "int64"
2681+
},
2682+
"preview_height": {
2683+
"type": "integer",
2684+
"format": "int64"
26752685
}
26762686
}
26772687
}

openapi-full.json

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11141,14 +11141,24 @@
1114111141
"type": "object",
1114211142
"required": [
1114311143
"template",
11144-
"isDefault"
11144+
"isDefault",
11145+
"preview_width",
11146+
"preview_height"
1114511147
],
1114611148
"properties": {
1114711149
"template": {
1114811150
"type": "string"
1114911151
},
1115011152
"isDefault": {
1115111153
"type": "boolean"
11154+
},
11155+
"preview_width": {
11156+
"type": "integer",
11157+
"format": "int64"
11158+
},
11159+
"preview_height": {
11160+
"type": "integer",
11161+
"format": "int64"
1115211162
}
1115311163
}
1115411164
}

package-lock.json

Lines changed: 32 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)