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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
+ + + + {INSTALLATION_TITLE} + +
+
+ + + + + + +
+ {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(' + + + + + + + + + + + +
+ + + + + + + +
+ + + + ' . $this->installation_title . ' + +
+
+ + + + + + +
+ ' . $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(); + } + }; + } +}