diff --git a/htdocs/modules/system/themes/modern/docs/DEVELOPER_GUIDE.md b/htdocs/modules/system/themes/modern/docs/DEVELOPER_GUIDE.md new file mode 100644 index 000000000..c69ca0991 --- /dev/null +++ b/htdocs/modules/system/themes/modern/docs/DEVELOPER_GUIDE.md @@ -0,0 +1,1308 @@ +# Modern Admin Theme — Developer & Designer Guide + +An in-depth technical reference for designers and developers who want to understand, customize, or extend the Modern admin theme. + +## Table of Contents + +- [Architecture Overview](#architecture-overview) +- [File Structure](#file-structure) +- [Request Lifecycle](#request-lifecycle) +- [Template System](#template-system) +- [CSS Architecture](#css-architecture) +- [JavaScript Architecture](#javascript-architecture) +- [The Widget System](#the-widget-system) +- [Data Flow Reference](#data-flow-reference) +- [Customization Recipes](#customization-recipes) +- [Creating a New Color Preset](#creating-a-new-color-preset) +- [Adding a Dashboard Section](#adding-a-dashboard-section) +- [Adding a Sidebar Section](#adding-a-sidebar-section) +- [Replacing Emoji Icons with SVG](#replacing-emoji-icons-with-svg) +- [Adding a New Chart](#adding-a-new-chart) +- [Modifying the Header](#modifying-the-header) +- [Adding a New Template Partial](#adding-a-new-template-partial) +- [Creating Module Widgets](#creating-module-widgets) +- [Internationalization](#internationalization) +- [Performance Considerations](#performance-considerations) +- [Testing](#testing) +- [Compatibility Notes](#compatibility-notes) + +--- + +## Architecture Overview + +The Modern theme is a XOOPS admin GUI theme that extends `XoopsSystemGui`. It consists of: + +```mermaid +block-beta + columns 1 + block:php["PHP Layer — modern.php"] + columns 3 + p1["Extends XoopsSystemGui"] + p2["Queries DB for stats, charts, widgets"] + p3["Assigns all Smarty template variables"] + end + block:tpl["Template Layer — theme.tpl + xotpl/*.tpl"] + columns 3 + t1["Smarty 3 with <{ }> delimiters"] + t2["Modular partials (head, sidebar, dashboard)"] + t3["Data bridge: PHP → JSON → JavaScript"] + end + block:css["CSS Layer — css/*.css"] + columns 3 + c1["Design tokens via CSS custom properties"] + c2["Light mode defaults, dark mode overrides"] + c3["fixes.css for XOOPS core !important rules"] + end + block:js["JavaScript Layer — js/*.js"] + columns 3 + j1["jQuery-based, no build step"] + j2["theme.js / charts.js"] + j3["customizer.js + cookies"] + end + block:wid["Widget Layer — class/ + widgets/"] + columns 3 + w1["ModernThemeWidgetInterface contract"] + w2["WidgetLoader: auto-discovery + sort"] + w3["11 pre-built module widgets"] + end + + php --> tpl --> css --> js --> wid +``` + +Key design principles: +- **No build tooling** — Plain CSS, plain JS, no Sass/Webpack/npm required +- **CSS custom properties** — All colors, spacing, and shadows use `var()` tokens +- **Progressive enhancement** — Works without JavaScript (charts and customizer degrade gracefully) +- **Cookie-based persistence** — No database tables, no module configs, no server-side storage +- **Auto-discovery** — Widgets load automatically from module directories + +--- + +## File Structure + +```text +modern/ +├── modern.php # Theme class (XoopsGuiModern extends XoopsSystemGui) +├── theme.tpl # Root HTML document template +├── menu.php # System admin menu builder +│ +├── class/ +│ ├── ModuleWidgetInterface.php # Widget contract (3 methods) +│ └── WidgetLoader.php # Widget auto-discovery engine +│ +├── css/ +│ ├── modern.css # Design tokens + full light-mode styles (~1200 lines) +│ ├── dark.css # Dark mode variable overrides (~200 lines) +│ └── fixes.css # !important overrides for XOOPS core CSS (~300 lines) +│ +├── js/ +│ ├── theme.js # Core: sidebar toggle, dark mode, help, messages +│ ├── charts.js # Chart.js initialization, dark mode sync, content filter +│ ├── customizer.js # Settings panel, color presets, cookie management +│ └── dashboard.js # Table hover, refresh button +│ +├── xotpl/ # Smarty template partials +│ ├── xo_metas.tpl # meta tags + $xoops_module_header +│ ├── xo_scripts.tpl # Placeholder for inline scripts +│ ├── xo_head.tpl # Header bar (brand, services, dark mode toggle) +│ ├── xo_sidebar.tpl # Left sidebar navigation +│ ├── xo_toolbar.tpl # Placeholder for breadcrumb/toolbar +│ ├── xo_dashboard.tpl # Dashboard: KPIs, charts, widgets, system info +│ ├── xo_widgets.tpl # Widget card renderer (iterates $module_widgets) +│ ├── xo_page.tpl # Module content area + admin links bar +│ ├── xo_customizer.tpl # Slide-in settings panel + FAB button +│ └── xo_footer.tpl # Footer bar +│ +├── language/english/ +│ ├── main.php # ~63 language constants (_MODERN_* prefix) +│ └── index.php # Security guard +│ +├── widgets/ # Pre-built widget files (copy to module dirs) +│ ├── alumni/class/ModernThemeWidget.php +│ ├── jobs/class/ModernThemeWidget.php +│ ├── newbb/class/ModernThemeWidget.php +│ ├── news/class/ModernThemeWidget.php +│ ├── pedigree/class/ModernThemeWidget.php +│ ├── protector/class/ModernThemeWidget.php +│ ├── publisher/class/ModernThemeWidget.php +│ ├── realestate/class/ModernThemeWidget.php +│ ├── tdmdownloads/class/ModernThemeWidget.php +│ ├── vision2026/class/ModernThemeWidget.php +│ └── xblog/class/ModernThemeWidget.php +│ +├── icons/ # System service icons (PNG) +├── images/ # Theme images +├── docs/ # Documentation +└── tests/ # Compatibility tests +``` + +--- + +## Request Lifecycle + +When an admin page loads, execution flows through: + +```mermaid +flowchart TD + A["1. XOOPS Bootstrap
include/common.php"] --> B["2. admin.php dispatches
to active admin GUI"] + B --> C["3. XoopsSystemGui loads
modern.php"] + C --> D["4. xoops_loadLanguage()
Defines all _MODERN_* constants"] + D --> E["5. XoopsGuiModern::header()"] + + E --> E1["parent::header()"] + E --> E2["Asset injection
Chart.js, jQuery, CSS/JS"] + E --> E3["getSystemInfo()"] + E --> E4["getEnhancedStats()"] + E --> E5["getUserStats()"] + E --> E6["getModuleStats()"] + E --> E7["getContentStats()"] + E --> E8["loadModuleWidgets()"] + E --> E9["ComposerInfo::getComposerInfo()"] + E --> E10["buildMenu()"] + + E10 --> F["6. Smarty renders theme.tpl"] + + F --> F1["xo_metas.tpl
‹head› + CSS/JS"] + F --> F2["xo_head.tpl
Header bar"] + F --> F3["xo_sidebar.tpl
Navigation"] + F --> F4["xo_dashboard.tpl
KPIs, charts, widgets"] + F4 -.-> F4a["Emits
window.XOOPS_DASHBOARD_DATA"] + F --> F5["xo_page.tpl
Module content"] + F --> F6["xo_footer.tpl"] + F --> F7["xo_customizer.tpl
Settings panel"] + + F7 --> G["7. JavaScript executes"] + + G --> G1["theme.js
Dark mode, sidebar, messages"] + G --> G2["dashboard.js
Table interactions"] + G --> G3["charts.js
Builds Chart.js instances"] + G --> G4["customizer.js
Restores cookie preferences"] +``` + +### Component Interaction + +The same lifecycle viewed as inter-component communication: + +```mermaid +sequenceDiagram + participant Admin as Admin User + participant Browser as Browser + participant Theme as XoopsGuiModern + participant WidgetLoader as ModernThemeWidgetLoader + participant Module as Module ModernThemeWidget + participant DB as Database + participant Chart as Chart.js + + Admin->>Browser: Open admin dashboard + Browser->>Theme: Request header()/render + Theme->>DB: collect system/user/module/content stats + Theme->>WidgetLoader: loadWidgets() + WidgetLoader->>DB: enumerate active modules + WidgetLoader->>Module: include & instantiate ModernThemeWidget + Module->>DB: getWidgetData() queries + Module-->>WidgetLoader: widget array or false + WidgetLoader->>WidgetLoader: validate, augment (priority, module), sort + WidgetLoader-->>Theme: aggregated widgets + Theme-->>Browser: render dashboard + window.XOOPS_DASHBOARD_DATA + Browser->>Chart: charts.js initializes with data + Browser->>Browser: customizer interactions persist cookies + Browser->>Chart: call XOOPS_CHARTS.rebuildContentChart(selected) + Chart-->>Browser: update chart visuals +``` + +--- + +## Template System + +### Smarty Delimiters + +XOOPS uses custom Smarty delimiters. **Never** use standard `{` `}` delimiters: + +```smarty +<{$variable}> ← Variable output +<{$variable|escape:'html'}> ← Escaped output (XSS prevention) +<{foreach from=$items item=row}> ← Control structure +<{if $condition}>...<{/if}> ← Conditional +<{include file="$theme_tpl/xo_head.tpl"}> ← Partial include +<{$smarty.const._MODERN_LABEL}> ← Language constant +``` + +### Template Hierarchy + +`theme.tpl` is the root document. It includes all partials via the `$theme_tpl` variable: + +```mermaid +graph TD + ROOT["theme.tpl\nRoot HTML document"] --> META["xo_metas.tpl\nmeta tags, CSS, JS links"] + ROOT --> HEAD["xo_head.tpl\nFixed header bar"] + ROOT --> SIDE["xo_sidebar.tpl\nLeft navigation"] + ROOT --> DASH["xo_dashboard.tpl\nKPIs, charts, widgets, sysinfo"] + ROOT --> PAGE["xo_page.tpl\nModule content + admin links"] + ROOT --> FOOT["xo_footer.tpl\nFooter bar"] + ROOT --> CUST["xo_customizer.tpl\nSettings panel + FAB"] + + DASH --> WID["xo_widgets.tpl\nWidget card renderer"] + DASH --> SCRIPT["Inline script block\nwindow.XOOPS_DASHBOARD_DATA"] + + HEAD --> SERV["System service icons\nfrom mod_options"] + SIDE --> CTRL["Control panel nav\nHome, Dashboard, Logout"] + SIDE --> MODS["Module nav\nActive modules list"] + SIDE --> SYS["System nav\nSystem services"] + + style ROOT fill:#2563eb,color:#fff + style DASH fill:#10b981,color:#fff + style HEAD fill:#8b5cf6,color:#fff + style SIDE fill:#f59e0b,color:#fff +``` + +All partials are included via `<{include file="$theme_tpl/xo_*.tpl"}>`. The `$theme_tpl` variable resolves to the `xotpl/` directory path, making it easy to override individual templates. + +### Key Smarty Variables + +These variables are assigned by `modern.php` and available in all templates: + +| Variable | Type | Source | +|----------|------|--------| +| `$xoops_sitename` | string | Site configuration | +| `$xoops_dirname` | string | Current module dirname | +| `$dark_mode` | string | Cookie value ('0' or '1') | +| `$theme_url` | string | URL to theme directory | +| `$enhanced_stats` | array | User statistics | +| `$user_chart_data` | JSON string | Registration chart data | +| `$group_stats` | JSON string | Group distribution data | +| `$content_stats` | JSON string | Content per module | +| `$module_widgets` | array | Widget data keyed by dirname | +| `$control_menu` | array | Control panel nav items | +| `$module_menu` | array | Module nav items | +| `$mod_options` | array | Current module admin icons | +| `$system_services` | array | System admin service icons | +| `$modules` | array | Module list for dashboard grid | +| `$composerPackages` | array | Installed Composer packages | + +### Adding a Template Partial + +1. Create `xotpl/xo_mypartial.tpl` +1. Include it in `theme.tpl`: + +```smarty +<{include file="$theme_tpl/xo_mypartial.tpl"}> +``` + +1. Assign any needed variables in `modern.php`: + +```php +$tpl->assign('my_data', $data); +``` + +--- + +## CSS Architecture + +### Design Token System + +All visual properties flow from CSS custom properties defined in `:root`. This means changing one variable propagates everywhere: + +```css +:root { + /* Layout */ + --sidebar-width: 260px; + --header-height: 64px; + + /* Border Radius */ + --radius-sm: 4px; /* Badges, small elements */ + --radius: 8px; /* Cards, inputs, buttons */ + --radius-lg: 12px; /* Large containers, panels */ + + /* Shadows */ + --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1); + --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1); + --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1); + + /* Brand Colors */ + --primary: #2563eb; /* Main accent (Blue 600) */ + --primary-dark: #1e40af; /* Hover/active (Blue 800) */ + --primary-light: #3b82f6; /* Light accent (Blue 500) */ + --secondary: #64748b; /* Secondary UI (Slate 500) */ + --success: #10b981; /* Success states (Emerald 500) */ + --warning: #f59e0b; /* Warnings (Amber 500) */ + --danger: #ef4444; /* Errors (Red 500) */ + --info: #06b6d4; /* Info (Cyan 500) */ + + /* Backgrounds */ + --bg-primary: #f8fafc; /* Page background (Slate 50) */ + --bg-secondary: #ffffff; /* Cards, sidebar, header */ + --bg-tertiary: #f1f5f9; /* Hover states, alt rows (Slate 100) */ + + /* Text */ + --text-primary: #0f172a; /* Main text (Slate 900) */ + --text-secondary: #475569;/* Subdued text (Slate 600) */ + --text-tertiary: #94a3b8; /* Placeholders (Slate 400) */ + + /* Borders */ + --border: #e2e8f0; /* Standard borders (Slate 200) */ + --border-light: #f1f5f9; /* Subtle separators (Slate 100) */ +} +``` + +### Three-File Strategy + +```mermaid +flowchart LR + subgraph Layer1["Layer 1: Design System"] + M["modern.css\n~1200 lines\nTokens + all components"] + end + subgraph Layer2["Layer 2: Dark Mode"] + D["dark.css\n~200 lines\nVariable overrides on\nbody.dark-mode"] + end + subgraph Layer3["Layer 3: Core Fixes"] + F["fixes.css\n~300 lines\n!important overrides for\nXOOPS core inline styles"] + end + + M --> D --> F + + M -. ":root vars" .-> D + D -. "same vars,\ndark values" .-> F +``` + +| File | Purpose | Uses `!important`? | +|------|---------|-------------------| +| `modern.css` | Design tokens, layout, all components | No | +| `dark.css` | Dark mode variable overrides on `body.dark-mode` | No | +| `fixes.css` | Overrides for XOOPS core inline styles | Yes (required) | + +**Why `fixes.css` exists:** XOOPS core and some modules inject inline styles and CSS rules that can't be overridden through normal cascade. `fixes.css` uses `!important` surgically on specific selectors to fix these conflicts. It's organized into numbered sections with comments explaining each override. + +### Dark Mode + +```mermaid +sequenceDiagram + actor User + participant TJS as theme.js + participant Cookie as Browser Cookie + participant Body as document.body + participant CSS as dark.css + participant Charts as charts.js + + User->>TJS: Click moon/sun icon + TJS->>Cookie: setCookie('xoops_dark_mode', '1') + TJS->>Body: classList.toggle('dark-mode') + Body->>CSS: body.dark-mode selector activates + CSS->>CSS: All CSS variables reassigned to dark values + Note over CSS: --bg-primary: #0f172a
--text-primary: #f1f5f9
etc. + CSS->>Body: Every var() reference updates instantly + Body->>Charts: MutationObserver detects class change + Charts->>Charts: updateChartsForTheme() + Note over Charts: Grid lines, labels, legends
switch to light-on-dark colors +``` + +Dark mode is implemented by reassigning all CSS custom properties on the `body.dark-mode` selector in `dark.css`: + +```css +body.dark-mode { + --bg-primary: #0f172a; /* Deep navy */ + --bg-secondary: #1e293b; /* Card backgrounds */ + --bg-tertiary: #334155; /* Hover states */ + --text-primary: #f1f5f9; /* Light text */ + --text-secondary: #cbd5e1; + --text-tertiary: #94a3b8; + --border: #334155; + --border-light: #475569; + /* ... shadows, brand colors adjusted for dark */ +} +``` + +Because all components use `var()` tokens, dark mode works automatically without per-component overrides. + +### Responsive Breakpoints + +| Breakpoint | Behavior | +|------------|----------| +| `> 1024px` | Full desktop layout. Sidebar visible, collapsible to 80px icon-only mode. | +| `≤ 1024px` | Sidebar hidden by default, slides in as overlay via `body.sidebar-open`. Footer and main content span full width. | +| `≤ 768px` | KPIs and charts collapse to single column. Header text shrinks. Customizer panel goes full-width. | + +### Sidebar State Machine + +```mermaid +stateDiagram-v2 + state "Desktop (> 1024px)" as Desktop { + [*] --> Expanded + Expanded --> Collapsed: Click hamburger icon + Collapsed --> Expanded: Click hamburger icon + state Expanded { + note right of Expanded: 260px wide\nIcons + labels\nmargin-left: 260px + } + state Collapsed { + note right of Collapsed: 80px wide\nIcons only (tooltips)\nmargin-left: 80px + } + } + + state "Tablet/Mobile (≤ 1024px)" as Mobile { + [*] --> Hidden + Hidden --> Overlay: Click hamburger icon + Overlay --> Hidden: Click outside / hamburger + state Hidden { + note right of Hidden: Sidebar off-screen\nmargin-left: 0 + } + state Overlay { + note right of Overlay: Sidebar slides in\nDim backdrop behind\nbody.sidebar-open + } + } +``` + +### Page Layout Structure + +```mermaid +block-beta + columns 5 + + block:header:5 + columns 5 + hamburger["Hamburger"] + brand["Site Name"] + icons["Service Icons"] + space3:1 + actions["Dark Mode Toggle"] + end + + block:sidebar:1 + columns 1 + ctrl["Control Panel\nHome | Dashboard | Logout"] + mods["Modules\nPublisher | News | ..."] + sys["System\nUsers | Groups | Blocks | ..."] + end + + block:main:4 + columns 1 + content["Main Content Area\nDashboard or Module Admin"] + end + + block:footer:5 + columns 1 + foot["Footer: Powered by XOOPS | Execution time"] + end + + style header fill:#2563eb,color:#fff + style sidebar fill:#1e293b,color:#fff + style main fill:#f8fafc,color:#0f172a + style footer fill:#f1f5f9,color:#475569 +``` + +### Key Layout Selectors + +```css +.modern-header { position: fixed; height: var(--header-height); z-index: 1000; } +.modern-sidebar { width: var(--sidebar-width); position: fixed; z-index: 999; } +.modern-main { margin-left: var(--sidebar-width); padding-top: var(--header-height); } +.modern-footer { margin-left: var(--sidebar-width); } +``` + +When the sidebar collapses (`body.sidebar-open` on desktop), `--sidebar-width` effectively becomes `80px` through explicit rules that override `margin-left`. + +--- + +## JavaScript Architecture + +All JavaScript is ES2015+ (uses `const`, `let`, arrow functions) with jQuery. No build step or transpilation — scripts are loaded directly by the browser. Each file is a self-contained IIFE or plain script. + +### Load Order + +Scripts are injected by `modern.php` via `$xoTheme->addScript()`: + +```mermaid +flowchart LR + subgraph Dependencies["External Dependencies"] + JQ["jQuery\nXOOPS core Frameworks"] + CJ["Chart.js\nLocal file or CDN fallback"] + end + + subgraph Theme["Theme Scripts"] + TH["theme.js\nDark mode, sidebar,\nhelp, messages"] + DA["dashboard.js\nTable hover,\nrefresh button"] + CH["charts.js\nChart.js init,\ndark mode sync"] + CU["customizer.js\nSettings panel,\ncookie management"] + end + + CJ --> JQ --> TH --> DA --> CH --> CU + + style JQ fill:#f59e0b,color:#fff + style CJ fill:#f59e0b,color:#fff +``` + +1. `Chart.js` (local via `browse.php` or CDN fallback) +1. `jQuery` (from XOOPS core Frameworks) +1. `theme.js` — Core UI behaviors +1. `dashboard.js` — Table interactions +1. `charts.js` — Chart initialization +1. `customizer.js` — Settings panel + +### theme.js — Core Behaviors + +```text +initDarkMode() → Reads/writes xoops_dark_mode cookie, toggles body.dark-mode +initSidebarToggle() → Toggles body.sidebar-open on hamburger click +initHelpToggle() → Shows/hides .tips and .xo-help-content, persists in cookie +initMessages() → Moves .errorMsg/.warningMsg to top of .modern-main, + auto-dismisses after 5 seconds +setCookie(name, val, days) → SameSite=Lax, conditional Secure flag +getCookie(name) → Standard cookie reader +``` + +### charts.js — Chart.js Integration + +```text +initCharts() → Entry point, waits for Chart.js to load (500ms retry) +initUserRegistrationChart() → Line chart from window.XOOPS_DASHBOARD_DATA.userChart +initUserGroupChart() → Doughnut chart from .groupStats +initContentChart() → Bar chart from .contentStats (filtered by cookie) +rebuildContentChart(sel) → Live rebuild when customizer changes module selection +updateChartsForTheme() → Updates all chart colors for dark/light mode +``` + +**PHP → JS data bridge:** `xo_dashboard.tpl` emits a `