+ * Widening these selectors would restyle the whole instance. + * + * UPSTREAM: patched at source in src/ExternalApp.vue (lines 34 and 46) and + * submitted to LibreSign. Once that lands and this instance runs a release + * containing it, this file can be deleted. + */ + +#body-public #content.app-public { + height: 100dvh; +} + +@media (max-width: 512px) { + #body-public #app-sidebar { + height: 100dvh; + } +} diff --git a/ansible/roles/podman/files/debyltechmail/img/debyltech-mark-white.png b/ansible/roles/podman/files/debyltechmail/img/debyltech-mark-white.png new file mode 100644 index 0000000000000000000000000000000000000000..07691f5cc38dfb6cebb6ffe9585a9e0681364eaf GIT binary patch literal 6745 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 BUkCsI literal 0 HcmV?d00001 diff --git a/ansible/roles/podman/files/debyltechmail/img/debyltech-wordmark.png b/ansible/roles/podman/files/debyltechmail/img/debyltech-wordmark.png new file mode 100644 index 0000000000000000000000000000000000000000..d74647ea6577afad2b6002432952f5f3e8e60b39 GIT binary patch literal 9624 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 z#@}0)O6`KxT4W*3)B*^AregisterEventListener(BeforeMessageSent::class, DebyltechMailListener::class); + + // BeforeTemplateRenderedEvent is dispatched from + // lib/private/AppFramework/Middleware/AdditionalScriptsMiddleware.php:35 and + // lib/private/Template/TemplateManager.php:82 -- the latter covers public + // (unauthenticated) pages, which is the case that matters here since the + // LibreSign signing page is a #[PublicPage]. + $context->registerEventListener(BeforeTemplateRenderedEvent::class, DebyltechStyleListener::class); + } + + public function boot(IBootContext $context): void { + } +} diff --git a/ansible/roles/podman/files/debyltechmail/lib/Listener/DebyltechMailListener.php b/ansible/roles/podman/files/debyltechmail/lib/Listener/DebyltechMailListener.php new file mode 100644 index 0000000..5542e13 --- /dev/null +++ b/ansible/roles/podman/files/debyltechmail/lib/Listener/DebyltechMailListener.php @@ -0,0 +1,121 @@ +. The fix is a cid: reference backed by an inline MIME part, and + * that part must be attached to the MESSAGE. An IEMailTemplate subclass has no + * reference to the message, so it physically cannot do this; the template emits + * the , this listener supplies the bytes and rewrites the src. + * + * BeforeMessageSent is the sanctioned hook -- "Emitted before a system mail is + * sent. It can be used to alter the message." (lib/public/Mail/Events/ + * BeforeMessageSent.php). It fires at lib/private/Mail/Mailer.php:186, after + * useTemplate() has already rendered subject/plain/html onto the message and + * before setRecipients() and the transport, so a body rewrite here takes + * effect. No core patch, no LibreSign fork. + * + * FAILURE POSTURE: every step is defensive. If the asset is missing, the body + * is not ours, or anything throws, the listener leaves the message untouched + * and mail still goes out with a remote -- degraded, never blocked. Mail + * that carries signature requests must not fail to send because branding + * broke. + */ + +namespace OCA\Debyltechmail\Listener; + +use OC\Mail\Message; +use OCP\EventDispatcher\Event; +use OCP\EventDispatcher\IEventListener; +use OCP\Mail\Events\BeforeMessageSent; +use Psr\Log\LoggerInterface; + +/** @template-implements IEventListener */ +class DebyltechMailListener implements IEventListener { + /** Must match DebyltechEMailTemplate::LOGO_PATH. */ + private const LOGO_PATH_FRAGMENT = '/custom_apps/debyltechmail/img/debyltech-wordmark.png'; + + /** Content-ID. Symfony emits this as . */ + private const CID = 'debyltech-wordmark.png'; + + public function __construct( + private LoggerInterface $logger, + ) { + } + + public function handle(Event $event): void { + if (!$event instanceof BeforeMessageSent) { + return; + } + + try { + $this->embedWordmark($event->getMessage()); + } catch (\Throwable $e) { + // Never let branding break delivery of a signature request. + $this->logger->warning('debyltechmail: inline logo embed skipped', [ + 'exception' => $e, + ]); + } + } + + private function embedWordmark(\OCP\Mail\IMessage $message): void { + // Mailer::send() guards `instanceof Message` before dispatching this + // event, so the concrete type is guaranteed -- but getSymfonyEmail() + // is not on the interface, so narrow explicitly rather than assume. + if (!$message instanceof Message) { + return; + } + + $email = $message->getSymfonyEmail(); + $html = $email->getHtmlBody(); + if (!is_string($html) || $html === '') { + return; + } + + // Only touch mail that actually renders our wordmark. Anything else -- + // password resets, share notifications, other apps -- passes through. + if (!str_contains($html, self::LOGO_PATH_FRAGMENT)) { + return; + } + + $asset = $this->assetPath(); + if ($asset === null) { + return; + } + + $bytes = @file_get_contents($asset); + if ($bytes === false || $bytes === '') { + return; + } + + // Rewrite the absolute URL to a cid: reference. Matched on the path + // fragment with an optional query string so a cachebuster or a change + // of host still resolves. + $rewritten = preg_replace( + '#https?://[^"\']*' . preg_quote(self::LOGO_PATH_FRAGMENT, '#') . '(\?[^"\']*)?#', + 'cid:' . self::CID, + $html, + ); + + if (!is_string($rewritten) || $rewritten === $html) { + return; + } + + $email->embed($bytes, self::CID, 'image/png'); + $message->setHtmlBody($rewritten); + } + + /** + * Resolves img/debyltech-wordmark.png relative to this file, so the app works + * from whatever apps directory Nextcloud has it in. + */ + private function assetPath(): ?string { + $path = dirname(__DIR__, 2) . '/img/debyltech-wordmark.png'; + return is_readable($path) ? $path : null; + } +} diff --git a/ansible/roles/podman/files/debyltechmail/lib/Listener/DebyltechStyleListener.php b/ansible/roles/podman/files/debyltechmail/lib/Listener/DebyltechStyleListener.php new file mode 100644 index 0000000..c385c40 --- /dev/null +++ b/ansible/roles/podman/files/debyltechmail/lib/Listener/DebyltechStyleListener.php @@ -0,0 +1,51 @@ + */ +class DebyltechStyleListener implements IEventListener { + public function handle(Event $event): void { + if (!$event instanceof BeforeTemplateRenderedEvent) { + return; + } + + // Never let a styling concern break page rendering. A signing page that + // loads unstyled is recoverable; one that 500s is not. + try { + Util::addStyle(Application::APP_ID, 'libresign-mobile'); + } catch (\Throwable $e) { + // Intentionally swallowed -- no logger dependency is worth adding + // for a stylesheet, and a failure here has no user-visible effect + // beyond the override not applying. + } + } +} diff --git a/ansible/roles/podman/files/debyltechmail/lib/Mail/DebyltechEMailTemplate.php b/ansible/roles/podman/files/debyltechmail/lib/Mail/DebyltechEMailTemplate.php new file mode 100644 index 0000000..97b3f13 --- /dev/null +++ b/ansible/roles/podman/files/debyltechmail/lib/Mail/DebyltechEMailTemplate.php @@ -0,0 +1,421 @@ +/img/ is served publicly without auth (verified). + * + * Note that remote images are blocked by default in Apple Mail, Gmail and + * Outlook, and Apple Mail renders its own placeholder box rather than styled + * alt text -- so alt styling cannot rescue it. Surviving that requires a CID + * inline part via IMessage::attachInline(), which lives on the MESSAGE and is + * unreachable from a template subclass. Mitigated instead by dropping the + * band: a blocked logo now leaves plain white space, not a black slab. + * + * IMPLEMENTATION NOTE: font restyling is done by string-substitution against + * the PARENT's own markup rather than by redefining it. Those properties are + * large inline-CSS blobs with positional sprintf placeholders; copying them + * wholesale would mean re-auditing every placeholder on every upgrade, and a + * mismatch renders broken mail. Substitution degrades safely -- if upstream + * changes markup the replacements no-op and mail still sends, just unstyled. + * The header IS replaced wholesale, deliberately, because "no band" cannot be + * expressed as a substitution; its placeholder order is documented at its + * definition and must be kept in sync with upstream. + */ + +namespace OCA\Debyltechmail\Mail; + +use OC\Mail\EMailTemplate; + +class DebyltechEMailTemplate extends EMailTemplate { + /** Stock Nextcloud font stack, replaced wholesale. Must match exactly. */ + private const STOCK_FONTS = "-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif"; + + /** $primary-font, with the stock stack retained as fallback. */ + private const BRAND_FONTS = "'Open Sans',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif"; + + private const ACCENT = '#BC804D'; // $primary-color (copper) + private const ON_ACCENT = '#FFFFFF'; + private const INK = '#0A0A0A'; + private const MUTED = '#525252'; + private const FAINT = '#A3A3A3'; + private const RULE = '#E5E5E5'; + + private const ENTITY = 'de Byl Technologies LLC'; + private const SITE = 'https://debyltech.com'; + private const LOGO_PATH = '/custom_apps/debyltechmail/img/debyltech-wordmark.png'; + + /** Displayed width in px. The asset is 600px wide for retina. */ + private const LOGO_DISPLAY_WIDTH = 190; + + /** + * LibreSign's l10n wraps document names in German guillemets -- "Sign + * »contract«" -- regardless of locale. Mapped to US curly quotes, matching + * the ``...'' convention in the LaTeX document templates. + */ + private const QUOTE_MAP = ['»' => "\u{201C}", '«' => "\u{201D}"]; + + /** + * LibreSign subject -> de Byl Tech subject. Keys are the exact English msgids + * from custom_apps/libresign/lib/Service/MailService.php (lines 51, 87, + * 121, 150, 172). Anything unmatched passes through untouched, so an + * upstream string change degrades to the original subject rather than a + * blank one. + */ + private const SUBJECT_MAP = [ + 'LibreSign: There is a file for you to sign' => 'Document for your signature', + 'LibreSign: Changes into a file for you to sign' => 'Updated document for your signature', + 'LibreSign: A file has been signed' => 'A document has been signed', + 'LibreSign: A signature request has been canceled' => 'Signature request cancelled', + 'LibreSign: Code to sign file' => 'Your signing verification code', + ]; + + /** + * LibreSign heading -> de Byl Tech heading. Exact English msgids from + * MailService.php lines 53/89, 123, 152. + */ + private const HEADING_MAP = [ + 'File to sign' => 'Review and sign', + 'File signed' => 'Document signed', + 'Signature request canceled' => 'Signature request cancelled', + ]; + + /** + * LibreSign body copy -> de Byl Tech body copy (MailService.php lines 60, 96, + * 174). Only the strings with NO %s interpolation are mapped; the two that + * carry a name or filename (lines 125, 154) arrive already substituted and + * so cannot be matched exactly -- they pass through unchanged. + */ + private const BODY_MAP = [ + 'There is a document for you to sign. Access the link below:' + => 'de Byl Technologies LLC has sent you a document that requires your signature. Review it and sign using the link below.', + 'Changes have been made in a file that you have to sign. Access the link below:' + => 'A document awaiting your signature has been updated by de Byl Technologies LLC. Review the current version and sign using the link below.', + 'Use this code to sign the document:' + => 'Use this verification code to complete your signature:', + ]; + + /** + * Template properties carrying the font stack. Listed explicitly rather + * than discovered reflectively so an upstream rename fails loudly in + * testing instead of silently skipping a block. + */ + private const STYLED_PARTS = [ + 'head', 'tail', 'heading', 'bodyBegin', 'bodyText', + 'listBegin', 'listItem', 'listEnd', 'buttonGroup', 'button', + 'bodyEnd', 'footer', + ]; + + /** + * Own flag, deliberately NOT the parent's $footerAdded. + * + * Message::useTemplate() (lib/private/Mail/Message.php:289-296) calls + * renderText() at :291 BEFORE renderHtml() at :293, and renderText() sets + * $footerAdded = true. Guarding footer injection on !$footerAdded therefore + * never fires on the real send path -- the footer silently vanished from + * every mail while a renderHtml()-only test passed. Both renderers below + * call inject() and this flag makes the second call inert. + */ + private bool $brandFooterInjected = false; + + public function __construct( + \OCP\Defaults $themingDefaults, + \OCP\IURLGenerator $urlGenerator, + \OCP\L10N\IFactory $l10nFactory, + ?int $logoWidth, + ?int $logoHeight, + string $emailId, + array $data, + ) { + $this->applyBrandStyling(); + + // Must run AFTER the substitutions: the parent constructor copies + // $this->head into $htmlBody as its first act, so restyling head + // afterwards would leave the already-emitted copy untouched. + parent::__construct( + $themingDefaults, + $urlGenerator, + $l10nFactory, + $logoWidth, + $logoHeight, + $emailId, + $data, + ); + } + + private function applyBrandStyling(): void { + foreach (self::STYLED_PARTS as $part) { + if (!property_exists($this, $part)) { + continue; + } + $this->$part = str_replace(self::STOCK_FONTS, self::BRAND_FONTS, $this->$part); + } + + // Light, tightly tracked headings, carried over from the Skudak template. + $this->heading = str_replace( + 'font-size:24px;font-weight:400', + 'font-size:26px;font-weight:300;letter-spacing:-0.02em', + $this->heading, + ); + } + + /** + * Rewrites LibreSign's subjects. Called by LibreSign on the TEMPLATE + * (MailService.php:51 etc.), not on the message, which is what makes this + * interceptable at all -- Message::useTemplate() later pulls the result via + * renderSubject(). Prefixed with the entity so the sender is unambiguous in + * an inbox list. + */ + public function setSubject(string $subject): void { + $mapped = self::SUBJECT_MAP[$subject] ?? null; + parent::setSubject( + $mapped === null ? $subject : self::ENTITY . ' — ' . $mapped, + ); + } + + /** + * Replaces the stock header wholesale: no coloured band, wordmark centred + * on white. + * + * Does NOT use the parent's $header property or its placeholder order -- + * this is independent markup, so upstream changes to $header cannot break + * it (and equally cannot improve it). $logoWidth/$logoHeight from the + * Mailer are ignored on purpose: they are clamped to MAX_LOGO_SIZE = 105 + * (lib/private/Mail/Mailer.php:60), which is too small for a wordmark to + * be legible. + */ + public function addHeader(): void { + if ($this->headerAdded) { + return; + } + $this->headerAdded = true; + + $logoUrl = $this->urlGenerator->getAbsoluteURL(self::LOGO_PATH); + $alt = htmlspecialchars(self::ENTITY, ENT_QUOTES, 'UTF-8'); + $w = self::LOGO_DISPLAY_WIDTH; + $fonts = self::BRAND_FONTS; + $ink = self::INK; + + $this->htmlBody .= << + + + {$alt} + + + +HTML; + } + + /** + * Both renderers inject the footer -- see $brandFooterInjected. + * + * Mirrors the parent's own guard structure (renderHtml at + * lib/private/Mail/EMailTemplate.php:643, renderText at :656): close the + * body, append $tail, flip $footerAdded. The brand block goes in before + * $tail. + */ + public function renderHtml(): string { + $this->injectBrandFooter(); + return parent::renderHtml(); + } + + public function renderText(): string { + $this->injectBrandFooter(); + return parent::renderText(); + } + + private function injectBrandFooter(): void { + if ($this->brandFooterInjected || $this->footerAdded) { + return; + } + $this->brandFooterInjected = true; + + // Close the body ourselves so the footer lands INSIDE the layout + // rather than after it. The parent's render methods are then a no-op + // for body closing and only append $tail. + $this->ensureBodyIsClosed(); + $this->htmlBody .= $this->brandFooterHtml(); + $this->plainBody .= $this->brandFooterText(); + } + + private function brandFooterHtml(): string { + $year = date('Y'); + $entity = htmlspecialchars(self::ENTITY, ENT_QUOTES, 'UTF-8'); + $fonts = self::BRAND_FONTS; + $site = self::SITE; + [$muted, $faint, $rule, $ink] = [self::MUTED, self::FAINT, self::RULE, self::INK]; + + // Table-based and fully inline-styled: