From 4671b8578239efb87d95b56d76224c809383d904 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Tue, 16 Dec 2025 17:05:09 -0300 Subject: [PATCH 01/17] feat: store description in notification metadata Include signer description in notification metadata when incrementing notification counter. This allows tracking custom messages sent to signers for each notification attempt. Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- lib/Db/SignRequestMapper.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/Db/SignRequestMapper.php b/lib/Db/SignRequestMapper.php index 30745d6ec2..adad246ce7 100644 --- a/lib/Db/SignRequestMapper.php +++ b/lib/Db/SignRequestMapper.php @@ -60,10 +60,17 @@ public function incrementNotificationCounter(SignRequest $signRequest, string $m if (!isset($metadata['notify'])) { $this->firstNotification = true; } - $metadata['notify'][] = [ + + $notificationEntry = [ 'method' => $method, 'date' => time(), ]; + + if (!empty($fromDatabase->getDescription())) { + $notificationEntry['description'] = $fromDatabase->getDescription(); + } + + $metadata['notify'][] = $notificationEntry; $fromDatabase->setMetadata($metadata); $this->update($fromDatabase); $this->db->commit(); From 32eb0101fbbb6f6323e2a7c9974e9b2632596ae5 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Tue, 16 Dec 2025 17:05:50 -0300 Subject: [PATCH 02/17] feat: add custom description to email notifications Add optional description parameter to notifyUnsignedUser and notifySignDataUpdated methods. When provided, the custom message is prepended to the email body, allowing personalized instructions for signers. Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- lib/Service/MailService.php | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/Service/MailService.php b/lib/Service/MailService.php index bb100e5f4e..5f24ff1d04 100644 --- a/lib/Service/MailService.php +++ b/lib/Service/MailService.php @@ -45,12 +45,18 @@ private function getFileById(int $fileId): File { /** * @psalm-suppress MixedMethodCall */ - public function notifySignDataUpdated(SignRequest $data, string $email): void { + public function notifySignDataUpdated(SignRequest $data, string $email, ?string $description = null): void { $emailTemplate = $this->mailer->createEMailTemplate('settings.TestEmail'); // TRANSLATORS The subject of the email that is sent after changes are made to the signature request that may affect something for the signer who will sign the document. Some possible reasons: URL for signature changed (when the URL expires), the person who requested the signature sent a notification $emailTemplate->setSubject($this->l10n->t('LibreSign: Changes into a file for you to sign')); $emailTemplate->addHeader(); $emailTemplate->addHeading($this->l10n->t('File to sign'), false); + + if (!empty($description)) { + $emailTemplate->addBodyText($description); + $emailTemplate->addBodyText(''); + } + $emailTemplate->addBodyText($this->l10n->t('Changes have been made in a file that you have to sign. Access the link below:')); $link = $this->urlGenerator->linkToRouteAbsolute('libresign.page.sign', ['uuid' => $data->getUuid()]); $file = $this->getFileById($data->getFileId()); @@ -76,11 +82,17 @@ public function notifySignDataUpdated(SignRequest $data, string $email): void { /** * @psalm-suppress MixedMethodCall */ - public function notifyUnsignedUser(SignRequest $data, string $email): void { + public function notifyUnsignedUser(SignRequest $data, string $email, ?string $description = null): void { $emailTemplate = $this->mailer->createEMailTemplate('settings.TestEmail'); $emailTemplate->setSubject($this->l10n->t('LibreSign: There is a file for you to sign')); $emailTemplate->addHeader(); $emailTemplate->addHeading($this->l10n->t('File to sign'), false); + + if (!empty($description)) { + $emailTemplate->addBodyText($description); + $emailTemplate->addBodyText(''); + } + $emailTemplate->addBodyText($this->l10n->t('There is a document for you to sign. Access the link below:')); $link = $this->urlGenerator->linkToRouteAbsolute('libresign.page.sign', ['uuid' => $data->getUuid()]); $file = $this->getFileById($data->getFileId()); From be8603a850ed19aff6d793ed915a9eb7eb81261d Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Tue, 16 Dec 2025 17:06:13 -0300 Subject: [PATCH 03/17] fix: check Activity admin setting before user preference Add explicit check for Activity admin (global) setting before checking user preference in email notifications. This ensures that when an admin disables email notifications globally, users cannot override it with their personal settings. The admin setting acts as a gate that must be enabled for user preferences to take effect. Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- lib/Listener/MailNotifyListener.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/Listener/MailNotifyListener.php b/lib/Listener/MailNotifyListener.php index 8ba2db6afc..c9a7bee83f 100644 --- a/lib/Listener/MailNotifyListener.php +++ b/lib/Listener/MailNotifyListener.php @@ -88,10 +88,10 @@ protected function sendSignMailNotification( $isFirstNotification = $this->signRequestMapper->incrementNotificationCounter($signRequest, 'mail'); if ($isFirstNotification) { - $this->mail->notifyUnsignedUser($signRequest, $email); + $this->mail->notifyUnsignedUser($signRequest, $email, $signRequest->getDescription()); return; } - $this->mail->notifySignDataUpdated($signRequest, $email); + $this->mail->notifySignDataUpdated($signRequest, $email, $signRequest->getDescription()); } catch (\InvalidArgumentException $e) { $this->logger->error($e->getMessage(), ['exception' => $e]); return; @@ -173,6 +173,10 @@ private function isNotificationDisabledAtActivity(string $userId, string $type): } $activityUserSettings = \OCP\Server::get(\OCA\Activity\UserSettings::class); if ($activityUserSettings) { + $adminSetting = $activityUserSettings->getAdminSetting('email', $type); + if (!$adminSetting) { + return true; + } $notificationSetting = $activityUserSettings->getUserSetting( $userId, 'email', From d1d69671d3572f3f784284ae50c4bbb736bc6d83 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Tue, 16 Dec 2025 17:06:39 -0300 Subject: [PATCH 04/17] fix: check Activity admin setting in notification listener Add explicit check for Activity admin (global) setting before checking user preference for in-app notifications. This ensures consistency across all notification channels and prevents users from enabling notifications when disabled globally by the admin. Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- lib/Listener/NotificationListener.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/Listener/NotificationListener.php b/lib/Listener/NotificationListener.php index 05cd3e1e10..a721af1aa5 100644 --- a/lib/Listener/NotificationListener.php +++ b/lib/Listener/NotificationListener.php @@ -209,13 +209,16 @@ private function isNotificationDisabledAtActivity(string $userId, string $type): } $activityUserSettings = \OCP\Server::get(\OCA\Activity\UserSettings::class); if ($activityUserSettings) { + $adminSetting = $activityUserSettings->getAdminSetting('notification', $type); + if (!$adminSetting) { + return true; + } + $notificationSetting = $activityUserSettings->getUserSetting( $userId, 'notification', $type ); - // If setting is explicitly false, notifications are disabled - // If setting is null/not configured, notifications are enabled by default if ($notificationSetting === false) { return true; } From cf3de73cb000edc1e9e901bb426b30fd31534f5d Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Tue, 16 Dec 2025 17:06:47 -0300 Subject: [PATCH 05/17] feat: add custom description to gateway notifications Support custom signer descriptions in SMS, Signal, Telegram, WhatsApp, and XMPP notifications. The description is prepended to the notification message when provided, allowing personalized instructions through all supported notification channels. Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- lib/Listener/TwofactorGatewayListener.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/Listener/TwofactorGatewayListener.php b/lib/Listener/TwofactorGatewayListener.php index 5581c209f1..f8ebddf498 100644 --- a/lib/Listener/TwofactorGatewayListener.php +++ b/lib/Listener/TwofactorGatewayListener.php @@ -80,10 +80,16 @@ protected function sendSignNotification( } $isFirstNotification = $this->signRequestMapper->incrementNotificationCounter($signRequest, $entity->getIdentifierKey()); + + $message = ''; + if (!empty($signRequest->getDescription())) { + $message = $signRequest->getDescription() . "\n\n"; + } + if ($isFirstNotification) { - $message = $this->l10n->t('There is a document for you to sign. Access the link below:'); + $message .= $this->l10n->t('There is a document for you to sign. Access the link below:'); } else { - $message = $this->l10n->t('Changes have been made in a file that you have to sign. Access the link below:'); + $message .= $this->l10n->t('Changes have been made in a file that you have to sign. Access the link below:'); } $message .= "\n"; $link = $this->urlGenerator->linkToRouteAbsolute('libresign.page.sign', ['uuid' => $signRequest->getUuid()]); From f52b6ca9d4e4e7fea924ee9d08d6bc34637f7e9b Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Tue, 16 Dec 2025 17:06:57 -0300 Subject: [PATCH 06/17] docs: update API response definitions Add description, displayName, notify, and acceptsEmailNotifications fields to OpenAPI response type definitions for LibresignNewSigner and LibresignIdentifyAccount. Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- lib/ResponseDefinitions.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/ResponseDefinitions.php b/lib/ResponseDefinitions.php index a8b88477ff..aabe88d612 100644 --- a/lib/ResponseDefinitions.php +++ b/lib/ResponseDefinitions.php @@ -28,6 +28,9 @@ * email?: string, * account?: string, * }, + * displayName?: string, + * description?: string, + * notify?: non-negative-int, * signingOrder?: non-negative-int, * } * @psalm-type LibresignNewFile = array{ @@ -55,6 +58,7 @@ * subname: string, * shareType: 0|4, * icon?: 'icon-mail'|'icon-user', + * acceptsEmailNotifications?: boolean, * } * @psalm-type LibresignPagination = array{ * total: non-negative-int, From 77f59e16f593dea7c8774abd8775acad0c7aac47 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Tue, 16 Dec 2025 17:07:06 -0300 Subject: [PATCH 07/17] feat: add email notification preferences to account search Include acceptsEmailNotifications field in search results for account method signers. This field indicates whether a user accepts email notifications based on both Activity admin settings and user preferences. Returns false when user has no email, Activity app is unavailable, or notifications are disabled at admin or user level. Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- lib/Controller/IdentifyAccountController.php | 56 ++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/lib/Controller/IdentifyAccountController.php b/lib/Controller/IdentifyAccountController.php index cd370524a2..4dff5d32ac 100644 --- a/lib/Controller/IdentifyAccountController.php +++ b/lib/Controller/IdentifyAccountController.php @@ -19,8 +19,10 @@ use OCP\AppFramework\Http\Attribute\NoAdminRequired; use OCP\AppFramework\Http\DataResponse; use OCP\Collaboration\Collaborators\ISearch; +use OCP\IConfig; use OCP\IRequest; use OCP\IURLGenerator; +use OCP\IUserManager; use OCP\IUserSession; use OCP\Share\IShare; @@ -36,6 +38,8 @@ public function __construct( private IURLGenerator $urlGenerator, private Email $identifyEmailMethod, private Account $identifyAccountMethod, + private IUserManager $userManager, + private IConfig $config, ) { parent::__construct(Application::APP_ID, $request); } @@ -76,6 +80,7 @@ public function search(string $search = '', string $method = '', int $page = 1, $return = $this->addHerselfAccount($return, $search); $return = $this->addHerselfEmail($return, $search); $return = $this->replaceShareTypeByMethod($return); + $return = $this->addEmailNotificationPreference($return); $return = $this->excludeNotAllowed($return); return new DataResponse($return); @@ -239,4 +244,55 @@ private function replaceShareTypeByMethod(array $list): array { } return $list; } + + private function addEmailNotificationPreference(array $list): array { + foreach ($list as $key => $item) { + if ($item['method'] !== 'account') { + continue; + } + + $user = $this->userManager->get($item['id']); + if ($user === null) { + $list[$key]['acceptsEmailNotifications'] = false; + continue; + } + + $email = $user->getEMailAddress(); + if (empty($email)) { + $list[$key]['acceptsEmailNotifications'] = false; + continue; + } + + if ($this->isNotificationDisabledAtActivity($user->getUID(), 'libresign_file_to_sign')) { + $list[$key]['acceptsEmailNotifications'] = false; + continue; + } + + $list[$key]['acceptsEmailNotifications'] = true; + } + return $list; + } + + private function isNotificationDisabledAtActivity(string $userId, string $type): bool { + if (!class_exists(\OCA\Activity\UserSettings::class)) { + return false; + } + $activityUserSettings = \OCP\Server::get(\OCA\Activity\UserSettings::class); + if ($activityUserSettings) { + $adminSetting = $activityUserSettings->getAdminSetting('email', $type); + if (!$adminSetting) { + return true; + } + + $notificationSetting = $activityUserSettings->getUserSetting( + $userId, + 'email', + $type + ); + if (!$notificationSetting) { + return true; + } + } + return false; + } } From d0df5e446ee60248c92ce80cd95ebd6e96af487a Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Tue, 16 Dec 2025 17:07:13 -0300 Subject: [PATCH 08/17] docs: regenerate OpenAPI specification Update OpenAPI schemas to include acceptsEmailNotifications in LibresignIdentifyAccount and description/displayName/notify in LibresignNewSigner types. Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- openapi-full.json | 14 ++++++++++++++ openapi.json | 14 ++++++++++++++ src/types/openapi/openapi-full.ts | 5 +++++ src/types/openapi/openapi.ts | 5 +++++ 4 files changed, 38 insertions(+) diff --git a/openapi-full.json b/openapi-full.json index 410bdc6f7e..4c065cb325 100644 --- a/openapi-full.json +++ b/openapi-full.json @@ -453,6 +453,9 @@ "icon-mail", "icon-user" ] + }, + "acceptsEmailNotifications": { + "type": "boolean" } } }, @@ -514,6 +517,17 @@ } } }, + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "notify": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, "signingOrder": { "type": "integer", "format": "int64", diff --git a/openapi.json b/openapi.json index 217121894d..0eb99d6307 100644 --- a/openapi.json +++ b/openapi.json @@ -383,6 +383,9 @@ "icon-mail", "icon-user" ] + }, + "acceptsEmailNotifications": { + "type": "boolean" } } }, @@ -444,6 +447,17 @@ } } }, + "displayName": { + "type": "string" + }, + "description": { + "type": "string" + }, + "notify": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, "signingOrder": { "type": "integer", "format": "int64", diff --git a/src/types/openapi/openapi-full.ts b/src/types/openapi/openapi-full.ts index d3b0efb37d..f068780225 100644 --- a/src/types/openapi/openapi-full.ts +++ b/src/types/openapi/openapi-full.ts @@ -1584,6 +1584,7 @@ export type components = { shareType: 0 | 4; /** @enum {string} */ icon?: "icon-mail" | "icon-user"; + acceptsEmailNotifications?: boolean; }; IdentifyMethod: { /** @enum {string} */ @@ -1603,6 +1604,10 @@ export type components = { email?: string; account?: string; }; + displayName?: string; + description?: string; + /** Format: int64 */ + notify?: number; /** Format: int64 */ signingOrder?: number; }; diff --git a/src/types/openapi/openapi.ts b/src/types/openapi/openapi.ts index 70783f69de..8e0923626c 100644 --- a/src/types/openapi/openapi.ts +++ b/src/types/openapi/openapi.ts @@ -1128,6 +1128,7 @@ export type components = { shareType: 0 | 4; /** @enum {string} */ icon?: "icon-mail" | "icon-user"; + acceptsEmailNotifications?: boolean; }; IdentifyMethod: { /** @enum {string} */ @@ -1147,6 +1148,10 @@ export type components = { email?: string; account?: string; }; + displayName?: string; + description?: string; + /** Format: int64 */ + notify?: number; /** Format: int64 */ signingOrder?: number; }; From f22591291832f50a4b10f76169648657eb062873 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Tue, 16 Dec 2025 17:07:26 -0300 Subject: [PATCH 09/17] feat: add custom message UI in signer identification Add switch-based UI to enable custom messages for signers during identification. The custom message field: - Appears conditionally based on method and email notification settings - Is hidden for account method when user doesn't accept email - Supports up to 500 characters with 3-row textarea - Resets when toggled off or signer changes to non-accepting account - Is saved as part of signer data Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- src/Components/Request/IdentifySigner.vue | 52 +++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/Components/Request/IdentifySigner.vue b/src/Components/Request/IdentifySigner.vue index d34f6071fc..40d575ed4e 100644 --- a/src/Components/Request/IdentifySigner.vue +++ b/src/Components/Request/IdentifySigner.vue @@ -26,6 +26,23 @@ :error="nameHaveError" :helper-text="nameHelperText" @update:value="onNameChange" /> + +
+ + {{ t('libresign', 'Add custom message') }} + + +
+