diff --git a/components/ILIAS/UI/resources/ui-examples/css/mail_examples.css b/components/ILIAS/UI/resources/ui-examples/css/mail_examples.css
new file mode 100644
index 000000000000..e0d087ed06d2
--- /dev/null
+++ b/components/ILIAS/UI/resources/ui-examples/css/mail_examples.css
@@ -0,0 +1,122 @@
+.table {
+ background-color: #f0f0f0;
+ width: 100%;
+}
+
+.header {
+ background-color: white;
+ border-bottom: 2px solid #f0f0f0;
+}
+
+.footer {
+ background-color: white;
+}
+
+.image-data {
+ width: 90px;
+}
+
+td.spacing {
+ padding: 20px 0;
+}
+
+.logo {
+ width: 45px;
+ height: 45px;
+ outline-style: none;
+ text-decoration: none;
+ border: none;
+ margin: 0;
+}
+
+.installation-text {
+ vertical-align: middle;
+}
+.installation-text span {
+ border-collapse: collapse;
+ color: #161616;
+ font-family: "Open Sans", Arial, Helvetica, sans-serif;
+ font-size: 16px;
+ line-height: 22px;
+}
+
+.content .spacing {
+ padding: 80px 0;
+}
+
+.content td td {
+ background-color: white;
+}
+
+.text-color {
+ color: #4c6586;
+}
+
+.footer .font-definition {
+ color: #161616;
+}
+
+.font-definition {
+ font-family: "Open Sans", Arial, Helvetica, sans-serif;
+ font-size: 16px;
+ line-height: 22px;
+ padding: 30px;
+}
+
+body {
+ height: 100% !important;
+ margin: 0;
+ padding: 0;
+ width: 100% !important;
+ mso-margin-top-alt: 0px;
+ mso-margin-bottom-alt: 0px;
+ mso-padding-alt: 0px 0px 0px 0px;
+ overflow-y: scroll;
+ -webkit-text-size-adjust: none;
+ -ms-text-size-adjust: none;
+}
+
+.center {
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.w-750 {
+ width: 750px;
+}
+
+.link-color {
+ color: #4c6586;
+}
+
+table {
+ mso-table-lspace: 0pt;
+ mso-table-rspace: 0pt;
+}
+
+table, table th, table td {
+ border-collapse: collapse;
+}
+
+a, img, a img {
+ border: 0;
+ outline: none;
+ text-decoration: none;
+}
+
+img {
+ -ms-interpolation-mode: bicubic;
+}
+
+body, table, td, th, p, a, li {
+ -ms-text-size-adjust: 100%;
+ -webkit-text-size-adjust: 100%;
+}
+
+@media only screen and (max-width: 760px) {
+ .w-750 {
+ width: 100%;
+ }
+}
+
+/*# sourceMappingURL=mail.css.map */
diff --git a/components/ILIAS/UI/src/Component/Layout/Page/Factory.php b/components/ILIAS/UI/src/Component/Layout/Page/Factory.php
index fa73af27d94d..a02b442416e3 100755
--- a/components/ILIAS/UI/src/Component/Layout/Page/Factory.php
+++ b/components/ILIAS/UI/src/Component/Layout/Page/Factory.php
@@ -20,6 +20,8 @@
namespace ILIAS\UI\Component\Layout\Page;
+use ILIAS\UI\Component\Legacy\Content;
+use ILIAS\Data\Link;
use ILIAS\UI\Component\MainControls\Mainbar;
use ILIAS\UI\Component\MainControls\MetaBar;
use ILIAS\UI\Component\Breadcrumbs\Breadcrumbs;
@@ -109,4 +111,43 @@ public function standard(
string $short_title = '',
string $view_title = ''
): Standard;
+
+ /**
+ * ---
+ * description:
+ * purpose: The Mail Page is used to render HTML content for mails.
+ * composition: >
+ * The Mail Page consists of HTML content, a header and a footer.
+ * The header contains the logo and the installation-text.
+ * The footer contains the installation-text and a link to ILIAS.
+ * The stylesheet path refers to the CSS file that is used to style the page and will be included in the HTML.
+ *
+ * rules:
+ * usage:
+ * 1: The Mail Page MUST be rendered with content.
+ * 2: >
+ * The Mail Page MUST be rendered with a logo.
+ * The logo must be either
+ * a cid URL (see https://datatracker.ietf.org/doc/html/rfc2392)
+ * or a data URL (see https://developer.mozilla.org/en-US/docs/Web/URI/Reference/Schemes/data).
+ * 3: The Mail Page MUST be rendered with a footer.
+ * 4: The Mail Page MUST be rendered with a stylesheet path.
+ * 5: The Header of the Mail Page MUST contain the logo and the installation-text.
+ * 6: The Footer of the Mail Page MUST contain the installation-text and a link to ILIAS.
+ * 7: The Mail Page's stylesheet path MUST include all necessary styles to render the page correctly.
+ * ----
+ * @param string $stylesheet_path
+ * @param string $logo_url
+ * @param string $installation_title
+ * @param \ILIAS\UI\Component\Legacy\Content $html_content
+ * @param Link $footer_url
+ * @return \ILIAS\UI\Component\Layout\Page\Mail
+ */
+ public function mail(
+ string $stylesheet_path,
+ string $logo_url,
+ string $installation_title,
+ Content $html_content,
+ Link $footer_url,
+ ): Mail;
}
diff --git a/components/ILIAS/UI/src/Component/Layout/Page/Mail.php b/components/ILIAS/UI/src/Component/Layout/Page/Mail.php
new file mode 100644
index 000000000000..bf7732909389
--- /dev/null
+++ b/components/ILIAS/UI/src/Component/Layout/Page/Mail.php
@@ -0,0 +1,28 @@
+getStyleSheetPath())) {
+ throw new InvalidArgumentException("Could not read stylesheet at {$this->getStyleSheetPath()}.");
+ }
+
+ if (!str_starts_with($this->getLogoURL(), 'cid:') && !str_starts_with($this->getLogoURL(), 'data:')) {
+ throw new InvalidArgumentException('The logo URL must be a cid or data URL.');
+ }
+ }
+
+ public function getContent(): array
+ {
+ return [
+ $this->html_content,
+ ];
+ }
+
+ public function getLogoURL(): string
+ {
+ return $this->logo_url;
+ }
+
+ public function getInstallationTitle(): string
+ {
+ return $this->installation_title;
+ }
+
+ public function getStyleSheetPath(): string
+ {
+ return $this->stylesheet_path;
+ }
+
+ public function getFooterURL(): Link
+ {
+ return $this->footer_url;
+ }
+}
diff --git a/components/ILIAS/UI/src/Implementation/Component/Layout/Page/Renderer.php b/components/ILIAS/UI/src/Implementation/Component/Layout/Page/Renderer.php
index 37e796a301a7..4c725d590783 100755
--- a/components/ILIAS/UI/src/Implementation/Component/Layout/Page/Renderer.php
+++ b/components/ILIAS/UI/src/Implementation/Component/Layout/Page/Renderer.php
@@ -27,7 +27,6 @@
use ILIAS\UI\Implementation\Render\Template;
use ILIAS\UI\Implementation\Render\ResourceRegistry;
use iljQueryUtil;
-use LogicException;
class Renderer extends AbstractComponentRenderer
{
@@ -40,6 +39,8 @@ public function render(Component\Component $component, RendererInterface $defaul
{
if ($component instanceof Component\Layout\Page\Standard) {
return $this->renderStandardPage($component, $default_renderer);
+ } elseif ($component instanceof Mail) {
+ return $this->renderMailPage($component, $default_renderer);
}
$this->cannotHandleComponent($component);
@@ -115,6 +116,22 @@ protected function renderStandardPage(
return $tpl->get();
}
+ protected function renderMailPage(
+ Mail $component,
+ RendererInterface $default_renderer
+ ): string {
+
+ $tpl = $this->getTemplate('tpl.mailpage.html', true, true);
+
+ $tpl->setVariable('LOGO_SRC', $component->getLogoURL());
+ $tpl->setVariable('INSTALLATION_TITLE', $component->getInstallationTitle());
+ $tpl->setVariable('CONTENT', $default_renderer->render($component->getContent()));
+ $tpl->setVariable('FOOTER_URL', $default_renderer->render($this->getUIFactory()->link()->standard($component->getFooterURL()->getLabel(), $component->getFooterURL()->getURL()->getBaseURI())));
+ $tpl->setVariable('CSS_CONTENT', file_get_contents($component->getStyleSheetPath()) ?: '');
+
+ return $tpl->get();
+ }
+
protected function convertBreadcrumbsToDropdownLocator(
Component\Breadcrumbs\Breadcrumbs $breadcrumbs
): Component\Dropdown\Dropdown {
diff --git a/components/ILIAS/UI/src/examples/Layout/Page/Mail/base.php b/components/ILIAS/UI/src/examples/Layout/Page/Mail/base.php
new file mode 100644
index 000000000000..93c178a78d1b
--- /dev/null
+++ b/components/ILIAS/UI/src/examples/Layout/Page/Mail/base.php
@@ -0,0 +1,82 @@
+
+ * Example for rendering a Mail Page.
+ *
+ * expected output: >
+ * ILIAS shows a mail page with a header which contains a logo and an installation-text.
+ * The content of the page is a headline and a paragraph.
+ * The footer contains an installation-text and a link to the ILIAS website.
+ * ---
+ */
+function base(): string
+{
+ global $DIC;
+ $factory = $DIC->ui()->factory();
+ $renderer = $DIC->ui()->renderer();
+
+ $icon = $factory->symbol()->icon()->standard('root', '')->withSize('large');
+ $target = new URI(
+ $DIC->http()->request()->getUri()->__toString() . '&new_ui=1'
+ );
+
+ return $renderer->render(
+ $factory->link()->bulky($icon, 'See UI in fullscreen-mode', $target),
+ );
+}
+
+global $DIC;
+$request_wrapper = $DIC->http()->wrapper()->query();
+$refinery = $DIC->refinery();
+
+if ($request_wrapper->has('new_ui')
+ && $request_wrapper->retrieve('new_ui', $refinery->kindlyTo()->int()) === 1
+) {
+ ilInitialisation::initILIAS();
+ echo(renderFullDemoPage($DIC));
+ exit();
+}
+
+function renderFullDemoPage(Container $dic): string
+{
+ $factory = $dic->ui()->factory();
+ $renderer = $dic->ui()->renderer();
+ $dataFactory = new Factory();
+
+ $page = $factory->layout()->page()->mail(
+ 'assets/ui-examples/css/mail_examples.css',
+ 'data:image/svg+xml;base64,' . base64_encode(file_get_contents('assets/images/logo/HeaderIcon.svg')),
+ 'ILIAS e-Learning',
+ $factory->legacy()->content('
Mail Page Content
Dear John Doe, ...
'),
+ $dataFactory->link('https://www.ilias.de', $dataFactory->uri('https://www.ilias.de')),
+ );
+
+ return $renderer->render($page);
+}
diff --git a/components/ILIAS/UI/src/templates/default/Layout/tpl.mailpage.html b/components/ILIAS/UI/src/templates/default/Layout/tpl.mailpage.html
new file mode 100644
index 000000000000..943b29196728
--- /dev/null
+++ b/components/ILIAS/UI/src/templates/default/Layout/tpl.mailpage.html
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+ {CONTENT}
+ |
+
+
+
+ |
+
+
+
+
+
+
+
+
+
diff --git a/components/ILIAS/UI/tests/Component/Layout/Page/MailPageTest.php b/components/ILIAS/UI/tests/Component/Layout/Page/MailPageTest.php
new file mode 100644
index 000000000000..0fb24ba1b3ee
--- /dev/null
+++ b/components/ILIAS/UI/tests/Component/Layout/Page/MailPageTest.php
@@ -0,0 +1,173 @@
+html_content = $this->createMock(Content::class);
+ $this->html_content->method('getContent')->willReturn('ILIAS HTML Content');
+
+ $this->logo = 'cid:ILIAS Logo';
+ $this->installation_title = 'ILIAS Installation Title';
+ $this->stylesheet_path = __FILE__;
+ $this->uri = $this->createMock(URI::class);
+ $this->uri->method('getBaseURI')->willReturn('ILIAS Link URI');
+ $this->footer_url = $this->createMock(DataLink::class);
+ $this->footer_url->method('getLabel')->willReturn('ILIAS Link Label');
+ $this->footer_url->method('getURL')->willReturn($this->uri);
+
+ $this->factory = new Page\Factory();
+ $this->mailpage = $this->factory->mail(
+ $this->stylesheet_path,
+ $this->logo,
+ $this->installation_title,
+ $this->html_content,
+ $this->footer_url,
+ );
+ }
+
+ public function testConstruction(): void
+ {
+ static::assertInstanceOf(
+ Page\Mail::class,
+ $this->mailpage,
+ );
+ }
+
+ public static function provideGetData(): array
+ {
+ return [
+ ['html_content', 'getContent', 'assertContains'],
+ ['logo', 'getLogoURL', 'assertEquals'],
+ ['installation_title', 'getInstallationTitle', 'assertEquals'],
+ ['stylesheet_path', 'getStyleSheetPath', 'assertEquals'],
+ ['footer_url', 'getFooterURL', 'assertEquals'],
+ ];
+ }
+
+ /**
+ * @dataProvider provideGetData
+ */
+ public function testGet(mixed $expected, string $method_to_call, string $assert_method): void
+ {
+ static::$assert_method(
+ $this->$expected,
+ $this->mailpage->$method_to_call(),
+ );
+ }
+
+ public function testRenderMailPage(): void
+ {
+ $renderer = $this->getDefaultRenderer(null, [$this->html_content]);
+ $mailpage = $this->factory->mail(
+ $this->stylesheet_path,
+ $this->logo,
+ $this->installation_title,
+ $this->html_content,
+ $this->footer_url,
+ );
+
+ $html = $this->brutallyTrimHTML($renderer->render($mailpage));
+ $expected = $this->brutallyTrimHTML('
+
+
+
+
+
+
+
+
+ |
+ ' . $renderer->render($this->html_content) . '
+ |
+
+
+
+ |
+
+
+
+');
+
+ static::assertStringContainsString($expected, $html);
+ }
+
+ public function getUIFactory(): NoUIFactory
+ {
+ return new class () extends NoUIFactory {
+ public function link(): Link\Factory
+ {
+ return new Link\Factory();
+ }
+ };
+ }
+}