Compare commits
61 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 36658fa8ca | |||
| 5645516845 | |||
| 4ce8c6b4ea | |||
| 01056afe74 | |||
| 3cc39cfa8f | |||
| 0956757445 | |||
| 9c75d0254e | |||
| c847b4a274 | |||
| c93ae27b64 | |||
| 0e28958ede | |||
| 46bb7c31c2 | |||
| 04af4a93a8 | |||
| 3b99c5b6bc | |||
| 1b47876a6c | |||
| 48ff2b2109 | |||
| 0c4857d6e0 | |||
| 9f3e4b9d31 | |||
| 3834ba4c1c | |||
| a8a41e9bad | |||
| 8c927b0a1b | |||
| 21e57eaadc | |||
| fadd3a01cd | |||
| 95097c4d3f | |||
| e71b075d94 | |||
| 1ecc8be8d1 | |||
| 361a58f8cd | |||
| 51ac178281 | |||
| b46da78e6c | |||
| 57a54e8959 | |||
| 1c8625f828 | |||
| 66b19f184c | |||
| 4694e67e1c | |||
| e2e2ac8b56 | |||
| 415eeaac56 | |||
| 4c8bb93952 | |||
| 561fdcd881 | |||
| 0d096acfa8 | |||
| 3db14d29ef | |||
| dfff3c327f | |||
| 4ab3b163f6 | |||
| 60910c2b8b | |||
| 4e0151be1b | |||
| 36d958f31f | |||
| e482a293c9 | |||
| 04a4bf8aba | |||
| b3082f27e3 | |||
| f30d7dd7af | |||
| ecd5b6c786 | |||
| 1f7419f33d | |||
| 171f489e3d | |||
| e808a168cb | |||
| 1f89a323d5 | |||
| 329eca3db6 | |||
| 42b47be564 | |||
| 79ac068bc4 | |||
| c5aef3c939 | |||
| 0d96174f75 | |||
| 6acae6d20f | |||
| 9dacc01a67 | |||
| c7d914f786 | |||
| 729aa3850d |
@@ -9,7 +9,7 @@
|
||||
<display-name>Package - MokoWaaS</display-name>
|
||||
<org>MokoConsulting</org>
|
||||
<description>White-label identity, security hardening, and tenant restriction layer for WaaS-managed Joomla environments</description>
|
||||
<version>02.33.01</version>
|
||||
<version>02.34.08</version>
|
||||
<license spdx="GPL-3.0-or-later">GNU General Public License v3</license>
|
||||
</identity>
|
||||
<governance>
|
||||
|
||||
@@ -48,15 +48,12 @@ jobs:
|
||||
if ! command -v composer &> /dev/null; then
|
||||
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
|
||||
fi
|
||||
if [ -d "/opt/moko-platform/cli" ]; then
|
||||
echo "MOKO_CLI=/opt/moko-platform/cli" >> "$GITHUB_ENV"
|
||||
else
|
||||
git clone --depth 1 --branch main --quiet \
|
||||
"https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/moko-platform.git" \
|
||||
/tmp/moko-platform-api
|
||||
cd /tmp/moko-platform-api && composer install --no-dev --no-interaction --quiet
|
||||
echo "MOKO_CLI=/tmp/moko-platform-api/cli" >> "$GITHUB_ENV"
|
||||
fi
|
||||
rm -rf /tmp/moko-platform-api
|
||||
git clone --depth 1 --branch main --quiet \
|
||||
"https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/moko-platform.git" \
|
||||
/tmp/moko-platform-api
|
||||
cd /tmp/moko-platform-api && composer install --no-dev --no-interaction --quiet
|
||||
echo "MOKO_CLI=/tmp/moko-platform-api/cli" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Bump version
|
||||
run: |
|
||||
|
||||
@@ -1,318 +1,316 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: moko-platform.Release
|
||||
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
|
||||
# PATH: /templates/workflows/universal/auto-release.yml.template
|
||||
# VERSION: 05.00.00
|
||||
# BRIEF: Universal build & release � detects platform from manifest.xml
|
||||
#
|
||||
# +========================================================================+
|
||||
# | UNIVERSAL BUILD & RELEASE PIPELINE |
|
||||
# +========================================================================+
|
||||
# | |
|
||||
# | Reads manifest.xml (joomla|dolibarr|generic) to branch logic. |
|
||||
# | |
|
||||
# | Platform-specific: |
|
||||
# | joomla: XML manifest, updates.xml, type-prefixed packages |
|
||||
# | dolibarr: mod*.class.php, update.txt, dev version reset |
|
||||
# | generic: README-only, no update stream |
|
||||
# | |
|
||||
# +========================================================================+
|
||||
|
||||
name: "Universal: Build & Release"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, closed]
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
action:
|
||||
description: 'Action to perform'
|
||||
required: false
|
||||
type: choice
|
||||
default: release
|
||||
options:
|
||||
- release
|
||||
- promote-rc
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
|
||||
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
# ── PR Opened → Rename branch to RC and build RC release ─────────────────────
|
||||
promote-rc:
|
||||
name: Promote to RC
|
||||
runs-on: release
|
||||
if: >-
|
||||
(github.event.action == 'opened' && github.event.pull_request.merged != true) ||
|
||||
(github.event_name == 'workflow_dispatch' && inputs.action == 'promote-rc')
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup moko-platform tools
|
||||
env:
|
||||
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||
run: |
|
||||
if ! command -v composer &> /dev/null; then
|
||||
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
|
||||
fi
|
||||
# Always fetch latest CLI tools — never use stale cache from previous runs
|
||||
rm -rf /tmp/moko-platform-api
|
||||
git clone --depth 1 --branch main --quiet \
|
||||
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
|
||||
/tmp/moko-platform-api
|
||||
cd /tmp/moko-platform-api
|
||||
composer install --no-dev --no-interaction --quiet
|
||||
|
||||
- name: Rename branch to rc
|
||||
run: |
|
||||
php /tmp/moko-platform-api/cli/branch_rename.php \
|
||||
--from "${{ github.event.pull_request.head.ref || 'dev' }}" --to rc \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||
--api-base "${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" \
|
||||
--pr "${{ github.event.pull_request.number }}"
|
||||
|
||||
- name: Checkout rc and configure git
|
||||
run: |
|
||||
git fetch origin rc
|
||||
git checkout rc
|
||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||
git config --local user.name "gitea-actions[bot]"
|
||||
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||
|
||||
- name: Publish RC release
|
||||
run: |
|
||||
php /tmp/moko-platform-api/cli/release_publish.php \
|
||||
--path . --stability rc --bump minor --branch rc \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||
--skip-update-stream
|
||||
|
||||
- name: Summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "## Promoted to Release Candidate" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Branch renamed to rc, minor bump, RC release built (updates.xml managed by Gitea Pages)" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# ── Merged PR → Build & Release (or promote RC to stable) ────────────────────
|
||||
release:
|
||||
name: Build & Release Pipeline
|
||||
runs-on: release
|
||||
if: >-
|
||||
github.event.pull_request.merged == true ||
|
||||
(github.event_name == 'workflow_dispatch' && inputs.action != 'promote-rc')
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Configure git for bot pushes
|
||||
run: |
|
||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||
git config --local user.name "gitea-actions[bot]"
|
||||
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||
|
||||
- name: Check for merge conflict markers
|
||||
run: |
|
||||
CONFLICTS=$(grep -rn '<<<<<<< \|>>>>>>> \|^=======$' --include='*.php' --include='*.xml' --include='*.css' --include='*.js' --include='*.json' --include='*.md' --include='*.yml' --include='*.yaml' --include='*.ini' --include='*.txt' . 2>/dev/null | grep -v '.git/' || true)
|
||||
if [ -n "$CONFLICTS" ]; then
|
||||
echo "::error::Merge conflict markers found — aborting release"
|
||||
echo "## Release Blocked: Conflict Markers" >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
echo "$CONFLICTS" >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
fi
|
||||
echo "No conflict markers found"
|
||||
|
||||
- name: Setup moko-platform tools
|
||||
env:
|
||||
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_MIRROR_TOKEN }}"}}'
|
||||
run: |
|
||||
# Ensure PHP + Composer are available
|
||||
if ! command -v composer &> /dev/null; then
|
||||
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
|
||||
fi
|
||||
# Always fetch latest CLI tools — never use stale cache from previous runs
|
||||
rm -rf /tmp/moko-platform-api
|
||||
git clone --depth 1 --branch main --quiet \
|
||||
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
|
||||
/tmp/moko-platform-api
|
||||
cd /tmp/moko-platform-api
|
||||
composer install --no-dev --no-interaction --quiet
|
||||
|
||||
|
||||
- name: "Publish stable release"
|
||||
run: |
|
||||
php /tmp/moko-platform-api/cli/release_publish.php \
|
||||
--path . --stability stable --bump minor --branch main \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||
--skip-update-stream
|
||||
|
||||
- name: Update release notes from CHANGELOG.md
|
||||
run: |
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
|
||||
# Extract [Unreleased] section from changelog
|
||||
if [ -f "CHANGELOG.md" ]; then
|
||||
NOTES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md)
|
||||
[ -z "$NOTES" ] && NOTES="Stable release"
|
||||
else
|
||||
NOTES="Stable release"
|
||||
fi
|
||||
|
||||
# Update release body via API
|
||||
RELEASE_ID=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||
"${API_BASE}/releases/tags/stable" | python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
|
||||
|
||||
if [ -n "$RELEASE_ID" ]; then
|
||||
python3 -c "
|
||||
import json, urllib.request
|
||||
body = open('/dev/stdin').read()
|
||||
payload = json.dumps({'body': body}).encode()
|
||||
req = urllib.request.Request(
|
||||
'${API_BASE}/releases/${RELEASE_ID}',
|
||||
data=payload, method='PATCH',
|
||||
headers={
|
||||
'Authorization': 'token ${{ secrets.MOKOGITEA_TOKEN }}',
|
||||
'Content-Type': 'application/json'
|
||||
})
|
||||
urllib.request.urlopen(req)
|
||||
" <<< "$NOTES"
|
||||
echo "Release notes updated from CHANGELOG.md"
|
||||
fi
|
||||
|
||||
# -- STEP 9: Mirror to GitHub (stable only) --------------------------------
|
||||
- name: "Step 9: Mirror release to GitHub"
|
||||
if: >-
|
||||
steps.version.outputs.skip != 'true' &&
|
||||
secrets.GH_MIRROR_TOKEN != ''
|
||||
continue-on-error: true
|
||||
run: |
|
||||
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
||||
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
||||
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
php /tmp/moko-platform-api/cli/release_mirror.php \
|
||||
--version "$VERSION" --tag "$RELEASE_TAG" \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
||||
--gh-token "${{ secrets.GH_MIRROR_TOKEN }}" --gh-repo "$GH_REPO" \
|
||||
--branch main 2>&1 || true
|
||||
echo "GitHub mirror updated" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# -- STEP 10: Sync main branch to GitHub mirror ----------------------------
|
||||
- name: "Step 10: Push main to GitHub mirror"
|
||||
if: >-
|
||||
steps.version.outputs.skip != 'true' &&
|
||||
secrets.GH_MIRROR_TOKEN != ''
|
||||
continue-on-error: true
|
||||
run: |
|
||||
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
|
||||
GH_ORG=$(echo "$GH_REPO" | cut -d/ -f1)
|
||||
GH_NAME=$(echo "$GH_REPO" | cut -d/ -f2)
|
||||
git remote add github "https://x-access-token:${{ secrets.GH_MIRROR_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git" 2>/dev/null || \
|
||||
git remote set-url github "https://x-access-token:${{ secrets.GH_MIRROR_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git"
|
||||
git fetch origin main --depth=1
|
||||
git push github origin/main:refs/heads/main --force 2>/dev/null \
|
||||
&& echo "main branch pushed to GitHub mirror" \
|
||||
|| echo "WARNING: GitHub mirror push failed"
|
||||
|
||||
- name: "Step 11: Delete rc branch and recreate dev from main"
|
||||
if: steps.version.outputs.skip != 'true'
|
||||
continue-on-error: true
|
||||
run: |
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||
|
||||
# Delete rc branch (ephemeral — created by promote-rc)
|
||||
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
|
||||
"${API_BASE}/branches/rc" 2>/dev/null \
|
||||
&& echo "Deleted rc branch" || echo "rc branch not found"
|
||||
|
||||
# Delete dev branch
|
||||
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
|
||||
"${API_BASE}/branches/dev" 2>/dev/null && echo "Deleted dev branch"
|
||||
|
||||
# Recreate dev from main (now includes version bump + changelog promotion)
|
||||
curl -sf -X POST -H "Authorization: token ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
"${API_BASE}/branches" \
|
||||
-d '{"new_branch_name":"dev","old_branch_name":"main"}' 2>/dev/null && echo "Recreated dev from main"
|
||||
|
||||
echo "Pre-release branches cleaned, dev reset from main" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: "Step 12: Create version branch from main"
|
||||
if: steps.version.outputs.skip != 'true'
|
||||
continue-on-error: true
|
||||
run: |
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
||||
BRANCH_NAME="version/${VERSION}"
|
||||
MAIN_SHA=$(git rev-parse HEAD)
|
||||
|
||||
# Delete old version branch if it exists (same version re-release)
|
||||
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" "${API_BASE}/branches/${BRANCH_NAME}" 2>/dev/null && echo "Deleted old ${BRANCH_NAME}"
|
||||
|
||||
# Create version/XX.YY.ZZ from main
|
||||
curl -sf -X POST -H "Authorization: token ${TOKEN}" -H "Content-Type: application/json" "${API_BASE}/branches" -d "{\"new_branch_name\":\"${BRANCH_NAME}\",\"old_branch_name\":\"main\"}" 2>/dev/null && echo "Created ${BRANCH_NAME} from main (${MAIN_SHA})" || echo "WARNING: ${BRANCH_NAME} creation failed"
|
||||
|
||||
echo "Version branch created: ${BRANCH_NAME} (${MAIN_SHA})" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
|
||||
|
||||
# -- Dolibarr post-release: Reset dev version -----------------------------
|
||||
- name: "Post-release: Reset dev version"
|
||||
if: steps.version.outputs.skip != 'true'
|
||||
continue-on-error: true
|
||||
run: |
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
php /tmp/moko-platform-api/cli/version_reset_dev.php \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "${API_BASE}" \
|
||||
--branch dev --path . 2>&1 || true
|
||||
|
||||
# -- Summary --------------------------------------------------------------
|
||||
- name: Pipeline Summary
|
||||
if: always()
|
||||
run: |
|
||||
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
||||
PLATFORM="${{ steps.platform.outputs.platform }}"
|
||||
if [ "${{ steps.version.outputs.skip }}" = "true" ]; then
|
||||
echo "## Release Skipped" >> $GITHUB_STEP_SUMMARY
|
||||
echo "No VERSION in README.md" >> $GITHUB_STEP_SUMMARY
|
||||
elif [ "${{ steps.check.outputs.already_released }}" = "true" ]; then
|
||||
echo "## Already Released — ${VERSION}" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "## Build & Release Complete (${PLATFORM})" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Step | Result |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|------|--------|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Platform | \`${PLATFORM}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Branch | \`${{ steps.version.outputs.branch }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Tag | \`${{ steps.version.outputs.tag }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Release | [View](${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: moko-platform.Release
|
||||
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
|
||||
# PATH: /templates/workflows/universal/auto-release.yml.template
|
||||
# VERSION: 05.00.00
|
||||
# BRIEF: Universal build & release � detects platform from manifest.xml
|
||||
#
|
||||
# +========================================================================+
|
||||
# | UNIVERSAL BUILD & RELEASE PIPELINE |
|
||||
# +========================================================================+
|
||||
# | |
|
||||
# | Reads manifest.xml (joomla|dolibarr|generic) to branch logic. |
|
||||
# | |
|
||||
# | Platform-specific: |
|
||||
# | joomla: XML manifest, updates.xml, type-prefixed packages |
|
||||
# | dolibarr: mod*.class.php, update.txt, dev version reset |
|
||||
# | generic: README-only, no update stream |
|
||||
# | |
|
||||
# +========================================================================+
|
||||
|
||||
name: "Universal: Build & Release"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, closed]
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
action:
|
||||
description: 'Action to perform'
|
||||
required: false
|
||||
type: choice
|
||||
default: release
|
||||
options:
|
||||
- release
|
||||
- promote-rc
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
|
||||
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
# ── PR Opened → Rename branch to RC and build RC release ─────────────────────
|
||||
promote-rc:
|
||||
name: Promote to RC
|
||||
runs-on: release
|
||||
if: >-
|
||||
(github.event.action == 'opened' && github.event.pull_request.merged != true) ||
|
||||
(github.event_name == 'workflow_dispatch' && inputs.action == 'promote-rc')
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup moko-platform tools
|
||||
env:
|
||||
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||
run: |
|
||||
if ! command -v composer &> /dev/null; then
|
||||
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
|
||||
fi
|
||||
rm -rf /tmp/moko-platform-api
|
||||
git clone --depth 1 --branch main --quiet \
|
||||
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
|
||||
/tmp/moko-platform-api
|
||||
cd /tmp/moko-platform-api
|
||||
composer install --no-dev --no-interaction --quiet
|
||||
echo "MOKO_CLI=/tmp/moko-platform-api/cli" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Rename branch to rc
|
||||
run: |
|
||||
php ${MOKO_CLI}/branch_rename.php \
|
||||
--from "${{ github.event.pull_request.head.ref || 'dev' }}" --to rc \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||
--api-base "${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" \
|
||||
--pr "${{ github.event.pull_request.number }}"
|
||||
|
||||
- name: Checkout rc and configure git
|
||||
run: |
|
||||
git fetch origin rc
|
||||
git checkout rc
|
||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||
git config --local user.name "gitea-actions[bot]"
|
||||
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||
|
||||
- name: Publish RC release
|
||||
run: |
|
||||
php ${MOKO_CLI}/release_publish.php \
|
||||
--path . --stability rc --bump minor --branch rc \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||
--skip-update-stream
|
||||
|
||||
- name: Summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "## Promoted to Release Candidate" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Branch renamed to rc, minor bump, RC release built (updates.xml managed by Gitea Pages)" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# ── Merged PR → Build & Release (or promote RC to stable) ────────────────────
|
||||
release:
|
||||
name: Build & Release Pipeline
|
||||
runs-on: release
|
||||
if: >-
|
||||
github.event.pull_request.merged == true ||
|
||||
(github.event_name == 'workflow_dispatch' && inputs.action != 'promote-rc')
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Configure git for bot pushes
|
||||
run: |
|
||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||
git config --local user.name "gitea-actions[bot]"
|
||||
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||
|
||||
- name: Check for merge conflict markers
|
||||
run: |
|
||||
CONFLICTS=$(grep -rn '<<<<<<< \|>>>>>>> \|^=======$' --include='*.php' --include='*.xml' --include='*.css' --include='*.js' --include='*.json' --include='*.md' --include='*.yml' --include='*.yaml' --include='*.ini' --include='*.txt' . 2>/dev/null | grep -v '.git/' || true)
|
||||
if [ -n "$CONFLICTS" ]; then
|
||||
echo "::error::Merge conflict markers found - aborting release"
|
||||
echo "## Release Blocked: Conflict Markers" >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
echo "$CONFLICTS" >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
fi
|
||||
echo "No conflict markers found"
|
||||
|
||||
- name: Setup moko-platform tools
|
||||
env:
|
||||
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_MIRROR_TOKEN }}"}}'
|
||||
run: |
|
||||
if ! command -v composer &> /dev/null; then
|
||||
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
|
||||
fi
|
||||
rm -rf /tmp/moko-platform-api
|
||||
git clone --depth 1 --branch main --quiet \
|
||||
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
|
||||
/tmp/moko-platform-api
|
||||
cd /tmp/moko-platform-api
|
||||
composer install --no-dev --no-interaction --quiet
|
||||
echo "MOKO_CLI=/tmp/moko-platform-api/cli" >> "$GITHUB_ENV"
|
||||
|
||||
- name: "Publish stable release"
|
||||
run: |
|
||||
php ${MOKO_CLI}/release_publish.php \
|
||||
--path . --stability stable --bump minor --branch main \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||
--skip-update-stream
|
||||
|
||||
- name: Update release notes from CHANGELOG.md
|
||||
run: |
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
|
||||
# Extract [Unreleased] section from changelog
|
||||
if [ -f "CHANGELOG.md" ]; then
|
||||
NOTES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md)
|
||||
[ -z "$NOTES" ] && NOTES="Stable release"
|
||||
else
|
||||
NOTES="Stable release"
|
||||
fi
|
||||
|
||||
# Update release body via API
|
||||
RELEASE_ID=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||
"${API_BASE}/releases/tags/stable" | python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
|
||||
|
||||
if [ -n "$RELEASE_ID" ]; then
|
||||
python3 -c "
|
||||
import json, urllib.request
|
||||
body = open('/dev/stdin').read()
|
||||
payload = json.dumps({'body': body}).encode()
|
||||
req = urllib.request.Request(
|
||||
'${API_BASE}/releases/${RELEASE_ID}',
|
||||
data=payload, method='PATCH',
|
||||
headers={
|
||||
'Authorization': 'token ${{ secrets.MOKOGITEA_TOKEN }}',
|
||||
'Content-Type': 'application/json'
|
||||
})
|
||||
urllib.request.urlopen(req)
|
||||
" <<< "$NOTES"
|
||||
echo "Release notes updated from CHANGELOG.md"
|
||||
fi
|
||||
|
||||
# -- STEP 9: Mirror to GitHub (stable only) --------------------------------
|
||||
- name: "Step 9: Mirror release to GitHub"
|
||||
if: >-
|
||||
steps.version.outputs.skip != 'true' &&
|
||||
secrets.GH_MIRROR_TOKEN != ''
|
||||
continue-on-error: true
|
||||
run: |
|
||||
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
||||
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
||||
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
php ${MOKO_CLI}/release_mirror.php \
|
||||
--version "$VERSION" --tag "$RELEASE_TAG" \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
||||
--gh-token "${{ secrets.GH_MIRROR_TOKEN }}" --gh-repo "$GH_REPO" \
|
||||
--branch main 2>&1 || true
|
||||
echo "GitHub mirror updated" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# -- STEP 10: Sync main branch to GitHub mirror ----------------------------
|
||||
- name: "Step 10: Push main to GitHub mirror"
|
||||
if: >-
|
||||
steps.version.outputs.skip != 'true' &&
|
||||
secrets.GH_MIRROR_TOKEN != ''
|
||||
continue-on-error: true
|
||||
run: |
|
||||
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
|
||||
GH_ORG=$(echo "$GH_REPO" | cut -d/ -f1)
|
||||
GH_NAME=$(echo "$GH_REPO" | cut -d/ -f2)
|
||||
git remote add github "https://x-access-token:${{ secrets.GH_MIRROR_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git" 2>/dev/null || \
|
||||
git remote set-url github "https://x-access-token:${{ secrets.GH_MIRROR_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git"
|
||||
git fetch origin main --depth=1
|
||||
git push github origin/main:refs/heads/main --force 2>/dev/null \
|
||||
&& echo "main branch pushed to GitHub mirror" \
|
||||
|| echo "WARNING: GitHub mirror push failed"
|
||||
|
||||
- name: "Step 11: Delete rc branch and recreate dev from main"
|
||||
if: steps.version.outputs.skip != 'true'
|
||||
continue-on-error: true
|
||||
run: |
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||
|
||||
# Delete rc branch (ephemeral - created by promote-rc)
|
||||
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
|
||||
"${API_BASE}/branches/rc" 2>/dev/null \
|
||||
&& echo "Deleted rc branch" || echo "rc branch not found"
|
||||
|
||||
# Delete dev branch
|
||||
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
|
||||
"${API_BASE}/branches/dev" 2>/dev/null && echo "Deleted dev branch"
|
||||
|
||||
# Recreate dev from main (now includes version bump + changelog promotion)
|
||||
curl -sf -X POST -H "Authorization: token ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
"${API_BASE}/branches" \
|
||||
-d '{"new_branch_name":"dev","old_branch_name":"main"}' 2>/dev/null && echo "Recreated dev from main"
|
||||
|
||||
echo "Pre-release branches cleaned, dev reset from main" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: "Step 12: Create version branch from main"
|
||||
if: steps.version.outputs.skip != 'true'
|
||||
continue-on-error: true
|
||||
run: |
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
||||
BRANCH_NAME="version/${VERSION}"
|
||||
MAIN_SHA=$(git rev-parse HEAD)
|
||||
|
||||
# Delete old version branch if it exists (same version re-release)
|
||||
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" "${API_BASE}/branches/${BRANCH_NAME}" 2>/dev/null && echo "Deleted old ${BRANCH_NAME}"
|
||||
|
||||
# Create version/XX.YY.ZZ from main
|
||||
curl -sf -X POST -H "Authorization: token ${TOKEN}" -H "Content-Type: application/json" "${API_BASE}/branches" -d "{\"new_branch_name\":\"${BRANCH_NAME}\",\"old_branch_name\":\"main\"}" 2>/dev/null && echo "Created ${BRANCH_NAME} from main (${MAIN_SHA})" || echo "WARNING: ${BRANCH_NAME} creation failed"
|
||||
|
||||
echo "Version branch created: ${BRANCH_NAME} (${MAIN_SHA})" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
|
||||
|
||||
# -- Dolibarr post-release: Reset dev version -----------------------------
|
||||
- name: "Post-release: Reset dev version"
|
||||
if: steps.version.outputs.skip != 'true'
|
||||
continue-on-error: true
|
||||
run: |
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
php ${MOKO_CLI}/version_reset_dev.php \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "${API_BASE}" \
|
||||
--branch dev --path . 2>&1 || true
|
||||
|
||||
# -- Summary --------------------------------------------------------------
|
||||
- name: Pipeline Summary
|
||||
if: always()
|
||||
run: |
|
||||
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
||||
PLATFORM="${{ steps.platform.outputs.platform }}"
|
||||
if [ "${{ steps.version.outputs.skip }}" = "true" ]; then
|
||||
echo "## Release Skipped" >> $GITHUB_STEP_SUMMARY
|
||||
echo "No VERSION in README.md" >> $GITHUB_STEP_SUMMARY
|
||||
elif [ "${{ steps.check.outputs.already_released }}" = "true" ]; then
|
||||
echo "## Already Released - ${VERSION}" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "## Build & Release Complete (${PLATFORM})" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Step | Result |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|------|--------|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Platform | \`${PLATFORM}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Branch | \`${{ steps.version.outputs.branch }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Tag | \`${{ steps.version.outputs.tag }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Release | [View](${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: moko-platform.Automation
|
||||
# VERSION: 02.33.01
|
||||
# VERSION: 02.34.08
|
||||
# BRIEF: Auto-create feature branch when an issue is opened
|
||||
|
||||
name: "Universal: Issue Branch"
|
||||
|
||||
@@ -66,7 +66,6 @@ jobs:
|
||||
if ! command -v composer &> /dev/null; then
|
||||
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
|
||||
fi
|
||||
# Always fetch latest CLI tools — never use stale cache from previous runs
|
||||
rm -rf /tmp/moko-platform-api
|
||||
git clone --depth 1 --branch main --quiet \
|
||||
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
|
||||
@@ -96,31 +95,23 @@ jobs:
|
||||
release-candidate) SUFFIX="-rc"; TAG="release-candidate" ;;
|
||||
esac
|
||||
|
||||
# Read current version (bump already handled by push workflow)
|
||||
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null)
|
||||
[ -z "$VERSION" ] && VERSION="00.00.01"
|
||||
|
||||
# Strip any existing suffix from version before applying stability
|
||||
VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//')
|
||||
|
||||
# RC and stable consolidate dev patches into a clean minor bump
|
||||
# e.g. 02.33.15 → 02.34.00 (not 02.33.15-rc)
|
||||
# Bump version via CLI: patch for dev/alpha/beta, minor for RC
|
||||
case "$STABILITY" in
|
||||
release-candidate)
|
||||
MAJOR=$(echo "$VERSION" | cut -d. -f1)
|
||||
MINOR=$(echo "$VERSION" | cut -d. -f2)
|
||||
MINOR=$(printf "%02d" $((10#$MINOR + 1)))
|
||||
VERSION="${MAJOR}.${MINOR}.00"
|
||||
;;
|
||||
release-candidate) BUMP="minor" ;;
|
||||
*) BUMP="patch" ;;
|
||||
esac
|
||||
|
||||
php ${MOKO_CLI}/version_bump.php --path . $([ "$BUMP" = "minor" ] && echo "--minor") 2>/dev/null || true
|
||||
|
||||
# Set stability suffix and verify consistency
|
||||
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null || echo "00.00.01")
|
||||
VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//')
|
||||
|
||||
php ${MOKO_CLI}/version_set_platform.php \
|
||||
--path . --version "$VERSION" --branch "${{ github.ref_name }}" --stability "$STABILITY" 2>/dev/null || true
|
||||
|
||||
# Verify version consistency across all files
|
||||
php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true
|
||||
|
||||
# Update VERSION variable with suffix
|
||||
# Append suffix for output
|
||||
if [ -n "$SUFFIX" ]; then
|
||||
VERSION="${VERSION}${SUFFIX}"
|
||||
fi
|
||||
@@ -212,55 +203,8 @@ jobs:
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
||||
--repo "${GITEA_REPO}" --output /tmp || true
|
||||
|
||||
- name: Update updates.xml
|
||||
if: steps.platform.outputs.platform == 'joomla'
|
||||
run: |
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||
SHA256="${{ steps.package.outputs.sha256_zip }}"
|
||||
|
||||
if [ ! -f "updates.xml" ]; then
|
||||
echo "No updates.xml -- skipping"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
SHA_FLAG=""
|
||||
[ -n "$SHA256" ] && SHA_FLAG="--sha ${SHA256}"
|
||||
|
||||
php ${MOKO_CLI}/updates_xml_build.php \
|
||||
--path . --version "${VERSION}" --stability "${STABILITY}" \
|
||||
--gitea-url "${GITEA_URL}" --org "${GITEA_ORG}" --repo "${GITEA_REPO}" \
|
||||
${SHA_FLAG}
|
||||
|
||||
# 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 push origin HEAD 2>&1 || echo "WARNING: push failed"
|
||||
fi
|
||||
|
||||
- name: "Sync updates.xml to all branches"
|
||||
if: steps.platform.outputs.platform == 'joomla'
|
||||
run: |
|
||||
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}" -- updates.xml 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
|
||||
# updates.xml is generated dynamically by MokoGitea license server
|
||||
# No need to build, commit, or sync updates.xml from workflows
|
||||
|
||||
- name: "Delete lesser pre-release channels (cascade)"
|
||||
continue-on-error: true
|
||||
|
||||
+2
-1
@@ -14,7 +14,7 @@
|
||||
INGROUP: MokoWaaS.Documentation
|
||||
REPO: https://github.com/mokoconsulting-tech/mokowaas
|
||||
PATH: ./CHANGELOG.md
|
||||
VERSION: 02.33.01
|
||||
VERSION: 02.34.08
|
||||
BRIEF: Version history using `Keep a Changelog`
|
||||
-->
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
|
||||
|
||||
### Changed
|
||||
- Move security hardening methods (protectPlugin, ensureProtectedFlag, isOurExtension) from core plugin to firewall plugin (#155)
|
||||
- Admin menu module uses native Joomla MetisMenu CSS classes
|
||||
- Helpdesk icon changed to fa-handshake-angle, .htaccess to fa-solid fa-file-code
|
||||
- clearCache purges all cache files recursively (replaces Regular Labs Cache Cleaner behavior)
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@
|
||||
DEFGROUP: Joomla.Plugin
|
||||
INGROUP: MokoWaaS.Documentation
|
||||
REPO: https://github.com/mokoconsulting-tech/mokowaas
|
||||
VERSION: 02.33.01
|
||||
VERSION: 02.34.08
|
||||
PATH: ./CODE_OF_CONDUCT.md
|
||||
BRIEF: Reference + packaging repo for Moko Consulting Developer GPT Other Default
|
||||
-->
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@
|
||||
DEFGROUP: mokoconsulting-tech.MokoWaaSBrand
|
||||
INGROUP: MokoStandards.Governance
|
||||
REPO: https://github.com/mokoconsulting-tech/MokoWaaSBrand
|
||||
VERSION: 02.33.01
|
||||
VERSION: 02.34.08
|
||||
PATH: /GOVERNANCE.md
|
||||
BRIEF: Project governance rules, roles, and decision process for MokoWaaSBrand
|
||||
-->
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
INGROUP: MokoWaaS.Documentation
|
||||
REPO: https://github.com/mokoconsulting-tech/mokowaas
|
||||
PATH: ./LICENSE.md
|
||||
VERSION: 02.33.01
|
||||
VERSION: 02.34.08
|
||||
BRIEF: Project license (GPL-3.0-or-later)
|
||||
-->
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
DEFGROUP: Joomla.Plugin
|
||||
INGROUP: MokoWaaS
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS
|
||||
VERSION: 02.33.01
|
||||
VERSION: 02.34.08
|
||||
PATH: /README.md
|
||||
BRIEF: MokoWaaS platform plugin for Joomla
|
||||
-->
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ DEFGROUP: [PROJECT_NAME]
|
||||
INGROUP: [PROJECT_NAME].Documentation
|
||||
REPO: [REPOSITORY_URL]
|
||||
PATH: /SECURITY.md
|
||||
VERSION: 02.33.01
|
||||
VERSION: 02.34.08
|
||||
BRIEF: Security vulnerability reporting and handling policy
|
||||
-->
|
||||
|
||||
|
||||
@@ -11,13 +11,13 @@
|
||||
INGROUP: MokoWaaS.Build
|
||||
REPO: https://github.com/mokoconsulting-tech/mokowaas
|
||||
FILE: build-guide.md
|
||||
VERSION: 02.33.01
|
||||
VERSION: 02.34.08
|
||||
PATH: /docs/guides/
|
||||
BRIEF: Build and packaging guide for the MokoWaaS system plugin
|
||||
NOTE: Defines environment setup, repository layout, packaging rules, and release preparation
|
||||
-->
|
||||
|
||||
# MokoWaaS Build Guide (VERSION: 02.33.01)
|
||||
# MokoWaaS Build Guide (VERSION: 02.34.08)
|
||||
|
||||
## 1. Purpose
|
||||
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
DEFGROUP: Joomla.Plugin
|
||||
INGROUP: MokoWaaS.Guides
|
||||
REPO: https://github.com/mokoconsulting-tech/mokowaas
|
||||
VERSION: 02.33.01
|
||||
VERSION: 02.34.08
|
||||
PATH: /docs/guides/configuration-guide.md
|
||||
BRIEF: Configuration guide for the MokoWaaS system plugin
|
||||
NOTE: Defines plugin parameters, expected behaviors, and recommended defaults
|
||||
-->
|
||||
|
||||
# MokoWaaS Configuration Guide (VERSION: 02.33.01)
|
||||
# MokoWaaS Configuration Guide (VERSION: 02.34.08)
|
||||
|
||||
## 1. Objective
|
||||
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
DEFGROUP: Joomla.Plugin
|
||||
INGROUP: MokoWaaS.Guides
|
||||
REPO: https://github.com/mokoconsulting-tech/mokowaas
|
||||
VERSION: 02.33.01
|
||||
VERSION: 02.34.08
|
||||
PATH: /docs/guides/installation-guide.md
|
||||
BRIEF: Installation guide for the MokoWaaS system plugin
|
||||
NOTE: First document in the guide set
|
||||
-->
|
||||
|
||||
# MokoWaaS Installation Guide (VERSION: 02.33.01)
|
||||
# MokoWaaS Installation Guide (VERSION: 02.34.08)
|
||||
|
||||
## Introduction
|
||||
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
DEFGROUP: Joomla.Plugin
|
||||
INGROUP: MokoWaaS.Guides
|
||||
REPO: https://github.com/mokoconsulting-tech/mokowaas
|
||||
VERSION: 02.33.01
|
||||
VERSION: 02.34.08
|
||||
PATH: /docs/guides/operations-guide.md
|
||||
BRIEF: Operational guide for administering and managing the MokoWaaS system plugin
|
||||
NOTE: Defines lifecycle, responsibilities, and operational behaviors
|
||||
-->
|
||||
|
||||
# MokoWaaS Operations Guide (VERSION: 02.33.01)
|
||||
# MokoWaaS Operations Guide (VERSION: 02.34.08)
|
||||
|
||||
## Introduction
|
||||
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
DEFGROUP: Joomla.Plugin
|
||||
INGROUP: MokoWaaS.Guides
|
||||
REPO: https://github.com/mokoconsulting-tech/mokowaas
|
||||
VERSION: 02.33.01
|
||||
VERSION: 02.34.08
|
||||
PATH: /docs/guides/rollback-and-recovery-guide.md
|
||||
BRIEF: Rollback and recovery guide for restoring stable operation after plugin related incidents
|
||||
NOTE: Completes the core guide set for WaaS plugin governance
|
||||
-->
|
||||
|
||||
# MokoWaaS Rollback and Recovery Guide (VERSION: 02.33.01)
|
||||
# MokoWaaS Rollback and Recovery Guide (VERSION: 02.34.08)
|
||||
|
||||
## Introduction
|
||||
|
||||
|
||||
@@ -7,13 +7,13 @@
|
||||
DEFGROUP: Joomla.Plugin
|
||||
INGROUP: MokoWaaS.Guides
|
||||
REPO: https://github.com/mokoconsulting-tech/mokowaas
|
||||
VERSION: 02.33.01
|
||||
VERSION: 02.34.08
|
||||
PATH: /docs/guides/testing-guide.md
|
||||
BRIEF: Testing guide for MokoWaaS v02.01.08
|
||||
NOTE: Covers manual test procedures for language overrides, install/uninstall, and configuration
|
||||
-->
|
||||
|
||||
# MokoWaaS Testing Guide (VERSION: 02.33.01)
|
||||
# MokoWaaS Testing Guide (VERSION: 02.34.08)
|
||||
|
||||
## 1. Prerequisites
|
||||
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
DEFGROUP: Joomla.Plugin
|
||||
INGROUP: MokoWaaS.Guides
|
||||
REPO: https://github.com/mokoconsulting-tech/mokowaas
|
||||
VERSION: 02.33.01
|
||||
VERSION: 02.34.08
|
||||
PATH: /docs/guides/troubleshooting-guide.md
|
||||
BRIEF: Troubleshooting guide for diagnosing and resolving issues related to the MokoWaaS plugin
|
||||
NOTE: Designed for administrators and WaaS operations teams
|
||||
-->
|
||||
|
||||
# MokoWaaS Troubleshooting Guide (VERSION: 02.33.01)
|
||||
# MokoWaaS Troubleshooting Guide (VERSION: 02.34.08)
|
||||
|
||||
## Introduction
|
||||
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
DEFGROUP: Joomla.Plugin
|
||||
INGROUP: MokoWaaS.Guides
|
||||
REPO: https://github.com/mokoconsulting-tech/mokowaas
|
||||
VERSION: 02.33.01
|
||||
VERSION: 02.34.08
|
||||
PATH: /docs/guides/upgrade-and-versioning-guide.md
|
||||
BRIEF: Guide for updating, versioning, and maintaining the MokoWaaS plugin
|
||||
NOTE: Defines release flow, version rules, and upgrade validation
|
||||
-->
|
||||
|
||||
# MokoWaaS Upgrade and Versioning Guide (VERSION: 02.33.01)
|
||||
# MokoWaaS Upgrade and Versioning Guide (VERSION: 02.34.08)
|
||||
|
||||
## Introduction
|
||||
|
||||
|
||||
+2
-2
@@ -10,13 +10,13 @@
|
||||
DEFGROUP: Joomla.Plugin
|
||||
INGROUP: MokoWaaS.Documentation
|
||||
REPO: https://github.com/mokoconsulting-tech/mokowaas
|
||||
VERSION: 02.33.01
|
||||
VERSION: 02.34.08
|
||||
PATH: /docs/index.md
|
||||
BRIEF: Master index of all documentation for the MokoWaaS plugin
|
||||
NOTE: Automatically maintained index for all guide canvases
|
||||
-->
|
||||
|
||||
# MokoWaaS Documentation Index (VERSION: 02.33.01)
|
||||
# MokoWaaS Documentation Index (VERSION: 02.34.08)
|
||||
|
||||
## Introduction
|
||||
|
||||
|
||||
@@ -11,12 +11,12 @@
|
||||
INGROUP: MokoWaaS
|
||||
REPO: https://github.com/mokoconsulting-tech/mokowaas
|
||||
PATH: /docs/plugin-basic.md
|
||||
VERSION: 02.33.01
|
||||
VERSION: 02.34.08
|
||||
BRIEF: Baseline documentation for the MokoWaaS system plugin
|
||||
NOTE: Foundational reference for internal and external stakeholders
|
||||
-->
|
||||
|
||||
# MokoWaaS Plugin Overview (VERSION: 02.33.01)
|
||||
# MokoWaaS Plugin Overview (VERSION: 02.34.08)
|
||||
|
||||
## Introduction
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ DEFGROUP: MokoWaaS.Documentation
|
||||
INGROUP: MokoStandards.Templates
|
||||
REPO: https://github.com/mokoconsulting-tech/MokoWaaS
|
||||
PATH: /docs/update-server.md
|
||||
VERSION: 02.33.01
|
||||
VERSION: 02.34.08
|
||||
BRIEF: How this extension's Joomla update server file (update.xml) is managed
|
||||
-->
|
||||
|
||||
|
||||
@@ -580,12 +580,40 @@ class DisplayController extends BaseController
|
||||
return;
|
||||
}
|
||||
|
||||
$input = Factory::getApplication()->getInput();
|
||||
$model = new \Moko\Component\MokoWaaS\Administrator\Model\PrivacyModel();
|
||||
$input = Factory::getApplication()->getInput();
|
||||
$model = new \Moko\Component\MokoWaaS\Administrator\Model\PrivacyModel();
|
||||
$action = $input->getString('action', 'deny');
|
||||
|
||||
if ($action === 'create')
|
||||
{
|
||||
$result = $model->createRequest(
|
||||
$input->getInt('user_id', 0),
|
||||
$input->getString('type', 'export')
|
||||
);
|
||||
$this->jsonResponse($result);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($action === 'approve' && !$input->getInt('request_id', 0) && $input->getInt('user_id', 0))
|
||||
{
|
||||
// Auto-process: create then immediately approve
|
||||
$result = $model->createRequest(
|
||||
$input->getInt('user_id', 0),
|
||||
$input->getString('type', 'export')
|
||||
);
|
||||
|
||||
if ($result['success'] && !empty($result['id']))
|
||||
{
|
||||
$result = $model->processRequest((int) $result['id'], 'approve');
|
||||
}
|
||||
|
||||
$this->jsonResponse($result);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->jsonResponse($model->processRequest(
|
||||
$input->getInt('request_id', 0),
|
||||
$input->getString('action', 'deny')
|
||||
$action
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -267,48 +267,18 @@ class DashboardModel extends BaseDatabaseModel
|
||||
{
|
||||
try
|
||||
{
|
||||
// Purge all file-based cache directories
|
||||
$root = JPATH_ROOT;
|
||||
$dirs = [
|
||||
$root . '/cache',
|
||||
$root . '/administrator/cache',
|
||||
// Use Joomla's native cache API — same as com_cache
|
||||
$cache = Factory::getContainer()->get(\Joomla\CMS\Cache\CacheControllerFactoryInterface::class);
|
||||
$cache->createCacheController('', ['defaultgroup' => ''])->cache->clean('');
|
||||
|
||||
// Also clean admin cache
|
||||
$conf = Factory::getApplication()->get('cache_handler', 'file');
|
||||
$options = [
|
||||
'defaultgroup' => '',
|
||||
'cachebase' => JPATH_ADMINISTRATOR . '/cache',
|
||||
'storage' => $conf,
|
||||
];
|
||||
|
||||
foreach ($dirs as $dir)
|
||||
{
|
||||
if (!is_dir($dir))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$it = new \RecursiveIteratorIterator(
|
||||
new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS),
|
||||
\RecursiveIteratorIterator::CHILD_FIRST
|
||||
);
|
||||
|
||||
foreach ($it as $file)
|
||||
{
|
||||
$name = $file->getFilename();
|
||||
|
||||
if ($name === 'index.html' || $name === '.htaccess')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($file->isDir())
|
||||
{
|
||||
@rmdir($file->getPathname());
|
||||
}
|
||||
else
|
||||
{
|
||||
@unlink($file->getPathname());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Also run Joomla's built-in cache GC for non-file handlers
|
||||
Factory::getCache('', '')->gc();
|
||||
Factory::getCache('', '', 'administrator')->gc();
|
||||
$cache->createCacheController('', $options)->cache->clean('');
|
||||
|
||||
// Clear opcache if available
|
||||
if (\function_exists('opcache_reset'))
|
||||
|
||||
@@ -83,6 +83,26 @@ $statusBadge = [
|
||||
Install
|
||||
</button>
|
||||
<?php elseif ($pkg->status === 'installed'): ?>
|
||||
<?php
|
||||
$dashLink = '';
|
||||
if ($pkg->type === 'component')
|
||||
{
|
||||
$dashLink = 'index.php?option=' . $pkg->element;
|
||||
}
|
||||
elseif ($pkg->type === 'package' && strpos($pkg->element, 'pkg_') === 0)
|
||||
{
|
||||
$comElement = 'com_' . substr($pkg->element, 4);
|
||||
if (is_dir(JPATH_ADMINISTRATOR . '/components/' . $comElement))
|
||||
{
|
||||
$dashLink = 'index.php?option=' . $comElement;
|
||||
}
|
||||
}
|
||||
?>
|
||||
<?php if ($dashLink): ?>
|
||||
<a href="<?php echo Route::_($dashLink); ?>" class="btn btn-sm btn-outline-primary" title="Open">
|
||||
<span class="icon-arrow-right" aria-hidden="true"></span> Open
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
<span class="btn btn-sm btn-outline-success disabled">
|
||||
<span class="icon-check" aria-hidden="true"></span> Installed
|
||||
</span>
|
||||
|
||||
@@ -53,6 +53,63 @@ $typeBadge = [
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- New Request Form -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<strong><span class="icon-plus"></span> Create Data Request</strong>
|
||||
<button class="btn btn-sm btn-outline-primary" type="button" data-bs-toggle="collapse" data-bs-target="#newRequestForm" aria-expanded="false">
|
||||
<span class="icon-plus"></span> New Request
|
||||
</button>
|
||||
</div>
|
||||
<div class="collapse" id="newRequestForm">
|
||||
<div class="card-body">
|
||||
<form id="formNewRequest" class="row g-3">
|
||||
<div class="col-12 col-md-5">
|
||||
<label for="req_user_id" class="form-label">User</label>
|
||||
<select id="req_user_id" class="form-select" required>
|
||||
<option value="">Select a user...</option>
|
||||
<?php
|
||||
$db = Factory::getDbo();
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->select([$db->quoteName('id'), $db->quoteName('name'), $db->quoteName('email')])
|
||||
->from($db->quoteName('#__users'))
|
||||
->where($db->quoteName('block') . ' = 0')
|
||||
->order($db->quoteName('name'))
|
||||
);
|
||||
foreach ($db->loadObjectList() as $u):
|
||||
?>
|
||||
<option value="<?php echo (int) $u->id; ?>"><?php echo $this->escape($u->name); ?> (<?php echo $this->escape($u->email); ?>)</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-12 col-md-3">
|
||||
<label for="req_type" class="form-label">Request Type</label>
|
||||
<select id="req_type" class="form-select" required>
|
||||
<option value="export">Export Data</option>
|
||||
<option value="delete">Delete Data</option>
|
||||
<option value="anonymize">Anonymize Data</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-12 col-md-2">
|
||||
<label for="req_auto" class="form-label">Auto-process</label>
|
||||
<select id="req_auto" class="form-select">
|
||||
<option value="0">No (pending)</option>
|
||||
<option value="1">Yes (immediate)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-12 col-md-2 d-flex align-items-end">
|
||||
<button type="submit" class="btn btn-primary w-100" id="btnCreateRequest"
|
||||
data-url="<?php echo Route::_('index.php?option=com_mokowaas&task=display.processDataRequest&format=json'); ?>"
|
||||
data-token="<?php echo $token; ?>">
|
||||
<span class="icon-check"></span> Submit
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Data Requests -->
|
||||
<div class="col-12 col-xl-8">
|
||||
@@ -158,6 +215,32 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
});
|
||||
});
|
||||
|
||||
// Create new request
|
||||
var form = document.getElementById('formNewRequest');
|
||||
if (form) {
|
||||
form.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
var btn = document.getElementById('btnCreateRequest');
|
||||
var userId = document.getElementById('req_user_id').value;
|
||||
var type = document.getElementById('req_type').value;
|
||||
var auto = document.getElementById('req_auto').value;
|
||||
if (!userId) { Joomla.renderMessages({warning:['Please select a user.']}); return; }
|
||||
btn.disabled = true;
|
||||
var fd = new FormData();
|
||||
fd.append('user_id', userId);
|
||||
fd.append('type', type);
|
||||
fd.append('action', auto === '1' ? 'approve' : 'create');
|
||||
fd.append(btn.dataset.token, '1');
|
||||
fetch(btn.dataset.url, {method:'POST', body:fd, headers:{'X-Requested-With':'XMLHttpRequest'}})
|
||||
.then(function(r){return r.json()})
|
||||
.then(function(d){
|
||||
if (d.success) { Joomla.renderMessages({message:[d.message || 'Request created.']}); location.reload(); }
|
||||
else { Joomla.renderMessages({error:[d.message || 'Failed.']}); btn.disabled = false; }
|
||||
})
|
||||
.catch(function(){ btn.disabled = false; });
|
||||
});
|
||||
}
|
||||
|
||||
// Export download
|
||||
document.querySelectorAll('.btn-export-download').forEach(function(btn) {
|
||||
btn.addEventListener('click', function() {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
DEFGROUP: Joomla.Component
|
||||
INGROUP: MokoWaaS
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS
|
||||
VERSION: 02.32.04
|
||||
VERSION: 02.34.00
|
||||
PATH: /mokowaas.xml
|
||||
BRIEF: Component manifest for MokoWaaS admin dashboard and REST API
|
||||
-->
|
||||
@@ -20,7 +20,7 @@
|
||||
<license>GPL-3.0-or-later</license>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<version>02.33.01-dev</version>
|
||||
<version>02.34.08-dev</version>
|
||||
<description>MokoWaaS admin dashboard and REST API. Provides a control panel for managing MokoWaaS feature plugins, site health monitoring, and remote management endpoints.</description>
|
||||
|
||||
<namespace path="src">Moko\Component\MokoWaaS</namespace>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<license>GPL-3.0-or-later</license>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<version>02.33.01-dev</version>
|
||||
<version>02.34.08-dev</version>
|
||||
<description>MOD_MOKOWAAS_CACHE_DESC</description>
|
||||
<namespace path="src">Moko\Module\MokoWaaSCache</namespace>
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<license>GPL-3.0-or-later</license>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<version>02.33.01-dev</version>
|
||||
<version>02.34.08-dev</version>
|
||||
<description>MOD_MOKOWAAS_CPANEL_DESC</description>
|
||||
<namespace path="src">Moko\Module\MokoWaaSCpanel</namespace>
|
||||
|
||||
|
||||
@@ -58,35 +58,47 @@ $diskColor = ($diskPct !== null && $diskPct > 90) ? 'bg-danger' : (($diskPct !==
|
||||
?>
|
||||
|
||||
<div class="mod-mokowaas-cpanel card p-3 mb-4">
|
||||
<!-- Header row (always visible, acts as collapse toggle) -->
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<a class="d-flex align-items-center gap-2 text-decoration-none text-reset" data-bs-toggle="collapse" href="#mokowaas-cpanel-body" role="button" aria-expanded="<?php echo $collapsed ? 'false' : 'true'; ?>" aria-controls="mokowaas-cpanel-body">
|
||||
<span class="icon-shield-alt" aria-hidden="true" style="font-size:1.25rem;color:#1a2744"></span>
|
||||
<strong>MokoWaaS</strong>
|
||||
<span class="badge bg-primary"><?php echo htmlspecialchars($siteInfo->mokowaas_version ?? ''); ?></span>
|
||||
<?php if (!empty($siteInfo->debug)): ?>
|
||||
<span class="badge bg-warning text-dark">Debug</span>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($siteInfo->offline)): ?>
|
||||
<span class="badge bg-danger">Offline</span>
|
||||
<?php endif; ?>
|
||||
<?php if (($counts->moko_updates ?? 0) > 0): ?>
|
||||
<a href="<?php echo Route::_('index.php?option=com_installer&view=update'); ?>" class="badge bg-info text-decoration-none" title="MokoWaaS updates available">
|
||||
<span class="icon-upload" aria-hidden="true"></span> <?php echo $counts->moko_updates; ?> MokoWaaS update<?php echo $counts->moko_updates > 1 ? 's' : ''; ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
<?php if ($counts->updates > 0 && $counts->updates !== ($counts->moko_updates ?? 0)): ?>
|
||||
<a href="<?php echo Route::_('index.php?option=com_installer&view=update'); ?>" class="badge bg-warning text-dark text-decoration-none" title="Other updates available">
|
||||
<span class="icon-upload" aria-hidden="true"></span> <?php echo $counts->updates - ($counts->moko_updates ?? 0); ?> update<?php echo ($counts->updates - ($counts->moko_updates ?? 0)) > 1 ? 's' : ''; ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
<span class="icon-chevron-down small text-muted" aria-hidden="true"></span>
|
||||
</a>
|
||||
<a href="<?php echo Route::_('index.php?option=com_mokowaas'); ?>" class="btn btn-sm btn-primary">
|
||||
<span class="icon-cogs" aria-hidden="true"></span>
|
||||
<?php echo Text::_('MOD_MOKOWAAS_CPANEL_OPEN_DASHBOARD'); ?>
|
||||
</a>
|
||||
<!-- Header row -->
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<button type="button" class="btn btn-sm btn-link p-0 text-muted" data-bs-toggle="collapse" data-bs-target="#mokowaas-cpanel-body" aria-expanded="<?php echo $collapsed ? 'false' : 'true'; ?>" aria-controls="mokowaas-cpanel-body" id="mokowaas-cpanel-toggle" style="font-size:1rem;line-height:1;width:1.5rem;">
|
||||
<span class="fa-solid fa-caret-<?php echo $collapsed ? 'right' : 'down'; ?>" aria-hidden="true" id="mokowaas-cpanel-caret"></span>
|
||||
</button>
|
||||
<span class="icon-shield-alt" aria-hidden="true" style="font-size:1.25rem;color:#1a2744"></span>
|
||||
<strong>MokoWaaS</strong>
|
||||
<span class="badge bg-primary"><?php echo htmlspecialchars($siteInfo->mokowaas_version ?? ''); ?></span>
|
||||
<?php if (!empty($siteInfo->debug)): ?>
|
||||
<span class="badge bg-warning text-dark">Debug</span>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($siteInfo->offline)): ?>
|
||||
<span class="badge bg-danger">Offline</span>
|
||||
<?php endif; ?>
|
||||
<?php if (($counts->moko_updates ?? 0) > 0): ?>
|
||||
<a href="<?php echo Route::_('index.php?option=com_installer&view=update'); ?>" class="badge bg-info text-decoration-none" title="MokoWaaS updates available">
|
||||
<span class="icon-upload" aria-hidden="true"></span> <?php echo $counts->moko_updates; ?> MokoWaaS update<?php echo $counts->moko_updates > 1 ? 's' : ''; ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
<?php if ($counts->updates > 0 && $counts->updates !== ($counts->moko_updates ?? 0)): ?>
|
||||
<a href="<?php echo Route::_('index.php?option=com_installer&view=update'); ?>" class="badge bg-warning text-dark text-decoration-none" title="Other updates available">
|
||||
<span class="icon-upload" aria-hidden="true"></span> <?php echo $counts->updates - ($counts->moko_updates ?? 0); ?> update<?php echo ($counts->updates - ($counts->moko_updates ?? 0)) > 1 ? 's' : ''; ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
<span class="ms-auto">
|
||||
<a href="<?php echo Route::_('index.php?option=com_mokowaas'); ?>" class="btn btn-sm btn-primary">
|
||||
<span class="icon-cogs" aria-hidden="true"></span>
|
||||
<?php echo Text::_('MOD_MOKOWAAS_CPANEL_OPEN_DASHBOARD'); ?>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var target = document.getElementById('mokowaas-cpanel-body');
|
||||
var caret = document.getElementById('mokowaas-cpanel-caret');
|
||||
if (target && caret) {
|
||||
target.addEventListener('show.bs.collapse', function() { caret.className = 'fa-solid fa-caret-down'; });
|
||||
target.addEventListener('hide.bs.collapse', function() { caret.className = 'fa-solid fa-caret-right'; });
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Collapsible body -->
|
||||
<div class="collapse<?php echo $collapsed ? '' : ' show'; ?> mt-3" id="mokowaas-cpanel-body">
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<license>GPL-3.0-or-later</license>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<version>02.33.01-dev</version>
|
||||
<version>02.34.08-dev</version>
|
||||
<description>MokoWaaS admin sidebar menu — renders a dedicated MokoWaaS section in the admin menu before Joomla's default menu.</description>
|
||||
<namespace path="src">Moko\Module\MokoWaaSMenu</namespace>
|
||||
|
||||
|
||||
@@ -2,45 +2,130 @@
|
||||
/**
|
||||
* MokoWaaS Admin Sidebar Menu
|
||||
*
|
||||
* Uses native Joomla admin menu classes (MetisMenu) so it renders
|
||||
* identically to Joomla's own sidebar menu items.
|
||||
* Renders MokoWaaS static views first, then auto-discovers installed
|
||||
* Moko components from #__menu and renders their submenu items as
|
||||
* nested MetisMenu collapsible sections.
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Router\Route;
|
||||
|
||||
$items = [
|
||||
['icon' => 'icon-cogs', 'title' => 'Dashboard', 'link' => 'index.php?option=com_mokowaas'],
|
||||
['icon' => 'fa-solid fa-handshake-angle', 'title' => 'Helpdesk', 'link' => 'index.php?option=com_mokowaas&view=tickets'],
|
||||
['icon' => 'icon-puzzle-piece', 'title' => 'Extensions', 'link' => 'index.php?option=com_mokowaas&view=extensions'],
|
||||
['icon' => 'fa-solid fa-file-code', 'title' => '.htaccess Maker', 'link' => 'index.php?option=com_mokowaas&view=htaccess'],
|
||||
['icon' => 'icon-lock', 'title' => 'Privacy Guard', 'link' => 'index.php?option=com_mokowaas&view=privacy'],
|
||||
['icon' => 'icon-shield-alt', 'title' => 'WAF Log', 'link' => 'index.php?option=com_mokowaas&view=waflog'],
|
||||
['icon' => 'icon-database', 'title' => 'Database Tools', 'link' => 'index.php?option=com_mokowaas&view=database'],
|
||||
['icon' => 'icon-trash', 'title' => 'Cache Cleanup', 'link' => 'index.php?option=com_mokowaas&view=cleanup'],
|
||||
['icon' => 'icon-power-off', 'title' => 'Feature Plugins', 'link' => 'index.php?option=com_plugins&filter[folder]=system&filter[search]=mokowaas'],
|
||||
];
|
||||
|
||||
$app = \Joomla\CMS\Factory::getApplication();
|
||||
$app = Factory::getApplication();
|
||||
$currentOption = $app->getInput()->get('option', '');
|
||||
$currentView = $app->getInput()->get('view', '');
|
||||
|
||||
// Determine if any child is active (auto-expand)
|
||||
$anyActive = ($currentOption === 'com_mokowaas');
|
||||
$parentClass = 'item parent item-level-1' . ($anyActive ? ' mm-active' : '');
|
||||
$collapseClass = 'collapse-level-1 mm-collapse' . ($anyActive ? ' mm-show' : '');
|
||||
// ── Static MokoWaaS views ────────────────────────────────────────────
|
||||
$mokowaasItems = [
|
||||
['icon' => 'icon-cogs', 'title' => 'Dashboard', 'link' => 'index.php?option=com_mokowaas'],
|
||||
['icon' => 'fa-solid fa-handshake-angle', 'title' => 'Helpdesk', 'link' => 'index.php?option=com_mokowaas&view=tickets'],
|
||||
['icon' => 'icon-puzzle-piece', 'title' => 'Extensions', 'link' => 'index.php?option=com_mokowaas&view=extensions'],
|
||||
['icon' => 'fa-solid fa-file-code', 'title' => '.htaccess Maker', 'link' => 'index.php?option=com_mokowaas&view=htaccess'],
|
||||
['icon' => 'icon-lock', 'title' => 'Privacy Guard', 'link' => 'index.php?option=com_mokowaas&view=privacy'],
|
||||
['icon' => 'icon-shield-alt', 'title' => 'WAF Log', 'link' => 'index.php?option=com_mokowaas&view=waflog'],
|
||||
['icon' => 'icon-database', 'title' => 'Database Tools', 'link' => 'index.php?option=com_mokowaas&view=database'],
|
||||
['icon' => 'icon-trash', 'title' => 'Cache Cleanup', 'link' => 'index.php?option=com_mokowaas&view=cleanup'],
|
||||
['icon' => 'icon-power-off', 'title' => 'Feature Plugins', 'link' => 'index.php?option=com_plugins&filter[folder]=system&filter[search]=mokowaas'],
|
||||
];
|
||||
|
||||
// ── Auto-discover Moko component menus from #__menu ──────────────────
|
||||
$mokoComponents = [];
|
||||
|
||||
try
|
||||
{
|
||||
$db = Factory::getContainer()->get(\Joomla\Database\DatabaseInterface::class);
|
||||
|
||||
// Find all Moko component menu items (exclude com_mokowaas — handled above)
|
||||
$db->setQuery(
|
||||
"SELECT m.id, m.title, m.link, m.level, m.parent_id, m.img, e.element"
|
||||
. " FROM " . $db->quoteName('#__menu') . " m"
|
||||
. " LEFT JOIN " . $db->quoteName('#__extensions') . " e ON m.component_id = e.extension_id"
|
||||
. " WHERE m.client_id = 1 AND m.level >= 1 AND m.published = 1"
|
||||
. " AND e.element LIKE 'com_moko%'"
|
||||
. " AND e.element != 'com_mokowaas'"
|
||||
. " AND e.enabled = 1"
|
||||
. " ORDER BY e.element, m.level, m.lft"
|
||||
);
|
||||
$menuItems = $db->loadObjectList() ?: [];
|
||||
|
||||
// Load sys.ini language files for discovered components
|
||||
$lang = Factory::getLanguage();
|
||||
$loadedLangs = [];
|
||||
foreach ($menuItems as $m)
|
||||
{
|
||||
if (!isset($loadedLangs[$m->element]))
|
||||
{
|
||||
$lang->load($m->element . '.sys', JPATH_ADMINISTRATOR);
|
||||
$lang->load($m->element, JPATH_ADMINISTRATOR);
|
||||
$loadedLangs[$m->element] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Group: level 1 = component parent, level 2 = children
|
||||
foreach ($menuItems as $m)
|
||||
{
|
||||
if ((int) $m->level === 1)
|
||||
{
|
||||
$mokoComponents[$m->element] = [
|
||||
'id' => $m->id,
|
||||
'title' => Text::_($m->title),
|
||||
'link' => $m->link,
|
||||
'icon' => str_replace('class:', 'icon-', $m->img ?: 'class:puzzle-piece'),
|
||||
'element' => $m->element,
|
||||
'children' => [],
|
||||
];
|
||||
}
|
||||
elseif ((int) $m->level === 2 && isset($mokoComponents[$m->element]))
|
||||
{
|
||||
$mokoComponents[$m->element]['children'][] = [
|
||||
'title' => Text::_($m->title),
|
||||
'link' => $m->link,
|
||||
'icon' => str_replace('class:', 'icon-', $m->img ?: 'class:cog'),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (\Throwable $e)
|
||||
{
|
||||
// Silent — menu works without auto-discovered components
|
||||
}
|
||||
|
||||
// ── Determine active state ───────────────────────────────────────────
|
||||
$mokowaasActive = ($currentOption === 'com_mokowaas');
|
||||
$anyMokoActive = $mokowaasActive;
|
||||
|
||||
foreach ($mokoComponents as $comp)
|
||||
{
|
||||
$parsed = [];
|
||||
parse_str(parse_url($comp['link'], PHP_URL_QUERY) ?? '', $parsed);
|
||||
if (($parsed['option'] ?? '') === $currentOption)
|
||||
{
|
||||
$anyMokoActive = true;
|
||||
}
|
||||
}
|
||||
|
||||
$topClass = 'item parent item-level-1' . ($anyMokoActive ? ' mm-active' : '');
|
||||
$topCollapse = 'collapse-level-1 mm-collapse' . ($anyMokoActive ? ' mm-show' : '');
|
||||
?>
|
||||
|
||||
<style>
|
||||
.sidebar-wrapper .item-level-1 > a { padding-inline-start: 1.5rem; }
|
||||
.sidebar-wrapper .mokowaas-menu-item > a { padding-inline-start: 1rem; }
|
||||
.sidebar-wrapper .mokowaas-menu-child > a { padding-inline-start: 1.5rem; }
|
||||
</style>
|
||||
|
||||
<ul class="nav flex-column main-nav">
|
||||
<li class="<?php echo $parentClass; ?>">
|
||||
<li class="<?php echo $topClass; ?>">
|
||||
<a class="has-arrow" href="#" aria-label="MokoWaaS">
|
||||
<span class="icon-shield-alt" aria-hidden="true"></span>
|
||||
<span class="sidebar-item-title">MokoWaaS</span>
|
||||
</a>
|
||||
<ul class="<?php echo $collapseClass; ?>">
|
||||
<?php foreach ($items as $item): ?>
|
||||
<ul class="<?php echo $topCollapse; ?>" style="padding-inline-start:0.5rem;">
|
||||
|
||||
<?php // ── MokoWaaS static items ── ?>
|
||||
<?php foreach ($mokowaasItems as $item): ?>
|
||||
<?php
|
||||
$active = false;
|
||||
$parsed = [];
|
||||
@@ -51,7 +136,7 @@ $collapseClass = 'collapse-level-1 mm-collapse' . ($anyActive ? ' mm-show' : '')
|
||||
? ($currentView === '' || $currentView === 'dashboard')
|
||||
: ($currentView === ($parsed['view'] ?? ''));
|
||||
}
|
||||
$liClass = 'item item-level-2' . ($active ? ' mm-active' : '');
|
||||
$liClass = 'item mokowaas-menu-item' . ($active ? ' mm-active' : '');
|
||||
$aClass = 'no-dropdown' . ($active ? ' mm-active' : '');
|
||||
?>
|
||||
<li class="<?php echo $liClass; ?>">
|
||||
@@ -61,6 +146,46 @@ $collapseClass = 'collapse-level-1 mm-collapse' . ($anyActive ? ' mm-show' : '')
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<?php // ── Auto-discovered Moko components with submenus ── ?>
|
||||
<?php foreach ($mokoComponents as $comp): ?>
|
||||
<?php
|
||||
$compParsed = [];
|
||||
parse_str(parse_url($comp['link'], PHP_URL_QUERY) ?? '', $compParsed);
|
||||
$compActive = ($compParsed['option'] ?? '') === $currentOption;
|
||||
$hasChildren = !empty($comp['children']);
|
||||
$compLiClass = 'item mokowaas-menu-item' . ($hasChildren ? ' parent' : '') . ($compActive ? ' mm-active' : '');
|
||||
$compAClass = ($hasChildren ? 'has-arrow' : 'no-dropdown') . ($compActive ? ' mm-active' : '');
|
||||
$childCollapse = 'collapse-level-2 mm-collapse' . ($compActive ? ' mm-show' : '');
|
||||
?>
|
||||
<li class="<?php echo $compLiClass; ?>">
|
||||
<a class="<?php echo $compAClass; ?>" href="<?php echo $hasChildren ? '#' : Route::_($comp['link']); ?>"<?php echo ($compActive && !$hasChildren) ? ' aria-current="page"' : ''; ?>>
|
||||
<span class="<?php echo $comp['icon']; ?>" aria-hidden="true" style="display:inline-block!important;width:1.25em;text-align:center;margin-inline-end:0.4em;"></span>
|
||||
<span class="sidebar-item-title"><?php echo $comp['title']; ?></span>
|
||||
</a>
|
||||
<?php if ($hasChildren): ?>
|
||||
<ul class="<?php echo $childCollapse; ?>" style="padding-inline-start:0.75rem;">
|
||||
<?php foreach ($comp['children'] as $child): ?>
|
||||
<?php
|
||||
$childParsed = [];
|
||||
parse_str(parse_url($child['link'], PHP_URL_QUERY) ?? '', $childParsed);
|
||||
$childActive = ($childParsed['option'] ?? '') === $currentOption
|
||||
&& ($childParsed['view'] ?? '') === $currentView;
|
||||
$childLiClass = 'item mokowaas-menu-child' . ($childActive ? ' mm-active' : '');
|
||||
$childAClass = 'no-dropdown' . ($childActive ? ' mm-active' : '');
|
||||
?>
|
||||
<li class="<?php echo $childLiClass; ?>">
|
||||
<a class="<?php echo $childAClass; ?>" href="<?php echo Route::_($child['link']); ?>"<?php echo $childActive ? ' aria-current="page"' : ''; ?>>
|
||||
<span class="<?php echo $child['icon']; ?>" aria-hidden="true" style="display:inline-block!important;width:1.25em;text-align:center;margin-inline-end:0.4em;"></span>
|
||||
<span class="sidebar-item-title"><?php echo $child['title']; ?></span>
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
* DEFGROUP: Joomla.Plugin
|
||||
* INGROUP: MokoWaaS
|
||||
* REPO: https://github.com/mokoconsulting-tech/mokowaas
|
||||
* VERSION: 02.33.01
|
||||
* VERSION: 02.34.08
|
||||
* PATH: /src/Extension/MokoWaaS.php
|
||||
* NOTE: Core system plugin for MokoWaaS admin tools suite
|
||||
*/
|
||||
@@ -830,16 +830,6 @@ class MokoWaaS extends CMSPlugin implements BootableExtensionInterface
|
||||
*
|
||||
* @since 02.01.08
|
||||
*/
|
||||
public function onAfterRoute()
|
||||
{
|
||||
if (!$this->app->isClient('administrator'))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$this->protectPlugin();
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Automation event hooks (#151) — delegate to ticket automation engine
|
||||
// ------------------------------------------------------------------
|
||||
@@ -1133,163 +1123,20 @@ class MokoWaaS extends CMSPlugin implements BootableExtensionInterface
|
||||
|
||||
$doc->addScriptDeclaration("
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var url = " . json_encode($supportUrl) . ";
|
||||
document.querySelectorAll('a[href*=\"help.joomla.org\"], a[href*=\"docs.joomla.org\"]').forEach(function(link) {
|
||||
link.href = " . json_encode($supportUrl) . ";
|
||||
link.href = url;
|
||||
link.target = '_blank';
|
||||
});
|
||||
document.querySelectorAll('a[href*=\"dashboard=help\"]').forEach(function(link) {
|
||||
link.href = url;
|
||||
link.target = '_blank';
|
||||
link.rel = 'noopener noreferrer';
|
||||
});
|
||||
});
|
||||
");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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');
|
||||
}
|
||||
}
|
||||
|
||||
// Block non-master from viewing or editing MokoWaaS plugin settings
|
||||
if ($option === 'com_plugins')
|
||||
{
|
||||
$view = $this->app->input->get('view', '');
|
||||
$layout = $this->app->input->get('layout', '');
|
||||
$extensionId = (int) $this->app->input->get('extension_id', 0);
|
||||
|
||||
if (($view === 'plugin' || $layout === 'edit') && $extensionId > 0)
|
||||
{
|
||||
$db = Factory::getDbo();
|
||||
$query = $db->getQuery(true)
|
||||
->select('COUNT(*)')
|
||||
->from($db->quoteName('#__extensions'))
|
||||
->where($db->quoteName('extension_id') . ' = ' . $extensionId)
|
||||
->where($db->quoteName('element') . ' = ' . $db->quote('mokowaas'))
|
||||
->where($db->quoteName('type') . ' = ' . $db->quote('plugin'));
|
||||
|
||||
if ((int) $db->setQuery($query)->loadResult() > 0)
|
||||
{
|
||||
$this->app->enqueueMessage('MokoWaaS settings are restricted to the master user.', 'warning');
|
||||
$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();
|
||||
|
||||
// Set protected=1, locked=0 on MokoWaaS extensions
|
||||
$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();
|
||||
|
||||
// Ensure update site stays enabled (protected extensions get their update site disabled by Joomla)
|
||||
$query = $db->getQuery(true)
|
||||
->update($db->quoteName('#__update_sites') . ' AS us')
|
||||
->join('INNER', $db->quoteName('#__update_sites_extensions') . ' AS use2 ON us.update_site_id = use2.update_site_id')
|
||||
->join('INNER', $db->quoteName('#__extensions') . ' AS e ON use2.extension_id = e.extension_id')
|
||||
->set('us.enabled = 1')
|
||||
->where('us.enabled = 0')
|
||||
->where('(' . $db->quoteName('e.element') . ' = ' . $db->quote('mokowaas')
|
||||
. ' OR ' . $db->quoteName('e.element') . ' = ' . $db->quote('pkg_mokowaas') . ')');
|
||||
$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.
|
||||
*
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: Joomla.Plugin
|
||||
* INGROUP: MokoWaaS
|
||||
* VERSION: 02.33.01
|
||||
* VERSION: 02.34.08
|
||||
* PATH: /src/Field/AllowedIpsField.php
|
||||
* BRIEF: Custom form field that displays the current IP whitelist
|
||||
*/
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: Joomla.Plugin
|
||||
* INGROUP: MokoWaaS
|
||||
* VERSION: 02.33.01
|
||||
* VERSION: 02.34.08
|
||||
* PATH: /src/Field/CopyableTokenField.php
|
||||
* BRIEF: Read-only token field with a copy-to-clipboard button
|
||||
*/
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: Joomla.Plugin
|
||||
* INGROUP: MokoWaaS
|
||||
* VERSION: 02.33.01
|
||||
* VERSION: 02.34.08
|
||||
* PATH: /src/Field/CurrentIpField.php
|
||||
* BRIEF: Read-only field that displays the current user's IP address
|
||||
*/
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: Joomla.Plugin
|
||||
* INGROUP: MokoWaaS
|
||||
* VERSION: 02.33.01
|
||||
* VERSION: 02.34.08
|
||||
* PATH: /src/Field/DemoTaskInfoField.php
|
||||
* BRIEF: Read-only field showing scheduled task info with link to manage it
|
||||
*/
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: Joomla.Plugin
|
||||
* INGROUP: MokoWaaS
|
||||
* VERSION: 02.33.01
|
||||
* VERSION: 02.34.08
|
||||
* PATH: /src/Field/NextResetField.php
|
||||
* BRIEF: Read-only field showing next reset time from Joomla scheduled task
|
||||
*/
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: Joomla.Plugin
|
||||
* INGROUP: MokoWaaS
|
||||
* VERSION: 02.33.01
|
||||
* VERSION: 02.34.08
|
||||
* PATH: /src/Field/SnapshotTablesField.php
|
||||
* BRIEF: Multi-select list field that loads DB tables with sensible defaults
|
||||
*/
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* INGROUP: MokoWaaS
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS
|
||||
* PATH: /src/packages/plg_system_mokowaas/Service/ContentSyncReceiver.php
|
||||
* VERSION: 02.33.01
|
||||
* VERSION: 02.34.08
|
||||
* BRIEF: Receiver-side content sync — applies incoming payload to local DB
|
||||
*/
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* INGROUP: MokoWaaS
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS
|
||||
* PATH: /src/packages/plg_system_mokowaas/Service/ContentSyncService.php
|
||||
* VERSION: 02.33.01
|
||||
* VERSION: 02.34.08
|
||||
* BRIEF: Sender-side content sync — builds payload and pushes to remote sites
|
||||
*/
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* INGROUP: MokoWaaS
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS
|
||||
* PATH: /src/packages/plg_system_mokowaas/Service/DemoResetService.php
|
||||
* VERSION: 02.33.01
|
||||
* VERSION: 02.34.08
|
||||
* BRIEF: Content-only snapshot/restore for demo site reset
|
||||
*/
|
||||
|
||||
|
||||
+2
-2
@@ -15,5 +15,5 @@
|
||||
; Variables: (none)
|
||||
; -----------------------------------------------------------------------------
|
||||
|
||||
PLG_SYSTEM_MOKOWAAS="System - Moko WaaS"
|
||||
PLG_SYSTEM_MOKOWAAS_XML_DESCRIPTION="This plugin rebrands the Joomla system interface with MokoWaaS identity. It applies language overrides and ensures consistent branding across the platform."
|
||||
PLG_SYSTEM_MOKOWAAS="System - MokoWaaS Core"
|
||||
PLG_SYSTEM_MOKOWAAS_XML_DESCRIPTION="MokoWaaS core system plugin — coordinates feature plugins, master user management, event routing, and admin customizations."
|
||||
|
||||
+2
-2
@@ -15,5 +15,5 @@
|
||||
; Variables: (none)
|
||||
; -----------------------------------------------------------------------------
|
||||
|
||||
PLG_SYSTEM_MOKOWAAS="System - Moko WaaS"
|
||||
PLG_SYSTEM_MOKOWAAS_XML_DESCRIPTION="This plugin rebrands the Joomla system interface with MokoWaaS identity. It applies language overrides and ensures consistent branding across the platform."
|
||||
PLG_SYSTEM_MOKOWAAS="System - MokoWaaS Core"
|
||||
PLG_SYSTEM_MOKOWAAS_XML_DESCRIPTION="MokoWaaS core system plugin — coordinates feature plugins, master user management, event routing, and admin customizations."
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
; Variables: (none)
|
||||
; -----------------------------------------------------------------------------
|
||||
|
||||
PLG_SYSTEM_MOKOWAAS="System - MokoWaaS"
|
||||
PLG_SYSTEM_MOKOWAAS_XML_DESCRIPTION="This plugin rebrands the Joomla system interface with MokoWaaS identity. It applies language overrides and ensures consistent branding across the platform."
|
||||
PLG_SYSTEM_MOKOWAAS="System - MokoWaaS Core"
|
||||
PLG_SYSTEM_MOKOWAAS_XML_DESCRIPTION="MokoWaaS core system plugin — coordinates feature plugins, master user management, event routing, and admin customizations."
|
||||
|
||||
PLG_SYSTEM_MOKOWAAS_ENABLE_BRANDING_LABEL="Enable Branding"
|
||||
PLG_SYSTEM_MOKOWAAS_ENABLE_BRANDING_DESC="Enable or disable the branding overrides across the system."
|
||||
@@ -111,7 +111,7 @@ PLG_SYSTEM_MOKOWAAS_SYNC_PUSH_NOW_DESC="Set to Yes and save to immediately push
|
||||
PLG_SYSTEM_MOKOWAAS_SYNC_TARGET_URL_LABEL="Site URL"
|
||||
PLG_SYSTEM_MOKOWAAS_SYNC_TARGET_URL_DESC="Full URL of the remote Joomla site (e.g. https://client.example.com)."
|
||||
PLG_SYSTEM_MOKOWAAS_SYNC_TARGET_TOKEN_LABEL="API Token"
|
||||
PLG_SYSTEM_MOKOWAAS_SYNC_TARGET_TOKEN_DESC="The health_api_token from the remote site's MokoWaaS plugin settings."
|
||||
PLG_SYSTEM_MOKOWAAS_SYNC_TARGET_TOKEN_DESC="The heartbeat token from the remote site's MokoWaaS plugin settings."
|
||||
PLG_SYSTEM_MOKOWAAS_SYNC_TARGET_LABEL_LABEL="Label"
|
||||
PLG_SYSTEM_MOKOWAAS_SYNC_TARGET_LABEL_DESC="Friendly name for this target (for identification only)."
|
||||
|
||||
@@ -121,7 +121,7 @@ PLG_SYSTEM_MOKOWAAS_FIELDSET_DIAGNOSTICS_DESC="Health check endpoint for externa
|
||||
|
||||
PLG_SYSTEM_MOKOWAAS_ENABLE_HEALTH_LABEL="Enable Health Endpoint"
|
||||
PLG_SYSTEM_MOKOWAAS_ENABLE_HEALTH_DESC="Expose a JSON health check endpoint at <code>/?mokowaas=health</code>. Requires a valid API token. A random token is generated automatically when enabled."
|
||||
PLG_SYSTEM_MOKOWAAS_HEALTH_TOKEN_LABEL="Health API Token"
|
||||
PLG_SYSTEM_MOKOWAAS_HEALTH_TOKEN_LABEL="Heartbeat Token"
|
||||
PLG_SYSTEM_MOKOWAAS_HEALTH_TOKEN_DESC="Auto-generated bearer token for the health endpoint. Use this token in your Grafana datasource configuration. Send as <code>Authorization: Bearer <token></code> header or <code>&token=<value></code> query parameter."
|
||||
PLG_SYSTEM_MOKOWAAS_GRAFANA_URL_LABEL="Grafana URL"
|
||||
PLG_SYSTEM_MOKOWAAS_GRAFANA_URL_DESC="Base URL of your Grafana instance (e.g. <code>https://grafana.example.com</code>). When provided along with an API key, the plugin will auto-provision a datasource and dashboard in Grafana when the health endpoint is enabled."
|
||||
|
||||
@@ -15,5 +15,5 @@
|
||||
; Variables: (none)
|
||||
; -----------------------------------------------------------------------------
|
||||
|
||||
PLG_SYSTEM_MOKOWAAS="System - MokoWaaS"
|
||||
PLG_SYSTEM_MOKOWAAS_XML_DESCRIPTION="This plugin rebrands the Joomla system interface with MokoWaaS identity. It applies language overrides and ensures consistent branding across the platform."
|
||||
PLG_SYSTEM_MOKOWAAS="System - MokoWaaS Core"
|
||||
PLG_SYSTEM_MOKOWAAS_XML_DESCRIPTION="MokoWaaS core system plugin — coordinates feature plugins, master user management, event routing, and admin customizations."
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
; Variables: (none)
|
||||
; -----------------------------------------------------------------------------
|
||||
|
||||
PLG_SYSTEM_MOKOWAAS="System - MokoWaaS"
|
||||
PLG_SYSTEM_MOKOWAAS_XML_DESCRIPTION="This plugin rebrands the Joomla system interface with MokoWaaS identity. It applies language overrides and ensures consistent branding across the platform."
|
||||
PLG_SYSTEM_MOKOWAAS="System - MokoWaaS Core"
|
||||
PLG_SYSTEM_MOKOWAAS_XML_DESCRIPTION="MokoWaaS core system plugin — coordinates feature plugins, master user management, event routing, and admin customizations."
|
||||
|
||||
PLG_SYSTEM_MOKOWAAS_ENABLE_BRANDING_LABEL="Enable Branding"
|
||||
PLG_SYSTEM_MOKOWAAS_ENABLE_BRANDING_DESC="Enable or disable the branding overrides across the system."
|
||||
@@ -111,7 +111,7 @@ PLG_SYSTEM_MOKOWAAS_SYNC_PUSH_NOW_DESC="Set to Yes and save to immediately push
|
||||
PLG_SYSTEM_MOKOWAAS_SYNC_TARGET_URL_LABEL="Site URL"
|
||||
PLG_SYSTEM_MOKOWAAS_SYNC_TARGET_URL_DESC="Full URL of the remote Joomla site (e.g. https://client.example.com)."
|
||||
PLG_SYSTEM_MOKOWAAS_SYNC_TARGET_TOKEN_LABEL="API Token"
|
||||
PLG_SYSTEM_MOKOWAAS_SYNC_TARGET_TOKEN_DESC="The health_api_token from the remote site's MokoWaaS plugin settings."
|
||||
PLG_SYSTEM_MOKOWAAS_SYNC_TARGET_TOKEN_DESC="The heartbeat token from the remote site's MokoWaaS plugin settings."
|
||||
PLG_SYSTEM_MOKOWAAS_SYNC_TARGET_LABEL_LABEL="Label"
|
||||
PLG_SYSTEM_MOKOWAAS_SYNC_TARGET_LABEL_DESC="Friendly name for this target (for identification only)."
|
||||
|
||||
@@ -121,7 +121,7 @@ PLG_SYSTEM_MOKOWAAS_FIELDSET_DIAGNOSTICS_DESC="Health check endpoint for externa
|
||||
|
||||
PLG_SYSTEM_MOKOWAAS_ENABLE_HEALTH_LABEL="Enable Health Endpoint"
|
||||
PLG_SYSTEM_MOKOWAAS_ENABLE_HEALTH_DESC="Expose a JSON health check endpoint at <code>/?mokowaas=health</code>. Requires a valid API token. A random token is generated automatically when enabled."
|
||||
PLG_SYSTEM_MOKOWAAS_HEALTH_TOKEN_LABEL="Health API Token"
|
||||
PLG_SYSTEM_MOKOWAAS_HEALTH_TOKEN_LABEL="Heartbeat Token"
|
||||
PLG_SYSTEM_MOKOWAAS_HEALTH_TOKEN_DESC="Auto-generated bearer token for the health endpoint. Use this token in your Grafana datasource configuration. Send as <code>Authorization: Bearer <token></code> header or <code>&token=<value></code> query parameter."
|
||||
PLG_SYSTEM_MOKOWAAS_GRAFANA_URL_LABEL="Grafana URL"
|
||||
PLG_SYSTEM_MOKOWAAS_GRAFANA_URL_DESC="Base URL of your Grafana instance (e.g. <code>https://grafana.example.com</code>). When provided along with an API key, the plugin will auto-provision a datasource and dashboard in Grafana when the health endpoint is enabled."
|
||||
|
||||
@@ -15,5 +15,5 @@
|
||||
; Variables: (none)
|
||||
; -----------------------------------------------------------------------------
|
||||
|
||||
PLG_SYSTEM_MOKOWAAS="System - MokoWaaS"
|
||||
PLG_SYSTEM_MOKOWAAS_XML_DESCRIPTION="This plugin rebrands the Joomla system interface with MokoWaaS identity. It applies language overrides and ensures consistent branding across the platform."
|
||||
PLG_SYSTEM_MOKOWAAS="System - MokoWaaS Core"
|
||||
PLG_SYSTEM_MOKOWAAS_XML_DESCRIPTION="MokoWaaS core system plugin — coordinates feature plugins, master user management, event routing, and admin customizations."
|
||||
|
||||
@@ -16,13 +16,13 @@
|
||||
DEFGROUP: Joomla.Plugin
|
||||
INGROUP: MokoWaaS
|
||||
REPO: https://github.com/mokoconsulting-tech/mokowaas
|
||||
VERSION: 02.32.04
|
||||
VERSION: 02.34.00
|
||||
PATH: /src/mokowaas.xml
|
||||
BRIEF: Plugin manifest for MokoWaaS system plugin
|
||||
NOTE: Defines installation metadata, files, and configuration for Joomla
|
||||
-->
|
||||
<extension type="plugin" group="system" method="upgrade">
|
||||
<name>System - MokoWaaS</name>
|
||||
<name>System - MokoWaaS Core</name>
|
||||
<element>mokowaas</element>
|
||||
<author>Moko Consulting</author>
|
||||
<creationDate>2026-05-22</creationDate>
|
||||
@@ -30,8 +30,8 @@
|
||||
<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.33.01-dev</version>
|
||||
<description>This plugin rebrands the Joomla system interface with MokoWaaS identity. It applies language overrides and ensures consistent branding across the platform.</description>
|
||||
<version>02.34.08-dev</version>
|
||||
<description>MokoWaaS core system plugin — coordinates feature plugins, master user management, event routing, and admin customizations.</description>
|
||||
<namespace path=".">Moko\Plugin\System\MokoWaaS</namespace>
|
||||
<scriptfile>script.php</scriptfile>
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
* DEFGROUP: Joomla.Plugin
|
||||
* INGROUP: MokoWaaS
|
||||
* REPO: https://github.com/mokoconsulting-tech/mokowaas
|
||||
* VERSION: 02.33.01
|
||||
* VERSION: 02.34.08
|
||||
* PATH: /src/script.php
|
||||
* BRIEF: Installation script for MokoWaaS plugin
|
||||
* NOTE: Handles installation, update, and uninstallation tasks including language override deployment
|
||||
@@ -127,7 +127,6 @@ class plgSystemMokoWaaSInstallerScript implements InstallerScriptInterface
|
||||
$this->ensureMokoCassiopeia();
|
||||
$this->installLanguageOverrides();
|
||||
$this->updateLoginSupportUrls();
|
||||
$this->updateAtumBranding();
|
||||
$this->registerActionLogExtension();
|
||||
$this->provisionHealthEndpoint();
|
||||
$this->sendInstallNotification($type);
|
||||
@@ -552,7 +551,6 @@ class plgSystemMokoWaaSInstallerScript implements InstallerScriptInterface
|
||||
$params = $this->getPluginParams();
|
||||
|
||||
return [
|
||||
'{{BRAND_NAME}}' => $params->get('brand_name', 'MokoWaaS'),
|
||||
'{{COMPANY_NAME}}' => $params->get('company_name', 'Moko Consulting'),
|
||||
'{{SUPPORT_URL}}' => $params->get('support_url', 'https://mokoconsulting.tech/support'),
|
||||
];
|
||||
@@ -727,75 +725,6 @@ class plgSystemMokoWaaSInstallerScript implements InstallerScriptInterface
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Atum admin template branding params at install time.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 02.01.08
|
||||
*/
|
||||
private function updateAtumBranding()
|
||||
{
|
||||
$mediaBase = 'media/plg_system_mokowaas/';
|
||||
|
||||
$expected = [
|
||||
'logoBrandLarge' => $mediaBase . 'logo.png',
|
||||
'logoBrandSmall' => $mediaBase . 'favicon_256.png',
|
||||
'loginLogo' => $mediaBase . 'logo.png',
|
||||
'logoBrandLargeAlt' => '',
|
||||
'logoBrandSmallAlt' => '',
|
||||
'loginLogoAlt' => '',
|
||||
'emptyLogoBrandLargeAlt' => '1',
|
||||
'emptyLogoBrandSmallAlt' => '1',
|
||||
'emptyLoginLogoAlt' => '1',
|
||||
'hue' => 'hsl(219, 44%, 18%)',
|
||||
'special-color' => '#1a2744',
|
||||
'link-color' => '#0051ad',
|
||||
];
|
||||
|
||||
$db = Factory::getDbo();
|
||||
$query = $db->getQuery(true)
|
||||
->select([$db->quoteName('id'), $db->quoteName('params')])
|
||||
->from($db->quoteName('#__template_styles'))
|
||||
->where($db->quoteName('template') . ' = '
|
||||
. $db->quote('atum'))
|
||||
->where($db->quoteName('client_id') . ' = 1');
|
||||
|
||||
$db->setQuery($query);
|
||||
$styles = $db->loadObjectList();
|
||||
|
||||
if (empty($styles))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($styles as $style)
|
||||
{
|
||||
$params = new \Joomla\Registry\Registry(
|
||||
$style->params ?: '{}'
|
||||
);
|
||||
|
||||
foreach ($expected as $key => $value)
|
||||
{
|
||||
$params->set($key, $value);
|
||||
}
|
||||
|
||||
$update = $db->getQuery(true)
|
||||
->update($db->quoteName('#__template_styles'))
|
||||
->set($db->quoteName('params') . ' = '
|
||||
. $db->quote($params->toString()))
|
||||
->where($db->quoteName('id') . ' = '
|
||||
. (int) $style->id);
|
||||
|
||||
$db->setQuery($update);
|
||||
$db->execute();
|
||||
}
|
||||
|
||||
Factory::getApplication()->enqueueMessage(
|
||||
'Updated Atum template branding.', 'message'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the plugin in #__action_logs_extensions so it appears
|
||||
* as a filterable extension in System > Action Logs.
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
* DEFGROUP: Joomla.Plugin
|
||||
* INGROUP: MokoWaaS
|
||||
* REPO: https://github.com/mokoconsulting-tech/mokowaas
|
||||
* VERSION: 02.33.01
|
||||
* VERSION: 02.34.08
|
||||
* PATH: /src/services/provider.php
|
||||
* BRIEF: Service provider for dependency injection in Joomla 5.x
|
||||
* NOTE: Registers the plugin with Joomla's DI container
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<license>GPL-3.0-or-later</license>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<version>02.33.01-dev</version>
|
||||
<version>02.34.08-dev</version>
|
||||
<description>PLG_SYSTEM_MOKOWAAS_DEVTOOLS_DESC</description>
|
||||
<namespace path="src">Moko\Plugin\System\MokoWaaSDevTools</namespace>
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<license>GPL-3.0-or-later</license>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<version>02.33.01-dev</version>
|
||||
<version>02.34.08-dev</version>
|
||||
<description>PLG_SYSTEM_MOKOWAAS_FIREWALL_DESC</description>
|
||||
<namespace path="src">Moko\Plugin\System\MokoWaaSFirewall</namespace>
|
||||
|
||||
|
||||
@@ -65,6 +65,7 @@ class Firewall extends CMSPlugin implements SubscriberInterface, BootableExtensi
|
||||
{
|
||||
return [
|
||||
'onAfterInitialise' => 'onAfterInitialise',
|
||||
'onAfterRoute' => 'onAfterRoute',
|
||||
'onUserBeforeSave' => 'onUserBeforeSave',
|
||||
];
|
||||
}
|
||||
@@ -792,4 +793,177 @@ class Firewall extends CMSPlugin implements SubscriberInterface, BootableExtensi
|
||||
$config->set('upload_maxsize', $maxMb);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================================================================
|
||||
// Extension Protection (#155)
|
||||
// ==================================================================
|
||||
|
||||
/**
|
||||
* Protect MokoWaaS extensions after routing.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 02.35.00
|
||||
*/
|
||||
public function onAfterRoute(): void
|
||||
{
|
||||
$app = $this->getApplication();
|
||||
|
||||
if (!$app->isClient('administrator'))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$this->protectPlugin();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
private function protectPlugin(): void
|
||||
{
|
||||
// Ensure protected flag is set (self-healing -- runs once per session)
|
||||
static $flagChecked = false;
|
||||
|
||||
if (!$flagChecked)
|
||||
{
|
||||
$flagChecked = true;
|
||||
$this->ensureProtectedFlag();
|
||||
}
|
||||
|
||||
if (MokoWaaSHelper::isMasterUser())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$app = $this->getApplication();
|
||||
$option = $app->input->get('option', '');
|
||||
$task = $app->input->get('task', '');
|
||||
|
||||
// Block non-master from uninstalling MokoWaaS
|
||||
if ($option === 'com_installer' && strpos($task, 'manage.remove') !== false)
|
||||
{
|
||||
$cid = $app->input->get('cid', [], 'array');
|
||||
|
||||
if ($this->isOurExtension($cid))
|
||||
{
|
||||
$app->enqueueMessage('MokoWaaS cannot be uninstalled.', 'error');
|
||||
$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 = $app->input->get('cid', [], 'array');
|
||||
|
||||
if ($this->isOurExtension($cid))
|
||||
{
|
||||
$app->enqueueMessage('MokoWaaS cannot be disabled.', 'error');
|
||||
$app->redirect('index.php?option=com_plugins');
|
||||
}
|
||||
}
|
||||
|
||||
// Block non-master from viewing or editing MokoWaaS plugin settings
|
||||
if ($option === 'com_plugins')
|
||||
{
|
||||
$view = $app->input->get('view', '');
|
||||
$layout = $app->input->get('layout', '');
|
||||
$extensionId = (int) $app->input->get('extension_id', 0);
|
||||
|
||||
if (($view === 'plugin' || $layout === 'edit') && $extensionId > 0)
|
||||
{
|
||||
$db = Factory::getDbo();
|
||||
$query = $db->getQuery(true)
|
||||
->select('COUNT(*)')
|
||||
->from($db->quoteName('#__extensions'))
|
||||
->where($db->quoteName('extension_id') . ' = ' . $extensionId)
|
||||
->where($db->quoteName('element') . ' = ' . $db->quote('mokowaas'))
|
||||
->where($db->quoteName('type') . ' = ' . $db->quote('plugin'));
|
||||
|
||||
if ((int) $db->setQuery($query)->loadResult() > 0)
|
||||
{
|
||||
$app->enqueueMessage('MokoWaaS settings are restricted to the master user.', 'warning');
|
||||
$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
|
||||
*/
|
||||
private function ensureProtectedFlag(): void
|
||||
{
|
||||
try
|
||||
{
|
||||
$db = Factory::getDbo();
|
||||
|
||||
// Set protected=1, locked=0 on MokoWaaS extensions
|
||||
$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();
|
||||
|
||||
// Ensure update site stays enabled (protected extensions get their update site disabled by Joomla)
|
||||
$query = $db->getQuery(true)
|
||||
->update($db->quoteName('#__update_sites') . ' AS us')
|
||||
->join('INNER', $db->quoteName('#__update_sites_extensions') . ' AS use2 ON us.update_site_id = use2.update_site_id')
|
||||
->join('INNER', $db->quoteName('#__extensions') . ' AS e ON use2.extension_id = e.extension_id')
|
||||
->set('us.enabled = 1')
|
||||
->where('us.enabled = 0')
|
||||
->where('(' . $db->quoteName('e.element') . ' = ' . $db->quote('mokowaas')
|
||||
. ' OR ' . $db->quoteName('e.element') . ' = ' . $db->quote('pkg_mokowaas') . ')');
|
||||
$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
|
||||
*/
|
||||
private 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<license>GPL-3.0-or-later</license>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<version>02.33.01-dev</version>
|
||||
<version>02.34.08-dev</version>
|
||||
<description>PLG_SYSTEM_MOKOWAAS_MONITOR_DESC</description>
|
||||
<namespace path="src">Moko\Plugin\System\MokoWaaSMonitor</namespace>
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<license>GPL-3.0-or-later</license>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<version>02.33.01-dev</version>
|
||||
<version>02.34.08-dev</version>
|
||||
<description>PLG_SYSTEM_MOKOWAAS_OFFLINE_DESC</description>
|
||||
<namespace path="src">Moko\Plugin\System\MokoWaaSOffline</namespace>
|
||||
|
||||
|
||||
@@ -30,11 +30,11 @@ final class Tos extends CMSPlugin implements SubscriberInterface
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
'onAfterRoute' => 'onAfterRoute',
|
||||
'onAfterInitialise' => 'onAfterInitialise',
|
||||
];
|
||||
}
|
||||
|
||||
public function onAfterRoute(): void
|
||||
public function onAfterInitialise(): void
|
||||
{
|
||||
$app = $this->getApplication();
|
||||
|
||||
@@ -61,9 +61,17 @@ final class Tos extends CMSPlugin implements SubscriberInterface
|
||||
$slugs = (array) $slugs;
|
||||
}
|
||||
|
||||
// Default bypassed pages when none configured
|
||||
if (empty($slugs))
|
||||
{
|
||||
return;
|
||||
$slugs = [
|
||||
'legal/terms-of-service',
|
||||
'legal/privacy-policy',
|
||||
'legal/community-guidelines',
|
||||
'support',
|
||||
'support/tickets',
|
||||
'support/submit-a-ticket',
|
||||
];
|
||||
}
|
||||
|
||||
$includeChildren = (int) $this->params->get('include_children', 1);
|
||||
@@ -167,6 +175,5 @@ final class Tos extends CMSPlugin implements SubscriberInterface
|
||||
private function bypassOffline($config, $app): void
|
||||
{
|
||||
$config->set('offline', 0);
|
||||
$app->getInput()->set('tmpl', 'component');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<license>GPL-3.0-or-later</license>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<version>02.33.01-dev</version>
|
||||
<version>02.34.08-dev</version>
|
||||
<description>PLG_SYSTEM_MOKOWAAS_TENANT_DESC</description>
|
||||
<namespace path="src">Moko\Plugin\System\MokoWaaSTenant</namespace>
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<license>GPL-3.0-or-later</license>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<version>02.33.01-dev</version>
|
||||
<version>02.34.08-dev</version>
|
||||
<description>Runs scheduled helpdesk automation rules — auto-close resolved tickets, SLA breach escalation, and time-based actions.</description>
|
||||
<namespace path="src">Moko\Plugin\Task\MokoWaaSTickets</namespace>
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<license>GNU General Public License version 3 or later; see LICENSE</license>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<version>02.33.01-dev</version>
|
||||
<version>02.34.08-dev</version>
|
||||
<description>PLG_TASK_MOKOWAASDEMO_DESC</description>
|
||||
<namespace path="src">Moko\Plugin\Task\MokoWaaSDemo</namespace>
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<license>GNU General Public License version 3 or later; see LICENSE</license>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<version>02.33.01-dev</version>
|
||||
<version>02.34.08-dev</version>
|
||||
<description>PLG_TASK_MOKOWAASSYNC_DESC</description>
|
||||
<namespace path="src">Moko\Plugin\Task\MokoWaaSSync</namespace>
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<license>GPL-3.0-or-later</license>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<version>02.33.01-dev</version>
|
||||
<version>02.34.08-dev</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>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<license>GPL-3.0-or-later</license>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<version>02.33.01-dev</version>
|
||||
<version>02.34.08-dev</version>
|
||||
<description>Joomla Web Services API routes for Perfect Publisher (com_autotweet) — channels, posts, requests, rules, and feeds.</description>
|
||||
<namespace path="src">Moko\Plugin\WebServices\PerfectPublisher</namespace>
|
||||
<files>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
* INGROUP: MokoWaaS
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS
|
||||
* PATH: /src/packages/plg_webservices_perfectpublisher/services/provider.php
|
||||
* VERSION: 02.33.01
|
||||
* VERSION: 02.34.08
|
||||
* BRIEF: DI service provider for Perfect Publisher Web Services plugin
|
||||
*/
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
* INGROUP: MokoWaaS
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS
|
||||
* PATH: /src/packages/plg_webservices_perfectpublisher/src/Extension/PerfectPublisherApi.php
|
||||
* VERSION: 02.33.01
|
||||
* VERSION: 02.34.08
|
||||
* BRIEF: Web Services API plugin for Perfect Publisher (com_autotweet)
|
||||
*/
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<extension type="package" method="upgrade">
|
||||
<name>Package - MokoWaaS</name>
|
||||
<packagename>mokowaas</packagename>
|
||||
<version>02.33.01-dev</version>
|
||||
<version>02.34.08-dev</version>
|
||||
<creationDate>2026-06-02</creationDate>
|
||||
<author>Moko Consulting</author>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
@@ -10,6 +10,8 @@
|
||||
<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 — admin dashboard, security firewall, tenant restrictions, health monitoring, developer tools, and REST API.</description>
|
||||
<dlid prefix="dlid=" suffix=""/>
|
||||
<blockChildUninstall>true</blockChildUninstall>
|
||||
<scriptfile>script.php</scriptfile>
|
||||
|
||||
<files folder="packages">
|
||||
|
||||
+209
-5
@@ -89,6 +89,9 @@ class Pkg_MokowaasInstallerScript
|
||||
// Set menu_icon params on submenu items (Joomla only renders img on level 1)
|
||||
$this->fixMenuIcons();
|
||||
|
||||
// Set up MokoWaaS guided tours and unpublish Joomla defaults
|
||||
$this->setupGuidedTours();
|
||||
|
||||
// Mark MokoWaaS extensions as protected (prevents disable/uninstall at framework level)
|
||||
$this->protectExtensions();
|
||||
|
||||
@@ -98,6 +101,9 @@ class Pkg_MokowaasInstallerScript
|
||||
// Clean up stale/duplicate update sites
|
||||
$this->cleanupStaleUpdateSites();
|
||||
|
||||
// Fix orphaned update records (extension_id=0)
|
||||
$this->fixUpdateRecords();
|
||||
|
||||
// Trigger heartbeat registration
|
||||
$this->sendHeartbeat();
|
||||
|
||||
@@ -552,6 +558,29 @@ class Pkg_MokowaasInstallerScript
|
||||
*
|
||||
* @since 02.31.00
|
||||
*/
|
||||
private function fixUpdateRecords(): void
|
||||
{
|
||||
try
|
||||
{
|
||||
$db = Factory::getDbo();
|
||||
|
||||
// Link orphaned #__updates records to the installed extension
|
||||
$db->setQuery(
|
||||
"UPDATE " . $db->quoteName('#__updates') . " u"
|
||||
. " JOIN " . $db->quoteName('#__extensions') . " e"
|
||||
. " ON u.element = e.element AND u.type = e.type"
|
||||
. " SET u.extension_id = e.extension_id"
|
||||
. " WHERE u.extension_id = 0"
|
||||
. " AND u.element LIKE " . $db->quote('%mokowaas%')
|
||||
);
|
||||
$db->execute();
|
||||
}
|
||||
catch (\Throwable $e)
|
||||
{
|
||||
// Non-critical
|
||||
}
|
||||
}
|
||||
|
||||
private function cleanupStaleUpdateSites(): void
|
||||
{
|
||||
try
|
||||
@@ -951,8 +980,8 @@ class Pkg_MokowaasInstallerScript
|
||||
$iconMap = [
|
||||
'class:cogs' => 'icon-cogs',
|
||||
'class:puzzle-piece' => 'icon-puzzle-piece',
|
||||
'class:headphones' => 'icon-headphones',
|
||||
'class:file-code' => 'icon-file-code',
|
||||
'class:headphones' => 'fa-solid fa-handshake-angle',
|
||||
'class:file-code' => 'fa-solid fa-file-code',
|
||||
'class:lock' => 'icon-lock',
|
||||
'class:shield-alt' => 'icon-shield-alt',
|
||||
'class:database' => 'icon-database',
|
||||
@@ -963,10 +992,15 @@ class Pkg_MokowaasInstallerScript
|
||||
'class:bolt' => 'icon-bolt',
|
||||
];
|
||||
|
||||
// Find all MokoWaaS component submenu items (including those linking to other components)
|
||||
$db->setQuery(
|
||||
"SELECT id, img, params FROM #__menu"
|
||||
. " WHERE client_id = 1 AND level >= 2"
|
||||
. " AND link LIKE '%com_mokowaas%'"
|
||||
$db->getQuery(true)
|
||||
->select(['m.id', 'm.img', 'm.params'])
|
||||
->from($db->quoteName('#__menu', 'm'))
|
||||
->where('m.client_id = 1')
|
||||
->where('m.level >= 2')
|
||||
->where('m.parent_id IN (SELECT id FROM ' . $db->quoteName('#__menu')
|
||||
. ' WHERE client_id = 1 AND level = 1 AND link LIKE ' . $db->quote('%com_mokowaas%') . ')')
|
||||
);
|
||||
|
||||
foreach ($db->loadObjectList() as $item)
|
||||
@@ -1001,6 +1035,176 @@ class Pkg_MokowaasInstallerScript
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpublish default Joomla guided tours and create MokoWaaS tours.
|
||||
* Re-enables the guided tours plugin if disabled.
|
||||
*/
|
||||
private function setupGuidedTours(): void
|
||||
{
|
||||
try
|
||||
{
|
||||
$db = Factory::getDbo();
|
||||
|
||||
// Re-enable guided tours plugin (may have been disabled)
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->update($db->quoteName('#__extensions'))
|
||||
->set($db->quoteName('enabled') . ' = 1')
|
||||
->where($db->quoteName('element') . ' = ' . $db->quote('guidedtours'))
|
||||
->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
|
||||
)->execute();
|
||||
|
||||
// Re-enable the guided tours module (shows our tours, not Joomla's)
|
||||
$db->setQuery(
|
||||
"UPDATE " . $db->quoteName('#__modules')
|
||||
. " SET published = 1, title = 'MokoWaaS Tours'"
|
||||
. " WHERE module = 'mod_guidedtours'"
|
||||
);
|
||||
$db->execute();
|
||||
|
||||
// Override the guided tours module language string
|
||||
$overridePath = JPATH_ADMINISTRATOR . '/language/overrides/en-GB.override.ini';
|
||||
$overrides = file_exists($overridePath) ? parse_ini_file($overridePath) : [];
|
||||
|
||||
if (empty($overrides['MOD_GUIDEDTOURS']))
|
||||
{
|
||||
$overrides['MOD_GUIDEDTOURS'] = 'MokoWaaS Tours';
|
||||
$overrides['MOD_GUIDEDTOURS_TITLE'] = 'MokoWaaS Tours';
|
||||
|
||||
$lines = [];
|
||||
foreach ($overrides as $k => $v)
|
||||
{
|
||||
$lines[] = $k . '="' . str_replace('"', '\"', $v) . '"';
|
||||
}
|
||||
file_put_contents($overridePath, implode("\n", $lines) . "\n");
|
||||
}
|
||||
|
||||
// Unpublish all default Joomla tours
|
||||
$db->setQuery(
|
||||
"UPDATE " . $db->quoteName('#__guidedtours')
|
||||
. " SET published = 0"
|
||||
. " WHERE " . $db->quoteName('uid') . " LIKE 'joomla-%'"
|
||||
);
|
||||
$db->execute();
|
||||
|
||||
// Define MokoWaaS tours
|
||||
$tours = [
|
||||
[
|
||||
'uid' => 'mokowaas-welcome',
|
||||
'title' => 'Welcome to MokoWaaS',
|
||||
'desc' => 'Get started with the MokoWaaS Admin Tools Suite. This tour shows you the key areas of your admin dashboard.',
|
||||
'url' => 'administrator/index.php?option=com_mokowaas',
|
||||
'steps' => [
|
||||
['title' => 'MokoWaaS Dashboard', 'desc' => 'This is your MokoWaaS control center. You can see site info, feature plugins, WAF activity, and quick actions all in one place.', 'target' => '#mokowaas-dashboard', 'type' => 0],
|
||||
['title' => 'Site Information', 'desc' => 'The info bar shows your Joomla version, PHP version, database type, and debug/offline status at a glance.', 'target' => '.mokowaas-info-bar', 'type' => 0],
|
||||
['title' => 'Quick Actions', 'desc' => 'Use these buttons to clear cache, check updates, manage extensions, and perform common admin tasks with one click.', 'target' => '#mokowaas-btn-cache', 'type' => 0],
|
||||
['title' => 'Feature Plugins', 'desc' => 'MokoWaaS features are split into toggleable plugins. Enable or disable security, tenant restrictions, developer tools, and more from here.', 'target' => '.mokowaas-plugin-grid', 'type' => 0],
|
||||
['title' => 'MokoWaaS Menu', 'desc' => 'The MokoWaaS sidebar menu gives you quick access to all admin tools — Helpdesk, Extensions, WAF Log, Database Tools, and more.', 'target' => '.mokowaas-admin-menu, [class*="mokowaas"]', 'type' => 0],
|
||||
],
|
||||
],
|
||||
[
|
||||
'uid' => 'mokowaas-firewall',
|
||||
'title' => 'MokoWaaS Firewall Setup',
|
||||
'desc' => 'Configure the Web Application Firewall to protect your site from common attacks.',
|
||||
'url' => 'administrator/index.php?option=com_plugins&task=plugin.edit&filter[search]=mokowaas_firewall',
|
||||
'steps' => [
|
||||
['title' => 'Firewall Plugin', 'desc' => 'The MokoWaaS Firewall provides 10 security shields including SQL injection, XSS, and malicious user agent detection.', 'target' => '', 'type' => 0],
|
||||
['title' => 'WAF Shields', 'desc' => 'Enable or disable individual WAF shields. Each shield protects against a specific attack vector. All shields are enabled by default.', 'target' => '', 'type' => 0],
|
||||
['title' => 'Security Headers', 'desc' => 'Configure HTTP security headers like X-Frame-Options, Content-Security-Policy, and HSTS to harden your site against browser-based attacks.', 'target' => '', 'type' => 0],
|
||||
['title' => 'IP Blocklist', 'desc' => 'Block specific IP addresses, CIDR ranges, or wildcard patterns. The auto-ban feature automatically blocks IPs that trigger too many WAF alerts.', 'target' => '', 'type' => 0],
|
||||
],
|
||||
],
|
||||
[
|
||||
'uid' => 'mokowaas-helpdesk',
|
||||
'title' => 'MokoWaaS Helpdesk',
|
||||
'desc' => 'Learn how to manage support tickets, categories, and automation rules.',
|
||||
'url' => 'administrator/index.php?option=com_mokowaas&view=tickets',
|
||||
'steps' => [
|
||||
['title' => 'Ticket List', 'desc' => 'View all support tickets with status, priority, SLA tracking, and assignment. Filter by status or search to find specific tickets.', 'target' => '', 'type' => 0],
|
||||
['title' => 'Create a Ticket', 'desc' => 'Click the New button to create a support ticket. Assign a category, priority, and optional SLA deadline.', 'target' => '', 'type' => 0],
|
||||
['title' => 'Ticket Automation', 'desc' => 'Set up automation rules that trigger on ticket events (new ticket, status change) or Joomla events (user login, registration). Automate assignment, notifications, and status changes.', 'target' => '', 'type' => 0],
|
||||
],
|
||||
],
|
||||
[
|
||||
'uid' => 'mokowaas-extensions',
|
||||
'title' => 'Moko Extensions Manager',
|
||||
'desc' => 'Browse and install Moko Consulting extensions from the built-in catalog.',
|
||||
'url' => 'administrator/index.php?option=com_mokowaas&view=extensions',
|
||||
'steps' => [
|
||||
['title' => 'Extension Catalog', 'desc' => 'Browse all available Moko Consulting extensions. Each card shows the extension name, description, install status, and current version.', 'target' => '', 'type' => 0],
|
||||
['title' => 'Install Extensions', 'desc' => 'Click Install to add an extension from the Moko Consulting repository. Updates are handled through Joomla\'s standard update system.', 'target' => '', 'type' => 0],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($tours as $tourDef)
|
||||
{
|
||||
// Check if tour already exists
|
||||
$db->setQuery(
|
||||
$db->getQuery(true)
|
||||
->select('id')
|
||||
->from($db->quoteName('#__guidedtours'))
|
||||
->where($db->quoteName('uid') . ' = ' . $db->quote($tourDef['uid']))
|
||||
);
|
||||
|
||||
if ($db->loadResult())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$tour = (object) [
|
||||
'title' => $tourDef['title'],
|
||||
'uid' => $tourDef['uid'],
|
||||
'description' => $tourDef['desc'],
|
||||
'extensions' => '',
|
||||
'url' => $tourDef['url'],
|
||||
'created' => date('Y-m-d H:i:s'),
|
||||
'created_by' => 0,
|
||||
'modified' => date('Y-m-d H:i:s'),
|
||||
'modified_by' => 0,
|
||||
'published' => 1,
|
||||
'language' => '*',
|
||||
'note' => 'MokoWaaS',
|
||||
'access' => 3,
|
||||
'ordering' => 0,
|
||||
'autostart' => 0,
|
||||
];
|
||||
|
||||
$db->insertObject('#__guidedtours', $tour, 'id');
|
||||
$tourId = (int) $tour->id;
|
||||
|
||||
foreach ($tourDef['steps'] as $i => $stepDef)
|
||||
{
|
||||
$step = (object) [
|
||||
'tour_id' => $tourId,
|
||||
'title' => $stepDef['title'],
|
||||
'description' => $stepDef['desc'],
|
||||
'target' => $stepDef['target'],
|
||||
'type' => $stepDef['type'],
|
||||
'interactive_type' => 1,
|
||||
'url' => '',
|
||||
'position' => 'bottom',
|
||||
'ordering' => $i + 1,
|
||||
'published' => 1,
|
||||
'created' => date('Y-m-d H:i:s'),
|
||||
'created_by' => 0,
|
||||
'modified' => date('Y-m-d H:i:s'),
|
||||
'modified_by' => 0,
|
||||
'language' => '*',
|
||||
'note' => '',
|
||||
'params' => '{}',
|
||||
];
|
||||
|
||||
$db->insertObject('#__guidedtour_steps', $step, 'id');
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (\Throwable $e)
|
||||
{
|
||||
Log::add('Guided tours setup error: ' . $e->getMessage(), Log::WARNING, 'mokowaas');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a "Support" menu item on the frontend main menu.
|
||||
*/
|
||||
|
||||
+4
-4
@@ -1,7 +1,7 @@
|
||||
<?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.33.01-dev
|
||||
VERSION: 02.34.08-dev
|
||||
-->
|
||||
|
||||
<updates>
|
||||
@@ -11,13 +11,13 @@
|
||||
<element>pkg_mokowaas</element>
|
||||
<type>package</type>
|
||||
<client>site</client>
|
||||
<version>02.33.01-dev</version>
|
||||
<version>02.34.02-dev</version>
|
||||
<creationDate>2026-06-04</creationDate>
|
||||
<infourl title='Package - MokoWaaS'>https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/tag/development</infourl>
|
||||
<downloads>
|
||||
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/development/pkg_mokowaas-02.33.01-dev.zip</downloadurl>
|
||||
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases/download/development/pkg_mokowaas-02.34.02-dev.zip</downloadurl>
|
||||
</downloads>
|
||||
<sha256>be79af0f02a31ec83f15f3319af00cbf084590e72ef2cf13693a23028206539a</sha256>
|
||||
<sha256>16cd0c7cef22b6f260fb921767a841839d7060cd39fb11b402e9a96f217e7810</sha256>
|
||||
<tags><tag>dev</tag></tags>
|
||||
<changelogurl>https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/raw/branch/main/CHANGELOG.md</changelogurl>
|
||||
<maintainer>Moko Consulting</maintainer>
|
||||
|
||||
Reference in New Issue
Block a user