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. 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/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/passwd-responsive.css b/themes/default/screen.css similarity index 57% rename from themes/default/passwd-responsive.css rename to themes/default/screen.css index 3e6a1b5..7e5e08e 100644 --- a/themes/default/passwd-responsive.css +++ b/themes/default/screen.css @@ -1,28 +1,41 @@ /** - * Passwd Responsive Styles - * Mobile-first password change form + * Passwd Screen Styles * * Copyright 2026 Horde LLC (http://www.horde.org/) */ -/* Layout */ -.passwd-container { +/* ============================================================================ + 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); } -.passwd-card { +.horde-responsive .passwd-card { max-width: 480px; margin: 0 auto; } -/* Form spacing */ -#passwd-form { +.horde-responsive #passwd-form { margin-top: var(--space-lg); } -/* Select styling to match inputs */ -#passwd-backend { +.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; @@ -31,8 +44,7 @@ padding-right: calc(var(--space-md) + 20px); } -/* Info message styling */ -.info-message { +.horde-responsive .info-message { display: flex; align-items: flex-start; gap: var(--space-sm); @@ -45,30 +57,28 @@ line-height: var(--line-height-relaxed); } -.info-icon { +.horde-responsive .info-icon { flex-shrink: 0; font-size: var(--font-size-lg); line-height: 1; } -.info-text { +.horde-responsive .info-text { flex: 1; color: var(--text-primary); } -.info-text strong { +.horde-responsive .info-text strong { font-weight: var(--font-weight-semibold); color: var(--text-primary); } -/* Mobile optimizations */ @media (max-width: 480px) { - .passwd-container { + .horde-responsive .passwd-container { padding-top: var(--space-md); } - .passwd-card { + .horde-responsive .passwd-card { border-radius: var(--radius-md); } } -