diff --git a/.gitignore b/.gitignore index 2a61bd13..53539f77 100644 --- a/.gitignore +++ b/.gitignore @@ -121,7 +121,7 @@ releases/ build/ dist/ out/ -site/ +/site/ !source/packages/*/site/ *.map *.css.map diff --git a/.mokogitea/CLAUDE.md b/.mokogitea/CLAUDE.md index abba657b..50591f52 100644 --- a/.mokogitea/CLAUDE.md +++ b/.mokogitea/CLAUDE.md @@ -1,4 +1,4 @@ -# MokoWaaS +# MokoSuite Joomla 5/6 admin tools suite — heartbeat health monitoring, extension management, security firewall, tenant restrictions, and site administration. @@ -6,10 +6,10 @@ Joomla 5/6 admin tools suite — heartbeat health monitoring, extension manageme | Field | Value | |---|---| -| **Package** | `pkg_mokowaas` | +| **Package** | `pkg_mokosuite` | | **Language** | PHP 8.1+ | | **Branch** | develop on `dev`, merge to `main` (protected) | -| **Wiki** | [MokoWaaS Wiki](https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/wiki) | +| **Wiki** | [MokoSuite Wiki](https://git.mokoconsulting.tech/MokoConsulting/MokoSuite/wiki) | ## Commands @@ -19,38 +19,38 @@ composer install # Install PHP dependencies ## Architecture -Joomla **package** (`pkg_mokowaas`) with 17 sub-extensions: +Joomla **package** (`pkg_mokosuite`) with 17 sub-extensions: -### Core Plugin (`plg_system_mokowaas`) -- Heartbeat health endpoint (`/?mokowaas=health`) with 16 diagnostic checks +### Core Plugin (`plg_system_mokosuite`) +- Heartbeat health endpoint (`/?mokosuite=health`) with 16 diagnostic checks - Grafana provisioning and heartbeat sender - Site alias / domain management - Extension cascade (enable/disable coordination) - Download key preservation across Joomla updates -- Namespace: `Moko\Plugin\System\MokoWaaS` +- Namespace: `Moko\Plugin\System\MokoSuite` ### Feature Plugins -- `plg_system_mokowaas_firewall` — WAF, IP blocklist, security headers, password policy -- `plg_system_mokowaas_tenant` — admin restrictions for non-master users -- `plg_system_mokowaas_devtools` — dev mode, hit reset, version cleanup, download key reset -- `plg_system_mokowaas_offline` — offline mode bypass for legal pages -- `plg_system_mokowaas_monitor` — Grafana heartbeat registration +- `plg_system_mokosuite_firewall` — WAF, IP blocklist, security headers, password policy +- `plg_system_mokosuite_tenant` — admin restrictions for non-master users +- `plg_system_mokosuite_devtools` — dev mode, hit reset, version cleanup, download key reset +- `plg_system_mokosuite_offline` — offline mode bypass for legal pages +- `plg_system_mokosuite_monitor` — Grafana heartbeat registration -### Component (`com_mokowaas`) +### Component (`com_mokosuite`) - Admin dashboard with plugin management, WAF charts, extension catalog - Helpdesk ticketing system - REST API controllers ### Modules -- `mod_mokowaas_cpanel` — admin dashboard widget -- `mod_mokowaas_menu` — admin sidebar menu -- `mod_mokowaas_cache` — status bar cache/temp cleaner -- `mod_mokowaas_categories` — auto-category tree menu +- `mod_mokosuite_cpanel` — admin dashboard widget +- `mod_mokosuite_menu` — admin sidebar menu +- `mod_mokosuite_cache` — status bar cache/temp cleaner +- `mod_mokosuite_categories` — auto-category tree menu ### Task Plugins -- `plg_task_mokowaasdemo` — scheduled demo site reset -- `plg_task_mokowaassync` — scheduled content sync -- `plg_task_mokowaas_tickets` — ticket automation +- `plg_task_mokosuitedemo` — scheduled demo site reset +- `plg_task_mokosuitesync` — scheduled content sync +- `plg_task_mokosuite_tickets` — ticket automation ### Update Server @@ -59,7 +59,7 @@ MokoGitea generates update feeds dynamically from releases — no static `update ## Source Directory Source lives in `source/` (not `src/`): -- `source/pkg_mokowaas.xml` — package manifest +- `source/pkg_mokosuite.xml` — package manifest - `source/script.php` — install script - `source/packages/` — all sub-extensions diff --git a/.mokogitea/ISSUE_TEMPLATE/waas_site_issue.md b/.mokogitea/ISSUE_TEMPLATE/suite_site_issue.md similarity index 92% rename from .mokogitea/ISSUE_TEMPLATE/waas_site_issue.md rename to .mokogitea/ISSUE_TEMPLATE/suite_site_issue.md index 4a0c89b8..42b4cd47 100644 --- a/.mokogitea/ISSUE_TEMPLATE/waas_site_issue.md +++ b/.mokogitea/ISSUE_TEMPLATE/suite_site_issue.md @@ -1,6 +1,6 @@ --- -name: WaaS Client Site Issue -about: Report an issue with a WaaS client site (branding, deployment, media sync) +name: Suite Client Site Issue +about: Report an issue with a Suite client site (branding, deployment, media sync) title: '[WAAS] ' labels: 'waas, client-site' assignees: '' @@ -52,7 +52,7 @@ Attach screenshots showing the issue (desktop and mobile if relevant). ## Template Details - **Joomla Version**: [e.g., 5.x] - **Template Name**: [e.g., clienttemplate] -- **MokoWaaS Plugin**: [Active / Inactive] +- **MokoSuite Plugin**: [Active / Inactive] - **MokoOnyx Admin**: [Active / Inactive] ## CSS Custom Properties diff --git a/.mokogitea/copilot-instructions.md b/.mokogitea/copilot-instructions.md index a46f9a2a..7c90aee7 100644 --- a/.mokogitea/copilot-instructions.md +++ b/.mokogitea/copilot-instructions.md @@ -11,9 +11,9 @@ INGROUP: MokoStandards.Templates REPO: https://github.com/mokoconsulting-tech/MokoStandards PATH: /templates/github/copilot-instructions.joomla.md.template VERSION: XX.YY.ZZ -BRIEF: GitHub Copilot custom instructions template for Joomla/MokoWaaS governed repositories -NOTE: Synced to .github/copilot-instructions.md in all Joomla/WaaS repos via bulk sync. - Tokens replaced at sync time: MokoWaaS, https://github.com/mokoconsulting-tech/MokoWaaS, {{EXTENSION_NAME}}, +BRIEF: GitHub Copilot custom instructions template for Joomla/MokoSuite governed repositories +NOTE: Synced to .github/copilot-instructions.md in all Joomla/Suite repos via bulk sync. + Tokens replaced at sync time: MokoSuite, https://github.com/mokoconsulting-tech/MokoSuite, {{EXTENSION_NAME}}, {{EXTENSION_TYPE}}, {{EXTENSION_ELEMENT}} --> @@ -36,24 +36,24 @@ NOTE: Synced to .github/copilot-instructions.md in all Joomla/WaaS repos via bul > > | Placeholder | Where to find the value | > |---|---| -> | `MokoWaaS` | The GitHub repository name (visible in the URL, `README.md` heading, or `git remote -v`) | -> | `https://github.com/mokoconsulting-tech/MokoWaaS` | Full GitHub URL, e.g. `https://github.com/mokoconsulting-tech/` | +> | `MokoSuite` | The GitHub repository name (visible in the URL, `README.md` heading, or `git remote -v`) | +> | `https://github.com/mokoconsulting-tech/MokoSuite` | Full GitHub URL, e.g. `https://github.com/mokoconsulting-tech/` | > | `{{EXTENSION_NAME}}` | The `` element in `manifest.xml` at the repository root | > | `{{EXTENSION_TYPE}}` | The `type` attribute of the `` tag in `manifest.xml` (`component`, `module`, `plugin`, or `template`) | > | `{{EXTENSION_ELEMENT}}` | The `` tag in `manifest.xml`, or the filename prefix (e.g. `com_myextension`, `mod_mymodule`) | > > --- -# MokoWaaS — GitHub Copilot Custom Instructions +# MokoSuite — GitHub Copilot Custom Instructions ## What This Repo Is -This is a **Moko Consulting MokoWaaS** (Joomla) repository governed by [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards). All coding standards, workflows, and policies are defined there and enforced here via bulk sync. +This is a **Moko Consulting MokoSuite** (Joomla) repository governed by [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards). All coding standards, workflows, and policies are defined there and enforced here via bulk sync. -Repository URL: https://github.com/mokoconsulting-tech/MokoWaaS +Repository URL: https://github.com/mokoconsulting-tech/MokoSuite Extension name: **{{EXTENSION_NAME}}** Extension type: **{{EXTENSION_TYPE}}** (`{{EXTENSION_ELEMENT}}`) -Platform: **Joomla 4.x / MokoWaaS** +Platform: **Joomla 4.x / MokoSuite** --- @@ -77,9 +77,9 @@ Every new file needs a copyright header as its first content. * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION - * DEFGROUP: MokoWaaS.{{EXTENSION_TYPE}} - * INGROUP: MokoWaaS - * REPO: https://github.com/mokoconsulting-tech/MokoWaaS + * DEFGROUP: MokoSuite.{{EXTENSION_TYPE}} + * INGROUP: MokoSuite + * REPO: https://github.com/mokoconsulting-tech/MokoSuite * PATH: /path/to/file.php * VERSION: XX.YY.ZZ * BRIEF: One-line description of purpose @@ -98,9 +98,9 @@ This file is part of a Moko Consulting project. SPDX-License-Identifier: GPL-3.0-or-later # FILE INFORMATION -DEFGROUP: MokoWaaS.Documentation -INGROUP: MokoWaaS -REPO: https://github.com/mokoconsulting-tech/MokoWaaS +DEFGROUP: MokoSuite.Documentation +INGROUP: MokoSuite +REPO: https://github.com/mokoconsulting-tech/MokoSuite PATH: /docs/file.md VERSION: XX.YY.ZZ BRIEF: One-line description @@ -138,7 +138,7 @@ The version in `README.md` **must always match** the `` tag in `manifes 01.02.04 - https://github.com/mokoconsulting-tech/MokoWaaS/releases/download/01.02.04/{{EXTENSION_ELEMENT}}-01.02.04.zip + https://github.com/mokoconsulting-tech/MokoSuite/releases/download/01.02.04/{{EXTENSION_ELEMENT}}-01.02.04.zip @@ -152,7 +152,7 @@ The version in `README.md` **must always match** the `` tag in `manifes ## Joomla Extension Structure ``` -MokoWaaS/ +MokoSuite/ ├── manifest.xml # Joomla installer manifest (root — required) ├── (no updates.xml) # Update XML is generated dynamically by MokoGitea ├── site/ # Frontend (site) code @@ -191,11 +191,11 @@ MokoWaaS/ https://git.mokoconsulting.tech/{Owner}/{Repo}/updates.xml ``` -The package manifest (`pkg_mokowaas.xml`) references it via: +The package manifest (`pkg_mokosuite.xml`) references it via: ```xml - - https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/updates.xml + + https://git.mokoconsulting.tech/MokoConsulting/MokoSuite/updates.xml ``` @@ -257,7 +257,7 @@ This repository is governed by [MokoStandards](https://github.com/mokoconsulting | [branching-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/branching-strategy.md) | Branch naming, hierarchy, and release workflow | | [merge-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR title/body conventions | | [changelog-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md | -| [joomla-development-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/guide/waas/joomla-development-guide.md) | MokoWaaS Joomla extension development guide | +| [joomla-development-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/guide/waas/joomla-development-guide.md) | MokoSuite Joomla extension development guide | --- diff --git a/.mokogitea/manifest.xml b/.mokogitea/manifest.xml index 5ea26278..e0b86bb1 100644 --- a/.mokogitea/manifest.xml +++ b/.mokogitea/manifest.xml @@ -5,15 +5,11 @@ --> - MokoWaaS - Package - MokoWaaS + MokoSuite + Package - MokoSuite MokoConsulting - White-label identity, security hardening, and tenant restriction layer for WaaS-managed Joomla environments -<<<<<<< HEAD - 02.34.00 -======= - 02.34.16 ->>>>>>> origin/dev + White-label identity, security hardening, and tenant restriction layer for Suite-managed Joomla environments + 02.34.47 GNU General Public License v3 diff --git a/.mokogitea/workflows/auto-release.yml b/.mokogitea/workflows/auto-release.yml index 8fa46848..b657b980 100644 --- a/.mokogitea/workflows/auto-release.yml +++ b/.mokogitea/workflows/auto-release.yml @@ -17,7 +17,7 @@ # | Reads manifest.xml (joomla|dolibarr|generic) to branch logic. | # | | # | Platform-specific: | -# | joomla: XML manifest, updates.xml, type-prefixed packages | +# | joomla: XML manifest, type-prefixed packages | # | dolibarr: mod*.class.php, update.txt, dev version reset | # | generic: README-only, no update stream | # | | @@ -71,16 +71,21 @@ jobs: MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting run: | - if ! command -v composer &> /dev/null; then - sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1 + if [ -f /opt/moko-platform/cli/version_bump.php ] && [ -f /opt/moko-platform/vendor/autoload.php ]; then + echo Using pre-installed /opt/moko-platform + echo MOKO_CLI=/opt/moko-platform/cli >> $GITHUB_ENV + else + echo Falling back to fresh clone + if ! command -v composer > /dev/null 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 - rm -rf /tmp/moko-platform-api - git clone --depth 1 --branch main --quiet \ - "https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \ - /tmp/moko-platform-api - cd /tmp/moko-platform-api - composer install --no-dev --no-interaction --quiet - echo "MOKO_CLI=/tmp/moko-platform-api/cli" >> "$GITHUB_ENV" - name: Rename branch to rc run: | @@ -102,14 +107,13 @@ jobs: run: | php ${MOKO_CLI}/release_publish.php \ --path . --stability rc --bump minor --branch rc \ - --token "${{ secrets.MOKOGITEA_TOKEN }}" \ - --skip-update-stream + --token "${{ secrets.MOKOGITEA_TOKEN }}" - name: Summary if: always() run: | echo "## Promoted to Release Candidate" >> $GITHUB_STEP_SUMMARY - echo "Branch renamed to rc, minor bump, RC release built (updates.xml managed by Gitea Pages)" >> $GITHUB_STEP_SUMMARY + echo "Branch renamed to rc, minor bump, RC release built" >> $GITHUB_STEP_SUMMARY # ── Merged PR → Build & Release (or promote RC to stable) ──────────────────── release: @@ -136,7 +140,7 @@ jobs: run: | CONFLICTS=$(grep -rn '<<<<<<< \|>>>>>>> \|^=======$' --include='*.php' --include='*.xml' --include='*.css' --include='*.js' --include='*.json' --include='*.md' --include='*.yml' --include='*.yaml' --include='*.ini' --include='*.txt' . 2>/dev/null | grep -v '.git/' || true) if [ -n "$CONFLICTS" ]; then - echo "::error::Merge conflict markers found - aborting release" + echo "::error::Merge conflict markers found — aborting release" echo "## Release Blocked: Conflict Markers" >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY echo "$CONFLICTS" >> $GITHUB_STEP_SUMMARY @@ -151,23 +155,27 @@ jobs: MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_MIRROR_TOKEN }}"}}' run: | - if ! command -v composer &> /dev/null; then - sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1 + if [ -f /opt/moko-platform/cli/version_bump.php ] && [ -f /opt/moko-platform/vendor/autoload.php ]; then + echo Using pre-installed /opt/moko-platform + echo MOKO_CLI=/opt/moko-platform/cli >> $GITHUB_ENV + else + echo Falling back to fresh clone + if ! command -v composer > /dev/null 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 - rm -rf /tmp/moko-platform-api - git clone --depth 1 --branch main --quiet \ - "https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \ - /tmp/moko-platform-api - cd /tmp/moko-platform-api - composer install --no-dev --no-interaction --quiet - echo "MOKO_CLI=/tmp/moko-platform-api/cli" >> "$GITHUB_ENV" - name: "Publish stable release" run: | php ${MOKO_CLI}/release_publish.php \ --path . --stability stable --bump minor --branch main \ - --token "${{ secrets.MOKOGITEA_TOKEN }}" \ - --skip-update-stream + --token "${{ secrets.MOKOGITEA_TOKEN }}" - name: Update release notes from CHANGELOG.md run: | @@ -244,7 +252,7 @@ jobs: API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" TOKEN="${{ secrets.MOKOGITEA_TOKEN }}" - # Delete rc branch (ephemeral - created by promote-rc) + # Delete rc branch (ephemeral — created by promote-rc) curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \ "${API_BASE}/branches/rc" 2>/dev/null \ && echo "Deleted rc branch" || echo "rc branch not found" @@ -301,7 +309,7 @@ jobs: echo "## Release Skipped" >> $GITHUB_STEP_SUMMARY echo "No VERSION in README.md" >> $GITHUB_STEP_SUMMARY elif [ "${{ steps.check.outputs.already_released }}" = "true" ]; then - echo "## Already Released - ${VERSION}" >> $GITHUB_STEP_SUMMARY + echo "## Already Released — ${VERSION}" >> $GITHUB_STEP_SUMMARY else echo "" >> $GITHUB_STEP_SUMMARY echo "## Build & Release Complete (${PLATFORM})" >> $GITHUB_STEP_SUMMARY diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml index 8e534df0..9125ca15 100644 --- a/.mokogitea/workflows/issue-branch.yml +++ b/.mokogitea/workflows/issue-branch.yml @@ -5,11 +5,7 @@ # FILE INFORMATION # DEFGROUP: Gitea.Workflow # INGROUP: moko-platform.Automation -<<<<<<< HEAD -# VERSION: 02.34.00 -======= -# VERSION: 02.34.16 ->>>>>>> origin/dev +# VERSION: 02.34.47 # BRIEF: Auto-create feature branch when an issue is opened name: "Universal: Issue Branch" diff --git a/.mokogitea/workflows/pr-check.yml b/.mokogitea/workflows/pr-check.yml index aa31cc72..8a952da4 100644 --- a/.mokogitea/workflows/pr-check.yml +++ b/.mokogitea/workflows/pr-check.yml @@ -1,532 +1,532 @@ -# Copyright (C) 2026 Moko Consulting -# -# SPDX-License-Identifier: GPL-3.0-or-later -# -# FILE INFORMATION -# DEFGROUP: Gitea.Workflow -# INGROUP: moko-platform.CI -# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform -# PATH: /templates/workflows/universal/pr-check.yml.template -# VERSION: 09.23.00 -# BRIEF: PR gate — branch policy + code validation before merge - -name: "Universal: PR Check" - -on: - pull_request: - types: [opened, synchronize, reopened, edited] - -permissions: - contents: read - pull-requests: write - -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - -jobs: - # ── Branch Policy ────────────────────────────────────────────────────── - branch-policy: - name: Branch Policy - runs-on: ubuntu-latest - steps: - - name: Check branch merge target - run: | - HEAD="${{ github.head_ref }}" - BASE="${{ github.base_ref }}" - - echo "PR: ${HEAD} → ${BASE}" - - ALLOWED=true - REASON="" - - case "$HEAD" in - feature/*|feat/*) - if [ "$BASE" != "dev" ]; then - ALLOWED=false - REASON="Feature branches must target 'dev', not '${BASE}'" - fi - ;; - fix/*|bugfix/*) - if [ "$BASE" != "dev" ]; then - ALLOWED=false - REASON="Fix branches must target 'dev', not '${BASE}'" - fi - ;; - patch/*) - if [ "$BASE" != "dev" ] && [ "$BASE" != "rc" ]; then - ALLOWED=false - REASON="Patch branches must target 'dev' or 'rc', not '${BASE}'" - fi - ;; - hotfix/*) - if [ "$BASE" != "dev" ] && [ "$BASE" != "main" ]; then - ALLOWED=false - REASON="Hotfix branches can only target 'dev' or 'main', not '${BASE}'" - fi - ;; - rc) - if [ "$BASE" != "main" ]; then - ALLOWED=false - REASON="RC branch can only merge into 'main', not '${BASE}'" - fi - ;; - dev) - if [ "$BASE" != "main" ]; then - ALLOWED=false - REASON="Dev branch can only merge into 'main', not '${BASE}'" - fi - ;; - esac - - if [ "$ALLOWED" = false ]; then - echo "::error::${REASON}" - echo "## Branch Policy Violation" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "${REASON}" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Allowed merge paths:" >> $GITHUB_STEP_SUMMARY - echo "- \`feature/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY - echo "- \`fix/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY - echo "- \`hotfix/*\` → \`dev\` or \`main\`" >> $GITHUB_STEP_SUMMARY - echo "- \`dev\` → \`main\`" >> $GITHUB_STEP_SUMMARY - echo "- \`rc/*\` → \`main\`" >> $GITHUB_STEP_SUMMARY - exit 1 - fi - - echo "Branch policy: OK (${HEAD} → ${BASE})" - echo "## Branch Policy: Passed" >> $GITHUB_STEP_SUMMARY - - # ── Code Validation ──────────────────────────────────────────────────── - validate: - name: Validate PR - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Check for merge conflict markers - run: | - CONFLICTS=$(grep -rn '<<<<<<< \|>>>>>>> \|^=======$' --include='*.php' --include='*.xml' --include='*.css' --include='*.js' --include='*.json' --include='*.md' --include='*.yml' --include='*.yaml' --include='*.ini' --include='*.txt' . 2>/dev/null | grep -v '.git/' || true) - if [ -n "$CONFLICTS" ]; then - echo "::error::Merge conflict markers found in source files" - echo "## Conflict Markers Found" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - echo "$CONFLICTS" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - exit 1 - fi - echo "No conflict markers found" - - - name: Detect platform - id: platform - run: | - # Read platform from XML manifest ( tag) or plain text fallback - PLATFORM=$(sed -n 's/.*\([^<]*\)<\/platform>.*/\1/p' .mokogitea/manifest.xml 2>/dev/null | head -1) - [ -z "$PLATFORM" ] && PLATFORM=$(cat .mokogitea/manifest.xml 2>/dev/null | tr -d '[:space:]') - [ -z "$PLATFORM" ] && PLATFORM="generic" - echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT" - - - name: Setup PHP - if: steps.platform.outputs.platform == 'joomla' || steps.platform.outputs.platform == 'dolibarr' - 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: PHP syntax check - if: steps.platform.outputs.platform == 'joomla' || steps.platform.outputs.platform == 'dolibarr' - run: | - ERRORS=0 - while IFS= read -r -d '' file; do - if ! php -l "$file" 2>&1 | grep -q "No syntax errors"; then - ERRORS=$((ERRORS + 1)) - fi - done < <(find . -name "*.php" -not -path "./.git/*" -not -path "./vendor/*" -print0) - echo "PHP lint: ${ERRORS} error(s)" - [ "$ERRORS" -eq 0 ] || { echo "::error::PHP syntax errors found"; exit 1; } - - - name: Joomla JEXEC guard check - if: steps.platform.outputs.platform == 'joomla' - run: | - ERRORS=0 - while IFS= read -r -d '' file; do - # Skip vendor, node_modules, and index.html stub files - case "$file" in ./vendor/*|./node_modules/*) continue ;; esac - # Check first 10 lines for JEXEC or JPATH guard - if ! head -20 "$file" | grep -qE "defined\s*\(\s*['\"](_JEXEC|JPATH_BASE|\\\\JPATH_PLATFORM)['\"]"; then - echo "::error file=${file}::Missing JEXEC guard: ${file}" - ERRORS=$((ERRORS + 1)) - fi - done < <(find . -name "*.php" -path "*/src/*" -not -path "./.git/*" -not -path "./vendor/*" -print0) - if [ "$ERRORS" -gt 0 ]; then - echo "::error::${ERRORS} PHP file(s) missing defined('_JEXEC') or die guard" - echo "## JEXEC Guard Check: Failed" >> $GITHUB_STEP_SUMMARY - echo "${ERRORS} file(s) in src/ are missing the Joomla execution guard." >> $GITHUB_STEP_SUMMARY - exit 1 - fi - echo "JEXEC guard: OK" - - - name: Joomla directory listing protection - if: steps.platform.outputs.platform == 'joomla' - run: | - MISSING=0 - SOURCE_DIR="src" - [ ! -d "$SOURCE_DIR" ] && exit 0 - while IFS= read -r dir; do - if [ ! -f "${dir}/index.html" ]; then - echo "::warning::Missing index.html in ${dir} (directory listing protection)" - MISSING=$((MISSING + 1)) - fi - done < <(find "$SOURCE_DIR" -type d -not -path "./.git/*" -not -path "*/vendor/*" -not -path "*/node_modules/*") - if [ "$MISSING" -gt 0 ]; then - echo "## Directory Protection" >> $GITHUB_STEP_SUMMARY - echo "${MISSING} director(ies) missing index.html" >> $GITHUB_STEP_SUMMARY - fi - echo "Directory protection: ${MISSING} missing (advisory)" - - - name: Joomla script file and asset checks - if: steps.platform.outputs.platform == 'joomla' - run: | - ERRORS=0 - MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '/dev/null | head -1) - [ -z "$MANIFEST" ] && exit 0 - MANIFEST_DIR=$(dirname "$MANIFEST") - - # Check scriptfile exists if declared - SCRIPTFILE=$(sed -n 's/.*\([^<]*\)<\/scriptfile>.*/\1/p' "$MANIFEST" 2>/dev/null) - if [ -n "$SCRIPTFILE" ]; then - if [ ! -f "${MANIFEST_DIR}/${SCRIPTFILE}" ]; then - echo "::error::Manifest declares ${SCRIPTFILE} but file not found at ${MANIFEST_DIR}/${SCRIPTFILE}" - ERRORS=$((ERRORS + 1)) - else - echo "Script file: ${MANIFEST_DIR}/${SCRIPTFILE} (OK)" - fi - fi - - # Require joomla.asset.json and validate it - ASSET_JSON=$(find "$MANIFEST_DIR" -name "joomla.asset.json" -not -path "./.git/*" 2>/dev/null | head -1) - if [ -z "$ASSET_JSON" ]; then - echo "::error::joomla.asset.json not found — Joomla asset system is required" - ERRORS=$((ERRORS + 1)) - else - if command -v php &> /dev/null; then - php -r "json_decode(file_get_contents('$ASSET_JSON')); if(json_last_error()!==JSON_ERROR_NONE){echo json_last_error_msg();exit(1);}" 2>&1 || { - echo "::error::joomla.asset.json is not valid JSON" - ERRORS=$((ERRORS + 1)) - } - fi - echo "joomla.asset.json: valid" - fi - - # Validate all XML files in src/ are well-formed - XML_ERRORS=0 - if command -v php &> /dev/null; then - while IFS= read -r -d '' xmlfile; do - if ! php -r "libxml_use_internal_errors(true); \$x = simplexml_load_file('$xmlfile'); if(!\$x){foreach(libxml_get_errors() as \$e) echo trim(\$e->message) . ' in $xmlfile'; exit(1);}" 2>&1; then - XML_ERRORS=$((XML_ERRORS + 1)) - fi - done < <(find "$MANIFEST_DIR" -name "*.xml" -not -path "./.git/*" -print0) - fi - if [ "$XML_ERRORS" -gt 0 ]; then - echo "::error::${XML_ERRORS} XML file(s) are malformed" - ERRORS=$((ERRORS + 1)) - else - echo "XML well-formedness: OK" - fi - - [ "$ERRORS" -gt 0 ] && exit 1 - echo "Joomla asset checks: OK" - - - name: Validate platform manifest - run: | - PLATFORM="${{ steps.platform.outputs.platform }}" - case "$PLATFORM" in - joomla) - MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '/dev/null | head -1) - if [ -z "$MANIFEST" ]; then - echo "::warning::No Joomla manifest found (WaaS site)" - exit 0 - fi - echo "Manifest: ${MANIFEST}" - if command -v php &> /dev/null; then - php -r "libxml_use_internal_errors(true); \$x = simplexml_load_file('$MANIFEST'); if(!\$x){foreach(libxml_get_errors() as \$e) echo \$e->message; exit(1);}" || { echo "::error::Manifest XML is malformed"; exit 1; } - fi - for ELEMENT in name version description; do - grep -q "<${ELEMENT}>" "$MANIFEST" || { echo "::error::Missing <${ELEMENT}> in manifest"; exit 1; } - done - # Block legacy raw/branch update server URLs on MokoGitea - RAW_URLS=$(grep -n 'raw/branch' "$MANIFEST" | grep -i 'mokoconsulting\|mokogitea\|git\.mokoconsulting\.tech' || true) - if [ -n "$RAW_URLS" ]; then - echo "::error::Manifest contains legacy raw/branch update server URL on MokoGitea. Use the Gitea Pages URL instead (e.g. /{REPO}/updates.xml not /{REPO}/raw/branch/main/updates.xml)" - echo "$RAW_URLS" - exit 1 - fi - echo "Joomla manifest valid" - ;; - dolibarr) - MOD_FILE=$(find . -maxdepth 4 -name "mod*.class.php" ! -path "./.git/*" -exec grep -l 'extends DolibarrModules' {} \; 2>/dev/null | head -1) - if [ -z "$MOD_FILE" ]; then - echo "::error::No mod*.class.php found" - exit 1 - fi - echo "Dolibarr module: ${MOD_FILE}" - ;; - *) - echo "Generic platform — no manifest validation" - ;; - esac - - - name: Check update stream format - run: | - PLATFORM="${{ steps.platform.outputs.platform }}" - case "$PLATFORM" in - joomla) - if [ -f "updates.xml" ]; then - if command -v php &> /dev/null; then - php -r "libxml_use_internal_errors(true); \$x = simplexml_load_file('updates.xml'); if(!\$x){foreach(libxml_get_errors() as \$e) echo \$e->message; exit(1);}" || { echo "::error::updates.xml is malformed"; exit 1; } - fi - echo "updates.xml valid" - fi - ;; - dolibarr) - [ -f "update.txt" ] && echo "update.txt present" || echo "::warning::No update.txt" - ;; - esac - - - name: Check changelog has unreleased entries (PRs to main) - if: github.base_ref == 'main' - run: | - if [ ! -f "CHANGELOG.md" ]; then - echo "::error::CHANGELOG.md not found — required for releases" - exit 1 - fi - - # Extract content between [Unreleased] and next ## heading - ENTRIES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found && /^- /{count++} END{print count+0}' CHANGELOG.md) - - if [ "$ENTRIES" -eq 0 ]; then - echo "::error::CHANGELOG.md has no entries under [Unreleased]. Add changelog entries before releasing." - echo "## Changelog Check: Failed" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "No entries found under \`[Unreleased]\` in CHANGELOG.md." >> $GITHUB_STEP_SUMMARY - echo "Add entries describing what changed before merging to main." >> $GITHUB_STEP_SUMMARY - exit 1 - fi - - echo "Changelog: ${ENTRIES} unreleased entries found" - echo "## Changelog Check: Passed" >> $GITHUB_STEP_SUMMARY - echo "${ENTRIES} entries under [Unreleased]" >> $GITHUB_STEP_SUMMARY - - - name: Validate Joomla language files - if: steps.platform.outputs.platform == 'joomla' - run: | - ERRORS=0 - WARNINGS=0 - - # Require both en-GB and en-US language directories - LANG_ROOT=$(find . -path "*/language" -type d -not -path "./.git/*" 2>/dev/null | head -1) - if [ -z "$LANG_ROOT" ]; then - echo "No language/ directory found — skipping" - exit 0 - fi - - if [ ! -d "$LANG_ROOT/en-GB" ]; then - echo "::error::Missing en-GB language directory (${LANG_ROOT}/en-GB)" - ERRORS=$((ERRORS + 1)) - fi - if [ ! -d "$LANG_ROOT/en-US" ]; then - echo "::error::Missing en-US language directory (${LANG_ROOT}/en-US)" - ERRORS=$((ERRORS + 1)) - fi - - # Check that en-GB and en-US have matching .ini files - if [ -d "$LANG_ROOT/en-GB" ] && [ -d "$LANG_ROOT/en-US" ]; then - for GB_INI in "$LANG_ROOT/en-GB"/*.ini; do - [ ! -f "$GB_INI" ] && continue - US_INI="$LANG_ROOT/en-US/$(basename "$GB_INI")" - if [ ! -f "$US_INI" ]; then - echo "::error::$(basename "$GB_INI") exists in en-GB but missing from en-US" - ERRORS=$((ERRORS + 1)) - fi - done - for US_INI in "$LANG_ROOT/en-US"/*.ini; do - [ ! -f "$US_INI" ] && continue - GB_INI="$LANG_ROOT/en-GB/$(basename "$US_INI")" - if [ ! -f "$GB_INI" ]; then - echo "::error::$(basename "$US_INI") exists in en-US but missing from en-GB" - ERRORS=$((ERRORS + 1)) - fi - done - fi - - # Find all .ini language files - INI_FILES=$(find . -path "*/language/*/*.ini" -not -path "./.git/*" 2>/dev/null) - if [ -z "$INI_FILES" ]; then - echo "No .ini language files found" - [ "$ERRORS" -gt 0 ] && exit 1 - exit 0 - fi - - echo "Found $(echo "$INI_FILES" | wc -l) language file(s)" - - for FILE in $INI_FILES; do - FNAME=$(basename "$FILE") - LINENUM=0 - SEEN_KEYS="" - - while IFS= read -r line || [ -n "$line" ]; do - LINENUM=$((LINENUM + 1)) - - # Skip empty lines and comments - [ -z "$line" ] && continue - echo "$line" | grep -qE '^\s*;' && continue - echo "$line" | grep -qE '^\s*$' && continue - - # Must match KEY="VALUE" format - if ! echo "$line" | grep -qE '^[A-Z_][A-Z0-9_]*=".*"$'; then - echo "::error file=${FILE},line=${LINENUM}::Malformed line: ${line}" - ERRORS=$((ERRORS + 1)) - continue - fi - - # Extract key and check for duplicates - KEY=$(echo "$line" | sed 's/=.*//') - if echo "$SEEN_KEYS" | grep -qx "$KEY"; then - echo "::error file=${FILE},line=${LINENUM}::Duplicate key: ${KEY}" - ERRORS=$((ERRORS + 1)) - fi - SEEN_KEYS="${SEEN_KEYS} - ${KEY}" - done < "$FILE" - - echo " ${FILE}: checked ${LINENUM} lines" - done - - # Cross-check en-GB vs en-US key consistency - GB_DIR=$(find . -path "*/language/en-GB" -type d -not -path "./.git/*" 2>/dev/null | head -1) - US_DIR=$(find . -path "*/language/en-US" -type d -not -path "./.git/*" 2>/dev/null | head -1) - - if [ -n "$GB_DIR" ] && [ -n "$US_DIR" ]; then - for GB_FILE in "$GB_DIR"/*.ini; do - [ ! -f "$GB_FILE" ] && continue - FNAME=$(basename "$GB_FILE") - US_FILE="$US_DIR/$FNAME" - [ ! -f "$US_FILE" ] && continue - - GB_KEYS=$(grep -oP '^[A-Z_][A-Z0-9_]*(?==)' "$GB_FILE" 2>/dev/null | sort) - US_KEYS=$(grep -oP '^[A-Z_][A-Z0-9_]*(?==)' "$US_FILE" 2>/dev/null | sort) - - # Keys in en-GB but not en-US - MISSING_US=$(comm -23 <(echo "$GB_KEYS") <(echo "$US_KEYS")) - if [ -n "$MISSING_US" ]; then - echo "::warning::Keys in en-GB/$FNAME but missing from en-US/$FNAME:" - echo "$MISSING_US" | while read -r k; do echo " - $k"; done - WARNINGS=$((WARNINGS + 1)) - fi - - # Keys in en-US but not en-GB - MISSING_GB=$(comm -13 <(echo "$GB_KEYS") <(echo "$US_KEYS")) - if [ -n "$MISSING_GB" ]; then - echo "::warning::Keys in en-US/$FNAME but missing from en-GB/$FNAME:" - echo "$MISSING_GB" | while read -r k; do echo " - $k"; done - WARNINGS=$((WARNINGS + 1)) - fi - done - fi - - { - echo "### Language File Validation" - echo "| Metric | Count |" - echo "|---|---|" - echo "| Files checked | $(echo "$INI_FILES" | wc -l) |" - echo "| Errors | ${ERRORS} |" - echo "| Warnings | ${WARNINGS} |" - } >> $GITHUB_STEP_SUMMARY - - if [ "$ERRORS" -gt 0 ]; then - echo "::error::Language validation failed with ${ERRORS} error(s)" - exit 1 - fi - echo "Language files: OK (${WARNINGS} warning(s))" - - - name: Check changelog has unreleased entry - run: | - if [ ! -f "CHANGELOG.md" ]; then - echo "::warning::No CHANGELOG.md found" - exit 0 - fi - # Check for content under [Unreleased] section - if ! grep -q "## \[Unreleased\]" CHANGELOG.md; then - echo "::error::CHANGELOG.md missing [Unreleased] section" - exit 1 - fi - # Check there's at least one entry (Added/Changed/Fixed/Removed) under Unreleased - UNRELEASED_CONTENT=$(sed -n '/## \[Unreleased\]/,/## \[/p' CHANGELOG.md | grep -cE '^\s*-\s' || true) - if [ "$UNRELEASED_CONTENT" -eq 0 ]; then - echo "::error::CHANGELOG.md [Unreleased] section has no entries. Add a changelog entry describing your changes." - echo "## Changelog Check: Failed" >> $GITHUB_STEP_SUMMARY - echo "The \`[Unreleased]\` section in CHANGELOG.md has no entries." >> $GITHUB_STEP_SUMMARY - echo "Add a line like \`- Description of your change\` under a heading (\`### Added\`, \`### Changed\`, \`### Fixed\`, etc.)" >> $GITHUB_STEP_SUMMARY - exit 1 - fi - echo "Changelog: ${UNRELEASED_CONTENT} entry/entries in [Unreleased]" - - - name: Verify package source - run: | - SOURCE_DIR="src" - [ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs" - if [ ! -d "$SOURCE_DIR" ]; then - echo "::warning::No src/ or htdocs/ directory" - exit 0 - fi - FILE_COUNT=$(find "$SOURCE_DIR" -type f | wc -l) - echo "Source: ${FILE_COUNT} files" - [ "$FILE_COUNT" -gt 0 ] || { echo "::error::Source directory is empty"; exit 1; } - - # ── Pre-Release RC Build ───────────────────────────────────────────────── - pre-release: - name: Build RC Package - runs-on: ubuntu-latest - needs: [branch-policy, validate] - - steps: - - name: Trigger RC pre-release - env: - GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} - REPO: ${{ github.repository }} - BRANCH: ${{ github.head_ref }} - GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} - run: | - curl -s -X POST "${GITEA_URL}/api/v1/repos/${REPO}/actions/workflows/pre-release.yml/dispatches" -H "Authorization: token ${GITEA_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 - - # ── Issue Reporter ────────────────────────────────────────────────────── - report-issues: - name: Report Issues - runs-on: ubuntu-latest - needs: [branch-policy, validate] - if: >- - always() && - needs.validate.result == 'failure' - - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - sparse-checkout: automation/ci-issue-reporter.sh - sparse-checkout-cone-mode: false - - - name: "File issue for PR validation failure" - env: - GITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} - GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} - run: | - chmod +x automation/ci-issue-reporter.sh - ./automation/ci-issue-reporter.sh \ - --gate "PR Validation" \ - --workflow "PR Check" \ - --severity error \ - --details "PR validation failed (syntax, manifest, changelog, or source checks). See the CI run for the specific check that failed." +# Copyright (C) 2026 Moko Consulting +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +# FILE INFORMATION +# DEFGROUP: Gitea.Workflow +# INGROUP: moko-platform.CI +# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform +# PATH: /templates/workflows/universal/pr-check.yml.template +# VERSION: 09.23.00 +# BRIEF: PR gate — branch policy + code validation before merge + +name: "Universal: PR Check" + +on: + pull_request: + types: [opened, synchronize, reopened, edited] + +permissions: + contents: read + pull-requests: write + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + +jobs: + # ── Branch Policy ────────────────────────────────────────────────────── + branch-policy: + name: Branch Policy + runs-on: ubuntu-latest + steps: + - name: Check branch merge target + run: | + HEAD="${{ github.head_ref }}" + BASE="${{ github.base_ref }}" + + echo "PR: ${HEAD} → ${BASE}" + + ALLOWED=true + REASON="" + + case "$HEAD" in + feature/*|feat/*) + if [ "$BASE" != "dev" ]; then + ALLOWED=false + REASON="Feature branches must target 'dev', not '${BASE}'" + fi + ;; + fix/*|bugfix/*) + if [ "$BASE" != "dev" ]; then + ALLOWED=false + REASON="Fix branches must target 'dev', not '${BASE}'" + fi + ;; + patch/*) + if [ "$BASE" != "dev" ] && [ "$BASE" != "rc" ]; then + ALLOWED=false + REASON="Patch branches must target 'dev' or 'rc', not '${BASE}'" + fi + ;; + hotfix/*) + if [ "$BASE" != "dev" ] && [ "$BASE" != "main" ]; then + ALLOWED=false + REASON="Hotfix branches can only target 'dev' or 'main', not '${BASE}'" + fi + ;; + rc) + if [ "$BASE" != "main" ]; then + ALLOWED=false + REASON="RC branch can only merge into 'main', not '${BASE}'" + fi + ;; + dev) + if [ "$BASE" != "main" ]; then + ALLOWED=false + REASON="Dev branch can only merge into 'main', not '${BASE}'" + fi + ;; + esac + + if [ "$ALLOWED" = false ]; then + echo "::error::${REASON}" + echo "## Branch Policy Violation" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "${REASON}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Allowed merge paths:" >> $GITHUB_STEP_SUMMARY + echo "- \`feature/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY + echo "- \`fix/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY + echo "- \`hotfix/*\` → \`dev\` or \`main\`" >> $GITHUB_STEP_SUMMARY + echo "- \`dev\` → \`main\`" >> $GITHUB_STEP_SUMMARY + echo "- \`rc/*\` → \`main\`" >> $GITHUB_STEP_SUMMARY + exit 1 + fi + + echo "Branch policy: OK (${HEAD} → ${BASE})" + echo "## Branch Policy: Passed" >> $GITHUB_STEP_SUMMARY + + # ── Code Validation ──────────────────────────────────────────────────── + validate: + name: Validate PR + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Check for merge conflict markers + run: | + CONFLICTS=$(grep -rn '<<<<<<< \|>>>>>>> \|^=======$' --include='*.php' --include='*.xml' --include='*.css' --include='*.js' --include='*.json' --include='*.md' --include='*.yml' --include='*.yaml' --include='*.ini' --include='*.txt' . 2>/dev/null | grep -v '.git/' || true) + if [ -n "$CONFLICTS" ]; then + echo "::error::Merge conflict markers found in source files" + echo "## Conflict Markers Found" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + echo "$CONFLICTS" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + exit 1 + fi + echo "No conflict markers found" + + - name: Detect platform + id: platform + run: | + # Read platform from XML manifest ( tag) or plain text fallback + PLATFORM=$(sed -n 's/.*\([^<]*\)<\/platform>.*/\1/p' .mokogitea/manifest.xml 2>/dev/null | head -1) + [ -z "$PLATFORM" ] && PLATFORM=$(cat .mokogitea/manifest.xml 2>/dev/null | tr -d '[:space:]') + [ -z "$PLATFORM" ] && PLATFORM="generic" + echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT" + + - name: Setup PHP + if: steps.platform.outputs.platform == 'joomla' || steps.platform.outputs.platform == 'dolibarr' + 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: PHP syntax check + if: steps.platform.outputs.platform == 'joomla' || steps.platform.outputs.platform == 'dolibarr' + run: | + ERRORS=0 + while IFS= read -r -d '' file; do + if ! php -l "$file" 2>&1 | grep -q "No syntax errors"; then + ERRORS=$((ERRORS + 1)) + fi + done < <(find . -name "*.php" -not -path "./.git/*" -not -path "./vendor/*" -print0) + echo "PHP lint: ${ERRORS} error(s)" + [ "$ERRORS" -eq 0 ] || { echo "::error::PHP syntax errors found"; exit 1; } + + - name: Joomla JEXEC guard check + if: steps.platform.outputs.platform == 'joomla' + run: | + ERRORS=0 + while IFS= read -r -d '' file; do + # Skip vendor, node_modules, and index.html stub files + case "$file" in ./vendor/*|./node_modules/*) continue ;; esac + # Check first 10 lines for JEXEC or JPATH guard + if ! head -20 "$file" | grep -qE "defined\s*\(\s*['\"](_JEXEC|JPATH_BASE|\\\\JPATH_PLATFORM)['\"]"; then + echo "::error file=${file}::Missing JEXEC guard: ${file}" + ERRORS=$((ERRORS + 1)) + fi + done < <(find . -name "*.php" -path "*/src/*" -not -path "./.git/*" -not -path "./vendor/*" -print0) + if [ "$ERRORS" -gt 0 ]; then + echo "::error::${ERRORS} PHP file(s) missing defined('_JEXEC') or die guard" + echo "## JEXEC Guard Check: Failed" >> $GITHUB_STEP_SUMMARY + echo "${ERRORS} file(s) in src/ are missing the Joomla execution guard." >> $GITHUB_STEP_SUMMARY + exit 1 + fi + echo "JEXEC guard: OK" + + - name: Joomla directory listing protection + if: steps.platform.outputs.platform == 'joomla' + run: | + MISSING=0 + SOURCE_DIR="src" + [ ! -d "$SOURCE_DIR" ] && exit 0 + while IFS= read -r dir; do + if [ ! -f "${dir}/index.html" ]; then + echo "::warning::Missing index.html in ${dir} (directory listing protection)" + MISSING=$((MISSING + 1)) + fi + done < <(find "$SOURCE_DIR" -type d -not -path "./.git/*" -not -path "*/vendor/*" -not -path "*/node_modules/*") + if [ "$MISSING" -gt 0 ]; then + echo "## Directory Protection" >> $GITHUB_STEP_SUMMARY + echo "${MISSING} director(ies) missing index.html" >> $GITHUB_STEP_SUMMARY + fi + echo "Directory protection: ${MISSING} missing (advisory)" + + - name: Joomla script file and asset checks + if: steps.platform.outputs.platform == 'joomla' + run: | + ERRORS=0 + MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '/dev/null | head -1) + [ -z "$MANIFEST" ] && exit 0 + MANIFEST_DIR=$(dirname "$MANIFEST") + + # Check scriptfile exists if declared + SCRIPTFILE=$(sed -n 's/.*\([^<]*\)<\/scriptfile>.*/\1/p' "$MANIFEST" 2>/dev/null) + if [ -n "$SCRIPTFILE" ]; then + if [ ! -f "${MANIFEST_DIR}/${SCRIPTFILE}" ]; then + echo "::error::Manifest declares ${SCRIPTFILE} but file not found at ${MANIFEST_DIR}/${SCRIPTFILE}" + ERRORS=$((ERRORS + 1)) + else + echo "Script file: ${MANIFEST_DIR}/${SCRIPTFILE} (OK)" + fi + fi + + # Require joomla.asset.json and validate it + ASSET_JSON=$(find "$MANIFEST_DIR" -name "joomla.asset.json" -not -path "./.git/*" 2>/dev/null | head -1) + if [ -z "$ASSET_JSON" ]; then + echo "::error::joomla.asset.json not found — Joomla asset system is required" + ERRORS=$((ERRORS + 1)) + else + if command -v php &> /dev/null; then + php -r "json_decode(file_get_contents('$ASSET_JSON')); if(json_last_error()!==JSON_ERROR_NONE){echo json_last_error_msg();exit(1);}" 2>&1 || { + echo "::error::joomla.asset.json is not valid JSON" + ERRORS=$((ERRORS + 1)) + } + fi + echo "joomla.asset.json: valid" + fi + + # Validate all XML files in src/ are well-formed + XML_ERRORS=0 + if command -v php &> /dev/null; then + while IFS= read -r -d '' xmlfile; do + if ! php -r "libxml_use_internal_errors(true); \$x = simplexml_load_file('$xmlfile'); if(!\$x){foreach(libxml_get_errors() as \$e) echo trim(\$e->message) . ' in $xmlfile'; exit(1);}" 2>&1; then + XML_ERRORS=$((XML_ERRORS + 1)) + fi + done < <(find "$MANIFEST_DIR" -name "*.xml" -not -path "./.git/*" -print0) + fi + if [ "$XML_ERRORS" -gt 0 ]; then + echo "::error::${XML_ERRORS} XML file(s) are malformed" + ERRORS=$((ERRORS + 1)) + else + echo "XML well-formedness: OK" + fi + + [ "$ERRORS" -gt 0 ] && exit 1 + echo "Joomla asset checks: OK" + + - name: Validate platform manifest + run: | + PLATFORM="${{ steps.platform.outputs.platform }}" + case "$PLATFORM" in + joomla) + MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '/dev/null | head -1) + if [ -z "$MANIFEST" ]; then + echo "::warning::No Joomla manifest found (Suite site)" + exit 0 + fi + echo "Manifest: ${MANIFEST}" + if command -v php &> /dev/null; then + php -r "libxml_use_internal_errors(true); \$x = simplexml_load_file('$MANIFEST'); if(!\$x){foreach(libxml_get_errors() as \$e) echo \$e->message; exit(1);}" || { echo "::error::Manifest XML is malformed"; exit 1; } + fi + for ELEMENT in name version description; do + grep -q "<${ELEMENT}>" "$MANIFEST" || { echo "::error::Missing <${ELEMENT}> in manifest"; exit 1; } + done + # Block legacy raw/branch update server URLs on MokoGitea + RAW_URLS=$(grep -n 'raw/branch' "$MANIFEST" | grep -i 'mokoconsulting\|mokogitea\|git\.mokoconsulting\.tech' || true) + if [ -n "$RAW_URLS" ]; then + echo "::error::Manifest contains legacy raw/branch update server URL on MokoGitea. Use the Gitea Pages URL instead (e.g. /{REPO}/updates.xml not /{REPO}/raw/branch/main/updates.xml)" + echo "$RAW_URLS" + exit 1 + fi + echo "Joomla manifest valid" + ;; + dolibarr) + MOD_FILE=$(find . -maxdepth 4 -name "mod*.class.php" ! -path "./.git/*" -exec grep -l 'extends DolibarrModules' {} \; 2>/dev/null | head -1) + if [ -z "$MOD_FILE" ]; then + echo "::error::No mod*.class.php found" + exit 1 + fi + echo "Dolibarr module: ${MOD_FILE}" + ;; + *) + echo "Generic platform — no manifest validation" + ;; + esac + + - name: Check update stream format + run: | + PLATFORM="${{ steps.platform.outputs.platform }}" + case "$PLATFORM" in + joomla) + if [ -f "updates.xml" ]; then + if command -v php &> /dev/null; then + php -r "libxml_use_internal_errors(true); \$x = simplexml_load_file('updates.xml'); if(!\$x){foreach(libxml_get_errors() as \$e) echo \$e->message; exit(1);}" || { echo "::error::updates.xml is malformed"; exit 1; } + fi + echo "updates.xml valid" + fi + ;; + dolibarr) + [ -f "update.txt" ] && echo "update.txt present" || echo "::warning::No update.txt" + ;; + esac + + - name: Check changelog has unreleased entries (PRs to main) + if: github.base_ref == 'main' + run: | + if [ ! -f "CHANGELOG.md" ]; then + echo "::error::CHANGELOG.md not found — required for releases" + exit 1 + fi + + # Extract content between [Unreleased] and next ## heading + ENTRIES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found && /^- /{count++} END{print count+0}' CHANGELOG.md) + + if [ "$ENTRIES" -eq 0 ]; then + echo "::error::CHANGELOG.md has no entries under [Unreleased]. Add changelog entries before releasing." + echo "## Changelog Check: Failed" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "No entries found under \`[Unreleased]\` in CHANGELOG.md." >> $GITHUB_STEP_SUMMARY + echo "Add entries describing what changed before merging to main." >> $GITHUB_STEP_SUMMARY + exit 1 + fi + + echo "Changelog: ${ENTRIES} unreleased entries found" + echo "## Changelog Check: Passed" >> $GITHUB_STEP_SUMMARY + echo "${ENTRIES} entries under [Unreleased]" >> $GITHUB_STEP_SUMMARY + + - name: Validate Joomla language files + if: steps.platform.outputs.platform == 'joomla' + run: | + ERRORS=0 + WARNINGS=0 + + # Require both en-GB and en-US language directories + LANG_ROOT=$(find . -path "*/language" -type d -not -path "./.git/*" 2>/dev/null | head -1) + if [ -z "$LANG_ROOT" ]; then + echo "No language/ directory found — skipping" + exit 0 + fi + + if [ ! -d "$LANG_ROOT/en-GB" ]; then + echo "::error::Missing en-GB language directory (${LANG_ROOT}/en-GB)" + ERRORS=$((ERRORS + 1)) + fi + if [ ! -d "$LANG_ROOT/en-US" ]; then + echo "::error::Missing en-US language directory (${LANG_ROOT}/en-US)" + ERRORS=$((ERRORS + 1)) + fi + + # Check that en-GB and en-US have matching .ini files + if [ -d "$LANG_ROOT/en-GB" ] && [ -d "$LANG_ROOT/en-US" ]; then + for GB_INI in "$LANG_ROOT/en-GB"/*.ini; do + [ ! -f "$GB_INI" ] && continue + US_INI="$LANG_ROOT/en-US/$(basename "$GB_INI")" + if [ ! -f "$US_INI" ]; then + echo "::error::$(basename "$GB_INI") exists in en-GB but missing from en-US" + ERRORS=$((ERRORS + 1)) + fi + done + for US_INI in "$LANG_ROOT/en-US"/*.ini; do + [ ! -f "$US_INI" ] && continue + GB_INI="$LANG_ROOT/en-GB/$(basename "$US_INI")" + if [ ! -f "$GB_INI" ]; then + echo "::error::$(basename "$US_INI") exists in en-US but missing from en-GB" + ERRORS=$((ERRORS + 1)) + fi + done + fi + + # Find all .ini language files + INI_FILES=$(find . -path "*/language/*/*.ini" -not -path "./.git/*" 2>/dev/null) + if [ -z "$INI_FILES" ]; then + echo "No .ini language files found" + [ "$ERRORS" -gt 0 ] && exit 1 + exit 0 + fi + + echo "Found $(echo "$INI_FILES" | wc -l) language file(s)" + + for FILE in $INI_FILES; do + FNAME=$(basename "$FILE") + LINENUM=0 + SEEN_KEYS="" + + while IFS= read -r line || [ -n "$line" ]; do + LINENUM=$((LINENUM + 1)) + + # Skip empty lines and comments + [ -z "$line" ] && continue + echo "$line" | grep -qE '^\s*;' && continue + echo "$line" | grep -qE '^\s*$' && continue + + # Must match KEY="VALUE" format + if ! echo "$line" | grep -qE '^[A-Z_][A-Z0-9_]*=".*"$'; then + echo "::error file=${FILE},line=${LINENUM}::Malformed line: ${line}" + ERRORS=$((ERRORS + 1)) + continue + fi + + # Extract key and check for duplicates + KEY=$(echo "$line" | sed 's/=.*//') + if echo "$SEEN_KEYS" | grep -qx "$KEY"; then + echo "::error file=${FILE},line=${LINENUM}::Duplicate key: ${KEY}" + ERRORS=$((ERRORS + 1)) + fi + SEEN_KEYS="${SEEN_KEYS} + ${KEY}" + done < "$FILE" + + echo " ${FILE}: checked ${LINENUM} lines" + done + + # Cross-check en-GB vs en-US key consistency + GB_DIR=$(find . -path "*/language/en-GB" -type d -not -path "./.git/*" 2>/dev/null | head -1) + US_DIR=$(find . -path "*/language/en-US" -type d -not -path "./.git/*" 2>/dev/null | head -1) + + if [ -n "$GB_DIR" ] && [ -n "$US_DIR" ]; then + for GB_FILE in "$GB_DIR"/*.ini; do + [ ! -f "$GB_FILE" ] && continue + FNAME=$(basename "$GB_FILE") + US_FILE="$US_DIR/$FNAME" + [ ! -f "$US_FILE" ] && continue + + GB_KEYS=$(grep -oP '^[A-Z_][A-Z0-9_]*(?==)' "$GB_FILE" 2>/dev/null | sort) + US_KEYS=$(grep -oP '^[A-Z_][A-Z0-9_]*(?==)' "$US_FILE" 2>/dev/null | sort) + + # Keys in en-GB but not en-US + MISSING_US=$(comm -23 <(echo "$GB_KEYS") <(echo "$US_KEYS")) + if [ -n "$MISSING_US" ]; then + echo "::warning::Keys in en-GB/$FNAME but missing from en-US/$FNAME:" + echo "$MISSING_US" | while read -r k; do echo " - $k"; done + WARNINGS=$((WARNINGS + 1)) + fi + + # Keys in en-US but not en-GB + MISSING_GB=$(comm -13 <(echo "$GB_KEYS") <(echo "$US_KEYS")) + if [ -n "$MISSING_GB" ]; then + echo "::warning::Keys in en-US/$FNAME but missing from en-GB/$FNAME:" + echo "$MISSING_GB" | while read -r k; do echo " - $k"; done + WARNINGS=$((WARNINGS + 1)) + fi + done + fi + + { + echo "### Language File Validation" + echo "| Metric | Count |" + echo "|---|---|" + echo "| Files checked | $(echo "$INI_FILES" | wc -l) |" + echo "| Errors | ${ERRORS} |" + echo "| Warnings | ${WARNINGS} |" + } >> $GITHUB_STEP_SUMMARY + + if [ "$ERRORS" -gt 0 ]; then + echo "::error::Language validation failed with ${ERRORS} error(s)" + exit 1 + fi + echo "Language files: OK (${WARNINGS} warning(s))" + + - name: Check changelog has unreleased entry + run: | + if [ ! -f "CHANGELOG.md" ]; then + echo "::warning::No CHANGELOG.md found" + exit 0 + fi + # Check for content under [Unreleased] section + if ! grep -q "## \[Unreleased\]" CHANGELOG.md; then + echo "::error::CHANGELOG.md missing [Unreleased] section" + exit 1 + fi + # Check there's at least one entry (Added/Changed/Fixed/Removed) under Unreleased + UNRELEASED_CONTENT=$(sed -n '/## \[Unreleased\]/,/## \[/p' CHANGELOG.md | grep -cE '^\s*-\s' || true) + if [ "$UNRELEASED_CONTENT" -eq 0 ]; then + echo "::error::CHANGELOG.md [Unreleased] section has no entries. Add a changelog entry describing your changes." + echo "## Changelog Check: Failed" >> $GITHUB_STEP_SUMMARY + echo "The \`[Unreleased]\` section in CHANGELOG.md has no entries." >> $GITHUB_STEP_SUMMARY + echo "Add a line like \`- Description of your change\` under a heading (\`### Added\`, \`### Changed\`, \`### Fixed\`, etc.)" >> $GITHUB_STEP_SUMMARY + exit 1 + fi + echo "Changelog: ${UNRELEASED_CONTENT} entry/entries in [Unreleased]" + + - name: Verify package source + run: | + SOURCE_DIR="src" + [ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs" + if [ ! -d "$SOURCE_DIR" ]; then + echo "::warning::No src/ or htdocs/ directory" + exit 0 + fi + FILE_COUNT=$(find "$SOURCE_DIR" -type f | wc -l) + echo "Source: ${FILE_COUNT} files" + [ "$FILE_COUNT" -gt 0 ] || { echo "::error::Source directory is empty"; exit 1; } + + # ── Pre-Release RC Build ───────────────────────────────────────────────── + pre-release: + name: Build RC Package + runs-on: ubuntu-latest + needs: [branch-policy, validate] + + steps: + - name: Trigger RC pre-release + env: + GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} + REPO: ${{ github.repository }} + BRANCH: ${{ github.head_ref }} + GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} + run: | + curl -s -X POST "${GITEA_URL}/api/v1/repos/${REPO}/actions/workflows/pre-release.yml/dispatches" -H "Authorization: token ${GITEA_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 + + # ── Issue Reporter ────────────────────────────────────────────────────── + report-issues: + name: Report Issues + runs-on: ubuntu-latest + needs: [branch-policy, validate] + if: >- + always() && + needs.validate.result == 'failure' + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + sparse-checkout: automation/ci-issue-reporter.sh + sparse-checkout-cone-mode: false + + - name: "File issue for PR validation failure" + env: + GITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} + GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} + run: | + chmod +x automation/ci-issue-reporter.sh + ./automation/ci-issue-reporter.sh \ + --gate "PR Validation" \ + --workflow "PR Check" \ + --severity error \ + --details "PR validation failed (syntax, manifest, changelog, or source checks). See the CI run for the specific check that failed." diff --git a/.mokogitea/workflows/pre-release.yml b/.mokogitea/workflows/pre-release.yml index 1a9eeef0..7de57b4a 100644 --- a/.mokogitea/workflows/pre-release.yml +++ b/.mokogitea/workflows/pre-release.yml @@ -8,19 +8,17 @@ # REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform # PATH: /templates/workflows/universal/pre-release.yml.template # VERSION: 05.01.00 -# BRIEF: Manual pre-release -- builds dev/alpha/beta/rc packages from any branch +# BRIEF: Auto pre-release on push to dev/alpha/beta/rc branches name: "Universal: Pre-Release" on: - pull_request: - types: [closed] + push: branches: - dev - pull_request_target: - types: [synchronize, opened, reopened] - branches: - - main + - alpha + - beta + - rc workflow_dispatch: inputs: stability: @@ -43,12 +41,11 @@ env: jobs: build: - name: "Build Pre-Release (${{ inputs.stability || 'development' }})" + name: "Build Pre-Release (${{ inputs.stability || github.ref_name }})" runs-on: release if: >- github.event_name == 'workflow_dispatch' || - (github.event_name == 'pull_request' && github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'dev') || - (github.event_name == 'pull_request_target' && github.event.pull_request.base.ref == 'main') + github.event_name == 'push' steps: - name: Checkout @@ -56,7 +53,7 @@ jobs: with: fetch-depth: 0 token: ${{ secrets.MOKOGITEA_TOKEN }} - ref: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || '' }} + ref: ${{ github.ref_name }} - name: Setup moko-platform tools env: @@ -64,20 +61,19 @@ jobs: 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/vendor/autoload.php” ]; then - echo “Using pre-installed /opt/moko-platform” - echo “MOKO_CLI=/opt/moko-platform/cli” >> “$GITHUB_ENV” + 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; 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 + 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 - git clone --depth 1 --branch main --quiet \ - “https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git” \ - /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” + echo MOKO_CLI=/tmp/moko-platform-api/cli >> $GITHUB_ENV fi - name: Detect platform @@ -88,9 +84,14 @@ jobs: - name: Resolve metadata and bump version id: meta run: | - # Auto-detect stability: RC for PRs targeting main, else use input or default to development - if [ "${{ github.event_name }}" = "pull_request_target" ] && [ "${{ github.event.pull_request.base.ref }}" = "main" ]; then - STABILITY="release-candidate" + # 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 @@ -118,6 +119,9 @@ jobs: --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}" @@ -162,7 +166,7 @@ jobs: php ${MOKO_CLI}/release_create.php \ --path . --version "$VERSION" --tag "$TAG" \ --token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \ - --repo "${GITEA_REPO}" --branch dev --prerelease + --repo "${GITEA_REPO}" --branch "${{ github.ref_name }}" --prerelease - name: Update release notes from CHANGELOG.md run: | diff --git a/.mokogitea/workflows/repo-health.yml b/.mokogitea/workflows/repo-health.yml index 8d57aaf0..d0538d52 100644 --- a/.mokogitea/workflows/repo-health.yml +++ b/.mokogitea/workflows/repo-health.yml @@ -1,711 +1,711 @@ -# ============================================================================ -# Copyright (C) 2025 Moko Consulting -# -# This file is part of a Moko Consulting project. -# -# SPDX-License-Identifier: GPL-3.0-or-later -# -# FILE INFORMATION -# DEFGROUP: Gitea.Workflow -# INGROUP: moko-platform.Validation -# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform -# PATH: /templates/workflows/joomla/repo_health.yml.template -# VERSION: 09.23.00 -# BRIEF: Enforces repository guardrails by validating scripts governance, tooling availability, and core repository health artifacts. -# ============================================================================ - -name: "Generic: Repo Health" - -defaults: - run: - shell: bash - -on: - workflow_dispatch: - inputs: - profile: - description: 'Validation profile: all, scripts, or repo' - required: true - default: all - type: choice - options: - - all - - scripts - - repo - pull_request: - push: - -permissions: - contents: read - -env: - # Scripts governance policy - SCRIPTS_REQUIRED_DIRS: - SCRIPTS_ALLOWED_DIRS: scripts,scripts/fix,scripts/lib,scripts/release,scripts/run,scripts/validate - - # Repo health policy - REPO_REQUIRED_ARTIFACTS: README.md,LICENSE,CHANGELOG.md,CONTRIBUTING.md,CODE_OF_CONDUCT.md,.mokogitea/workflows/ - REPO_OPTIONAL_FILES: SECURITY.md,GOVERNANCE.md,.editorconfig,.gitattributes,.gitignore,README.md,docs/ - REPO_DISALLOWED_DIRS: - REPO_DISALLOWED_FILES: TODO.md,todo.md - - # Extended checks toggles - EXTENDED_CHECKS: "true" - - # File / directory variables - DOCS_INDEX: docs/docs-index.md - SCRIPT_DIR: scripts - WORKFLOWS_DIR: .mokogitea/workflows - SHELLCHECK_PATTERN: '*.sh' - SPDX_FILE_GLOBS: '*.sh,*.php,*.js,*.ts,*.css,*.xml,*.yml,*.yaml' - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - -jobs: - access_check: - name: Access control - runs-on: ubuntu-latest - timeout-minutes: 10 - permissions: - contents: read - - outputs: - allowed: ${{ steps.perm.outputs.allowed }} - permission: ${{ steps.perm.outputs.permission }} - - steps: - - name: Check actor permission (admin only) - id: perm - env: - TOKEN: ${{ secrets.MOKOGITEA_TOKEN || secrets.MOKOGITEA_TOKEN || github.token }} - REPO: ${{ github.repository }} - ACTOR: ${{ github.actor }} - run: | - set -euo pipefail - ALLOWED=false - PERMISSION=unknown - METHOD="" - - # Hardcoded authorized users — always allowed - case "$ACTOR" in - jmiller|gitea-actions[bot]) - ALLOWED=true - PERMISSION=admin - METHOD="hardcoded allowlist" - ;; - *) - # Detect platform and check permissions via API - API_BASE="${GITHUB_API_URL:-${GITEA_API_URL:-https://api.github.com}}" - RESP=$(curl -sf -H "Authorization: token ${TOKEN}" \ - "${API_BASE}/repos/${REPO}/collaborators/${ACTOR}/permission" 2>/dev/null || echo '{}') - PERMISSION=$(echo "$RESP" | grep -oP '"permission"\s*:\s*"\K[^"]+' || echo "unknown") - if [ "$PERMISSION" = "admin" ] || [ "$PERMISSION" = "maintain" ] || [ "$PERMISSION" = "owner" ]; then - ALLOWED=true - fi - METHOD="collaborator API" - ;; - esac - - echo "permission=${PERMISSION}" >> "$GITHUB_OUTPUT" - echo "allowed=${ALLOWED}" >> "$GITHUB_OUTPUT" - - { - echo "## Access Authorization" - echo "" - echo "| Field | Value |" - echo "|-------|-------|" - echo "| **Actor** | \`${ACTOR}\` |" - echo "| **Repository** | \`${REPO}\` |" - echo "| **Permission** | \`${PERMISSION}\` |" - echo "| **Method** | ${METHOD} |" - echo "| **Authorized** | ${ALLOWED} |" - echo "" - if [ "$ALLOWED" = "true" ]; then - echo "${ACTOR} authorized (${METHOD})" - else - echo "${ACTOR} is NOT authorized. Requires admin or maintain role." - fi - } >> "${GITHUB_STEP_SUMMARY}" - - - name: Deny execution when not permitted - if: ${{ steps.perm.outputs.allowed != 'true' }} - run: | - set -euo pipefail - printf '%s\n' 'ERROR: Access denied. Admin permission required.' >> "${GITHUB_STEP_SUMMARY}" - exit 1 - - scripts_governance: - name: Scripts governance - needs: access_check - if: ${{ needs.access_check.outputs.allowed == 'true' }} - runs-on: ubuntu-latest - timeout-minutes: 15 - permissions: - contents: read - - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - fetch-depth: 0 - - - name: Scripts folder checks - env: - PROFILE_RAW: ${{ github.event.inputs.profile }} - run: | - set -euo pipefail - - profile="${PROFILE_RAW:-all}" - case "${profile}" in - all|scripts|repo) ;; - *) - printf '%s\n' "ERROR: Unknown profile: ${profile}" >> "${GITHUB_STEP_SUMMARY}" - exit 1 - ;; - esac - - if [ "${profile}" = 'repo' ]; then - { - printf '%s\n' '### Scripts governance' - printf '%s\n' "Profile: ${profile}" - printf '%s\n' 'Status: SKIPPED' - printf '%s\n' 'Reason: profile excludes scripts governance' - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - exit 0 - fi - - if [ ! -d "${SCRIPT_DIR}" ]; then - { - printf '%s\n' '### Scripts governance' - printf '%s\n' 'Status: OK (advisory)' - printf '%s\n' 'scripts/ directory not present. No scripts governance enforced.' - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - exit 0 - fi - - if [ -n "${SCRIPTS_REQUIRED_DIRS:-}" ]; then IFS=',' read -r -a required_dirs <<< "${SCRIPTS_REQUIRED_DIRS}"; else required_dirs=(); fi - IFS=',' read -r -a allowed_dirs <<< "${SCRIPTS_ALLOWED_DIRS}" - - missing_dirs=() - unapproved_dirs=() - - for d in "${required_dirs[@]}"; do - req="${d%/}" - [ ! -d "${req}" ] && missing_dirs+=("${req}/") - done - - while IFS= read -r d; do - allowed=false - for a in "${allowed_dirs[@]}"; do - a_norm="${a%/}" - [ "${d%/}" = "${a_norm}" ] && allowed=true - done - [ "${allowed}" = false ] && unapproved_dirs+=("${d%/}/") - done < <(find "${SCRIPT_DIR}" -maxdepth 1 -mindepth 1 -type d 2>/dev/null | sed 's#^\./##') - - { - printf '%s\n' '### Scripts governance' - printf '%s\n' "Profile: ${profile}" - printf '%s\n' '| Area | Status | Notes |' - printf '%s\n' '|---|---|---|' - - if [ "${#missing_dirs[@]}" -gt 0 ]; then - printf '%s\n' '| Required directories | Warning | Missing required subfolders |' - else - printf '%s\n' '| Required directories | OK | All required subfolders present |' - fi - - if [ "${#unapproved_dirs[@]}" -gt 0 ]; then - printf '%s\n' '| Directory policy | Warning | Unapproved directories detected |' - else - printf '%s\n' '| Directory policy | OK | No unapproved directories |' - fi - - printf '%s\n' '| Enforcement mode | Advisory | scripts folder is optional |' - printf '\n' - - if [ "${#missing_dirs[@]}" -gt 0 ]; then - printf '%s\n' 'Missing required script directories:' - for m in "${missing_dirs[@]}"; do printf '%s\n' "- ${m}"; done - printf '\n' - else - printf '%s\n' 'Missing required script directories: none.' - printf '\n' - fi - - if [ "${#unapproved_dirs[@]}" -gt 0 ]; then - printf '%s\n' 'Unapproved script directories detected:' - for m in "${unapproved_dirs[@]}"; do printf '%s\n' "- ${m}"; done - printf '\n' - else - printf '%s\n' 'Unapproved script directories detected: none.' - printf '\n' - fi - - printf '%s\n' 'Scripts governance completed in advisory mode.' - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - - repo_health: - name: Repository health - needs: access_check - if: ${{ needs.access_check.outputs.allowed == 'true' }} - runs-on: ubuntu-latest - timeout-minutes: 20 - permissions: - contents: read - - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - fetch-depth: 0 - - - name: Repository health checks - env: - PROFILE_RAW: ${{ github.event.inputs.profile }} - run: | - set -euo pipefail - - profile="${PROFILE_RAW:-all}" - case "${profile}" in - all|scripts|repo) ;; - *) - printf '%s\n' "ERROR: Unknown profile: ${profile}" >> "${GITHUB_STEP_SUMMARY}" - exit 1 - ;; - esac - - if [ "${profile}" = 'scripts' ]; then - { - printf '%s\n' '### Repository health' - printf '%s\n' "Profile: ${profile}" - printf '%s\n' 'Status: SKIPPED' - printf '%s\n' 'Reason: profile excludes repository health' - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - exit 0 - fi - - IFS=',' read -r -a required_artifacts <<< "${REPO_REQUIRED_ARTIFACTS}" - IFS=',' read -r -a optional_files <<< "${REPO_OPTIONAL_FILES}" - if [ -n "${REPO_DISALLOWED_DIRS:-}" ]; then IFS=',' read -r -a disallowed_dirs <<< "${REPO_DISALLOWED_DIRS}"; else disallowed_dirs=(); fi - IFS=',' read -r -a disallowed_files <<< "${REPO_DISALLOWED_FILES:-}" - - missing_required=() - missing_optional=() - - # Source directory: src/ or htdocs/ (either is valid for extension repos) - SOURCE_DIR="" - if [ -d "src" ]; then - SOURCE_DIR="src" - elif [ -d "htdocs" ]; then - SOURCE_DIR="htdocs" - elif [ -d "deploy" ] || [ -d "cli" ] || [ -d "monitoring" ]; then - # Platform/tooling repos don't need src/ - SOURCE_DIR="" - else - missing_required+=("src/ or htdocs/ (source directory required)") - fi - - for item in "${required_artifacts[@]}"; do - if printf '%s' "${item}" | grep -q '/$'; then - d="${item%/}" - [ ! -d "${d}" ] && missing_required+=("${item}") - else - [ ! -f "${item}" ] && missing_required+=("${item}") - fi - done - - for f in "${optional_files[@]}"; do - if printf '%s' "${f}" | grep -q '/$'; then - d="${f%/}" - [ ! -d "${d}" ] && missing_optional+=("${f}") - else - [ ! -f "${f}" ] && missing_optional+=("${f}") - fi - done - - for d in "${disallowed_dirs[@]}"; do - d_norm="${d%/}" - [ -d "${d_norm}" ] && missing_required+=("${d_norm}/ (disallowed)") - done - - for f in "${disallowed_files[@]}"; do - [ -f "${f}" ] && missing_required+=("${f} (disallowed)") - done - - git fetch origin --prune - - dev_paths=() - dev_branches=() - - while IFS= read -r b; do - name="${b#origin/}" - if [ "${name}" = 'dev' ]; then - dev_branches+=("${name}") - else - dev_paths+=("${name}") - fi - done < <(git branch -r --list 'origin/dev*' | sed 's/^ *//') - - if [ "${#dev_paths[@]}" -eq 0 ] && [ "${#dev_branches[@]}" -eq 0 ]; then - missing_required+=("dev or dev/* branch") - fi - - content_warnings=() - - if [ -f 'CHANGELOG.md' ] && ! grep -Eq '^# Changelog' CHANGELOG.md; then - content_warnings+=("CHANGELOG.md missing '# Changelog' header") - fi - - if [ -f 'CHANGELOG.md' ] && grep -Eq '^[# ]*Unreleased' CHANGELOG.md; then - content_warnings+=("CHANGELOG.md contains Unreleased section (review release readiness)") - fi - - if [ -f 'LICENSE' ] && ! grep -qiE 'GNU GENERAL PUBLIC LICENSE|GPL' LICENSE; then - content_warnings+=("LICENSE does not look like a GPL text") - fi - - if [ -f 'README.md' ] && ! grep -qiE 'moko|Moko' README.md; then - content_warnings+=("README.md missing expected brand keyword") - fi - - export PROFILE_RAW="${profile}" - export MISSING_REQUIRED="$(printf '%s\n' "${missing_required[@]:-}")" - export MISSING_OPTIONAL="$(printf '%s\n' "${missing_optional[@]:-}")" - export CONTENT_WARNINGS="$(printf '%s\n' "${content_warnings[@]:-}")" - - report_json=$(printf '{"profile":"%s","missing_required":%d,"missing_optional":%d,"content_warnings":%d}' "$profile" "${#missing_required[@]}" "${#missing_optional[@]}" "${#content_warnings[@]}") - - { - printf '%s\n' '### Repository health' - printf '%s\n' "Profile: ${profile}" - printf '%s\n' '| Metric | Value |' - printf '%s\n' '|---|---|' - printf '%s\n' "| Missing required | ${#missing_required[@]} |" - printf '%s\n' "| Missing optional | ${#missing_optional[@]} |" - printf '%s\n' "| Content warnings | ${#content_warnings[@]} |" - printf '\n' - - printf '%s\n' '### Guardrails report (JSON)' - printf '%s\n' '```json' - printf '%s\n' "${report_json}" - printf '%s\n' '```' - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - - if [ "${#missing_required[@]}" -gt 0 ]; then - { - printf '%s\n' '### Missing required repo artifacts' - for m in "${missing_required[@]}"; do printf '%s\n' "- ${m}"; done - printf '%s\n' 'ERROR: Guardrails failed. Missing required repository artifacts.' - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - exit 1 - fi - - if [ "${#missing_optional[@]}" -gt 0 ]; then - { - printf '%s\n' '### Missing optional repo artifacts' - for m in "${missing_optional[@]}"; do printf '%s\n' "- ${m}"; done - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - fi - - if [ "${#content_warnings[@]}" -gt 0 ]; then - { - printf '%s\n' '### Repo content warnings' - for m in "${content_warnings[@]}"; do printf '%s\n' "- ${m}"; done - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - fi - - # -- Joomla-specific checks -- - joomla_findings=() - - MANIFEST="$(find . -maxdepth 2 -name '*.xml' -exec grep -l '/dev/null | head -1 || true)" - if [ -z "${MANIFEST}" ]; then - joomla_findings+=("Joomla XML manifest not found (no *.xml with tag)") - else - if ! grep -qP '' "${MANIFEST}"; then - joomla_findings+=("XML manifest: tag missing") - fi - if ! grep -qP 'type="(component|module|plugin|library|package|template|language)"' "${MANIFEST}"; then - joomla_findings+=("XML manifest: type attribute missing or invalid") - fi - if ! grep -qP '' "${MANIFEST}"; then - joomla_findings+=("XML manifest: tag missing") - fi - if ! grep -qP '' "${MANIFEST}"; then - joomla_findings+=("XML manifest: tag missing") - fi - if ! grep -qP ' missing (required for Joomla 5+)") - fi - fi - - INI_COUNT="$(find . -name '*.ini' -type f 2>/dev/null | wc -l)" - if [ "${INI_COUNT}" -eq 0 ]; then - joomla_findings+=("No .ini language files found") - fi - - if [ ! -f 'updates.xml' ]; then - joomla_findings+=("updates.xml missing in root (required for Joomla update server)") - fi - - if [ -n "${SOURCE_DIR}" ]; then - INDEX_DIRS=("${SOURCE_DIR}" "${SOURCE_DIR}/admin" "${SOURCE_DIR}/site") - for dir in "${INDEX_DIRS[@]}"; do - if [ -d "${dir}" ] && [ ! -f "${dir}/index.html" ]; then - joomla_findings+=("${dir}/index.html missing (directory listing protection)") - fi - done - fi - - if [ "${#joomla_findings[@]}" -gt 0 ]; then - { - printf '%s\n' '### Joomla extension checks' - printf '%s\n' '| Check | Status |' - printf '%s\n' '|---|---|' - for f in "${joomla_findings[@]}"; do - printf '%s\n' "| ${f} | Warning |" - done - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - else - { - printf '%s\n' '### Joomla extension checks' - printf '%s\n' 'All Joomla-specific checks passed.' - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - fi - - extended_enabled="${EXTENDED_CHECKS:-true}" - extended_findings=() - - if [ "${extended_enabled}" = 'true' ]; then - if [ -f '.github/CODEOWNERS' ] || [ -f 'CODEOWNERS' ] || [ -f 'docs/CODEOWNERS' ]; then - : - else - extended_findings+=("CODEOWNERS not found (.github/CODEOWNERS preferred)") - fi - - if ls "${WORKFLOWS_DIR}"/*.yml >/dev/null 2>&1 || ls "${WORKFLOWS_DIR}"/*.yaml >/dev/null 2>&1; then - bad_refs="$(grep -RIn --include='*.yml' --include='*.yaml' -E '^[[:space:]]*uses:[[:space:]]*[^#]+@(main|master)\b' "${WORKFLOWS_DIR}" 2>/dev/null || true)" - if [ -n "${bad_refs}" ]; then - extended_findings+=("Workflows reference actions @main/@master (pin versions): see log excerpt") - { - printf '%s\n' '### Workflow pinning advisory' - printf '%s\n' 'Found uses: entries pinned to main/master:' - printf '%s\n' '```' - printf '%s\n' "${bad_refs}" - printf '%s\n' '```' - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - fi - fi - - if [ -f "${DOCS_INDEX}" ]; then - missing_links="" - while IFS= read -r docline; do - for link in $(echo "$docline" | grep -oE '\]\([^)]+\)' | sed 's/\](//' | sed 's/)$//' || true); do - case "$link" in http://*|https://*|"#"*|mailto:*) continue ;; esac - linkpath="${link%%#*}" - linkpath="${linkpath%%\?*}" - [ -z "$linkpath" ] && continue - if [ "${linkpath:0:1}" = "/" ]; then - testpath="${linkpath#/}" - else - testpath="$(dirname "${DOCS_INDEX}")/${linkpath}" - fi - [ ! -e "$testpath" ] && missing_links="${missing_links}${testpath} " - done - done < "${DOCS_INDEX}" - if [ -n "${missing_links}" ]; then - extended_findings+=("docs/docs-index.md contains broken relative links") - { - printf '%s\n' '### Docs index link integrity' - printf '%s\n' 'Broken relative links:' - for bl in ${missing_links}; do - printf '%s\n' "- ${bl}" - done - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - fi - fi - - if [ -d "${SCRIPT_DIR}" ]; then - if ! command -v shellcheck >/dev/null 2>&1; then - sudo apt-get update -qq - sudo apt-get install -y shellcheck >/dev/null - fi - - sc_out='' - while IFS= read -r shf; do - [ -z "${shf}" ] && continue - out_one="$(shellcheck -S warning -x "${shf}" 2>/dev/null || true)" - if [ -n "${out_one}" ]; then - sc_out="${sc_out}${out_one}\n" - fi - done < <(find "${SCRIPT_DIR}" -type f -name "${SHELLCHECK_PATTERN}" 2>/dev/null | sort) - - if [ -n "${sc_out}" ]; then - extended_findings+=("ShellCheck warnings detected (advisory)") - sc_head="$(printf '%s' "${sc_out}" | head -n 200)" - { - printf '%s\n' '### ShellCheck (advisory)' - printf '%s\n' '```' - printf '%s\n' "${sc_head}" - printf '%s\n' '```' - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - fi - fi - - spdx_missing=() - IFS=',' read -r -a spdx_globs <<< "${SPDX_FILE_GLOBS}" - spdx_args=() - for g in "${spdx_globs[@]}"; do spdx_args+=("${g}"); done - - while IFS= read -r f; do - [ -z "${f}" ] && continue - if ! head -n 40 "${f}" | grep -q 'SPDX-License-Identifier:'; then - spdx_missing+=("${f}") - fi - done < <(git ls-files "${spdx_args[@]}" 2>/dev/null || true) - - if [ "${#spdx_missing[@]}" -gt 0 ]; then - extended_findings+=("SPDX header missing in some tracked files (advisory)") - { - printf '%s\n' '### SPDX header advisory' - printf '%s\n' 'Files missing SPDX-License-Identifier (first 40 lines scan):' - for f in "${spdx_missing[@]}"; do printf '%s\n' "- ${f}"; done - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - fi - - stale_cutoff_days=180 - stale_branches="$(git for-each-ref --format='%(refname:short) %(committerdate:unix)' refs/remotes/origin 2>/dev/null | awk -v now="$(date +%s)" -v days="${stale_cutoff_days}" '{if (now-$2 > days*86400) print $1}' | head -50)" - if [ -n "${stale_branches}" ]; then - extended_findings+=("Stale remote branches detected (advisory)") - { - printf '%s\n' '### Git hygiene advisory' - printf '%s\n' "Branches with last commit older than ${stale_cutoff_days} days (sample up to 50):" - while IFS= read -r b; do [ -n "${b}" ] && printf '%s\n' "- ${b}"; done <<< "${stale_branches}" - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - fi - fi - - { - printf '%s\n' '### Guardrails coverage matrix' - printf '%s\n' '| Domain | Status | Notes |' - printf '%s\n' '|---|---|---|' - printf '%s\n' '| Access control | OK | Admin-only execution gate |' - printf '%s\n' '| Release policy | N/A | Releases handled by MokoGitea |' - printf '%s\n' '| Scripts governance | OK | Directory policy and advisory reporting |' - printf '%s\n' '| Repo required artifacts | OK | Required, optional, disallowed enforcement |' - printf '%s\n' '| Repo content heuristics | OK | Brand, license, changelog structure |' - if [ "${extended_enabled}" = 'true' ]; then - if [ "${#extended_findings[@]}" -gt 0 ]; then - printf '%s\n' '| Extended checks | Warning | See extended findings below |' - else - printf '%s\n' '| Extended checks | OK | No findings |' - fi - else - printf '%s\n' '| Extended checks | SKIPPED | EXTENDED_CHECKS disabled |' - fi - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - - if [ "${extended_enabled}" = 'true' ] && [ "${#extended_findings[@]}" -gt 0 ]; then - { - printf '%s\n' '### Extended findings (advisory)' - for f in "${extended_findings[@]}"; do printf '%s\n' "- ${f}"; done - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - fi - - printf '%s\n' 'Repository health guardrails passed.' >> "${GITHUB_STEP_SUMMARY}" - - - site-health: - name: Site Health - runs-on: ubuntu-latest - if: github.event_name == 'workflow_dispatch' - steps: - - uses: actions/checkout@v4 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.3' - - - name: Uptime check - if: env.URLS != '' - run: | - echo "$URLS" > /tmp/urls.txt - php monitoring/uptime-probe.php --urls /tmp/urls.txt --timeout 15 || echo "::warning::Some sites are down" - rm -f /tmp/urls.txt - env: - URLS: ${{ vars.MONITORED_URLS }} - - - name: SSL certificate check - if: env.DOMAINS != '' - run: | - echo "$DOMAINS" > /tmp/domains.txt - php monitoring/ssl-check.php --domains /tmp/domains.txt --warn-days 30 || echo "::warning::SSL certificates expiring soon" - rm -f /tmp/domains.txt - env: - DOMAINS: ${{ vars.MONITORED_DOMAINS }} - - - name: Summary - if: always() - run: | - echo "### Site Health" >> $GITHUB_STEP_SUMMARY - echo "Uptime and SSL checks completed." >> $GITHUB_STEP_SUMMARY - - # ═══════════════════════════════════════════════════════════════════════ - # Issue Reporter — file issues for failed gates - # ═══════════════════════════════════════════════════════════════════════ - report-issues: - name: "Report Issues" - runs-on: ubuntu-latest - needs: [access_check, scripts_governance, repo_health] - if: >- - always() && - (needs.scripts_governance.result == 'failure' || - needs.repo_health.result == 'failure') - - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - sparse-checkout: automation/ci-issue-reporter.sh - sparse-checkout-cone-mode: false - - - name: "File issues for failed gates" - env: - GITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} - GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} - run: | - chmod +x automation/ci-issue-reporter.sh - REPORTER="./automation/ci-issue-reporter.sh" - WF="Repo Health" - - report_gate() { - local gate="$1" result="$2" details="$3" - if [ "$result" = "failure" ]; then - "$REPORTER" --gate "$gate" --details "$details" --workflow "$WF" --severity error - fi - } - - report_gate "Scripts Governance" \ - "${{ needs.scripts_governance.result }}" \ - "Scripts directory policy violations detected. Review required and allowed directories." - - report_gate "Repository Health" \ - "${{ needs.repo_health.result }}" \ - "Repository health checks failed — missing required artifacts, disallowed files, or content warnings. Check the CI run summary." +# ============================================================================ +# Copyright (C) 2025 Moko Consulting +# +# This file is part of a Moko Consulting project. +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +# FILE INFORMATION +# DEFGROUP: Gitea.Workflow +# INGROUP: moko-platform.Validation +# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform +# PATH: /templates/workflows/joomla/repo_health.yml.template +# VERSION: 09.23.00 +# BRIEF: Enforces repository guardrails by validating scripts governance, tooling availability, and core repository health artifacts. +# ============================================================================ + +name: "Generic: Repo Health" + +defaults: + run: + shell: bash + +on: + workflow_dispatch: + inputs: + profile: + description: 'Validation profile: all, scripts, or repo' + required: true + default: all + type: choice + options: + - all + - scripts + - repo + pull_request: + push: + +permissions: + contents: read + +env: + # Scripts governance policy + SCRIPTS_REQUIRED_DIRS: + SCRIPTS_ALLOWED_DIRS: scripts,scripts/fix,scripts/lib,scripts/release,scripts/run,scripts/validate + + # Repo health policy + REPO_REQUIRED_ARTIFACTS: README.md,LICENSE,CHANGELOG.md,CONTRIBUTING.md,CODE_OF_CONDUCT.md,.mokogitea/workflows/ + REPO_OPTIONAL_FILES: SECURITY.md,GOVERNANCE.md,.editorconfig,.gitattributes,.gitignore,README.md,docs/ + REPO_DISALLOWED_DIRS: + REPO_DISALLOWED_FILES: TODO.md,todo.md + + # Extended checks toggles + EXTENDED_CHECKS: "true" + + # File / directory variables + DOCS_INDEX: docs/docs-index.md + SCRIPT_DIR: scripts + WORKFLOWS_DIR: .mokogitea/workflows + SHELLCHECK_PATTERN: '*.sh' + SPDX_FILE_GLOBS: '*.sh,*.php,*.js,*.ts,*.css,*.xml,*.yml,*.yaml' + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + +jobs: + access_check: + name: Access control + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read + + outputs: + allowed: ${{ steps.perm.outputs.allowed }} + permission: ${{ steps.perm.outputs.permission }} + + steps: + - name: Check actor permission (admin only) + id: perm + env: + TOKEN: ${{ secrets.MOKOGITEA_TOKEN || secrets.MOKOGITEA_TOKEN || github.token }} + REPO: ${{ github.repository }} + ACTOR: ${{ github.actor }} + run: | + set -euo pipefail + ALLOWED=false + PERMISSION=unknown + METHOD="" + + # Hardcoded authorized users — always allowed + case "$ACTOR" in + jmiller|gitea-actions[bot]) + ALLOWED=true + PERMISSION=admin + METHOD="hardcoded allowlist" + ;; + *) + # Detect platform and check permissions via API + API_BASE="${GITHUB_API_URL:-${GITEA_API_URL:-https://api.github.com}}" + RESP=$(curl -sf -H "Authorization: token ${TOKEN}" \ + "${API_BASE}/repos/${REPO}/collaborators/${ACTOR}/permission" 2>/dev/null || echo '{}') + PERMISSION=$(echo "$RESP" | grep -oP '"permission"\s*:\s*"\K[^"]+' || echo "unknown") + if [ "$PERMISSION" = "admin" ] || [ "$PERMISSION" = "maintain" ] || [ "$PERMISSION" = "owner" ]; then + ALLOWED=true + fi + METHOD="collaborator API" + ;; + esac + + echo "permission=${PERMISSION}" >> "$GITHUB_OUTPUT" + echo "allowed=${ALLOWED}" >> "$GITHUB_OUTPUT" + + { + echo "## Access Authorization" + echo "" + echo "| Field | Value |" + echo "|-------|-------|" + echo "| **Actor** | \`${ACTOR}\` |" + echo "| **Repository** | \`${REPO}\` |" + echo "| **Permission** | \`${PERMISSION}\` |" + echo "| **Method** | ${METHOD} |" + echo "| **Authorized** | ${ALLOWED} |" + echo "" + if [ "$ALLOWED" = "true" ]; then + echo "${ACTOR} authorized (${METHOD})" + else + echo "${ACTOR} is NOT authorized. Requires admin or maintain role." + fi + } >> "${GITHUB_STEP_SUMMARY}" + + - name: Deny execution when not permitted + if: ${{ steps.perm.outputs.allowed != 'true' }} + run: | + set -euo pipefail + printf '%s\n' 'ERROR: Access denied. Admin permission required.' >> "${GITHUB_STEP_SUMMARY}" + exit 1 + + scripts_governance: + name: Scripts governance + needs: access_check + if: ${{ needs.access_check.outputs.allowed == 'true' }} + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + fetch-depth: 0 + + - name: Scripts folder checks + env: + PROFILE_RAW: ${{ github.event.inputs.profile }} + run: | + set -euo pipefail + + profile="${PROFILE_RAW:-all}" + case "${profile}" in + all|scripts|repo) ;; + *) + printf '%s\n' "ERROR: Unknown profile: ${profile}" >> "${GITHUB_STEP_SUMMARY}" + exit 1 + ;; + esac + + if [ "${profile}" = 'repo' ]; then + { + printf '%s\n' '### Scripts governance' + printf '%s\n' "Profile: ${profile}" + printf '%s\n' 'Status: SKIPPED' + printf '%s\n' 'Reason: profile excludes scripts governance' + printf '\n' + } >> "${GITHUB_STEP_SUMMARY}" + exit 0 + fi + + if [ ! -d "${SCRIPT_DIR}" ]; then + { + printf '%s\n' '### Scripts governance' + printf '%s\n' 'Status: OK (advisory)' + printf '%s\n' 'scripts/ directory not present. No scripts governance enforced.' + printf '\n' + } >> "${GITHUB_STEP_SUMMARY}" + exit 0 + fi + + if [ -n "${SCRIPTS_REQUIRED_DIRS:-}" ]; then IFS=',' read -r -a required_dirs <<< "${SCRIPTS_REQUIRED_DIRS}"; else required_dirs=(); fi + IFS=',' read -r -a allowed_dirs <<< "${SCRIPTS_ALLOWED_DIRS}" + + missing_dirs=() + unapproved_dirs=() + + for d in "${required_dirs[@]}"; do + req="${d%/}" + [ ! -d "${req}" ] && missing_dirs+=("${req}/") + done + + while IFS= read -r d; do + allowed=false + for a in "${allowed_dirs[@]}"; do + a_norm="${a%/}" + [ "${d%/}" = "${a_norm}" ] && allowed=true + done + [ "${allowed}" = false ] && unapproved_dirs+=("${d%/}/") + done < <(find "${SCRIPT_DIR}" -maxdepth 1 -mindepth 1 -type d 2>/dev/null | sed 's#^\./##') + + { + printf '%s\n' '### Scripts governance' + printf '%s\n' "Profile: ${profile}" + printf '%s\n' '| Area | Status | Notes |' + printf '%s\n' '|---|---|---|' + + if [ "${#missing_dirs[@]}" -gt 0 ]; then + printf '%s\n' '| Required directories | Warning | Missing required subfolders |' + else + printf '%s\n' '| Required directories | OK | All required subfolders present |' + fi + + if [ "${#unapproved_dirs[@]}" -gt 0 ]; then + printf '%s\n' '| Directory policy | Warning | Unapproved directories detected |' + else + printf '%s\n' '| Directory policy | OK | No unapproved directories |' + fi + + printf '%s\n' '| Enforcement mode | Advisory | scripts folder is optional |' + printf '\n' + + if [ "${#missing_dirs[@]}" -gt 0 ]; then + printf '%s\n' 'Missing required script directories:' + for m in "${missing_dirs[@]}"; do printf '%s\n' "- ${m}"; done + printf '\n' + else + printf '%s\n' 'Missing required script directories: none.' + printf '\n' + fi + + if [ "${#unapproved_dirs[@]}" -gt 0 ]; then + printf '%s\n' 'Unapproved script directories detected:' + for m in "${unapproved_dirs[@]}"; do printf '%s\n' "- ${m}"; done + printf '\n' + else + printf '%s\n' 'Unapproved script directories detected: none.' + printf '\n' + fi + + printf '%s\n' 'Scripts governance completed in advisory mode.' + printf '\n' + } >> "${GITHUB_STEP_SUMMARY}" + + repo_health: + name: Repository health + needs: access_check + if: ${{ needs.access_check.outputs.allowed == 'true' }} + runs-on: ubuntu-latest + timeout-minutes: 20 + permissions: + contents: read + + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + fetch-depth: 0 + + - name: Repository health checks + env: + PROFILE_RAW: ${{ github.event.inputs.profile }} + run: | + set -euo pipefail + + profile="${PROFILE_RAW:-all}" + case "${profile}" in + all|scripts|repo) ;; + *) + printf '%s\n' "ERROR: Unknown profile: ${profile}" >> "${GITHUB_STEP_SUMMARY}" + exit 1 + ;; + esac + + if [ "${profile}" = 'scripts' ]; then + { + printf '%s\n' '### Repository health' + printf '%s\n' "Profile: ${profile}" + printf '%s\n' 'Status: SKIPPED' + printf '%s\n' 'Reason: profile excludes repository health' + printf '\n' + } >> "${GITHUB_STEP_SUMMARY}" + exit 0 + fi + + IFS=',' read -r -a required_artifacts <<< "${REPO_REQUIRED_ARTIFACTS}" + IFS=',' read -r -a optional_files <<< "${REPO_OPTIONAL_FILES}" + if [ -n "${REPO_DISALLOWED_DIRS:-}" ]; then IFS=',' read -r -a disallowed_dirs <<< "${REPO_DISALLOWED_DIRS}"; else disallowed_dirs=(); fi + IFS=',' read -r -a disallowed_files <<< "${REPO_DISALLOWED_FILES:-}" + + missing_required=() + missing_optional=() + + # Source directory: src/ or htdocs/ (either is valid for extension repos) + SOURCE_DIR="" + if [ -d "src" ]; then + SOURCE_DIR="src" + elif [ -d "htdocs" ]; then + SOURCE_DIR="htdocs" + elif [ -d "deploy" ] || [ -d "cli" ] || [ -d "monitoring" ]; then + # Platform/tooling repos don't need src/ + SOURCE_DIR="" + else + missing_required+=("src/ or htdocs/ (source directory required)") + fi + + for item in "${required_artifacts[@]}"; do + if printf '%s' "${item}" | grep -q '/$'; then + d="${item%/}" + [ ! -d "${d}" ] && missing_required+=("${item}") + else + [ ! -f "${item}" ] && missing_required+=("${item}") + fi + done + + for f in "${optional_files[@]}"; do + if printf '%s' "${f}" | grep -q '/$'; then + d="${f%/}" + [ ! -d "${d}" ] && missing_optional+=("${f}") + else + [ ! -f "${f}" ] && missing_optional+=("${f}") + fi + done + + for d in "${disallowed_dirs[@]}"; do + d_norm="${d%/}" + [ -d "${d_norm}" ] && missing_required+=("${d_norm}/ (disallowed)") + done + + for f in "${disallowed_files[@]}"; do + [ -f "${f}" ] && missing_required+=("${f} (disallowed)") + done + + git fetch origin --prune + + dev_paths=() + dev_branches=() + + while IFS= read -r b; do + name="${b#origin/}" + if [ "${name}" = 'dev' ]; then + dev_branches+=("${name}") + else + dev_paths+=("${name}") + fi + done < <(git branch -r --list 'origin/dev*' | sed 's/^ *//') + + if [ "${#dev_paths[@]}" -eq 0 ] && [ "${#dev_branches[@]}" -eq 0 ]; then + missing_required+=("dev or dev/* branch") + fi + + content_warnings=() + + if [ -f 'CHANGELOG.md' ] && ! grep -Eq '^# Changelog' CHANGELOG.md; then + content_warnings+=("CHANGELOG.md missing '# Changelog' header") + fi + + if [ -f 'CHANGELOG.md' ] && grep -Eq '^[# ]*Unreleased' CHANGELOG.md; then + content_warnings+=("CHANGELOG.md contains Unreleased section (review release readiness)") + fi + + if [ -f 'LICENSE' ] && ! grep -qiE 'GNU GENERAL PUBLIC LICENSE|GPL' LICENSE; then + content_warnings+=("LICENSE does not look like a GPL text") + fi + + if [ -f 'README.md' ] && ! grep -qiE 'moko|Moko' README.md; then + content_warnings+=("README.md missing expected brand keyword") + fi + + export PROFILE_RAW="${profile}" + export MISSING_REQUIRED="$(printf '%s\n' "${missing_required[@]:-}")" + export MISSING_OPTIONAL="$(printf '%s\n' "${missing_optional[@]:-}")" + export CONTENT_WARNINGS="$(printf '%s\n' "${content_warnings[@]:-}")" + + report_json=$(printf '{"profile":"%s","missing_required":%d,"missing_optional":%d,"content_warnings":%d}' "$profile" "${#missing_required[@]}" "${#missing_optional[@]}" "${#content_warnings[@]}") + + { + printf '%s\n' '### Repository health' + printf '%s\n' "Profile: ${profile}" + printf '%s\n' '| Metric | Value |' + printf '%s\n' '|---|---|' + printf '%s\n' "| Missing required | ${#missing_required[@]} |" + printf '%s\n' "| Missing optional | ${#missing_optional[@]} |" + printf '%s\n' "| Content warnings | ${#content_warnings[@]} |" + printf '\n' + + printf '%s\n' '### Guardrails report (JSON)' + printf '%s\n' '```json' + printf '%s\n' "${report_json}" + printf '%s\n' '```' + printf '\n' + } >> "${GITHUB_STEP_SUMMARY}" + + if [ "${#missing_required[@]}" -gt 0 ]; then + { + printf '%s\n' '### Missing required repo artifacts' + for m in "${missing_required[@]}"; do printf '%s\n' "- ${m}"; done + printf '%s\n' 'ERROR: Guardrails failed. Missing required repository artifacts.' + printf '\n' + } >> "${GITHUB_STEP_SUMMARY}" + exit 1 + fi + + if [ "${#missing_optional[@]}" -gt 0 ]; then + { + printf '%s\n' '### Missing optional repo artifacts' + for m in "${missing_optional[@]}"; do printf '%s\n' "- ${m}"; done + printf '\n' + } >> "${GITHUB_STEP_SUMMARY}" + fi + + if [ "${#content_warnings[@]}" -gt 0 ]; then + { + printf '%s\n' '### Repo content warnings' + for m in "${content_warnings[@]}"; do printf '%s\n' "- ${m}"; done + printf '\n' + } >> "${GITHUB_STEP_SUMMARY}" + fi + + # -- Joomla-specific checks -- + joomla_findings=() + + MANIFEST="$(find . -maxdepth 2 -name '*.xml' -exec grep -l '/dev/null | head -1 || true)" + if [ -z "${MANIFEST}" ]; then + joomla_findings+=("Joomla XML manifest not found (no *.xml with tag)") + else + if ! grep -qP '' "${MANIFEST}"; then + joomla_findings+=("XML manifest: tag missing") + fi + if ! grep -qP 'type="(component|module|plugin|library|package|template|language)"' "${MANIFEST}"; then + joomla_findings+=("XML manifest: type attribute missing or invalid") + fi + if ! grep -qP '' "${MANIFEST}"; then + joomla_findings+=("XML manifest: tag missing") + fi + if ! grep -qP '' "${MANIFEST}"; then + joomla_findings+=("XML manifest: tag missing") + fi + if ! grep -qP ' missing (required for Joomla 5+)") + fi + fi + + INI_COUNT="$(find . -name '*.ini' -type f 2>/dev/null | wc -l)" + if [ "${INI_COUNT}" -eq 0 ]; then + joomla_findings+=("No .ini language files found") + fi + + if [ ! -f 'updates.xml' ]; then + joomla_findings+=("updates.xml missing in root (required for Joomla update server)") + fi + + if [ -n "${SOURCE_DIR}" ]; then + INDEX_DIRS=("${SOURCE_DIR}" "${SOURCE_DIR}/admin" "${SOURCE_DIR}/site") + for dir in "${INDEX_DIRS[@]}"; do + if [ -d "${dir}" ] && [ ! -f "${dir}/index.html" ]; then + joomla_findings+=("${dir}/index.html missing (directory listing protection)") + fi + done + fi + + if [ "${#joomla_findings[@]}" -gt 0 ]; then + { + printf '%s\n' '### Joomla extension checks' + printf '%s\n' '| Check | Status |' + printf '%s\n' '|---|---|' + for f in "${joomla_findings[@]}"; do + printf '%s\n' "| ${f} | Warning |" + done + printf '\n' + } >> "${GITHUB_STEP_SUMMARY}" + else + { + printf '%s\n' '### Joomla extension checks' + printf '%s\n' 'All Joomla-specific checks passed.' + printf '\n' + } >> "${GITHUB_STEP_SUMMARY}" + fi + + extended_enabled="${EXTENDED_CHECKS:-true}" + extended_findings=() + + if [ "${extended_enabled}" = 'true' ]; then + if [ -f '.github/CODEOWNERS' ] || [ -f 'CODEOWNERS' ] || [ -f 'docs/CODEOWNERS' ]; then + : + else + extended_findings+=("CODEOWNERS not found (.github/CODEOWNERS preferred)") + fi + + if ls "${WORKFLOWS_DIR}"/*.yml >/dev/null 2>&1 || ls "${WORKFLOWS_DIR}"/*.yaml >/dev/null 2>&1; then + bad_refs="$(grep -RIn --include='*.yml' --include='*.yaml' -E '^[[:space:]]*uses:[[:space:]]*[^#]+@(main|master)\b' "${WORKFLOWS_DIR}" 2>/dev/null || true)" + if [ -n "${bad_refs}" ]; then + extended_findings+=("Workflows reference actions @main/@master (pin versions): see log excerpt") + { + printf '%s\n' '### Workflow pinning advisory' + printf '%s\n' 'Found uses: entries pinned to main/master:' + printf '%s\n' '```' + printf '%s\n' "${bad_refs}" + printf '%s\n' '```' + printf '\n' + } >> "${GITHUB_STEP_SUMMARY}" + fi + fi + + if [ -f "${DOCS_INDEX}" ]; then + missing_links="" + while IFS= read -r docline; do + for link in $(echo "$docline" | grep -oE '\]\([^)]+\)' | sed 's/\](//' | sed 's/)$//' || true); do + case "$link" in http://*|https://*|"#"*|mailto:*) continue ;; esac + linkpath="${link%%#*}" + linkpath="${linkpath%%\?*}" + [ -z "$linkpath" ] && continue + if [ "${linkpath:0:1}" = "/" ]; then + testpath="${linkpath#/}" + else + testpath="$(dirname "${DOCS_INDEX}")/${linkpath}" + fi + [ ! -e "$testpath" ] && missing_links="${missing_links}${testpath} " + done + done < "${DOCS_INDEX}" + if [ -n "${missing_links}" ]; then + extended_findings+=("docs/docs-index.md contains broken relative links") + { + printf '%s\n' '### Docs index link integrity' + printf '%s\n' 'Broken relative links:' + for bl in ${missing_links}; do + printf '%s\n' "- ${bl}" + done + printf '\n' + } >> "${GITHUB_STEP_SUMMARY}" + fi + fi + + if [ -d "${SCRIPT_DIR}" ]; then + if ! command -v shellcheck >/dev/null 2>&1; then + sudo apt-get update -qq + sudo apt-get install -y shellcheck >/dev/null + fi + + sc_out='' + while IFS= read -r shf; do + [ -z "${shf}" ] && continue + out_one="$(shellcheck -S warning -x "${shf}" 2>/dev/null || true)" + if [ -n "${out_one}" ]; then + sc_out="${sc_out}${out_one}\n" + fi + done < <(find "${SCRIPT_DIR}" -type f -name "${SHELLCHECK_PATTERN}" 2>/dev/null | sort) + + if [ -n "${sc_out}" ]; then + extended_findings+=("ShellCheck warnings detected (advisory)") + sc_head="$(printf '%s' "${sc_out}" | head -n 200)" + { + printf '%s\n' '### ShellCheck (advisory)' + printf '%s\n' '```' + printf '%s\n' "${sc_head}" + printf '%s\n' '```' + printf '\n' + } >> "${GITHUB_STEP_SUMMARY}" + fi + fi + + spdx_missing=() + IFS=',' read -r -a spdx_globs <<< "${SPDX_FILE_GLOBS}" + spdx_args=() + for g in "${spdx_globs[@]}"; do spdx_args+=("${g}"); done + + while IFS= read -r f; do + [ -z "${f}" ] && continue + if ! head -n 40 "${f}" | grep -q 'SPDX-License-Identifier:'; then + spdx_missing+=("${f}") + fi + done < <(git ls-files "${spdx_args[@]}" 2>/dev/null || true) + + if [ "${#spdx_missing[@]}" -gt 0 ]; then + extended_findings+=("SPDX header missing in some tracked files (advisory)") + { + printf '%s\n' '### SPDX header advisory' + printf '%s\n' 'Files missing SPDX-License-Identifier (first 40 lines scan):' + for f in "${spdx_missing[@]}"; do printf '%s\n' "- ${f}"; done + printf '\n' + } >> "${GITHUB_STEP_SUMMARY}" + fi + + stale_cutoff_days=180 + stale_branches="$(git for-each-ref --format='%(refname:short) %(committerdate:unix)' refs/remotes/origin 2>/dev/null | awk -v now="$(date +%s)" -v days="${stale_cutoff_days}" '{if (now-$2 > days*86400) print $1}' | head -50)" + if [ -n "${stale_branches}" ]; then + extended_findings+=("Stale remote branches detected (advisory)") + { + printf '%s\n' '### Git hygiene advisory' + printf '%s\n' "Branches with last commit older than ${stale_cutoff_days} days (sample up to 50):" + while IFS= read -r b; do [ -n "${b}" ] && printf '%s\n' "- ${b}"; done <<< "${stale_branches}" + printf '\n' + } >> "${GITHUB_STEP_SUMMARY}" + fi + fi + + { + printf '%s\n' '### Guardrails coverage matrix' + printf '%s\n' '| Domain | Status | Notes |' + printf '%s\n' '|---|---|---|' + printf '%s\n' '| Access control | OK | Admin-only execution gate |' + printf '%s\n' '| Release policy | N/A | Releases handled by MokoGitea |' + printf '%s\n' '| Scripts governance | OK | Directory policy and advisory reporting |' + printf '%s\n' '| Repo required artifacts | OK | Required, optional, disallowed enforcement |' + printf '%s\n' '| Repo content heuristics | OK | Brand, license, changelog structure |' + if [ "${extended_enabled}" = 'true' ]; then + if [ "${#extended_findings[@]}" -gt 0 ]; then + printf '%s\n' '| Extended checks | Warning | See extended findings below |' + else + printf '%s\n' '| Extended checks | OK | No findings |' + fi + else + printf '%s\n' '| Extended checks | SKIPPED | EXTENDED_CHECKS disabled |' + fi + printf '\n' + } >> "${GITHUB_STEP_SUMMARY}" + + if [ "${extended_enabled}" = 'true' ] && [ "${#extended_findings[@]}" -gt 0 ]; then + { + printf '%s\n' '### Extended findings (advisory)' + for f in "${extended_findings[@]}"; do printf '%s\n' "- ${f}"; done + printf '\n' + } >> "${GITHUB_STEP_SUMMARY}" + fi + + printf '%s\n' 'Repository health guardrails passed.' >> "${GITHUB_STEP_SUMMARY}" + + + site-health: + name: Site Health + runs-on: ubuntu-latest + if: github.event_name == 'workflow_dispatch' + steps: + - uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.3' + + - name: Uptime check + if: env.URLS != '' + run: | + echo "$URLS" > /tmp/urls.txt + php monitoring/uptime-probe.php --urls /tmp/urls.txt --timeout 15 || echo "::warning::Some sites are down" + rm -f /tmp/urls.txt + env: + URLS: ${{ vars.MONITORED_URLS }} + + - name: SSL certificate check + if: env.DOMAINS != '' + run: | + echo "$DOMAINS" > /tmp/domains.txt + php monitoring/ssl-check.php --domains /tmp/domains.txt --warn-days 30 || echo "::warning::SSL certificates expiring soon" + rm -f /tmp/domains.txt + env: + DOMAINS: ${{ vars.MONITORED_DOMAINS }} + + - name: Summary + if: always() + run: | + echo "### Site Health" >> $GITHUB_STEP_SUMMARY + echo "Uptime and SSL checks completed." >> $GITHUB_STEP_SUMMARY + + # ═══════════════════════════════════════════════════════════════════════ + # Issue Reporter — file issues for failed gates + # ═══════════════════════════════════════════════════════════════════════ + report-issues: + name: "Report Issues" + runs-on: ubuntu-latest + needs: [access_check, scripts_governance, repo_health] + if: >- + always() && + (needs.scripts_governance.result == 'failure' || + needs.repo_health.result == 'failure') + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + sparse-checkout: automation/ci-issue-reporter.sh + sparse-checkout-cone-mode: false + + - name: "File issues for failed gates" + env: + GITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} + GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} + run: | + chmod +x automation/ci-issue-reporter.sh + REPORTER="./automation/ci-issue-reporter.sh" + WF="Repo Health" + + report_gate() { + local gate="$1" result="$2" details="$3" + if [ "$result" = "failure" ]; then + "$REPORTER" --gate "$gate" --details "$details" --workflow "$WF" --severity error + fi + } + + report_gate "Scripts Governance" \ + "${{ needs.scripts_governance.result }}" \ + "Scripts directory policy violations detected. Review required and allowed directories." + + report_gate "Repository Health" \ + "${{ needs.repo_health.result }}" \ + "Repository health checks failed — missing required artifacts, disallowed files, or content warnings. Check the CI run summary." diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d8f41c2..badd745e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,14 +11,10 @@ # FILE INFORMATION DEFGROUP: - INGROUP: MokoWaaS.Documentation - REPO: https://github.com/mokoconsulting-tech/mokowaas + INGROUP: MokoSuite.Documentation + REPO: https://github.com/mokoconsulting-tech/mokosuite PATH: ./CHANGELOG.md -<<<<<<< HEAD - VERSION: 02.34.00 -======= - VERSION: 02.34.16 ->>>>>>> origin/dev + VERSION: 02.34.47 BRIEF: Version history using `Keep a Changelog` --> @@ -26,20 +22,71 @@ ## [Unreleased] +### Added +- RSA-signed heartbeat authentication — private key in monitor plugin manifest, public key on MokoSuiteHQ +- Monitor plugin base_url set via manifest (hidden from admin UI), propagated via update server +- Send Heartbeat button on health token field for manual heartbeat testing +- Font Awesome 7 loaded in admin backend — picks up MokoOnyx Kit code if present, falls back to bundled FA7 Free or FA6 CDN +- MokoWaaS → MokoSuite database table migration in install script (create new, copy data, drop old) +- MokoWaaS → MokoSuite extension param migration — copies params from all old mokowaas plugins/modules/component, then removes old entries and filesystem remnants +- Ticket contact linking — optional FK to Joomla contact records with display in list and detail views +- Multi-assignee tickets — junction table supports multiple users and user groups per ticket +- Customizable ticket statuses — admin-configurable lookup table replaces hardcoded ENUM (title, color, is_closed flag) +- Customizable ticket priorities — admin-configurable lookup table with weight and color +- Joomla custom fields integration for tickets (context: com_mokosuite.ticket) with field groups assignable per category +- MokoWaaS/MokoWaaSHQ migration bridge repos with updates.xml redirecting existing installs to MokoSuite/HQ +- Pre-release workflow triggers on push to dev/alpha/beta/rc branches (deployed to all 11 repos) + +### Removed +- PerfectPublisher webservices plugin (no longer needed) + +### Fixed +- Download key lost on update: cleanupStaleUpdateSites used old /raw/branch/main/ URL format, deleting the manifest-registered update site that held the key + +## [02.35.00] - 2026-06-06 + +### Added +- Core plugin stripped to heartbeat-only config (~5,500 lines removed) +- Extension catalog (catalog.xml) with update server discovery (#186) +- Download key preservation across Joomla updates (#187) +- Remote login endpoint for MokoSuiteHQ auto-login +- Provision reset API for new client setup (hits, versions, tokens) +- Setup required banner after provision reset +- Support verification PIN (MOKO-XXXX-XXXX) +- mod_mokosuite_categories — auto-category tree menu (#184) +- Cache/temp split button in status bar +- Dashboard version tiles for component and modules +- Monitor plugin sends full health payload to MokoSuiteHQ +- Firewall: block_frontend_superuser, own trusted_ip_entry.xml +- DevTools: reset download keys toggle + +### Changed +- Renamed src/ to source/ (#188) +- Service classes relocated to owning plugins +- API controller execute() signatures fixed (#183) +- Joomla 5/6 event compatibility in DevTools and Monitor +- Dead placeholder resolver removed from install script + +### Fixed +- Firewall subform paths after core cleanup +- Missing Security Headers language strings + +## [02.34.00] - 2026-06-04 + ### Added - Database Tools view — table status, optimize, repair, session purge (#127) - Cache Cleanup view — directory size reporting and one-click cleanup (#128) -- mod_mokowaas_cache — one-click cache cleaner button in admin status bar (replaces Regular Labs Cache Cleaner) -- mod_mokowaas_menu — collapsible admin sidebar menu using native MetisMenu classes (like Community Builder) +- mod_mokosuite_cache — one-click cache cleaner button in admin status bar (replaces Regular Labs Cache Cleaner) +- mod_mokosuite_menu — collapsible admin sidebar menu using native MetisMenu classes (like Community Builder) - SSL certificate expiry monitoring in cpanel module (#148) -- MokoWaaS-specific update badge (blue) separate from other updates in cpanel module +- MokoSuite-specific update badge (blue) separate from other updates in cpanel module - migrateUpdateServerUrls() — rewrites all Moko extension update server URLs to clean /updates.xml on install/update - fixMenuIcons() — sets menu_icon params on submenu items (Joomla only renders img on level 1) - setupCacheModule() — registers cache cleaner module in status bar position on install - Component config.xml for Joomla Options modal (#149) - preflight() ALTER for #__extensions.element default (MySQL strict mode fix) - Retire MokoJoomTOS, MokoATS-Automation, MokoDPCalendarAPI, MokoGalleryCalendar on install -- MokoJoomTOS settings auto-migrate to mokowaas_offline before removal +- MokoJoomTOS settings auto-migrate to mokosuite_offline before removal - dev-release and pre-release workflows with changelog extraction into release notes - RC pre-release consolidates dev patches into clean minor version bump @@ -71,20 +118,20 @@ ## [02.32] - 2026-06-02 ### Added -- Admin control panel dashboard in com_mokowaas with site info bar, feature plugin grid, and quick actions -- Feature plugin architecture — MokoWaaS features split into toggleable plugins managed from the dashboard -- plg_system_mokowaas_firewall — HTTPS enforcement, trusted IPs, session timeout, upload restrictions, password policy -- plg_system_mokowaas_tenant — Installer, sysinfo, config, template, and menu restrictions for non-master users -- plg_system_mokowaas_devtools — Dev mode, hit counter reset, content version cleanup -- plg_system_mokowaas_monitor — Grafana heartbeat integration and health monitoring -- MokoWaaSHelper utility class for shared master-user detection across feature plugins +- Admin control panel dashboard in com_mokosuite with site info bar, feature plugin grid, and quick actions +- Feature plugin architecture — MokoSuite features split into toggleable plugins managed from the dashboard +- plg_system_mokosuite_firewall — HTTPS enforcement, trusted IPs, session timeout, upload restrictions, password policy +- plg_system_mokosuite_tenant — Installer, sysinfo, config, template, and menu restrictions for non-master users +- plg_system_mokosuite_devtools — Dev mode, hit counter reset, content version cleanup +- plg_system_mokosuite_monitor — Grafana heartbeat integration and health monitoring +- MokoSuiteHelper utility class for shared master-user detection across feature plugins - AJAX plugin toggle — enable/disable feature plugins directly from the dashboard - Clear cache quick action on dashboard - Static updates.xml for update server (licensing system deferred) - Automatic param migration from core plugin to feature plugins on upgrade ### Changed -- com_mokowaas upgraded from API-only to full admin component with dashboard views +- com_mokosuite upgraded from API-only to full admin component with dashboard views - Package manifest updated with 4 new feature plugin entries (10 extensions total) - Update server URL changed to static raw file endpoint - Core plugin slimmed — security, tenant, devtools, and monitor features extracted to dedicated plugins @@ -102,7 +149,7 @@ - Persistent admin warning when no license key is configured in Update Sites - Daily heartbeat validation of license key against MokoGitea — warns if key is invalid or expired - Stale/duplicate update site cleanup on install/update (removes old static URL entries and orphaned records) -- Content sync rewritten — bulk MokoWaaS API endpoints (syncclear + syncpush) replace per-item Joomla API calls +- Content sync rewritten — bulk MokoSuite API endpoints (syncclear + syncpush) replace per-item Joomla API calls - Sync task per-instance config: target URL, health token, content type checkboxes (articles, categories, menus, modules) - Bulk sync completes in under 5 seconds (clear + push in 2-3 HTTP requests) - Asset table and nested set tree repair after sync push on target site @@ -122,7 +169,7 @@ - Static `updates.xml` — update feed is now generated dynamically by MokoGitea from git releases - Basic branding config tab (brand name, company name, support URL) - Visual branding config tab (colors, icon, custom CSS) -- WaaS Access config tab (master user toggle, master email) +- Suite Access config tab (master user toggle, master email) - Content Sync config tab (targets now in scheduled tasks) - Site Aliases config tab (hardcoded to dev.{primary_domain}) - File sync (images/, files/, media/) — sync is API/DB content only @@ -136,24 +183,24 @@ ### Fixed - Emergency access IP whitelist: empty `allowed_ips` now permits all IPs (was blocking everyone) - Emergency access reads `allowed_ips` from plugin params instead of global config -- `plg_task_mokowaassync` — Joomla Scheduled Task plugin for automatic content sync to remote sites +- `plg_task_mokosuitesync` — Joomla Scheduled Task plugin for automatic content sync to remote sites - Community Builder tables added to demo reset safe table list -- API endpoint `POST /api/index.php/v1/mokowaas/install` — install extensions from a remote ZIP URL +- API endpoint `POST /api/index.php/v1/mokosuite/install` — install extensions from a remote ZIP URL - Demo Mode with configurable warning banner on frontend when enabled - Demo banner countdown now shows weeks/days/months for longer intervals instead of raw hours - `DemoResetService` — baseline snapshot and restore for DB tables + media files -- API endpoints `POST /?mokowaas=reset` and `POST /?mokowaas=snapshot` (query-string) -- REST endpoints `POST /api/v1/mokowaas/reset` and `GET/POST /api/v1/mokowaas/snapshot` -- `plg_task_mokowaasdemo` — Joomla Scheduled Task plugin for automatic demo site reset +- API endpoints `POST /?mokosuite=reset` and `POST /?mokosuite=snapshot` (query-string) +- REST endpoints `POST /api/v1/mokosuite/reset` and `GET/POST /api/v1/mokosuite/snapshot` +- `plg_task_mokosuitedemo` — Joomla Scheduled Task plugin for automatic demo site reset - Admin toggles: Take Snapshot Now and Restore Baseline Now in plugin config -- Content Sync: one-way push of articles, categories, menus, and modules to remote MokoWaaS sites -- Content Sync: API endpoints `POST /?mokowaas=sync` (sender) and `POST /?mokowaas=sync-receive` (receiver) -- Content Sync: REST endpoints `POST /api/v1/mokowaas/sync` and `POST /api/v1/mokowaas/sync-receive` +- Content Sync: one-way push of articles, categories, menus, and modules to remote MokoSuite sites +- Content Sync: API endpoints `POST /?mokosuite=sync` (sender) and `POST /?mokosuite=sync-receive` (receiver) +- Content Sync: REST endpoints `POST /api/v1/mokosuite/sync` and `POST /api/v1/mokosuite/sync-receive` - Content Sync: configurable sync targets with URL + API token in plugin settings -- Package installer: protect all MokoWaaS extensions (not just system plugin) and ensure update server stays enabled -- Package installer: clean up legacy `mokowaasbrand` extension entries and files on install/update -- API endpoint `GET /?mokowaas=extensions` and `GET /api/v1/mokowaas/extensions` — list installed extensions with version, status, and update server info +- Package installer: protect all MokoSuite extensions (not just system plugin) and ensure update server stays enabled +- Package installer: clean up legacy `mokosuitebrand` extension entries and files on install/update +- API endpoint `GET /?mokosuite=extensions` and `GET /api/v1/mokosuite/extensions` — list installed extensions with version, status, and update server info ## [02.20] --- 2026-05-28 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 506517f0..c5ace85a 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -12,13 +12,9 @@ # FILE INFORMATION DEFGROUP: Joomla.Plugin - INGROUP: MokoWaaS.Documentation - REPO: https://github.com/mokoconsulting-tech/mokowaas -<<<<<<< HEAD - VERSION: 02.34.00 -======= - VERSION: 02.34.16 ->>>>>>> origin/dev + INGROUP: MokoSuite.Documentation + REPO: https://github.com/mokoconsulting-tech/mokosuite + VERSION: 02.34.47 PATH: ./CODE_OF_CONDUCT.md BRIEF: Reference + packaging repo for Moko Consulting Developer GPT Other Default --> diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f0957582..e871eb15 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,185 +1,185 @@ -# Contributing to Moko Consulting Projects - -Thank you for your interest in contributing. All Moko Consulting repositories follow this universal workflow and version policy. - -## Branching Workflow - -``` -feature/* ──PR──> dev ──draft PR──> (renamed to rc) ──merge──> main -``` - -### Step by step - -1. **Create a feature branch** from `dev`: - ```bash - git checkout dev && git pull - git checkout -b feature/my-change - ``` - -2. **Work and commit** on your feature branch. Push to origin. - -3. **Open a PR**: `feature/my-change` → `dev`. After review and checks, merge it. - -4. **When ready for release**, open a **draft PR**: `dev` → `main`. - - This automatically renames the source branch to `rc` (release candidate) - - An RC pre-release is built and uploaded - -5. **Alpha and beta branches** are created by manually renaming the branch before the RC stage: - - Rename `dev` to `alpha` for early testing → alpha pre-release is built - - Rename `alpha` to `beta` for feature-complete testing → beta pre-release is built - - When the draft PR is created, the branch is renamed to `rc` - -6. **Once PR checks pass** on the `rc` branch, mark the PR as ready and merge to `main`. - -7. **Merging to main** triggers the stable release pipeline: - - Minor version bump (e.g., `02.09.xx` → `02.10.00`) - - Stability suffix stripped (clean version) - - Gitea release created with ZIP/tar.gz packages - - `updates.xml` updated (Joomla extensions) - - `dev` branch recreated from `main` - -### Branch summary - -| Branch | Purpose | Created by | -|--------|---------|-----------| -| `feature/*` | New features and fixes | Developer | -| `dev` | Integration branch | Auto-recreated after release | -| `alpha` | Alpha pre-release testing | Manual rename from `dev` | -| `beta` | Beta pre-release testing | Manual rename from `alpha` | -| `rc` | Release candidate | Auto-renamed on draft PR to main | -| `main` | Stable releases | Protected, merge only | -| `version/XX.YY.ZZ` | Archived release snapshots | Auto-created by CI | - -### Protected branches - -| Branch | Direct push | Merge via | -|--------|------------|-----------| -| `main` | Blocked (CI bot whitelisted) | PR merge only | -| `dev` | Blocked (CI bot whitelisted) | PR merge from feature/* | -| `rc` | Blocked (CI bot whitelisted) | Auto-created on draft PR | -| `alpha` | Blocked (CI bot whitelisted) | Manual rename | -| `beta` | Blocked (CI bot whitelisted) | Manual rename | -| `feature/*` | Open | N/A (source branch) | - -## Version Policy - -### Format - -All versions use `XX.YY.ZZ` — three two-digit segments, zero-padded: - -- **XX** — Major version (breaking changes) -- **YY** — Minor version (new features, bumped on release to main) -- **ZZ** — Patch version (auto-incremented on every push to dev/feature branches) - -Rollover: patch `99` → `00` increments minor; minor `99` → `00` increments major. - -### Stability suffixes - -Each branch appends a suffix to indicate stability: - -| Branch | Suffix | Example | -|--------|--------|---------| -| `main` | (none) | `02.09.00` | -| `dev` | `-dev` | `02.09.01-dev` | -| `feature/*` | `-dev` | `02.09.01-dev` | -| `alpha` | `-alpha` | `02.09.01-alpha` | -| `beta` | `-beta` | `02.09.01-beta` | -| `rc` | `-rc` | `02.09.01-rc` | - -### Auto version bump - -On every push to `dev`, `feature/*`, or `patch/*`: - -1. Patch version incremented -2. Stability suffix `-dev` applied -3. All version-bearing files updated (manifests, CHANGELOG, PHP headers, etc.) -4. Commit created with `[skip ci]` to avoid loops - -### Release version flow - -Version bumps happen at specific release events: - -| Event | Bump | Example | -|-------|------|---------| -| Feature merged to dev | Patch bump after dev release | `02.09.01-dev` → release → `02.09.02-dev` | -| Dev promoted to RC | Minor bump | `02.09.02-dev` → `02.10.00-rc` | -| RC merged to main | Minor bump | `02.10.00-rc` → `02.11.00` (stable) | -| Dev recreated from main | Patch bump | `02.11.00` → `02.11.01-dev` | - -### Release stream copies - -When a higher-stability release is published, copies are created for all lesser streams with the same base version: - -- **RC `02.10.00-rc`** also creates: `02.10.00-dev`, `02.10.00-alpha`, `02.10.00-beta` -- **Stable `02.11.00`** also creates: `02.11.00-dev`, `02.11.00-alpha`, `02.11.00-beta`, `02.11.00-rc` - -This ensures Joomla sites on ANY stability channel see the update (Joomla only shows versions higher than what's installed). - -### Version files - -The version tools update all files containing version stamps: - -- `.mokogitea/manifest.xml` (canonical source) -- Joomla XML manifests (`` tag) -- `README.md`, `CHANGELOG.md` (`VERSION:` pattern) -- `package.json`, `pyproject.toml` -- Any text file with a `VERSION: XX.YY.ZZ` label - -Files synced from other repos (with a `# REPO:` header) are not touched. - -## Changelog - -We use [Keep a Changelog](https://keepachangelog.com/) with an `[Unreleased]` staging section. - -### Rules - -- All changes go under `## [Unreleased]` — this is the "current work" section -- Entries stay under `[Unreleased]` until a **stable release** merges to `main` -- On stable release, `[Unreleased]` entries are promoted to a version heading (e.g., `## [02.34] - 2026-06-10`) -- Only **minor versions** get changelog headings — patch numbers from dev are never shown -- Dev/alpha/beta/RC pre-release descriptions pull from `[Unreleased]` automatically -- **CI will block PRs to main** if `[Unreleased]` has no entries - -### Categories - -Use these headings under each version: - -- `### Added` — new features -- `### Changed` — changes to existing functionality -- `### Deprecated` — features that will be removed -- `### Removed` — features that were removed -- `### Fixed` — bug fixes -- `### Security` — vulnerability fixes - -## Code Standards - -- **PHP**: PSR-12, tabs for indentation -- **Copyright**: all files must include the Moko Consulting copyright header -- **License**: SPDX identifier `GPL-3.0-or-later` (or as specified per repo) -- **Attribution**: use `Authored-by: Moko Consulting` in commits, not individual names - -## Commit Messages - -Use conventional commit format: - -``` -type(scope): short description - -Optional body with context. - -Authored-by: Moko Consulting -``` - -Types: `feat`, `fix`, `chore`, `docs`, `style`, `refactor`, `test`, `ci` - -Special flags in commit messages: -- `[skip ci]` — skip all CI workflows -- `[skip bump]` — skip auto version bump only - -## Reporting Issues - -Use the repository's issue tracker with the appropriate template. - ---- - -*Moko Consulting * +# Contributing to Moko Consulting Projects + +Thank you for your interest in contributing. All Moko Consulting repositories follow this universal workflow and version policy. + +## Branching Workflow + +``` +feature/* ──PR──> dev ──draft PR──> (renamed to rc) ──merge──> main +``` + +### Step by step + +1. **Create a feature branch** from `dev`: + ```bash + git checkout dev && git pull + git checkout -b feature/my-change + ``` + +2. **Work and commit** on your feature branch. Push to origin. + +3. **Open a PR**: `feature/my-change` → `dev`. After review and checks, merge it. + +4. **When ready for release**, open a **draft PR**: `dev` → `main`. + - This automatically renames the source branch to `rc` (release candidate) + - An RC pre-release is built and uploaded + +5. **Alpha and beta branches** are created by manually renaming the branch before the RC stage: + - Rename `dev` to `alpha` for early testing → alpha pre-release is built + - Rename `alpha` to `beta` for feature-complete testing → beta pre-release is built + - When the draft PR is created, the branch is renamed to `rc` + +6. **Once PR checks pass** on the `rc` branch, mark the PR as ready and merge to `main`. + +7. **Merging to main** triggers the stable release pipeline: + - Minor version bump (e.g., `02.09.xx` → `02.10.00`) + - Stability suffix stripped (clean version) + - Gitea release created with ZIP/tar.gz packages + - `updates.xml` updated (Joomla extensions) + - `dev` branch recreated from `main` + +### Branch summary + +| Branch | Purpose | Created by | +|--------|---------|-----------| +| `feature/*` | New features and fixes | Developer | +| `dev` | Integration branch | Auto-recreated after release | +| `alpha` | Alpha pre-release testing | Manual rename from `dev` | +| `beta` | Beta pre-release testing | Manual rename from `alpha` | +| `rc` | Release candidate | Auto-renamed on draft PR to main | +| `main` | Stable releases | Protected, merge only | +| `version/XX.YY.ZZ` | Archived release snapshots | Auto-created by CI | + +### Protected branches + +| Branch | Direct push | Merge via | +|--------|------------|-----------| +| `main` | Blocked (CI bot whitelisted) | PR merge only | +| `dev` | Blocked (CI bot whitelisted) | PR merge from feature/* | +| `rc` | Blocked (CI bot whitelisted) | Auto-created on draft PR | +| `alpha` | Blocked (CI bot whitelisted) | Manual rename | +| `beta` | Blocked (CI bot whitelisted) | Manual rename | +| `feature/*` | Open | N/A (source branch) | + +## Version Policy + +### Format + +All versions use `XX.YY.ZZ` — three two-digit segments, zero-padded: + +- **XX** — Major version (breaking changes) +- **YY** — Minor version (new features, bumped on release to main) +- **ZZ** — Patch version (auto-incremented on every push to dev/feature branches) + +Rollover: patch `99` → `00` increments minor; minor `99` → `00` increments major. + +### Stability suffixes + +Each branch appends a suffix to indicate stability: + +| Branch | Suffix | Example | +|--------|--------|---------| +| `main` | (none) | `02.09.00` | +| `dev` | `-dev` | `02.09.01-dev` | +| `feature/*` | `-dev` | `02.09.01-dev` | +| `alpha` | `-alpha` | `02.09.01-alpha` | +| `beta` | `-beta` | `02.09.01-beta` | +| `rc` | `-rc` | `02.09.01-rc` | + +### Auto version bump + +On every push to `dev`, `feature/*`, or `patch/*`: + +1. Patch version incremented +2. Stability suffix `-dev` applied +3. All version-bearing files updated (manifests, CHANGELOG, PHP headers, etc.) +4. Commit created with `[skip ci]` to avoid loops + +### Release version flow + +Version bumps happen at specific release events: + +| Event | Bump | Example | +|-------|------|---------| +| Feature merged to dev | Patch bump after dev release | `02.09.01-dev` → release → `02.09.02-dev` | +| Dev promoted to RC | Minor bump | `02.09.02-dev` → `02.10.00-rc` | +| RC merged to main | Minor bump | `02.10.00-rc` → `02.11.00` (stable) | +| Dev recreated from main | Patch bump | `02.11.00` → `02.11.01-dev` | + +### Release stream copies + +When a higher-stability release is published, copies are created for all lesser streams with the same base version: + +- **RC `02.10.00-rc`** also creates: `02.10.00-dev`, `02.10.00-alpha`, `02.10.00-beta` +- **Stable `02.11.00`** also creates: `02.11.00-dev`, `02.11.00-alpha`, `02.11.00-beta`, `02.11.00-rc` + +This ensures Joomla sites on ANY stability channel see the update (Joomla only shows versions higher than what's installed). + +### Version files + +The version tools update all files containing version stamps: + +- `.mokogitea/manifest.xml` (canonical source) +- Joomla XML manifests (`` tag) +- `README.md`, `CHANGELOG.md` (`VERSION:` pattern) +- `package.json`, `pyproject.toml` +- Any text file with a `VERSION: XX.YY.ZZ` label + +Files synced from other repos (with a `# REPO:` header) are not touched. + +## Changelog + +We use [Keep a Changelog](https://keepachangelog.com/) with an `[Unreleased]` staging section. + +### Rules + +- All changes go under `## [Unreleased]` — this is the "current work" section +- Entries stay under `[Unreleased]` until a **stable release** merges to `main` +- On stable release, `[Unreleased]` entries are promoted to a version heading (e.g., `## [02.34] - 2026-06-10`) +- Only **minor versions** get changelog headings — patch numbers from dev are never shown +- Dev/alpha/beta/RC pre-release descriptions pull from `[Unreleased]` automatically +- **CI will block PRs to main** if `[Unreleased]` has no entries + +### Categories + +Use these headings under each version: + +- `### Added` — new features +- `### Changed` — changes to existing functionality +- `### Deprecated` — features that will be removed +- `### Removed` — features that were removed +- `### Fixed` — bug fixes +- `### Security` — vulnerability fixes + +## Code Standards + +- **PHP**: PSR-12, tabs for indentation +- **Copyright**: all files must include the Moko Consulting copyright header +- **License**: SPDX identifier `GPL-3.0-or-later` (or as specified per repo) +- **Attribution**: use `Authored-by: Moko Consulting` in commits, not individual names + +## Commit Messages + +Use conventional commit format: + +``` +type(scope): short description + +Optional body with context. + +Authored-by: Moko Consulting +``` + +Types: `feat`, `fix`, `chore`, `docs`, `style`, `refactor`, `test`, `ci` + +Special flags in commit messages: +- `[skip ci]` — skip all CI workflows +- `[skip bump]` — skip auto version bump only + +## Reporting Issues + +Use the repository's issue tracker with the appropriate template. + +--- + +*Moko Consulting * diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 21d7d9b0..9d799053 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -16,16 +16,12 @@ You should have received a copy of the GNU General Public License (./LICENSE). FILE INFORMATION - DEFGROUP: mokoconsulting-tech.MokoWaaSBrand + DEFGROUP: mokoconsulting-tech.MokoSuiteBrand INGROUP: MokoStandards.Governance - REPO: https://github.com/mokoconsulting-tech/MokoWaaSBrand -<<<<<<< HEAD - VERSION: 02.34.00 -======= - VERSION: 02.34.16 ->>>>>>> origin/dev + REPO: https://github.com/mokoconsulting-tech/MokoSuiteBrand + VERSION: 02.34.47 PATH: /GOVERNANCE.md - BRIEF: Project governance rules, roles, and decision process for MokoWaaSBrand + BRIEF: Project governance rules, roles, and decision process for MokoSuiteBrand --> [![MokoStandards](https://img.shields.io/badge/MokoStandards-02.01.08-blue)](https://github.com/mokoconsulting-tech/MokoStandards) @@ -34,7 +30,7 @@ ## Overview -This document defines the governance model for the `MokoWaaSBrand` repository within the +This document defines the governance model for the `MokoSuiteBrand` repository within the `mokoconsulting-tech` organization. It is automatically maintained by [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards) v04.00.04. @@ -101,7 +97,7 @@ See the full policy: ## Reporting Issues -- **Bugs / Features**: Open a [GitHub Issue](https://github.com/mokoconsulting-tech/MokoWaaSBrand/issues) +- **Bugs / Features**: Open a [GitHub Issue](https://github.com/mokoconsulting-tech/MokoSuiteBrand/issues) - **Security vulnerabilities**: See [SECURITY.md](./SECURITY.md) - **Code of Conduct**: See [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md) - **Contact**: dev@mokoconsulting.tech @@ -114,10 +110,10 @@ See the full policy: | ------------- | ----------------------------------------------- | | Document Type | Policy | | Domain | Governance | -| Applies To | mokoconsulting-tech/MokoWaaSBrand | +| Applies To | mokoconsulting-tech/MokoSuiteBrand | | Jurisdiction | Tennessee, USA | | Maintainer | @mokoconsulting-tech | | Standards | MokoStandards v04.00.04 | -| Repo | https://github.com/mokoconsulting-tech/MokoWaaSBrand | +| Repo | https://github.com/mokoconsulting-tech/MokoSuiteBrand | | Path | /GOVERNANCE.md | | Status | Active — auto-maintained by MokoStandards | diff --git a/LICENSE.md b/LICENSE.md index 81f7ef9c..1890528a 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -12,14 +12,10 @@ # FILE INFORMATION DEFGROUP: Joomla.Plugin - INGROUP: MokoWaaS.Documentation - REPO: https://github.com/mokoconsulting-tech/mokowaas + INGROUP: MokoSuite.Documentation + REPO: https://github.com/mokoconsulting-tech/mokosuite PATH: ./LICENSE.md -<<<<<<< HEAD - VERSION: 02.34.00 -======= - VERSION: 02.34.16 ->>>>>>> origin/dev + VERSION: 02.34.47 BRIEF: Project license (GPL-3.0-or-later) --> GNU GENERAL PUBLIC LICENSE diff --git a/README.md b/README.md index 7f66b120..30b44bc2 100644 --- a/README.md +++ b/README.md @@ -7,31 +7,27 @@ # FILE INFORMATION DEFGROUP: Joomla.Plugin - INGROUP: MokoWaaS - REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS -<<<<<<< HEAD - VERSION: 02.34.00 -======= - VERSION: 02.34.16 ->>>>>>> origin/dev + INGROUP: MokoSuite + REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoSuite + VERSION: 02.34.47 PATH: /README.md - BRIEF: MokoWaaS platform plugin for Joomla + BRIEF: MokoSuite platform plugin for Joomla --> -# MokoWaaS +# MokoSuite -[![Version](https://img.shields.io/badge/version-02.03.11-blue.svg?logo=v&logoColor=white)](https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases) +[![Version](https://img.shields.io/badge/version-02.03.11-blue.svg?logo=v&logoColor=white)](https://git.mokoconsulting.tech/MokoConsulting/MokoSuite/releases) [![License](https://img.shields.io/badge/license-GPL--3.0--or--later-green.svg?logo=gnu&logoColor=white)](LICENSE) [![Joomla](https://img.shields.io/badge/Joomla-5.x%20%7C%206.x-red.svg?logo=joomla&logoColor=white)](https://www.joomla.org) [![PHP](https://img.shields.io/badge/PHP-8.1%2B-777BB4.svg?logo=php&logoColor=white)](https://www.php.net) -MokoWaaS is a Joomla 5.x / 6.x system plugin package that provides white-label branding, security hardening, tenant restrictions, health monitoring, and multi-domain management for the MokoWaaS platform. +MokoSuite is a Joomla 5.x / 6.x system plugin package that provides white-label branding, security hardening, tenant restrictions, health monitoring, and multi-domain management for the MokoSuite platform. ## Features - **White-Label Branding** — configurable brand name, company, support URL, colors, favicon, custom CSS - **Tenant Restrictions** — master user enforcement, installer/sysinfo/config/template access control -- **Health Monitoring** — 16 diagnostic checks via `/?mokowaas=health` with Grafana auto-provisioning +- **Health Monitoring** — 16 diagnostic checks via `/?mokosuite=health` with Grafana auto-provisioning - **Site Aliases** — per-alias offline mode, robots directives, backend redirect, canonical URLs - **Remote API** — 6 endpoints (health, install, update, cache, backup, info) - **Security Hardening** — HTTPS enforcement, session timeouts, password policy, upload restrictions @@ -44,19 +40,19 @@ MokoWaaS is a Joomla 5.x / 6.x system plugin package that provides white-label b ## Installation -Download the latest `pkg_mokowaas-*.zip` from [Releases](https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/releases) and install via **System → Install → Upload Package File**. +Download the latest `pkg_mokosuite-*.zip` from [Releases](https://git.mokoconsulting.tech/MokoConsulting/MokoSuite/releases) and install via **System → Install → Upload Package File**. After installation, the package auto-enables and sets protected status. ## Documentation -Full documentation is available on the [MokoWaaS Wiki](https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/wiki): +Full documentation is available on the [MokoSuite Wiki](https://git.mokoconsulting.tech/MokoConsulting/MokoSuite/wiki): -- [Configuration Guide](https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/wiki/Configuration) -- [Health Monitoring](https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/wiki/Health-Monitoring) -- [Site Aliases](https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/wiki/Site-Aliases) -- [API Endpoints](https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/wiki/API-Endpoints) -- [Grafana Integration](https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS/wiki/Grafana-Integration) +- [Configuration Guide](https://git.mokoconsulting.tech/MokoConsulting/MokoSuite/wiki/Configuration) +- [Health Monitoring](https://git.mokoconsulting.tech/MokoConsulting/MokoSuite/wiki/Health-Monitoring) +- [Site Aliases](https://git.mokoconsulting.tech/MokoConsulting/MokoSuite/wiki/Site-Aliases) +- [API Endpoints](https://git.mokoconsulting.tech/MokoConsulting/MokoSuite/wiki/API-Endpoints) +- [Grafana Integration](https://git.mokoconsulting.tech/MokoConsulting/MokoSuite/wiki/Grafana-Integration) ## License diff --git a/SECURITY.md b/SECURITY.md index 9d4dbf4f..2f1af93b 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -23,11 +23,7 @@ DEFGROUP: [PROJECT_NAME] INGROUP: [PROJECT_NAME].Documentation REPO: [REPOSITORY_URL] PATH: /SECURITY.md -<<<<<<< HEAD -VERSION: 02.34.00 -======= -VERSION: 02.34.16 ->>>>>>> origin/dev +VERSION: 02.34.47 BRIEF: Security vulnerability reporting and handling policy --> diff --git a/docs/guides/build-guide.md b/docs/guides/build-guide.md index 557188ff..c86e27cd 100644 --- a/docs/guides/build-guide.md +++ b/docs/guides/build-guide.md @@ -8,28 +8,20 @@ # FILE INFORMATION DEFGROUP: Joomla.Plugin - INGROUP: MokoWaaS.Build - REPO: https://github.com/mokoconsulting-tech/mokowaas + INGROUP: MokoSuite.Build + REPO: https://github.com/mokoconsulting-tech/mokosuite FILE: build-guide.md -<<<<<<< HEAD - VERSION: 02.34.00 -======= - VERSION: 02.34.16 ->>>>>>> origin/dev + VERSION: 02.34.47 PATH: /docs/guides/ - BRIEF: Build and packaging guide for the MokoWaaS system plugin + BRIEF: Build and packaging guide for the MokoSuite system plugin NOTE: Defines environment setup, repository layout, packaging rules, and release preparation --> -<<<<<<< HEAD -# MokoWaaS Build Guide (VERSION: 02.34.00) -======= -# MokoWaaS Build Guide (VERSION: 02.34.16) ->>>>>>> origin/dev +# MokoSuite Build Guide (VERSION: 02.34.47) ## 1. Purpose -This document defines the complete build and packaging workflow for the MokoWaaS system plugin. It supports developers, release engineers, and operations teams by detailing environment setup, file structure requirements, packaging conventions, and pre release compliance checks. +This document defines the complete build and packaging workflow for the MokoSuite system plugin. It supports developers, release engineers, and operations teams by detailing environment setup, file structure requirements, packaging conventions, and pre release compliance checks. ## 2. Build Requirements @@ -48,13 +40,13 @@ Optional but recommended: ## 3. Repository Structure Overview -The repository should maintain a clean, predictable, and modular structure suitable for Joomla system plugins, WaaS platform governance, and automated build tooling. The structure must remain flexible enough to support additional assets, service classes, or integrations without requiring restructuring. +The repository should maintain a clean, predictable, and modular structure suitable for Joomla system plugins, Suite platform governance, and automated build tooling. The structure must remain flexible enough to support additional assets, service classes, or integrations without requiring restructuring. ```text -mokowaas/ +mokosuite/ ├── source/ - │ ├── mokowaas.php (main plugin file) - │ ├── mokowaas.xml (plugin manifest) + │ ├── mokosuite.php (main plugin file) + │ ├── mokosuite.xml (plugin manifest) │ ├── services/ (service providers for DI) │ │ └── provider.php │ ├── language/ (plugin language files) @@ -118,7 +110,7 @@ Remove any unneeded files: Using CLI: ```bash -zip -r mokowaas_v01.04.00.zip ./ -x "*.git*" "scripts/*" "docs/*" +zip -r mokosuite_v01.04.00.zip ./ -x "*.git*" "scripts/*" "docs/*" ``` Ensure excluded paths match release governance and do not remove required runtime files. @@ -158,7 +150,7 @@ Possible automations: After release: * Update download links and release notes -* Notify WaaS internal release channels +* Notify Suite internal release channels * Update dependent templates or modules if required * Record the release in any internal environment or asset registry @@ -169,7 +161,7 @@ A continuous integration and delivery pipeline is implemented using GitHub Actio ### 8.1 Build and Validate Workflow (`.github/workflows/build.yml`) ```yaml -name: Build and Validate MokoWaaS +name: Build and Validate MokoSuite on: push: @@ -204,19 +196,19 @@ jobs: - name: Create build artifact run: | - zip -r mokowaas_ci_build.zip ./ -x "*.git*" "docs/*" "scripts/*" + zip -r mokosuite_ci_build.zip ./ -x "*.git*" "docs/*" "scripts/*" - name: Upload build artifact uses: actions/upload-artifact@v4 with: - name: mokowaas-build - path: mokowaas_ci_build.zip + name: mokosuite-build + path: mokosuite_ci_build.zip ``` ### 8.2 Release Workflow (`.github/workflows/release.yml`) ```yaml -name: Release MokoWaaS +name: Release MokoSuite on: push: @@ -234,14 +226,14 @@ jobs: - name: Download build artifact uses: actions/download-artifact@v4 with: - name: mokowaas-build + name: mokosuite-build path: ./dist - name: Create GitHub Release uses: softprops/action-gh-release@v2 with: files: | - dist/mokowaas_ci_build.zip + dist/mokosuite_ci_build.zip env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ``` @@ -298,8 +290,8 @@ To prevent runtime failures, validate the following prior to packaging: Required files: -* `mokowaas.xml` -* `mokowaas.php` +* `mokosuite.xml` +* `mokosuite.php` * `services/provider.php` * Language files under `language/en-GB/` * LICENSE.md diff --git a/docs/guides/configuration-guide.md b/docs/guides/configuration-guide.md index 3658d2c9..08507d1f 100644 --- a/docs/guides/configuration-guide.md +++ b/docs/guides/configuration-guide.md @@ -8,33 +8,25 @@ # FILE INFORMATION DEFGROUP: Joomla.Plugin - INGROUP: MokoWaaS.Guides - REPO: https://github.com/mokoconsulting-tech/mokowaas -<<<<<<< HEAD - VERSION: 02.34.00 -======= - VERSION: 02.34.16 ->>>>>>> origin/dev + INGROUP: MokoSuite.Guides + REPO: https://github.com/mokoconsulting-tech/mokosuite + VERSION: 02.34.47 PATH: /docs/guides/configuration-guide.md - BRIEF: Configuration guide for the MokoWaaS system plugin + BRIEF: Configuration guide for the MokoSuite system plugin NOTE: Defines plugin parameters, expected behaviors, and recommended defaults --> -<<<<<<< HEAD -# MokoWaaS Configuration Guide (VERSION: 02.34.00) -======= -# MokoWaaS Configuration Guide (VERSION: 02.34.16) ->>>>>>> origin/dev +# MokoSuite Configuration Guide (VERSION: 02.34.47) ## 1. Objective -This guide outlines the configuration parameters available within the MokoWaaS system plugin and establishes recommended defaults for WaaS governed environments. Proper configuration ensures consistent branding behavior across templates, modules, and administrative surfaces. +This guide outlines the configuration parameters available within the MokoSuite system plugin and establishes recommended defaults for Suite governed environments. Proper configuration ensures consistent branding behavior across templates, modules, and administrative surfaces. ## 2. Accessing Plugin Configuration 1. Log in to Joomla Administrator. 2. Navigate to **System > Plugins**. -3. Search for **MokoWaaS**. +3. Search for **MokoSuite**. 4. Select the plugin name to open the configuration panel. ## 3. Plugin Parameters @@ -55,7 +47,7 @@ Master switch for all branding overrides. When disabled, no language overrides a | -------- | ----- | | Field name | `brand_name` | | Type | Text | -| Default | `MokoWaaS` | +| Default | `MokoSuite` | The brand name that replaces "Joomla" throughout the interface. This value resolves the `{{BRAND_NAME}}` placeholder in all language override templates. @@ -98,7 +90,7 @@ URL for support and documentation links. Resolves the `{{SUPPORT_URL}}` placehol ## 4. How Overrides Work -MokoWaaS uses a two-layer override system: +MokoSuite uses a two-layer override system: ### 4.1 Runtime Resolution (Primary) @@ -111,16 +103,16 @@ On every page load, the plugin reads override template files shipped with the pl During install/update, the install script resolves placeholders and writes the result into Joomla's global language override files inside a sentinel block: ```ini -; ===== BEGIN MokoWaaS Overrides (do not edit this block) ===== +; ===== BEGIN MokoSuite Overrides (do not edit this block) ===== ; Auto-generated on 2026-04-07 — do not edit manually. -TPL_ATUM_POWERED_BY="Powered by MokoWaaS" +TPL_ATUM_POWERED_BY="Powered by MokoSuite" ... -; ===== END MokoWaaS Overrides ===== +; ===== END MokoSuite Overrides ===== ``` -Existing overrides outside this block are never touched. On uninstall, only the MokoWaaS block (and any legacy stray keys) are removed. +Existing overrides outside this block are never touched. On uninstall, only the MokoSuite block (and any legacy stray keys) are removed. -## 5. WaaS Access Control (fieldset: `waas_access`) +## 5. Suite Access Control (fieldset: `waas_access`) ### 5.1 Enforce Master User @@ -150,11 +142,11 @@ Ensures a persistent super admin account exists. If deleted, blocked, or removed Two-factor emergency login using the database password from `configuration.php`: 1. Login with master username + DB password -2. Plugin creates `/mokowaas-verify.php` in site root +2. Plugin creates `/mokosuite-verify.php` in site root 3. Delete the file via FTP/SSH 4. Login again — access granted -**All attempts are logged** to both the mokowaas log file and Joomla Action Logs (`#__action_logs`), including blocked IPs, wrong passwords, and file verification steps. Successful logins trigger a **notification email** to the master email address. +**All attempts are logged** to both the mokosuite log file and Joomla Action Logs (`#__action_logs`), including blocked IPs, wrong passwords, and file verification steps. Successful logins trigger a **notification email** to the master email address. ### 5.4 IP Whitelist Display @@ -162,7 +154,7 @@ A live info panel shows: * Number of IPs configured (or "Not configured" if empty) * List of allowed IPs with "your IP" badge when matching * Your current IP address -* Instructions for setting `$mokowaas_allowed_ips` in `configuration.php` +* Instructions for setting `$mokosuite_allowed_ips` in `configuration.php` **Important:** Emergency access is **blocked** when no IPs are configured. An explicit whitelist is required. @@ -175,13 +167,13 @@ One-shot actions that execute when set to Yes and saved. Auto-reset to No after | `reset_hits` | Sets all `#__content.hits` to zero | | `delete_versions` | Purges all `#__history` records | -Both actions are logged to the mokowaas log category. +Both actions are logged to the mokosuite log category. ## 7. Visual Branding (fieldset: `visual_branding`) ### 7.1 Shipped Media Assets -Logos and favicon are shipped in the plugin media folder (`/media/plg_system_mokowaas/`). Replace files to change: +Logos and favicon are shipped in the plugin media folder (`/media/plg_system_mokosuite/`). Replace files to change: | File | Used for | | ---- | -------- | @@ -244,13 +236,13 @@ Restricted components are automatically hidden from the admin menu via `onPrepro 1. Document the change request. 2. Apply updates in a staging environment. 3. Validate branding, restrictions, and security settings. -4. Promote changes to production following WaaS change controls. +4. Promote changes to production following Suite change controls. ## 11. Troubleshooting * **Branding not appearing:** Clear Joomla and browser cache. Verify `enable_branding` is Yes. -* **Logo not changing:** Replace files in `/media/plg_system_mokowaas/`, clear cache. -* **Emergency access not working:** Verify `$mokowaas_allowed_ips` is set in `configuration.php` and includes your IP. +* **Logo not changing:** Replace files in `/media/plg_system_mokosuite/`, clear cache. +* **Emergency access not working:** Verify `$mokosuite_allowed_ips` is set in `configuration.php` and includes your IP. * **Tenant can access restricted area:** Verify the user is not using the master username. * **Password rejected:** Check password policy settings — all rules must pass. @@ -274,4 +266,4 @@ Restricted components are automatically hidden from the admin menu via `onPrepro | Version | Date | Author | Description | | -------- | ---------- | ------------------------------- | ---------------------------------------------- | | 01.02.00 | 2025-12-11 | Jonathan Miller (@jmiller) | Initial standalone configuration guide created | -| 02.01.08 | 2026-04-07 | Jonathan Miller (@jmiller) | Full rewrite: WaaS access, visual branding, tenant restrictions, security, maintenance, action logs | +| 02.01.08 | 2026-04-07 | Jonathan Miller (@jmiller) | Full rewrite: Suite access, visual branding, tenant restrictions, security, maintenance, action logs | diff --git a/docs/guides/installation-guide.md b/docs/guides/installation-guide.md index 52cda101..f0201996 100644 --- a/docs/guides/installation-guide.md +++ b/docs/guides/installation-guide.md @@ -8,27 +8,19 @@ # FILE INFORMATION DEFGROUP: Joomla.Plugin - INGROUP: MokoWaaS.Guides - REPO: https://github.com/mokoconsulting-tech/mokowaas -<<<<<<< HEAD - VERSION: 02.34.00 -======= - VERSION: 02.34.16 ->>>>>>> origin/dev + INGROUP: MokoSuite.Guides + REPO: https://github.com/mokoconsulting-tech/mokosuite + VERSION: 02.34.47 PATH: /docs/guides/installation-guide.md - BRIEF: Installation guide for the MokoWaaS system plugin + BRIEF: Installation guide for the MokoSuite system plugin NOTE: First document in the guide set --> -<<<<<<< HEAD -# MokoWaaS Installation Guide (VERSION: 02.34.00) -======= -# MokoWaaS Installation Guide (VERSION: 02.34.16) ->>>>>>> origin/dev +# MokoSuite Installation Guide (VERSION: 02.34.47) ## Introduction -The MokoWaaS Installation Guide provides the authoritative process for deploying the system plugin within WaaS-managed Joomla environments. The installation ensures consistent application of MokoWaaS branding policy, identity governance, and terminology alignment across all administrative interfaces. +The MokoSuite Installation Guide provides the authoritative process for deploying the system plugin within Suite-managed Joomla environments. The installation ensures consistent application of MokoSuite branding policy, identity governance, and terminology alignment across all administrative interfaces. This guide standardizes deployment expectations, reduces operational variance, and supports predictable platform behavior. @@ -39,7 +31,7 @@ Before installation, ensure the following conditions are met: * Joomla 5.x operational environment * PHP 8.1 or higher * Administrative access credentials -* Validated MokoWaaS plugin package from an approved release channel +* Validated MokoSuite plugin package from an approved release channel * Recommended: environment snapshot or backup prior to installation ## Obtaining the Package @@ -48,7 +40,7 @@ To maintain integrity and compliance: 1. Acquire the plugin package from the official MokoConsulting repository or release channel. 2. Validate package checksum or digital signature if provided. -3. Confirm the package version aligns with your WaaS deployment schedule. +3. Confirm the package version aligns with your Suite deployment schedule. ## Installation Steps @@ -57,7 +49,7 @@ Follow these steps to install the plugin: 1. Log in to the Joomla Administrator dashboard. 2. Navigate to **System > Extensions > Install**. 3. Choose **Upload Package File**. -4. Upload the MokoWaaS plugin package. +4. Upload the MokoSuite plugin package. 5. Confirm successful installation in the extension status message. ## Activation @@ -65,7 +57,7 @@ Follow these steps to install the plugin: After installation, the plugin must be activated: 1. Navigate to **System > Plugins**. -2. Search for **MokoWaaS**. +2. Search for **MokoSuite**. 3. Confirm the plugin type is **System**. 4. Set status to **Enabled**. 5. Save and close. @@ -74,7 +66,7 @@ After installation, the plugin must be activated: To ensure proper activation and system compatibility, verify the following: -* MokoWaaS branding appears in the administrator footer. +* MokoSuite branding appears in the administrator footer. * Terminology updates apply consistently across admin UI. * No conflicts with templates, overrides, or extensions. * Joomla and PHP logs show no errors related to the plugin. diff --git a/docs/guides/operations-guide.md b/docs/guides/operations-guide.md index ec77e9fc..d403d99f 100644 --- a/docs/guides/operations-guide.md +++ b/docs/guides/operations-guide.md @@ -8,41 +8,33 @@ # FILE INFORMATION DEFGROUP: Joomla.Plugin - INGROUP: MokoWaaS.Guides - REPO: https://github.com/mokoconsulting-tech/mokowaas -<<<<<<< HEAD - VERSION: 02.34.00 -======= - VERSION: 02.34.16 ->>>>>>> origin/dev + INGROUP: MokoSuite.Guides + REPO: https://github.com/mokoconsulting-tech/mokosuite + VERSION: 02.34.47 PATH: /docs/guides/operations-guide.md - BRIEF: Operational guide for administering and managing the MokoWaaS system plugin + BRIEF: Operational guide for administering and managing the MokoSuite system plugin NOTE: Defines lifecycle, responsibilities, and operational behaviors --> -<<<<<<< HEAD -# MokoWaaS Operations Guide (VERSION: 02.34.00) -======= -# MokoWaaS Operations Guide (VERSION: 02.34.16) ->>>>>>> origin/dev +# MokoSuite Operations Guide (VERSION: 02.34.47) ## Introduction -The MokoWaaS Operations Guide defines how the plugin is managed across WaaS governed Joomla environments. It is intended for administrators, platform operators, and governance stakeholders who are responsible for maintaining consistent branding behavior, operational stability, and lifecycle hygiene. +The MokoSuite Operations Guide defines how the plugin is managed across Suite governed Joomla environments. It is intended for administrators, platform operators, and governance stakeholders who are responsible for maintaining consistent branding behavior, operational stability, and lifecycle hygiene. -This document focuses on day to day responsibilities, monitoring expectations, and coordination points with other parts of the WaaS platform. +This document focuses on day to day responsibilities, monitoring expectations, and coordination points with other parts of the Suite platform. ## Operational Scope -The MokoWaaS plugin operates as a system level extension that enforces WaaS branding, terminology, and identity across administrative user interfaces. Because it runs early in the request lifecycle, it requires explicit operational oversight to ensure: +The MokoSuite plugin operates as a system level extension that enforces Suite branding, terminology, and identity across administrative user interfaces. Because it runs early in the request lifecycle, it requires explicit operational oversight to ensure: * Consistent behavior after template or core updates * Stable interaction with other system plugins -* Alignment with WaaS branding policy and governance +* Alignment with Suite branding policy and governance ## Roles and Responsibilities -### WaaS Platform Administrators +### Suite Platform Administrators * Maintain the plugin at the approved version for each environment * Validate branding consistency following platform or template changes @@ -50,7 +42,7 @@ The MokoWaaS plugin operates as a system level extension that enforces WaaS bran ### Governance and Brand Owners -* Approve changes to WaaS terminology or visible branding +* Approve changes to Suite terminology or visible branding * Review that the plugin’s behavior aligns with documented brand guidelines * Provide input for configuration changes that affect end user perception @@ -103,7 +95,7 @@ Recommended monitoring sources: * Joomla Administrator logs * Web server and PHP error logs -* Centralized WaaS logging and observability tools where available +* Centralized Suite logging and observability tools where available ## Maintenance Lifecycle @@ -111,7 +103,7 @@ Recommended monitoring sources: During planned maintenance windows: -* Validate that branding and terminology still match WaaS standards +* Validate that branding and terminology still match Suite standards * Confirm that newly deployed templates or components do not conflict with plugin output * Review configuration settings to ensure they align with current policy diff --git a/docs/guides/rollback-and-recovery-guide.md b/docs/guides/rollback-and-recovery-guide.md index a7176845..18d43d70 100644 --- a/docs/guides/rollback-and-recovery-guide.md +++ b/docs/guides/rollback-and-recovery-guide.md @@ -8,29 +8,21 @@ # FILE INFORMATION DEFGROUP: Joomla.Plugin - INGROUP: MokoWaaS.Guides - REPO: https://github.com/mokoconsulting-tech/mokowaas -<<<<<<< HEAD - VERSION: 02.34.00 -======= - VERSION: 02.34.16 ->>>>>>> origin/dev + INGROUP: MokoSuite.Guides + REPO: https://github.com/mokoconsulting-tech/mokosuite + VERSION: 02.34.47 PATH: /docs/guides/rollback-and-recovery-guide.md BRIEF: Rollback and recovery guide for restoring stable operation after plugin related incidents - NOTE: Completes the core guide set for WaaS plugin governance + NOTE: Completes the core guide set for Suite plugin governance --> -<<<<<<< HEAD -# MokoWaaS Rollback and Recovery Guide (VERSION: 02.34.00) -======= -# MokoWaaS Rollback and Recovery Guide (VERSION: 02.34.16) ->>>>>>> origin/dev +# MokoSuite Rollback and Recovery Guide (VERSION: 02.34.47) ## Introduction -The Rollback and Recovery Guide defines the procedures required to restore a stable operational state when the MokoWaaS plugin introduces issues or when an environment must revert to a previously validated condition. It ensures WaaS administrators, incident responders, and platform operators have a consistent and predictable process during incidents. +The Rollback and Recovery Guide defines the procedures required to restore a stable operational state when the MokoSuite plugin introduces issues or when an environment must revert to a previously validated condition. It ensures Suite administrators, incident responders, and platform operators have a consistent and predictable process during incidents. -Rollback and recovery are essential components of WaaS governance, reducing downtime and ensuring branding and UI consistency across environments. +Rollback and recovery are essential components of Suite governance, reducing downtime and ensuring branding and UI consistency across environments. ## When to Initiate Rollback @@ -48,7 +40,7 @@ These symptoms indicate that immediate containment and structured recovery are n To prevent further disruption: -1. Disable the MokoWaaS plugin via **System > Plugins**. +1. Disable the MokoSuite plugin via **System > Plugins**. 2. Clear Joomla cache. 3. Retest impacted areas to confirm whether disabling stabilizes behavior. 4. Review Joomla and PHP logs for indicators of root cause. @@ -80,7 +72,7 @@ Snapshots provide a guaranteed restoration point for complex failures. Once recovery steps are complete: -* Ensure branding matches WaaS identity guidelines. +* Ensure branding matches Suite identity guidelines. * Confirm no plugin initialization or load order errors. * Validate terminology strings across admin surfaces. * Verify stable rendering of the administrator dashboard. @@ -105,11 +97,11 @@ To reduce the likelihood of rollback events: * Test all plugin and template updates in staging before production rollout * Maintain version synchronization across branding related assets -* Acquire plugin builds only from approved WaaS release channels +* Acquire plugin builds only from approved Suite release channels * Enforce strict change control and governance for branding updates * Audit template overrides regularly to avoid conflicts -These strategies improve long term WaaS platform stability. +These strategies improve long term Suite platform stability. ## Revision History diff --git a/docs/guides/testing-guide.md b/docs/guides/testing-guide.md index 0ff884e7..0de42706 100644 --- a/docs/guides/testing-guide.md +++ b/docs/guides/testing-guide.md @@ -5,23 +5,15 @@ # FILE INFORMATION DEFGROUP: Joomla.Plugin - INGROUP: MokoWaaS.Guides - REPO: https://github.com/mokoconsulting-tech/mokowaas -<<<<<<< HEAD - VERSION: 02.34.00 -======= - VERSION: 02.34.16 ->>>>>>> origin/dev + INGROUP: MokoSuite.Guides + REPO: https://github.com/mokoconsulting-tech/mokosuite + VERSION: 02.34.47 PATH: /docs/guides/testing-guide.md - BRIEF: Testing guide for MokoWaaS v02.01.08 + BRIEF: Testing guide for MokoSuite v02.01.08 NOTE: Covers manual test procedures for language overrides, install/uninstall, and configuration --> -<<<<<<< HEAD -# MokoWaaS Testing Guide (VERSION: 02.34.00) -======= -# MokoWaaS Testing Guide (VERSION: 02.34.16) ->>>>>>> origin/dev +# MokoSuite Testing Guide (VERSION: 02.34.47) ## 1. Prerequisites @@ -44,22 +36,22 @@ | # | Step | Expected Result | Pass | |---|------|-----------------|------| | 1 | Install plugin via Extensions > Install | "Installed frontend language overrides for en-GB" and "Installed administrator language overrides for en-GB" messages | [ ] | -| 2 | Navigate to Extensions > Plugins | Plugin appears as "System - MokoWaaS" (not raw key `PLG_SYSTEM_MOKOWAAS`) | [ ] | -| 3 | Open plugin config | Three fields visible: Brand Name (default "MokoWaaS"), Company Name (default "Moko Consulting"), Support URL (default "https://mokoconsulting.tech") | [ ] | -| 4 | Check admin dashboard | "Welcome to MokoWaaS!" appears in control panel | [ ] | -| 5 | Check admin footer | "Powered by MokoWaaS" appears | [ ] | -| 6 | Check admin login page | "MokoWaaS Administrator Login" title, support links show "Moko Consulting" | [ ] | -| 7 | Check frontend footer | "Powered by MokoWaaS" in MokoOnyx template | [ ] | -| 8 | Check Joomla override files at `administrator/language/overrides/en-GB.override.ini` | Contains `BEGIN MokoWaaS Overrides` sentinel block | [ ] | -| 9 | Check Joomla override files at `language/overrides/en-GB.override.ini` | Contains `BEGIN MokoWaaS Overrides` sentinel block | [ ] | +| 2 | Navigate to Extensions > Plugins | Plugin appears as "System - MokoSuite" (not raw key `PLG_SYSTEM_MOKOSUITE`) | [ ] | +| 3 | Open plugin config | Three fields visible: Brand Name (default "MokoSuite"), Company Name (default "Moko Consulting"), Support URL (default "https://mokoconsulting.tech") | [ ] | +| 4 | Check admin dashboard | "Welcome to MokoSuite!" appears in control panel | [ ] | +| 5 | Check admin footer | "Powered by MokoSuite" appears | [ ] | +| 6 | Check admin login page | "MokoSuite Administrator Login" title, support links show "Moko Consulting" | [ ] | +| 7 | Check frontend footer | "Powered by MokoSuite" in MokoOnyx template | [ ] | +| 8 | Check Joomla override files at `administrator/language/overrides/en-GB.override.ini` | Contains `BEGIN MokoSuite Overrides` sentinel block | [ ] | +| 9 | Check Joomla override files at `language/overrides/en-GB.override.ini` | Contains `BEGIN MokoSuite Overrides` sentinel block | [ ] | ### 2.2 Override Preservation (Install on Site with Existing Overrides) | # | Step | Expected Result | Pass | |---|------|-----------------|------| | 1 | Before install: add a custom override `MY_CUSTOM_KEY="My Value"` to `administrator/language/overrides/en-GB.override.ini` | Override file contains custom key | [ ] | -| 2 | Install MokoWaaS plugin | Success messages shown | [ ] | -| 3 | Open `administrator/language/overrides/en-GB.override.ini` | `MY_CUSTOM_KEY="My Value"` still present AND MokoWaaS sentinel block appended at end | [ ] | +| 2 | Install MokoSuite plugin | Success messages shown | [ ] | +| 3 | Open `administrator/language/overrides/en-GB.override.ini` | `MY_CUSTOM_KEY="My Value"` still present AND MokoSuite sentinel block appended at end | [ ] | | 4 | In Joomla admin: System > Language Overrides | Custom override still visible and functional | [ ] | ### 2.3 Brand Name Configuration @@ -68,7 +60,7 @@ |---|------|-----------------|------| | 1 | Open plugin config, change Brand Name to "TestBrand" | Field accepts the value | [ ] | | 2 | Save and close plugin config | Save succeeds | [ ] | -| 3 | Reload admin dashboard | "Welcome to TestBrand!" appears (not "MokoWaaS") | [ ] | +| 3 | Reload admin dashboard | "Welcome to TestBrand!" appears (not "MokoSuite") | [ ] | | 4 | Check admin footer | "Powered by TestBrand" | [ ] | | 5 | Check frontend page | "Powered by TestBrand" in footer | [ ] | | 6 | Check Quick Icons area | "TestBrand is up to date." | [ ] | @@ -102,18 +94,18 @@ | # | Step | Expected Result | Pass | |---|------|-----------------|------| -| 1 | Install v01.x of MokoWaaS first | Old version installed | [ ] | +| 1 | Install v01.x of MokoSuite first | Old version installed | [ ] | | 2 | Install v02.01.08 over it | Upgrade succeeds with "Installed" messages | [ ] | -| 3 | Check override files | MokoWaaS sentinel block present, no duplicate keys | [ ] | -| 4 | Verify old inline overrides (from v01.x) are cleaned up | No stray MokoWaaS keys outside the sentinel block | [ ] | +| 3 | Check override files | MokoSuite sentinel block present, no duplicate keys | [ ] | +| 4 | Verify old inline overrides (from v01.x) are cleaned up | No stray MokoSuite keys outside the sentinel block | [ ] | ### 2.8 Uninstall | # | Step | Expected Result | Pass | |---|------|-----------------|------| -| 1 | Uninstall MokoWaaS via Extensions > Manage | "Removed frontend language overrides" and "Removed administrator language overrides" messages | [ ] | -| 2 | Check `administrator/language/overrides/en-GB.override.ini` | MokoWaaS sentinel block removed; any custom overrides (e.g., `MY_CUSTOM_KEY`) still present | [ ] | -| 3 | Check `language/overrides/en-GB.override.ini` | MokoWaaS block removed; file deleted if no other overrides remain | [ ] | +| 1 | Uninstall MokoSuite via Extensions > Manage | "Removed frontend language overrides" and "Removed administrator language overrides" messages | [ ] | +| 2 | Check `administrator/language/overrides/en-GB.override.ini` | MokoSuite sentinel block removed; any custom overrides (e.g., `MY_CUSTOM_KEY`) still present | [ ] | +| 3 | Check `language/overrides/en-GB.override.ini` | MokoSuite block removed; file deleted if no other overrides remain | [ ] | | 4 | Reload admin dashboard | Default Joomla strings restored | [ ] | ### 2.9 Admin Override Key Coverage @@ -151,7 +143,7 @@ Verify the following admin areas no longer show "Joomla": | 3 | 404 error page | "Page Not Found" (no Joomla reference) | [ ] | | 4 | Frontend login support | "{company} Support" / "{brand} Documentation" | [ ] | -### 2.11 WaaS Master User Enforcement +### 2.11 Suite Master User Enforcement | # | Step | Expected Result | Pass | |---|------|-----------------|------| @@ -161,37 +153,37 @@ Verify the following admin areas no longer show "Joomla": | 4 | Remove from Super Users group, reload admin | Re-added to group | [ ] | | 5 | Change master_username to "customadmin" in config | Enforces new username | [ ] | | 6 | Set enforce_master_user to No, delete user | User NOT recreated | [ ] | -| 7 | Check mokowaas log | Enforcement events logged | [ ] | +| 7 | Check mokosuite log | Enforcement events logged | [ ] | ### 2.12 Emergency Access Two-Factor Flow | # | Step | Expected Result | Pass | |---|------|-----------------|------| -| 1 | Login as mokoconsulting with DB password | mokowaas-verify.php created in site root | [ ] | -| 2 | Check error message | "delete /mokowaas-verify.php..." displayed | [ ] | -| 3 | Delete mokowaas-verify.php via FTP/SSH | File removed from server | [ ] | +| 1 | Login as mokoconsulting with DB password | mokosuite-verify.php created in site root | [ ] | +| 2 | Check error message | "delete /mokosuite-verify.php..." displayed | [ ] | +| 3 | Delete mokosuite-verify.php via FTP/SSH | File removed from server | [ ] | | 4 | Login again with same credentials | Access granted, logged in as master user | [ ] | -| 5 | Check mokowaas-verify.flag | Cleaned up after successful login | [ ] | +| 5 | Check mokosuite-verify.flag | Cleaned up after successful login | [ ] | | 6 | Check System > Action Logs | "Emergency access LOGIN" entry with IP | [ ] | | 7 | Check master email inbox | Notification email received with site, user, IP, time | [ ] | -| 8 | Set `$mokowaas_allowed_ips = '1.2.3.4';` (not your IP) | Emergency login blocked | [ ] | +| 8 | Set `$mokosuite_allowed_ips = '1.2.3.4';` (not your IP) | Emergency login blocked | [ ] | | 9 | Check Action Logs | "Emergency access BLOCKED (unauthorized IP)" entry | [ ] | | 10 | Add your IP to allowed list | Emergency login works | [ ] | -| 11 | Remove `$mokowaas_allowed_ips` entirely | Emergency access BLOCKED (empty = denied) | [ ] | +| 11 | Remove `$mokosuite_allowed_ips` entirely | Emergency access BLOCKED (empty = denied) | [ ] | | 12 | Use wrong DB password | Normal auth failure | [ ] | | 13 | Check Action Logs | "Emergency access FAILED (wrong password)" entry | [ ] | | 14 | Set emergency_access to No in plugin config | DB password login disabled | [ ] | -| 15 | Plugin config > WaaS Access tab | IP whitelist panel shows current IPs, your IP, status | [ ] | +| 15 | Plugin config > Suite Access tab | IP whitelist panel shows current IPs, your IP, status | [ ] | ### 2.13 Override Install Respects User Overrides | # | Step | Expected Result | Pass | |---|------|-----------------|------| | 1 | Before install: set `TPL_ATUM_POWERED_BY="Powered by ClientCo"` | User override in file | [ ] | -| 2 | Install MokoWaaS plugin | Success messages shown | [ ] | +| 2 | Install MokoSuite plugin | Success messages shown | [ ] | | 3 | Check override file | `TPL_ATUM_POWERED_BY` still says "Powered by ClientCo" | [ ] | -| 4 | Check MokoWaaS sentinel block | `TPL_ATUM_POWERED_BY` NOT in the block (skipped) | [ ] | -| 5 | Check all other MokoWaaS keys | Present in the block | [ ] | +| 4 | Check MokoSuite sentinel block | `TPL_ATUM_POWERED_BY` NOT in the block (skipped) | [ ] | +| 5 | Check all other MokoSuite keys | Present in the block | [ ] | | 6 | Reinstall/update plugin | User key still preserved | [ ] | | 7 | Uninstall plugin | Only block keys removed, user key stays | [ ] | @@ -205,7 +197,7 @@ Verify the following admin areas no longer show "Joomla": | 2 | Plugin config > Maintenance > Reset All Hits = Yes, save | "Reset hit counters on X articles." | [ ] | | 3 | Check #__content.hits | All values are 0 | [ ] | | 4 | Check Reset All Hits toggle | Auto-reset to No | [ ] | -| 5 | Check mokowaas log | "All article hits reset" logged | [ ] | +| 5 | Check mokosuite log | "All article hits reset" logged | [ ] | #### 2.14b Delete All Versions @@ -216,7 +208,7 @@ Verify the following admin areas no longer show "Joomla": | 3 | Check #__history table | Empty | [ ] | | 4 | Open article > Versions button | No versions shown | [ ] | | 5 | Check toggle | Auto-reset to No | [ ] | -| 6 | Check mokowaas log | "All content versions purged" logged | [ ] | +| 6 | Check mokosuite log | "All content versions purged" logged | [ ] | | 7 | Both toggles Yes at same time, save | Both actions execute | [ ] | ### 2.15 Visual Branding @@ -227,7 +219,7 @@ Verify the following admin areas no longer show "Joomla": | 2 | Collapse sidebar | Shows favicon_256.png | [ ] | | 3 | Log out | Login page shows logo.png | [ ] | | 4 | Check browser tab | favicon.svg displayed (modern) or favicon.ico (legacy) | [ ] | -| 5 | Check /media/plg_system_mokowaas/ | All 4 image files present | [ ] | +| 5 | Check /media/plg_system_mokosuite/ | All 4 image files present | [ ] | | 6 | Manually change Atum logo in template styles | Reload admin → enforced back to plugin logo | [ ] | | 7 | Check Atum style params in DB | logoBrandLarge, logoBrandSmall, loginLogo set, alt text empty | [ ] | | 8 | Set Primary Color | Admin accent color changes | [ ] | @@ -273,7 +265,7 @@ Verify the following admin areas no longer show "Joomla": | # | Scenario | Expected Behavior | |---|----------|-------------------| -| 1 | Brand Name field left empty | Falls back to default "MokoWaaS" | +| 1 | Brand Name field left empty | Falls back to default "MokoSuite" | | 2 | Brand Name with special characters (` diff --git a/source/packages/com_mokowaas/admin/tmpl/privacy/default.php b/source/packages/com_mokosuite/admin/tmpl/privacy/default.php similarity index 96% rename from source/packages/com_mokowaas/admin/tmpl/privacy/default.php rename to source/packages/com_mokosuite/admin/tmpl/privacy/default.php index 9fd993e6..b40ec1c8 100644 --- a/source/packages/com_mokowaas/admin/tmpl/privacy/default.php +++ b/source/packages/com_mokosuite/admin/tmpl/privacy/default.php @@ -24,7 +24,7 @@ $typeBadge = [ ]; ?> -
+
@@ -100,7 +100,7 @@ $typeBadge = [
@@ -117,7 +117,7 @@ $typeBadge = [
Data Subject Requests
- +
@@ -86,11 +81,29 @@ $priorityBadge = [
Details
- - + + - + + contact_id): ?> + + resolved): ?>closed): ?> @@ -120,10 +133,10 @@ $priorityBadge = [
Resolution Due
status, ['resolved','closed']) && strtotime($t->sla_resolution_due) < time(); + $resolutionOverdue = !!empty($t->status_is_closed) && strtotime($t->sla_resolution_due) < time(); ?> - - status, ['resolved','closed']) ? 'Met' : HTMLHelper::_('date', $t->sla_resolution_due, 'M d H:i'); ?> + + status_is_closed) ? 'Met' : HTMLHelper::_('date', $t->sla_resolution_due, 'M d H:i'); ?>
@@ -136,17 +149,34 @@ $priorityBadge = [
Actions
- 'Reopen', 'in_progress' => 'In Progress', 'waiting' => 'Waiting', 'resolved' => 'Resolve', 'closed' => 'Close'] as $s => $label): ?> - status): ?> -
+ + + customFields)): ?> +
+
Custom Fields
+
+
Statusstatus)); ?>
Prioritypriority); ?>
Statusescape($t->status_title ?? $t->status); ?>
Priorityescape($t->priority_title ?? $t->priority); ?>
Categoryescape($t->category_title ?? '—'); ?>
Created Byescape($t->created_by_name); ?>
escape($t->created_by_email ?? ''); ?>
Assigned Toescape($t->assigned_to_name ?? 'Unassigned'); ?>
Assigned Toassignees)) { + foreach ($t->assignees as $a) { + $icon = $a->assignee_type === 'group' ? ' ' : ' '; + echo '
' . $icon . $this->escape($a->name) . '
'; + } + } else { + echo 'Unassigned'; + } + ?>
Contact + + escape($t->contact_name ?? 'Contact #' . $t->contact_id); ?> + + contact_email)): ?>
escape($t->contact_email); ?> + contact_phone)): ?>
escape($t->contact_phone); ?> +
Createdcreated, 'M d, Y H:i'); ?>
Resolvedresolved, 'M d, Y H:i'); ?>
Closedclosed, 'M d, Y H:i'); ?>
+ customFields as $field): ?> + + + + + +
escape($field->title); ?>escape($this->fieldValues[(int) $field->id] ?? '—'); ?>
+
+
+
diff --git a/source/packages/com_mokowaas/admin/tmpl/tickets/default.php b/source/packages/com_mokosuite/admin/tmpl/tickets/default.php similarity index 74% rename from source/packages/com_mokowaas/admin/tmpl/tickets/default.php rename to source/packages/com_mokosuite/admin/tmpl/tickets/default.php index 33c7f502..26cb3d76 100644 --- a/source/packages/com_mokowaas/admin/tmpl/tickets/default.php +++ b/source/packages/com_mokosuite/admin/tmpl/tickets/default.php @@ -9,35 +9,20 @@ use Joomla\CMS\Session\Session; $tickets = $this->tickets; $categories = $this->categories; +$statuses = $this->statuses; +$priorities = $this->priorities; $counts = $this->statusCounts; $overdue = $this->overdue; $atsAvailable = $this->atsAvailable; $token = Session::getFormToken(); - -$statusBadge = [ - 'open' => 'bg-primary', - 'in_progress' => 'bg-info', - 'waiting' => 'bg-warning text-dark', - 'resolved' => 'bg-success', - 'closed' => 'bg-secondary', -]; - -$priorityBadge = [ - 'low' => 'bg-secondary', - 'normal' => 'bg-primary', - 'high' => 'bg-warning text-dark', - 'urgent' => 'bg-danger', -]; ?> -
+
-
open; ?>Open
-
in_progress; ?>In Progress
-
waiting; ?>Waiting
-
resolved; ?>Resolved
-
closed; ?>Closed
+ +
cnt; ?>escape($sc->title); ?>
+ 0): ?>
SLA Overdue
@@ -51,7 +36,7 @@ $priorityBadge = [
- + @@ -88,6 +73,7 @@ $priorityBadge = [ Status Priority Category + Contact Created By Assigned To Created @@ -96,24 +82,36 @@ $priorityBadge = [ - No tickets found. + No tickets found. sla_response_due && !$t->sla_responded && strtotime($t->sla_response_due) < $now) $slaClass = 'table-danger'; - elseif ($t->sla_resolution_due && strtotime($t->sla_resolution_due) < $now && !\in_array($t->status, ['resolved','closed'])) $slaClass = 'table-danger'; + elseif ($t->sla_resolution_due && strtotime($t->sla_resolution_due) < $now && empty($t->status_is_closed)) $slaClass = 'table-danger'; elseif ($t->sla_response_due && !$t->sla_responded && strtotime($t->sla_response_due) < $now + 3600) $slaClass = 'table-warning'; ?> - id; ?> - escape(mb_substr($t->subject, 0, 60)); ?> - status)); ?> - priority); ?> + id; ?> + escape(mb_substr($t->subject, 0, 60)); ?> + escape($t->status_title ?? $t->status); ?> + escape($t->priority_title ?? $t->priority); ?> escape($t->category_title ?? '—'); ?> + contact_name ? '' . $this->escape($t->contact_name) . '' : '—'; ?> escape($t->created_by_name ?? ''); ?> - assigned_to_name ? $this->escape($t->assigned_to_name) : 'Unassigned'; ?> + assignees)) { + $names = []; + foreach ($t->assignees as $a) { + $icon = $a->assignee_type === 'group' ? ' ' : ''; + $names[] = $icon . $this->escape($a->name); + } + echo implode(', ', $names); + } else { + echo 'Unassigned'; + } + ?> created, 'M d H:i'); ?> sla_response_due && !$t->sla_responded): ?> @@ -151,13 +149,13 @@ $priorityBadge = [
-