Compare commits

..

19 Commits

Author SHA1 Message Date
Jonathan Miller d353b1ee36 fix: Joomla 5/6 event compatibility in DevTools and Monitor plugins
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Auto Version Bump / Version Bump (push) Successful in 8s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 24s
Both plugins used Joomla 6-only event->getArgument() pattern without
fallback for Joomla 5 individual args. Added dual-compat pattern
matching DemoReset plugin: check for event object, fallback to
func_get_arg for Joomla 5.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-06 08:28:23 -05:00
Jonathan Miller d07eb89f66 feat: implement block_frontend_superuser in firewall plugin
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Auto Version Bump / Version Bump (push) Successful in 7s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 24s
The config field existed but had no backing implementation. Super admin
users are now redirected to the admin panel when accessing the frontend
if the toggle is enabled.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-06 08:25:54 -05:00
Jonathan Miller 398fefe2fd feat: show component and module version tiles on dashboard
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Auto Version Bump / Version Bump (push) Successful in 7s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 22s
Add getMokoExtensions() to DashboardModel to fetch installed MokoWaaS
component and modules with versions. Renders version badges below the
info bar for com_mokowaas, mod_mokowaas_cpanel, mod_mokowaas_menu,
mod_mokowaas_cache, and mod_mokowaas_categories.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-06 08:17:12 -05:00
Jonathan Miller 5e33f94cce chore: bump version to 02.34.16-dev, update updates.xml
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Auto Version Bump / Version Bump (push) Successful in 8s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 25s
Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-06 08:08:45 -05:00
jmiller e7cdc41648 Merge pull request 'chore: rename src/ to source/ per moko-platform standards (#188)' (#189) from chore/rename-src-to-source into dev
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Universal: Auto Version Bump / Version Bump (push) Has been skipped
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 24s
2026-06-06 12:48:06 +00:00
Jonathan Miller e3c15979b8 chore: rename src/ to source/ per moko-platform standards (#188)
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (pull_request) Blocked by required conditions
Platform: moko-platform CI / CI Summary (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Universal: PR Check / Report Issues (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Report Issues (pull_request) Blocked by required conditions
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 21s
Platform: moko-platform CI / Gate 1: Code Quality (pull_request) Failing after 26s
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Failing after 7s
Rename top-level src/ directory to source/ and update all references
in .gitignore, CLAUDE.md, manifest.xml, docs, and PATH comments.
Internal namespace path="src" attributes within extension packages
are unchanged (they refer to the package-internal src/ folder).

Closes #188

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-06 07:43:59 -05:00
Jonathan Miller 68ab5bdd44 Merge branch 'dev' of https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS into dev
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 32s
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Auto Version Bump / Version Bump (push) Successful in 6s
2026-06-06 07:33:25 -05:00
Jonathan Miller 1fe19fe5f1 fix: cache module uses split button layout "Clear: Cache | Temp"
Single header item with two clickable halves instead of two separate
status bar entries.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-06 07:32:19 -05:00
jmiller 17ef84e867 chore: sync .mokogitea/workflows/pre-release.yml from moko-platform [skip ci] 2026-06-06 12:31:31 +00:00
Jonathan Miller 8d4a5b7a04 chore: bump version to 02.34.15-dev, update updates.xml
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Auto Version Bump / Version Bump (push) Successful in 5s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 20s
Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-06 07:29:51 -05:00
Jonathan Miller 9fed55d5c0 fix: rename core plugin label, add temp cleaner to cache module
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Auto Version Bump / Version Bump (push) Successful in 7s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 24s
- Dashboard: rename "Core — Branding & Identity" to "Core" with updated description
- Cache module: add temp directory cleaner button (trash icon) alongside cache cleaner
- DashboardModel: add clearTemp() method that removes tmp contents preserving .htaccess
- DisplayController: add clearTemp task handler

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-06 07:24:52 -05:00
Jonathan Miller d79d5393be chore: bump version to 02.34.14-dev, update updates.xml
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Auto Version Bump / Version Bump (push) Successful in 8s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 24s
Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-06 07:17:47 -05:00
Jonathan Miller 3cfe653b18 fix: firewall language strings and subform path after core cleanup
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Auto Version Bump / Version Bump (push) Successful in 8s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 23s
- Add forms/trusted_ip_entry.xml to firewall plugin (was deleted from core)
- Update formsource paths to point to firewall's own forms directory
- Add missing Security Headers fieldset language strings
- Use hardcoded English labels in subform to avoid cross-plugin translation issues

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-06 07:08:32 -05:00
Jonathan Miller 882a4bfb5c chore: bump version to 02.34.13-dev, update updates.xml
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Universal: Auto Version Bump / Version Bump (push) Successful in 8s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 27s
Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-06 07:06:39 -05:00
Jonathan Miller d792b7ff0c fix: preserve download keys across Joomla extension updates (#187)
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Auto Version Bump / Version Bump (push) Successful in 9s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 33s
Joomla's installer can wipe extra_query (dlid) from #__update_sites
when rebuilding or reinstalling. The core plugin now backs up all
download keys and auto-restores any that get cleared. Runs on every
admin page load with a single lightweight query.

Closes #187

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-06 07:02:22 -05:00
Jonathan Miller 68ffffe2af chore: bump version to 02.34.12-dev, update updates.xml
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 27s
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Auto Version Bump / Version Bump (push) Successful in 10s
Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-06 06:53:56 -05:00
Jonathan Miller 0fb82306bb feat: XML-based extension catalog with update server discovery (#186)
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Auto Version Bump / Version Bump (push) Successful in 8s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 31s
Replace hardcoded CATALOG constant with catalog.xml that points to each
extension's updates.xml. The model fetches update servers at runtime to
resolve latest version and download URL. Adds update_available status
with Update button in the extensions view.

Closes #186

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-06 06:49:52 -05:00
Jonathan Miller b170894228 chore: bump version to 02.34.11-dev, update updates.xml
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 24s
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Auto Version Bump / Version Bump (push) Successful in 8s
Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-06 06:37:21 -05:00
Jonathan Miller 082fa0798c feat: add auto-category menu module for knowledge base sections (#184)
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Report Issues (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Auto Version Bump / Version Bump (push) Successful in 6s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 21s
New module mod_mokowaas_categories auto-discovers article categories
from a configurable root and renders them as a collapsible sidebar tree.
Supports configurable depth, article counts, empty category filtering,
and ACL-aware access. Matches existing MokoWaaS sidebar styling.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-06-06 06:34:17 -05:00
197 changed files with 1379 additions and 489 deletions
+3 -3
View File
@@ -107,7 +107,7 @@ replit.md
*.tar.gz
*.tgz
*.zip
!src/payload/*.zip
!source/payload/*.zip
artifacts/
release/
releases/
@@ -122,7 +122,7 @@ build/
dist/
out/
site/
!src/packages/*/site/
!source/packages/*/site/
*.map
*.css.map
*.js.map
@@ -161,7 +161,7 @@ package-lock.json
# PHP / Composer tooling
# ============================================================
vendor/
!src/media/vendor/
!source/media/vendor/
composer.lock
*.phar
codeception.phar
+2 -2
View File
@@ -9,7 +9,7 @@
<display-name>Package - MokoWaaS</display-name>
<org>MokoConsulting</org>
<description>White-label identity, security hardening, and tenant restriction layer for WaaS-managed Joomla environments</description>
<version>02.34.10</version>
<version>02.34.16</version>
<license spdx="GPL-3.0-or-later">GNU General Public License v3</license>
</identity>
<governance>
@@ -21,6 +21,6 @@
<build>
<language>PHP</language>
<package-type>package</package-type>
<entry-point>src/</entry-point>
<entry-point>source/</entry-point>
</build>
</moko-platform>
+1 -1
View File
@@ -5,7 +5,7 @@
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: moko-platform.Automation
# VERSION: 02.34.10
# VERSION: 02.34.16
# BRIEF: Auto-create feature branch when an issue is opened
name: "Universal: Issue Branch"
+15 -8
View File
@@ -63,15 +63,22 @@ jobs:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
run: |
if ! command -v composer &> /dev/null; then
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
# Use pre-installed /opt/moko-platform if available (updated by cron every 6h)
if [ -f “/opt/moko-platform/cli/version_bump.php” ] && [ -f “/opt/moko-platform/vendor/autoload.php” ]; then
echo “Using pre-installed /opt/moko-platform”
echo “MOKO_CLI=/opt/moko-platform/cli” >> “$GITHUB_ENV”
else
echo “Falling back to fresh clone”
if ! command -v composer &> /dev/null; then
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
fi
rm -rf /tmp/moko-platform-api
git clone --depth 1 --branch main --quiet \
“https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git” \
/tmp/moko-platform-api
cd /tmp/moko-platform-api && composer install --no-dev --no-interaction --quiet
echo “MOKO_CLI=/tmp/moko-platform-api/cli” >> “$GITHUB_ENV”
fi
rm -rf /tmp/moko-platform-api
git clone --depth 1 --branch main --quiet \
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
/tmp/moko-platform-api
cd /tmp/moko-platform-api && composer install --no-dev --no-interaction --quiet
echo "MOKO_CLI=/tmp/moko-platform-api/cli" >> "$GITHUB_ENV"
- name: Detect platform
id: platform
+1 -1
View File
@@ -14,7 +14,7 @@
INGROUP: MokoWaaS.Documentation
REPO: https://github.com/mokoconsulting-tech/mokowaas
PATH: ./CHANGELOG.md
VERSION: 02.34.10
VERSION: 02.34.16
BRIEF: Version history using `Keep a Changelog`
-->
+5 -5
View File
@@ -24,11 +24,11 @@ composer install # Install PHP dependencies
## Architecture
This is a Joomla extension. Key directories:
- `src/` -- extension source (deployed to Joomla)
- `src/*.xml` -- manifest file (version, files, params)
- `src/src/` or `src/services/` -- PHP classes
- `src/language/` -- translation strings
- `src/media/` -- CSS/JS/images
- `source/` -- extension source (deployed to Joomla)
- `source/*.xml` -- manifest file (version, files, params)
- `source/src/` or `source/services/` -- PHP classes
- `source/language/` -- translation strings
- `source/media/` -- CSS/JS/images
## Rules
+1 -1
View File
@@ -14,7 +14,7 @@
DEFGROUP: Joomla.Plugin
INGROUP: MokoWaaS.Documentation
REPO: https://github.com/mokoconsulting-tech/mokowaas
VERSION: 02.34.10
VERSION: 02.34.16
PATH: ./CODE_OF_CONDUCT.md
BRIEF: Reference + packaging repo for Moko Consulting Developer GPT Other Default
-->
+1 -1
View File
@@ -19,7 +19,7 @@
DEFGROUP: mokoconsulting-tech.MokoWaaSBrand
INGROUP: MokoStandards.Governance
REPO: https://github.com/mokoconsulting-tech/MokoWaaSBrand
VERSION: 02.34.10
VERSION: 02.34.16
PATH: /GOVERNANCE.md
BRIEF: Project governance rules, roles, and decision process for MokoWaaSBrand
-->
+1 -1
View File
@@ -15,7 +15,7 @@
INGROUP: MokoWaaS.Documentation
REPO: https://github.com/mokoconsulting-tech/mokowaas
PATH: ./LICENSE.md
VERSION: 02.34.10
VERSION: 02.34.16
BRIEF: Project license (GPL-3.0-or-later)
-->
GNU GENERAL PUBLIC LICENSE
+1 -1
View File
@@ -9,7 +9,7 @@
DEFGROUP: Joomla.Plugin
INGROUP: MokoWaaS
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS
VERSION: 02.34.10
VERSION: 02.34.16
PATH: /README.md
BRIEF: MokoWaaS platform plugin for Joomla
-->
+1 -1
View File
@@ -23,7 +23,7 @@ DEFGROUP: [PROJECT_NAME]
INGROUP: [PROJECT_NAME].Documentation
REPO: [REPOSITORY_URL]
PATH: /SECURITY.md
VERSION: 02.34.10
VERSION: 02.34.16
BRIEF: Security vulnerability reporting and handling policy
-->
+4 -4
View File
@@ -11,13 +11,13 @@
INGROUP: MokoWaaS.Build
REPO: https://github.com/mokoconsulting-tech/mokowaas
FILE: build-guide.md
VERSION: 02.34.10
VERSION: 02.34.16
PATH: /docs/guides/
BRIEF: Build and packaging guide for the MokoWaaS system plugin
NOTE: Defines environment setup, repository layout, packaging rules, and release preparation
-->
# MokoWaaS Build Guide (VERSION: 02.34.10)
# MokoWaaS Build Guide (VERSION: 02.34.16)
## 1. Purpose
@@ -44,7 +44,7 @@ The repository should maintain a clean, predictable, and modular structure suita
```text
mokowaas/
├── src/
├── source/
│ ├── mokowaas.php (main plugin file)
│ ├── mokowaas.xml (plugin manifest)
│ ├── services/ (service providers for DI)
@@ -192,7 +192,7 @@ jobs:
- name: Lint PHP and syntax check
run: |
echo "[INFO] Run php -l over src/ and any additional linting as needed."
echo "[INFO] Run php -l over source/ and any additional linting as needed."
- name: Create build artifact
run: |
+2 -2
View File
@@ -10,13 +10,13 @@
DEFGROUP: Joomla.Plugin
INGROUP: MokoWaaS.Guides
REPO: https://github.com/mokoconsulting-tech/mokowaas
VERSION: 02.34.10
VERSION: 02.34.16
PATH: /docs/guides/configuration-guide.md
BRIEF: Configuration guide for the MokoWaaS system plugin
NOTE: Defines plugin parameters, expected behaviors, and recommended defaults
-->
# MokoWaaS Configuration Guide (VERSION: 02.34.10)
# MokoWaaS Configuration Guide (VERSION: 02.34.16)
## 1. Objective
+2 -2
View File
@@ -10,13 +10,13 @@
DEFGROUP: Joomla.Plugin
INGROUP: MokoWaaS.Guides
REPO: https://github.com/mokoconsulting-tech/mokowaas
VERSION: 02.34.10
VERSION: 02.34.16
PATH: /docs/guides/installation-guide.md
BRIEF: Installation guide for the MokoWaaS system plugin
NOTE: First document in the guide set
-->
# MokoWaaS Installation Guide (VERSION: 02.34.10)
# MokoWaaS Installation Guide (VERSION: 02.34.16)
## Introduction
+2 -2
View File
@@ -10,13 +10,13 @@
DEFGROUP: Joomla.Plugin
INGROUP: MokoWaaS.Guides
REPO: https://github.com/mokoconsulting-tech/mokowaas
VERSION: 02.34.10
VERSION: 02.34.16
PATH: /docs/guides/operations-guide.md
BRIEF: Operational guide for administering and managing the MokoWaaS system plugin
NOTE: Defines lifecycle, responsibilities, and operational behaviors
-->
# MokoWaaS Operations Guide (VERSION: 02.34.10)
# MokoWaaS Operations Guide (VERSION: 02.34.16)
## Introduction
+2 -2
View File
@@ -10,13 +10,13 @@
DEFGROUP: Joomla.Plugin
INGROUP: MokoWaaS.Guides
REPO: https://github.com/mokoconsulting-tech/mokowaas
VERSION: 02.34.10
VERSION: 02.34.16
PATH: /docs/guides/rollback-and-recovery-guide.md
BRIEF: Rollback and recovery guide for restoring stable operation after plugin related incidents
NOTE: Completes the core guide set for WaaS plugin governance
-->
# MokoWaaS Rollback and Recovery Guide (VERSION: 02.34.10)
# MokoWaaS Rollback and Recovery Guide (VERSION: 02.34.16)
## Introduction
+8 -8
View File
@@ -7,13 +7,13 @@
DEFGROUP: Joomla.Plugin
INGROUP: MokoWaaS.Guides
REPO: https://github.com/mokoconsulting-tech/mokowaas
VERSION: 02.34.10
VERSION: 02.34.16
PATH: /docs/guides/testing-guide.md
BRIEF: Testing guide for MokoWaaS v02.01.08
NOTE: Covers manual test procedures for language overrides, install/uninstall, and configuration
-->
# MokoWaaS Testing Guide (VERSION: 02.34.10)
# MokoWaaS Testing Guide (VERSION: 02.34.16)
## 1. Prerequisites
@@ -27,7 +27,7 @@
1. Clean Joomla 5.x installation OR existing site with custom language overrides.
2. Admin account with Super User access.
3. Build the plugin package: `make package` or zip the `src/` directory.
3. Build the plugin package: `make package` or zip the `source/` directory.
## 2. Test Suites
@@ -278,19 +278,19 @@ Run from the project root:
```bash
# Lint all PHP files
php -l src/script.php
php -l src/Extension/MokoWaaS.php
php -l source/script.php
php -l source/Extension/MokoWaaS.php
# Verify all override files have placeholders (no hardcoded "MokoWaaS" in values)
grep -r '"MokoWaaS' src/language/overrides/ src/administrator/language/overrides/
grep -r '"MokoWaaS' source/language/overrides/ source/administrator/language/overrides/
# Expected: no output (all values should use {{BRAND_NAME}})
# Verify sentinel constants match
grep -c 'BLOCK_START\|BLOCK_END' src/script.php
grep -c 'BLOCK_START\|BLOCK_END' source/script.php
# Expected: 6+ references
# Verify all .ini files have version 02.01.08
grep -r 'Version:' src/**/*.ini | grep -v '02.01.08'
grep -r 'Version:' source/**/*.ini | grep -v '02.01.08'
# Expected: no output
```
+2 -2
View File
@@ -10,13 +10,13 @@
DEFGROUP: Joomla.Plugin
INGROUP: MokoWaaS.Guides
REPO: https://github.com/mokoconsulting-tech/mokowaas
VERSION: 02.34.10
VERSION: 02.34.16
PATH: /docs/guides/troubleshooting-guide.md
BRIEF: Troubleshooting guide for diagnosing and resolving issues related to the MokoWaaS plugin
NOTE: Designed for administrators and WaaS operations teams
-->
# MokoWaaS Troubleshooting Guide (VERSION: 02.34.10)
# MokoWaaS Troubleshooting Guide (VERSION: 02.34.16)
## Introduction
+2 -2
View File
@@ -10,13 +10,13 @@
DEFGROUP: Joomla.Plugin
INGROUP: MokoWaaS.Guides
REPO: https://github.com/mokoconsulting-tech/mokowaas
VERSION: 02.34.10
VERSION: 02.34.16
PATH: /docs/guides/upgrade-and-versioning-guide.md
BRIEF: Guide for updating, versioning, and maintaining the MokoWaaS plugin
NOTE: Defines release flow, version rules, and upgrade validation
-->
# MokoWaaS Upgrade and Versioning Guide (VERSION: 02.34.10)
# MokoWaaS Upgrade and Versioning Guide (VERSION: 02.34.16)
## Introduction
+2 -2
View File
@@ -10,13 +10,13 @@
DEFGROUP: Joomla.Plugin
INGROUP: MokoWaaS.Documentation
REPO: https://github.com/mokoconsulting-tech/mokowaas
VERSION: 02.34.10
VERSION: 02.34.16
PATH: /docs/index.md
BRIEF: Master index of all documentation for the MokoWaaS plugin
NOTE: Automatically maintained index for all guide canvases
-->
# MokoWaaS Documentation Index (VERSION: 02.34.10)
# MokoWaaS Documentation Index (VERSION: 02.34.16)
## Introduction
+2 -2
View File
@@ -11,12 +11,12 @@
INGROUP: MokoWaaS
REPO: https://github.com/mokoconsulting-tech/mokowaas
PATH: /docs/plugin-basic.md
VERSION: 02.34.10
VERSION: 02.34.16
BRIEF: Baseline documentation for the MokoWaaS system plugin
NOTE: Foundational reference for internal and external stakeholders
-->
# MokoWaaS Plugin Overview (VERSION: 02.34.10)
# MokoWaaS Plugin Overview (VERSION: 02.34.16)
## Introduction
+3 -3
View File
@@ -10,7 +10,7 @@ DEFGROUP: MokoWaaS.Documentation
INGROUP: MokoStandards.Templates
REPO: https://github.com/mokoconsulting-tech/MokoWaaS
PATH: /docs/update-server.md
VERSION: 02.34.10
VERSION: 02.34.16
BRIEF: How this extension's Joomla update server file (update.xml) is managed
-->
@@ -84,7 +84,7 @@ Since Joomla sites read `updates.xml` from the `main` branch, the `update-server
### Metadata Source
All metadata is extracted from the extension's XML manifest (`src/*.xml`) at build time:
All metadata is extracted from the extension's XML manifest (`source/*.xml`) at build time:
| XML Element | Source | Notes |
|-------------|--------|-------|
@@ -136,7 +136,7 @@ The `repo_health.yml` workflow verifies on every commit:
- `<version>`, `<name>`, `<author>`, `<namespace>` tags present
- Extension `type` attribute is valid
- Language `.ini` files exist
- `index.html` directory listing protection in `src/`, `src/admin/`, `src/site/`
- `index.html` directory listing protection in `source/`, `source/admin/`, `source/site/`
---
@@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Extension catalog for MokoWaaS Extension Manager.
Each entry points to the extension's own updates.xml — the installer
resolves the latest version and download URL at runtime.
To add an extension: copy an <extension> block and fill in the fields.
The updateserver URL must point to a valid Joomla updates.xml file.
-->
<catalog>
<extension>
<name>MokoWaaS</name>
<element>pkg_mokowaas</element>
<type>package</type>
<description>Admin dashboard, security firewall, tenant restrictions, health monitoring, and REST API.</description>
<icon>icon-shield-alt</icon>
<category>Platform</category>
<article>https://mokoconsulting.tech/support/products/mokowaas-platform</article>
<protected>true</protected>
<updateserver>https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/raw/branch/dev/updates.xml</updateserver>
</extension>
<extension>
<name>MokoOnyx</name>
<element>mokoonyx</element>
<type>template</type>
<description>Modern Joomla site template with dark mode, custom layouts, and MokoWaaS integration.</description>
<icon>icon-paint-brush</icon>
<category>Templates</category>
<article>https://mokoconsulting.tech/support/products/mokoonyx-template</article>
<updateserver>https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/raw/branch/dev/updates.xml</updateserver>
</extension>
<extension>
<name>MokoJoomTOS</name>
<element>com_mokojoomtos</element>
<type>component</type>
<description>Terms of Service and privacy policy component with consent tracking.</description>
<icon>icon-file-contract</icon>
<category>Components</category>
<article>https://mokoconsulting.tech/support/products/mokojoomtos</article>
<updateserver>https://git.mokoconsulting.tech/MokoConsulting/MokoJoomTOS/raw/branch/dev/updates.xml</updateserver>
</extension>
<extension>
<name>MokoJoomHero</name>
<element>mod_mokojoomhero</element>
<type>module</type>
<description>Random hero image module from a configurable folder.</description>
<icon>icon-image</icon>
<category>Modules</category>
<article>https://mokoconsulting.tech/support/products/mokojoomhero</article>
<updateserver>https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/raw/branch/dev/updates.xml</updateserver>
</extension>
<extension>
<name>MokoWaaS Announce</name>
<element>mod_mokowaas_announce</element>
<type>module</type>
<description>Centralized announcement system via admin module.</description>
<icon>icon-bullhorn</icon>
<category>Modules</category>
<article>https://mokoconsulting.tech/support/products/mokowaas-announce</article>
<updateserver>https://git.mokoconsulting.tech/MokoConsulting/MokoWaaSAnnounce/raw/branch/dev/updates.xml</updateserver>
</extension>
<extension>
<name>DPCalendar API</name>
<element>mokodpcalendarapi</element>
<type>plugin</type>
<description>Web Services plugin exposing DPCalendar events and calendars via REST API.</description>
<icon>icon-calendar</icon>
<category>Plugins</category>
<article>https://mokoconsulting.tech/support/products/mokodpcalendarapi</article>
<updateserver>https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/raw/branch/dev/updates.xml</updateserver>
</extension>
<extension>
<name>Gallery Calendar</name>
<element>mokogallerycalendar</element>
<type>plugin</type>
<description>JoomGallery and DPCalendar integration — link galleries to events.</description>
<icon>icon-images</icon>
<category>Plugins</category>
<article>https://mokoconsulting.tech/support/products/mokogallerycalendar</article>
<updateserver>https://git.mokoconsulting.tech/MokoConsulting/MokoGalleryCalendar/raw/branch/dev/updates.xml</updateserver>
</extension>
<extension>
<name>MokoJoomOpenGraph</name>
<element>pkg_mokoog</element>
<type>package</type>
<description>Open Graph, Twitter Card, and social sharing meta tags for articles, categories, and pages.</description>
<icon>icon-share-alt</icon>
<category>Components</category>
<article>https://mokoconsulting.tech/support/products/mokojoomopengraph</article>
<updateserver>https://git.mokoconsulting.tech/MokoConsulting/MokoJoomOpenGraph/raw/branch/dev/updates.xml</updateserver>
</extension>
</catalog>
@@ -96,6 +96,19 @@ class DisplayController extends BaseController
$this->jsonResponse($this->getModel('Dashboard')->clearCache());
}
public function clearTemp()
{
Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
if (!$this->checkAcl('mokowaas.cache'))
{
$this->jsonForbidden();
return;
}
$this->jsonResponse($this->getModel('Dashboard')->clearTemp());
}
// ==================================================================
// Extensions
// ==================================================================
@@ -24,8 +24,8 @@ class DashboardModel extends BaseDatabaseModel
'mokowaas' => [
'icon' => 'icon-shield-alt',
'category' => 'core',
'label' => 'Core — Branding & Identity',
'description' => 'White-label branding, master user enforcement, emergency access, and plugin protection.',
'label' => 'Core',
'description' => 'Heartbeat, health monitoring, site aliases, extension coordination, and download key preservation.',
'protected' => true,
'configure_only' => false,
],
@@ -212,6 +212,54 @@ class DashboardModel extends BaseDatabaseModel
];
}
/**
* Get installed MokoWaaS component and modules with versions.
*
* @return array Array of extension objects with name, element, type, version.
*/
public function getMokoExtensions(): array
{
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select([
$db->quoteName('element'),
$db->quoteName('name'),
$db->quoteName('type'),
$db->quoteName('enabled'),
$db->quoteName('manifest_cache'),
])
->from($db->quoteName('#__extensions'))
->where('('
// The component
. '(' . $db->quoteName('type') . ' = ' . $db->quote('component')
. ' AND ' . $db->quoteName('element') . ' = ' . $db->quote('com_mokowaas') . ')'
// Admin modules
. ' OR (' . $db->quoteName('type') . ' = ' . $db->quote('module')
. ' AND ' . $db->quoteName('element') . ' LIKE ' . $db->quote('mod_mokowaas%') . ')'
. ')')
->order($db->quoteName('type') . ' ASC, ' . $db->quoteName('element') . ' ASC');
$db->setQuery($query);
$rows = $db->loadObjectList() ?: [];
$extensions = [];
foreach ($rows as $row)
{
$manifest = json_decode($row->manifest_cache ?? '{}');
$extensions[] = (object) [
'element' => $row->element,
'name' => $manifest->name ?? $row->name,
'type' => $row->type,
'version' => $manifest->version ?? '',
'enabled' => (int) $row->enabled,
];
}
return $extensions;
}
/**
* Toggle a plugin's enabled state.
*
@@ -294,6 +342,62 @@ class DashboardModel extends BaseDatabaseModel
}
}
/**
* Clear the Joomla tmp directory.
*
* Removes all files and subdirectories from the configured tmp_path,
* preserving the directory itself and any .htaccess / web.config files.
*
* @return array Result with success and message keys.
*/
public function clearTemp(): array
{
try
{
$tmpPath = Factory::getApplication()->get('tmp_path', JPATH_ROOT . '/tmp');
if (!is_dir($tmpPath))
{
return ['success' => false, 'message' => 'Temp directory does not exist: ' . $tmpPath];
}
$count = 0;
$protected = ['.htaccess', 'web.config', 'index.html', '.gitkeep'];
$items = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($tmpPath, \RecursiveDirectoryIterator::SKIP_DOTS),
\RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($items as $item)
{
$basename = $item->getBasename();
// Skip protected files in the root tmp directory
if ($item->getPath() === $tmpPath && \in_array($basename, $protected, true))
{
continue;
}
if ($item->isDir())
{
@rmdir($item->getPathname());
}
else
{
@unlink($item->getPathname());
$count++;
}
}
return ['success' => true, 'message' => sprintf('Temp directory cleaned (%d files removed).', $count)];
}
catch (\Throwable $e)
{
return ['success' => false, 'message' => 'Temp clear failed: ' . $e->getMessage()];
}
}
/**
* Auto-generate dashboard metadata for plugins not in the static map.
*/
@@ -0,0 +1,321 @@
<?php
/**
* @package MokoWaaS
* @subpackage com_mokowaas
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
*/
namespace Moko\Component\MokoWaaS\Administrator\Model;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\MVC\Model\BaseDatabaseModel;
/**
* Extension catalog model — reads catalog.xml, fetches each extension's
* updates.xml to resolve latest version and download URL, and checks
* local install status.
*
* @since 02.32.00
*/
class ExtensionsModel extends BaseDatabaseModel
{
/**
* Parsed catalog entries (cached per request).
*
* @var array|null
*/
private ?array $catalogCache = null;
/**
* Get the full catalog with install status and release info.
*
* @return array Array of catalog entry objects
*/
public function getCatalog(): array
{
$catalog = $this->loadCatalog();
$installed = $this->getInstalledVersions($catalog);
$packages = [];
foreach ($catalog as $entry)
{
$release = $this->fetchFromUpdateServer($entry['updateserver'] ?? '');
$localVersion = $installed[$entry['element']] ?? null;
$remoteVersion = $release['version'] ?? '';
$downloadUrl = $release['download_url'] ?? '';
$status = 'not_installed';
if ($localVersion !== null)
{
$status = 'installed';
if ($remoteVersion !== '' && version_compare($remoteVersion, $localVersion, '>'))
{
$status = 'update_available';
}
}
$extensionId = $this->getExtensionId($entry['element']);
$packages[] = (object) [
'label' => $entry['name'],
'description' => $entry['description'],
'element' => $entry['element'],
'type' => $entry['type'],
'icon' => $entry['icon'],
'category' => $entry['category'],
'local_version' => $localVersion ?? '',
'remote_version' => $remoteVersion,
'download_url' => $downloadUrl,
'status' => $status,
'article_url' => $entry['article'] ?? '',
'protected' => ($entry['protected'] ?? 'false') === 'true',
'extension_id' => $extensionId,
];
}
return $packages;
}
/**
* Install an extension from a remote ZIP URL.
*
* @param string $url The download URL
*
* @return array Result with success, message, and extension info
*/
public function installFromUrl(string $url): array
{
$tmpPath = Factory::getConfig()->get('tmp_path', JPATH_ROOT . '/tmp');
$tmpFile = $tmpPath . '/mokowaas_install_' . md5($url) . '.zip';
try
{
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 120);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$data = curl_exec($ch);
$code = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if ($error || $code !== 200 || empty($data))
{
return ['success' => false, 'message' => 'Download failed: ' . ($error ?: "HTTP {$code}")];
}
file_put_contents($tmpFile, $data);
$installer = new \Joomla\CMS\Installer\Installer();
$result = $installer->install($tmpFile);
@unlink($tmpFile);
if (!$result)
{
return ['success' => false, 'message' => 'Installation failed.'];
}
return [
'success' => true,
'message' => 'Installed successfully.',
];
}
catch (\Throwable $e)
{
@unlink($tmpFile);
return ['success' => false, 'message' => 'Error: ' . $e->getMessage()];
}
}
/**
* Load and parse the catalog.xml file.
*
* @return array Array of associative arrays, one per extension
*/
private function loadCatalog(): array
{
if ($this->catalogCache !== null)
{
return $this->catalogCache;
}
$catalogFile = JPATH_ADMINISTRATOR . '/components/com_mokowaas/catalog.xml';
if (!file_exists($catalogFile))
{
$this->catalogCache = [];
return [];
}
$xml = @simplexml_load_file($catalogFile);
if (!$xml)
{
$this->catalogCache = [];
return [];
}
$entries = [];
foreach ($xml->extension as $ext)
{
$entries[] = [
'name' => (string) $ext->name,
'element' => (string) $ext->element,
'type' => (string) $ext->type,
'description' => (string) $ext->description,
'icon' => (string) $ext->icon,
'category' => (string) $ext->category,
'article' => (string) $ext->article,
'protected' => (string) $ext->protected,
'updateserver' => (string) $ext->updateserver,
];
}
$this->catalogCache = $entries;
return $entries;
}
/**
* Fetch the latest version and download URL from an extension's updates.xml.
*
* Parses the standard Joomla update server XML format and returns
* the highest version entry with its download URL.
*
* @param string $updateServerUrl URL to the updates.xml file
*
* @return array [version, download_url] or empty array
*/
private function fetchFromUpdateServer(string $updateServerUrl): array
{
if (empty($updateServerUrl))
{
return [];
}
$ch = curl_init($updateServerUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$response = curl_exec($ch);
$code = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($code !== 200 || empty($response))
{
return [];
}
$xml = @simplexml_load_string($response);
if (!$xml)
{
return [];
}
// Find the highest version entry
$bestVersion = '0.0.0';
$downloadUrl = '';
foreach ($xml->update as $update)
{
$ver = (string) ($update->version ?? '');
if ($ver === '' || version_compare($ver, $bestVersion, '<='))
{
continue;
}
$bestVersion = $ver;
// Get download URL from <downloads><downloadurl>
if (isset($update->downloads->downloadurl))
{
$downloadUrl = (string) $update->downloads->downloadurl;
}
}
if ($bestVersion === '0.0.0')
{
return [];
}
return [
'version' => $bestVersion,
'download_url' => $downloadUrl,
];
}
/**
* Get installed versions of catalog extensions.
*
* @param array $catalog The parsed catalog entries
*
* @return array element => version
*/
private function getInstalledVersions(array $catalog): array
{
if (empty($catalog))
{
return [];
}
$db = $this->getDatabase();
$elements = [];
foreach ($catalog as $entry)
{
$elements[] = $db->quote($entry['element']);
}
$query = $db->getQuery(true)
->select([$db->quoteName('element'), $db->quoteName('manifest_cache')])
->from($db->quoteName('#__extensions'))
->where($db->quoteName('element') . ' IN (' . implode(',', $elements) . ')');
$db->setQuery($query);
$rows = $db->loadObjectList() ?: [];
$versions = [];
foreach ($rows as $row)
{
$mc = json_decode($row->manifest_cache ?? '{}');
$versions[$row->element] = $mc->version ?? '0.0.0';
}
return $versions;
}
/**
* Get the extension_id for an element (for uninstall links).
*
* @param string $element Extension element name
*
* @return int
*/
private function getExtensionId(string $element): int
{
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select($db->quoteName('extension_id'))
->from($db->quoteName('#__extensions'))
->where($db->quoteName('element') . ' = ' . $db->quote($element))
->setLimit(1);
$db->setQuery($query);
return (int) $db->loadResult();
}
}
@@ -25,6 +25,7 @@ class HtmlView extends BaseHtmlView
protected $wafBlocks = [];
protected $wafChartData = [];
protected $loginChartData = [];
protected $mokoExtensions = [];
public function display($tpl = null)
{
@@ -38,6 +39,7 @@ class HtmlView extends BaseHtmlView
$this->wafBlocks = $model->getRecentWafBlocks(5);
$this->wafChartData = $model->getWafBlocksByDay(14);
$this->loginChartData = $model->getLoginsByDay(14);
$this->mokoExtensions = $model->getMokoExtensions();
// Check for importable Akeeba data
try
@@ -19,6 +19,7 @@ $siteInfo = $this->siteInfo;
$plugins = $this->plugins;
$recentLogins = $this->recentLogins;
$pendingUpdates = $this->pendingUpdates;
$mokoExts = $this->mokoExtensions;
$adminToolsAvail = $this->adminToolsAvailable ?? null;
$atsAvail = $this->atsAvailable ?? null;
$checkedOut = $this->checkedOutItems;
@@ -72,6 +73,31 @@ $categoryOrder = ['core', 'security', 'monitoring', 'content', 'tools', 'api'];
</div>
</div>
<?php if (!empty($mokoExts)): ?>
<!-- Moko Component & Module Versions -->
<div class="d-flex flex-wrap gap-2 mb-4">
<?php
$extIcons = [
'com_mokowaas' => 'icon-cogs',
'mod_mokowaas_cpanel' => 'icon-tachometer-alt',
'mod_mokowaas_menu' => 'icon-bars',
'mod_mokowaas_cache' => 'icon-bolt',
'mod_mokowaas_categories' => 'icon-folder',
];
foreach ($mokoExts as $ext):
$icon = $extIcons[$ext->element] ?? 'icon-puzzle-piece';
$label = str_replace(['mod_mokowaas_', 'com_mokowaas'], ['', 'Component'], $ext->element);
$label = ucfirst($label ?: 'Component');
?>
<div class="d-flex align-items-center gap-2 px-3 py-2 rounded border bg-white" style="font-size:0.85rem;">
<span class="<?php echo $icon; ?>" aria-hidden="true" style="color:#1a2744;"></span>
<span><?php echo $this->escape($label); ?></span>
<span class="badge bg-light text-dark"><?php echo $this->escape($ext->version); ?></span>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<?php if ($adminToolsAvail || $atsAvail): ?>
<!-- Akeeba Import Banner -->
<div class="alert alert-info d-flex flex-wrap align-items-center gap-3 mb-4">
@@ -25,8 +25,9 @@ foreach ($packages as $pkg)
}
$statusBadge = [
'installed' => ['bg-success', 'Installed'],
'not_installed' => ['bg-secondary', 'Not Installed'],
'installed' => ['bg-success', 'Installed'],
'update_available' => ['bg-warning text-dark', 'Update Available'],
'not_installed' => ['bg-secondary', 'Not Installed'],
];
?>
@@ -63,6 +64,9 @@ $statusBadge = [
<div class="small text-muted">
<?php if ($pkg->local_version): ?>
v<?php echo htmlspecialchars($pkg->local_version); ?>
<?php if ($pkg->remote_version && $pkg->status === 'update_available'): ?>
&rarr; <?php echo htmlspecialchars($pkg->remote_version); ?>
<?php endif; ?>
<?php elseif ($pkg->remote_version): ?>
Latest: <?php echo htmlspecialchars($pkg->remote_version); ?>
<?php endif; ?>
@@ -73,7 +77,16 @@ $statusBadge = [
<span class="icon-book" aria-hidden="true"></span>
</a>
<?php endif; ?>
<?php if ($pkg->download_url && $pkg->status === 'not_installed'): ?>
<?php if ($pkg->download_url && $pkg->status === 'update_available'): ?>
<button type="button" class="btn btn-sm btn-warning mokowaas-install-btn"
data-url="<?php echo Route::_('index.php?option=com_mokowaas&task=display.installExtension&format=json'); ?>"
data-download="<?php echo htmlspecialchars($pkg->download_url); ?>"
data-token="<?php echo $token; ?>"
data-label="<?php echo htmlspecialchars($pkg->label); ?>">
<span class="icon-refresh" aria-hidden="true"></span>
Update to <?php echo htmlspecialchars($pkg->remote_version); ?>
</button>
<?php elseif ($pkg->download_url && $pkg->status === 'not_installed'): ?>
<button type="button" class="btn btn-sm btn-primary mokowaas-install-btn"
data-url="<?php echo Route::_('index.php?option=com_mokowaas&task=display.installExtension&format=json'); ?>"
data-download="<?php echo htmlspecialchars($pkg->download_url); ?>"
@@ -8,7 +8,7 @@
DEFGROUP: Joomla.Component
INGROUP: MokoWaaS
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS
VERSION: 02.34.10
VERSION: 02.34.16
PATH: /mokowaas.xml
BRIEF: Component manifest for MokoWaaS admin dashboard and REST API
-->
@@ -20,7 +20,7 @@
<license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.34.10</version>
<version>02.34.15</version>
<description>MokoWaaS admin dashboard and REST API. Provides a control panel for managing MokoWaaS feature plugins, site health monitoring, and remote management endpoints.</description>
<namespace path="src">Moko\Component\MokoWaaS</namespace>
@@ -43,6 +43,7 @@
</submenu>
<files folder="admin">
<filename>access.xml</filename>
<filename>catalog.xml</filename>
<filename>config.xml</filename>
<folder>language</folder>
<folder>services</folder>
@@ -0,0 +1,4 @@
MOD_MOKOWAAS_CACHE="MokoWaaS Cache Cleaner"
MOD_MOKOWAAS_CACHE_DESC="One-click cache and temp cleaner in the admin status bar."
MOD_MOKOWAAS_CACHE_CLEAR_ALL="Clear All Cache"
MOD_MOKOWAAS_CACHE_CLEAR_TEMP="Clear Temp"
@@ -7,7 +7,7 @@
<license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.34.10</version>
<version>02.34.15</version>
<description>MOD_MOKOWAAS_CACHE_DESC</description>
<namespace path="src">Moko\Module\MokoWaaSCache</namespace>
@@ -0,0 +1,89 @@
<?php
/**
* MokoWaaS Cache & Temp Cleaner — status bar split button
*
* Displays "Clear: Cache | Temp" as a single header item with two
* clickable halves. Uses native Atum header-item markup.
*/
defined('_JEXEC') or die;
use Joomla\CMS\Session\Session;
$token = Session::getFormToken();
$cacheUrl = 'index.php?option=com_mokowaas&task=clearCache&format=json';
$tempUrl = 'index.php?option=com_mokowaas&task=clearTemp&format=json';
?>
<style>
.mokowaas-cleaner { display:flex; align-items:center; gap:0; padding:0 0.25rem; }
.mokowaas-cleaner-label { font-size:0.8rem; color:var(--template-text-dark,#495057); white-space:nowrap; padding-inline-end:0.35rem; }
.mokowaas-cleaner-btn { cursor:pointer; padding:0.2rem 0.5rem; font-size:0.8rem; border-radius:3px; text-decoration:none; color:var(--template-text-dark,#495057); transition:background 0.15s; white-space:nowrap; }
.mokowaas-cleaner-btn:hover { background:rgba(0,0,0,0.08); color:var(--template-text-dark,#212529); text-decoration:none; }
.mokowaas-cleaner-sep { color:var(--template-text-dark,#adb5bd); padding:0 0.1rem; font-size:0.8rem; }
</style>
<div class="header-item-content mokowaas-cleaner">
<span class="mokowaas-cleaner-label">Clear:</span>
<a href="#" class="mokowaas-cleaner-btn" id="mokowaas-clear-cache" title="Clear all Joomla cache">
<span class="icon-bolt" aria-hidden="true" id="mokowaas-cache-icon"></span> Cache
</a>
<span class="mokowaas-cleaner-sep">|</span>
<a href="#" class="mokowaas-cleaner-btn" id="mokowaas-clear-temp" title="Clear temp directory">
<span class="icon-trash" aria-hidden="true" id="mokowaas-temp-icon"></span> Temp
</a>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
function setupCleaner(btnId, iconId, url, token) {
var btn = document.getElementById(btnId);
var icon = document.getElementById(iconId);
if (!btn || !icon) return;
var origClass = icon.className;
btn.addEventListener('click', function(e) {
e.preventDefault();
if (btn.dataset.busy) return;
btn.dataset.busy = '1';
icon.className = 'icon-spinner icon-spin';
var formData = new FormData();
formData.append(token, '1');
fetch(url, {
method: 'POST',
headers: {'X-Requested-With': 'XMLHttpRequest'},
body: formData
})
.then(function(r) { return r.json(); })
.then(function(data) {
if (data.success) {
icon.className = 'icon-check';
icon.style.color = '#198754';
} else {
icon.className = 'icon-times';
icon.style.color = '#dc3545';
}
setTimeout(function() {
icon.className = origClass;
icon.style.color = '';
delete btn.dataset.busy;
}, 2000);
})
.catch(function() {
icon.className = 'icon-times';
icon.style.color = '#dc3545';
setTimeout(function() {
icon.className = origClass;
icon.style.color = '';
delete btn.dataset.busy;
}, 2000);
});
});
}
setupCleaner('mokowaas-clear-cache', 'mokowaas-cache-icon', '<?php echo $cacheUrl; ?>', '<?php echo $token; ?>');
setupCleaner('mokowaas-clear-temp', 'mokowaas-temp-icon', '<?php echo $tempUrl; ?>', '<?php echo $token; ?>');
});
</script>
@@ -0,0 +1,24 @@
MOD_MOKOWAAS_CATEGORIES="MokoWaaS Categories"
MOD_MOKOWAAS_CATEGORIES_DESC="Auto-discovers article categories and renders them as a collapsible tree menu. Ideal for knowledge base and help sections."
MOD_MOKOWAAS_CATEGORIES_ROOT_LABEL="Root Category"
MOD_MOKOWAAS_CATEGORIES_ROOT_DESC="Select a parent category. Only its children (and their subcategories) will be displayed. Leave as All to show the entire category tree."
MOD_MOKOWAAS_CATEGORIES_ALL_CATEGORIES="- All Categories -"
MOD_MOKOWAAS_CATEGORIES_DEPTH_LABEL="Maximum Depth"
MOD_MOKOWAAS_CATEGORIES_DEPTH_DESC="How many levels deep to display. 1 shows only top-level categories, 2 adds one level of subcategories, etc."
MOD_MOKOWAAS_CATEGORIES_COUNT_LABEL="Show Article Count"
MOD_MOKOWAAS_CATEGORIES_COUNT_DESC="Display the number of published articles next to each category name."
MOD_MOKOWAAS_CATEGORIES_EMPTY_LABEL="Show Empty Categories"
MOD_MOKOWAAS_CATEGORIES_EMPTY_DESC="Display categories that have no published articles. Only applies when Show Article Count is enabled."
MOD_MOKOWAAS_CATEGORIES_MENUITEM_LABEL="Target Menu Item"
MOD_MOKOWAAS_CATEGORIES_MENUITEM_DESC="The menu item to use as the base for category links. This sets the Itemid parameter for proper template and menu highlighting."
MOD_MOKOWAAS_CATEGORIES_ORDER_LABEL="Category Ordering"
MOD_MOKOWAAS_CATEGORIES_ORDER_DESC="How to sort categories within each level."
MOD_MOKOWAAS_CATEGORIES_ORDER_TREE="Tree Order (default)"
MOD_MOKOWAAS_CATEGORIES_ORDER_TITLE="Alphabetical"
MOD_MOKOWAAS_CATEGORIES_ORDER_CREATED="Date Created"
@@ -0,0 +1,2 @@
MOD_MOKOWAAS_CATEGORIES="MokoWaaS Categories"
MOD_MOKOWAAS_CATEGORIES_DESC="Auto-discovers article categories and renders them as a collapsible tree menu. Ideal for knowledge base and help sections."
@@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
<extension type="module" client="administrator" method="upgrade">
<name>mod_mokowaas_categories</name>
<author>Moko Consulting</author>
<creationDate>2026-06-06</creationDate>
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
<license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>02.34.15</version>
<description>MOD_MOKOWAAS_CATEGORIES_DESC</description>
<namespace path="src">Moko\Module\MokoWaaSCategories</namespace>
<files>
<folder module="mod_mokowaas_categories">services</folder>
<folder>src</folder>
<folder>tmpl</folder>
</files>
<languages folder="language">
<language tag="en-GB">en-GB/mod_mokowaas_categories.ini</language>
<language tag="en-GB">en-GB/mod_mokowaas_categories.sys.ini</language>
</languages>
<config>
<fields name="params">
<fieldset name="basic">
<field name="root_category" type="category"
extension="com_content"
label="MOD_MOKOWAAS_CATEGORIES_ROOT_LABEL"
description="MOD_MOKOWAAS_CATEGORIES_ROOT_DESC"
default="0"
show_root="true"
>
<option value="0">MOD_MOKOWAAS_CATEGORIES_ALL_CATEGORIES</option>
</field>
<field name="max_depth" type="number"
label="MOD_MOKOWAAS_CATEGORIES_DEPTH_LABEL"
description="MOD_MOKOWAAS_CATEGORIES_DEPTH_DESC"
default="3"
min="1"
max="10"
/>
<field name="show_article_count" type="radio"
label="MOD_MOKOWAAS_CATEGORIES_COUNT_LABEL"
description="MOD_MOKOWAAS_CATEGORIES_COUNT_DESC"
default="1"
class="btn-group btn-group-yesno">
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
<field name="show_empty" type="radio"
label="MOD_MOKOWAAS_CATEGORIES_EMPTY_LABEL"
description="MOD_MOKOWAAS_CATEGORIES_EMPTY_DESC"
default="0"
class="btn-group btn-group-yesno">
<option value="1">JYES</option>
<option value="0">JNO</option>
</field>
<field name="menu_item_id" type="menuitem"
label="MOD_MOKOWAAS_CATEGORIES_MENUITEM_LABEL"
description="MOD_MOKOWAAS_CATEGORIES_MENUITEM_DESC"
default=""
/>
<field name="ordering" type="list"
label="MOD_MOKOWAAS_CATEGORIES_ORDER_LABEL"
description="MOD_MOKOWAAS_CATEGORIES_ORDER_DESC"
default="lft">
<option value="lft">MOD_MOKOWAAS_CATEGORIES_ORDER_TREE</option>
<option value="title">MOD_MOKOWAAS_CATEGORIES_ORDER_TITLE</option>
<option value="created_time">MOD_MOKOWAAS_CATEGORIES_ORDER_CREATED</option>
</field>
</fieldset>
</fields>
</config>
</extension>
@@ -0,0 +1,25 @@
<?php
/**
* @package MokoWaaS
* @subpackage mod_mokowaas_categories
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
*/
defined('_JEXEC') or die;
use Joomla\CMS\Extension\Service\Provider\HelperFactory;
use Joomla\CMS\Extension\Service\Provider\Module;
use Joomla\CMS\Extension\Service\Provider\ModuleDispatcherFactory;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
return new class implements ServiceProviderInterface
{
public function register(Container $container): void
{
$container->registerServiceProvider(new ModuleDispatcherFactory('\\Moko\\Module\\MokoWaaSCategories'));
$container->registerServiceProvider(new HelperFactory('\\Moko\\Module\\MokoWaaSCategories\\Administrator\\Helper'));
$container->registerServiceProvider(new Module());
}
};
@@ -0,0 +1,32 @@
<?php
/**
* @package MokoWaaS
* @subpackage mod_mokowaas_categories
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
*/
namespace Moko\Module\MokoWaaSCategories\Administrator\Dispatcher;
defined('_JEXEC') or die;
use Joomla\CMS\Dispatcher\AbstractModuleDispatcher;
use Joomla\CMS\Helper\HelperFactoryAwareInterface;
use Joomla\CMS\Helper\HelperFactoryAwareTrait;
class Dispatcher extends AbstractModuleDispatcher implements HelperFactoryAwareInterface
{
use HelperFactoryAwareTrait;
protected function getLayoutData()
{
$data = parent::getLayoutData();
$params = $data['params'];
$helper = $this->getHelperFactory()->getHelper('CategoriesHelper');
$data['categories'] = $helper->getCategories($params);
return $data;
}
}
@@ -0,0 +1,148 @@
<?php
/**
* @package MokoWaaS
* @subpackage mod_mokowaas_categories
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
*/
namespace Moko\Module\MokoWaaSCategories\Administrator\Helper;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\Database\DatabaseInterface;
use Joomla\Registry\Registry;
class CategoriesHelper
{
/**
* Get category tree from a root category with configurable depth.
*
* @param Registry $params Module parameters
*
* @return array Nested array of category objects
*/
public function getCategories(Registry $params): array
{
$db = Factory::getContainer()->get(DatabaseInterface::class);
$rootId = (int) $params->get('root_category', 0);
$maxDepth = (int) $params->get('max_depth', 3);
$showEmpty = (int) $params->get('show_empty', 0);
$showCount = (int) $params->get('show_article_count', 1);
$ordering = $params->get('ordering', 'lft');
$user = Factory::getApplication()->getIdentity();
$accessLevels = $user->getAuthorisedViewLevels();
// Build base query
$query = $db->getQuery(true)
->select([
$db->quoteName('c.id'),
$db->quoteName('c.title'),
$db->quoteName('c.alias'),
$db->quoteName('c.parent_id'),
$db->quoteName('c.level'),
$db->quoteName('c.lft'),
$db->quoteName('c.rgt'),
$db->quoteName('c.description'),
])
->from($db->quoteName('#__categories', 'c'))
->where($db->quoteName('c.extension') . ' = ' . $db->quote('com_content'))
->where($db->quoteName('c.published') . ' = 1')
->whereIn($db->quoteName('c.access'), $accessLevels);
// If a root category is set, constrain to its subtree
if ($rootId > 0)
{
$rootQuery = $db->getQuery(true)
->select([$db->quoteName('lft'), $db->quoteName('rgt'), $db->quoteName('level')])
->from($db->quoteName('#__categories'))
->where($db->quoteName('id') . ' = ' . $rootId);
$db->setQuery($rootQuery);
$root = $db->loadObject();
if (!$root)
{
return [];
}
$query->where($db->quoteName('c.lft') . ' > ' . (int) $root->lft)
->where($db->quoteName('c.rgt') . ' < ' . (int) $root->rgt)
->where($db->quoteName('c.level') . ' <= ' . ((int) $root->level + $maxDepth));
}
else
{
// No root — show from level 1 (skip the virtual root)
$query->where($db->quoteName('c.level') . ' >= 1')
->where($db->quoteName('c.level') . ' <= ' . $maxDepth);
}
// Article count subquery
if ($showCount)
{
$countSub = $db->getQuery(true)
->select('COUNT(*)')
->from($db->quoteName('#__content', 'a'))
->where($db->quoteName('a.catid') . ' = ' . $db->quoteName('c.id'))
->where($db->quoteName('a.state') . ' = 1');
$query->select('(' . $countSub . ') AS ' . $db->quoteName('article_count'));
}
// Ordering
$validOrders = ['lft', 'title', 'created_time'];
$orderCol = \in_array($ordering, $validOrders, true) ? $ordering : 'lft';
$query->order($db->quoteName('c.' . $orderCol) . ' ASC');
$db->setQuery($query);
$categories = $db->loadObjectList() ?: [];
// Filter empty categories if configured
if (!$showEmpty && $showCount)
{
$categories = array_filter($categories, function ($cat) {
return (int) $cat->article_count > 0;
});
$categories = array_values($categories);
}
// Build nested tree
return $this->buildTree($categories, $rootId);
}
/**
* Build a nested tree from a flat list of categories.
*
* @param array $categories Flat list of category objects
* @param int $rootId Root category ID (0 for all)
*
* @return array Nested array with 'children' key on each node
*/
private function buildTree(array $categories, int $rootId): array
{
$map = [];
$tree = [];
foreach ($categories as $cat)
{
$cat->children = [];
$map[$cat->id] = $cat;
}
foreach ($categories as $cat)
{
$parentId = (int) $cat->parent_id;
if (isset($map[$parentId]))
{
$map[$parentId]->children[] = $cat;
}
else
{
$tree[] = $cat;
}
}
return $tree;
}
}
@@ -0,0 +1,138 @@
<?php
/**
* @package MokoWaaS
* @subpackage mod_mokowaas_categories
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
*/
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Router\Route;
use Joomla\CMS\Uri\Uri;
$categories = $categories ?? [];
$showCount = (int) ($params->get('show_article_count', 1));
$menuItemId = (int) $params->get('menu_item_id', 0);
if (empty($categories))
{
return;
}
// Detect active category from current URL
$app = \Joomla\CMS\Factory::getApplication();
$activeCatId = (int) $app->input->getInt('id', 0);
$currentView = $app->input->getCmd('view', '');
$isCatView = \in_array($currentView, ['category', 'categories'], true);
/**
* Build the link for a category.
*/
$buildLink = function (object $cat) use ($menuItemId): string {
$link = 'index.php?option=com_content&view=category&id=' . (int) $cat->id;
if ($menuItemId)
{
$link .= '&Itemid=' . $menuItemId;
}
return Route::_($link);
};
/**
* Check if a category or any of its descendants is the active category.
*/
$isActiveOrAncestor = function (object $cat) use ($activeCatId, $isCatView, &$isActiveOrAncestor): bool {
if (!$isCatView || !$activeCatId)
{
return false;
}
if ((int) $cat->id === $activeCatId)
{
return true;
}
foreach ($cat->children as $child)
{
if ($isActiveOrAncestor($child))
{
return true;
}
}
return false;
};
/**
* Render a category list recursively.
*/
$renderTree = function (array $categories, int $depth = 1) use (
&$renderTree, $buildLink, $isActiveOrAncestor, $showCount, $activeCatId, $isCatView
): void {
foreach ($categories as $cat):
$hasChildren = !empty($cat->children);
$isActive = $isCatView && (int) $cat->id === $activeCatId;
$isAncestor = $hasChildren && $isActiveOrAncestor($cat);
$liClass = 'item mokowaas-cat-item mokowaas-cat-level-' . $depth;
if ($isActive)
{
$liClass .= ' mm-active';
}
if ($hasChildren)
{
$liClass .= ' parent';
}
$aClass = ($hasChildren ? 'has-arrow' : 'no-dropdown');
if ($isActive)
{
$aClass .= ' mm-active';
}
$collapseClass = 'collapse-cat-level-' . ($depth + 1) . ' mm-collapse';
if ($isAncestor || $isActive)
{
$collapseClass .= ' mm-show';
}
$count = isset($cat->article_count) ? (int) $cat->article_count : 0;
?>
<li class="<?php echo $liClass; ?>">
<a class="<?php echo $aClass; ?>"
href="<?php echo $hasChildren ? '#' : $buildLink($cat); ?>"
<?php echo $isActive ? ' aria-current="page"' : ''; ?>>
<span class="icon-folder" aria-hidden="true"
style="display:inline-block!important;width:1.25em;text-align:center;margin-inline-end:0.4em;"></span>
<span class="sidebar-item-title"><?php echo htmlspecialchars($cat->title); ?></span>
<?php if ($showCount): ?>
<span class="badge bg-secondary ms-auto"><?php echo $count; ?></span>
<?php endif; ?>
</a>
<?php if ($hasChildren): ?>
<ul class="<?php echo $collapseClass; ?>" style="padding-inline-start:0.75rem;">
<?php $renderTree($cat->children, $depth + 1); ?>
</ul>
<?php endif; ?>
</li>
<?php endforeach;
};
?>
<style>
.sidebar-wrapper .mokowaas-cat-item > a { padding-inline-start: 2rem; }
.sidebar-wrapper .mokowaas-cat-item > a .badge { font-size: 0.65em; padding: 0.15em 0.45em; }
.sidebar-wrapper .mokowaas-cat-level-2 > a { padding-inline-start: 2.5rem; }
.sidebar-wrapper .mokowaas-cat-level-3 > a { padding-inline-start: 3rem; }
.sidebar-wrapper .mokowaas-cat-level-4 > a { padding-inline-start: 3.5rem; }
</style>
<ul class="nav flex-column">
<?php $renderTree($categories); ?>
</ul>

Some files were not shown because too many files have changed in this diff Show More