Compare commits
98 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6cd690b737 | |||
| 8fea27e8b6 | |||
| cbea5752d1 | |||
| 34e789298b | |||
| 62c49eab5a | |||
| 2f8c81792d | |||
| 7b5a83c71a | |||
| cff932dcbb | |||
| 8c9e3e6d44 | |||
| 74e535c929 | |||
| efdaaf479a | |||
| f2b0c2e420 | |||
| 40e6a1f086 | |||
| 7b7dc4a553 | |||
| edfd9fa326 | |||
| 2de4d08430 | |||
| 872f55f376 | |||
| ee7a42e14b | |||
| 9f434aefdc | |||
| 1000f028d2 | |||
| b048b47e7c | |||
| 6e0d5387cf | |||
| 76bbf7ad85 | |||
| b1c2b3c92a | |||
| a5dc00e056 | |||
| c6475ff29a | |||
| b7a52cc6a4 | |||
| ffd98a19d9 | |||
| 34469609dd | |||
| d766b0568a | |||
| da4b544da7 | |||
| cfea9fac99 | |||
| 7d6d654d6d | |||
| dca452e49d | |||
| f68a477c56 | |||
| b827b3382a | |||
| 42841f7335 | |||
| 8cfe596754 | |||
| 0f354422aa | |||
| 86aae39be1 | |||
| b5e932d78b | |||
| b0e15b8747 | |||
| 8d232e8c7b | |||
| 485322ba08 | |||
| c738eb6669 | |||
| e0f98dc5e2 | |||
| ede07c6675 | |||
| fa67ffaa00 | |||
| 1fe8422fc0 | |||
| f40998fc30 | |||
| 6e216de0dc | |||
| 86e40fb978 | |||
| b2f52c191b | |||
| fb74a255d3 | |||
| 8e1040efee | |||
| c203e970b9 | |||
| 7e489b072a | |||
| bf3c986113 | |||
| 955c08a387 | |||
| 52dbefbb14 | |||
| 379ca36613 | |||
| 35ca1af6b8 | |||
| e9ec664f03 | |||
| 6929c636b9 | |||
| 20797d663f | |||
| 11d5fd2019 | |||
| 1fa965dddb | |||
| 5a3ec7d9b1 | |||
| aef5ca43f6 | |||
| 0631d80fa1 | |||
| e84c10b14f | |||
| 87e543ef1c | |||
| 32236ad7ff | |||
| d6e462f3b7 | |||
| d470669634 | |||
| feaccf0758 | |||
| 9542c88ba4 | |||
| 7e3d366043 | |||
| 4c5919f209 | |||
| 0b3e699f29 | |||
| 406242fb7d | |||
| e6cb6eb531 | |||
| bdceb4256f | |||
| 81591477b2 | |||
| cc907a5aa2 | |||
| 5360c641e9 | |||
| d7a4066261 | |||
| 478eb262b9 | |||
| ea934ba04b | |||
| a92c1ce772 | |||
| 0e55a546ff | |||
| 14e94518ba | |||
| b32d91c446 | |||
| 03e0b6d13b | |||
| 21cb335727 | |||
| d2700f96fe | |||
| 7f7cdb4cc9 | |||
| b1b21e79d8 |
@@ -203,3 +203,5 @@ venv/
|
||||
*.coverage
|
||||
hypothesis/
|
||||
|
||||
profile.ps1
|
||||
TODO.md
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
</governance>
|
||||
<build>
|
||||
<language>PHP</language>
|
||||
<package-type>plugin</package-type>
|
||||
<package-type>package</package-type>
|
||||
<entry-point>src/</entry-point>
|
||||
</build>
|
||||
</moko-platform>
|
||||
|
||||
@@ -339,6 +339,8 @@ jobs:
|
||||
[ -z "$NOTES" ] && NOTES="Release ${VERSION}"
|
||||
|
||||
# Build release name: "Pretty Name VERSION (type_element-VERSION)"
|
||||
# Strip existing type prefix to prevent duplication
|
||||
EXT_ELEMENT=$(echo "$EXT_ELEMENT" | sed -E 's/^(pkg_|com_|mod_|plg_[a-z]+_|tpl_|lib_)//')
|
||||
TYPE_PREFIX=""
|
||||
case "${EXT_TYPE}" in
|
||||
plugin) TYPE_PREFIX="plg_${EXT_FOLDER}_" ;;
|
||||
@@ -409,6 +411,13 @@ jobs:
|
||||
# ZIP name: type_folder_element-VERSION (e.g. plg_system_mokojgdpc-01.01.00.zip)
|
||||
EXT_TYPE=$(sed -n 's/.*<extension[^>]*type="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
|
||||
EXT_FOLDER=$(sed -n 's/.*<extension[^>]*group="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
|
||||
# For packages, prefer <packagename> over filename-derived element
|
||||
if [ "$EXT_TYPE" = "package" ]; then
|
||||
PKG_NAME=$(sed -n 's/.*<packagename>\([^<]*\)<\/packagename>.*/\1/p' "$MANIFEST" 2>/dev/null | head -1)
|
||||
[ -n "$PKG_NAME" ] && EXT_ELEMENT="$PKG_NAME"
|
||||
fi
|
||||
# Strip existing type prefix to prevent duplication (e.g. pkg_mokowaas → mokowaas)
|
||||
EXT_ELEMENT=$(echo "$EXT_ELEMENT" | sed -E 's/^(pkg_|com_|mod_|plg_[a-z]+_|tpl_|lib_)//')
|
||||
TYPE_PREFIX=""
|
||||
case "${EXT_TYPE}" in
|
||||
plugin) TYPE_PREFIX="plg_${EXT_FOLDER}_" ;;
|
||||
@@ -570,6 +579,7 @@ jobs:
|
||||
EXT_FOLDER="${{ steps.updates.outputs.ext_folder }}"
|
||||
|
||||
# Build TYPE_PREFIX to match Step 8's ZIP naming
|
||||
EXT_ELEMENT=$(echo "$EXT_ELEMENT" | sed -E 's/^(pkg_|com_|mod_|plg_[a-z]+_|tpl_|lib_)//')
|
||||
TYPE_PREFIX=""
|
||||
case "${EXT_TYPE}" in
|
||||
plugin) TYPE_PREFIX="plg_${EXT_FOLDER}_" ;;
|
||||
|
||||
@@ -448,3 +448,20 @@ jobs:
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
exit $EXIT
|
||||
|
||||
pre-release:
|
||||
name: Build RC Pre-Release
|
||||
runs-on: ubuntu-latest
|
||||
needs: [lint-and-validate, test]
|
||||
if: github.event_name == 'pull_request'
|
||||
|
||||
steps:
|
||||
- name: Trigger pre-release build
|
||||
env:
|
||||
GA_TOKEN: ${{ secrets.GA_TOKEN }}
|
||||
REPO: ${{ github.repository }}
|
||||
BRANCH: ${{ github.head_ref }}
|
||||
run: |
|
||||
curl -s -X POST "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${REPO}/actions/workflows/pre-release.yml/dispatches" -H "Authorization: token ${GA_TOKEN}" -H "Content-Type: application/json" -d "{\"ref\":\"${BRANCH}\",\"inputs\":{\"stability\":\"release-candidate\"}}"
|
||||
echo "### Pre-Release" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Triggered RC build on branch \`${BRANCH}\`" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: MokoStandards.Deploy
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
|
||||
# PATH: /templates/workflows/joomla/deploy-manual.yml.template
|
||||
# VERSION: 04.07.00
|
||||
# BRIEF: Manual SFTP deploy to dev server for Joomla repos
|
||||
|
||||
name: "Universal: Deploy to Dev (Manual)"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
clear_remote:
|
||||
description: 'Delete all remote files before uploading'
|
||||
required: false
|
||||
default: 'false'
|
||||
type: boolean
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
name: SFTP Deploy to Dev
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Setup PHP
|
||||
run: |
|
||||
php -v && composer --version
|
||||
|
||||
- name: Setup MokoStandards tools
|
||||
env:
|
||||
GA_TOKEN: ${{ secrets.GA_TOKEN || secrets.GA_TOKEN || github.token }}
|
||||
MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN || secrets.GA_TOKEN || github.token }}
|
||||
MOKO_CLONE_HOST: ${{ secrets.GA_TOKEN && 'git.mokoconsulting.tech/MokoConsulting' || 'github.com/mokoconsulting-tech' }}
|
||||
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GA_TOKEN || github.token }}"}}'
|
||||
run: |
|
||||
git clone --depth 1 --branch main --quiet \
|
||||
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \
|
||||
/tmp/mokostandards-api 2>/dev/null || true
|
||||
if [ -d "/tmp/mokostandards-api" ] && [ -f "/tmp/mokostandards-api/composer.json" ]; then
|
||||
cd /tmp/mokostandards-api && composer install --no-dev --no-interaction --quiet 2>/dev/null || true
|
||||
fi
|
||||
|
||||
- name: Check FTP configuration
|
||||
id: check
|
||||
env:
|
||||
HOST: ${{ vars.DEV_FTP_HOST }}
|
||||
PATH_VAR: ${{ vars.DEV_FTP_PATH }}
|
||||
PORT: ${{ vars.DEV_FTP_PORT }}
|
||||
run: |
|
||||
if [ -z "$HOST" ] || [ -z "$PATH_VAR" ]; then
|
||||
echo "DEV_FTP_HOST or DEV_FTP_PATH not configured -- cannot deploy"
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
echo "skip=false" >> "$GITHUB_OUTPUT"
|
||||
echo "host=$HOST" >> "$GITHUB_OUTPUT"
|
||||
|
||||
REMOTE="${PATH_VAR%/}"
|
||||
echo "remote=$REMOTE" >> "$GITHUB_OUTPUT"
|
||||
|
||||
[ -z "$PORT" ] && PORT="22"
|
||||
echo "port=$PORT" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Deploy via SFTP
|
||||
if: steps.check.outputs.skip != 'true'
|
||||
env:
|
||||
SFTP_KEY: ${{ secrets.DEV_FTP_KEY }}
|
||||
SFTP_PASS: ${{ secrets.DEV_FTP_PASSWORD }}
|
||||
SFTP_USER: ${{ vars.DEV_FTP_USERNAME }}
|
||||
run: |
|
||||
SOURCE_DIR="src"
|
||||
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
|
||||
[ ! -d "$SOURCE_DIR" ] && { echo "No src/ or htdocs/ -- nothing to deploy"; exit 0; }
|
||||
|
||||
printf '{"host":"%s","port":%s,"username":"%s","remotePath":"%s"' \
|
||||
"${{ steps.check.outputs.host }}" "${{ steps.check.outputs.port }}" "$SFTP_USER" "${{ steps.check.outputs.remote }}" \
|
||||
> /tmp/sftp-config.json
|
||||
|
||||
if [ -n "$SFTP_KEY" ]; then
|
||||
echo "$SFTP_KEY" > /tmp/deploy_key
|
||||
chmod 600 /tmp/deploy_key
|
||||
printf ',"privateKeyPath":"/tmp/deploy_key"}' >> /tmp/sftp-config.json
|
||||
else
|
||||
printf ',"password":"%s"}' "$SFTP_PASS" >> /tmp/sftp-config.json
|
||||
fi
|
||||
|
||||
DEPLOY_ARGS=(--path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json)
|
||||
[ "${{ inputs.clear_remote }}" = "true" ] && DEPLOY_ARGS+=(--clear-remote)
|
||||
|
||||
PLATFORM=$(php /tmp/mokostandards-api/cli/platform_detect.php --path . 2>/dev/null || true)
|
||||
if [ "$PLATFORM" = "waas-component" ] && [ -f "/tmp/mokostandards-api/deploy/deploy-joomla.php" ]; then
|
||||
php /tmp/mokostandards-api/deploy/deploy-joomla.php "${DEPLOY_ARGS[@]}"
|
||||
else
|
||||
php /tmp/mokostandards-api/deploy/deploy-sftp.php "${DEPLOY_ARGS[@]}"
|
||||
fi
|
||||
|
||||
rm -f /tmp/deploy_key /tmp/sftp-config.json
|
||||
|
||||
- name: Summary
|
||||
if: always()
|
||||
run: |
|
||||
if [ "${{ steps.check.outputs.skip }}" = "true" ]; then
|
||||
echo "### Deploy Skipped -- FTP not configured" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "### Manual Dev Deploy Complete" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Host | \`${{ steps.check.outputs.host }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Remote | \`${{ steps.check.outputs.remote }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Clear | ${{ inputs.clear_remote }} |" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
@@ -0,0 +1,73 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: moko-platform.Automation
|
||||
# VERSION: 01.00.00
|
||||
# BRIEF: Auto-create feature branch when an issue is opened
|
||||
|
||||
name: "Universal: Issue Branch"
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
|
||||
env:
|
||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
|
||||
jobs:
|
||||
create-branch:
|
||||
name: Create feature branch
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Create branch and comment
|
||||
run: |
|
||||
TOKEN="${{ secrets.GA_TOKEN }}"
|
||||
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
||||
ISSUE_NUM="${{ github.event.issue.number }}"
|
||||
ISSUE_TITLE="${{ github.event.issue.title }}"
|
||||
|
||||
# Build slug from title: lowercase, replace non-alnum with dash, trim
|
||||
SLUG=$(echo "${ISSUE_TITLE}" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//' | cut -c1-40)
|
||||
BRANCH="feature/${ISSUE_NUM}-${SLUG}"
|
||||
|
||||
# Check dev branch exists
|
||||
DEV_EXISTS=$(curl -sf -o /dev/null -w '%{http_code}' \
|
||||
-H "Authorization: token ${TOKEN}" \
|
||||
"${API}/branches/dev" 2>/dev/null || echo "000")
|
||||
|
||||
if [ "${DEV_EXISTS}" != "200" ]; then
|
||||
echo "No dev branch -- skipping"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Create branch from dev
|
||||
HTTP=$(curl -sf -o /dev/null -w '%{http_code}' -X POST \
|
||||
-H "Authorization: token ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
"${API}/branches" \
|
||||
-d "{\"new_branch_name\":\"${BRANCH}\",\"old_branch_name\":\"dev\"}" 2>/dev/null || echo "000")
|
||||
|
||||
if [ "${HTTP}" = "201" ]; then
|
||||
echo "Created branch: ${BRANCH}"
|
||||
|
||||
# Comment on issue with branch link
|
||||
REPO_URL="${GITEA_URL}/${{ github.repository }}"
|
||||
BODY="Branch created: [\`${BRANCH}\`](${REPO_URL}/src/branch/${BRANCH})\n\n\`\`\`bash\ngit fetch origin\ngit checkout ${BRANCH}\n\`\`\`"
|
||||
|
||||
curl -sf -X POST \
|
||||
-H "Authorization: token ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
"${API}/issues/${ISSUE_NUM}/comments" \
|
||||
-d "{\"body\":\"${BODY}\"}" > /dev/null 2>&1
|
||||
|
||||
echo "Commented on issue #${ISSUE_NUM}"
|
||||
else
|
||||
echo "Failed to create branch (HTTP ${HTTP}) -- may already exist"
|
||||
fi
|
||||
@@ -222,3 +222,21 @@ jobs:
|
||||
echo "::error::CHANGELOG.md must be updated before merging to main. Add [skip changelog] to the PR title to bypass."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ── Pre-Release RC Build ─────────────────────────────────────────────────
|
||||
pre-release:
|
||||
name: Build RC Package
|
||||
runs-on: ubuntu-latest
|
||||
needs: [branch-policy, validate]
|
||||
|
||||
steps:
|
||||
- name: Trigger RC pre-release
|
||||
env:
|
||||
GA_TOKEN: ${{ secrets.GA_TOKEN }}
|
||||
REPO: ${{ github.repository }}
|
||||
BRANCH: ${{ github.head_ref }}
|
||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
run: |
|
||||
curl -s -X POST "${GITEA_URL}/api/v1/repos/${REPO}/actions/workflows/pre-release.yml/dispatches" -H "Authorization: token ${GA_TOKEN}" -H "Content-Type: application/json" -d "{\"ref\":\"${BRANCH}\",\"inputs\":{\"stability\":\"release-candidate\"}}"
|
||||
echo "### Pre-Release" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Triggered RC build on branch \`${BRANCH}\`" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
# INGROUP: moko-platform.Release
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
# PATH: /templates/workflows/universal/pre-release.yml.template
|
||||
# VERSION: 05.00.00
|
||||
# BRIEF: Manual pre-release — builds dev/alpha/beta/rc packages from any branch
|
||||
# VERSION: 05.01.00
|
||||
# BRIEF: Manual pre-release -- builds dev/alpha/beta/rc packages from any branch
|
||||
|
||||
name: "Universal: Pre-Release"
|
||||
|
||||
@@ -45,30 +45,46 @@ jobs:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GA_TOKEN }}
|
||||
|
||||
- name: Setup PHP
|
||||
- name: Setup tools
|
||||
run: |
|
||||
if ! command -v php &> /dev/null; then
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl >/dev/null 2>&1
|
||||
# Update moko-platform CLI tools if available; install PHP if missing
|
||||
if command -v moko-platform-update &> /dev/null; then
|
||||
moko-platform-update
|
||||
elif [ -d "/opt/moko-platform" ]; then
|
||||
cd /opt/moko-platform && git pull origin main --quiet 2>/dev/null || true
|
||||
else
|
||||
if ! command -v php &> /dev/null; then
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl >/dev/null 2>&1
|
||||
fi
|
||||
git clone --depth 1 --branch main --quiet \
|
||||
"https://x-access-token:${{ secrets.GA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/moko-platform.git" \
|
||||
/tmp/moko-platform-api
|
||||
fi
|
||||
# Set MOKO_CLI to whichever path exists
|
||||
if [ -d "/opt/moko-platform/cli" ]; then
|
||||
echo "MOKO_CLI=/opt/moko-platform/cli" >> "$GITHUB_ENV"
|
||||
else
|
||||
echo "MOKO_CLI=/tmp/moko-platform-api/cli" >> "$GITHUB_ENV"
|
||||
fi
|
||||
|
||||
- name: Setup moko-platform tools
|
||||
env:
|
||||
MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN }}
|
||||
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||
run: |
|
||||
git clone --depth 1 --branch main --quiet "https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" /tmp/moko-platform-api
|
||||
|
||||
- name: Detect platform
|
||||
id: platform
|
||||
run: |
|
||||
php /tmp/moko-platform-api/cli/manifest_read.php --path . --github-output
|
||||
PLATFORM=$(sed -n 's/.*<platform>\([^<]*\)<\/platform>.*/\1/p' .mokogitea/manifest.xml 2>/dev/null | head -1 | tr -d '[:space:]')
|
||||
[ -z "$PLATFORM" ] && PLATFORM="generic"
|
||||
echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT"
|
||||
MANIFEST=$(find ./src -maxdepth 1 -name "pkg_*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
|
||||
[ -z "$MANIFEST" ] && MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" ! -path "*/packages/*" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
|
||||
[ -z "$MANIFEST" ] && MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
|
||||
MOD_FILE=$(find . -maxdepth 4 -name "mod*.class.php" ! -path "./.git/*" -exec grep -l 'extends DolibarrModules' {} \; 2>/dev/null | head -1)
|
||||
echo "manifest=${MANIFEST}" >> "$GITHUB_OUTPUT"
|
||||
echo "mod_file=${MOD_FILE}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Resolve metadata
|
||||
- name: Resolve metadata and bump version
|
||||
id: meta
|
||||
run: |
|
||||
STABILITY="${{ inputs.stability }}"
|
||||
MOKO_API="/tmp/moko-platform-api/cli"
|
||||
|
||||
case "$STABILITY" in
|
||||
development) SUFFIX="-dev"; TAG="development" ;;
|
||||
@@ -77,14 +93,19 @@ jobs:
|
||||
release-candidate) SUFFIX="-rc"; TAG="release-candidate" ;;
|
||||
esac
|
||||
|
||||
# Bump patch version
|
||||
BUMP_OUTPUT=$(php ${MOKO_API}/version_bump.php --path .)
|
||||
VERSION=$(echo "$BUMP_OUTPUT" | grep -oP '\d{2}\.\d{2}\.\d{2}$' || true)
|
||||
[ -z "$VERSION" ] && VERSION=$(php ${MOKO_API}/version_read.php --path .)
|
||||
echo "Version: ${VERSION}"
|
||||
# Patch bump via CLI tool
|
||||
php ${MOKO_CLI}/version_bump.php --path .
|
||||
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null)
|
||||
[ -z "$VERSION" ] && VERSION="00.00.01"
|
||||
TODAY=$(date +%Y-%m-%d)
|
||||
|
||||
# Update platform-specific manifest
|
||||
php ${MOKO_API}/version_set_platform.php --path . --version "${VERSION}"
|
||||
PLATFORM="${{ steps.platform.outputs.platform }}"
|
||||
MANIFEST="${{ steps.platform.outputs.manifest }}"
|
||||
MOD_FILE="${{ steps.platform.outputs.mod_file }}"
|
||||
|
||||
php ${MOKO_CLI}/version_set_platform.php \
|
||||
--path . --version "$VERSION" --branch "${{ github.ref_name }}" 2>/dev/null || true
|
||||
|
||||
# Commit version bump
|
||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||
@@ -92,22 +113,38 @@ jobs:
|
||||
git remote set-url origin "https://jmiller:${{ secrets.GA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||
git add -A
|
||||
git diff --cached --quiet || {
|
||||
git commit -m "chore(version): bump to ${VERSION} [skip ci]"
|
||||
git commit -m "chore(version): pre-release bump to ${VERSION} [skip ci]"
|
||||
git push origin HEAD 2>&1
|
||||
}
|
||||
|
||||
# Detect element from Joomla/Dolibarr manifest
|
||||
PLATFORM="${{ steps.platform.outputs.platform }}"
|
||||
EXT_ELEMENT=$(php ${MOKO_API}/manifest_read.php --path . --field name 2>/dev/null | tr -d ' ' | tr '[:upper:]' '[:lower:]' || true)
|
||||
# For Joomla, prefer <element> tag
|
||||
if [ "$PLATFORM" = "joomla" ]; then
|
||||
MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '<extension' {} \; 2>/dev/null | head -1 || true)
|
||||
if [ -n "$MANIFEST" ]; then
|
||||
ELEM=$(grep -oP "<element>\K[^<]+" "$MANIFEST" 2>/dev/null | head -1)
|
||||
[ -n "$ELEM" ] && EXT_ELEMENT="$ELEM"
|
||||
fi
|
||||
fi
|
||||
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
|
||||
# Auto-detect element (platform-aware)
|
||||
EXT_ELEMENT=""
|
||||
case "$PLATFORM" in
|
||||
joomla)
|
||||
if [ -n "$MANIFEST" ]; then
|
||||
EXT_ELEMENT=$(sed -n 's/.*<element>\([^<]*\)<\/element>.*/\1/p' "$MANIFEST" 2>/dev/null | head -1)
|
||||
if [ -z "$EXT_ELEMENT" ]; then
|
||||
EXT_ELEMENT=$(basename "$MANIFEST" .xml | tr '[:upper:]' '[:lower:]')
|
||||
case "$EXT_ELEMENT" in
|
||||
templatedetails|manifest) EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') ;;
|
||||
esac
|
||||
fi
|
||||
else
|
||||
EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
|
||||
fi
|
||||
;;
|
||||
dolibarr)
|
||||
if [ -n "$MOD_FILE" ]; then
|
||||
MOD_BASENAME=$(basename "$MOD_FILE" .class.php)
|
||||
EXT_ELEMENT=$(echo "$MOD_BASENAME" | sed 's/^mod//' | tr '[:upper:]' '[:lower:]')
|
||||
else
|
||||
EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
|
||||
;;
|
||||
esac
|
||||
|
||||
ZIP_NAME="${EXT_ELEMENT}-${VERSION}${SUFFIX}.zip"
|
||||
|
||||
@@ -117,42 +154,76 @@ jobs:
|
||||
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
|
||||
echo "zip_name=${ZIP_NAME}" >> "$GITHUB_OUTPUT"
|
||||
echo "ext_element=${EXT_ELEMENT}" >> "$GITHUB_OUTPUT"
|
||||
echo "manifest=${MANIFEST}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
echo "=== Pre-Release: ${EXT_ELEMENT} ${VERSION}${SUFFIX} ==="
|
||||
|
||||
- name: Build package
|
||||
run: |
|
||||
SOURCE_DIR="src"
|
||||
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
|
||||
if [ ! -d "$SOURCE_DIR" ]; then
|
||||
echo "::error::No src/ or htdocs/ directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
MANIFEST="${{ steps.meta.outputs.manifest }}"
|
||||
EXT_TYPE=""
|
||||
if [ -n "$MANIFEST" ]; then
|
||||
EXT_TYPE=$(sed -n 's/.*<extension[^>]*type="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
|
||||
fi
|
||||
|
||||
EXCLUDES="sftp-config* .ftpignore *.ppk *.pem *.key .env* *.local .build-trigger"
|
||||
|
||||
mkdir -p build/package
|
||||
|
||||
if [ "$EXT_TYPE" = "package" ] && [ -d "${SOURCE_DIR}/packages" ]; then
|
||||
echo "=== Building Joomla PACKAGE (multi-extension) ==="
|
||||
for ext_dir in "${SOURCE_DIR}"/packages/*/; do
|
||||
[ ! -d "$ext_dir" ] && continue
|
||||
EXT_NAME=$(basename "$ext_dir")
|
||||
echo " Packaging sub-extension: ${EXT_NAME}"
|
||||
cd "$ext_dir"
|
||||
zip -r "../../build/package/${EXT_NAME}.zip" . -x $EXCLUDES
|
||||
cd "$OLDPWD"
|
||||
done
|
||||
for f in "${SOURCE_DIR}"/*.xml "${SOURCE_DIR}"/*.php; do
|
||||
[ -f "$f" ] && cp "$f" build/package/
|
||||
done
|
||||
else
|
||||
echo "=== Building standard extension ==="
|
||||
rsync -a \
|
||||
--exclude='sftp-config*' \
|
||||
--exclude='.ftpignore' \
|
||||
--exclude='*.ppk' \
|
||||
--exclude='*.pem' \
|
||||
--exclude='*.key' \
|
||||
--exclude='.env*' \
|
||||
--exclude='*.local' \
|
||||
--exclude='.build-trigger' \
|
||||
"${SOURCE_DIR}/" build/package/
|
||||
fi
|
||||
|
||||
- name: Create ZIP
|
||||
id: zip
|
||||
run: |
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
SUFFIX="${{ steps.meta.outputs.suffix }}"
|
||||
PLATFORM="${{ steps.platform.outputs.platform }}"
|
||||
ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
|
||||
cd build/package
|
||||
zip -r "../${ZIP_NAME}" .
|
||||
cd ..
|
||||
|
||||
if [ "$PLATFORM" = "joomla" ]; then
|
||||
php /tmp/moko-platform-api/cli/joomla_build.php --path . --version "${VERSION}" --suffix "${SUFFIX}" --output build --github-output
|
||||
else
|
||||
# Generic build: zip src/ directory
|
||||
SOURCE_DIR="src"
|
||||
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
|
||||
[ ! -d "$SOURCE_DIR" ] && { echo "::error::No src/ or htdocs/"; exit 1; }
|
||||
EXT_ELEMENT="${{ steps.meta.outputs.ext_element }}"
|
||||
ZIP_NAME="${EXT_ELEMENT}-${VERSION}${SUFFIX}.zip"
|
||||
mkdir -p build
|
||||
cd "$SOURCE_DIR" && zip -r "../build/${ZIP_NAME}" . && cd ..
|
||||
SHA256=$(sha256sum "build/${ZIP_NAME}" | cut -d' ' -f1)
|
||||
echo "zip_name=${ZIP_NAME}" >> "$GITHUB_OUTPUT"
|
||||
echo "zip_path=build/${ZIP_NAME}" >> "$GITHUB_OUTPUT"
|
||||
echo "sha256=${SHA256}" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
SHA256=$(sha256sum "${ZIP_NAME}" | cut -d' ' -f1)
|
||||
echo "sha256=${SHA256}" >> "$GITHUB_OUTPUT"
|
||||
echo "ZIP: ${ZIP_NAME} (SHA: ${SHA256:0:16}...)"
|
||||
|
||||
- name: Create or replace Gitea release
|
||||
id: release
|
||||
continue-on-error: true
|
||||
run: |
|
||||
TAG="${{ steps.meta.outputs.tag }}"
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||
SHA256="${{ steps.zip.outputs.sha256 }}"
|
||||
ZIP_NAME="${{ steps.zip.outputs.zip_name }}"
|
||||
ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
|
||||
EXT_ELEMENT="${{ steps.meta.outputs.ext_element }}"
|
||||
TOKEN="${{ secrets.GA_TOKEN }}"
|
||||
API="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
@@ -190,57 +261,115 @@ jobs:
|
||||
curl -sS -X POST -H "Authorization: token ${TOKEN}" \
|
||||
-H "Content-Type: application/octet-stream" \
|
||||
"${API}/releases/${RELEASE_ID}/assets?name=${ZIP_NAME}" \
|
||||
--data-binary "@${{ steps.zip.outputs.zip_path }}"
|
||||
--data-binary "@build/${ZIP_NAME}"
|
||||
|
||||
echo "Released: ${EXT_ELEMENT} ${VERSION} (${STABILITY})"
|
||||
|
||||
- name: "Update updates.xml"
|
||||
- name: Update updates.xml
|
||||
if: steps.platform.outputs.platform == 'joomla'
|
||||
run: |
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
SHA256="${{ steps.zip.outputs.sha256 }}"
|
||||
php /tmp/moko-platform-api/cli/updates_xml_build.php --path . --version "$VERSION" --stability "$STABILITY" --sha "$SHA256" --gitea-url "$GITEA_URL" --org "$GITEA_ORG" --repo "$GITEA_REPO"
|
||||
ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
|
||||
TAG="${{ steps.meta.outputs.tag }}"
|
||||
|
||||
if [ ! -f "updates.xml" ]; then
|
||||
echo "No updates.xml -- skipping"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Map stability to XML tag name
|
||||
case "$STABILITY" in
|
||||
development) XML_TAG="development" ;;
|
||||
alpha) XML_TAG="alpha" ;;
|
||||
beta) XML_TAG="beta" ;;
|
||||
release-candidate) XML_TAG="rc" ;;
|
||||
*) XML_TAG="$STABILITY" ;;
|
||||
esac
|
||||
|
||||
DOWNLOAD_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/${TAG}/${ZIP_NAME}"
|
||||
|
||||
# Use PHP to update the channel in updates.xml
|
||||
php -r '
|
||||
$xml_tag = $argv[1];
|
||||
$version = $argv[2];
|
||||
$sha256 = $argv[3];
|
||||
$url = $argv[4];
|
||||
$date = date("Y-m-d");
|
||||
|
||||
$content = file_get_contents("updates.xml");
|
||||
$pattern = "/(<update>(?:(?!<\/update>).)*?<tag>" . preg_quote($xml_tag) . "<\/tag>.*?<\/update>)/s";
|
||||
|
||||
$content = preg_replace_callback($pattern, function($m) use ($version, $sha256, $url, $date) {
|
||||
$block = $m[0];
|
||||
$block = preg_replace("/<version>[^<]*<\/version>/", "<version>{$version}</version>", $block);
|
||||
if (strpos($block, "<sha256>") !== false) {
|
||||
$block = preg_replace("/<sha256>[^<]*<\/sha256>/", "<sha256>{$sha256}</sha256>", $block);
|
||||
} else {
|
||||
$block = str_replace("</downloads>", "</downloads>\n <sha256>{$sha256}</sha256>", $block);
|
||||
}
|
||||
$block = preg_replace("/(<downloadurl[^>]*>)[^<]*(<\/downloadurl>)/", "\${1}{$url}\${2}", $block);
|
||||
return $block;
|
||||
}, $content);
|
||||
|
||||
file_put_contents("updates.xml", $content);
|
||||
echo "Updated {$xml_tag} channel: version={$version}\n";
|
||||
' "$XML_TAG" "$VERSION" "$SHA256" "$DOWNLOAD_URL"
|
||||
|
||||
# Commit and push
|
||||
if ! git diff --quiet updates.xml 2>/dev/null; then
|
||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||
git config --local user.name "gitea-actions[bot]"
|
||||
git add updates.xml
|
||||
git commit -m "chore: update $STABILITY channel $VERSION [skip ci]"
|
||||
git commit -m "chore: update ${STABILITY} channel ${VERSION} [skip ci]"
|
||||
git push origin HEAD 2>&1 || echo "WARNING: push failed"
|
||||
fi
|
||||
|
||||
- name: "Sync updates.xml to all branches"
|
||||
if: steps.platform.outputs.platform == 'joomla'
|
||||
run: |
|
||||
php /tmp/moko-platform-api/cli/updates_xml_sync.php --path . --current "${{ github.ref_name }}" --branches main,dev --version "${{ steps.meta.outputs.version }}" --token "${{ secrets.GA_TOKEN }}" --org "${GITEA_ORG}" --repo "${GITEA_REPO}" --gitea-url "${GITEA_URL}"
|
||||
CURRENT_BRANCH="${{ github.ref_name }}"
|
||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||
git config --local user.name "gitea-actions[bot]"
|
||||
|
||||
for BRANCH in main dev; do
|
||||
[ "$BRANCH" = "$CURRENT_BRANCH" ] && continue
|
||||
echo "Syncing updates.xml -> ${BRANCH}"
|
||||
git fetch origin "${BRANCH}" 2>/dev/null || continue
|
||||
git checkout "origin/${BRANCH}" -- . 2>/dev/null || continue
|
||||
git checkout "${CURRENT_BRANCH}" -- updates.xml
|
||||
if ! git diff --quiet updates.xml 2>/dev/null; then
|
||||
git add updates.xml
|
||||
git commit -m "chore: sync updates.xml from ${CURRENT_BRANCH} [skip ci]"
|
||||
git push origin HEAD:refs/heads/${BRANCH} 2>&1 || echo "WARNING: push to ${BRANCH} failed"
|
||||
fi
|
||||
git checkout "${CURRENT_BRANCH}" 2>/dev/null
|
||||
done
|
||||
|
||||
- name: "Delete lesser pre-release channels (cascade)"
|
||||
continue-on-error: true
|
||||
run: |
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
TOKEN="${{ secrets.GA_TOKEN }}"
|
||||
|
||||
php ${MOKO_CLI}/release_cascade.php \
|
||||
--stability "${{ steps.meta.outputs.stability }}" \
|
||||
--token "${TOKEN}" \
|
||||
--api-base "${API_BASE}"
|
||||
|
||||
- name: Summary
|
||||
if: always()
|
||||
run: |
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||
|
||||
# Cascade: rc → beta,alpha,dev | beta → alpha,dev | alpha → dev | dev → nothing
|
||||
case "$STABILITY" in
|
||||
release-candidate) TAGS_TO_DELETE="beta alpha development" ;;
|
||||
beta) TAGS_TO_DELETE="alpha development" ;;
|
||||
alpha) TAGS_TO_DELETE="development" ;;
|
||||
*) TAGS_TO_DELETE="" ;;
|
||||
esac
|
||||
|
||||
[ -z "$TAGS_TO_DELETE" ] && exit 0
|
||||
|
||||
for TAG in $TAGS_TO_DELETE; do
|
||||
RELEASE_ID=$(curl -sS -H "Authorization: token ${TOKEN}" \
|
||||
"${API_BASE}/releases/tags/${TAG}" 2>/dev/null | \
|
||||
python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
|
||||
|
||||
if [ -n "$RELEASE_ID" ] && [ "$RELEASE_ID" != "None" ]; then
|
||||
curl -sS -X DELETE -H "Authorization: token ${TOKEN}" \
|
||||
"${API_BASE}/releases/${RELEASE_ID}" 2>/dev/null || true
|
||||
curl -sS -X DELETE -H "Authorization: token ${TOKEN}" \
|
||||
"${API_BASE}/tags/${TAG}" 2>/dev/null || true
|
||||
echo "Deleted: ${TAG} (id: ${RELEASE_ID})"
|
||||
fi
|
||||
done
|
||||
ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
|
||||
SHA256="${{ steps.zip.outputs.sha256 }}"
|
||||
echo "## Pre-Release Complete" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Channel | ${STABILITY} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Package | \`${ZIP_NAME}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| SHA-256 | \`${SHA256:-n/a}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
@@ -31,6 +31,59 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- License/subscription check
|
||||
- System email template branding (DB approach)
|
||||
|
||||
## [02.03.10] - 2026-05-24
|
||||
|
||||
### Added
|
||||
- Canonical URL injection for alias domains (prevents SEO duplication)
|
||||
- Primary Domain config field in Site Aliases tab
|
||||
- Heartbeat registration for alias domains (each alias gets Grafana datasource)
|
||||
- Plugin protection: hidden from non-master users, disable/uninstall blocked
|
||||
- Dynamic plugin version read from manifest XML (no more hardcoded strings)
|
||||
- Package structure: `pkg_mokowaas` with system plugin, webservices plugin, and component
|
||||
|
||||
### Changed
|
||||
- Alias offline mode uses Joomla's native template offline.php (not custom HTML)
|
||||
- Alias detection simplified: direct lookup in aliases list (no primary host comparison)
|
||||
- handleSiteAlias() moved to onAfterRoute (client type resolved at that point)
|
||||
- Package script.php enables plugins on every install/update and sends heartbeat
|
||||
|
||||
### Fixed
|
||||
- Alias domain matching: strip trailing slashes, handle Joomla subform stdClass format
|
||||
- Backend redirect: use primary_domain setting instead of Uri::root() (returned alias domain on mirrors)
|
||||
- CI: version_bump reads manifest XML with priority over README.md VERSION header
|
||||
- CI: version bump occurs after release build, not before
|
||||
- CI: pipefail disabled during element detection (SIGPIPE on find|head)
|
||||
- CI: pkg_pkg_ prefix duplication in zip names and updates.xml URLs
|
||||
- CI: updates_xml_build preserves existing channel entries (stable not wiped by dev releases)
|
||||
|
||||
### Removed
|
||||
- deploy-manual.yml workflow — using Joomla update server for distribution
|
||||
- Accidentally committed profile.ps1 and TODO.md
|
||||
|
||||
## [02.01.43] - 2026-05-23
|
||||
|
||||
### Added
|
||||
- Site Aliases tab with Joomla subform repeatable-table UI
|
||||
- Per-alias offline toggle with custom maintenance message (503 response)
|
||||
- Per-alias robots meta directive (index/noindex/follow/nofollow/none)
|
||||
- Per-alias backend redirect (admin panel redirects to primary domain)
|
||||
- 6 MokoWaaS API endpoints: health, install, update, cache, backup, info
|
||||
- Remote plugin install via `/?mokowaas=install` endpoint
|
||||
- Remote update trigger via `/?mokowaas=update` endpoint
|
||||
- Remote cache clear via `/?mokowaas=cache` endpoint (site + admin + opcache)
|
||||
- Remote Akeeba Backup trigger via `/?mokowaas=backup` endpoint
|
||||
- Compact site info via `/?mokowaas=info` endpoint
|
||||
|
||||
### Changed
|
||||
- Site aliases moved from comma-separated text field to structured subform
|
||||
- Each alias now stores domain, offline, offline_message, robots, redirect_backend
|
||||
- Heartbeat provisioning updated for subform alias format
|
||||
- Grafana datasource names use domain-only (removed "MokoWaaS - " prefix)
|
||||
|
||||
### Fixed
|
||||
- Heartbeat receiver accepts any 200 status (registered/updated/ok)
|
||||
- script.php uses heartbeat receiver instead of Grafana API (fixes 403 RBAC)
|
||||
|
||||
## [02.01.37] - 2026-05-23
|
||||
|
||||
### Added
|
||||
|
||||
@@ -5,356 +5,62 @@
|
||||
|
||||
SPDX-LICENSE-IDENTIFIER: GPL-3.0-or-later
|
||||
|
||||
This program is free software; you can redistribute it and modify it under the terms of the GNU General Public License version 3 or later.
|
||||
|
||||
This program is distributed in the hope that it will be useful but without warranty.
|
||||
|
||||
You should have received a copy of the GNU General Public License in LICENSE.md.
|
||||
|
||||
# FILE INFORMATION
|
||||
DEFGROUP: Joomla.Plugin
|
||||
INGROUP: MokoWaaS
|
||||
REPO: https://github.com/mokoconsulting-tech/mokowaas
|
||||
VERSION: 02.01.42
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS
|
||||
VERSION: 02.05.00
|
||||
PATH: /README.md
|
||||
BRIEF: Rebranding plugin for MokoWaaS platform
|
||||
NOTE: Internal WaaS identity abstraction layer
|
||||
BRIEF: MokoWaaS platform plugin for Joomla
|
||||
-->
|
||||
|
||||
# MokoWaaS Plugin
|
||||
# MokoWaaS
|
||||
|
||||
[](https://github.com/mokoconsulting-tech/MokoWaaS/releases/tag/v02)
|
||||
[](https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases)
|
||||
[](LICENSE)
|
||||
[](https://www.joomla.org)
|
||||
[](https://www.php.net)
|
||||
|
||||
MokoWaaS is a Joomla 5.x / 6.x system plugin that provides a configurable white-label identity layer for the MokoWaaS platform. It replaces all visible Joomla branding with your own brand name, company name, and support URLs — configurable from the plugin admin without code changes.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Features](#features)
|
||||
- [System Requirements](#system-requirements)
|
||||
- [Installation](#installation)
|
||||
- [Usage](#usage)
|
||||
- [Configuration](#configuration)
|
||||
- [Technical Implementation](#technical-implementation)
|
||||
- [Repository Structure](#repository-structure)
|
||||
- [Development](#development)
|
||||
- [Documentation](#documentation)
|
||||
- [Support](#support)
|
||||
- [License](#license)
|
||||
- [Changelog](#changelog)
|
||||
|
||||
## Overview
|
||||
|
||||
The MokoWaaS plugin operationalizes a unified naming convention, brand-controlled visuals, and enforced terminology across all tenant sites. This ensures consistent service delivery within the WaaS (Website as a Service) framework by abstracting all upstream Joomla identifiers behind MokoWaaS-compliant terminology.
|
||||
MokoWaaS is a Joomla 5.x / 6.x system plugin package that provides white-label branding, security hardening, tenant restrictions, health monitoring, and multi-domain management for the MokoWaaS platform.
|
||||
|
||||
## Features
|
||||
|
||||
- **Template-Based Overrides**: 50+ language keys with `{{BRAND_NAME}}`, `{{COMPANY_NAME}}`, `{{SUPPORT_URL}}` placeholders
|
||||
- **Configurable Brand**: Change brand name, company, and support URL from plugin config — takes effect immediately
|
||||
- **Safe Override Merging**: Sentinel-block pattern preserves existing site overrides during install/update
|
||||
- **Clean Uninstall**: Only MokoWaaS keys are removed; all other overrides are preserved
|
||||
- **Joomla 5.x / 6.x Compatible**: Built using modern Joomla plugin architecture with dependency injection
|
||||
- **Multi-Language Support**: en-GB and en-US locales
|
||||
- **Admin & Frontend Coverage**: Dashboard, footer, login, installer, system info, update component, error pages, and more
|
||||
- **Health Monitoring**: 16 diagnostic checks via `/?mokowaas=health` — database, filesystem, cache, extensions, Akeeba Backup, Admin Tools, SSL, cron, errors, DB size, content, users, mail, SEO, templates, config
|
||||
- **Grafana Integration**: Auto-provisions Infinity datasource via heartbeat receiver — 9-row dashboard with all health metrics
|
||||
- **ntfy Notifications**: Heartbeat events pushed to `mokowaas-heartbeat` topic
|
||||
- **Plugin Protection**: Hidden from non-super-admins, self-healing lock, uninstall blocked
|
||||
- **Governance Compliant**: Aligned with [moko-platform](https://git.mokoconsulting.tech/MokoConsulting/moko-platform)
|
||||
- **White-Label Branding** — configurable brand name, company, support URL, colors, favicon, custom CSS
|
||||
- **Tenant Restrictions** — master user enforcement, installer/sysinfo/config/template access control
|
||||
- **Health Monitoring** — 16 diagnostic checks via `/?mokowaas=health` with Grafana auto-provisioning
|
||||
- **Site Aliases** — per-alias offline mode, robots directives, backend redirect, canonical URLs
|
||||
- **Remote API** — 6 endpoints (health, install, update, cache, backup, info)
|
||||
- **Security Hardening** — HTTPS enforcement, session timeouts, password policy, upload restrictions
|
||||
- **Plugin Protection** — protected status, hidden from non-master users, disable/uninstall blocked
|
||||
|
||||
## System Requirements
|
||||
## Requirements
|
||||
|
||||
- **Joomla**: 5.0+ or 6.x
|
||||
- **PHP**: 8.1 or higher (8.3+ for Joomla 6)
|
||||
- **Extensions**: Standard Joomla PHP extensions
|
||||
- **Permissions**: Write access to language override directories
|
||||
- Joomla 5.0+ or 6.x
|
||||
- PHP 8.1+ (8.3+ for Joomla 6)
|
||||
|
||||
## Installation
|
||||
|
||||
### Method 1: Via Joomla Extension Manager (Recommended)
|
||||
Download the latest `pkg_mokowaas-*.zip` from [Releases](https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases) and install via **System → Install → Upload Package File**.
|
||||
|
||||
1. Download the latest release package from the releases page
|
||||
2. Log into your Joomla Administrator panel
|
||||
3. Navigate to **System → Extensions → Install**
|
||||
4. Click **Upload Package File**
|
||||
5. Select the downloaded `.zip` file
|
||||
6. Click **Upload & Install**
|
||||
7. Navigate to **System → Plugins**
|
||||
8. Search for "MokoWaaS Brand"
|
||||
9. Enable the plugin
|
||||
10. Clear Joomla cache
|
||||
|
||||
### Method 2: Manual Installation
|
||||
|
||||
1. Extract the plugin package
|
||||
2. Upload contents to your Joomla installation's `/tmp` directory
|
||||
3. Install via Joomla Extension Manager → Install from Folder
|
||||
4. Enable the plugin as described above
|
||||
|
||||
### Post-Installation
|
||||
|
||||
After installation, verify the branding is active:
|
||||
- Check the administrator footer for "Powered by MokoWaaS"
|
||||
- Verify the control panel shows "Welcome to MokoWaaS!"
|
||||
- Clear browser cache if branding doesn't appear immediately
|
||||
|
||||
### Automatic Updates
|
||||
|
||||
This plugin supports Joomla's automatic update system. Once installed:
|
||||
|
||||
1. Navigate to **System → Update → Extensions**
|
||||
2. The plugin will automatically check for updates from the MokoWaaS update server
|
||||
3. When a new version is available, it will appear in the update list
|
||||
4. Click **Update** to install the latest version
|
||||
|
||||
The update server URL is configured in the plugin manifest and points to:
|
||||
```
|
||||
https://raw.githubusercontent.com/mokoconsulting-tech/MokoWaaS/main/updates.xml
|
||||
```
|
||||
|
||||
Updates are published automatically when new releases are created through the GitHub release workflow.
|
||||
|
||||
## Usage
|
||||
|
||||
Once installed and enabled, the plugin automatically replaces Joomla branding with your configured values. No code changes needed.
|
||||
|
||||
### Changing the Brand Name
|
||||
|
||||
1. Navigate to **System → Plugins → System - MokoWaaS**
|
||||
2. Set **Brand Name** to your desired name (e.g., "MyPlatform")
|
||||
3. Set **Company Name** to your company (e.g., "My Company Inc.")
|
||||
4. Set **Support URL** to your support site (e.g., "https://support.mycompany.com")
|
||||
5. Click **Save & Close**
|
||||
6. The new branding appears immediately across admin and frontend
|
||||
|
||||
### What Gets Rebranded
|
||||
|
||||
| Area | Example |
|
||||
| ---- | ------- |
|
||||
| Admin footer | "Powered by [YourBrand](https://your-url)" |
|
||||
| Dashboard | "Welcome to YourBrand!" |
|
||||
| Quick Icons | "YourBrand is up to date." |
|
||||
| System Info | "YourBrand Version" |
|
||||
| Login page | "YourBrand Administrator Login" |
|
||||
| Update component | "YourBrand Update" |
|
||||
| Frontend footer | "Powered by [YourBrand](https://your-url)" |
|
||||
| Error pages | No Joomla references |
|
||||
|
||||
## Configuration
|
||||
|
||||
The plugin provides the following configuration options accessible through **System → Plugins → System - MokoWaaS**:
|
||||
|
||||
### Parameters
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
| --------- | ---- | ------- | ----------- |
|
||||
| Enable Branding | Yes/No | Yes | Master toggle for all branding overrides |
|
||||
| Brand Name | Text | MokoWaaS | Replaces "Joomla" throughout the interface |
|
||||
| Company Name | Text | Moko Consulting | Used in support/attribution links |
|
||||
| Support URL | URL | https://mokoconsulting.tech | Destination for help and documentation links |
|
||||
|
||||
See the [Configuration Guide](docs/guides/configuration-guide.md) for detailed documentation on how overrides work.
|
||||
|
||||
## Technical Implementation
|
||||
|
||||
### Architecture
|
||||
|
||||
The plugin follows Joomla 5.x system plugin architecture:
|
||||
|
||||
```
|
||||
PlgSystemMokoWaaS
|
||||
├── Event Handlers
|
||||
│ ├── onAfterInitialise - Framework initialization hook
|
||||
│ └── onAfterRoute - Route determination hook
|
||||
├── Dependency Injection
|
||||
│ └── ServiceProvider - DI container registration
|
||||
└── Language Integration
|
||||
└── Native Override System - Joomla's built-in override mechanism
|
||||
```
|
||||
|
||||
### Core Components
|
||||
|
||||
1. **mokowaas.php**
|
||||
- Main plugin class extending `CMSPlugin`
|
||||
- Implements system event handlers
|
||||
- Namespace: `Moko\Plugin\System\MokoWaaS`
|
||||
|
||||
2. **mokowaas.xml**
|
||||
- Plugin manifest defining metadata and structure
|
||||
- Joomla 5.x namespace configuration
|
||||
- File and folder definitions
|
||||
|
||||
3. **services/provider.php**
|
||||
- Dependency injection service provider
|
||||
- Registers plugin with Joomla's DI container
|
||||
- Joomla 5.x compatibility layer
|
||||
|
||||
4. **language/en-GB/**
|
||||
- Plugin-specific language strings
|
||||
- Installation and configuration UI text
|
||||
|
||||
5. **language/overrides/**
|
||||
- Frontend language override files
|
||||
- Replaces Joomla terminology with MokoWaaS branding
|
||||
|
||||
6. **administrator/language/overrides/**
|
||||
- Administrator language override files
|
||||
- Backend-specific branding replacements
|
||||
|
||||
### Language Override Integration
|
||||
|
||||
The plugin leverages Joomla's native language override system rather than programmatically loading strings. Language override files are placed in standard Joomla locations:
|
||||
|
||||
- Frontend: `language/overrides/{locale}.override.ini`
|
||||
- Administrator: `administrator/language/overrides/{locale}.override.ini`
|
||||
|
||||
Joomla automatically loads these overrides during initialization, ensuring optimal performance and compatibility.
|
||||
|
||||
## Repository Structure
|
||||
|
||||
```
|
||||
mokowaas/
|
||||
├── src/ # Plugin source files
|
||||
│ ├── mokowaas.php # Main plugin class
|
||||
│ ├── mokowaas.xml # Plugin manifest
|
||||
│ ├── services/
|
||||
│ │ └── provider.php # DI service provider
|
||||
│ ├── language/
|
||||
│ │ ├── en-GB/ # Plugin language files
|
||||
│ │ └── overrides/ # Frontend language overrides
|
||||
│ └── administrator/
|
||||
│ └── language/
|
||||
│ └── overrides/ # Admin language overrides
|
||||
├── docs/ # Documentation
|
||||
│ ├── index.md # Documentation index
|
||||
│ ├── plugin-basic.md # Plugin overview
|
||||
│ ├── guides/ # Operational guides
|
||||
│ └── reference/ # Reference materials
|
||||
├── scripts/ # Build and validation scripts
|
||||
│ ├── validate_manifest.sh
|
||||
│ ├── verify_changelog.sh
|
||||
│ └── update_changelog.sh
|
||||
├── .github/ # GitHub workflows
|
||||
│ └── workflows/
|
||||
│ ├── build.yml
|
||||
│ ├── ci.yml
|
||||
│ └── release_from_version.yml
|
||||
├── CHANGELOG.md # Version history
|
||||
├── README.md # This file
|
||||
├── LICENSE.md # GPL-3.0-or-later license
|
||||
├── CONTRIBUTING.md # Contribution guidelines
|
||||
└── CODE_OF_CONDUCT.md # Community guidelines
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
### Building the Plugin
|
||||
|
||||
Build the installable plugin package from source:
|
||||
|
||||
```bash
|
||||
cd src
|
||||
zip -r ../mokowaas_v01.04.00.zip . -x "*.git*"
|
||||
```
|
||||
|
||||
### Running Validation Scripts
|
||||
|
||||
```bash
|
||||
# Validate plugin manifest
|
||||
./scripts/validate_manifest.sh
|
||||
|
||||
# Verify changelog format
|
||||
./scripts/verify_changelog.sh
|
||||
```
|
||||
|
||||
### PHP Syntax Validation
|
||||
|
||||
```bash
|
||||
cd src
|
||||
find . -name "*.php" -exec php -l {} \;
|
||||
```
|
||||
|
||||
### Automated Build via GitHub Actions
|
||||
|
||||
The repository includes automated workflows:
|
||||
|
||||
- **build.yml**: Creates ZIP package on release
|
||||
- **ci.yml**: Runs validation checks on pull requests
|
||||
- **release_from_version.yml**: Automates release process
|
||||
After installation, the package auto-enables and sets protected status.
|
||||
|
||||
## Documentation
|
||||
|
||||
Comprehensive documentation is available in the `/docs` directory:
|
||||
Full documentation is available on the [MokoWaaS Wiki](https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/wiki):
|
||||
|
||||
- **[Plugin Overview](docs/plugin-basic.md)**: Detailed plugin documentation
|
||||
- **[Installation Guide](docs/guides/installation-guide.md)**: Step-by-step installation
|
||||
- **[Build Guide](docs/guides/build-guide.md)**: Building and packaging
|
||||
- **[Configuration Guide](docs/guides/configuration-guide.md)**: Configuration options
|
||||
- **[Operations Guide](docs/guides/operations-guide.md)**: Operational procedures
|
||||
- **[Troubleshooting Guide](docs/guides/troubleshooting-guide.md)**: Common issues
|
||||
|
||||
All documentation follows the [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards) documentation framework.
|
||||
|
||||
## Support
|
||||
|
||||
### Getting Help
|
||||
|
||||
- **Documentation**: Check the `/docs` directory for detailed guides
|
||||
- **Issues**: Submit issues through the GitHub issue tracker
|
||||
- **Service Support**: For operational issues, submit a ticket through the Moko Consulting service channel
|
||||
|
||||
### Reporting Issues
|
||||
|
||||
When reporting issues, include:
|
||||
- Joomla version
|
||||
- PHP version
|
||||
- Plugin version
|
||||
- Steps to reproduce
|
||||
- Expected vs actual behavior
|
||||
- Relevant error messages or logs
|
||||
- [Configuration Guide](https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/wiki/Configuration)
|
||||
- [Health Monitoring](https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/wiki/Health-Monitoring)
|
||||
- [Site Aliases](https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/wiki/Site-Aliases)
|
||||
- [API Endpoints](https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/wiki/API-Endpoints)
|
||||
- [Grafana Integration](https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/wiki/Grafana-Integration)
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the GNU General Public License version 3 or later (GPL-3.0-or-later).
|
||||
|
||||
See [LICENSE.md](LICENSE.md) for the full license text.
|
||||
|
||||
## Versioning
|
||||
|
||||
This extension follows the [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards) version governance model using semantic versioning: `MAJOR.MINOR.PATCH`
|
||||
|
||||
Current version: **02.01.18**
|
||||
GPL-3.0-or-later — see [LICENSE.md](LICENSE.md)
|
||||
|
||||
## Changelog
|
||||
|
||||
See [CHANGELOG.md](CHANGELOG.md) for a complete version history.
|
||||
|
||||
### Recent Changes (v02.01.18 - 2026-04-23)
|
||||
|
||||
- Always install and lock MokoOnyx template on install/update
|
||||
- Always unlock MokoCassiopeia on install/update (allow uninstall)
|
||||
- Bundle MokoOnyx payload (replaces MokoCassiopeia payload)
|
||||
- Update payload workflow to fetch MokoOnyx from Gitea releases
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome contributions! Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on:
|
||||
|
||||
- Code of conduct
|
||||
- Development workflow
|
||||
- Coding standards
|
||||
- Pull request process
|
||||
- Documentation requirements
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
- Built for the MokoWaaS platform
|
||||
- Follows [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards)
|
||||
- Designed for Joomla 5.x architecture
|
||||
- Maintained by Moko Consulting
|
||||
See [CHANGELOG.md](CHANGELOG.md)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
# TODO
|
||||
|
||||
> **Note:** This file is not tracked in version control (.gitignore). It is for local task tracking only.
|
||||
|
||||
## Critical
|
||||
-
|
||||
|
||||
## Normal
|
||||
-
|
||||
|
||||
## Low
|
||||
-
|
||||
@@ -0,0 +1,38 @@
|
||||
<?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
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Dispatcher\ComponentDispatcherFactoryInterface;
|
||||
use Joomla\CMS\Extension\ComponentInterface;
|
||||
use Joomla\CMS\Extension\Service\Provider\ComponentDispatcherFactory;
|
||||
use Joomla\CMS\Extension\Service\Provider\MVCFactory;
|
||||
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
|
||||
use Joomla\DI\Container;
|
||||
use Joomla\DI\ServiceProviderInterface;
|
||||
|
||||
return new class implements ServiceProviderInterface
|
||||
{
|
||||
public function register(Container $container): void
|
||||
{
|
||||
$container->registerServiceProvider(new MVCFactory('\\Moko\\Component\\MokoWaaS'));
|
||||
$container->registerServiceProvider(new ComponentDispatcherFactory('\\Moko\\Component\\MokoWaaS'));
|
||||
|
||||
$container->set(
|
||||
ComponentInterface::class,
|
||||
function (Container $container) {
|
||||
$component = new \Joomla\CMS\Extension\MVCComponent(
|
||||
$container->get(ComponentDispatcherFactoryInterface::class)
|
||||
);
|
||||
$component->setMVCFactory($container->get(MVCFactoryInterface::class));
|
||||
|
||||
return $component;
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,89 @@
|
||||
<?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\Api\Controller;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\MVC\Controller\BaseController;
|
||||
|
||||
/**
|
||||
* Cache management API controller.
|
||||
*
|
||||
* POST /api/index.php/v1/mokowaas/cache
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class CacheController extends BaseController
|
||||
{
|
||||
/**
|
||||
* Clear all Joomla caches.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function execute(): void
|
||||
{
|
||||
$app = Factory::getApplication();
|
||||
|
||||
if ($app->input->getMethod() !== 'POST')
|
||||
{
|
||||
$this->sendJson(405, ['error' => 'POST required']);
|
||||
return;
|
||||
}
|
||||
|
||||
$user = $app->getIdentity();
|
||||
if (!$user->authorise('core.manage', 'com_plugins'))
|
||||
{
|
||||
$this->sendJson(403, ['error' => 'Not authorized']);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$cache = Factory::getCache('');
|
||||
$cache->clean('');
|
||||
|
||||
$adminCache = Factory::getCache('', 'callback', 'administrator');
|
||||
$adminCache->clean('');
|
||||
|
||||
if (function_exists('opcache_reset'))
|
||||
{
|
||||
opcache_reset();
|
||||
}
|
||||
|
||||
$this->sendJson(200, [
|
||||
'status' => 'ok',
|
||||
'message' => 'Cache cleared',
|
||||
]);
|
||||
}
|
||||
catch (\Throwable $e)
|
||||
{
|
||||
$this->sendJson(500, [
|
||||
'error' => 'Cache clear failed',
|
||||
'message' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $code HTTP status code
|
||||
* @param array $payload Response data
|
||||
* @return void
|
||||
*/
|
||||
private function sendJson(int $code, array $payload): void
|
||||
{
|
||||
$app = Factory::getApplication();
|
||||
$app->setHeader('Content-Type', 'application/json', true);
|
||||
$app->setHeader('Status', (string) $code, true);
|
||||
echo json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
|
||||
$app->close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
<?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\Api\Controller;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\MVC\Controller\BaseController;
|
||||
use Joomla\CMS\Plugin\PluginHelper;
|
||||
use Joomla\CMS\Uri\Uri;
|
||||
use Joomla\Registry\Registry;
|
||||
|
||||
/**
|
||||
* Health check API controller.
|
||||
*
|
||||
* GET /api/index.php/v1/mokowaas/health
|
||||
*
|
||||
* Returns full health diagnostics from the MokoWaaS system plugin.
|
||||
* Requires a Joomla API token with core.manage permissions.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class HealthController extends BaseController
|
||||
{
|
||||
/**
|
||||
* Return full health check data.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function displayList(): void
|
||||
{
|
||||
$app = Factory::getApplication();
|
||||
$user = $app->getIdentity();
|
||||
|
||||
if (!$user->authorise('core.manage', 'com_plugins'))
|
||||
{
|
||||
$this->sendJson(403, ['error' => 'Not authorized']);
|
||||
return;
|
||||
}
|
||||
|
||||
$plugin = PluginHelper::getPlugin('system', 'mokowaas');
|
||||
|
||||
if (!$plugin)
|
||||
{
|
||||
$this->sendJson(503, ['error' => 'MokoWaaS system plugin not enabled']);
|
||||
return;
|
||||
}
|
||||
|
||||
$params = new Registry($plugin->params);
|
||||
$config = Factory::getConfig();
|
||||
$db = Factory::getDbo();
|
||||
|
||||
// Collect basic health data
|
||||
$payload = [
|
||||
'status' => 'ok',
|
||||
'timestamp' => gmdate('Y-m-d\TH:i:s\Z'),
|
||||
'site' => [
|
||||
'name' => $config->get('sitename', ''),
|
||||
'url' => rtrim(Uri::root(), '/'),
|
||||
'joomla_version' => JVERSION,
|
||||
'php_version' => PHP_VERSION,
|
||||
'db_type' => $db->getName(),
|
||||
'offline' => (bool) $config->get('offline', 0),
|
||||
'debug' => (bool) $config->get('debug', 0),
|
||||
'sef' => (bool) $config->get('sef', 0),
|
||||
'caching' => (bool) $config->get('caching', 0),
|
||||
],
|
||||
'plugin' => [
|
||||
'brand' => $params->get('brand_name', 'MokoWaaS'),
|
||||
'company' => $params->get('company_name', 'Moko Consulting'),
|
||||
],
|
||||
];
|
||||
|
||||
// Database check
|
||||
try
|
||||
{
|
||||
$db->setQuery('SELECT 1');
|
||||
$db->loadResult();
|
||||
$payload['checks']['database'] = ['status' => 'ok'];
|
||||
}
|
||||
catch (\Throwable $e)
|
||||
{
|
||||
$payload['status'] = 'error';
|
||||
$payload['checks']['database'] = ['status' => 'error', 'message' => $e->getMessage()];
|
||||
}
|
||||
|
||||
// Disk space
|
||||
$free = @disk_free_space(JPATH_ROOT);
|
||||
$total = @disk_total_space(JPATH_ROOT);
|
||||
if ($free !== false && $total !== false)
|
||||
{
|
||||
$freeMb = round($free / 1048576);
|
||||
$payload['checks']['filesystem'] = [
|
||||
'status' => $freeMb < 100 ? 'degraded' : 'ok',
|
||||
'free_disk_mb' => $freeMb,
|
||||
'total_disk_mb' => round($total / 1048576),
|
||||
];
|
||||
}
|
||||
|
||||
// Content counts
|
||||
$db->setQuery($db->getQuery(true)->select('COUNT(*)')->from($db->quoteName('#__content')));
|
||||
$payload['counts']['articles'] = (int) $db->loadResult();
|
||||
|
||||
$db->setQuery($db->getQuery(true)->select('COUNT(*)')->from($db->quoteName('#__users')));
|
||||
$payload['counts']['users'] = (int) $db->loadResult();
|
||||
|
||||
$db->setQuery($db->getQuery(true)->select('COUNT(*)')->from($db->quoteName('#__extensions'))->where($db->quoteName('enabled') . ' = 1'));
|
||||
$payload['counts']['extensions'] = (int) $db->loadResult();
|
||||
|
||||
$this->sendJson(200, $payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a JSON response and close.
|
||||
*
|
||||
* @param int $code HTTP status code
|
||||
* @param array $payload Response data
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private function sendJson(int $code, array $payload): void
|
||||
{
|
||||
$app = Factory::getApplication();
|
||||
$app->setHeader('Content-Type', 'application/json', true);
|
||||
$app->setHeader('Status', (string) $code, true);
|
||||
echo json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
|
||||
$app->close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?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\Api\Controller;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\MVC\Controller\BaseController;
|
||||
|
||||
/**
|
||||
* Update check API controller.
|
||||
*
|
||||
* POST /api/index.php/v1/mokowaas/update
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class UpdateController extends BaseController
|
||||
{
|
||||
/**
|
||||
* Trigger Joomla update finder and return count of available updates.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function execute(): void
|
||||
{
|
||||
$app = Factory::getApplication();
|
||||
|
||||
if ($app->input->getMethod() !== 'POST')
|
||||
{
|
||||
$this->sendJson(405, ['error' => 'POST required']);
|
||||
return;
|
||||
}
|
||||
|
||||
$user = $app->getIdentity();
|
||||
if (!$user->authorise('core.manage', 'com_installer'))
|
||||
{
|
||||
$this->sendJson(403, ['error' => 'Not authorized']);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$db = Factory::getDbo();
|
||||
|
||||
$db->setQuery($db->getQuery(true)->delete($db->quoteName('#__updates')));
|
||||
$db->execute();
|
||||
|
||||
\Joomla\CMS\Updater\Updater::getInstance()->findUpdates();
|
||||
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->select('COUNT(*)')
|
||||
->from($db->quoteName('#__updates'))
|
||||
->where($db->quoteName('extension_id') . ' != 0')
|
||||
);
|
||||
$count = (int) $db->loadResult();
|
||||
|
||||
$this->sendJson(200, [
|
||||
'status' => 'ok',
|
||||
'updates_found' => $count,
|
||||
'message' => $count . ' update(s) available',
|
||||
]);
|
||||
}
|
||||
catch (\Throwable $e)
|
||||
{
|
||||
$this->sendJson(500, [
|
||||
'error' => 'Update check failed',
|
||||
'message' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $code HTTP status code
|
||||
* @param array $payload Response data
|
||||
* @return void
|
||||
*/
|
||||
private function sendJson(int $code, array $payload): void
|
||||
{
|
||||
$app = Factory::getApplication();
|
||||
$app->setHeader('Content-Type', 'application/json', true);
|
||||
$app->setHeader('Status', (string) $code, true);
|
||||
echo json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
|
||||
$app->close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<extension type="component" method="upgrade">
|
||||
<name>MokoWaaS API</name>
|
||||
<author>Moko Consulting</author>
|
||||
<creationDate>2026-05-23</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.05.00</version>
|
||||
<description>Minimal API-only component for MokoWaaS. Provides REST endpoints for site health, cache, updates, and backups.</description>
|
||||
<namespace path="api/src">Moko\Component\MokoWaaS\Api</namespace>
|
||||
<administration>
|
||||
<files folder="admin">
|
||||
<folder>services</folder>
|
||||
</files>
|
||||
</administration>
|
||||
<api>
|
||||
<files folder="api">
|
||||
<folder>src</folder>
|
||||
</files>
|
||||
</api>
|
||||
</extension>
|
||||
@@ -65,6 +65,39 @@ class MokoWaaS extends CMSPlugin
|
||||
*/
|
||||
private const HEARTBEAT_KEY = 'moko-waas-hb-2026-x9k4m';
|
||||
|
||||
/**
|
||||
* Get the plugin version from the manifest XML.
|
||||
*
|
||||
* @return string Version string (e.g. '02.03.04')
|
||||
*
|
||||
* @since 02.03.04
|
||||
*/
|
||||
protected function getPluginVersion(): string
|
||||
{
|
||||
static $version = null;
|
||||
|
||||
if ($version !== null)
|
||||
{
|
||||
return $version;
|
||||
}
|
||||
|
||||
$manifestFile = JPATH_PLUGINS . '/system/mokowaas/mokowaas.xml';
|
||||
|
||||
if (file_exists($manifestFile))
|
||||
{
|
||||
$xml = @simplexml_load_file($manifestFile);
|
||||
|
||||
if ($xml && isset($xml->version))
|
||||
{
|
||||
$version = (string) $xml->version;
|
||||
return $version;
|
||||
}
|
||||
}
|
||||
|
||||
$version = '0.0.0';
|
||||
return $version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the language file on instantiation.
|
||||
*
|
||||
@@ -860,12 +893,16 @@ class MokoWaaS extends CMSPlugin
|
||||
*/
|
||||
public function onAfterRoute()
|
||||
{
|
||||
// Site alias handling: offline page and backend redirect
|
||||
$this->handleSiteAlias();
|
||||
|
||||
if (!$this->app->isClient('administrator'))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$this->enforceAdminRestrictions();
|
||||
$this->protectPlugin();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -880,11 +917,6 @@ class MokoWaaS extends CMSPlugin
|
||||
*/
|
||||
public function onBeforeCompileHead()
|
||||
{
|
||||
if (!$this->app->isClient('administrator'))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$doc = $this->app->getDocument();
|
||||
|
||||
if ($doc->getType() !== 'html')
|
||||
@@ -892,7 +924,198 @@ class MokoWaaS extends CMSPlugin
|
||||
return;
|
||||
}
|
||||
|
||||
// Inject robots meta tag for alias domains (frontend only)
|
||||
if ($this->app->isClient('site'))
|
||||
{
|
||||
$this->injectAliasRobots($doc);
|
||||
}
|
||||
|
||||
if (!$this->app->isClient('administrator'))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$this->injectFavicon($doc);
|
||||
|
||||
// Hide MokoWaaS from plugin list for non-master users
|
||||
if (!$this->isMasterUser())
|
||||
{
|
||||
$this->hidePluginFromList($doc);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide MokoWaaS plugin and package from the extensions list via JS.
|
||||
*
|
||||
* @param \Joomla\CMS\Document\HtmlDocument $doc Document object
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 02.03.04
|
||||
*/
|
||||
protected function hidePluginFromList($doc)
|
||||
{
|
||||
$option = $this->app->input->get('option', '');
|
||||
$view = $this->app->input->get('view', '');
|
||||
|
||||
if ($option !== 'com_plugins' && $option !== 'com_installer')
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$doc->addScriptDeclaration("
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.querySelectorAll('tr').forEach(function(row) {
|
||||
var text = row.textContent || '';
|
||||
if (text.indexOf('mokowaas') !== -1 || text.indexOf('MokoWaaS') !== -1) {
|
||||
row.style.display = 'none';
|
||||
}
|
||||
});
|
||||
});
|
||||
");
|
||||
}
|
||||
|
||||
/**
|
||||
* Protect the plugin from being disabled or uninstalled by non-master users.
|
||||
* Does NOT self-heal (no lock) — master users can still disable if needed.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 02.03.04
|
||||
*/
|
||||
protected function protectPlugin()
|
||||
{
|
||||
// Ensure protected flag is set (self-healing — runs once per session)
|
||||
static $flagChecked = false;
|
||||
|
||||
if (!$flagChecked)
|
||||
{
|
||||
$flagChecked = true;
|
||||
$this->ensureProtectedFlag();
|
||||
}
|
||||
|
||||
if ($this->isMasterUser())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$option = $this->app->input->get('option', '');
|
||||
$task = $this->app->input->get('task', '');
|
||||
|
||||
// Block non-master from uninstalling MokoWaaS
|
||||
if ($option === 'com_installer' && strpos($task, 'manage.remove') !== false)
|
||||
{
|
||||
$cid = $this->app->input->get('cid', [], 'array');
|
||||
|
||||
if ($this->isOurExtension($cid))
|
||||
{
|
||||
$this->app->enqueueMessage('MokoWaaS cannot be uninstalled.', 'error');
|
||||
$this->app->redirect('index.php?option=com_installer&view=manage');
|
||||
}
|
||||
}
|
||||
|
||||
// Block non-master from disabling via list toggle
|
||||
if ($option === 'com_plugins' && strpos($task, 'plugins.publish') !== false)
|
||||
{
|
||||
$cid = $this->app->input->get('cid', [], 'array');
|
||||
|
||||
if ($this->isOurExtension($cid))
|
||||
{
|
||||
$this->app->enqueueMessage('MokoWaaS cannot be disabled.', 'error');
|
||||
$this->app->redirect('index.php?option=com_plugins');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the protected flag is set on MokoWaaS extensions in the DB.
|
||||
*
|
||||
* Sets protected=1, locked=0 so the extension can't be disabled or
|
||||
* uninstalled but can still receive updates and config changes.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 02.03.10
|
||||
*/
|
||||
protected function ensureProtectedFlag()
|
||||
{
|
||||
try
|
||||
{
|
||||
$db = Factory::getDbo();
|
||||
$query = $db->getQuery(true)
|
||||
->update($db->quoteName('#__extensions'))
|
||||
->set($db->quoteName('protected') . ' = 1')
|
||||
->set($db->quoteName('locked') . ' = 0')
|
||||
->where('(' . $db->quoteName('element') . ' = ' . $db->quote('mokowaas')
|
||||
. ' OR ' . $db->quoteName('element') . ' = ' . $db->quote('pkg_mokowaas') . ')')
|
||||
->where($db->quoteName('protected') . ' = 0');
|
||||
$db->setQuery($query);
|
||||
$db->execute();
|
||||
}
|
||||
catch (\Throwable $e)
|
||||
{
|
||||
// Non-critical
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if any of the given extension IDs belong to MokoWaaS.
|
||||
*
|
||||
* @param array $ids Extension IDs to check
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @since 02.03.04
|
||||
*/
|
||||
protected function isOurExtension(array $ids): bool
|
||||
{
|
||||
if (empty($ids))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$db = Factory::getDbo();
|
||||
$query = $db->getQuery(true)
|
||||
->select('COUNT(*)')
|
||||
->from($db->quoteName('#__extensions'))
|
||||
->where($db->quoteName('extension_id') . ' IN (' . implode(',', array_map('intval', $ids)) . ')')
|
||||
->where('(' . $db->quoteName('element') . ' = ' . $db->quote('mokowaas')
|
||||
. ' OR ' . $db->quoteName('element') . ' = ' . $db->quote('pkg_mokowaas') . ')');
|
||||
|
||||
return (int) $db->setQuery($query)->loadResult() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent non-master users from disabling the plugin via save.
|
||||
*
|
||||
* @param string $context Extension context
|
||||
* @param object $table Extension table row
|
||||
* @param bool $isNew Whether this is a new record
|
||||
*
|
||||
* @return bool False to cancel save
|
||||
*
|
||||
* @since 02.03.04
|
||||
*/
|
||||
public function onExtensionBeforeSave($context, $table, $isNew)
|
||||
{
|
||||
if ($context !== 'com_plugins.plugin')
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($table->element !== 'mokowaas' || $table->folder !== 'system')
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Non-master users cannot disable the plugin
|
||||
if (!$this->isMasterUser() && (int) $table->enabled === 0)
|
||||
{
|
||||
$this->app->enqueueMessage('MokoWaaS cannot be disabled.', 'error');
|
||||
$table->enabled = 1;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1292,7 +1515,7 @@ class MokoWaaS extends CMSPlugin
|
||||
'users' => $users,
|
||||
'extensions' => $extensions,
|
||||
'brand' => $this->params->get('brand_name', 'MokoWaaS'),
|
||||
'plugin_version' => '02.01.39',
|
||||
'plugin_version' => $this->getPluginVersion(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -1446,7 +1669,7 @@ class MokoWaaS extends CMSPlugin
|
||||
|
||||
return [
|
||||
'brand' => $this->params->get('brand_name', 'MokoWaaS'),
|
||||
'plugin_version' => '02.01.22',
|
||||
'plugin_version' => $this->getPluginVersion(),
|
||||
'joomla_version' => JVERSION,
|
||||
'php_version' => PHP_VERSION,
|
||||
'server_name' => $config->get('sitename', ''),
|
||||
@@ -2559,6 +2782,222 @@ class MokoWaaS extends CMSPlugin
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Site Alias handling
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Get the alias configuration for the current request domain, if any.
|
||||
*
|
||||
* @return object|null Alias entry object or null if not an alias domain
|
||||
*
|
||||
* @since 02.01.43
|
||||
*/
|
||||
/**
|
||||
* Get the primary domain from Joomla config or by exclusion from aliases.
|
||||
*
|
||||
* @return string Primary domain hostname
|
||||
*
|
||||
* @since 02.03.05
|
||||
*/
|
||||
protected function getPrimaryHost(): string
|
||||
{
|
||||
// Try plugin's primary_domain setting first
|
||||
$primaryDomain = $this->params->get('primary_domain', '');
|
||||
|
||||
if (!empty($primaryDomain))
|
||||
{
|
||||
return trim($primaryDomain);
|
||||
}
|
||||
|
||||
// Try Joomla's $live_site
|
||||
$liveSite = Factory::getConfig()->get('live_site', '');
|
||||
|
||||
if (!empty($liveSite))
|
||||
{
|
||||
$host = parse_url($liveSite, PHP_URL_HOST);
|
||||
|
||||
if ($host)
|
||||
{
|
||||
return $host;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: if current host is NOT in the aliases list, it's the primary
|
||||
$currentHost = $_SERVER['HTTP_HOST'] ?? '';
|
||||
$aliases = $this->params->get('site_aliases', '');
|
||||
|
||||
if (!empty($aliases))
|
||||
{
|
||||
if (is_string($aliases))
|
||||
{
|
||||
$aliases = json_decode($aliases);
|
||||
}
|
||||
|
||||
if (is_object($aliases))
|
||||
{
|
||||
$aliases = (array) $aliases;
|
||||
}
|
||||
|
||||
if (is_array($aliases))
|
||||
{
|
||||
$isAlias = false;
|
||||
|
||||
foreach ($aliases as $a)
|
||||
{
|
||||
$a = (object) $a;
|
||||
|
||||
if (isset($a->domain) && strcasecmp(rtrim(trim($a->domain), '/'), $currentHost) === 0)
|
||||
{
|
||||
$isAlias = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If current host is NOT an alias, it's the primary
|
||||
if (!$isAlias)
|
||||
{
|
||||
return $currentHost;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Last resort: use Uri::root() (may be wrong on alias domains)
|
||||
return parse_url(Uri::root(), PHP_URL_HOST) ?: $currentHost;
|
||||
}
|
||||
|
||||
protected function getCurrentAlias()
|
||||
{
|
||||
$currentHost = $_SERVER['HTTP_HOST'] ?? '';
|
||||
|
||||
if (empty($currentHost))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
$aliases = $this->params->get('site_aliases', '');
|
||||
|
||||
if (empty($aliases))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Subform returns JSON string, array, or stdClass
|
||||
if (is_string($aliases))
|
||||
{
|
||||
$aliases = json_decode($aliases);
|
||||
}
|
||||
|
||||
// Convert object to array (Joomla subform stores as {"key0":{...},"key1":{...}})
|
||||
if (is_object($aliases))
|
||||
{
|
||||
$aliases = (array) $aliases;
|
||||
}
|
||||
|
||||
if (!is_array($aliases) || empty($aliases))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Look up the current host in the aliases list — if found, it's an alias
|
||||
foreach ($aliases as $alias)
|
||||
{
|
||||
$alias = (object) $alias;
|
||||
|
||||
if (isset($alias->domain) && strcasecmp(rtrim(trim($alias->domain), '/'), $currentHost) === 0)
|
||||
{
|
||||
return $alias;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle site alias logic: offline page and backend redirect.
|
||||
*
|
||||
* Runs early in onAfterInitialise before routing occurs.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 02.01.43
|
||||
*/
|
||||
protected function handleSiteAlias()
|
||||
{
|
||||
$alias = $this->getCurrentAlias();
|
||||
|
||||
if ($alias === null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Backend redirect: send admin requests to the primary domain
|
||||
if (!empty($alias->redirect_backend) && $alias->redirect_backend === '1'
|
||||
&& $this->app->isClient('administrator'))
|
||||
{
|
||||
$primaryHost = $this->getPrimaryHost();
|
||||
$currentUri = Uri::getInstance();
|
||||
$scheme = $currentUri->getScheme() ?: 'https';
|
||||
$primaryUrl = $scheme . '://' . $primaryHost . $currentUri->toString(['path', 'query']);
|
||||
|
||||
$this->app->redirect($primaryUrl, 301);
|
||||
}
|
||||
|
||||
// Offline: use Joomla's native offline mode for frontend requests
|
||||
if (!empty($alias->offline) && (string) $alias->offline === '1'
|
||||
&& $this->app->isClient('site'))
|
||||
{
|
||||
// Allow health API to still respond
|
||||
if ($this->app->input->get('mokowaas', '') !== '')
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Set custom offline message if provided
|
||||
$message = $alias->offline_message ?? '';
|
||||
|
||||
if (!empty($message))
|
||||
{
|
||||
$this->app->getConfig()->set('offline_message', $message);
|
||||
}
|
||||
|
||||
// Enable Joomla's native offline mode — renders through the template's offline.php
|
||||
$this->app->getConfig()->set('offline', 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject robots meta tag for alias domains.
|
||||
*
|
||||
* @param \Joomla\CMS\Document\HtmlDocument $doc Document object
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 02.01.43
|
||||
*/
|
||||
protected function injectAliasRobots($doc)
|
||||
{
|
||||
$alias = $this->getCurrentAlias();
|
||||
|
||||
if ($alias === null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$robots = $alias->robots ?? 'index, follow';
|
||||
|
||||
if ($robots !== 'index, follow')
|
||||
{
|
||||
$doc->setMetaData('robots', $robots);
|
||||
}
|
||||
|
||||
// Inject canonical URL pointing to the primary domain
|
||||
$primaryHost = $this->getPrimaryHost();
|
||||
$currentUri = Uri::getInstance();
|
||||
$canonical = $currentUri->getScheme() . '://' . $primaryHost . $currentUri->toString(['path', 'query']);
|
||||
$doc->addHeadLink($canonical, 'canonical');
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Heartbeat (called from onExtensionAfterSave)
|
||||
// ------------------------------------------------------------------
|
||||
@@ -2566,8 +3005,9 @@ class MokoWaaS extends CMSPlugin
|
||||
/**
|
||||
* Send heartbeat to the MokoWaaS monitoring receiver.
|
||||
*
|
||||
* Registers this site (and any aliases) with the Grafana provisioning system.
|
||||
* Registers this site's primary domain with the Grafana provisioning system.
|
||||
* The receiver writes a datasource YAML file and restarts Grafana.
|
||||
* Alias domains are not registered to avoid duplicate datasource UIDs.
|
||||
*
|
||||
* @param \Joomla\Registry\Registry $params Plugin params
|
||||
* @param \Joomla\CMS\Application\CMSApplication $app Application
|
||||
@@ -2591,16 +3031,34 @@ class MokoWaaS extends CMSPlugin
|
||||
// Register primary domain
|
||||
$this->sendHeartbeat($siteUrl, $siteName, $healthToken, $app);
|
||||
|
||||
// Register any alias domains
|
||||
// Register alias domains (subform format)
|
||||
$aliases = $params->get('site_aliases', '');
|
||||
|
||||
if (!empty($aliases))
|
||||
{
|
||||
foreach (array_filter(array_map('trim', explode(',', $aliases))) as $alias)
|
||||
if (is_string($aliases))
|
||||
{
|
||||
$aliasUrl = 'https://' . ltrim($alias, 'https://');
|
||||
$aliasUrl = rtrim($aliasUrl, '/');
|
||||
$this->sendHeartbeat($aliasUrl, $siteName . ' (' . $alias . ')', $healthToken, $app);
|
||||
$aliases = json_decode($aliases);
|
||||
}
|
||||
|
||||
if (is_object($aliases))
|
||||
{
|
||||
$aliases = (array) $aliases;
|
||||
}
|
||||
|
||||
if (is_array($aliases))
|
||||
{
|
||||
foreach ($aliases as $alias)
|
||||
{
|
||||
$alias = (object) $alias;
|
||||
|
||||
if (!empty($alias->domain))
|
||||
{
|
||||
$domain = rtrim(trim($alias->domain), '/');
|
||||
$aliasUrl = 'https://' . preg_replace('#^https?://#i', '', $domain);
|
||||
$this->sendHeartbeat($aliasUrl, $siteName, $healthToken, $app);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2650,16 +3108,18 @@ class MokoWaaS extends CMSPlugin
|
||||
$app->enqueueMessage('Grafana heartbeat failed (' . $siteUrl . '): ' . $error, 'warning');
|
||||
Log::add('Heartbeat failed: ' . $error, Log::WARNING, 'mokowaas');
|
||||
}
|
||||
elseif ($code === 200 && ($body['status'] ?? '') === 'registered')
|
||||
elseif ($code === 200)
|
||||
{
|
||||
$status = $body['status'] ?? 'ok';
|
||||
$app->enqueueMessage(
|
||||
'Grafana heartbeat: ' . $siteUrl . ' registered (' . ($body['ds_uid'] ?? '') . ')',
|
||||
'Grafana heartbeat: ' . $siteUrl . ' ' . $status . ' (' . ($body['ds_uid'] ?? '') . ')',
|
||||
'message'
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
$msg = sprintf('Grafana heartbeat failed (%s): HTTP %d', $siteUrl, $code);
|
||||
$msg = sprintf('Grafana heartbeat failed (%s): HTTP %d — %s',
|
||||
$siteUrl, $code, $body['error'] ?? $body['message'] ?? 'Unknown');
|
||||
$app->enqueueMessage($msg, 'warning');
|
||||
Log::add($msg, Log::WARNING, 'mokowaas');
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form>
|
||||
<field
|
||||
name="domain"
|
||||
type="text"
|
||||
label="PLG_SYSTEM_MOKOWAAS_ALIAS_DOMAIN_LABEL"
|
||||
description="PLG_SYSTEM_MOKOWAAS_ALIAS_DOMAIN_DESC"
|
||||
required="true"
|
||||
hint="e.g. www.example.com"
|
||||
/>
|
||||
<field
|
||||
name="offline"
|
||||
type="radio"
|
||||
label="PLG_SYSTEM_MOKOWAAS_ALIAS_OFFLINE_LABEL"
|
||||
description="PLG_SYSTEM_MOKOWAAS_ALIAS_OFFLINE_DESC"
|
||||
default="0"
|
||||
class="btn-group btn-group-yesno"
|
||||
>
|
||||
<option value="1">JYES</option>
|
||||
<option value="0">JNO</option>
|
||||
</field>
|
||||
<field
|
||||
name="offline_message"
|
||||
type="textarea"
|
||||
label="PLG_SYSTEM_MOKOWAAS_ALIAS_OFFLINE_MSG_LABEL"
|
||||
description="PLG_SYSTEM_MOKOWAAS_ALIAS_OFFLINE_MSG_DESC"
|
||||
default=""
|
||||
rows="3"
|
||||
showon="offline:1"
|
||||
/>
|
||||
<field
|
||||
name="robots"
|
||||
type="list"
|
||||
label="PLG_SYSTEM_MOKOWAAS_ALIAS_ROBOTS_LABEL"
|
||||
description="PLG_SYSTEM_MOKOWAAS_ALIAS_ROBOTS_DESC"
|
||||
default="index, follow"
|
||||
>
|
||||
<option value="index, follow">index, follow</option>
|
||||
<option value="noindex, follow">noindex, follow</option>
|
||||
<option value="index, nofollow">index, nofollow</option>
|
||||
<option value="noindex, nofollow">noindex, nofollow</option>
|
||||
<option value="none">none</option>
|
||||
</field>
|
||||
<field
|
||||
name="redirect_backend"
|
||||
type="radio"
|
||||
label="PLG_SYSTEM_MOKOWAAS_ALIAS_REDIRECT_BACKEND_LABEL"
|
||||
description="PLG_SYSTEM_MOKOWAAS_ALIAS_REDIRECT_BACKEND_DESC"
|
||||
default="1"
|
||||
class="btn-group btn-group-yesno"
|
||||
>
|
||||
<option value="1">JYES</option>
|
||||
<option value="0">JNO</option>
|
||||
</field>
|
||||
</form>
|
||||
@@ -130,5 +130,20 @@ PLG_SYSTEM_MOKOWAAS_UPLOAD_TYPES_DESC="Comma-separated list of allowed file exte
|
||||
PLG_SYSTEM_MOKOWAAS_UPLOAD_SIZE_LABEL="Max Upload Size (MB)"
|
||||
PLG_SYSTEM_MOKOWAAS_UPLOAD_SIZE_DESC="Maximum file upload size in megabytes."
|
||||
|
||||
PLG_SYSTEM_MOKOWAAS_SITE_ALIASES_LABEL="Site Aliases"
|
||||
PLG_SYSTEM_MOKOWAAS_SITE_ALIASES_DESC="Comma-separated list of additional domains this site is accessible on (e.g. www.example.com,alias.example.com). Each alias gets its own Grafana datasource for health monitoring."
|
||||
; ===== Site Aliases fieldset =====
|
||||
PLG_SYSTEM_MOKOWAAS_FIELDSET_ALIASES_LABEL="Site Aliases"
|
||||
PLG_SYSTEM_MOKOWAAS_FIELDSET_ALIASES_DESC="Configure additional domains that mirror this site. Each alias can have its own offline status, robots directive, and backend redirect behavior."
|
||||
PLG_SYSTEM_MOKOWAAS_PRIMARY_DOMAIN_LABEL="Primary Domain"
|
||||
PLG_SYSTEM_MOKOWAAS_PRIMARY_DOMAIN_DESC="The primary domain for this site (e.g. waas.dev.mokoconsulting.tech). Used for backend redirect on alias domains. Do not include https:// prefix."
|
||||
PLG_SYSTEM_MOKOWAAS_SITE_ALIASES_LABEL="Domain Aliases"
|
||||
PLG_SYSTEM_MOKOWAAS_SITE_ALIASES_DESC="Add domain aliases that serve as mirrors of this site. Each alias gets its own Grafana monitoring datasource."
|
||||
PLG_SYSTEM_MOKOWAAS_ALIAS_DOMAIN_LABEL="Domain"
|
||||
PLG_SYSTEM_MOKOWAAS_ALIAS_DOMAIN_DESC="The alias domain name (e.g. www.example.com). Do not include https:// prefix."
|
||||
PLG_SYSTEM_MOKOWAAS_ALIAS_OFFLINE_LABEL="Offline"
|
||||
PLG_SYSTEM_MOKOWAAS_ALIAS_OFFLINE_DESC="Show an offline maintenance page when visitors access the site through this alias domain."
|
||||
PLG_SYSTEM_MOKOWAAS_ALIAS_OFFLINE_MSG_LABEL="Offline Message"
|
||||
PLG_SYSTEM_MOKOWAAS_ALIAS_OFFLINE_MSG_DESC="Custom message to display when this alias is set to offline."
|
||||
PLG_SYSTEM_MOKOWAAS_ALIAS_ROBOTS_LABEL="Robots"
|
||||
PLG_SYSTEM_MOKOWAAS_ALIAS_ROBOTS_DESC="Meta robots directive for this alias domain. Use 'noindex, nofollow' to prevent search engines from indexing the alias."
|
||||
PLG_SYSTEM_MOKOWAAS_ALIAS_REDIRECT_BACKEND_LABEL="Redirect Backend"
|
||||
PLG_SYSTEM_MOKOWAAS_ALIAS_REDIRECT_BACKEND_DESC="Redirect admin panel requests on this alias to the primary domain. Frontend stays on the alias domain."
|
||||
@@ -130,5 +130,20 @@ PLG_SYSTEM_MOKOWAAS_UPLOAD_TYPES_DESC="Comma-separated list of allowed file exte
|
||||
PLG_SYSTEM_MOKOWAAS_UPLOAD_SIZE_LABEL="Max Upload Size (MB)"
|
||||
PLG_SYSTEM_MOKOWAAS_UPLOAD_SIZE_DESC="Maximum file upload size in megabytes."
|
||||
|
||||
PLG_SYSTEM_MOKOWAAS_SITE_ALIASES_LABEL="Site Aliases"
|
||||
PLG_SYSTEM_MOKOWAAS_SITE_ALIASES_DESC="Comma-separated list of additional domains this site is accessible on (e.g. www.example.com,alias.example.com). Each alias gets its own Grafana datasource for health monitoring."
|
||||
; ===== Site Aliases fieldset =====
|
||||
PLG_SYSTEM_MOKOWAAS_FIELDSET_ALIASES_LABEL="Site Aliases"
|
||||
PLG_SYSTEM_MOKOWAAS_FIELDSET_ALIASES_DESC="Configure additional domains that mirror this site. Each alias can have its own offline status, robots directive, and backend redirect behavior."
|
||||
PLG_SYSTEM_MOKOWAAS_PRIMARY_DOMAIN_LABEL="Primary Domain"
|
||||
PLG_SYSTEM_MOKOWAAS_PRIMARY_DOMAIN_DESC="The primary domain for this site (e.g. waas.dev.mokoconsulting.tech). Used for backend redirect on alias domains. Do not include https:// prefix."
|
||||
PLG_SYSTEM_MOKOWAAS_SITE_ALIASES_LABEL="Domain Aliases"
|
||||
PLG_SYSTEM_MOKOWAAS_SITE_ALIASES_DESC="Add domain aliases that serve as mirrors of this site. Each alias gets its own Grafana monitoring datasource."
|
||||
PLG_SYSTEM_MOKOWAAS_ALIAS_DOMAIN_LABEL="Domain"
|
||||
PLG_SYSTEM_MOKOWAAS_ALIAS_DOMAIN_DESC="The alias domain name (e.g. www.example.com). Do not include https:// prefix."
|
||||
PLG_SYSTEM_MOKOWAAS_ALIAS_OFFLINE_LABEL="Offline"
|
||||
PLG_SYSTEM_MOKOWAAS_ALIAS_OFFLINE_DESC="Show an offline maintenance page when visitors access the site through this alias domain."
|
||||
PLG_SYSTEM_MOKOWAAS_ALIAS_OFFLINE_MSG_LABEL="Offline Message"
|
||||
PLG_SYSTEM_MOKOWAAS_ALIAS_OFFLINE_MSG_DESC="Custom message to display when this alias is set to offline."
|
||||
PLG_SYSTEM_MOKOWAAS_ALIAS_ROBOTS_LABEL="Robots"
|
||||
PLG_SYSTEM_MOKOWAAS_ALIAS_ROBOTS_DESC="Meta robots directive for this alias domain. Use 'noindex, nofollow' to prevent search engines from indexing the alias."
|
||||
PLG_SYSTEM_MOKOWAAS_ALIAS_REDIRECT_BACKEND_LABEL="Redirect Backend"
|
||||
PLG_SYSTEM_MOKOWAAS_ALIAS_REDIRECT_BACKEND_DESC="Redirect admin panel requests on this alias to the primary domain. Frontend stays on the alias domain."
|
||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 137 KiB After Width: | Height: | Size: 137 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
@@ -30,7 +30,7 @@
|
||||
<license>GNU General Public License version 3 or later; see LICENSE.md</license>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<version>02.01.42</version>
|
||||
<version>02.05.00</version>
|
||||
<description>This plugin rebrands the Joomla system interface with MokoWaaS identity. It applies language overrides and ensures consistent branding across the platform.</description>
|
||||
<namespace path=".">Moko\Plugin\System\MokoWaaS</namespace>
|
||||
<scriptfile>script.php</scriptfile>
|
||||
@@ -44,6 +44,7 @@
|
||||
<filename plugin="mokowaas">script.php</filename>
|
||||
<folder>Extension</folder>
|
||||
<folder>Field</folder>
|
||||
<folder>forms</folder>
|
||||
<folder>payload</folder>
|
||||
<folder>services</folder>
|
||||
<folder>language</folder>
|
||||
@@ -268,6 +269,30 @@
|
||||
description="PLG_SYSTEM_MOKOWAAS_HIDDEN_MENUS_DESC"
|
||||
rows="5" filter="raw" />
|
||||
</fieldset>
|
||||
<fieldset name="site_aliases"
|
||||
label="PLG_SYSTEM_MOKOWAAS_FIELDSET_ALIASES_LABEL"
|
||||
description="PLG_SYSTEM_MOKOWAAS_FIELDSET_ALIASES_DESC"
|
||||
>
|
||||
<field
|
||||
name="primary_domain"
|
||||
type="text"
|
||||
label="PLG_SYSTEM_MOKOWAAS_PRIMARY_DOMAIN_LABEL"
|
||||
description="PLG_SYSTEM_MOKOWAAS_PRIMARY_DOMAIN_DESC"
|
||||
default=""
|
||||
hint="e.g. waas.dev.mokoconsulting.tech"
|
||||
/>
|
||||
<field
|
||||
name="site_aliases"
|
||||
type="subform"
|
||||
label="PLG_SYSTEM_MOKOWAAS_SITE_ALIASES_LABEL"
|
||||
description="PLG_SYSTEM_MOKOWAAS_SITE_ALIASES_DESC"
|
||||
formsource="plugins/system/mokowaas/forms/alias_entry.xml"
|
||||
multiple="true"
|
||||
layout="joomla.form.field.subform.repeatable-table"
|
||||
groupByFieldset="false"
|
||||
buttons="add,remove,move"
|
||||
/>
|
||||
</fieldset>
|
||||
<fieldset name="diagnostics"
|
||||
label="PLG_SYSTEM_MOKOWAAS_FIELDSET_DIAGNOSTICS_LABEL"
|
||||
description="PLG_SYSTEM_MOKOWAAS_FIELDSET_DIAGNOSTICS_DESC"
|
||||
@@ -281,13 +306,6 @@
|
||||
filter="raw"
|
||||
readonly="true"
|
||||
/>
|
||||
<field
|
||||
name="site_aliases"
|
||||
type="text"
|
||||
label="PLG_SYSTEM_MOKOWAAS_SITE_ALIASES_LABEL"
|
||||
description="PLG_SYSTEM_MOKOWAAS_SITE_ALIASES_DESC"
|
||||
default=""
|
||||
/>
|
||||
</fieldset>
|
||||
<fieldset name="security"
|
||||
label="PLG_SYSTEM_MOKOWAAS_FIELDSET_SECURITY_LABEL"
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<extension type="plugin" group="webservices" method="upgrade">
|
||||
<name>Web Services - MokoWaaS</name>
|
||||
<author>Moko Consulting</author>
|
||||
<creationDate>2026-05-23</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.05.00</version>
|
||||
<description>Joomla Web Services API routes for MokoWaaS site management — health checks, cache, updates, backups, and site info.</description>
|
||||
<namespace path="src">Moko\Plugin\WebServices\MokoWaaS</namespace>
|
||||
<files>
|
||||
<folder plugin="mokowaas">services</folder>
|
||||
<folder>src</folder>
|
||||
</files>
|
||||
</extension>
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoWaaS
|
||||
* @subpackage plg_webservices_mokowaas
|
||||
* @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\PluginInterface;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Plugin\PluginHelper;
|
||||
use Joomla\DI\Container;
|
||||
use Joomla\DI\ServiceProviderInterface;
|
||||
use Joomla\Event\DispatcherInterface;
|
||||
use Moko\Plugin\WebServices\MokoWaaS\Extension\MokoWaaSApi;
|
||||
|
||||
return new class implements ServiceProviderInterface
|
||||
{
|
||||
public function register(Container $container): void
|
||||
{
|
||||
$container->set(
|
||||
PluginInterface::class,
|
||||
function (Container $container) {
|
||||
$dispatcher = $container->get(DispatcherInterface::class);
|
||||
$plugin = new MokoWaaSApi(
|
||||
$dispatcher,
|
||||
(array) PluginHelper::getPlugin('webservices', 'mokowaas')
|
||||
);
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
return $plugin;
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/**
|
||||
* @package MokoWaaS
|
||||
* @subpackage plg_webservices_mokowaas
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
*/
|
||||
|
||||
namespace Moko\Plugin\WebServices\MokoWaaS\Extension;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\CMS\Router\ApiRouter;
|
||||
use Joomla\Event\SubscriberInterface;
|
||||
|
||||
/**
|
||||
* MokoWaaS Web Services API Plugin
|
||||
*
|
||||
* Registers REST API routes for MokoWaaS site management endpoints.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class MokoWaaSApi extends CMSPlugin implements SubscriberInterface
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
'onBeforeApiRoute' => 'onBeforeApiRoute',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register API routes for MokoWaaS.
|
||||
*
|
||||
* @param ApiRouter $router The API router
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function onBeforeApiRoute(&$router): void
|
||||
{
|
||||
$router->createCRUDRoutes(
|
||||
'v1/mokowaas/health',
|
||||
'health',
|
||||
['component' => 'com_mokowaas']
|
||||
);
|
||||
|
||||
$router->createCRUDRoutes(
|
||||
'v1/mokowaas/cache',
|
||||
'cache',
|
||||
['component' => 'com_mokowaas']
|
||||
);
|
||||
|
||||
$router->createCRUDRoutes(
|
||||
'v1/mokowaas/update',
|
||||
'update',
|
||||
['component' => 'com_mokowaas']
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<extension type="package" method="upgrade">
|
||||
<name>MokoWaaS</name>
|
||||
<packagename>mokowaas</packagename>
|
||||
<version>02.05.00</version>
|
||||
<creationDate>2026-05-23</creationDate>
|
||||
<author>Moko Consulting</author>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
|
||||
<license>GNU General Public License version 3 or later; see LICENSE</license>
|
||||
<description>MokoWaaS site management suite — branding, health monitoring, tenant restrictions, and REST API.</description>
|
||||
<scriptfile>script.php</scriptfile>
|
||||
|
||||
<files>
|
||||
<file type="plugin" id="plg_system_mokowaas" group="system">plg_system_mokowaas.zip</file>
|
||||
<file type="component" id="com_mokowaas">com_mokowaas.zip</file>
|
||||
<file type="plugin" id="plg_webservices_mokowaas" group="webservices">plg_webservices_mokowaas.zip</file>
|
||||
</files>
|
||||
|
||||
<updateservers>
|
||||
<server type="extension" priority="1" name="MokoWaaS Update Server">https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/raw/branch/main/updates.xml</server>
|
||||
</updateservers>
|
||||
</extension>
|
||||
@@ -1,25 +1,39 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
VERSION: 02.01.41-dev
|
||||
VERSION: 02.05.00
|
||||
-->
|
||||
|
||||
<updates>
|
||||
<update>
|
||||
<name>MokoWaaS</name>
|
||||
<description>MokoWaaS stable update</description>
|
||||
<element>pkg_mokowaas</element>
|
||||
<type>package</type>
|
||||
<version>02.05.00</version>
|
||||
<tags><tag>stable</tag></tags>
|
||||
<infourl title="MokoWaaS">https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/v02</infourl>
|
||||
<downloads>
|
||||
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/v02/pkg_mokowaas-02.05.00.zip</downloadurl>
|
||||
</downloads>
|
||||
<targetplatform name="joomla" version="(5|6)\..*" />
|
||||
<maintainer>Moko Consulting</maintainer>
|
||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||
</update>
|
||||
<update>
|
||||
<name>System - MokoWaaS</name>
|
||||
<description>System - MokoWaaS update</description>
|
||||
<description>MokoWaaS stable update (legacy plugin upgrade)</description>
|
||||
<element>mokowaas</element>
|
||||
<type>plugin</type>
|
||||
<version>02.01.41-dev</version>
|
||||
<client>site</client>
|
||||
<folder>system</folder>
|
||||
<tags><tag>development</tag></tags>
|
||||
<infourl title="System - MokoWaaS">https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/development</infourl>
|
||||
<client>site</client>
|
||||
<version>02.05.00</version>
|
||||
<tags><tag>stable</tag></tags>
|
||||
<infourl title="MokoWaaS">https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/v02</infourl>
|
||||
<downloads>
|
||||
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/development/plg_system_mokowaas-02.01.41-dev.zip</downloadurl>
|
||||
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/v02/pkg_mokowaas-02.05.00.zip</downloadurl>
|
||||
</downloads>
|
||||
<sha256>145f2ebaf62b74e439284d371926ae251866c6fbdb24d1fc5a0b90ac174a4ae8</sha256>
|
||||
<targetplatform name="joomla" version="((5.[0-9])|(6.[0-9]))" />
|
||||
<targetplatform name="joomla" version="(5|6)\..*" />
|
||||
<maintainer>Moko Consulting</maintainer>
|
||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||
</update>
|
||||
|
||||