diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cffab2..adf0df7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,18 @@ # Changelog — MokoOnyx (VERSION: 02.28.09) ## [Unreleased] +### Added +- Tabbed user profile edit and registration screens: `com_users` fieldsets are grouped into Bootstrap tabs via the shared `layouts/mokoonyx/profile-tabset` layout (Account, Basic Settings, Passkey Login, Multi-factor Authentication, plus any plugin-contributed fieldsets such as MokoSuiteCRM / MokoSuiteCommunity) + +### Changed +- Drawer menu links now follow the drawer's own foreground colour (`--offcanvas-color`) instead of the header navbar link colour, so they adapt to the active theme + +### Fixed +- Drawer menu links were invisible (white-on-white) in the light theme; they now render correctly in both light and dark themes + +### Removed +- Retired the right drawer entirely: the `drawer-right` template position, its toggle button, offcanvas markup, the `drawerRightIcon` parameter, and the associated CSS / JS / RTL rules. On update, any modules still assigned to `drawer-right` are automatically moved to the `sidebar-right` position + ## [02.28.00] --- 2026-07-13 ## [02.27.06] --- 2026-06-29 diff --git a/source/error.php b/source/error.php index 9a797c0..4903e65 100644 --- a/source/error.php +++ b/source/error.php @@ -36,7 +36,6 @@ $stickyHeader = (bool) $params->get('stickyHeader', 0); // Drawer icon params (escaped) $params_leftIcon = htmlspecialchars($params->get('drawerLeftIcon', 'fa-solid fa-chevron-left'), ENT_QUOTES, 'UTF-8'); -$params_rightIcon = htmlspecialchars($params->get('drawerRightIcon', 'fa-solid fa-chevron-right'), ENT_QUOTES, 'UTF-8'); // Template/Media path $templatePath = 'media/templates/site/mokoonyx'; @@ -69,7 +68,7 @@ $wa->useScript('template.js'); $wa->useStyle('template.font.osaka'); // Smart Bootstrap component loading - only load what's needed -if ($this->countModules('drawer-left', true) || $this->countModules('drawer-right', true)) { +if ($this->countModules('drawer-left', true)) { // Load Bootstrap Offcanvas component for drawers HTMLHelper::_('bootstrap.offcanvas'); } @@ -295,16 +294,6 @@ $wa->useScript('user.js'); // js/user.js - countModules('drawer-right')) : ?> - - - countModules('menu', true) || $this->countModules('search', true)) : ?>
countModules('menu', true)) : ?> @@ -430,18 +419,6 @@ $wa->useScript('user.js'); // js/user.js -countModules('drawer-right', true)) : ?> - - - - diff --git a/source/html/com_users/profile/edit.php b/source/html/com_users/profile/edit.php new file mode 100644 index 0000000..84fe485 --- /dev/null +++ b/source/html/com_users/profile/edit.php @@ -0,0 +1,78 @@ + + + This file is part of a Moko Consulting project. + + SPDX-License-Identifier: GPL-3.0-or-later + + Template override of com_users profile edit: groups the form fieldsets into + Bootstrap tabs via layouts/mokoonyx/profile-tabset. Structure otherwise mirrors + the core template (save/cancel tasks, control fields, MFA block). + */ + +defined('_JEXEC') or die; + +use Joomla\CMS\HTML\HTMLHelper; +use Joomla\CMS\Language\Text; +use Joomla\CMS\Layout\LayoutHelper; +use Joomla\CMS\Router\Route; + +/** @var Joomla\Component\Users\Site\View\Profile\HtmlView $this */ + +HTMLHelper::_('bootstrap.tooltip', '.hasTooltip'); + +// Load user_profile plugin language +$lang = $this->getLanguage(); +$lang->load('plg_user_profile', JPATH_ADMINISTRATOR); + +/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */ +$wa = $this->getDocument()->getWebAssetManager(); +$wa->useScript('keepalive') + ->useScript('form.validate'); + +// Multi-factor Authentication is rendered outside the form fieldsets by the core +// view, so pass it to the tab-set as an extra tab (only when the UI is present). +$extraTabs = []; + +if ($this->mfaConfigurationUI) { + $extraTabs[] = [ + 'id' => 'mfa', + 'title' => Text::_('COM_USERS_PROFILE_MULTIFACTOR_AUTH'), + 'icon' => 'fa-solid fa-shield-halved', + 'html' => $this->mfaConfigurationUI, + ]; +} +?> +
+ params->get('show_page_heading')) : ?> + + + +
+ $this->form, + 'idBase' => 'profile', + 'extraTabs' => $extraTabs, + ]); ?> + +
+
+ + + +
+
+ + form->renderControlFields(); ?> +
+
diff --git a/source/html/com_users/registration/default.php b/source/html/com_users/registration/default.php new file mode 100644 index 0000000..7d408a6 --- /dev/null +++ b/source/html/com_users/registration/default.php @@ -0,0 +1,57 @@ + + + This file is part of a Moko Consulting project. + + SPDX-License-Identifier: GPL-3.0-or-later + + Template override of com_users registration: groups the form fieldsets into + Bootstrap tabs via layouts/mokoonyx/profile-tabset. The captcha fieldset stays + outside the tabs (rendered just before submit), mirroring the core template. + */ + +defined('_JEXEC') or die; + +use Joomla\CMS\Language\Text; +use Joomla\CMS\Layout\LayoutHelper; +use Joomla\CMS\Router\Route; + +/** @var \Joomla\Component\Users\Site\View\Registration\HtmlView $this */ + +/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */ +$wa = $this->getDocument()->getWebAssetManager(); +$wa->useScript('keepalive') + ->useScript('form.validate'); +?> +
+ params->get('show_page_heading')) : ?> + + + +
+ + $this->form, + 'idBase' => 'registration', + 'skip' => $this->captchaEnabled ? ['captcha'] : [], + ]); ?> + + captchaEnabled) : ?> + form->renderFieldset('captcha'); ?> + + +
+
+ + + +
+
+ + form->renderControlFields(); ?> +
+
diff --git a/source/html/layouts/mokoonyx/profile-tabset.php b/source/html/layouts/mokoonyx/profile-tabset.php new file mode 100644 index 0000000..603ab44 --- /dev/null +++ b/source/html/layouts/mokoonyx/profile-tabset.php @@ -0,0 +1,135 @@ + + + This file is part of a Moko Consulting project. + + SPDX-License-Identifier: GPL-3.0-or-later + */ + +defined('_JEXEC') or die; + +use Joomla\CMS\HTML\HTMLHelper; +use Joomla\CMS\Language\Text; + +/** + * Profile / registration tab-set layout. + * + * Renders a Joomla form's fieldsets as Bootstrap 5 tabs using the native + * `uitab` HTMLHelper (which also loads the bootstrap.tab script). Any fieldset + * contributed by a plugin — including MokoSuiteCRM / MokoSuiteCommunity user + * plugins — automatically becomes its own tab; nothing here is hardcoded to a + * specific field. + * + * Expected $displayData keys: + * 'form' => Joomla\CMS\Form\Form The bound form to render. + * 'idBase' => string Unique prefix for tab ids ('profile' | 'registration'). + * 'skip' => string[] Fieldset names to exclude from tabs + * (caller renders them itself, e.g. 'captcha'). + * 'extraTabs' => array[] Extra tabs appended after the fieldset tabs. + * Each: ['id' => string, 'title' => string, + * 'icon' => string, 'html' => string]. Used for + * content rendered outside the form (e.g. MFA). + */ + +/** @var Joomla\CMS\Form\Form $form */ +$form = $displayData['form']; +$idBase = $displayData['idBase'] ?? 'profile'; +$skip = $displayData['skip'] ?? []; +$extraTabs = $displayData['extraTabs'] ?? []; + +// Collect fieldsets that actually contain fields (minus any the caller skips). +$groups = []; +foreach ($form->getFieldsets() as $name => $fieldset) { + if (in_array($name, $skip, true)) { + continue; + } + + if (count($form->getFieldset($name))) { + $groups[$name] = $fieldset; + } +} + +// Per-fieldset presentation. +// icon => Font Awesome 7 class for the tab. +// title => OPTIONAL title override, used only when a fieldset has no label of +// its own (e.g. a label-less "core" fieldset). May be a language key +// or a literal string. +// +// A fieldset's own translated label always wins when present, so Joomla/plugin +// wording ("Basic Settings", "Passkey Login", …) is preserved. Add entries only +// to pin an icon or name a label-less fieldset — including MokoSuiteCRM / +// MokoSuiteCommunity sections. +$tabMeta = [ + 'core' => ['icon' => 'fa-solid fa-user', 'title' => 'TPL_MOKOONYX_PROFILE_TAB_ACCOUNT'], + 'default' => ['icon' => 'fa-solid fa-user', 'title' => 'TPL_MOKOONYX_PROFILE_TAB_ACCOUNT'], + 'settings' => ['icon' => 'fa-solid fa-gear'], + 'params' => ['icon' => 'fa-solid fa-gear'], + 'profile' => ['icon' => 'fa-solid fa-id-card'], + 'passwordless' => ['icon' => 'fa-solid fa-fingerprint'], + 'webauthn' => ['icon' => 'fa-solid fa-fingerprint'], + // ── MokoSuiteCRM / MokoSuiteCommunity fieldsets: add entries here ───────── +]; + +// Resolve a fieldset to [title, icon] with graceful fallbacks. +$resolve = static function (string $name, $fieldset) use ($tabMeta): array { + $meta = $tabMeta[strtolower($name)] ?? []; + $icon = $meta['icon'] ?? 'fa-solid fa-folder-open'; + + if (!empty($fieldset->label)) { + $title = Text::_($fieldset->label); + } elseif (!empty($meta['title'])) { + $title = Text::_($meta['title']); + } else { + $title = ucwords(str_replace(['_', '-'], ' ', $name)); + } + + return [$title, $icon]; +}; + +// Build a uniform tab list: fieldset tabs first, then any extra tabs. +$tabs = []; + +foreach ($groups as $name => $fieldset) { + [$title, $icon] = $resolve($name, $fieldset); + $tabs[] = [ + 'id' => $idBase . '-' . $name, + 'title' => $title, + 'icon' => $icon, + 'html' => $form->renderFieldset($name), + ]; +} + +foreach ($extraTabs as $extra) { + if (empty($extra['html'])) { + continue; + } + + $tabs[] = [ + 'id' => $idBase . '-' . ($extra['id'] ?? 'extra'), + 'title' => $extra['title'] ?? '', + 'icon' => $extra['icon'] ?? 'fa-solid fa-folder-open', + 'html' => $extra['html'], + ]; +} + +// A single section doesn't warrant a tab strip — render it flat instead. +if (count($tabs) <= 1) { + foreach ($tabs as $tab) { + echo '
' . $tab['html'] . '
'; + } + + return; +} + +echo HTMLHelper::_('uitab.startTabSet', $idBase, ['active' => $tabs[0]['id'], 'recall' => true]); + +foreach ($tabs as $tab) { + $label = '' + . htmlspecialchars($tab['title'], ENT_QUOTES, 'UTF-8'); + + echo HTMLHelper::_('uitab.addTab', $idBase, $tab['id'], $label); + echo $tab['html']; + echo HTMLHelper::_('uitab.endTab'); +} + +echo HTMLHelper::_('uitab.endTabSet'); diff --git a/source/index.php b/source/index.php index b093233..927ac5a 100644 --- a/source/index.php +++ b/source/index.php @@ -195,11 +195,10 @@ $hasClass = ''; if ($this->countModules('sidebar-left', true)) { $hasClass .= ' has-sidebar-left'; } if ($this->countModules('sidebar-right', true)) { $hasClass .= ' has-sidebar-right'; } if ($this->countModules('drawer-left', true)) { $hasClass .= ' has-drawer-left'; } -if ($this->countModules('drawer-right', true)) { $hasClass .= ' has-drawer-right'; } // Smart Bootstrap component loading - only load what's needed HTMLHelper::_('bootstrap.collapse'); -if ($this->countModules('drawer-left', true) || $this->countModules('drawer-right', true)) { +if ($this->countModules('drawer-left', true)) { HTMLHelper::_('bootstrap.offcanvas'); } @@ -252,7 +251,6 @@ if ($faKitCode !== '') { } } $params_leftIcon = htmlspecialchars($this->params->get('drawerLeftIcon', 'fa-solid fa-chevron-left'), ENT_COMPAT, 'UTF-8'); -$params_rightIcon = htmlspecialchars($this->params->get('drawerRightIcon', 'fa-solid fa-chevron-right'), ENT_COMPAT, 'UTF-8'); // Load theme palette stylesheets — Joomla auto-serves .min when debug is off $wa->useStyle('template.light.standard'); @@ -490,16 +488,6 @@ $wa->useScript('user.js'); // js/user.js - countModules('drawer-right')) : ?> - - - countModules('menu', true) || $this->countModules('search', true)) && !$hideMenuHome) : ?>
@@ -633,18 +621,6 @@ endif; ?> -countModules('drawer-right', true)) : ?> - - - - diff --git a/source/language/en-GB/tpl_mokoonyx.ini b/source/language/en-GB/tpl_mokoonyx.ini index b462c10..495380e 100644 --- a/source/language/en-GB/tpl_mokoonyx.ini +++ b/source/language/en-GB/tpl_mokoonyx.ini @@ -18,8 +18,6 @@ TPL_MOKOONYX_OFFLINEEMBED_DESC="In addition to the 'Offline message' defined in TPL_MOKOONYX_DRAWERS_FIELDSET_LABEL="Drawers" TPL_MOKOONYX_DRAWER_LEFT_ICON_LABEL="Drawer Left Icon CSS" TPL_MOKOONYX_DRAWER_LEFT_ICON_DESC="Enter the Font-Awesome class for the left drawer toggle (e.g. 'fas fa-chevron-left')." -TPL_MOKOONYX_DRAWER_RIGHT_ICON_LABEL="Drawer Right Icon CSS" -TPL_MOKOONYX_DRAWER_RIGHT_ICON_DESC="Enter the Font-Awesome class for the right drawer toggle (e.g. 'fas fa-chevron-right')." ; ===== Favicon ===== TPL_MOKOONYX_FAVICON_FIELDSET_LABEL="Favicon" @@ -332,3 +330,8 @@ TPL_MOKOONYX_CSS_VARS_SOCIAL_DESC="--social-icon-size — Icon font MOD_BREADCRUMBS_HERE="YOU ARE HERE:" JGLOBAL_OFFLINE="Offline" + +; ===== User profile / registration tabs ===== +TPL_MOKOONYX_PROFILE_TAB_ACCOUNT="Account" +TPL_MOKOONYX_PROFILE_TAB_SETTINGS="Settings" +TPL_MOKOONYX_PROFILE_TAB_PROFILE="Profile" diff --git a/source/language/en-GB/tpl_mokoonyx.sys.ini b/source/language/en-GB/tpl_mokoonyx.sys.ini index 3657ef8..29d3626 100644 --- a/source/language/en-GB/tpl_mokoonyx.sys.ini +++ b/source/language/en-GB/tpl_mokoonyx.sys.ini @@ -27,5 +27,4 @@ TPL_MOKOONYX_POSITION_TOP_A="Top-a" TPL_MOKOONYX_POSITION_TOP_B="Top-b" TPL_MOKOONYX_POSITION_TOPBAR="Top Bar" TPL_MOKOONYX_POSITION_DRAWER_LEFT="Drawer-Left" -TPL_MOKOONYX_POSITION_DRAWER_RIGHT="Drawer-Right" TPL_MOKOONYX_XML_DESCRIPTION="

\"Version\" \"License\" \"Joomla\" \"PHP\"

MokoOnyx Template Description

MokoOnyx continues Joomla’s tradition of space-themed default templates— building on the legacy of Solarflare (Joomla 1.0), Milkyway (Joomla 1.5), and Protostar (Joomla 3.0).

This template is a customized fork of the Cassiopeia template introduced in Joomla 4, preserving its modern, accessible, and mobile-first foundation while introducing new stylistic enhancements and structural refinements specifically tailored for use by Moko Consulting.

Custom Colour Themes

Starter palette files are included with the template. To create a custom colour scheme, copy templates/mokoonyx/templates/light.custom.css to media/templates/site/mokoonyx/css/theme/light.custom.css, or templates/mokoonyx/templates/dark.custom.css to media/templates/site/mokoonyx/css/theme/dark.custom.css. Customise the CSS variables to match your brand, then activate your palette in System → Site Templates → MokoOnyx → Theme tab by selecting "Custom" for the Light or Dark Mode Palette. A full variable reference is available in the CSS Variables tab in template options.

Custom CSS & JavaScript

For site-specific styles and scripts that should survive template updates, create the following files:

  • media/templates/site/mokoonyx/css/user.css — loaded on every page for custom CSS overrides.
  • media/templates/site/mokoonyx/js/user.js — loaded on every page for custom JavaScript.

These files are gitignored and will not be overwritten by template updates.

Code Attribution

This template is based on the original Cassiopeia template developed by the Joomla! Project and released under the GNU General Public License.

Modifications and enhancements have been made by Moko Consulting in accordance with open-source licensing standards.

It includes integration with Bootstrap TOC, an open-source table of contents generator by A. Feld, licensed under the MIT License.

All third-party libraries and assets remain the property of their respective authors and are credited within their source files where applicable.

" diff --git a/source/language/en-US/tpl_mokoonyx.ini b/source/language/en-US/tpl_mokoonyx.ini index 6fad714..7bc6d45 100644 --- a/source/language/en-US/tpl_mokoonyx.ini +++ b/source/language/en-US/tpl_mokoonyx.ini @@ -18,8 +18,6 @@ TPL_MOKOONYX_OFFLINEEMBED_DESC="In addition to the 'Offline message' defined in TPL_MOKOONYX_DRAWERS_FIELDSET_LABEL="Drawers" TPL_MOKOONYX_DRAWER_LEFT_ICON_LABEL="Drawer Left Icon CSS" TPL_MOKOONYX_DRAWER_LEFT_ICON_DESC="Enter the Font-Awesome class for the left drawer toggle (e.g. 'fas fa-chevron-left')." -TPL_MOKOONYX_DRAWER_RIGHT_ICON_LABEL="Drawer Right Icon CSS" -TPL_MOKOONYX_DRAWER_RIGHT_ICON_DESC="Enter the Font-Awesome class for the right drawer toggle (e.g. 'fas fa-chevron-right')." ; ===== Favicon ===== TPL_MOKOONYX_FAVICON_FIELDSET_LABEL="Favicon" @@ -332,3 +330,8 @@ TPL_MOKOONYX_CSS_VARS_SOCIAL_DESC="--social-icon-size — Icon font MOD_BREADCRUMBS_HERE="YOU ARE HERE:" JGLOBAL_OFFLINE="Offline" + +; ===== User profile / registration tabs ===== +TPL_MOKOONYX_PROFILE_TAB_ACCOUNT="Account" +TPL_MOKOONYX_PROFILE_TAB_SETTINGS="Settings" +TPL_MOKOONYX_PROFILE_TAB_PROFILE="Profile" diff --git a/source/language/en-US/tpl_mokoonyx.sys.ini b/source/language/en-US/tpl_mokoonyx.sys.ini index 505a048..9ac1422 100644 --- a/source/language/en-US/tpl_mokoonyx.sys.ini +++ b/source/language/en-US/tpl_mokoonyx.sys.ini @@ -27,5 +27,4 @@ TPL_MOKOONYX_POSITION_TOP_A="Top-a" TPL_MOKOONYX_POSITION_TOP_B="Top-b" TPL_MOKOONYX_POSITION_TOPBAR="Top Bar" TPL_MOKOONYX_POSITION_DRAWER_LEFT="Drawer-Left" -TPL_MOKOONYX_POSITION_DRAWER_RIGHT="Drawer-Right" TPL_MOKOONYX_XML_DESCRIPTION="

MokoOnyx Template Description

MokoOnyx continues Joomla’s tradition of space-themed default templates— building on the legacy of Solarflare (Joomla 1.0), Milkyway (Joomla 1.5), and Protostar (Joomla 3.0).

This template is a customized fork of the Cassiopeia template introduced in Joomla 4, preserving its modern, accessible, and mobile-first foundation while introducing new stylistic enhancements and structural refinements specifically tailored for use by Moko Consulting.

Custom Color Themes

Starter palette files are included with the template. To create a custom color scheme, copy templates/mokoonyx/templates/light.custom.css to media/templates/site/mokoonyx/css/theme/light.custom.css, or templates/mokoonyx/templates/dark.custom.css to media/templates/site/mokoonyx/css/theme/dark.custom.css. Customize the CSS variables to match your brand, then activate your palette in System → Site Templates → MokoOnyx → Theme tab by selecting "Custom" for the Light or Dark Mode Palette. A full variable reference is available in the CSS Variables tab in template options.

Custom CSS & JavaScript

For site-specific styles and scripts that should survive template updates, create the following files:

  • media/templates/site/mokoonyx/css/user.css — loaded on every page for custom CSS overrides.
  • media/templates/site/mokoonyx/js/user.js — loaded on every page for custom JavaScript.

These files are gitignored and will not be overwritten by template updates.

Code Attribution

This template is based on the original Cassiopeia template developed by the Joomla! Project and released under the GNU General Public License.

Modifications and enhancements have been made by Moko Consulting in accordance with open-source licensing standards.

It includes integration with Bootstrap TOC, an open-source table of contents generator by A. Feld, licensed under the MIT License.

All third-party libraries and assets remain the property of their respective authors and are credited within their source files where applicable.

" diff --git a/source/media/css/template-rtl.css b/source/media/css/template-rtl.css index 42d6c42..1646033 100644 --- a/source/media/css/template-rtl.css +++ b/source/media/css/template-rtl.css @@ -33,18 +33,6 @@ padding-right: calc(var(--btn-padding-x)*0.5) !important; } -[dir="rtl"] .drawer-toggle-right { - - position: fixed; - top: 250px; - left: 0px; - z-index: 1050; - background-color: var(--nav-bg-color) !important; - color: var(--nav-text-color) !important; - padding-left: calc(var(--btn-padding-x)*0.5) !important; - padding-right: calc(var(--btn-padding-x)*0.5) !important; -} - [dir="rtl"] ol, [dir="rtl"] ul { diff --git a/source/media/css/template.css b/source/media/css/template.css index 2006543..76db874 100644 --- a/source/media/css/template.css +++ b/source/media/css/template.css @@ -96,8 +96,7 @@ form { color: var(--color-link, #224FAA); } -.offcanvas-start, -.offcanvas-end { +.offcanvas-start { width: 280px; } @@ -116,19 +115,23 @@ button.drawer-toggle-left, border-bottom-left-radius: 0 !important; } -button.drawer-toggle-right, -.drawer-toggle-right.btn { - position: fixed; - top: 50%; - right: 0px; - z-index: 1050; - background-color: var(--accent-color, var(--color-primary, #2a69b8)) !important; - color: var(--accent-color-text, #fff) !important; - border-color: var(--accent-color, var(--color-primary, #2a69b8)) !important; - padding-left: .5rem; - padding-right: .5rem; - border-top-right-radius: 0 !important; - border-bottom-right-radius: 0 !important; +/* Drawer menu links must follow the drawer's own foreground colour + (--offcanvas-color), not the header navbar's link colour, which is white to + sit on the coloured navbar. Without this, menu links render white-on-white in + the light theme (invisible) and only look right in dark by coincidence. */ +.offcanvas .mod-menu a, +.offcanvas .mod-menu span, +.offcanvas .mod-menu button, +.offcanvas .mod-menu .nav-link, +.offcanvas .mod-menu .metismenu-item > a, +.offcanvas .mod-menu .metismenu-item > span { + color: var(--offcanvas-color, var(--body-color, #22262a)) !important; +} + +.offcanvas .mod-menu a:hover, +.offcanvas .mod-menu a:focus, +.offcanvas .mod-menu .metismenu-item > a:hover { + color: var(--color-hover, var(--accent-color-primary, #3f8ff0)) !important; } hr { diff --git a/source/media/js/template.js b/source/media/js/template.js index e066516..d7fd664 100644 --- a/source/media/js/template.js +++ b/source/media/js/template.js @@ -506,7 +506,7 @@ */ function initDrawers() { // Check if any drawer buttons exist before initializing - var hasDrawers = doc.querySelector(".drawer-toggle-left") || doc.querySelector(".drawer-toggle-right"); + var hasDrawers = doc.querySelector(".drawer-toggle-left"); if (!hasDrawers) { return; // No drawers, skip initialization } diff --git a/source/script.php b/source/script.php index 4ed090f..9310a62 100644 --- a/source/script.php +++ b/source/script.php @@ -105,9 +105,56 @@ class Tpl_MokoonyxInstallerScript implements InstallerScriptInterface $this->lockExtension(); } + if ($type === 'update') { + $this->migrateRightDrawerModules(); + } + return true; } + /** + * Migrate modules from the retired drawer-right position to sidebar-right. + * + * The drawer-right template position and its markup were removed. Rather + * than stranding modules still assigned to it (they would silently stop + * displaying), move them to the sidebar-right position so they remain + * visible. Published and unpublished site modules are both moved so nothing + * is lost. Runs on update and is naturally idempotent — once migrated, no + * rows match drawer-right on subsequent updates. + */ + private function migrateRightDrawerModules(): void + { + try { + $db = Factory::getDbo(); + + $query = $db->getQuery(true) + ->update($db->quoteName('#__modules')) + ->set($db->quoteName('position') . ' = ' . $db->quote('sidebar-right')) + ->where($db->quoteName('position') . ' = ' . $db->quote('drawer-right')) + ->where($db->quoteName('client_id') . ' = 0'); + $db->setQuery($query)->execute(); + + $moved = $db->getAffectedRows(); + + if ($moved > 0) { + Factory::getApplication()->enqueueMessage( + sprintf( + 'MokoOnyx: The right drawer has been retired. ' + . '%d module(s) were moved from the removed drawer-right position to ' + . 'sidebar-right. Review their placement and adjust if needed.', + $moved + ), + 'notice' + ); + $this->logMessage("Right drawer retired — moved {$moved} module(s) from drawer-right to sidebar-right."); + } else { + $this->logMessage('Right drawer retired — no modules were assigned to drawer-right.'); + } + } catch (\Throwable $e) { + $this->logMessage('Right drawer module migration failed: ' . $e->getMessage(), 'warning'); + } + } + /** * Replace MokoCassiopeia references in article content and module content. */ diff --git a/source/templateDetails.xml b/source/templateDetails.xml index c2a2b7c..6367a15 100644 --- a/source/templateDetails.xml +++ b/source/templateDetails.xml @@ -92,7 +92,6 @@ offline offline-footer drawer-left - drawer-right language/en-GB/tpl_mokoonyx.ini @@ -144,7 +143,6 @@
-