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
4a41fb8
chore: upgrade @libresign/vue-pdf-editor to v1.5.0
vitormattos Dec 4, 2025
30e7bc8
feat: add FooterTemplateEditor component with live preview
vitormattos Dec 4, 2025
907f362
feat: integrate FooterTemplateEditor into Validation settings
vitormattos Dec 4, 2025
0c8818f
feat: add footer_preview_zoom_level to initial state
vitormattos Dec 4, 2025
217c7e9
feat: add footer preview dimensions to initial state
vitormattos Dec 4, 2025
84f08e9
feat: add dimension controls to footer template editor
vitormattos Dec 4, 2025
8904c7b
chore: move footer.twig SPDX headers to REUSE.toml
vitormattos Dec 4, 2025
df23e7e
chore: add vue-codemirror and codemirror dependencies
vitormattos Dec 4, 2025
fa71d50
feat: add CodeEditor component with syntax highlighting
vitormattos Dec 4, 2025
e3b7be1
feat: replace NcTextArea with CodeEditor in footer template
vitormattos Dec 4, 2025
385d1b2
fix: use realistic UUID format in footer preview
vitormattos Dec 4, 2025
7ae2f55
refactor: add IL10N injection and metadata to TemplateVariables
vitormattos Dec 5, 2025
9b252f1
refactor: inject TemplateVariables into FooterHandler via DI
vitormattos Dec 5, 2025
36bd533
feat: add template variables metadata endpoint to FooterService
vitormattos Dec 5, 2025
8974421
feat: add API endpoint for footer template variables metadata
vitormattos Dec 5, 2025
c94bbac
docs: update OpenAPI documentation and TypeScript types
vitormattos Dec 5, 2025
00aa5ef
style: fix code style in TemplateVariablesTest
vitormattos Dec 5, 2025
fabf3bf
refactor: provide footer template variables via initial state
vitormattos Dec 5, 2025
9a5ef1b
feat: add accessibility label support to CodeEditor component
vitormattos Dec 5, 2025
d7c25b6
feat: add template variables helper dialog to footer editor
vitormattos Dec 5, 2025
168dd1e
feat: provide footer template data via initial state
vitormattos Dec 5, 2025
3f5b204
docs: improve footer template endpoint documentation
vitormattos Dec 5, 2025
e897ebc
feat: add footer template editor with variable helper
vitormattos Dec 5, 2025
a3f058e
feat: add preview dimensions to footer template GET endpoint
vitormattos Dec 5, 2025
d88170d
fix: stylelint
vitormattos Dec 5, 2025
eff6ef7
test: add FooterService mock to AdminTest
vitormattos Dec 5, 2025
c03b875
fix: add pending 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