Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
f0b2c07
chore: upgrade @libresign/vue-pdf-editor to v1.5.0
vitormattos Dec 4, 2025
478b07a
feat: add FooterTemplateEditor component with live preview
vitormattos Dec 4, 2025
8195413
feat: integrate FooterTemplateEditor into Validation settings
vitormattos Dec 4, 2025
6603c0b
feat: add footer_preview_zoom_level to initial state
vitormattos Dec 4, 2025
fe7d43a
feat: add footer preview dimensions to initial state
vitormattos Dec 4, 2025
8145812
feat: add dimension controls to footer template editor
vitormattos Dec 4, 2025
319752e
chore: move footer.twig SPDX headers to REUSE.toml
vitormattos Dec 4, 2025
2ebd847
chore: add vue-codemirror and codemirror dependencies
vitormattos Dec 4, 2025
84aaae1
feat: add CodeEditor component with syntax highlighting
vitormattos Dec 4, 2025
e207ba3
feat: replace NcTextArea with CodeEditor in footer template
vitormattos Dec 4, 2025
1c84886
fix: use realistic UUID format in footer preview
vitormattos Dec 4, 2025
111ca58
refactor: add IL10N injection and metadata to TemplateVariables
vitormattos Dec 5, 2025
34bddf0
refactor: inject TemplateVariables into FooterHandler via DI
vitormattos Dec 5, 2025
41db62b
feat: add template variables metadata endpoint to FooterService
vitormattos Dec 5, 2025
cdae4fa
feat: add API endpoint for footer template variables metadata
vitormattos Dec 5, 2025
397d33e
docs: update OpenAPI documentation and TypeScript types
vitormattos Dec 5, 2025
41e5d69
style: fix code style in TemplateVariablesTest
vitormattos Dec 5, 2025
7584b19
refactor: provide footer template variables via initial state
vitormattos Dec 5, 2025
3749020
feat: add accessibility label support to CodeEditor component
vitormattos Dec 5, 2025
fb7c241
feat: add template variables helper dialog to footer editor
vitormattos Dec 5, 2025
9952fc3
feat: provide footer template data via initial state
vitormattos Dec 5, 2025
bd8409b
docs: improve footer template endpoint documentation
vitormattos Dec 5, 2025
21d8048
feat: add footer template editor with variable helper
vitormattos Dec 5, 2025
fe686c2
feat: add preview dimensions to footer template GET endpoint
vitormattos Dec 5, 2025
606b447
fix: stylelint
vitormattos Dec 5, 2025
99e8c59
test: add FooterService mock to AdminTest
vitormattos Dec 5, 2025
7da750b
fix: add pendign code after backport of #6006
vitormattos Dec 5, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions REUSE.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ path = [
"jsconfig.json",
"l10n/*.js",
"l10n/*.json",
"lib/Handler/Templates/footer.twig",
"Makefile",
"lib/Vendor/.gitkeep",
"openapi-administration.json",
Expand Down
4 changes: 3 additions & 1 deletion lib/Controller/AdminController.php
Original file line number Diff line number Diff line change
Expand Up @@ -791,7 +791,7 @@ public function deleteTsaConfig(): DataResponse {
*
* Returns the current footer template if set, otherwise returns the default template.
*
* @return DataResponse<Http::STATUS_OK, array{template: string, isDefault: bool}, array{}>
* @return DataResponse<Http::STATUS_OK, array{template: string, isDefault: bool, preview_width: int, preview_height: int}, array{}>
*
* 200: OK
*/
Expand All @@ -800,6 +800,8 @@ public function getFooterTemplate(): DataResponse {
return new DataResponse([
'template' => $this->footerService->getTemplate(),
'isDefault' => $this->footerService->isDefaultTemplate(),
'preview_width' => $this->appConfig->getValueInt(Application::APP_ID, 'footer_preview_width', 595),
'preview_height' => $this->appConfig->getValueInt(Application::APP_ID, 'footer_preview_height', 100),
]);
}

Expand Down
8 changes: 5 additions & 3 deletions lib/Handler/FooterHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,15 @@ class FooterHandler {
private const MIN_QRCODE_SIZE = 100;
private const POINT_TO_MILIMETER = 0.3527777778;

private TemplateVariables $templateVars;

public function __construct(
private IAppConfig $appConfig,
private PdfParserService $pdfParserService,
private IURLGenerator $urlGenerator,
private IL10N $l10n,
private IFactory $l10nFactory,
private ITempManager $tempManager,
private TemplateVariables $templateVars,
) {
$this->templateVars = new TemplateVariables();
}

public function getFooter(array $dimensions): string {
Expand Down Expand Up @@ -203,6 +201,10 @@ private function getQrCodeImageBase64(string $text): string {
return $qrcode;
}

public function getTemplateVariablesMetadata(): array {
return $this->templateVars->getVariablesMetadata();
}

private function setQrCodeSize(): void {
$blockValues = $this->getQrCodeBlocks();
$this->qrCode->setSize(self::MIN_QRCODE_SIZE);
Expand Down
88 changes: 72 additions & 16 deletions lib/Handler/TemplateVariables.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

namespace OCA\Libresign\Handler;

use OCP\IL10N;

/**
* @method self setDirection(string $value)
* @method self setLinkToSite(string $value)
Expand All @@ -31,21 +33,66 @@
*/
class TemplateVariables {
private array $variables = [];
private array $variablesMetadata = [];

/**
* Allowed template variable names with their expected types
*/
private const ALLOWED_VARIABLES = [
'direction' => 'string',
'linkToSite' => 'string',
'qrcode' => 'string',
'qrcodeSize' => 'integer',
'signedBy' => 'string',
'signers' => 'array',
'uuid' => 'string',
'validateIn' => 'string',
'validationSite' => 'string',
];
public function __construct(
private IL10N $l10n,
) {
$this->initializeVariablesMetadata();
}

private function initializeVariablesMetadata(): void {
$this->variablesMetadata = [
'direction' => [
'type' => 'string',
'description' => $this->l10n->t('Text direction for the footer (ltr or rtl based on language)'),
'example' => 'ltr',
],
'linkToSite' => [
'type' => 'string',
'description' => $this->l10n->t('Link to LibreSign or custom website'),
'example' => 'https://libresign.coop',
'default' => 'https://libresign.coop',
],
'qrcode' => [
'type' => 'string',
'description' => $this->l10n->t('QR Code image in base64 format for document validation'),
'example' => 'iVBORw0KGgoAAAANSUhEUgAA...',
],
'qrcodeSize' => [
'type' => 'integer',
'description' => $this->l10n->t('QR Code size in pixels (includes margin)'),
'example' => 108,
],
'signedBy' => [
'type' => 'string',
'description' => $this->l10n->t('Message indicating the document was digitally signed'),
'example' => 'Digitally signed by LibreSign.',
'default' => $this->l10n->t('Digitally signed by LibreSign.'),
],
'signers' => [
'type' => 'array',
'description' => $this->l10n->t('Array of signers with displayName and signed timestamp'),
'example' => '[{"displayName": "John Doe", "signed": "2025-01-01T10:00:00Z"}]',
],
'uuid' => [
'type' => 'string',
'description' => $this->l10n->t('Document unique identifier (UUID format)'),
'example' => 'de0a18d4-fe65-4abc-bdd1-84e819700260',
],
'validateIn' => [
'type' => 'string',
'description' => $this->l10n->t('Validation message template with placeholder'),
'example' => 'Validate in %s.',
'default' => $this->l10n->t('Validate in %s.', ['%s']),
],
'validationSite' => [
'type' => 'string',
'description' => $this->l10n->t('Complete URL for document validation with UUID'),
'example' => 'https://example.com/validation/de0a18d4-fe65-4abc-bdd1-84e819700260',
],
];
}

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

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

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

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

/**
* Get metadata for all available template variables
*
* @return array Associative array of variable metadata (name => config)
*/
public function getVariablesMetadata(): array {
return $this->variablesMetadata;
}

/**
* Merge additional variables, validating against whitelist and types
*
Expand Down
4 changes: 0 additions & 4 deletions lib/Handler/Templates/footer.twig
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
{#
SPDX-FileCopyrightText: 2024 LibreCode coop and contributors
SPDX-License-Identifier: AGPL-3.0-or-later
#}
<table style="width:100%; border:0; {% if not qrcode %}padding-left:15px;{% endif %} font-size:8px;">
<tr>
{% if qrcode %}
Expand Down
16 changes: 15 additions & 1 deletion lib/Service/FooterService.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,18 @@ public function renderPreviewPdf(string $template = '', int $width = 595, int $h
$this->saveTemplate($template);
}

// Generate a realistic UUID format for preview (36 chars with hyphens, same as real UUIDs)
// This ensures QR code size matches the final document
$previewUuid = sprintf(
'preview-%04x-%04x-%04x-%012x',
random_int(0, 0xffff),
random_int(0, 0xffff),
random_int(0, 0xffff),
random_int(0, 0xffffffffffff)
);

return $this->footerHandler
->setTemplateVar('uuid', 'preview-' . bin2hex(random_bytes(8)))
->setTemplateVar('uuid', $previewUuid)
->setTemplateVar('signers', [
[
'displayName' => 'Preview Signer',
Expand All @@ -56,4 +66,8 @@ public function renderPreviewPdf(string $template = '', int $width = 595, int $h
])
->getFooter([['w' => $width, 'h' => $height]]);
}

public function getTemplateVariablesMetadata(): array {
return $this->footerHandler->getTemplateVariablesMetadata();
}
}
8 changes: 8 additions & 0 deletions lib/Settings/Admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use OCA\Libresign\Exception\LibresignException;
use OCA\Libresign\Handler\CertificateEngine\CertificateEngineFactory;
use OCA\Libresign\Service\CertificatePolicyService;
use OCA\Libresign\Service\FooterService;
use OCA\Libresign\Service\IdentifyMethodService;
use OCA\Libresign\Service\SignatureBackgroundService;
use OCA\Libresign\Service\SignatureTextService;
Expand All @@ -32,6 +33,7 @@ public function __construct(
private IAppConfig $appConfig,
private SignatureTextService $signatureTextService,
private SignatureBackgroundService $signatureBackgroundService,
private FooterService $footerService,
) {
}
public function getForm(): TemplateResponse {
Expand All @@ -58,6 +60,12 @@ public function getForm(): TemplateResponse {
$this->initialState->provideInitialState('signature_font_size', $this->signatureTextService->getSignatureFontSize());
$this->initialState->provideInitialState('signature_height', $this->signatureTextService->getFullSignatureHeight());
$this->initialState->provideInitialState('signature_preview_zoom_level', $this->appConfig->getValueFloat(Application::APP_ID, 'signature_preview_zoom_level', 100));
$this->initialState->provideInitialState('footer_preview_zoom_level', $this->appConfig->getValueFloat(Application::APP_ID, 'footer_preview_zoom_level', 100));
$this->initialState->provideInitialState('footer_preview_width', $this->appConfig->getValueInt(Application::APP_ID, 'footer_preview_width', 595));
$this->initialState->provideInitialState('footer_preview_height', $this->appConfig->getValueInt(Application::APP_ID, 'footer_preview_height', 100));
$this->initialState->provideInitialState('footer_template_variables', $this->footerService->getTemplateVariablesMetadata());
$this->initialState->provideInitialState('footer_template', $this->footerService->getTemplate());
$this->initialState->provideInitialState('footer_template_is_default', $this->footerService->isDefaultTemplate());
$this->initialState->provideInitialState('signature_render_mode', $this->signatureTextService->getRenderMode());
$this->initialState->provideInitialState('signature_text_template', $this->signatureTextService->getTemplate());
$this->initialState->provideInitialState('signature_width', $this->signatureTextService->getFullSignatureWidth());
Expand Down
12 changes: 11 additions & 1 deletion openapi-administration.json
Original file line number Diff line number Diff line change
Expand Up @@ -2664,14 +2664,24 @@
"type": "object",
"required": [
"template",
"isDefault"
"isDefault",
"preview_width",
"preview_height"
],
"properties": {
"template": {
"type": "string"
},
"isDefault": {
"type": "boolean"
},
"preview_width": {
"type": "integer",
"format": "int64"
},
"preview_height": {
"type": "integer",
"format": "int64"
}
}
}
Expand Down
12 changes: 11 additions & 1 deletion openapi-full.json
Original file line number Diff line number Diff line change
Expand Up @@ -11141,14 +11141,24 @@
"type": "object",
"required": [
"template",
"isDefault"
"isDefault",
"preview_width",
"preview_height"
],
"properties": {
"template": {
"type": "string"
},
"isDefault": {
"type": "boolean"
},
"preview_width": {
"type": "integer",
"format": "int64"
},
"preview_height": {
"type": "integer",
"format": "int64"
}
}
}
Expand Down
36 changes: 32 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading