Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
Binary file added img/ionoscloud/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
100 changes: 100 additions & 0 deletions lib/BrandResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<?php

declare(strict_types=1);

namespace OCA\NcwMailtemplate;

use OCP\IConfig;

/**
* Resolves brand-specific template and image paths with fallback to the default brand.
*
* Reads the `ncw.brand` system config value to determine the active brand.
* For each template file or image, it first checks if a brand-specific version exists.
* If not, it falls back to the default brand ('IONOS').
*/
class BrandResolver {
public const DEFAULT_BRAND = 'IONOS';

private string $brand;
private string $templatesBasePath;
private string $imgBasePath;

public function __construct(IConfig $config) {
$brand = $config->getSystemValueString('ncw.brand', self::DEFAULT_BRAND);

// Sanitize: only allow alphanumeric, dash, and underscore to prevent path traversal
if (!preg_match('/^[a-zA-Z0-9_-]+$/', $brand)) {
$brand = self::DEFAULT_BRAND;
}

// Lowercase to match folder names on disk regardless of config casing
$brand = strtolower($brand);

$this->brand = $brand;
$this->templatesBasePath = __DIR__ . '/templates/email';
$this->imgBasePath = __DIR__ . '/../img';
}

/**
* Get the active brand identifier.
*/
public function getBrand(): string {
return $this->brand;
}

/**
* Resolve a template file path with brand fallback.
*
* If the brand-specific file exists, return its path.
* Otherwise, fall back to the default brand's version.
*
* @param string $fileName The template file name (e.g. 'header.html')
* @return string The resolved absolute file path
*/
public function resolveTemplatePath(string $fileName): string {
$defaultFolder = self::defaultBrandFolder();

if ($this->brand !== $defaultFolder) {
$brandPath = $this->templatesBasePath . '/' . $this->brand . '/' . $fileName;
if (file_exists($brandPath)) {
return $brandPath;
}
}

return $this->templatesBasePath . '/' . $defaultFolder . '/' . $fileName;
}

/**
* Resolve an image name with brand fallback.
*
* Returns the app-relative image path (e.g. 'ionos/logo.png') for use
* with IURLGenerator::imagePath().
*
* If the brand-specific image exists on disk, return '<brand>/<imageName>'.
* Otherwise, fall back to '<defaultBrandFolder>/<imageName>'.
*
* @param string $imageName The image file name (e.g. 'logo.png')
* @return string The resolved app-relative image path
*/
public function resolveImageName(string $imageName): string {
$defaultFolder = self::defaultBrandFolder();

if ($this->brand !== $defaultFolder) {
$brandImagePath = $this->imgBasePath . '/' . $this->brand . '/' . $imageName;
if (file_exists($brandImagePath)) {
return $this->brand . '/' . $imageName;
}
}

return $defaultFolder . '/' . $imageName;
}

/**
* Returns the lowercase folder name for the default brand.
* Brand folders on disk use lowercase names.
*/
private static function defaultBrandFolder(): string {
return strtolower(self::DEFAULT_BRAND);
}
}
38 changes: 26 additions & 12 deletions lib/EMailTemplate.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
class EMailTemplate extends ParentTemplate {
private IL10N $l;
private ?IUser $user = null;
private BrandResolver $brandResolver;

// Generated asset URLs (filled in constructor)
private string $spacerUrl = '';
Expand Down Expand Up @@ -58,28 +59,31 @@ public function __construct(
?int $logoHeight,
string $emailId,
array $data = [],
) {
) {
// Initialize parent first to set up basic properties
parent::__construct($defaults, $urlGenerator, $l10nFactory, $logoWidth, $logoHeight, $emailId, $data);


// Initialize the brand resolver from server config
$config = \OC::$server->get(IConfig::class);
$this->brandResolver = new BrandResolver($config);

// Try to get user from various sources (for recipient's language)
$this->user = $this->determineUser($this->data);

// Get language: user's preference, or system default
if ($this->user) {
$lang = $this->l10nFactory->getUserLanguage($this->user);
} else {
$config = \OC::$server->get(IConfig::class);
$lang = $config->getSystemValue('default_language', 'en');
}
$this->l = $this->l10nFactory->get(Application::APP_ID, $lang);

// Generate URLs for template assets
// Generate URLs for template assets (brand-aware)
$this->generateTemplateAssetUrls($urlGenerator);

// Load all HTML template files - this will override parent's head and tail
$this->loadHtmlTemplateFiles();

// Replace the parent's htmlBody that was set with the parent's head
// with our custom head
$this->htmlBody = $this->head;
Expand Down Expand Up @@ -154,24 +158,34 @@ private function determineUser(array $data): ?IUser {
/**
* Generate URLs for template assets (images, etc.)
*
* Uses BrandResolver to pick brand-specific images with fallback to the
* default brand.
*
* @param IURLGenerator $urlGenerator
*/
private function generateTemplateAssetUrls(IURLGenerator $urlGenerator): void {
// store on the instance so we can inject into templates
$this->spacerUrl = $urlGenerator->getAbsoluteURL($urlGenerator->imagePath(Application::APP_ID, 'spacer.png'));
$this->logoUrl = $urlGenerator->getAbsoluteURL($urlGenerator->imagePath(Application::APP_ID, 'ionos_logo_de.png'));
$this->emailIconUrl = $urlGenerator->getAbsoluteURL($urlGenerator->imagePath(Application::APP_ID, 'email.png'));
$this->listItemIconUrl = $urlGenerator->getAbsoluteURL($urlGenerator->imagePath(Application::APP_ID, 'list-item-icon.png'));
$spacerImage = $this->brandResolver->resolveImageName('spacer.png');
$logoImage = $this->brandResolver->resolveImageName('logo.png');
$emailIconImage = $this->brandResolver->resolveImageName('email.png');
$listItemIconImage = $this->brandResolver->resolveImageName('list-item-icon.png');

$this->spacerUrl = $urlGenerator->getAbsoluteURL($urlGenerator->imagePath(Application::APP_ID, $spacerImage));
$this->logoUrl = $urlGenerator->getAbsoluteURL($urlGenerator->imagePath(Application::APP_ID, $logoImage));
$this->emailIconUrl = $urlGenerator->getAbsoluteURL($urlGenerator->imagePath(Application::APP_ID, $emailIconImage));
$this->listItemIconUrl = $urlGenerator->getAbsoluteURL($urlGenerator->imagePath(Application::APP_ID, $listItemIconImage));
}

/**
* Load HTML template files for email components
*
* Uses BrandResolver to pick brand-specific template files with fallback
* to the default brand.
*
* @return void
*/
private function loadHtmlTemplateFiles(): void {
foreach (self::HTML_TEMPLATE_FILES as $property => $file) {
$templatePath = __DIR__ . '/templates/email/' . $file;
$templatePath = $this->brandResolver->resolveTemplatePath($file);
if (!file_exists($templatePath)) {
continue;
}
Expand Down
16 changes: 1 addition & 15 deletions lib/templates/email/head.html
Original file line number Diff line number Diff line change
@@ -1,15 +1 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en" style="-webkit-font-smoothing:antialiased;background:#fff!important">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width">
<title></title>
<style type="text/css">@media only screen{html{min-height:100%;background:#fff}}@media only screen and (max-width:610px){table.body img{width:auto;height:auto}table.body center{min-width:0!important}table.body .container{width:95%!important}table.body .columns{height:auto!important;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;padding-left:30px!important;padding-right:30px!important}th.small-12{display:inline-block!important;width:100%!important}table.menu{width:100%!important}table.menu td,table.menu th{width:auto!important;display:inline-block!important}table.menu.vertical td,table.menu.vertical th{display:block!important}table.menu[align=center]{width:auto!important}}</style>
</head>
<body style="-moz-box-sizing:border-box;-ms-text-size-adjust:100%;-webkit-box-sizing:border-box;-webkit-font-smoothing:antialiased;-webkit-text-size-adjust:100%;margin:0;background:#fff!important;box-sizing:border-box;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0;min-width:100%;padding:0;text-align:left;width:100%!important">
<span class="preheader" style="color:#F5F5F5;display:none!important;font-size:1px;line-height:1px;max-height:0;max-width:0;mso-hide:all!important;opacity:0;overflow:hidden;visibility:hidden">
</span>
<table class="body" style="-webkit-font-smoothing:antialiased;margin:0;background:#fff;border-collapse:collapse;border-spacing:0;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0;padding:0;text-align:left;vertical-align:top;width:100%">
<tr style="padding:0;text-align:left;vertical-align:top">
<td class="center" align="center" valign="top" style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:1.3;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word">
<center data-parsed="" style="width:100%;max-width:740px;margin:auto">
<!-- Legacy path retained for backward compatibility. Brand-specific templates are loaded from brand subdirectories (e.g. ionos/head.html). -->
File renamed without changes.
File renamed without changes.
15 changes: 15 additions & 0 deletions lib/templates/email/ionos/head.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en" style="-webkit-font-smoothing:antialiased;background:#fff!important">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width">
<title></title>
<style type="text/css">@media only screen{html{min-height:100%;background:#fff}}@media only screen and (max-width:610px){table.body img{width:auto;height:auto}table.body center{min-width:0!important}table.body .container{width:95%!important}table.body .columns{height:auto!important;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;padding-left:30px!important;padding-right:30px!important}th.small-12{display:inline-block!important;width:100%!important}table.menu{width:100%!important}table.menu td,table.menu th{width:auto!important;display:inline-block!important}table.menu.vertical td,table.menu.vertical th{display:block!important}table.menu[align=center]{width:auto!important}}</style>
</head>
<body style="-moz-box-sizing:border-box;-ms-text-size-adjust:100%;-webkit-box-sizing:border-box;-webkit-font-smoothing:antialiased;-webkit-text-size-adjust:100%;margin:0;background:#fff!important;box-sizing:border-box;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0;min-width:100%;padding:0;text-align:left;width:100%!important">
<span class="preheader" style="color:#F5F5F5;display:none!important;font-size:1px;line-height:1px;max-height:0;max-width:0;mso-hide:all!important;opacity:0;overflow:hidden;visibility:hidden">
</span>
<table class="body" style="-webkit-font-smoothing:antialiased;margin:0;background:#fff;border-collapse:collapse;border-spacing:0;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0;padding:0;text-align:left;vertical-align:top;width:100%">
<tr style="padding:0;text-align:left;vertical-align:top">
<td class="center" align="center" valign="top" style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:1.3;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word">
<center data-parsed="" style="width:100%;max-width:740px;margin:auto">
File renamed without changes.
File renamed without changes.
28 changes: 28 additions & 0 deletions lib/templates/email/ionoscloud/bodyEnd.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<!-- Start bodyEnd -->
<p class="footer-greeting" style="padding:0 0 0 0;margin:0;text-align:left;line-height:20px" >
<font class="footer-font" style="font-family:\'Open Sans\', \'Google Sans\', Arial, sans-serif;font-size:15px;font-weight:normal;color:#001B41;line-height:20px" ><?= $l->t('Best regards')?><br/>IONOS Cloud<br/></font>
</p>
</td>
<td width="20" style="min-width:20px;width:20px;line-height:1px;font-size:0px" >
<img src="<?= $spacerUrl ?>" width="20" height="1" style="display:block;width:20px;height:1px;border:0" alt=" " border="0" />
</td>
</tr>
</table>
<table cellpadding="0" cellspacing="0" border="0" width="100%%" >
<tr>
<td class="w-100 h-25" style="width:100%%;height:25px;line-height:25px" >
<img src="<?= $spacerUrl ?>" width="1" height="25" class="w-1 h-25" style="display:block;width:1px;height:25px;border:0" alt=" " border="0" />
</td>
</tr>
</table>
</td>
</tr>
</table>
<table cellpadding="0" cellspacing="0" border="0" name="vspace-bottom-section" width="100%%" >
<tr>
<td class="w-100 h-12" style="width:100%%;height:12px;line-height:12px" >
<img src="<?= $spacerUrl ?>" width="1" height="12" class="w-1 h-12" style="display:block;width:1px;height:12px;border:0" alt=" " border="0" />
</td>
</tr>
</table>
<!-- End bodyEnd -->
58 changes: 58 additions & 0 deletions lib/templates/email/ionoscloud/footer.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<!-- Start footer -->
<table cellpadding="0" cellspacing="0" border="0" width="100%%" bgcolor="#F2F5F8" style="width:100%%;border-radius:8px 8px 8px 8px;border-spacing:0;background-color:#F2F5F8" >
<tr valign="top" >
<td align="left" >
<table cellpadding="0" cellspacing="0" border="0" style="border-spacing:0" >
<tr valign="top" >
<td width="580" style="min-width:580px;width:580px;line-height:1px;font-size:0px" >
<img src="<?= $spacerUrl ?>" width="580" height="1" style="display:block;width:580px;height:1px;border:0" alt=" " border="0"/>
</td>
</tr>
</table>
<table cellpadding="0" cellspacing="0" border="0" width="100%%" >
<tr>
<td width="1" height="25" style="width:100%%;height:25px;line-height:25px" >
<img src="<?= $spacerUrl ?>" width="1" height="25" style="display:block;width:1px;height:25px;border:0" alt=" " border="0" />
</td>
</tr>
</table>
<table cellpadding="0" cellspacing="0" border="0" width="100%%" style="width:100%%;border-spacing:0" >
<tr valign="top" >
<td width="20" style="min-width:20px;width:20px;line-height:1px;font-size:0px" >
<img src="<?= $spacerUrl ?>" width="20" height="1" style="display:block;width:20px;height:1px;border:0" alt=" " border="0" />
</td>
<td align="left" >
<p class="footer-address" style="padding:0 0 0 0;margin:0;text-align:left;line-height:18px" >
<font class="footer-address-font" style="font-family:\'Open Sans\', \'Google Sans\', Arial, sans-serif;font-size:15px;font-weight:normal;color:#001B41;line-height:18px" >IONOS Cloud
<br/>Elgendorfer Straße 57
<br/>56410 Montabaur
<br/><?= $l->t('Germany')?>
</font>
</p>
<p>
<a href="https://ionos.eu" class="footer-link" style="font-family:\'Open Sans\', \'Google Sans\', Arial, sans-serif;font-size:16px;font-weight:normal;color:#001B41;line-height:18px" target="_blank"><?= $l->t('Further information')?></a>
</p>
<table cellpadding="0" cellspacing="0" border="0" width="100%%" >
<tr>
<td width="1" height="15" style="width:100%%;height:15px;line-height:15px" >
<img src="<?= $spacerUrl ?>" width="1" height="15" style="display:block;width:1px;height:15px;border:0" alt=" " border="0" />
</td>
</tr>
</table>
</td>
<td width="20" style="min-width:20px;width:20px;line-height:1px;font-size:0px" >
<img src="<?= $spacerUrl ?>" width="20" height="1" style="display:block;width:20px;height:1px;border:0" alt=" " border="0" />
</td>
</tr>
</table>
<table cellpadding="0" cellspacing="0" border="0" width="100%%" >
<tr>
<td width="1" height="25" style="width:100%%;height:25px;line-height:25px" >
<img src="<?= $spacerUrl ?>" width="1" height="25" style="display:block;width:1px;height:25px;border:0" alt=" " border="0" />
</td>
</tr>
</table>
</td>
</tr>
</table>
<!-- End footer -->
Loading
Loading