Compare commits
20 Commits
development
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 6cd16d9845 | |||
| 3d522134c2 | |||
| 8db306a4a7 | |||
| 6861643e6c | |||
| 4b1280213f | |||
| 9172baefb6 | |||
| 5b5b81eb0f | |||
| 82db443607 | |||
| 67f4da183c | |||
| bf05f4baa8 | |||
| 5303924f58 | |||
| 527c8b82cf | |||
| fb7c182b16 | |||
| 70419c4018 | |||
| 624b81b2d2 | |||
| 872889487e | |||
| d12971c0b7 | |||
| 21156deb0e | |||
| 1547bd5861 | |||
| f66871db2e |
@@ -1,63 +1,66 @@
|
|||||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
#
|
#
|
||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: Gitea.Workflow
|
||||||
# INGROUP: moko-platform.Release
|
# INGROUP: moko-platform.Release
|
||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
# PATH: /.mokogitea/workflows/auto-bump.yml
|
# PATH: /.mokogitea/workflows/auto-bump.yml
|
||||||
# VERSION: 09.23.00
|
# VERSION: 09.02.00
|
||||||
# BRIEF: Auto patch-bump version on every push to dev (skips merge commits)
|
# BRIEF: Auto patch-bump version on every push to dev (skips merge commits)
|
||||||
|
|
||||||
name: "Universal: Auto Version Bump"
|
name: "Universal: Auto Version Bump"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- dev
|
- dev
|
||||||
- rc
|
- rc
|
||||||
- 'feature/**'
|
- 'feature/**'
|
||||||
- 'patch/**'
|
- 'patch/**'
|
||||||
|
|
||||||
env:
|
env:
|
||||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
bump:
|
bump:
|
||||||
name: Version Bump
|
name: Version Bump
|
||||||
runs-on: release
|
runs-on: release
|
||||||
if: >-
|
if: >-
|
||||||
!contains(github.event.head_commit.message, '[skip ci]') &&
|
!contains(github.event.head_commit.message, '[skip ci]') &&
|
||||||
!contains(github.event.head_commit.message, '[skip bump]') &&
|
!contains(github.event.head_commit.message, '[skip bump]') &&
|
||||||
!startsWith(github.event.head_commit.message, 'Merge pull request')
|
!startsWith(github.event.head_commit.message, 'Merge pull request')
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
|
||||||
- name: Setup moko-platform tools
|
- name: Setup moko-platform tools
|
||||||
run: |
|
run: |
|
||||||
if ! command -v composer &> /dev/null; then
|
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
|
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
|
fi
|
||||||
rm -rf /tmp/moko-platform-api
|
if [ -d "/opt/moko-platform/cli" ]; then
|
||||||
git clone --depth 1 --branch main --quiet \
|
echo "MOKO_CLI=/opt/moko-platform/cli" >> "$GITHUB_ENV"
|
||||||
"https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/moko-platform.git" \
|
else
|
||||||
/tmp/moko-platform-api
|
git clone --depth 1 --branch main --quiet \
|
||||||
cd /tmp/moko-platform-api && composer install --no-dev --no-interaction --quiet
|
"https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/moko-platform.git" \
|
||||||
echo "MOKO_CLI=/tmp/moko-platform-api/cli" >> "$GITHUB_ENV"
|
/tmp/moko-platform-api
|
||||||
|
cd /tmp/moko-platform-api && composer install --no-dev --no-interaction --quiet
|
||||||
- name: Bump version
|
echo "MOKO_CLI=/tmp/moko-platform-api/cli" >> "$GITHUB_ENV"
|
||||||
run: |
|
fi
|
||||||
php ${MOKO_CLI}/version_auto_bump.php \
|
|
||||||
--path . --branch "${GITHUB_REF_NAME}" \
|
- name: Bump version
|
||||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
run: |
|
||||||
--repo-url "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
php ${MOKO_CLI}/version_auto_bump.php \
|
||||||
|
--path . --branch "${GITHUB_REF_NAME}" \
|
||||||
|
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||||
|
--repo-url "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||||
|
|||||||
@@ -4,10 +4,10 @@
|
|||||||
#
|
#
|
||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: Gitea.Workflow
|
||||||
# INGROUP: MokoPlatform.Universal
|
# INGROUP: MokoStandards.Universal
|
||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
# PATH: /.mokogitea/workflows/branch-cleanup.yml
|
# PATH: /.mokogitea/workflows/branch-cleanup.yml
|
||||||
# VERSION: 09.23.00
|
# VERSION: 01.00.00
|
||||||
# BRIEF: Delete feature branches after PR merge
|
# BRIEF: Delete feature branches after PR merge
|
||||||
|
|
||||||
name: "Branch Cleanup"
|
name: "Branch Cleanup"
|
||||||
|
|||||||
@@ -0,0 +1,204 @@
|
|||||||
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
#
|
||||||
|
# FILE INFORMATION
|
||||||
|
# DEFGROUP: Gitea.Workflow
|
||||||
|
# INGROUP: MokoStandards.CI
|
||||||
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
|
||||||
|
# PATH: /.gitea/workflows/ci-generic.yml
|
||||||
|
# VERSION: 01.00.00
|
||||||
|
# BRIEF: CI pipeline — lint, validate, and test for generic projects (PHP + Node.js)
|
||||||
|
|
||||||
|
name: "Generic: Project CI"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- dev
|
||||||
|
- dev/**
|
||||||
|
- rc/**
|
||||||
|
- version/**
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- dev
|
||||||
|
- dev/**
|
||||||
|
- rc/**
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
env:
|
||||||
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# ── Lint & Validate ───────────────────────────────────────────────────
|
||||||
|
lint:
|
||||||
|
name: Lint & Validate
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Detect toolchain
|
||||||
|
id: detect
|
||||||
|
run: |
|
||||||
|
HAS_PHP=false
|
||||||
|
HAS_NODE=false
|
||||||
|
[ -f "composer.json" ] && HAS_PHP=true
|
||||||
|
[ -f "package.json" ] && HAS_NODE=true
|
||||||
|
echo "has_php=$HAS_PHP" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "has_node=$HAS_NODE" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "Toolchain: PHP=$HAS_PHP Node=$HAS_NODE"
|
||||||
|
|
||||||
|
- name: Setup PHP
|
||||||
|
if: steps.detect.outputs.has_php == 'true'
|
||||||
|
run: |
|
||||||
|
if ! command -v php &> /dev/null; then
|
||||||
|
sudo apt-get update -qq
|
||||||
|
sudo apt-get install -y -qq php-cli php-mbstring php-xml >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
php -v
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
if: steps.detect.outputs.has_node == 'true'
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
|
||||||
|
- name: Install PHP dependencies
|
||||||
|
if: steps.detect.outputs.has_php == 'true'
|
||||||
|
run: |
|
||||||
|
if [ -f "composer.json" ]; then
|
||||||
|
composer install --no-interaction --prefer-dist --quiet 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Install Node.js dependencies
|
||||||
|
if: steps.detect.outputs.has_node == 'true'
|
||||||
|
run: |
|
||||||
|
if [ -f "package.json" ]; then
|
||||||
|
npm ci --quiet 2>/dev/null || npm install --quiet 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: PHP syntax check
|
||||||
|
if: steps.detect.outputs.has_php == 'true'
|
||||||
|
run: |
|
||||||
|
ERRORS=0
|
||||||
|
while IFS= read -r -d '' file; do
|
||||||
|
if ! php -l "$file" 2>&1 | grep -q "No syntax errors"; then
|
||||||
|
echo "::error file=${file}::PHP syntax error"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
done < <(find . -name "*.php" -not -path "./.git/*" -not -path "./vendor/*" -not -path "./node_modules/*" -print0)
|
||||||
|
|
||||||
|
echo "## PHP Lint" >> $GITHUB_STEP_SUMMARY
|
||||||
|
if [ "$ERRORS" -eq 0 ]; then
|
||||||
|
echo "All PHP files passed syntax check." >> $GITHUB_STEP_SUMMARY
|
||||||
|
else
|
||||||
|
echo "${ERRORS} file(s) with syntax errors." >> $GITHUB_STEP_SUMMARY
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: TypeScript/JavaScript lint
|
||||||
|
if: steps.detect.outputs.has_node == 'true'
|
||||||
|
run: |
|
||||||
|
if [ -f "node_modules/.bin/eslint" ]; then
|
||||||
|
npx eslint src/ --quiet 2>&1 || { echo "::error::ESLint errors found"; exit 1; }
|
||||||
|
echo "## ESLint" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "All files passed ESLint." >> $GITHUB_STEP_SUMMARY
|
||||||
|
elif [ -f ".eslintrc.json" ] || [ -f ".eslintrc.js" ] || [ -f "eslint.config.js" ]; then
|
||||||
|
echo "::warning::ESLint config found but eslint not installed"
|
||||||
|
else
|
||||||
|
echo "No ESLint configured — skipping"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: TypeScript compile check
|
||||||
|
if: steps.detect.outputs.has_node == 'true'
|
||||||
|
run: |
|
||||||
|
if [ -f "tsconfig.json" ] && [ -f "node_modules/.bin/tsc" ]; then
|
||||||
|
npx tsc --noEmit 2>&1 || { echo "::error::TypeScript compilation errors"; exit 1; }
|
||||||
|
echo "## TypeScript" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "TypeScript compilation passed." >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: PHPStan static analysis
|
||||||
|
if: steps.detect.outputs.has_php == 'true'
|
||||||
|
run: |
|
||||||
|
if [ -f "phpstan.neon" ] && [ -f "vendor/bin/phpstan" ]; then
|
||||||
|
vendor/bin/phpstan analyse --no-progress 2>&1 || { echo "::warning::PHPStan found issues"; }
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Tests ─────────────────────────────────────────────────────────────
|
||||||
|
test:
|
||||||
|
name: Tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: lint
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Detect toolchain
|
||||||
|
id: detect
|
||||||
|
run: |
|
||||||
|
HAS_PHP=false
|
||||||
|
HAS_NODE=false
|
||||||
|
[ -f "composer.json" ] && HAS_PHP=true
|
||||||
|
[ -f "package.json" ] && HAS_NODE=true
|
||||||
|
echo "has_php=$HAS_PHP" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "has_node=$HAS_NODE" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Setup PHP
|
||||||
|
if: steps.detect.outputs.has_php == 'true'
|
||||||
|
run: |
|
||||||
|
if ! command -v php &> /dev/null; then
|
||||||
|
sudo apt-get update -qq
|
||||||
|
sudo apt-get install -y -qq php-cli php-mbstring php-xml >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
if: steps.detect.outputs.has_node == 'true'
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
[ -f "composer.json" ] && composer install --no-interaction --prefer-dist --quiet 2>/dev/null || true
|
||||||
|
[ -f "package.json" ] && { npm ci --quiet 2>/dev/null || npm install --quiet 2>/dev/null || true; }
|
||||||
|
|
||||||
|
- name: Run PHP tests
|
||||||
|
if: steps.detect.outputs.has_php == 'true'
|
||||||
|
run: |
|
||||||
|
if [ -f "vendor/bin/phpunit" ]; then
|
||||||
|
vendor/bin/phpunit --testdox 2>&1
|
||||||
|
echo "## PHPUnit" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "Tests passed." >> $GITHUB_STEP_SUMMARY
|
||||||
|
elif [ -f "phpunit.xml" ] || [ -f "phpunit.xml.dist" ]; then
|
||||||
|
echo "::warning::PHPUnit config found but phpunit not installed"
|
||||||
|
else
|
||||||
|
echo "No PHPUnit configured — skipping"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Run Node.js tests
|
||||||
|
if: steps.detect.outputs.has_node == 'true'
|
||||||
|
run: |
|
||||||
|
if jq -e '.scripts.test' package.json > /dev/null 2>&1; then
|
||||||
|
npm test 2>&1
|
||||||
|
echo "## Node.js Tests" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "Tests passed." >> $GITHUB_STEP_SUMMARY
|
||||||
|
else
|
||||||
|
echo "No test script in package.json — skipping"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Build check
|
||||||
|
run: |
|
||||||
|
if [ -f "Makefile" ]; then
|
||||||
|
make build 2>&1 || echo "::warning::Build failed or not configured"
|
||||||
|
elif [ -f "package.json" ] && jq -e '.scripts.build' package.json > /dev/null 2>&1; then
|
||||||
|
npm run build 2>&1 || echo "::warning::Build failed"
|
||||||
|
fi
|
||||||
@@ -0,0 +1,500 @@
|
|||||||
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
#
|
||||||
|
# This file is part of a Moko Consulting project.
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
#
|
||||||
|
# FILE INFORMATION
|
||||||
|
# DEFGROUP: Gitea.Workflow.Template
|
||||||
|
# INGROUP: MokoStandards.CI
|
||||||
|
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API
|
||||||
|
# PATH: /templates/workflows/joomla/ci-joomla.yml.template
|
||||||
|
# VERSION: 04.06.00
|
||||||
|
# BRIEF: CI workflow for Joomla extensions — lint, validate, test
|
||||||
|
|
||||||
|
name: "Joomla: Extension CI"
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- 'dev/**'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
env:
|
||||||
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint-and-validate:
|
||||||
|
name: Lint & Validate
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup PHP
|
||||||
|
run: |
|
||||||
|
if ! command -v php &> /dev/null; then
|
||||||
|
sudo apt-get update -qq
|
||||||
|
sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
php -v && composer --version
|
||||||
|
|
||||||
|
- name: Setup moko-platform tools
|
||||||
|
env:
|
||||||
|
MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN || github.token }}
|
||||||
|
MOKO_CLONE_HOST: ${{ secrets.GA_TOKEN && 'git.mokoconsulting.tech/MokoConsulting' || 'github.com/mokoconsulting-tech' }}
|
||||||
|
run: |
|
||||||
|
if [ -d "/tmp/moko-platform" ] || [ -d "/opt/moko-platform" ]; then
|
||||||
|
echo "moko-platform already available on runner — skipping clone"
|
||||||
|
else
|
||||||
|
git clone --depth 1 --branch main --quiet \
|
||||||
|
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
|
||||||
|
/tmp/moko-platform 2>/dev/null || echo "moko-platform clone skipped — continuing without it"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
env:
|
||||||
|
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || secrets.GA_TOKEN || github.token }}"}}'
|
||||||
|
run: |
|
||||||
|
if [ -f "composer.json" ]; then
|
||||||
|
composer install \
|
||||||
|
--no-interaction \
|
||||||
|
--prefer-dist \
|
||||||
|
--optimize-autoloader
|
||||||
|
else
|
||||||
|
echo "No composer.json found — skipping dependency install"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: PHP syntax check
|
||||||
|
run: |
|
||||||
|
ERRORS=0
|
||||||
|
for DIR in src/ htdocs/; do
|
||||||
|
if [ -d "$DIR" ]; then
|
||||||
|
FOUND=1
|
||||||
|
while IFS= read -r -d '' FILE; do
|
||||||
|
OUTPUT=$(php -l "$FILE" 2>&1)
|
||||||
|
if echo "$OUTPUT" | grep -q "Parse error"; then
|
||||||
|
echo "::error file=${FILE}::${OUTPUT}"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
done < <(find "$DIR" -name "*.php" -print0)
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo "### PHP Syntax Check" >> $GITHUB_STEP_SUMMARY
|
||||||
|
if [ "${ERRORS}" -gt 0 ]; then
|
||||||
|
echo "**${ERRORS} syntax error(s) found.**" >> $GITHUB_STEP_SUMMARY
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "All PHP files passed syntax check." >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: XML manifest validation
|
||||||
|
run: |
|
||||||
|
echo "### XML Manifest Validation" >> $GITHUB_STEP_SUMMARY
|
||||||
|
ERRORS=0
|
||||||
|
|
||||||
|
# Find the extension manifest (XML with <extension tag)
|
||||||
|
MANIFEST=""
|
||||||
|
for XML_FILE in $(find . -maxdepth 2 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do
|
||||||
|
if grep -q "<extension" "$XML_FILE" 2>/dev/null; then
|
||||||
|
MANIFEST="$XML_FILE"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z "$MANIFEST" ]; then
|
||||||
|
echo "No Joomla extension manifest found (XML file with \`<extension\` tag)." >> $GITHUB_STEP_SUMMARY
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
else
|
||||||
|
echo "Manifest found: \`${MANIFEST}\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
# Validate well-formed XML
|
||||||
|
php -r "
|
||||||
|
\$xml = @simplexml_load_file('$MANIFEST');
|
||||||
|
if (\$xml === false) {
|
||||||
|
echo 'INVALID';
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
echo 'VALID';
|
||||||
|
" > /tmp/xml_result 2>&1
|
||||||
|
XML_RESULT=$(cat /tmp/xml_result)
|
||||||
|
if [ "$XML_RESULT" != "VALID" ]; then
|
||||||
|
echo "Manifest is not well-formed XML." >> $GITHUB_STEP_SUMMARY
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
else
|
||||||
|
echo "Manifest is well-formed XML." >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check required tags: name, version, author
|
||||||
|
for TAG in name version author; do
|
||||||
|
if ! grep -q "<${TAG}>" "$MANIFEST" 2>/dev/null; then
|
||||||
|
echo "Missing required tag: \`<${TAG}>\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
else
|
||||||
|
echo "Found required tag: \`<${TAG}>\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Namespace is required for components/plugins but not packages
|
||||||
|
EXT_TYPE=$(grep -oP '<extension[^>]*\btype="\K[^"]+' "$MANIFEST" | head -1)
|
||||||
|
if [ "$EXT_TYPE" != "package" ]; then
|
||||||
|
if ! grep -q "<namespace" "$MANIFEST" 2>/dev/null; then
|
||||||
|
echo "Missing required tag: \`<namespace>\` (required for Joomla 5+ ${EXT_TYPE} extensions)" >> $GITHUB_STEP_SUMMARY
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
else
|
||||||
|
echo "Found required tag: \`<namespace>\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Package extension — \`<namespace>\` not required." >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${ERRORS}" -gt 0 ]; then
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "**${ERRORS} manifest issue(s) found.**" >> $GITHUB_STEP_SUMMARY
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "**Manifest validation passed.**" >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Check language files referenced in manifest
|
||||||
|
run: |
|
||||||
|
echo "### Language File Check" >> $GITHUB_STEP_SUMMARY
|
||||||
|
ERRORS=0
|
||||||
|
|
||||||
|
MANIFEST=""
|
||||||
|
for XML_FILE in $(find . -maxdepth 2 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do
|
||||||
|
if grep -q "<extension" "$XML_FILE" 2>/dev/null; then
|
||||||
|
MANIFEST="$XML_FILE"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -n "$MANIFEST" ]; then
|
||||||
|
# Extract language file references from manifest
|
||||||
|
LANG_FILES=$(grep -oP 'language\s+tag="[^"]*"[^>]*>\K[^<]+' "$MANIFEST" 2>/dev/null || true)
|
||||||
|
if [ -z "$LANG_FILES" ]; then
|
||||||
|
echo "No language file references found in manifest — skipping." >> $GITHUB_STEP_SUMMARY
|
||||||
|
else
|
||||||
|
while IFS= read -r LANG_FILE; do
|
||||||
|
LANG_FILE=$(echo "$LANG_FILE" | xargs)
|
||||||
|
if [ -z "$LANG_FILE" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
# Check in common locations
|
||||||
|
FOUND=0
|
||||||
|
for BASE in "." "src" "htdocs"; do
|
||||||
|
if [ -f "${BASE}/${LANG_FILE}" ]; then
|
||||||
|
FOUND=1
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [ "$FOUND" -eq 0 ]; then
|
||||||
|
echo "Missing language file: \`${LANG_FILE}\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
else
|
||||||
|
echo "Language file present: \`${LANG_FILE}\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
done <<< "$LANG_FILES"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "No manifest found — skipping language check." >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${ERRORS}" -gt 0 ]; then
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "**${ERRORS} missing language file(s).**" >> $GITHUB_STEP_SUMMARY
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "**Language file check passed.**" >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Check index.html files in directories
|
||||||
|
run: |
|
||||||
|
echo "### Index.html Check" >> $GITHUB_STEP_SUMMARY
|
||||||
|
MISSING=0
|
||||||
|
CHECKED=0
|
||||||
|
|
||||||
|
for DIR in src/ htdocs/; do
|
||||||
|
if [ -d "$DIR" ]; then
|
||||||
|
while IFS= read -r -d '' SUBDIR; do
|
||||||
|
CHECKED=$((CHECKED + 1))
|
||||||
|
if [ ! -f "${SUBDIR}/index.html" ]; then
|
||||||
|
echo "Missing index.html in: \`${SUBDIR}\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
MISSING=$((MISSING + 1))
|
||||||
|
fi
|
||||||
|
done < <(find "$DIR" -type d -print0)
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "${CHECKED}" -eq 0 ]; then
|
||||||
|
echo "No src/ or htdocs/ directories found — skipping." >> $GITHUB_STEP_SUMMARY
|
||||||
|
elif [ "${MISSING}" -gt 0 ]; then
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "**${MISSING} director(ies) missing index.html out of ${CHECKED} checked.**" >> $GITHUB_STEP_SUMMARY
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "All ${CHECKED} directories contain index.html." >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
|
||||||
|
release-readiness:
|
||||||
|
name: Release Readiness Check
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.event_name == 'pull_request' && github.base_ref == 'main'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Validate release readiness
|
||||||
|
run: |
|
||||||
|
echo "## Release Readiness" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
ERRORS=0
|
||||||
|
|
||||||
|
# Extract version from README.md
|
||||||
|
README_VERSION=$(grep -oP '^\s*VERSION:\s*\K[0-9]{2}\.[0-9]{2}\.[0-9]{2}' README.md | head -1)
|
||||||
|
if [ -z "$README_VERSION" ]; then
|
||||||
|
echo "No VERSION found in README.md FILE INFORMATION block." >> $GITHUB_STEP_SUMMARY
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
else
|
||||||
|
echo "README version: \`${README_VERSION}\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Find the extension manifest
|
||||||
|
MANIFEST=""
|
||||||
|
for XML_FILE in $(find . -maxdepth 2 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do
|
||||||
|
if grep -q "<extension" "$XML_FILE" 2>/dev/null; then
|
||||||
|
MANIFEST="$XML_FILE"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z "$MANIFEST" ]; then
|
||||||
|
echo "No Joomla extension manifest found." >> $GITHUB_STEP_SUMMARY
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
else
|
||||||
|
echo "Manifest: \`${MANIFEST}\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
# Check <version> matches README VERSION
|
||||||
|
MANIFEST_VERSION=$(grep -oP '<version>\K[^<]+' "$MANIFEST" | head -1)
|
||||||
|
if [ -z "$MANIFEST_VERSION" ]; then
|
||||||
|
echo "No \`<version>\` tag in manifest." >> $GITHUB_STEP_SUMMARY
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
elif [ -n "$README_VERSION" ] && [ "$MANIFEST_VERSION" != "$README_VERSION" ]; then
|
||||||
|
echo "Manifest version \`${MANIFEST_VERSION}\` does not match README \`${README_VERSION}\`." >> $GITHUB_STEP_SUMMARY
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
else
|
||||||
|
echo "Manifest version: \`${MANIFEST_VERSION}\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check extension type, element, client attributes
|
||||||
|
EXT_TYPE=$(grep -oP '<extension[^>]*\btype="\K[^"]+' "$MANIFEST" | head -1)
|
||||||
|
if [ -z "$EXT_TYPE" ]; then
|
||||||
|
echo "Missing \`type\` attribute on \`<extension>\` tag." >> $GITHUB_STEP_SUMMARY
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
else
|
||||||
|
echo "Extension type: \`${EXT_TYPE}\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Element check (component/module/plugin name)
|
||||||
|
HAS_ELEMENT=$(grep -cP '<(element|name)>' "$MANIFEST" 2>/dev/null || echo "0")
|
||||||
|
if [ "$HAS_ELEMENT" -eq 0 ]; then
|
||||||
|
echo "Missing \`<element>\` or \`<name>\` in manifest." >> $GITHUB_STEP_SUMMARY
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Client attribute for site/admin modules and plugins
|
||||||
|
if echo "$EXT_TYPE" | grep -qP "^(module|plugin)$"; then
|
||||||
|
HAS_CLIENT=$(grep -cP '<extension[^>]*\bclient=' "$MANIFEST" 2>/dev/null || echo "0")
|
||||||
|
if [ "$HAS_CLIENT" -eq 0 ]; then
|
||||||
|
echo "Missing \`client\` attribute for ${EXT_TYPE} extension." >> $GITHUB_STEP_SUMMARY
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check updates.xml exists
|
||||||
|
if [ -f "updates.xml" ] || [ -f "updates.xml" ]; then
|
||||||
|
echo "Update XML present." >> $GITHUB_STEP_SUMMARY
|
||||||
|
else
|
||||||
|
echo "No updates.xml found." >> $GITHUB_STEP_SUMMARY
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check CHANGELOG.md exists
|
||||||
|
if [ -f "CHANGELOG.md" ]; then
|
||||||
|
echo "CHANGELOG.md present." >> $GITHUB_STEP_SUMMARY
|
||||||
|
else
|
||||||
|
echo "No CHANGELOG.md found." >> $GITHUB_STEP_SUMMARY
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
if [ $ERRORS -gt 0 ]; then
|
||||||
|
echo "**${ERRORS} issue(s) must be resolved before release.**" >> $GITHUB_STEP_SUMMARY
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "**Extension is ready for release.**" >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
|
||||||
|
test:
|
||||||
|
name: Tests (PHP ${{ matrix.php }})
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: lint-and-validate
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
php: ['8.2', '8.3']
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup PHP ${{ matrix.php }}
|
||||||
|
run: |
|
||||||
|
if ! command -v php &> /dev/null; then
|
||||||
|
sudo apt-get update -qq
|
||||||
|
sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
php -v && composer --version
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
env:
|
||||||
|
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || secrets.GA_TOKEN || github.token }}"}}'
|
||||||
|
run: |
|
||||||
|
if [ -f "composer.json" ]; then
|
||||||
|
composer install \
|
||||||
|
--no-interaction \
|
||||||
|
--prefer-dist \
|
||||||
|
--optimize-autoloader
|
||||||
|
else
|
||||||
|
echo "No composer.json found — skipping dependency install"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: |
|
||||||
|
echo "### Test Results (PHP ${{ matrix.php }})" >> $GITHUB_STEP_SUMMARY
|
||||||
|
if [ -f "phpunit.xml" ] || [ -f "phpunit.xml.dist" ]; then
|
||||||
|
vendor/bin/phpunit --testdox 2>&1 | tee /tmp/test-output.log
|
||||||
|
EXIT=${PIPESTATUS[0]}
|
||||||
|
if [ $EXIT -eq 0 ]; then
|
||||||
|
echo "All tests passed." >> $GITHUB_STEP_SUMMARY
|
||||||
|
else
|
||||||
|
echo "Test failures detected — see log." >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||||
|
cat /tmp/test-output.log >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
exit $EXIT
|
||||||
|
else
|
||||||
|
echo "No phpunit.xml found — skipping tests." >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
|
||||||
|
static-analysis:
|
||||||
|
name: PHPStan Analysis
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: lint-and-validate
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup PHP
|
||||||
|
run: |
|
||||||
|
if ! command -v php &> /dev/null; then
|
||||||
|
sudo apt-get update -qq
|
||||||
|
sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
php -v && composer --version
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
env:
|
||||||
|
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || secrets.GA_TOKEN || github.token }}"}}'
|
||||||
|
run: |
|
||||||
|
if [ -f "composer.json" ]; then
|
||||||
|
composer install --no-interaction --prefer-dist --optimize-autoloader
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Install PHPStan
|
||||||
|
run: |
|
||||||
|
if ! command -v vendor/bin/phpstan &> /dev/null; then
|
||||||
|
composer require --dev phpstan/phpstan --no-interaction 2>/dev/null || \
|
||||||
|
composer global require phpstan/phpstan --no-interaction
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Run PHPStan
|
||||||
|
run: |
|
||||||
|
echo "### PHPStan Static Analysis" >> $GITHUB_STEP_SUMMARY
|
||||||
|
PHPSTAN="vendor/bin/phpstan"
|
||||||
|
if [ ! -f "$PHPSTAN" ]; then
|
||||||
|
PHPSTAN=$(composer global config bin-dir --absolute 2>/dev/null)/phpstan
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Determine source directory
|
||||||
|
SRC_DIR=""
|
||||||
|
for DIR in src/ htdocs/ lib/; do
|
||||||
|
if [ -d "$DIR" ]; then
|
||||||
|
SRC_DIR="$DIR"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z "$SRC_DIR" ]; then
|
||||||
|
echo "No source directory found (src/, htdocs/, lib/) — skipping." >> $GITHUB_STEP_SUMMARY
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Use repo phpstan.neon if present, otherwise use baseline config
|
||||||
|
ARGS="analyse ${SRC_DIR} --memory-limit=512M --no-progress --error-format=table"
|
||||||
|
if [ -f "phpstan.neon" ] || [ -f "phpstan.neon.dist" ]; then
|
||||||
|
echo "Using project PHPStan config." >> $GITHUB_STEP_SUMMARY
|
||||||
|
else
|
||||||
|
ARGS="$ARGS --level=3"
|
||||||
|
echo "No phpstan.neon found — using level 3 (type inference)." >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
|
||||||
|
$PHPSTAN $ARGS 2>&1 | tee /tmp/phpstan-output.txt
|
||||||
|
EXIT=${PIPESTATUS[0]}
|
||||||
|
|
||||||
|
if [ $EXIT -eq 0 ]; then
|
||||||
|
echo "**No errors found.**" >> $GITHUB_STEP_SUMMARY
|
||||||
|
else
|
||||||
|
ERRORS=$(grep -c "ERROR" /tmp/phpstan-output.txt 2>/dev/null || echo "some")
|
||||||
|
echo "**${ERRORS} error(s) found.** Review output above." >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||||
|
tail -30 /tmp/phpstan-output.txt >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
exit $EXIT
|
||||||
|
|
||||||
|
pre-release:
|
||||||
|
name: Build RC Pre-Release
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [lint-and-validate, test]
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Trigger pre-release build
|
||||||
|
env:
|
||||||
|
GA_TOKEN: ${{ secrets.GA_TOKEN }}
|
||||||
|
REPO: ${{ github.repository }}
|
||||||
|
BRANCH: ${{ github.head_ref }}
|
||||||
|
run: |
|
||||||
|
curl -s -X POST \
|
||||||
|
"${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${REPO}/actions/workflows/pre-release.yml/dispatches" \
|
||||||
|
-H "Authorization: token ${GA_TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"ref\":\"${BRANCH}\",\"inputs\":{\"stability\":\"release-candidate\"}}"
|
||||||
|
echo "### Pre-Release" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "Triggered RC build on branch \`${BRANCH}\`" >> $GITHUB_STEP_SUMMARY
|
||||||
@@ -4,10 +4,10 @@
|
|||||||
#
|
#
|
||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: Gitea.Workflow
|
||||||
# INGROUP: moko-platform.Maintenance
|
# INGROUP: MokoStandards.Maintenance
|
||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
|
||||||
# PATH: /.gitea/workflows/cleanup.yml
|
# PATH: /.gitea/workflows/cleanup.yml
|
||||||
# VERSION: 09.23.00
|
# VERSION: 01.00.00
|
||||||
# BRIEF: Scheduled cleanup — delete merged branches and old workflow runs
|
# BRIEF: Scheduled cleanup — delete merged branches and old workflow runs
|
||||||
|
|
||||||
name: "Universal: Repository Cleanup"
|
name: "Universal: Repository Cleanup"
|
||||||
@@ -33,17 +33,17 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
token: ${{ secrets.GA_TOKEN }}
|
||||||
|
|
||||||
- name: Delete merged branches
|
- name: Delete merged branches
|
||||||
env:
|
env:
|
||||||
GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
GA_TOKEN: ${{ secrets.GA_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
echo "=== Merged Branch Cleanup ==="
|
echo "=== Merged Branch Cleanup ==="
|
||||||
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
||||||
|
|
||||||
# List branches via API
|
# List branches via API
|
||||||
BRANCHES=$(curl -sS -H "Authorization: token ${GITEA_TOKEN}" \
|
BRANCHES=$(curl -sS -H "Authorization: token ${GA_TOKEN}" \
|
||||||
"${API}/branches?limit=50" | jq -r '.[].name')
|
"${API}/branches?limit=50" | jq -r '.[].name')
|
||||||
|
|
||||||
DELETED=0
|
DELETED=0
|
||||||
@@ -56,7 +56,7 @@ jobs:
|
|||||||
# Check if branch is merged into main
|
# Check if branch is merged into main
|
||||||
if git merge-base --is-ancestor "origin/${BRANCH}" origin/main 2>/dev/null; then
|
if git merge-base --is-ancestor "origin/${BRANCH}" origin/main 2>/dev/null; then
|
||||||
echo " Deleting merged branch: ${BRANCH}"
|
echo " Deleting merged branch: ${BRANCH}"
|
||||||
curl -sS -X DELETE -H "Authorization: token ${GITEA_TOKEN}" \
|
curl -sS -X DELETE -H "Authorization: token ${GA_TOKEN}" \
|
||||||
"${API}/branches/${BRANCH}" 2>/dev/null || true
|
"${API}/branches/${BRANCH}" 2>/dev/null || true
|
||||||
DELETED=$((DELETED + 1))
|
DELETED=$((DELETED + 1))
|
||||||
fi
|
fi
|
||||||
@@ -66,20 +66,20 @@ jobs:
|
|||||||
|
|
||||||
- name: Clean old workflow runs
|
- name: Clean old workflow runs
|
||||||
env:
|
env:
|
||||||
GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
GA_TOKEN: ${{ secrets.GA_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
echo "=== Workflow Run Cleanup ==="
|
echo "=== Workflow Run Cleanup ==="
|
||||||
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
||||||
CUTOFF=$(date -d "30 days ago" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -v-30d +%Y-%m-%dT%H:%M:%SZ)
|
CUTOFF=$(date -d "30 days ago" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -v-30d +%Y-%m-%dT%H:%M:%SZ)
|
||||||
|
|
||||||
# Get old completed runs
|
# Get old completed runs
|
||||||
RUNS=$(curl -sS -H "Authorization: token ${GITEA_TOKEN}" \
|
RUNS=$(curl -sS -H "Authorization: token ${GA_TOKEN}" \
|
||||||
"${API}/actions/runs?status=completed&limit=50" | \
|
"${API}/actions/runs?status=completed&limit=50" | \
|
||||||
jq -r ".workflow_runs[] | select(.created_at < \"${CUTOFF}\") | .id" 2>/dev/null)
|
jq -r ".workflow_runs[] | select(.created_at < \"${CUTOFF}\") | .id" 2>/dev/null)
|
||||||
|
|
||||||
DELETED=0
|
DELETED=0
|
||||||
for RUN_ID in $RUNS; do
|
for RUN_ID in $RUNS; do
|
||||||
curl -sS -X DELETE -H "Authorization: token ${GITEA_TOKEN}" \
|
curl -sS -X DELETE -H "Authorization: token ${GA_TOKEN}" \
|
||||||
"${API}/actions/runs/${RUN_ID}" 2>/dev/null || true
|
"${API}/actions/runs/${RUN_ID}" 2>/dev/null || true
|
||||||
DELETED=$((DELETED + 1))
|
DELETED=$((DELETED + 1))
|
||||||
done
|
done
|
||||||
|
|||||||
@@ -0,0 +1,126 @@
|
|||||||
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
#
|
||||||
|
# FILE INFORMATION
|
||||||
|
# DEFGROUP: Gitea.Workflow
|
||||||
|
# INGROUP: MokoStandards.Deploy
|
||||||
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
|
||||||
|
# PATH: /templates/workflows/joomla/deploy-manual.yml.template
|
||||||
|
# VERSION: 04.07.00
|
||||||
|
# BRIEF: Manual SFTP deploy to dev server for Joomla repos
|
||||||
|
|
||||||
|
name: "Universal: Deploy to Dev (Manual)"
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
clear_remote:
|
||||||
|
description: 'Delete all remote files before uploading'
|
||||||
|
required: false
|
||||||
|
default: 'false'
|
||||||
|
type: boolean
|
||||||
|
|
||||||
|
env:
|
||||||
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
name: SFTP Deploy to Dev
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||||
|
|
||||||
|
- name: Setup PHP
|
||||||
|
run: |
|
||||||
|
php -v && composer --version
|
||||||
|
|
||||||
|
- name: Setup MokoStandards tools
|
||||||
|
env:
|
||||||
|
GA_TOKEN: ${{ secrets.GA_TOKEN || secrets.GA_TOKEN || github.token }}
|
||||||
|
MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN || secrets.GA_TOKEN || github.token }}
|
||||||
|
MOKO_CLONE_HOST: ${{ secrets.GA_TOKEN && 'git.mokoconsulting.tech/MokoConsulting' || 'github.com/mokoconsulting-tech' }}
|
||||||
|
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GA_TOKEN || github.token }}"}}'
|
||||||
|
run: |
|
||||||
|
git clone --depth 1 --branch main --quiet \
|
||||||
|
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \
|
||||||
|
/tmp/mokostandards-api 2>/dev/null || true
|
||||||
|
if [ -d "/tmp/mokostandards-api" ] && [ -f "/tmp/mokostandards-api/composer.json" ]; then
|
||||||
|
cd /tmp/mokostandards-api && composer install --no-dev --no-interaction --quiet 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Check FTP configuration
|
||||||
|
id: check
|
||||||
|
env:
|
||||||
|
HOST: ${{ vars.DEV_FTP_HOST }}
|
||||||
|
PATH_VAR: ${{ vars.DEV_FTP_PATH }}
|
||||||
|
PORT: ${{ vars.DEV_FTP_PORT }}
|
||||||
|
run: |
|
||||||
|
if [ -z "$HOST" ] || [ -z "$PATH_VAR" ]; then
|
||||||
|
echo "DEV_FTP_HOST or DEV_FTP_PATH not configured -- cannot deploy"
|
||||||
|
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "skip=false" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "host=$HOST" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
REMOTE="${PATH_VAR%/}"
|
||||||
|
echo "remote=$REMOTE" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
[ -z "$PORT" ] && PORT="22"
|
||||||
|
echo "port=$PORT" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Deploy via SFTP
|
||||||
|
if: steps.check.outputs.skip != 'true'
|
||||||
|
env:
|
||||||
|
SFTP_KEY: ${{ secrets.DEV_FTP_KEY }}
|
||||||
|
SFTP_PASS: ${{ secrets.DEV_FTP_PASSWORD }}
|
||||||
|
SFTP_USER: ${{ vars.DEV_FTP_USERNAME }}
|
||||||
|
run: |
|
||||||
|
SOURCE_DIR="src"
|
||||||
|
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
|
||||||
|
[ ! -d "$SOURCE_DIR" ] && { echo "No src/ or htdocs/ -- nothing to deploy"; exit 0; }
|
||||||
|
|
||||||
|
printf '{"host":"%s","port":%s,"username":"%s","remotePath":"%s"' \
|
||||||
|
"${{ steps.check.outputs.host }}" "${{ steps.check.outputs.port }}" "$SFTP_USER" "${{ steps.check.outputs.remote }}" \
|
||||||
|
> /tmp/sftp-config.json
|
||||||
|
|
||||||
|
if [ -n "$SFTP_KEY" ]; then
|
||||||
|
echo "$SFTP_KEY" > /tmp/deploy_key
|
||||||
|
chmod 600 /tmp/deploy_key
|
||||||
|
printf ',"privateKeyPath":"/tmp/deploy_key"}' >> /tmp/sftp-config.json
|
||||||
|
else
|
||||||
|
printf ',"password":"%s"}' "$SFTP_PASS" >> /tmp/sftp-config.json
|
||||||
|
fi
|
||||||
|
|
||||||
|
DEPLOY_ARGS=(--path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json)
|
||||||
|
[ "${{ inputs.clear_remote }}" = "true" ] && DEPLOY_ARGS+=(--clear-remote)
|
||||||
|
|
||||||
|
PLATFORM=$(php /tmp/mokostandards-api/cli/platform_detect.php --path . 2>/dev/null || true)
|
||||||
|
if [ "$PLATFORM" = "waas-component" ] && [ -f "/tmp/mokostandards-api/deploy/deploy-joomla.php" ]; then
|
||||||
|
php /tmp/mokostandards-api/deploy/deploy-joomla.php "${DEPLOY_ARGS[@]}"
|
||||||
|
else
|
||||||
|
php /tmp/mokostandards-api/deploy/deploy-sftp.php "${DEPLOY_ARGS[@]}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -f /tmp/deploy_key /tmp/sftp-config.json
|
||||||
|
|
||||||
|
- name: Summary
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
if [ "${{ steps.check.outputs.skip }}" = "true" ]; then
|
||||||
|
echo "### Deploy Skipped -- FTP not configured" >> $GITHUB_STEP_SUMMARY
|
||||||
|
else
|
||||||
|
echo "### Manual Dev Deploy Complete" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Host | \`${{ steps.check.outputs.host }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Remote | \`${{ steps.check.outputs.remote }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Clear | ${{ inputs.clear_remote }} |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
@@ -4,10 +4,10 @@
|
|||||||
#
|
#
|
||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: Gitea.Workflow
|
||||||
# INGROUP: moko-platform.Security
|
# INGROUP: MokoStandards.Security
|
||||||
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
|
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API
|
||||||
# PATH: /templates/workflows/gitleaks.yml.template
|
# PATH: /templates/workflows/gitleaks.yml.template
|
||||||
# VERSION: 09.23.00
|
# VERSION: 01.00.00
|
||||||
# BRIEF: Secret scanning — detect leaked credentials, API keys, and tokens
|
# BRIEF: Secret scanning — detect leaked credentials, API keys, and tokens
|
||||||
#
|
#
|
||||||
# +========================================================================+
|
# +========================================================================+
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: Gitea.Workflow
|
||||||
# INGROUP: moko-platform.Automation
|
# INGROUP: moko-platform.Automation
|
||||||
# VERSION: 02.34.50
|
# VERSION: 01.00.00
|
||||||
# BRIEF: Auto-create feature branch when an issue is opened
|
# BRIEF: Auto-create feature branch when an issue is opened
|
||||||
|
|
||||||
name: "Universal: Issue Branch"
|
name: "Universal: Issue Branch"
|
||||||
@@ -28,7 +28,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Create branch and comment
|
- name: Create branch and comment
|
||||||
run: |
|
run: |
|
||||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
TOKEN="${{ secrets.GA_TOKEN }}"
|
||||||
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
||||||
ISSUE_NUM="${{ github.event.issue.number }}"
|
ISSUE_NUM="${{ github.event.issue.number }}"
|
||||||
ISSUE_TITLE="${{ github.event.issue.title }}"
|
ISSUE_TITLE="${{ github.event.issue.title }}"
|
||||||
|
|||||||
@@ -4,10 +4,10 @@
|
|||||||
#
|
#
|
||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: Gitea.Workflow
|
||||||
# INGROUP: moko-platform.Notifications
|
# INGROUP: MokoStandards.Notifications
|
||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
|
||||||
# PATH: /.gitea/workflows/notify.yml
|
# PATH: /.gitea/workflows/notify.yml
|
||||||
# VERSION: 09.23.00
|
# VERSION: 01.00.00
|
||||||
# BRIEF: Push notifications via ntfy on release success or workflow failure
|
# BRIEF: Push notifications via ntfy on release success or workflow failure
|
||||||
|
|
||||||
name: "Universal: Notifications"
|
name: "Universal: Notifications"
|
||||||
|
|||||||
+508
-532
File diff suppressed because it is too large
Load Diff
@@ -8,244 +8,4 @@
|
|||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||||
# PATH: /templates/workflows/universal/pre-release.yml.template
|
# PATH: /templates/workflows/universal/pre-release.yml.template
|
||||||
# VERSION: 05.01.00
|
# VERSION: 05.01.00
|
||||||
# BRIEF: Auto pre-release on push to dev/alpha/beta/rc branches
|
# BRIEF: Auto pre-release on push to dev/alpha/beta/rc branches
|
||||||
|
|
||||||
name: "Universal: Pre-Release"
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- dev
|
|
||||||
- 'fix/**'
|
|
||||||
- 'patch/**'
|
|
||||||
- 'hotfix/**'
|
|
||||||
- 'bugfix/**'
|
|
||||||
- alpha
|
|
||||||
- beta
|
|
||||||
- rc
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
stability:
|
|
||||||
description: 'Pre-release channel'
|
|
||||||
required: true
|
|
||||||
type: choice
|
|
||||||
options:
|
|
||||||
- development
|
|
||||||
- alpha
|
|
||||||
- beta
|
|
||||||
- release-candidate
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
env:
|
|
||||||
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 }}
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: "Build Pre-Release (${{ inputs.stability || github.ref_name }})"
|
|
||||||
runs-on: release
|
|
||||||
if: >-
|
|
||||||
github.event_name == 'workflow_dispatch' ||
|
|
||||||
github.event_name == 'push'
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
|
||||||
ref: ${{ github.ref_name }}
|
|
||||||
|
|
||||||
- name: Setup moko-platform tools
|
|
||||||
env:
|
|
||||||
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
|
||||||
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
|
||||||
run: |
|
|
||||||
# Use pre-installed /opt/moko-platform if available (updated by cron every 6h)
|
|
||||||
if [ -f /opt/moko-platform/cli/version_bump.php ] && [ -f /opt/moko-platform/cli/manifest_element.php ] && [ -f /opt/moko-platform/vendor/autoload.php ]; then
|
|
||||||
echo Using pre-installed /opt/moko-platform
|
|
||||||
echo MOKO_CLI=/opt/moko-platform/cli >> $GITHUB_ENV
|
|
||||||
else
|
|
||||||
echo Falling back to fresh clone
|
|
||||||
if ! command -v composer > /dev/null 2>&1; 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
|
|
||||||
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git
|
|
||||||
git clone --depth 1 --branch main --quiet $CLONE_URL /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
|
|
||||||
|
|
||||||
- name: Detect platform
|
|
||||||
id: platform
|
|
||||||
run: |
|
|
||||||
# Auto-detect and update platform if not set in manifest
|
|
||||||
php ${MOKO_CLI}/platform_detect.php --path . --github-output 2>/dev/null || true
|
|
||||||
php ${MOKO_CLI}/manifest_read.php --path . --github-output
|
|
||||||
|
|
||||||
- name: Resolve metadata and bump version
|
|
||||||
id: meta
|
|
||||||
run: |
|
|
||||||
# Auto-detect stability from branch name on push, or use input on dispatch
|
|
||||||
if [ "${{ github.event_name }}" = "push" ]; then
|
|
||||||
case "${{ github.ref_name }}" in
|
|
||||||
rc) STABILITY="release-candidate" ;;
|
|
||||||
alpha) STABILITY="alpha" ;;
|
|
||||||
beta) STABILITY="beta" ;;
|
|
||||||
*) STABILITY="development" ;;
|
|
||||||
esac
|
|
||||||
else
|
|
||||||
STABILITY="${{ inputs.stability || 'development' }}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
case "$STABILITY" in
|
|
||||||
development) SUFFIX="-dev"; TAG="development" ;;
|
|
||||||
alpha) SUFFIX="-alpha"; TAG="alpha" ;;
|
|
||||||
beta) SUFFIX="-beta"; TAG="beta" ;;
|
|
||||||
release-candidate) SUFFIX="-rc"; TAG="release-candidate" ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# Bump version via CLI: patch for dev/alpha/beta, minor for RC
|
|
||||||
case "$STABILITY" in
|
|
||||||
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
|
|
||||||
php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true
|
|
||||||
|
|
||||||
# Ensure licensing tags (updateservers, dlid) if enabled in manifest.xml
|
|
||||||
php ${MOKO_CLI}/manifest_licensing.php --path . --fix 2>/dev/null || true
|
|
||||||
|
|
||||||
# Append suffix for output
|
|
||||||
if [ -n "$SUFFIX" ]; then
|
|
||||||
VERSION="${VERSION}${SUFFIX}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Commit version bump
|
|
||||||
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"
|
|
||||||
git add -A
|
|
||||||
git diff --cached --quiet || {
|
|
||||||
git commit -m "chore(version): pre-release bump to ${VERSION} [skip ci]"
|
|
||||||
git push origin HEAD 2>&1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Auto-detect element via manifest_element.php
|
|
||||||
php ${MOKO_CLI}/manifest_element.php \
|
|
||||||
--path . --version "$VERSION" --stability "$STABILITY" \
|
|
||||||
--repo "${GITEA_REPO}" --github-output
|
|
||||||
|
|
||||||
# Read back element outputs
|
|
||||||
EXT_ELEMENT=$(grep '^ext_element=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
|
|
||||||
ZIP_NAME=$(grep '^zip_name=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
|
|
||||||
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
|
|
||||||
[ -z "$ZIP_NAME" ] && ZIP_NAME="${EXT_ELEMENT}-${VERSION}.zip"
|
|
||||||
|
|
||||||
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "suffix=${SUFFIX}" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "zip_name=${ZIP_NAME}" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "ext_element=${EXT_ELEMENT}" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
echo "=== Pre-Release: ${EXT_ELEMENT} ${VERSION}${SUFFIX} ==="
|
|
||||||
|
|
||||||
- name: Create release
|
|
||||||
id: release
|
|
||||||
run: |
|
|
||||||
TAG="${{ steps.meta.outputs.tag }}"
|
|
||||||
VERSION="${{ steps.meta.outputs.version }}"
|
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
|
||||||
php ${MOKO_CLI}/release_create.php \
|
|
||||||
--path . --version "$VERSION" --tag "$TAG" \
|
|
||||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
|
||||||
--repo "${GITEA_REPO}" --branch "${{ github.ref_name }}" --prerelease
|
|
||||||
|
|
||||||
- name: Update release notes from CHANGELOG.md
|
|
||||||
run: |
|
|
||||||
TAG="${{ steps.meta.outputs.tag }}"
|
|
||||||
VERSION="${{ steps.meta.outputs.version }}"
|
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
|
||||||
|
|
||||||
# Extract [Unreleased] section from changelog (everything between [Unreleased] and next ## heading)
|
|
||||||
if [ -f "CHANGELOG.md" ]; then
|
|
||||||
NOTES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md)
|
|
||||||
[ -z "$NOTES" ] && NOTES="Release ${VERSION}"
|
|
||||||
else
|
|
||||||
NOTES="Release ${VERSION}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Update release body via API
|
|
||||||
RELEASE_ID=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \
|
|
||||||
"${API_BASE}/releases/tags/${TAG}" | 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
|
|
||||||
|
|
||||||
- name: Build package and upload
|
|
||||||
id: package
|
|
||||||
run: |
|
|
||||||
VERSION="${{ steps.meta.outputs.version }}"
|
|
||||||
TAG="${{ steps.meta.outputs.tag }}"
|
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
|
||||||
php ${MOKO_CLI}/release_package.php \
|
|
||||||
--path . --version "$VERSION" --tag "$TAG" \
|
|
||||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
|
||||||
--repo "${GITEA_REPO}" --output /tmp || true
|
|
||||||
|
|
||||||
# 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
|
|
||||||
run: |
|
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
|
||||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
|
||||||
|
|
||||||
php ${MOKO_CLI}/release_cascade.php \
|
|
||||||
--stability "${{ steps.meta.outputs.stability }}" \
|
|
||||||
--token "${TOKEN}" \
|
|
||||||
--api-base "${API_BASE}"
|
|
||||||
|
|
||||||
- name: Summary
|
|
||||||
if: always()
|
|
||||||
run: |
|
|
||||||
VERSION="${{ steps.meta.outputs.version }}"
|
|
||||||
STABILITY="${{ steps.meta.outputs.stability }}"
|
|
||||||
ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
|
|
||||||
SHA256="${{ steps.package.outputs.sha256_zip }}"
|
|
||||||
echo "## Pre-Release Complete" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "| Channel | ${STABILITY} |" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "| Package | \`${ZIP_NAME}\` |" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "| SHA-256 | \`${SHA256:-n/a}\` |" >> $GITHUB_STEP_SUMMARY
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -4,10 +4,10 @@
|
|||||||
#
|
#
|
||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: Gitea.Workflow
|
||||||
# INGROUP: moko-platform.Security
|
# INGROUP: MokoStandards.Security
|
||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
|
||||||
# PATH: /.gitea/workflows/security-audit.yml
|
# PATH: /.gitea/workflows/security-audit.yml
|
||||||
# VERSION: 09.23.00
|
# VERSION: 01.00.00
|
||||||
# BRIEF: Dependency vulnerability scanning for composer and npm packages
|
# BRIEF: Dependency vulnerability scanning for composer and npm packages
|
||||||
|
|
||||||
name: "Universal: Security Audit"
|
name: "Universal: Security Audit"
|
||||||
@@ -80,19 +80,3 @@ jobs:
|
|||||||
-H "Priority: high" \
|
-H "Priority: high" \
|
||||||
-d "Security audit found vulnerabilities. Review dependency updates." \
|
-d "Security audit found vulnerabilities. Review dependency updates." \
|
||||||
"${NTFY_URL}/${NTFY_TOPIC}" || true
|
"${NTFY_URL}/${NTFY_TOPIC}" || true
|
||||||
|
|
||||||
|
|
||||||
- name: Joomla version audit
|
|
||||||
if: always()
|
|
||||||
run: |
|
|
||||||
if [ -f "monitoring/joomla-version-audit.php" ] && [ -n "$JOOMLA_SITES" ]; then
|
|
||||||
echo "$JOOMLA_SITES" > /tmp/sites.json
|
|
||||||
php monitoring/joomla-version-audit.php --sites /tmp/sites.json || true
|
|
||||||
echo "### Joomla Version Audit" >> $GITHUB_STEP_SUMMARY
|
|
||||||
rm -f /tmp/sites.json
|
|
||||||
else
|
|
||||||
echo "Joomla audit skipped (no script or JOOMLA_SITES_JSON not configured)"
|
|
||||||
fi
|
|
||||||
env:
|
|
||||||
JOOMLA_SITES: ${{ vars.JOOMLA_SITES_JSON }}
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
|
||||||
|
SPDX-LICENSE-IDENTIFIER: GPL-3.0-or-later
|
||||||
|
|
||||||
|
FILE INFORMATION
|
||||||
|
DEFGROUP: Joomla.Component
|
||||||
|
INGROUP: MokoWaaS
|
||||||
|
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS
|
||||||
|
VERSION: 02.32.04
|
||||||
|
PATH: /mokowaas.xml
|
||||||
|
BRIEF: Component manifest for MokoWaaS admin dashboard and REST API
|
||||||
|
-->
|
||||||
|
<extension type="component" method="upgrade">
|
||||||
|
<name>MokoWaaS</name>
|
||||||
|
<author>Moko Consulting</author>
|
||||||
|
<creationDate>2026-06-02</creationDate>
|
||||||
|
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
|
||||||
|
<license>GPL-3.0-or-later</license>
|
||||||
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||||
|
<version>02.34.00</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>
|
||||||
|
|
||||||
|
<administration>
|
||||||
|
<menu img="class:cogs">MokoWaaS</menu>
|
||||||
|
<files folder="admin">
|
||||||
|
<folder>language</folder>
|
||||||
|
<folder>services</folder>
|
||||||
|
<folder>src</folder>
|
||||||
|
<folder>tmpl</folder>
|
||||||
|
</files>
|
||||||
|
</administration>
|
||||||
|
|
||||||
|
<api>
|
||||||
|
<files folder="api">
|
||||||
|
<folder>src</folder>
|
||||||
|
</files>
|
||||||
|
</api>
|
||||||
|
|
||||||
|
<media destination="com_mokowaas" folder="media">
|
||||||
|
<folder>css</folder>
|
||||||
|
<folder>js</folder>
|
||||||
|
</media>
|
||||||
|
</extension>
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,72 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
*
|
||||||
|
* SPDX-LICENSE-IDENTIFIER: GPL-3.0-or-later
|
||||||
|
*
|
||||||
|
* FILE INFORMATION
|
||||||
|
* DEFGROUP: Joomla.Plugin
|
||||||
|
* INGROUP: MokoWaaS
|
||||||
|
* VERSION: 02.34.00
|
||||||
|
* PATH: /src/Field/AllowedIpsField.php
|
||||||
|
* BRIEF: Custom form field that displays the current IP whitelist
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Moko\Plugin\System\MokoWaaS\Field;
|
||||||
|
|
||||||
|
defined('_JEXEC') or die;
|
||||||
|
|
||||||
|
use Joomla\CMS\Factory;
|
||||||
|
use Joomla\CMS\Form\FormField;
|
||||||
|
|
||||||
|
class AllowedIpsField extends FormField
|
||||||
|
{
|
||||||
|
protected $type = 'AllowedIps';
|
||||||
|
|
||||||
|
protected function getInput()
|
||||||
|
{
|
||||||
|
$config = Factory::getApplication()->getConfig();
|
||||||
|
$allowedRaw = $config->get('mokowaas_allowed_ips', '');
|
||||||
|
$currentIp = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
|
||||||
|
|
||||||
|
if (empty($allowedRaw))
|
||||||
|
{
|
||||||
|
$status = '<span class="badge bg-danger">Not configured</span>';
|
||||||
|
$ipList = '<em>No IPs set — emergency access is blocked.</em>';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$ips = array_map('trim', explode(',', $allowedRaw));
|
||||||
|
$status = '<span class="badge bg-success">'
|
||||||
|
. count($ips) . ' IP(s) configured</span>';
|
||||||
|
$ipItems = [];
|
||||||
|
|
||||||
|
foreach ($ips as $ip)
|
||||||
|
{
|
||||||
|
$match = ($ip === $currentIp)
|
||||||
|
? ' <span class="badge bg-info">your IP</span>'
|
||||||
|
: '';
|
||||||
|
$ipItems[] = '<code>' . htmlspecialchars($ip)
|
||||||
|
. '</code>' . $match;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ipList = implode(', ', $ipItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
$yourIp = '<code>' . htmlspecialchars($currentIp) . '</code>';
|
||||||
|
|
||||||
|
return '<div class="alert alert-info mb-0">'
|
||||||
|
. '<strong>IP Whitelist:</strong> ' . $status . '<br>'
|
||||||
|
. '<strong>Allowed IPs:</strong> ' . $ipList . '<br>'
|
||||||
|
. '<strong>Your current IP:</strong> ' . $yourIp . '<br>'
|
||||||
|
. '<small class="text-muted">Set <code>public '
|
||||||
|
. '$mokowaas_allowed_ips = \'1.2.3.4,5.6.7.8\';</code>'
|
||||||
|
. ' in configuration.php to change.</small>'
|
||||||
|
. '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getLabel()
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
*
|
||||||
|
* SPDX-LICENSE-IDENTIFIER: GPL-3.0-or-later
|
||||||
|
*
|
||||||
|
* FILE INFORMATION
|
||||||
|
* DEFGROUP: Joomla.Plugin
|
||||||
|
* INGROUP: MokoWaaS
|
||||||
|
* VERSION: 02.34.00
|
||||||
|
* PATH: /src/Field/CurrentIpField.php
|
||||||
|
* BRIEF: Read-only field that displays the current user's IP address
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Moko\Plugin\System\MokoWaaS\Field;
|
||||||
|
|
||||||
|
defined('_JEXEC') or die;
|
||||||
|
|
||||||
|
use Joomla\CMS\Form\FormField;
|
||||||
|
|
||||||
|
class CurrentIpField extends FormField
|
||||||
|
{
|
||||||
|
protected $type = 'CurrentIp';
|
||||||
|
|
||||||
|
protected function getInput()
|
||||||
|
{
|
||||||
|
$currentIp = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
|
||||||
|
|
||||||
|
return '<div class="alert alert-info mb-0 py-2">'
|
||||||
|
. '<strong>Your current IP:</strong> '
|
||||||
|
. '<code>' . htmlspecialchars($currentIp) . '</code> '
|
||||||
|
. '<small class="text-muted">— add this to the table below to keep your session alive.</small>'
|
||||||
|
. '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getLabel()
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,237 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @package MokoWaaS
|
||||||
|
* @subpackage plg_system_mokowaas
|
||||||
|
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||||
|
* @license GNU General Public License version 3 or later; see LICENSE
|
||||||
|
*
|
||||||
|
* FILE INFORMATION
|
||||||
|
* DEFGROUP: Joomla.Plugin
|
||||||
|
* INGROUP: MokoWaaS
|
||||||
|
* VERSION: 02.34.00
|
||||||
|
* PATH: /src/Field/DemoTaskInfoField.php
|
||||||
|
* BRIEF: Read-only field showing scheduled task info with link to manage it
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Moko\Plugin\System\MokoWaaS\Field;
|
||||||
|
|
||||||
|
defined('_JEXEC') or die;
|
||||||
|
|
||||||
|
use Joomla\CMS\Factory;
|
||||||
|
use Joomla\CMS\Form\FormField;
|
||||||
|
use Joomla\CMS\Router\Route;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays the demo reset scheduled task status: schedule, next run,
|
||||||
|
* last run, and a direct link to edit the task in Joomla's Scheduler.
|
||||||
|
*
|
||||||
|
* @since 02.29.00
|
||||||
|
*/
|
||||||
|
class DemoTaskInfoField extends FormField
|
||||||
|
{
|
||||||
|
protected $type = 'DemoTaskInfo';
|
||||||
|
|
||||||
|
protected function getInput()
|
||||||
|
{
|
||||||
|
// Query the scheduled task — if it exists and is enabled, demo mode is on
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$db = Factory::getDbo();
|
||||||
|
$query = $db->getQuery(true)
|
||||||
|
->select('*')
|
||||||
|
->from($db->quoteName('#__scheduler_tasks'))
|
||||||
|
->where($db->quoteName('type') . ' = ' . $db->quote('mokowaas.demo.reset'));
|
||||||
|
|
||||||
|
$db->setQuery($query);
|
||||||
|
$task = $db->loadAssoc();
|
||||||
|
}
|
||||||
|
catch (\Throwable $e)
|
||||||
|
{
|
||||||
|
$task = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$newTaskLink = Route::_('index.php?option=com_scheduler&task=task.add');
|
||||||
|
|
||||||
|
if (!$task)
|
||||||
|
{
|
||||||
|
return '<div class="alert alert-info mb-0 py-2">'
|
||||||
|
. 'No demo reset task configured. '
|
||||||
|
. '<a href="' . $newTaskLink . '" class="alert-link">Create a Scheduled Task</a> '
|
||||||
|
. 'and select <strong>MokoWaaS Demo Reset</strong> to enable demo mode.</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$taskId = (int) $task['id'];
|
||||||
|
$state = (int) $task['state'];
|
||||||
|
$siteTimezone = Factory::getApplication()->get('offset', 'UTC');
|
||||||
|
|
||||||
|
// Parse schedule from execution_rules
|
||||||
|
$rules = json_decode($task['execution_rules'] ?? '{}', true);
|
||||||
|
$ruleType = $rules['rule-type'] ?? '';
|
||||||
|
|
||||||
|
switch ($ruleType)
|
||||||
|
{
|
||||||
|
case 'cron-expression':
|
||||||
|
$schedule = $rules['cron-expression'] ?? '';
|
||||||
|
$friendlySchedule = $this->friendlySchedule($schedule);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'interval-minutes':
|
||||||
|
$mins = (int) ($rules['interval-minutes'] ?? 0);
|
||||||
|
|
||||||
|
if ($mins >= 1440 && $mins % 1440 === 0)
|
||||||
|
{
|
||||||
|
$days = $mins / 1440;
|
||||||
|
$schedule = 'Every ' . $days . ' day' . ($days > 1 ? 's' : '');
|
||||||
|
}
|
||||||
|
elseif ($mins >= 60 && $mins % 60 === 0)
|
||||||
|
{
|
||||||
|
$hours = $mins / 60;
|
||||||
|
$schedule = 'Every ' . $hours . ' hour' . ($hours > 1 ? 's' : '');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$schedule = 'Every ' . $mins . ' minute' . ($mins !== 1 ? 's' : '');
|
||||||
|
}
|
||||||
|
|
||||||
|
$friendlySchedule = $schedule;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'interval-hours':
|
||||||
|
$hours = (int) ($rules['interval-hours'] ?? 0);
|
||||||
|
$schedule = 'Every ' . $hours . ' hour' . ($hours !== 1 ? 's' : '');
|
||||||
|
$friendlySchedule = $schedule;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'interval-days':
|
||||||
|
$days = (int) ($rules['interval-days'] ?? 0);
|
||||||
|
$schedule = 'Every ' . $days . ' day' . ($days !== 1 ? 's' : '');
|
||||||
|
$friendlySchedule = $schedule;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$schedule = $ruleType ?: 'Not set';
|
||||||
|
$friendlySchedule = 'Custom';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next execution
|
||||||
|
$nextExec = $task['next_execution'] ?? '';
|
||||||
|
$nextFormatted = 'Not scheduled';
|
||||||
|
$nextBadge = '';
|
||||||
|
|
||||||
|
if (!empty($nextExec) && $nextExec !== '0000-00-00 00:00:00')
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$dt = new \DateTime($nextExec, new \DateTimeZone('UTC'));
|
||||||
|
$dt->setTimezone(new \DateTimeZone($siteTimezone));
|
||||||
|
$nextFormatted = $dt->format('M j, Y g:i A T');
|
||||||
|
}
|
||||||
|
catch (\Throwable $e)
|
||||||
|
{
|
||||||
|
$nextFormatted = $nextExec;
|
||||||
|
}
|
||||||
|
|
||||||
|
$diff = strtotime($nextExec . ' UTC') - time();
|
||||||
|
|
||||||
|
if ($diff <= 0)
|
||||||
|
{
|
||||||
|
$nextBadge = '<span class="badge bg-warning text-dark">DUE</span>';
|
||||||
|
}
|
||||||
|
elseif ($diff < 3600)
|
||||||
|
{
|
||||||
|
$nextBadge = '<span class="badge bg-info">in ' . (int) ceil($diff / 60) . ' min</span>';
|
||||||
|
}
|
||||||
|
elseif ($diff < 86400)
|
||||||
|
{
|
||||||
|
$nextBadge = '<span class="badge bg-info">in ' . round($diff / 3600, 1) . 'h</span>';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$nextBadge = '<span class="badge bg-secondary">in ' . round($diff / 86400, 1) . 'd</span>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last execution
|
||||||
|
$lastExec = $task['last_execution'] ?? '';
|
||||||
|
$lastFormatted = 'Never';
|
||||||
|
|
||||||
|
if (!empty($lastExec) && $lastExec !== '0000-00-00 00:00:00')
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$dt = new \DateTime($lastExec, new \DateTimeZone('UTC'));
|
||||||
|
$dt->setTimezone(new \DateTimeZone($siteTimezone));
|
||||||
|
$lastFormatted = $dt->format('M j, Y g:i A T');
|
||||||
|
}
|
||||||
|
catch (\Throwable $e)
|
||||||
|
{
|
||||||
|
$lastFormatted = $lastExec;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// State badge
|
||||||
|
$stateBadge = $state === 1
|
||||||
|
? '<span class="badge bg-success">Enabled</span>'
|
||||||
|
: '<span class="badge bg-danger">Disabled</span>';
|
||||||
|
|
||||||
|
// Link to edit the task
|
||||||
|
$editLink = Route::_('index.php?option=com_scheduler&task=task.edit&id=' . $taskId);
|
||||||
|
|
||||||
|
// Task params — default to On when keys are missing (matches form defaults)
|
||||||
|
$taskParams = json_decode($task['params'] ?? '{}', true) ?: [];
|
||||||
|
$bannerOn = !isset($taskParams['banner_enabled']) || (int) $taskParams['banner_enabled'] === 1;
|
||||||
|
$mediaOn = !isset($taskParams['include_media']) || (int) $taskParams['include_media'] === 1;
|
||||||
|
$countdownOn = !isset($taskParams['show_countdown']) || (int) $taskParams['show_countdown'] === 1;
|
||||||
|
|
||||||
|
// Check if snapshot exists
|
||||||
|
$snapshotExists = is_dir(JPATH_ROOT . '/mokowaas-snapshots/default');
|
||||||
|
|
||||||
|
// Build info card
|
||||||
|
return '<div class="card card-body bg-light py-2 px-3 mb-0">'
|
||||||
|
. '<table class="table table-sm table-borderless mb-1" style="max-width:550px">'
|
||||||
|
. '<tr><td class="text-muted" style="width:130px">Status</td><td>' . $stateBadge . '</td></tr>'
|
||||||
|
. '<tr><td class="text-muted">Schedule</td><td>' . htmlspecialchars($friendlySchedule) . '</td></tr>'
|
||||||
|
. '<tr><td class="text-muted">Next Reset</td><td>' . htmlspecialchars($nextFormatted) . ' ' . $nextBadge . '</td></tr>'
|
||||||
|
. '<tr><td class="text-muted">Last Reset</td><td>' . htmlspecialchars($lastFormatted) . '</td></tr>'
|
||||||
|
. '<tr><td class="text-muted">Runs</td><td>' . (int) ($task['times_executed'] ?? 0) . ' executed, ' . (int) ($task['times_failed'] ?? 0) . ' failed</td></tr>'
|
||||||
|
. '<tr><td class="text-muted">Baseline</td><td>' . ($snapshotExists ? '<span class="badge bg-success">Saved</span>' : '<span class="badge bg-warning text-dark">Not taken yet</span>') . '</td></tr>'
|
||||||
|
. '<tr><td class="text-muted">Banner</td><td>' . ($bannerOn ? 'On' : 'Off') . ($countdownOn ? ' + countdown' : '') . '</td></tr>'
|
||||||
|
. '<tr><td class="text-muted">Images</td><td>' . ($mediaOn ? 'Included' : 'Excluded') . '</td></tr>'
|
||||||
|
. '</table>'
|
||||||
|
. '<a href="' . $editLink . '" class="btn btn-sm btn-outline-primary">'
|
||||||
|
. '<span class="icon-cog" aria-hidden="true"></span> Manage Scheduled Task</a>'
|
||||||
|
. '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getLabel()
|
||||||
|
{
|
||||||
|
return '<label class="form-label"><strong>Scheduled Reset</strong></label>';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a cron expression to a human-readable string.
|
||||||
|
*
|
||||||
|
* @param string $cron Cron expression
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function friendlySchedule(string $cron): string
|
||||||
|
{
|
||||||
|
$map = [
|
||||||
|
'* * * * *' => 'Every minute',
|
||||||
|
'*/5 * * * *' => 'Every 5 minutes',
|
||||||
|
'*/15 * * * *' => 'Every 15 minutes',
|
||||||
|
'*/30 * * * *' => 'Every 30 minutes',
|
||||||
|
'0 */1 * * *' => 'Every hour',
|
||||||
|
'0 */4 * * *' => 'Every 4 hours',
|
||||||
|
'0 */6 * * *' => 'Every 6 hours',
|
||||||
|
'0 */12 * * *' => 'Every 12 hours',
|
||||||
|
'0 0 * * *' => 'Daily at midnight',
|
||||||
|
'0 6 * * *' => 'Daily at 6:00 AM',
|
||||||
|
'0 0 * * 0' => 'Weekly (Sunday)',
|
||||||
|
'0 0 1 * *' => 'Monthly (1st)',
|
||||||
|
];
|
||||||
|
|
||||||
|
return $map[$cron] ?? 'Custom';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @package MokoWaaS
|
||||||
|
* @subpackage plg_system_mokowaas
|
||||||
|
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||||
|
* @license GNU General Public License version 3 or later; see LICENSE
|
||||||
|
*
|
||||||
|
* FILE INFORMATION
|
||||||
|
* DEFGROUP: Joomla.Plugin
|
||||||
|
* INGROUP: MokoWaaS
|
||||||
|
* VERSION: 02.34.00
|
||||||
|
* PATH: /src/Field/NextResetField.php
|
||||||
|
* BRIEF: Read-only field showing next reset time from Joomla scheduled task
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Moko\Plugin\System\MokoWaaS\Field;
|
||||||
|
|
||||||
|
defined('_JEXEC') or die;
|
||||||
|
|
||||||
|
use Joomla\CMS\Factory;
|
||||||
|
use Joomla\CMS\Form\FormField;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pulls the next execution time directly from the Joomla scheduled task
|
||||||
|
* (#__scheduler_tasks) and displays it formatted in the site timezone.
|
||||||
|
*
|
||||||
|
* @since 02.29.00
|
||||||
|
*/
|
||||||
|
class NextResetField extends FormField
|
||||||
|
{
|
||||||
|
protected $type = 'NextReset';
|
||||||
|
|
||||||
|
protected function getInput()
|
||||||
|
{
|
||||||
|
// Check if demo mode is enabled
|
||||||
|
$demoEnabled = false;
|
||||||
|
|
||||||
|
if ($this->form)
|
||||||
|
{
|
||||||
|
$demoEnabled = (int) $this->form->getValue('demo_mode_enabled', 'params', 0) === 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$demoEnabled)
|
||||||
|
{
|
||||||
|
return '<span class="form-control-plaintext text-muted">Demo mode is off</span>'
|
||||||
|
. '<input type="hidden" name="' . $this->name . '" value="" />';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query the actual next_execution from the scheduled task
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$db = Factory::getDbo();
|
||||||
|
$query = $db->getQuery(true)
|
||||||
|
->select([
|
||||||
|
$db->quoteName('next_execution'),
|
||||||
|
$db->quoteName('last_execution'),
|
||||||
|
$db->quoteName('state'),
|
||||||
|
])
|
||||||
|
->from($db->quoteName('#__scheduler_tasks'))
|
||||||
|
->where($db->quoteName('type') . ' = ' . $db->quote('mokowaas.demo.reset'));
|
||||||
|
|
||||||
|
$db->setQuery($query);
|
||||||
|
$task = $db->loadAssoc();
|
||||||
|
}
|
||||||
|
catch (\Throwable $e)
|
||||||
|
{
|
||||||
|
$task = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$task)
|
||||||
|
{
|
||||||
|
return '<div class="alert alert-secondary mb-0 py-2">No scheduled task found — save to create one automatically.</div>'
|
||||||
|
. '<input type="hidden" name="' . $this->name . '" value="" />';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((int) $task['state'] !== 1)
|
||||||
|
{
|
||||||
|
return '<div class="alert alert-warning mb-0 py-2">Scheduled task is disabled.</div>'
|
||||||
|
. '<input type="hidden" name="' . $this->name . '" value="" />';
|
||||||
|
}
|
||||||
|
|
||||||
|
$nextExec = $task['next_execution'];
|
||||||
|
$lastExec = $task['last_execution'];
|
||||||
|
|
||||||
|
if (empty($nextExec) || $nextExec === '0000-00-00 00:00:00')
|
||||||
|
{
|
||||||
|
return '<div class="alert alert-secondary mb-0 py-2">Waiting for first run...</div>'
|
||||||
|
. '<input type="hidden" name="' . $this->name . '" value="" />';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to site timezone
|
||||||
|
$utcTimestamp = strtotime($nextExec);
|
||||||
|
$siteTimezone = Factory::getApplication()->get('offset', 'UTC');
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$dt = new \DateTime('@' . $utcTimestamp);
|
||||||
|
$dt->setTimezone(new \DateTimeZone($siteTimezone));
|
||||||
|
$formatted = $dt->format('l, F j, Y \a\t g:i A T');
|
||||||
|
}
|
||||||
|
catch (\Throwable $e)
|
||||||
|
{
|
||||||
|
$formatted = $nextExec . ' UTC';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Relative time
|
||||||
|
$diff = $utcTimestamp - time();
|
||||||
|
$relative = '';
|
||||||
|
|
||||||
|
if ($diff <= 0)
|
||||||
|
{
|
||||||
|
$relative = '<span class="badge bg-warning text-dark">overdue</span>';
|
||||||
|
}
|
||||||
|
elseif ($diff < 3600)
|
||||||
|
{
|
||||||
|
$mins = (int) ceil($diff / 60);
|
||||||
|
$relative = '<span class="badge bg-info">in ' . $mins . ' min</span>';
|
||||||
|
}
|
||||||
|
elseif ($diff < 86400)
|
||||||
|
{
|
||||||
|
$hours = round($diff / 3600, 1);
|
||||||
|
$relative = '<span class="badge bg-info">in ' . $hours . 'h</span>';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$days = round($diff / 86400, 1);
|
||||||
|
$relative = '<span class="badge bg-secondary">in ' . $days . 'd</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last run info
|
||||||
|
$lastInfo = '';
|
||||||
|
|
||||||
|
if (!empty($lastExec) && $lastExec !== '0000-00-00 00:00:00')
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$lastDt = new \DateTime($lastExec);
|
||||||
|
$lastDt->setTimezone(new \DateTimeZone($siteTimezone));
|
||||||
|
$lastInfo = '<small class="text-muted ms-2">Last run: ' . $lastDt->format('M j, g:i A') . '</small>';
|
||||||
|
}
|
||||||
|
catch (\Throwable $e)
|
||||||
|
{
|
||||||
|
// skip
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return '<div class="d-flex align-items-center gap-2 flex-wrap">'
|
||||||
|
. '<span class="form-control-plaintext" style="font-weight:500">'
|
||||||
|
. '<span class="icon-calendar" aria-hidden="true"></span> '
|
||||||
|
. htmlspecialchars($formatted) . '</span> '
|
||||||
|
. $relative
|
||||||
|
. $lastInfo
|
||||||
|
. '<input type="hidden" name="' . $this->name . '" value="' . htmlspecialchars($nextExec) . '" />'
|
||||||
|
. '</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @package MokoWaaS
|
||||||
|
* @subpackage plg_system_mokowaas
|
||||||
|
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||||
|
* @license GNU General Public License version 3 or later; see LICENSE
|
||||||
|
*
|
||||||
|
* FILE INFORMATION
|
||||||
|
* DEFGROUP: Joomla.Plugin
|
||||||
|
* INGROUP: MokoWaaS
|
||||||
|
* VERSION: 02.34.00
|
||||||
|
* PATH: /src/Field/SnapshotTablesField.php
|
||||||
|
* BRIEF: Multi-select list field that loads DB tables with sensible defaults
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Moko\Plugin\System\MokoWaaS\Field;
|
||||||
|
|
||||||
|
defined('_JEXEC') or die;
|
||||||
|
|
||||||
|
use Joomla\CMS\Factory;
|
||||||
|
use Joomla\CMS\Form\FormField;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders a multi-select list box of all Joomla database tables, with
|
||||||
|
* content-related tables pre-selected by default.
|
||||||
|
*
|
||||||
|
* @since 02.26.00
|
||||||
|
*/
|
||||||
|
class SnapshotTablesField extends FormField
|
||||||
|
{
|
||||||
|
protected $type = 'SnapshotTables';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tables selected by default when no value is stored yet.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
* @since 02.25.00
|
||||||
|
*/
|
||||||
|
private const DEFAULT_TABLES = [
|
||||||
|
'#__content',
|
||||||
|
'#__categories',
|
||||||
|
'#__fields',
|
||||||
|
'#__fields_values',
|
||||||
|
'#__fields_groups',
|
||||||
|
'#__menu',
|
||||||
|
'#__menu_types',
|
||||||
|
'#__modules',
|
||||||
|
'#__modules_menu',
|
||||||
|
'#__users',
|
||||||
|
'#__user_usergroup_map',
|
||||||
|
'#__user_profiles',
|
||||||
|
'#__tags',
|
||||||
|
'#__contentitem_tag_map',
|
||||||
|
'#__assets',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table suffixes grouped by category.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
* @since 02.25.00
|
||||||
|
*/
|
||||||
|
private const TABLE_GROUPS = [
|
||||||
|
'Content' => ['content', 'categories', 'fields', 'fields_values', 'fields_groups', 'tags', 'contentitem_tag_map', 'ucm_content', 'ucm_history'],
|
||||||
|
'Users' => ['users', 'user_usergroup_map', 'user_profiles', 'usergroups', 'user_keys', 'user_mfa'],
|
||||||
|
'Menus' => ['menu', 'menu_types'],
|
||||||
|
'Modules' => ['modules', 'modules_menu'],
|
||||||
|
'Assets' => ['assets'],
|
||||||
|
];
|
||||||
|
|
||||||
|
protected function getInput()
|
||||||
|
{
|
||||||
|
$db = Factory::getDbo();
|
||||||
|
$prefix = $db->getPrefix();
|
||||||
|
$tables = $db->getTableList();
|
||||||
|
|
||||||
|
// Resolve selected values
|
||||||
|
$selected = $this->value;
|
||||||
|
|
||||||
|
if ($selected === null || $selected === '')
|
||||||
|
{
|
||||||
|
$selected = self::DEFAULT_TABLES;
|
||||||
|
}
|
||||||
|
elseif (is_string($selected))
|
||||||
|
{
|
||||||
|
$selected = array_filter(array_map('trim', explode("\n", $selected)));
|
||||||
|
}
|
||||||
|
|
||||||
|
$selected = (array) $selected;
|
||||||
|
|
||||||
|
// Flatten nested arrays from broken save format [["#__content"],["#__categories"]]
|
||||||
|
$selected = array_map(function ($v) {
|
||||||
|
return is_array($v) ? reset($v) : $v;
|
||||||
|
}, $selected);
|
||||||
|
|
||||||
|
// Group tables
|
||||||
|
$grouped = [];
|
||||||
|
|
||||||
|
foreach ($tables as $table)
|
||||||
|
{
|
||||||
|
if (strpos($table, $prefix) !== 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$suffix = substr($table, strlen($prefix));
|
||||||
|
$logical = '#__' . $suffix;
|
||||||
|
$group = 'Other';
|
||||||
|
|
||||||
|
foreach (self::TABLE_GROUPS as $groupName => $patterns)
|
||||||
|
{
|
||||||
|
if (in_array($suffix, $patterns, true))
|
||||||
|
{
|
||||||
|
$group = $groupName;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$grouped[$group][] = $logical;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build HTML select with optgroups
|
||||||
|
$size = (int) ($this->element['size'] ?? 15);
|
||||||
|
$html = '<select name="' . $this->name . '" id="' . $this->id . '"'
|
||||||
|
. ' multiple="multiple" size="' . $size . '"'
|
||||||
|
. ' class="form-select">';
|
||||||
|
|
||||||
|
$priority = ['Content', 'Users', 'Menus', 'Modules', 'Assets'];
|
||||||
|
|
||||||
|
foreach ($priority as $g)
|
||||||
|
{
|
||||||
|
if (!empty($grouped[$g]))
|
||||||
|
{
|
||||||
|
$html .= '<optgroup label="' . $g . '">';
|
||||||
|
|
||||||
|
foreach ($grouped[$g] as $t)
|
||||||
|
{
|
||||||
|
$sel = in_array($t, $selected, true) ? ' selected="selected"' : '';
|
||||||
|
$html .= '<option value="' . htmlspecialchars($t) . '"' . $sel . '>' . htmlspecialchars($t) . '</option>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$html .= '</optgroup>';
|
||||||
|
unset($grouped[$g]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($grouped['Other']))
|
||||||
|
{
|
||||||
|
$html .= '<optgroup label="Other">';
|
||||||
|
|
||||||
|
foreach ($grouped['Other'] as $t)
|
||||||
|
{
|
||||||
|
$sel = in_array($t, $selected, true) ? ' selected="selected"' : '';
|
||||||
|
$html .= '<option value="' . htmlspecialchars($t) . '"' . $sel . '>' . htmlspecialchars($t) . '</option>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$html .= '</optgroup>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$html .= '</select>';
|
||||||
|
|
||||||
|
// "Reset to defaults" link
|
||||||
|
$defaultsJson = htmlspecialchars(json_encode(self::DEFAULT_TABLES), ENT_QUOTES, 'UTF-8');
|
||||||
|
$html .= '<div class="mt-1">'
|
||||||
|
. '<a href="#" class="small" onclick="'
|
||||||
|
. 'var sel=document.getElementById(\'' . $this->id . '\');'
|
||||||
|
. 'var defs=' . $defaultsJson . ';'
|
||||||
|
. 'Array.from(sel.options).forEach(function(o){o.selected=defs.indexOf(o.value)!==-1;});'
|
||||||
|
. 'return false;'
|
||||||
|
. '"><span class="icon-refresh" aria-hidden="true"></span> Reset to defaults</a>'
|
||||||
|
. '</div>';
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,260 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
|
||||||
|
This file is part of a Moko Consulting project.
|
||||||
|
|
||||||
|
SPDX-LICENSE-IDENTIFIER: GPL-3.0-or-later
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License (./LICENSE.md).
|
||||||
|
|
||||||
|
# FILE INFORMATION
|
||||||
|
DEFGROUP: Joomla.Plugin
|
||||||
|
INGROUP: MokoWaaS
|
||||||
|
REPO: https://github.com/mokoconsulting-tech/mokowaas
|
||||||
|
VERSION: 02.32.04
|
||||||
|
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>
|
||||||
|
<element>mokowaas</element>
|
||||||
|
<author>Moko Consulting</author>
|
||||||
|
<creationDate>2026-05-22</creationDate>
|
||||||
|
<copyright>Copyright (C) 2025 Moko Consulting. All rights reserved.</copyright>
|
||||||
|
<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.34.00</version>
|
||||||
|
<description>This plugin rebrands the Joomla system interface with MokoWaaS identity. It applies language overrides and ensures consistent branding across the platform.</description>
|
||||||
|
<namespace path=".">Moko\Plugin\System\MokoWaaS</namespace>
|
||||||
|
<scriptfile>script.php</scriptfile>
|
||||||
|
|
||||||
|
<files>
|
||||||
|
<filename plugin="mokowaas">script.php</filename>
|
||||||
|
<folder>Extension</folder>
|
||||||
|
<folder>Field</folder>
|
||||||
|
<folder>Helper</folder>
|
||||||
|
<folder>Service</folder>
|
||||||
|
<folder>forms</folder>
|
||||||
|
<folder>payload</folder>
|
||||||
|
<folder>services</folder>
|
||||||
|
<folder>language</folder>
|
||||||
|
<folder>administrator</folder>
|
||||||
|
</files>
|
||||||
|
|
||||||
|
<media destination="plg_system_mokowaas" folder="media">
|
||||||
|
<filename>index.html</filename>
|
||||||
|
<filename>favicon.ico</filename>
|
||||||
|
<filename>favicon.svg</filename>
|
||||||
|
<filename>favicon_256.png</filename>
|
||||||
|
<filename>logo.png</filename>
|
||||||
|
</media>
|
||||||
|
|
||||||
|
<languages folder="language">
|
||||||
|
<language tag="en-GB">en-GB/plg_system_mokowaas.ini</language>
|
||||||
|
<language tag="en-US">en-US/plg_system_mokowaas.ini</language>
|
||||||
|
</languages>
|
||||||
|
|
||||||
|
<languages folder="administrator/language">
|
||||||
|
<language tag="en-GB">en-GB/plg_system_mokowaas.sys.ini</language>
|
||||||
|
<language tag="en-US">en-US/plg_system_mokowaas.sys.ini</language>
|
||||||
|
</languages>
|
||||||
|
|
||||||
|
<administration>
|
||||||
|
<files folder="administrator">
|
||||||
|
<folder>language</folder>
|
||||||
|
</files>
|
||||||
|
</administration>
|
||||||
|
|
||||||
|
<config>
|
||||||
|
<fields name="params"
|
||||||
|
addfieldprefix="Moko\Plugin\System\MokoWaaS\Field"
|
||||||
|
>
|
||||||
|
<fieldset name="basic">
|
||||||
|
<field
|
||||||
|
name="health_api_token"
|
||||||
|
type="CopyableToken"
|
||||||
|
label="PLG_SYSTEM_MOKOWAAS_HEALTH_TOKEN_LABEL"
|
||||||
|
description="PLG_SYSTEM_MOKOWAAS_HEALTH_TOKEN_DESC"
|
||||||
|
default=""
|
||||||
|
filter="raw"
|
||||||
|
readonly="true"
|
||||||
|
/>
|
||||||
|
<field name="dev_mode" type="radio" default="0"
|
||||||
|
label="PLG_SYSTEM_MOKOWAAS_DEV_MODE_LABEL"
|
||||||
|
description="PLG_SYSTEM_MOKOWAAS_DEV_MODE_DESC"
|
||||||
|
class="btn-group btn-group-yesno">
|
||||||
|
<option value="1">JYES</option>
|
||||||
|
<option value="0">JNO</option>
|
||||||
|
</field>
|
||||||
|
<field
|
||||||
|
name="reset_hits"
|
||||||
|
type="radio"
|
||||||
|
label="PLG_SYSTEM_MOKOWAAS_RESET_HITS_LABEL"
|
||||||
|
description="PLG_SYSTEM_MOKOWAAS_RESET_HITS_DESC"
|
||||||
|
default="0"
|
||||||
|
class="btn-group btn-group-yesno"
|
||||||
|
>
|
||||||
|
<option value="1">JYES</option>
|
||||||
|
<option value="0">JNO</option>
|
||||||
|
</field>
|
||||||
|
<field
|
||||||
|
name="delete_versions"
|
||||||
|
type="radio"
|
||||||
|
label="PLG_SYSTEM_MOKOWAAS_DELETE_VERSIONS_LABEL"
|
||||||
|
description="PLG_SYSTEM_MOKOWAAS_DELETE_VERSIONS_DESC"
|
||||||
|
default="0"
|
||||||
|
class="btn-group btn-group-yesno"
|
||||||
|
>
|
||||||
|
<option value="1">JYES</option>
|
||||||
|
<option value="0">JNO</option>
|
||||||
|
</field>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset name="tenant_restrictions"
|
||||||
|
label="PLG_SYSTEM_MOKOWAAS_FIELDSET_TENANT_LABEL"
|
||||||
|
description="PLG_SYSTEM_MOKOWAAS_FIELDSET_TENANT_DESC"
|
||||||
|
>
|
||||||
|
<field name="restrict_installer" type="radio" default="1"
|
||||||
|
label="PLG_SYSTEM_MOKOWAAS_RESTRICT_INSTALLER_LABEL"
|
||||||
|
description="PLG_SYSTEM_MOKOWAAS_RESTRICT_INSTALLER_DESC"
|
||||||
|
class="btn-group btn-group-yesno">
|
||||||
|
<option value="1">JYES</option>
|
||||||
|
<option value="0">JNO</option>
|
||||||
|
</field>
|
||||||
|
<field name="allow_extension_updates" type="radio" default="1"
|
||||||
|
label="PLG_SYSTEM_MOKOWAAS_ALLOW_UPDATES_LABEL"
|
||||||
|
description="PLG_SYSTEM_MOKOWAAS_ALLOW_UPDATES_DESC"
|
||||||
|
class="btn-group btn-group-yesno"
|
||||||
|
showon="restrict_installer:1">
|
||||||
|
<option value="1">JYES</option>
|
||||||
|
<option value="0">JNO</option>
|
||||||
|
</field>
|
||||||
|
<field name="hide_sysinfo" type="radio" default="1"
|
||||||
|
label="PLG_SYSTEM_MOKOWAAS_HIDE_SYSINFO_LABEL"
|
||||||
|
description="PLG_SYSTEM_MOKOWAAS_HIDE_SYSINFO_DESC"
|
||||||
|
class="btn-group btn-group-yesno">
|
||||||
|
<option value="1">JYES</option>
|
||||||
|
<option value="0">JNO</option>
|
||||||
|
</field>
|
||||||
|
<field name="restrict_global_config" type="radio" default="1"
|
||||||
|
label="PLG_SYSTEM_MOKOWAAS_RESTRICT_CONFIG_LABEL"
|
||||||
|
description="PLG_SYSTEM_MOKOWAAS_RESTRICT_CONFIG_DESC"
|
||||||
|
class="btn-group btn-group-yesno">
|
||||||
|
<option value="1">JYES</option>
|
||||||
|
<option value="0">JNO</option>
|
||||||
|
</field>
|
||||||
|
<field name="restrict_template_editing" type="radio" default="1"
|
||||||
|
label="PLG_SYSTEM_MOKOWAAS_RESTRICT_TEMPLATE_LABEL"
|
||||||
|
description="PLG_SYSTEM_MOKOWAAS_RESTRICT_TEMPLATE_DESC"
|
||||||
|
class="btn-group btn-group-yesno">
|
||||||
|
<option value="1">JYES</option>
|
||||||
|
<option value="0">JNO</option>
|
||||||
|
</field>
|
||||||
|
<field name="disable_install_url" type="radio" default="1"
|
||||||
|
label="PLG_SYSTEM_MOKOWAAS_DISABLE_INSTALL_URL_LABEL"
|
||||||
|
description="PLG_SYSTEM_MOKOWAAS_DISABLE_INSTALL_URL_DESC"
|
||||||
|
class="btn-group btn-group-yesno">
|
||||||
|
<option value="1">JYES</option>
|
||||||
|
<option value="0">JNO</option>
|
||||||
|
</field>
|
||||||
|
<field name="hidden_menu_items" type="textarea"
|
||||||
|
label="PLG_SYSTEM_MOKOWAAS_HIDDEN_MENUS_LABEL"
|
||||||
|
description="PLG_SYSTEM_MOKOWAAS_HIDDEN_MENUS_DESC"
|
||||||
|
rows="5" filter="raw" />
|
||||||
|
</fieldset>
|
||||||
|
<fieldset name="demo_mode"
|
||||||
|
label="PLG_SYSTEM_MOKOWAAS_FIELDSET_DEMO_LABEL"
|
||||||
|
description="PLG_SYSTEM_MOKOWAAS_FIELDSET_DEMO_DESC"
|
||||||
|
addfieldprefix="Moko\Plugin\System\MokoWaaS\Field"
|
||||||
|
>
|
||||||
|
<field name="demo_scheduled_task" type="DemoTaskInfo"
|
||||||
|
label="PLG_SYSTEM_MOKOWAAS_DEMO_TASK_INFO_LABEL"
|
||||||
|
/>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset name="security"
|
||||||
|
label="PLG_SYSTEM_MOKOWAAS_FIELDSET_SECURITY_LABEL"
|
||||||
|
description="PLG_SYSTEM_MOKOWAAS_FIELDSET_SECURITY_DESC"
|
||||||
|
addfieldprefix="Moko\Plugin\System\MokoWaaS\Field"
|
||||||
|
>
|
||||||
|
<field
|
||||||
|
name="emergency_access"
|
||||||
|
type="radio"
|
||||||
|
label="PLG_SYSTEM_MOKOWAAS_EMERGENCY_ACCESS_LABEL"
|
||||||
|
description="PLG_SYSTEM_MOKOWAAS_EMERGENCY_ACCESS_DESC"
|
||||||
|
default="1"
|
||||||
|
class="btn-group btn-group-yesno"
|
||||||
|
>
|
||||||
|
<option value="1">JYES</option>
|
||||||
|
<option value="0">JNO</option>
|
||||||
|
</field>
|
||||||
|
<field
|
||||||
|
name="allowed_ips_display"
|
||||||
|
type="AllowedIps"
|
||||||
|
label=""
|
||||||
|
/>
|
||||||
|
<field name="force_https" type="radio" default="1"
|
||||||
|
label="PLG_SYSTEM_MOKOWAAS_FORCE_HTTPS_LABEL"
|
||||||
|
description="PLG_SYSTEM_MOKOWAAS_FORCE_HTTPS_DESC"
|
||||||
|
class="btn-group btn-group-yesno">
|
||||||
|
<option value="1">JYES</option>
|
||||||
|
<option value="0">JNO</option>
|
||||||
|
</field>
|
||||||
|
<field name="admin_session_timeout" type="number"
|
||||||
|
label="PLG_SYSTEM_MOKOWAAS_SESSION_TIMEOUT_LABEL"
|
||||||
|
description="PLG_SYSTEM_MOKOWAAS_SESSION_TIMEOUT_DESC"
|
||||||
|
default="60" hint="Minutes (0 = Joomla default)" />
|
||||||
|
<field
|
||||||
|
name="current_ip_display"
|
||||||
|
type="CurrentIp"
|
||||||
|
label=""
|
||||||
|
/>
|
||||||
|
<field
|
||||||
|
name="trusted_ips"
|
||||||
|
type="subform"
|
||||||
|
label="PLG_SYSTEM_MOKOWAAS_TRUSTED_IPS_LABEL"
|
||||||
|
description="PLG_SYSTEM_MOKOWAAS_TRUSTED_IPS_DESC"
|
||||||
|
formsource="plugins/system/mokowaas/forms/trusted_ip_entry.xml"
|
||||||
|
multiple="true"
|
||||||
|
layout="joomla.form.field.subform.repeatable-table"
|
||||||
|
groupByFieldset="false"
|
||||||
|
buttons="add,remove,move"
|
||||||
|
/>
|
||||||
|
<field name="password_min_length" type="number" default="12"
|
||||||
|
label="PLG_SYSTEM_MOKOWAAS_PASSWORD_LENGTH_LABEL"
|
||||||
|
description="PLG_SYSTEM_MOKOWAAS_PASSWORD_LENGTH_DESC" />
|
||||||
|
<field name="password_require_uppercase" type="radio" default="1"
|
||||||
|
label="PLG_SYSTEM_MOKOWAAS_PASSWORD_UPPER_LABEL"
|
||||||
|
class="btn-group btn-group-yesno">
|
||||||
|
<option value="1">JYES</option>
|
||||||
|
<option value="0">JNO</option>
|
||||||
|
</field>
|
||||||
|
<field name="password_require_number" type="radio" default="1"
|
||||||
|
label="PLG_SYSTEM_MOKOWAAS_PASSWORD_NUMBER_LABEL"
|
||||||
|
class="btn-group btn-group-yesno">
|
||||||
|
<option value="1">JYES</option>
|
||||||
|
<option value="0">JNO</option>
|
||||||
|
</field>
|
||||||
|
<field name="password_require_special" type="radio" default="1"
|
||||||
|
label="PLG_SYSTEM_MOKOWAAS_PASSWORD_SPECIAL_LABEL"
|
||||||
|
class="btn-group btn-group-yesno">
|
||||||
|
<option value="1">JYES</option>
|
||||||
|
<option value="0">JNO</option>
|
||||||
|
</field>
|
||||||
|
<field name="upload_allowed_types" type="text"
|
||||||
|
label="PLG_SYSTEM_MOKOWAAS_UPLOAD_TYPES_LABEL"
|
||||||
|
description="PLG_SYSTEM_MOKOWAAS_UPLOAD_TYPES_DESC"
|
||||||
|
default="jpg,jpeg,png,gif,webp,svg,pdf,doc,docx,xls,xlsx" />
|
||||||
|
<field name="upload_max_size_mb" type="number"
|
||||||
|
label="PLG_SYSTEM_MOKOWAAS_UPLOAD_SIZE_LABEL"
|
||||||
|
description="PLG_SYSTEM_MOKOWAAS_UPLOAD_SIZE_DESC"
|
||||||
|
default="100" />
|
||||||
|
</fieldset>
|
||||||
|
</fields>
|
||||||
|
</config>
|
||||||
|
</extension>
|
||||||
Reference in New Issue
Block a user