From ff7ce3e331d577d1809beee6c87c16ef6d300740 Mon Sep 17 00:00:00 2001 From: Ralf Lang Date: Sun, 24 May 2026 21:25:17 +0200 Subject: [PATCH 1/3] fix(themes): add screen.css stub for theme discovery The horde-installer-plugin requires a themes directory to be recognized during reconfigure. Without screen.css, the passwd theme was not symlinked to the web directory. --- themes/default/screen.css | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 themes/default/screen.css diff --git a/themes/default/screen.css b/themes/default/screen.css new file mode 100644 index 0000000..115d44c --- /dev/null +++ b/themes/default/screen.css @@ -0,0 +1,5 @@ +/** + * Passwd Screen Styles + * + * Copyright 2026 Horde LLC (http://www.horde.org/) + */ From b37ce62872735baebe2457771c542057f5bfc12c Mon Sep 17 00:00:00 2001 From: Ralf Lang Date: Sun, 24 May 2026 22:54:07 +0200 Subject: [PATCH 2/3] refactor(responsive): use ResponsiveChromeRenderer, consolidate CSS Migrate controller to use ResponsiveChromeRenderer with body-only template. Delete old full-page template and standalone CSS files. Responsive rules now live in screen.css under .horde-responsive scope. --- src/ResponsivePasswordController.php | 65 +++--------- templates/responsive-body.html.php | 113 ++++++++++++++++++++ templates/responsive.html.php | 148 --------------------------- themes/default/passwd-responsive.css | 74 -------------- themes/default/responsive.css | 23 ----- themes/default/screen.css | 79 ++++++++++++++ 6 files changed, 209 insertions(+), 293 deletions(-) create mode 100644 templates/responsive-body.html.php delete mode 100644 templates/responsive.html.php delete mode 100644 themes/default/passwd-responsive.css delete mode 100644 themes/default/responsive.css diff --git a/src/ResponsivePasswordController.php b/src/ResponsivePasswordController.php index 7cc2024..882ef8d 100644 --- a/src/ResponsivePasswordController.php +++ b/src/ResponsivePasswordController.php @@ -4,8 +4,8 @@ namespace Horde\Passwd; -use Horde\Core\Assets\ResponsiveAssets; -use Horde\Core\Config\RegistryState; +use Horde\Core\PageOutput\PageContent; +use Horde\Core\PageOutput\ResponsiveChromeRenderer; use Horde\Core\View\ResponsiveTemplateView; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -28,31 +28,15 @@ */ class ResponsivePasswordController implements RequestHandlerInterface { - /** - * Handle password change request - * - * @param ServerRequestInterface $request The request object - * - * @return ResponseInterface The response - */ public function handle(ServerRequestInterface $request): ResponseInterface { global $registry, $session, $injector, $conf; - // Get form variables $vars = $injector->getInstance('Horde_Variables'); - // Use existing Passwd_Basic for business logic - $basic = new \Passwd_Basic($vars); - - // Build responsive assets - $responsiveAssets = new ResponsiveAssets(new RegistryState($registry->applications)); - - // Get backend info $backends = $injector->getInstance('Passwd_Factory_Driver')->backends; $backend_key = $vars->backend; if (!isset($backends[$backend_key])) { - // Choose first available backend foreach ($backends as $k => $v) { if (substr($k, 0, 1) != '_') { $backend_key = $k; @@ -61,10 +45,8 @@ public function handle(ServerRequestInterface $request): ResponseInterface } } - // Determine if we should show backend selector $showlist = ($conf['backend']['backend_list'] == 'shown'); - // Build header text if ($showlist) { $header = _("Change your password"); } else { @@ -74,24 +56,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface ); } - // Prepare view data $viewData = [ - // Topbar data - 'topbar' => [ - 'appName' => _("Change Password"), - 'portalUrl' => (string) $registry->getServiceLink('portal')->setRaw(true), - 'logoutUrl' => (string) $registry->getServiceLink('logout')->setRaw(true), - 'userName' => $registry->getAuth(), - ], - - // Asset URIs - 'hordeThemesUri' => $registry->get('themesuri', 'horde'), - 'hordeJsUri' => $registry->get('jsuri', 'horde'), - 'passwdThemesUri' => $registry->get('themesuri', 'passwd'), - 'passwdJsUri' => $registry->get('jsuri', 'passwd'), - 'registry' => $registry, - - // Form data 'formInput' => \Horde_Util::formInput(), 'url' => $vars->return_to ?: '', 'userid' => $registry->getAuth() ?: '', @@ -101,26 +66,31 @@ public function handle(ServerRequestInterface $request): ResponseInterface 'backend' => $backend_key, 'token' => $session->getToken(), 'header' => $header, - - // Status messages (use responsive listener for proper CSS classes) 'status' => $this->getStatusMessages(), ]; - // Render responsive template - $templatePath = PASSWD_TEMPLATES . '/responsive.html.php'; + $templatePath = PASSWD_TEMPLATES . '/responsive-body.html.php'; $view = new ResponsiveTemplateView($templatePath, $viewData); + $bodyHtml = $view->render(); + + $chromeRenderer = $injector->get(ResponsiveChromeRenderer::class); + $pageContent = new PageContent( + title: _("Change Password") . ' - Horde', + bodyHtml: $bodyHtml, + app: 'passwd', + jsFiles: ['passwd-responsive.js'], + ); + + $pageHtml = $chromeRenderer->renderPage($pageContent, $request); $streamFactory = $injector->getInstance('Psr\Http\Message\StreamFactoryInterface'); $responseFactory = $injector->getInstance('Psr\Http\Message\ResponseFactoryInterface'); + return $responseFactory->createResponse(200) - ->withBody($streamFactory->createStream($view->render())); + ->withHeader('Content-Type', 'text/html; charset=UTF-8') + ->withBody($streamFactory->createStream($pageHtml)); } - /** - * Get status messages using responsive listener - * - * @return string HTML status messages - */ private function getStatusMessages(): string { $notification = $GLOBALS['notification'] ?? null; @@ -128,7 +98,6 @@ private function getStatusMessages(): string return ''; } - // Register our responsive listener if not already registered if (!$notification->getListener('responsive_status')) { $notification->attach( 'responsive_status', diff --git a/templates/responsive-body.html.php b/templates/responsive-body.html.php new file mode 100644 index 0000000..9a2f40a --- /dev/null +++ b/templates/responsive-body.html.php @@ -0,0 +1,113 @@ +
+
+
+

+
+ + + + + +
+ + + + + + + + +
+ ℹ️ + + ' . htmlspecialchars($backends[$backend]['name']) . '' + ); + } + ?> + +
+ + + +
+ + +
+ + + +
+ + +
+ + +
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+
+
diff --git a/templates/responsive.html.php b/templates/responsive.html.php deleted file mode 100644 index 8269583..0000000 --- a/templates/responsive.html.php +++ /dev/null @@ -1,148 +0,0 @@ - - - - - - <?= htmlspecialchars($topbar['appName']) ?> - Horde - - - - - - - - - get('templates', 'horde'); - include $hordeTemplates . '/responsive/topbar.html.php'; - ?> - -
-
-
-

-
- - - - - -
- - - - - - - - -
- ℹ️ - - ' . htmlspecialchars($backends[$backend]['name']) . '' - ); - } - ?> - -
- - - -
- - -
- - - -
- - -
- - -
- - -
- -
- - -
- -
- - -
- - -
-
-
- - - - - - - - - - - diff --git a/themes/default/passwd-responsive.css b/themes/default/passwd-responsive.css deleted file mode 100644 index 3e6a1b5..0000000 --- a/themes/default/passwd-responsive.css +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Passwd Responsive Styles - * Mobile-first password change form - * - * Copyright 2026 Horde LLC (http://www.horde.org/) - */ - -/* Layout */ -.passwd-container { - padding-top: var(--space-lg); - padding-bottom: var(--space-2xl); -} - -.passwd-card { - max-width: 480px; - margin: 0 auto; -} - -/* Form spacing */ -#passwd-form { - margin-top: var(--space-lg); -} - -/* Select styling to match inputs */ -#passwd-backend { - appearance: none; - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='%23666' d='M6 8L0 0h12z'/%3E%3C/svg%3E"); - background-repeat: no-repeat; - background-position: right var(--space-md) center; - background-size: 12px 8px; - padding-right: calc(var(--space-md) + 20px); -} - -/* Info message styling */ -.info-message { - display: flex; - align-items: flex-start; - gap: var(--space-sm); - padding: var(--space-md); - margin-bottom: var(--space-lg); - background-color: #e3f2fd; - border-left: 4px solid #2196f3; - border-radius: var(--radius-sm); - font-size: var(--font-size-sm); - line-height: var(--line-height-relaxed); -} - -.info-icon { - flex-shrink: 0; - font-size: var(--font-size-lg); - line-height: 1; -} - -.info-text { - flex: 1; - color: var(--text-primary); -} - -.info-text strong { - font-weight: var(--font-weight-semibold); - color: var(--text-primary); -} - -/* Mobile optimizations */ -@media (max-width: 480px) { - .passwd-container { - padding-top: var(--space-md); - } - - .passwd-card { - border-radius: var(--radius-md); - } -} - diff --git a/themes/default/responsive.css b/themes/default/responsive.css deleted file mode 100644 index cf201bc..0000000 --- a/themes/default/responsive.css +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Passwd Responsive Styles - * Extends base responsive.css with Passwd-specific components - * - * Copyright 2026 Horde LLC (http://www.horde.org/) - */ - -/* ============================================================================ - App Icon Override - ========================================================================= */ - -/* Override Horde base icon with Passwd-specific icon */ -.topbar-menu-app-passwd::before { - content: ''; - display: inline-block; - width: 20px; - height: 20px; - background-image: url('graphics/passwd.png'); - background-size: contain; - background-repeat: no-repeat; - background-position: center; - vertical-align: middle; -} diff --git a/themes/default/screen.css b/themes/default/screen.css index 115d44c..7e5e08e 100644 --- a/themes/default/screen.css +++ b/themes/default/screen.css @@ -3,3 +3,82 @@ * * Copyright 2026 Horde LLC (http://www.horde.org/) */ + +/* ============================================================================ + Responsive View (scoped under .horde-responsive) + ============================================================================ */ + +.horde-responsive .topbar-menu-app-passwd::before { + content: ''; + display: inline-block; + width: 20px; + height: 20px; + background-image: url('graphics/passwd.png'); + background-size: contain; + background-repeat: no-repeat; + background-position: center; + vertical-align: middle; +} + +/* Password form layout */ +.horde-responsive .passwd-container { + padding-top: var(--space-lg); + padding-bottom: var(--space-2xl); +} + +.horde-responsive .passwd-card { + max-width: 480px; + margin: 0 auto; +} + +.horde-responsive #passwd-form { + margin-top: var(--space-lg); +} + +.horde-responsive #passwd-backend { + appearance: none; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='%23666' d='M6 8L0 0h12z'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right var(--space-md) center; + background-size: 12px 8px; + padding-right: calc(var(--space-md) + 20px); +} + +.horde-responsive .info-message { + display: flex; + align-items: flex-start; + gap: var(--space-sm); + padding: var(--space-md); + margin-bottom: var(--space-lg); + background-color: #e3f2fd; + border-left: 4px solid #2196f3; + border-radius: var(--radius-sm); + font-size: var(--font-size-sm); + line-height: var(--line-height-relaxed); +} + +.horde-responsive .info-icon { + flex-shrink: 0; + font-size: var(--font-size-lg); + line-height: 1; +} + +.horde-responsive .info-text { + flex: 1; + color: var(--text-primary); +} + +.horde-responsive .info-text strong { + font-weight: var(--font-weight-semibold); + color: var(--text-primary); +} + +@media (max-width: 480px) { + .horde-responsive .passwd-container { + padding-top: var(--space-md); + } + + .horde-responsive .passwd-card { + border-radius: var(--radius-md); + } +} From baeaa82031df81e1b2248f26ceb2efaf48b48b69 Mon Sep 17 00:00:00 2001 From: Ralf Lang Date: Sun, 24 May 2026 23:45:16 +0200 Subject: [PATCH 3/3] docs: add Lucide Icons license notice --- doc/LUCIDE-LICENSE.md | 51 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 doc/LUCIDE-LICENSE.md diff --git a/doc/LUCIDE-LICENSE.md b/doc/LUCIDE-LICENSE.md new file mode 100644 index 0000000..135377a --- /dev/null +++ b/doc/LUCIDE-LICENSE.md @@ -0,0 +1,51 @@ +The application icon in themes/default/graphics/ is derived from Lucide Icons, +converted to PNG format. + +Source: https://github.com/lucide-icons/lucide +License: ISC (with some icons under MIT via Feather Icons) + +The full license text follows below. + +======================================================================== + +ISC License + +Copyright (c) 2020 Lucide Icons and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +======================================================================== + +Some Lucide icons are derived from Feather Icons by Cole Bemis: + +The MIT License (MIT) + +Copyright (c) 2013-present Cole Bemis + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.