Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d353b1ee36 | |||
| d07eb89f66 | |||
| 398fefe2fd | |||
| 5e33f94cce | |||
| e7cdc41648 | |||
| e3c15979b8 | |||
| 68ab5bdd44 | |||
| 1fe19fe5f1 | |||
| 17ef84e867 | |||
| 8d4a5b7a04 | |||
| 9fed55d5c0 | |||
| d79d5393be | |||
| 3cfe653b18 | |||
| 882a4bfb5c | |||
| d792b7ff0c | |||
| 68ffffe2af | |||
| 0fb82306bb | |||
| b170894228 | |||
| 082fa0798c |
+3
-3
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
@@ -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`
|
||||
-->
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
-->
|
||||
|
||||
|
||||
@@ -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: |
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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>
|
||||
+13
@@ -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
|
||||
// ==================================================================
|
||||
+106
-2
@@ -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();
|
||||
}
|
||||
}
|
||||
+2
@@ -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
|
||||
+26
@@ -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">
|
||||
+16
-3
@@ -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'): ?>
|
||||
→ <?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"
|
||||
+1
-1
@@ -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
Reference in New Issue
Block a user