diff --git a/.mokogitea/ISSUE_TEMPLATE/waas_site_issue.md b/.mokogitea/ISSUE_TEMPLATE/waas_site_issue.md
new file mode 100644
index 0000000..4a0c89b
--- /dev/null
+++ b/.mokogitea/ISSUE_TEMPLATE/waas_site_issue.md
@@ -0,0 +1,77 @@
+---
+name: WaaS Client Site Issue
+about: Report an issue with a WaaS client site (branding, deployment, media sync)
+title: '[WAAS] '
+labels: 'waas, client-site'
+assignees: ''
+
+---
+
+## Site Issue Type
+- [ ] Branding / CSS not applying
+- [ ] Deployment failure
+- [ ] Media sync issue
+- [ ] Template override not working
+- [ ] Module positioning issue
+- [ ] Mobile / responsive layout
+- [ ] Performance issue
+
+## Client Site
+- **Client Org**: [e.g., ClarksvilleFurs]
+- **Repo**: [e.g., client-waas-clarksvillefurs]
+- **Environment**: [Dev / Production]
+- **Site URL**: [dev or production URL — omit if private]
+
+## Issue Description
+Describe the issue clearly.
+
+## Steps to Reproduce
+1. Visit [page URL]
+2. Look at [element]
+3. See error
+
+## Expected Behavior
+What the site should look like or how it should behave.
+
+## Actual Behavior
+What is happening instead.
+
+## Screenshots
+Attach screenshots showing the issue (desktop and mobile if relevant).
+
+## Deployment Status
+- **Last deploy**: [date or "unknown"]
+- **Deploy workflow**: [succeeded / failed / not run]
+- **Branch**: [dev / main]
+
+## Media Sync
+- [ ] Images missing after sync
+- [ ] Sync direction: [dev-to-prod / prod-to-dev / bidirectional]
+- [ ] Last sync: [date]
+
+## Template Details
+- **Joomla Version**: [e.g., 5.x]
+- **Template Name**: [e.g., clienttemplate]
+- **MokoWaaS Plugin**: [Active / Inactive]
+- **MokoOnyx Admin**: [Active / Inactive]
+
+## CSS Custom Properties
+If branding issue, list the relevant CSS variables:
+```css
+:root {
+ --client-primary: #...;
+ --client-secondary: #...;
+}
+```
+
+## Browser / Device
+- **Browser**: [e.g., Chrome 120, Safari 17]
+- **Device**: [Desktop / Tablet / Mobile]
+- **Screen Width**: [e.g., 1920px, 768px, 375px]
+
+## Checklist
+- [ ] I have cleared Joomla cache
+- [ ] I have hard-refreshed the browser (Ctrl+Shift+R)
+- [ ] I have checked the deploy workflow completed
+- [ ] I have verified the change is on the correct branch
+- [ ] No credentials or PII are included in this issue
diff --git a/.mokogitea/workflows/auto-release.yml b/.mokogitea/workflows/auto-release.yml
index 46ce4b2..787b7a0 100644
--- a/.mokogitea/workflows/auto-release.yml
+++ b/.mokogitea/workflows/auto-release.yml
@@ -79,131 +79,37 @@ jobs:
- name: Detect platform
id: platform
run: |
- # Read platform from manifest.xml element; fallback to generic
- PLATFORM=$(sed -n 's/.*\([^<]*\)<\/platform>.*//p' .mokogitea/manifest.xml 2>/dev/null | head -1 | tr -d '[:space:]')
- [ -z "$PLATFORM" ] && PLATFORM="generic"
- echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT"
- echo "Platform detected: ${PLATFORM}"
- # For packages: prefer pkg_*.xml in src/; fallback to any manifest
- MANIFEST=$(find ./src -maxdepth 1 -name "pkg_*.xml" -exec grep -l '/dev/null | head -1)
- [ -z "$MANIFEST" ] && MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" ! -path "*/packages/*" -exec grep -l '/dev/null | head -1)
- [ -z "$MANIFEST" ] && MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '/dev/null | head -1)
- MOD_FILE=$(find . -maxdepth 4 -name "mod*.class.php" ! -path "./.git/*" -exec grep -l 'extends DolibarrModules' {} \; 2>/dev/null | head -1)
+ php /tmp/moko-platform-api/cli/manifest_read.php --path . --github-output
+ MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '/dev/null | head -1 || true)
+ MOD_FILE=$(find . -maxdepth 4 -name "mod*.class.php" ! -path "./.git/*" -exec grep -l 'extends DolibarrModules' {} \; 2>/dev/null | head -1 || true)
echo "manifest=${MANIFEST}" >> "$GITHUB_OUTPUT"
echo "mod_file=${MOD_FILE}" >> "$GITHUB_OUTPUT"
- # -- STEP 1: Read version -----------------------------------------------
- - name: "Step 1: Read version from README.md"
+ - name: "Step 1: Read version"
id: version
run: |
- VERSION=$(php /tmp/moko-platform-api/cli/version_read.php --path . 2>/dev/null)
+ VERSION=$(php /tmp/moko-platform-api/cli/version_read.php --path .)
if [ -z "$VERSION" ]; then
- echo "No VERSION in README.md — skipping release"
+ echo "::error::No VERSION in README.md"
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi
- # Derive major.minor for branch naming (patches update existing branch)
- MINOR=$(echo "$VERSION" | awk -F. '{printf "%s.%s", $1, $2}')
- PATCH=$(echo "$VERSION" | awk -F. '{print $3}')
-
- MAJOR=$(echo "$VERSION" | awk -F. '{print $1}')
- MINOR_NUM=$(echo "$VERSION" | awk -F. '{print $2}')
-
- echo "version=$VERSION" >> "$GITHUB_OUTPUT"
- echo "branch=version/${MAJOR}" >> "$GITHUB_OUTPUT"
- echo "minor=$MINOR" >> "$GITHUB_OUTPUT"
- echo "major=$MAJOR" >> "$GITHUB_OUTPUT"
- echo "release_tag=stable" >> "$GITHUB_OUTPUT"
- echo "stability=stable" >> "$GITHUB_OUTPUT"
- echo "skip=false" >> "$GITHUB_OUTPUT"
- if [ "$PATCH" = "00" ] || [ "$PATCH" = "01" ]; then
- echo "is_minor=true" >> "$GITHUB_OUTPUT"
- echo "Version: $VERSION (first release for this minor — full pipeline)"
- else
- echo "is_minor=false" >> "$GITHUB_OUTPUT"
- echo "Version: $VERSION (patch — platform version + badges only)"
- fi
-
- # -- STEP 1b: Bump minor version (stable = minor bump, reset patch) ------
- - name: "Step 1b: Bump minor version for stable release"
- if: steps.version.outputs.skip != 'true'
- id: bump
- run: |
- CURRENT=$(sed -n 's/.*VERSION:[[:space:]]*\([0-9][0-9]\.[0-9][0-9]\.[0-9][0-9]\).*/\1/p' README.md 2>/dev/null | head -1)
- [ -z "$CURRENT" ] && { echo "skip=true" >> "$GITHUB_OUTPUT"; exit 0; }
-
- MAJOR=$((10#$(echo "$CURRENT" | cut -d. -f1)))
- MINOR=$((10#$(echo "$CURRENT" | cut -d. -f2)))
-
- # Minor bump, reset patch. Rollover if minor > 99
- MINOR=$((MINOR + 1))
- if [ $MINOR -gt 99 ]; then
- MINOR=0
- MAJOR=$((MAJOR + 1))
- fi
-
- VERSION=$(printf "%02d.%02d.00" $MAJOR $MINOR)
- TODAY=$(date +%Y-%m-%d)
-
- echo "Stable bump: ${CURRENT} → ${VERSION} (minor)"
-
- # Update README.md
- sed -i "s/VERSION:[[:space:]]*${CURRENT}/VERSION: ${VERSION}/" README.md
-
- # Update platform-specific manifest
- PLATFORM="${{ steps.platform.outputs.platform }}"
- MANIFEST="${{ steps.platform.outputs.manifest }}"
- MOD_FILE="${{ steps.platform.outputs.mod_file }}"
- case "$PLATFORM" in
- joomla)
- if [ -n "$MANIFEST" ]; then
- MANIFEST_VER=$(sed -n 's/.*\([^<]*\)<\/version>.*/\1/p' "$MANIFEST" | head -1)
- [ -n "$MANIFEST_VER" ] && sed -i "s|${MANIFEST_VER}|${VERSION}|" "$MANIFEST"
- sed -i "s|[^<]*|${TODAY}|" "$MANIFEST"
- fi
- # For packages: also bump version in all sub-extension manifests
- if [ -d "src/packages" ]; then
- for SUB_MANIFEST in $(find src/packages -maxdepth 2 -name "*.xml" -exec grep -l '/dev/null); do
- SUB_VER=$(sed -n 's/.*\([^<]*\)<\/version>.*/\1/p' "$SUB_MANIFEST" | head -1)
- if [ -n "$SUB_VER" ]; then
- sed -i "s|${SUB_VER}|${VERSION}|" "$SUB_MANIFEST"
- sed -i "s|[^<]*|${TODAY}|" "$SUB_MANIFEST"
- echo " Bumped sub-extension: $(basename $SUB_MANIFEST) ${SUB_VER} → ${VERSION}"
- fi
- done
- fi
- ;;
- dolibarr)
- if [ -n "$MOD_FILE" ]; then
- sed -i "s/\$this->version = '[^']*'/\$this->version = '${VERSION}'/" "$MOD_FILE"
- fi
- echo "${VERSION}" > update.txt
- ;;
- *) ;;
- esac
-
- # Promote [Unreleased] section in CHANGELOG.md to new version
- if [ -f "CHANGELOG.md" ] && grep -qi "Unreleased" CHANGELOG.md; then
- sed -i "s|## \[Unreleased\]|## [${VERSION}] --- ${TODAY}|" CHANGELOG.md
- sed -i "s|## Unreleased|## [${VERSION}] --- ${TODAY}|" CHANGELOG.md
- sed -i "2i ## [Unreleased]" CHANGELOG.md
- sed -i "3i \\ " CHANGELOG.md
- echo "CHANGELOG promoted to [${VERSION}]"
- fi
-
- # Commit and push
- git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
- git config --local user.name "gitea-actions[bot]"
- git remote set-url origin "https://jmiller:${{ secrets.GA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
- git add -A
- git diff --cached --quiet || {
- git commit -m "chore(version): bump ${CURRENT} → ${VERSION} [skip ci]"
- git push origin HEAD:main 2>&1
- }
-
- # Override version output for rest of pipeline
+ MAJOR=$(echo "$VERSION" | cut -d. -f1)
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
- echo "major=$(printf "%02d" $MAJOR)" >> "$GITHUB_OUTPUT"
+ echo "release_tag=v${MAJOR}" >> "$GITHUB_OUTPUT"
+ echo "skip=false" >> "$GITHUB_OUTPUT"
+ echo "branch=version/${MAJOR}" >> "$GITHUB_OUTPUT"
+
+ - name: "Step 1b: Bump version"
+ id: bump
+ if: steps.version.outputs.skip != 'true'
+ run: |
+ MOKO_API="/tmp/moko-platform-api/cli"
+ BUMP=$(php ${MOKO_API}/version_bump.php --path . --minor)
+ VERSION=$(echo "$BUMP" | grep -oP '\d{2}\.\d{2}\.\d{2}$' || true)
+ [ -z "$VERSION" ] && VERSION=$(php ${MOKO_API}/version_read.php --path .)
+ echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
+ echo "Bumped to: ${VERSION}"
- name: Check if already released
if: steps.version.outputs.skip != 'true'
@@ -353,166 +259,22 @@ jobs:
# -- STEP 4: Update version badges ----------------------------------------
- name: "Step 4: Update version badges"
- if: >-
- steps.version.outputs.skip != 'true' &&
- steps.check.outputs.already_released != 'true'
+ if: steps.version.outputs.skip != 'true'
run: |
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
- find . -name "*.md" ! -path "./.git/*" ! -path "./vendor/*" | while read -r f; do
- if grep -q '\[VERSION:' "$f" 2>/dev/null; then
- sed -i "s/\[VERSION:[[:space:]]*[0-9]\{2\}\.[0-9]\{2\}\.[0-9]\{2\}\]/[VERSION: ${VERSION}]/" "$f"
- fi
- done
+ php /tmp/moko-platform-api/cli/badge_update.php --path . --version "${VERSION}" 2>/dev/null || true
- # -- STEP 5: Write updates.xml (Joomla update server) ---------------------
- name: "Step 5: Write update stream"
- id: updates
if: >-
steps.version.outputs.skip != 'true' &&
- steps.check.outputs.already_released != 'true'
+ steps.platform.outputs.platform == 'joomla'
run: |
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
- REPO="${{ github.repository }}"
+ php /tmp/moko-platform-api/cli/updates_xml_build.php \
+ --path . --version "${VERSION}" --stability stable \
+ --gitea-url "${GITEA_URL}" --org "${GITEA_ORG}" --repo "${GITEA_REPO}" \
+ --github-output
- # -- Parse extension metadata from XML manifest ----------------
- MANIFEST=$(find ./src -maxdepth 1 -name "pkg_*.xml" -exec grep -l '/dev/null | head -1)
- [ -z "$MANIFEST" ] && MANIFEST=$(find . -maxdepth 2 -name "*.xml" ! -path "*/packages/*" -exec grep -l '/dev/null | head -1)
- if [ -z "$MANIFEST" ]; then
- echo "Warning: No Joomla XML manifest found — skipping updates.xml" >> $GITHUB_STEP_SUMMARY
- exit 0
- fi
-
- # Extract fields using sed (portable — no grep -P)
- EXT_NAME=$(sed -n 's/.*\([^<]*\)<\/name>.*/\1/p' "$MANIFEST" | head -1)
- EXT_TYPE=$(sed -n 's/.*]*type="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
- EXT_ELEMENT=$(sed -n 's/.*\([^<]*\)<\/element>.*/\1/p' "$MANIFEST" | head -1)
- EXT_CLIENT=$(sed -n 's/.*]*client="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
- EXT_FOLDER=$(sed -n 's/.*]*group="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
- TARGET_PLATFORM=$(sed -n 's/.*\(\).*/\1/p' "$MANIFEST" | head -1)
- PHP_MINIMUM=$(sed -n 's/.*\([^<]*\)<\/php_minimum>.*/\1/p' "$MANIFEST" | head -1)
-
- # If EXT_NAME is a language key (e.g. PLG_SYSTEM_MOKOJGDPC), resolve from .ini
- if echo "$EXT_NAME" | grep -qE '^[A-Z_]+$'; then
- INI_NAME=$(find . -name "*.sys.ini" -path "*/en-GB/*" -exec grep -h "^${EXT_NAME}=" {} \; 2>/dev/null | head -1 | cut -d'"' -f2)
- [ -z "$INI_NAME" ] && INI_NAME=$(find . -name "*.sys.ini" -exec grep -h "^${EXT_NAME}=" {} \; 2>/dev/null | head -1 | cut -d'"' -f2)
- [ -n "$INI_NAME" ] && EXT_NAME="$INI_NAME"
- fi
-
- # Fallbacks
- [ -z "$EXT_NAME" ] && EXT_NAME="${{ github.event.repository.name }}"
- [ -z "$EXT_TYPE" ] && EXT_TYPE="component"
-
- # Derive element if not in manifest:
- # 1. plugin="xxx" attribute (plugins)
- # 2. module="xxx" attribute (modules)
- # 3. XML filename (components, packages)
- # 4. Repo name fallback (templates, anything else)
- if [ -z "$EXT_ELEMENT" ]; then
- EXT_ELEMENT=$(sed -n 's/.*plugin="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
- fi
- if [ -z "$EXT_ELEMENT" ]; then
- EXT_ELEMENT=$(sed -n 's/.*module="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
- fi
- if [ -z "$EXT_ELEMENT" ]; then
- FNAME=$(basename "$MANIFEST" .xml | tr '[:upper:]' '[:lower:]')
- # If filename is generic (templateDetails, manifest), use repo name
- case "$FNAME" in
- templatedetails|manifest) EXT_ELEMENT=$(echo "${{ github.event.repository.name }}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') ;;
- *) EXT_ELEMENT="$FNAME" ;;
- esac
- fi
- # Final fallback
- [ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${{ github.event.repository.name }}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
-
- # Save for Steps 7, 8, 8b
- echo "ext_element=${EXT_ELEMENT}" >> "$GITHUB_OUTPUT"
- echo "ext_name=${EXT_NAME}" >> "$GITHUB_OUTPUT"
- echo "ext_type=${EXT_TYPE}" >> "$GITHUB_OUTPUT"
- echo "ext_folder=${EXT_FOLDER}" >> "$GITHUB_OUTPUT"
-
- # Build client tag: plugins and frontend modules need site
- CLIENT_TAG=""
- if [ -n "$EXT_CLIENT" ]; then
- CLIENT_TAG="${EXT_CLIENT}"
- elif [ "$EXT_TYPE" = "module" ] || [ "$EXT_TYPE" = "plugin" ]; then
- CLIENT_TAG="site"
- fi
-
- # Build folder tag for plugins (required for Joomla to match the update)
- FOLDER_TAG=""
- if [ -n "$EXT_FOLDER" ] && [ "$EXT_TYPE" = "plugin" ]; then
- FOLDER_TAG="${EXT_FOLDER}"
- fi
-
- # Build targetplatform (fallback to Joomla 5 if not in manifest)
- if [ -z "$TARGET_PLATFORM" ]; then
- TARGET_PLATFORM=$(printf '' "/")
- fi
-
- # Build php_minimum tag
- PHP_TAG=""
- if [ -n "$PHP_MINIMUM" ]; then
- PHP_TAG="${PHP_MINIMUM}"
- fi
-
- # Build TYPE_PREFIX for download URL
- TYPE_PREFIX=""
- case "${EXT_TYPE}" in
- plugin) TYPE_PREFIX="plg_${EXT_FOLDER}_" ;;
- module) TYPE_PREFIX="mod_" ;;
- component) TYPE_PREFIX="com_" ;;
- template) TYPE_PREFIX="tpl_" ;;
- library) TYPE_PREFIX="lib_" ;;
- package) TYPE_PREFIX="pkg_" ;;
- esac
-
- DOWNLOAD_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/stable/${TYPE_PREFIX}${EXT_ELEMENT}-${VERSION}.zip"
- INFO_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/stable"
-
- # -- Build update entry for a given stability tag
- build_entry() {
- local TAG_NAME="$1"
- printf '%s\n' ' '
- printf '%s\n' " ${EXT_NAME}"
- printf '%s\n' " ${EXT_NAME} update"
- printf '%s\n' " ${EXT_ELEMENT}"
- printf '%s\n' " ${EXT_TYPE}"
- printf '%s\n' " ${VERSION}"
- [ -n "$CLIENT_TAG" ] && printf '%s\n' " ${CLIENT_TAG}"
- [ -n "$FOLDER_TAG" ] && printf '%s\n' " ${FOLDER_TAG}"
- printf '%s\n' " ${TAG_NAME}"
- printf '%s\n' " ${INFO_URL}"
- printf '%s\n' ' '
- printf '%s\n' " ${DOWNLOAD_URL}"
- printf '%s\n' ' '
- printf '%s\n' " ${TARGET_PLATFORM}"
- [ -n "$PHP_TAG" ] && printf '%s\n' " ${PHP_TAG}"
- printf '%s\n' ' Moko Consulting'
- printf '%s\n' ' https://mokoconsulting.tech'
- printf '%s\n' ' '
- }
-
- # -- Write updates.xml with cascading channels
- # Stable release updates ALL channels (development, alpha, beta, rc, stable)
- {
- printf '%s\n' ""
- printf '%s\n' ""
- printf '%s\n' ""
- printf '%s\n' ''
- build_entry "development"
- build_entry "alpha"
- build_entry "beta"
- build_entry "rc"
- build_entry "stable"
- printf '%s\n' ''
- } > updates.xml
-
- echo "updates.xml: ${VERSION} (all channels updated to stable)" >> $GITHUB_STEP_SUMMARY
-
- # -- Commit all changes ---------------------------------------------------
- name: Commit release changes
if: >-
steps.version.outputs.skip != 'true' &&
@@ -633,8 +395,7 @@ jobs:
fi
# Find extension element name from manifest
- MANIFEST=$(find ./src -maxdepth 1 -name "pkg_*.xml" -exec grep -l '/dev/null | head -1)
- [ -z "$MANIFEST" ] && MANIFEST=$(find . -maxdepth 2 -name "*.xml" ! -path "*/packages/*" -exec grep -l '/dev/null | head -1 || true)
+ MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '/dev/null | head -1 || true)
[ -z "$MANIFEST" ] && exit 0
# Reuse element from Step 5, with same fallback chain
@@ -663,44 +424,19 @@ jobs:
# -- Build install packages from src/ ----------------------------
SOURCE_DIR="src"
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
- [ ! -d "$SOURCE_DIR" ] && { echo "No src/ or htdocs/ — skipping package"; exit 0; }
+ [ ! -d "$SOURCE_DIR" ] && { echo "No src/ or htdocs/"; exit 0; }
- EXCLUDES=".ftpignore sftp-config* *.ppk *.pem *.key .env*"
-
- if [ "$EXT_TYPE" = "package" ] && [ -d "${SOURCE_DIR}/packages" ]; then
- echo "=== Building Joomla PACKAGE (multi-extension) ==="
- PKG_STAGE=$(mktemp -d)
-
- # ZIP each sub-extension
- for ext_dir in "${SOURCE_DIR}"/packages/*/; do
- [ ! -d "$ext_dir" ] && continue
- SUB_NAME=$(basename "$ext_dir")
- echo " Packaging sub-extension: ${SUB_NAME}"
- (cd "$ext_dir" && zip -r "${PKG_STAGE}/${SUB_NAME}.zip" . -x $EXCLUDES)
- done
-
- # Copy package-level files (manifest, script, etc.)
- for f in "${SOURCE_DIR}"/*.xml "${SOURCE_DIR}"/*.php; do
- [ -f "$f" ] && cp "$f" "${PKG_STAGE}/"
- done
-
- # Create ZIP and tar.gz from staged package
- (cd "$PKG_STAGE" && zip -r "/tmp/${ZIP_NAME}" .)
- tar -czf "/tmp/${TAR_NAME}" -C "$PKG_STAGE" .
-
- rm -rf "$PKG_STAGE"
- echo "Package contents built with sub-extension ZIPs"
- else
- # Standard extension: flat ZIP from src/
- cd "$SOURCE_DIR"
- zip -r "/tmp/${ZIP_NAME}" . -x $EXCLUDES
- cd ..
-
- tar -czf "/tmp/${TAR_NAME}" -C "$SOURCE_DIR" \
- --exclude='.ftpignore' --exclude='sftp-config*' \
- --exclude='*.ppk' --exclude='*.pem' --exclude='*.key' --exclude='.env*' .
+ # ZIP package (type-aware via moko-platform PHP API)
+ php /tmp/moko-platform-api/cli/joomla_build.php --path . --version "${VERSION}" --output /tmp
+ # Match the expected ZIP_NAME for upload
+ BUILT_ZIP=$(ls /tmp/${TYPE_PREFIX}${EXT_ELEMENT}-${VERSION}.zip 2>/dev/null | head -1 || true)
+ if [ -n "$BUILT_ZIP" ] && [ "$BUILT_ZIP" != "/tmp/${ZIP_NAME}" ]; then
+ mv "$BUILT_ZIP" "/tmp/${ZIP_NAME}"
fi
+ # tar.gz package (flat source archive)
+ tar -czf "/tmp/${TAR_NAME}" -C "$SOURCE_DIR" --exclude='.ftpignore' --exclude='sftp-config*' --exclude='*.ppk' --exclude='*.pem' --exclude='*.key' --exclude='.env*' .
+
ZIP_SIZE=$(stat -c%s "/tmp/${ZIP_NAME}" 2>/dev/null || stat -f%z "/tmp/${ZIP_NAME}" 2>/dev/null || echo "unknown")
TAR_SIZE=$(stat -c%s "/tmp/${TAR_NAME}" 2>/dev/null || stat -f%z "/tmp/${TAR_NAME}" 2>/dev/null || echo "unknown")
@@ -952,33 +688,14 @@ jobs:
# -- Clean up lesser pre-releases (cascade) ---------------------------------
# stable → deletes all | rc → beta,alpha,dev | beta → alpha,dev | alpha → dev
- name: "Delete lesser pre-release channels"
- if: steps.version.outputs.skip != 'true'
continue-on-error: true
run: |
- API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
- TOKEN="${{ secrets.GA_TOKEN }}"
+ php /tmp/moko-platform-api/cli/release_cascade.php \
+ --stability stable \
+ --token "${{ secrets.GA_TOKEN }}" \
+ --org "${GITEA_ORG}" --repo "${GITEA_REPO}" \
+ --gitea-url "${GITEA_URL}" 2>/dev/null || true
- # Stable deletes all pre-release channels
- TAGS_TO_DELETE="development alpha beta release-candidate"
-
- DELETED=0
- for TAG in $TAGS_TO_DELETE; do
- RELEASE_ID=$(curl -sS -H "Authorization: token ${TOKEN}" \
- "${API_BASE}/releases/tags/${TAG}" 2>/dev/null | \
- python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
-
- if [ -n "$RELEASE_ID" ] && [ "$RELEASE_ID" != "None" ]; then
- curl -sS -X DELETE -H "Authorization: token ${TOKEN}" \
- "${API_BASE}/releases/${RELEASE_ID}" 2>/dev/null || true
- curl -sS -X DELETE -H "Authorization: token ${TOKEN}" \
- "${API_BASE}/tags/${TAG}" 2>/dev/null || true
- echo "Deleted: ${TAG} (id: ${RELEASE_ID})"
- DELETED=$((DELETED + 1))
- fi
- done
- echo "Cleaned up ${DELETED} pre-release channel(s)" >> $GITHUB_STEP_SUMMARY
-
- # -- STEP 11: Reset dev branch from main ------------------------------------
- name: "Step 11: Delete and recreate dev branch from main"
if: steps.version.outputs.skip != 'true'
continue-on-error: true
diff --git a/.mokogitea/workflows/cascade-dev.yml b/.mokogitea/workflows/cascade-dev.yml
index 4dbb135..23b11a2 100644
--- a/.mokogitea/workflows/cascade-dev.yml
+++ b/.mokogitea/workflows/cascade-dev.yml
@@ -4,8 +4,8 @@
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
-# INGROUP: MokoStandards.Maintenance
-# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API
+# INGROUP: moko-platform.Maintenance
+# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
# PATH: /templates/workflows/cascade-dev.yml.template
# VERSION: 02.00.00
# BRIEF: Forward-merge main → all open branches after every push to main
diff --git a/.mokogitea/workflows/cleanup.yml b/.mokogitea/workflows/cleanup.yml
index 3a81856..a890001 100644
--- a/.mokogitea/workflows/cleanup.yml
+++ b/.mokogitea/workflows/cleanup.yml
@@ -4,8 +4,8 @@
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
-# INGROUP: MokoStandards.Maintenance
-# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
+# INGROUP: moko-platform.Maintenance
+# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
# PATH: /.gitea/workflows/cleanup.yml
# VERSION: 01.00.00
# BRIEF: Scheduled cleanup — delete merged branches and old workflow runs
diff --git a/.mokogitea/workflows/deploy-manual.yml b/.mokogitea/workflows/deploy-manual.yml
index bb133ed..6429460 100644
--- a/.mokogitea/workflows/deploy-manual.yml
+++ b/.mokogitea/workflows/deploy-manual.yml
@@ -4,8 +4,8 @@
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
-# INGROUP: MokoStandards.Deploy
-# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
+# INGROUP: moko-platform.Deploy
+# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
# PATH: /templates/workflows/joomla/deploy-manual.yml.template
# VERSION: 04.07.00
# BRIEF: Manual SFTP deploy to dev server for Joomla repos
@@ -40,7 +40,7 @@ jobs:
run: |
php -v && composer --version
- - name: Setup MokoStandards tools
+ - name: Setup moko-platform tools
env:
GA_TOKEN: ${{ secrets.GA_TOKEN || secrets.GA_TOKEN || github.token }}
MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN || secrets.GA_TOKEN || github.token }}
@@ -48,10 +48,10 @@ jobs:
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GA_TOKEN || github.token }}"}}'
run: |
git clone --depth 1 --branch main --quiet \
- "https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \
- /tmp/mokostandards-api 2>/dev/null || true
- if [ -d "/tmp/mokostandards-api" ] && [ -f "/tmp/mokostandards-api/composer.json" ]; then
- cd /tmp/mokostandards-api && composer install --no-dev --no-interaction --quiet 2>/dev/null || true
+ "https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
+ /tmp/moko-platform-api 2>/dev/null || true
+ if [ -d "/tmp/moko-platform-api" ] && [ -f "/tmp/moko-platform-api/composer.json" ]; then
+ cd /tmp/moko-platform-api && composer install --no-dev --no-interaction --quiet 2>/dev/null || true
fi
- name: Check FTP configuration
@@ -101,15 +101,28 @@ jobs:
DEPLOY_ARGS=(--path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json)
[ "${{ inputs.clear_remote }}" = "true" ] && DEPLOY_ARGS+=(--clear-remote)
- PLATFORM=$(php /tmp/mokostandards-api/cli/platform_detect.php --path . 2>/dev/null || true)
- if [ "$PLATFORM" = "waas-component" ] && [ -f "/tmp/mokostandards-api/deploy/deploy-joomla.php" ]; then
- php /tmp/mokostandards-api/deploy/deploy-joomla.php "${DEPLOY_ARGS[@]}"
+ PLATFORM=$(php /tmp/moko-platform-api/cli/platform_detect.php --path . 2>/dev/null || true)
+ if [ "$PLATFORM" = "waas-component" ] && [ -f "/tmp/moko-platform-api/deploy/deploy-joomla.php" ]; then
+ php /tmp/moko-platform-api/deploy/deploy-joomla.php "${DEPLOY_ARGS[@]}"
else
- php /tmp/mokostandards-api/deploy/deploy-sftp.php "${DEPLOY_ARGS[@]}"
+ php /tmp/moko-platform-api/deploy/deploy-sftp.php "${DEPLOY_ARGS[@]}"
fi
rm -f /tmp/deploy_key /tmp/sftp-config.json
+
+ - name: Post-deploy health check
+ if: success() && steps.check.outputs.skip != 'true'
+ run: |
+ if [ -f "deploy/health-check.php" ]; then
+ SITE_URL="${{ vars.DEV_SITE_URL }}"
+ if [ -n "$SITE_URL" ]; then
+ php deploy/health-check.php --url "$SITE_URL" --checks http --timeout 30 || echo "::warning::Health check failed after deploy"
+ else
+ echo "DEV_SITE_URL not configured, skipping health check"
+ fi
+ fi
+
- name: Summary
if: always()
run: |
diff --git a/.mokogitea/workflows/gitleaks.yml b/.mokogitea/workflows/gitleaks.yml
index 0c07612..e0fdd1d 100644
--- a/.mokogitea/workflows/gitleaks.yml
+++ b/.mokogitea/workflows/gitleaks.yml
@@ -4,8 +4,8 @@
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
-# INGROUP: MokoStandards.Security
-# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API
+# INGROUP: moko-platform.Security
+# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
# PATH: /templates/workflows/gitleaks.yml.template
# VERSION: 01.00.00
# BRIEF: Secret scanning — detect leaked credentials, API keys, and tokens
diff --git a/.mokogitea/workflows/notify.yml b/.mokogitea/workflows/notify.yml
index 463a900..cde4541 100644
--- a/.mokogitea/workflows/notify.yml
+++ b/.mokogitea/workflows/notify.yml
@@ -4,8 +4,8 @@
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
-# INGROUP: MokoStandards.Notifications
-# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
+# INGROUP: moko-platform.Notifications
+# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
# PATH: /.gitea/workflows/notify.yml
# VERSION: 01.00.00
# BRIEF: Push notifications via ntfy on release success or workflow failure
@@ -18,7 +18,6 @@ on:
- "Joomla Build & Release"
- "Joomla Extension CI"
- "Deploy"
- - "Cascade Main → Dev"
types:
- completed
diff --git a/.mokogitea/workflows/pr-check.yml b/.mokogitea/workflows/pr-check.yml
index 9290a89..bc1a001 100644
--- a/.mokogitea/workflows/pr-check.yml
+++ b/.mokogitea/workflows/pr-check.yml
@@ -4,8 +4,8 @@
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
-# INGROUP: MokoStandards.CI
-# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API
+# INGROUP: moko-platform.CI
+# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
# PATH: /templates/workflows/universal/pr-check.yml.template
# VERSION: 05.00.00
# BRIEF: PR gate — branch policy + code validation before merge
@@ -108,8 +108,9 @@ jobs:
- name: Detect platform
id: platform
run: |
- # Parse manifest for platform detection
- PLATFORM=$(php /tmp/mokostandards-api/cli/manifest_read.php --path . --field platform 2>/dev/null)
+ # 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"
@@ -193,32 +194,3 @@ jobs:
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; }
-
- # ── Changelog Gate ────────────────────────────────────────────────────
- changelog:
- name: Changelog Updated
- runs-on: ubuntu-latest
- if: github.base_ref == 'main'
- steps:
- - uses: actions/checkout@v4
- with:
- fetch-depth: 0
-
- - name: Check CHANGELOG.md was updated
- run: |
- BASE="${{ github.event.pull_request.base.sha }}"
- HEAD="${{ github.event.pull_request.head.sha }}"
-
- if git diff --name-only "$BASE" "$HEAD" | grep -q "^CHANGELOG.md$"; then
- echo "CHANGELOG.md updated"
- else
- # Allow [skip changelog] in PR title or body
- PR_TITLE="${{ github.event.pull_request.title }}"
- PR_BODY="${{ github.event.pull_request.body }}"
- if echo "$PR_TITLE $PR_BODY" | grep -qi "\[skip changelog\]"; then
- echo "::warning::Changelog skip requested via [skip changelog]"
- exit 0
- fi
- echo "::error::CHANGELOG.md must be updated before merging to main. Add [skip changelog] to the PR title to bypass."
- exit 1
- fi
diff --git a/.mokogitea/workflows/pre-release.yml b/.mokogitea/workflows/pre-release.yml
index 3ddd113..57d3380 100644
--- a/.mokogitea/workflows/pre-release.yml
+++ b/.mokogitea/workflows/pre-release.yml
@@ -5,7 +5,7 @@
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: moko-platform.Release
-# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
+# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
# PATH: /templates/workflows/universal/pre-release.yml.template
# VERSION: 05.00.00
# BRIEF: Manual pre-release — builds dev/alpha/beta/rc packages from any branch
@@ -49,27 +49,26 @@ jobs:
run: |
if ! command -v php &> /dev/null; then
sudo apt-get update -qq
- sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip >/dev/null 2>&1
+ sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl >/dev/null 2>&1
fi
+ - name: Setup moko-platform tools
+ env:
+ MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN }}
+ MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
+ run: |
+ git clone --depth 1 --branch main --quiet "https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" /tmp/moko-platform-api
+
- name: Detect platform
id: platform
run: |
- tr -d '[:space:]')| tr -d '[:space:]')
- [ -z "$PLATFORM" ] && PLATFORM="generic"
- echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT"
- # For packages: prefer pkg_*.xml in src/; fallback to any manifest
- MANIFEST=$(find ./src -maxdepth 1 -name "pkg_*.xml" -exec grep -l '/dev/null | head -1)
- [ -z "$MANIFEST" ] && MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" ! -path "*/packages/*" -exec grep -l '/dev/null | head -1)
- [ -z "$MANIFEST" ] && MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '/dev/null | head -1)
- MOD_FILE=$(find . -maxdepth 4 -name "mod*.class.php" ! -path "./.git/*" -exec grep -l 'extends DolibarrModules' {} \; 2>/dev/null | head -1)
- echo "manifest=${MANIFEST}" >> "$GITHUB_OUTPUT"
- echo "mod_file=${MOD_FILE}" >> "$GITHUB_OUTPUT"
+ php /tmp/moko-platform-api/cli/manifest_read.php --path . --github-output
- name: Resolve metadata
id: meta
run: |
STABILITY="${{ inputs.stability }}"
+ MOKO_API="/tmp/moko-platform-api/cli"
case "$STABILITY" in
development) SUFFIX="-dev"; TAG="development" ;;
@@ -78,66 +77,14 @@ jobs:
release-candidate) SUFFIX="-rc"; TAG="release-candidate" ;;
esac
- # Read and bump patch version (with rollover)
- CURRENT=$(sed -n 's/.*VERSION:[[:space:]]*\([0-9][0-9]\.[0-9][0-9]\.[0-9][0-9]\).*/\1/p' README.md 2>/dev/null | head -1)
- [ -z "$CURRENT" ] && CURRENT="00.00.00"
-
- MAJOR=$(echo "$CURRENT" | cut -d. -f1)
- MINOR=$(echo "$CURRENT" | cut -d. -f2)
- PATCH=$(echo "$CURRENT" | cut -d. -f3)
-
- # Patch bump with rollover: ZZ=99 → bump minor, YY=99 → bump major
- NEW_PATCH=$((10#$PATCH + 1))
- NEW_MINOR=$((10#$MINOR))
- NEW_MAJOR=$((10#$MAJOR))
-
- if [ $NEW_PATCH -gt 99 ]; then
- NEW_PATCH=0
- NEW_MINOR=$((NEW_MINOR + 1))
- fi
- if [ $NEW_MINOR -gt 99 ]; then
- NEW_MINOR=0
- NEW_MAJOR=$((NEW_MAJOR + 1))
- fi
-
- VERSION=$(printf "%02d.%02d.%02d" $NEW_MAJOR $NEW_MINOR $NEW_PATCH)
- TODAY=$(date +%Y-%m-%d)
-
- echo "Bumping: ${CURRENT} → ${VERSION} (patch)"
-
- # Update README.md
- sed -i "s/VERSION:[[:space:]]*${CURRENT}/VERSION: ${VERSION}/" README.md
+ # Bump patch version
+ BUMP_OUTPUT=$(php ${MOKO_API}/version_bump.php --path .)
+ VERSION=$(echo "$BUMP_OUTPUT" | grep -oP '\d{2}\.\d{2}\.\d{2}$' || true)
+ [ -z "$VERSION" ] && VERSION=$(php ${MOKO_API}/version_read.php --path .)
+ echo "Version: ${VERSION}"
# Update platform-specific manifest
- PLATFORM="${{ steps.platform.outputs.platform }}"
- MANIFEST="${{ steps.platform.outputs.manifest }}"
- MOD_FILE="${{ steps.platform.outputs.mod_file }}"
- case "$PLATFORM" in
- joomla)
- if [ -n "$MANIFEST" ]; then
- MANIFEST_VER=$(sed -n 's/.*\([^<]*\)<\/version>.*/\1/p' "$MANIFEST" | head -1)
- sed -i "s|${MANIFEST_VER}|${VERSION}|" "$MANIFEST"
- sed -i "s|[^<]*|${TODAY}|" "$MANIFEST"
- fi
- # For packages: also bump version in all sub-extension manifests
- if [ -d "src/packages" ]; then
- for SUB_MANIFEST in $(find src/packages -maxdepth 2 -name "*.xml" -exec grep -l '/dev/null); do
- SUB_VER=$(sed -n 's/.*\([^<]*\)<\/version>.*/\1/p' "$SUB_MANIFEST" | head -1)
- if [ -n "$SUB_VER" ]; then
- sed -i "s|${SUB_VER}|${VERSION}|" "$SUB_MANIFEST"
- sed -i "s|[^<]*|${TODAY}|" "$SUB_MANIFEST"
- echo " Bumped sub-extension: $(basename $SUB_MANIFEST) ${SUB_VER} → ${VERSION}"
- fi
- done
- fi
- ;;
- dolibarr)
- if [ -n "$MOD_FILE" ]; then
- sed -i "s/\$this->version = '[^']*'/\$this->version = '${VERSION}'/" "$MOD_FILE"
- fi
- ;;
- *) ;;
- esac
+ php ${MOKO_API}/version_set_platform.php --path . --version "${VERSION}"
# Commit version bump
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
@@ -145,40 +92,22 @@ jobs:
git remote set-url origin "https://jmiller:${{ secrets.GA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
git add -A
git diff --cached --quiet || {
- git commit -m "chore(version): bump ${CURRENT} → ${VERSION} [skip ci]"
+ git commit -m "chore(version): bump to ${VERSION} [skip ci]"
git push origin HEAD 2>&1
}
- # Auto-detect element (platform-aware)
- case "$PLATFORM" in
- joomla)
- MANIFEST="${{ steps.platform.outputs.manifest }}"
- EXT_ELEMENT=""
- if [ -n "$MANIFEST" ]; then
- EXT_ELEMENT=$(sed -n 's/.*\([^<]*\)<\/element>.*/\1/p' "$MANIFEST" 2>/dev/null | head -1)
- if [ -z "$EXT_ELEMENT" ]; then
- EXT_ELEMENT=$(basename "$MANIFEST" .xml | tr '[:upper:]' '[:lower:]')
- case "$EXT_ELEMENT" in
- templatedetails|manifest) EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') ;;
- esac
- fi
- else
- EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
- fi
- ;;
- dolibarr)
- MOD_FILE="${{ steps.platform.outputs.mod_file }}"
- if [ -n "$MOD_FILE" ]; then
- MOD_BASENAME=$(basename "$MOD_FILE" .class.php)
- EXT_ELEMENT=$(echo "$MOD_BASENAME" | sed 's/^mod//' | tr '[:upper:]' '[:lower:]')
- else
- EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
- fi
- ;;
- *)
- EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
- ;;
- esac
+ # Detect element from Joomla/Dolibarr manifest
+ PLATFORM="${{ steps.platform.outputs.platform }}"
+ EXT_ELEMENT=$(php ${MOKO_API}/manifest_read.php --path . --field name 2>/dev/null | tr -d ' ' | tr '[:upper:]' '[:lower:]' || true)
+ # For Joomla, prefer tag
+ if [ "$PLATFORM" = "joomla" ]; then
+ MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '/dev/null | head -1 || true)
+ if [ -n "$MANIFEST" ]; then
+ ELEM=$(grep -oP "\K[^<]+" "$MANIFEST" 2>/dev/null | head -1)
+ [ -n "$ELEM" ] && EXT_ELEMENT="$ELEM"
+ fi
+ fi
+ [ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
ZIP_NAME="${EXT_ELEMENT}-${VERSION}${SUFFIX}.zip"
@@ -188,83 +117,42 @@ jobs:
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
echo "zip_name=${ZIP_NAME}" >> "$GITHUB_OUTPUT"
echo "ext_element=${EXT_ELEMENT}" >> "$GITHUB_OUTPUT"
- echo "manifest=${MANIFEST}" >> "$GITHUB_OUTPUT"
echo "=== Pre-Release: ${EXT_ELEMENT} ${VERSION}${SUFFIX} ==="
- name: Build package
- run: |
- SOURCE_DIR="src"
- [ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
- if [ ! -d "$SOURCE_DIR" ]; then
- echo "::error::No src/ or htdocs/ directory"
- exit 1
- fi
-
- MANIFEST="${{ steps.meta.outputs.manifest }}"
- EXT_TYPE=""
- if [ -n "$MANIFEST" ]; then
- EXT_TYPE=$(sed -n 's/.*]*type="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
- fi
-
- EXCLUDES="sftp-config* .ftpignore *.ppk *.pem *.key .env* *.local .build-trigger"
-
- mkdir -p build/package
-
- if [ "$EXT_TYPE" = "package" ] && [ -d "${SOURCE_DIR}/packages" ]; then
- echo "=== Building Joomla PACKAGE (multi-extension) ==="
-
- # 1) ZIP each sub-extension in src/packages/
- for ext_dir in "${SOURCE_DIR}"/packages/*/; do
- [ ! -d "$ext_dir" ] && continue
- EXT_NAME=$(basename "$ext_dir")
- echo " Packaging sub-extension: ${EXT_NAME}"
- cd "$ext_dir"
- zip -r "../../build/package/${EXT_NAME}.zip" . -x $EXCLUDES
- cd "$OLDPWD"
- done
-
- # 2) Copy package-level files (manifest, script, etc.)
- for f in "${SOURCE_DIR}"/*.xml "${SOURCE_DIR}"/*.php; do
- [ -f "$f" ] && cp "$f" build/package/
- done
-
- echo "Package contents:"
- ls -la build/package/
- else
- echo "=== Building standard Joomla extension ==="
- rsync -a \
- --exclude='sftp-config*' \
- --exclude='.ftpignore' \
- --exclude='*.ppk' \
- --exclude='*.pem' \
- --exclude='*.key' \
- --exclude='.env*' \
- --exclude='*.local' \
- --exclude='.build-trigger' \
- "${SOURCE_DIR}/" build/package/
- fi
-
- - name: Create ZIP
id: zip
run: |
- ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
- cd build/package
- zip -r "../${ZIP_NAME}" .
- cd ..
+ VERSION="${{ steps.meta.outputs.version }}"
+ SUFFIX="${{ steps.meta.outputs.suffix }}"
+ PLATFORM="${{ steps.platform.outputs.platform }}"
- SHA256=$(sha256sum "${ZIP_NAME}" | cut -d' ' -f1)
- echo "sha256=${SHA256}" >> "$GITHUB_OUTPUT"
- echo "ZIP: ${ZIP_NAME} (SHA: ${SHA256:0:16}...)"
+ if [ "$PLATFORM" = "joomla" ]; then
+ php /tmp/moko-platform-api/cli/joomla_build.php --path . --version "${VERSION}" --suffix "${SUFFIX}" --output build --github-output
+ else
+ # Generic build: zip src/ directory
+ SOURCE_DIR="src"
+ [ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
+ [ ! -d "$SOURCE_DIR" ] && { echo "::error::No src/ or htdocs/"; exit 1; }
+ EXT_ELEMENT="${{ steps.meta.outputs.ext_element }}"
+ ZIP_NAME="${EXT_ELEMENT}-${VERSION}${SUFFIX}.zip"
+ mkdir -p build
+ cd "$SOURCE_DIR" && zip -r "../build/${ZIP_NAME}" . && cd ..
+ SHA256=$(sha256sum "build/${ZIP_NAME}" | cut -d' ' -f1)
+ echo "zip_name=${ZIP_NAME}" >> "$GITHUB_OUTPUT"
+ echo "zip_path=build/${ZIP_NAME}" >> "$GITHUB_OUTPUT"
+ echo "sha256=${SHA256}" >> "$GITHUB_OUTPUT"
+ fi
- name: Create or replace Gitea release
id: release
+ continue-on-error: true
run: |
TAG="${{ steps.meta.outputs.tag }}"
VERSION="${{ steps.meta.outputs.version }}"
STABILITY="${{ steps.meta.outputs.stability }}"
SHA256="${{ steps.zip.outputs.sha256 }}"
- ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
+ ZIP_NAME="${{ steps.zip.outputs.zip_name }}"
EXT_ELEMENT="${{ steps.meta.outputs.ext_element }}"
TOKEN="${{ secrets.GA_TOKEN }}"
API="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
@@ -302,99 +190,29 @@ jobs:
curl -sS -X POST -H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/octet-stream" \
"${API}/releases/${RELEASE_ID}/assets?name=${ZIP_NAME}" \
- --data-binary "@build/${ZIP_NAME}"
+ --data-binary "@${{ steps.zip.outputs.zip_path }}"
echo "Released: ${EXT_ELEMENT} ${VERSION} (${STABILITY})"
- - name: Update updates.xml
+ - name: "Update updates.xml"
if: steps.platform.outputs.platform == 'joomla'
run: |
- STABILITY="${{ steps.meta.outputs.stability }}"
VERSION="${{ steps.meta.outputs.version }}"
+ STABILITY="${{ steps.meta.outputs.stability }}"
SHA256="${{ steps.zip.outputs.sha256 }}"
- ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
- TAG="${{ steps.meta.outputs.tag }}"
- DATE=$(date +%Y-%m-%d)
-
- if [ ! -f "updates.xml" ]; then
- echo "No updates.xml — skipping"
- exit 0
- fi
-
- export PY_STABILITY="$STABILITY" PY_VERSION="$VERSION" PY_SHA256="$SHA256" \
- PY_ZIP_NAME="$ZIP_NAME" PY_TAG="$TAG" PY_DATE="$DATE" \
- PY_GITEA_ORG="$GITEA_ORG" PY_GITEA_REPO="$GITEA_REPO"
- python3 << 'PYEOF'
- import re, os
-
- stability = os.environ["PY_STABILITY"]
- version = os.environ["PY_VERSION"]
- sha256 = os.environ["PY_SHA256"]
- zip_name = os.environ["PY_ZIP_NAME"]
- tag = os.environ["PY_TAG"]
- date = os.environ["PY_DATE"]
- gitea_org = os.environ["PY_GITEA_ORG"]
- gitea_repo = os.environ["PY_GITEA_REPO"]
- download_url = f"https://git.mokoconsulting.tech/{gitea_org}/{gitea_repo}/releases/download/{tag}/{zip_name}"
-
- with open("updates.xml", "r") as f:
- content = f.read()
-
- # Map stability to XML tag name
- tag_map = {"development": "development", "alpha": "alpha", "beta": "beta", "release-candidate": "rc"}
- xml_tag = tag_map.get(stability, stability)
-
- pattern = r"((?:(?!).)*?" + re.escape(xml_tag) + r".*?)"
- match = re.search(pattern, content, re.DOTALL)
- if match:
- block = match.group(1)
- updated = re.sub(r"[^<]*", f"{version}", block)
- updated = re.sub(r"[^<]*", f"{date}", updated)
- if "" in updated:
- updated = re.sub(r"[^<]*", f"{sha256}", updated)
- else:
- updated = updated.replace("", f"\n {sha256}")
- updated = re.sub(r"(]*>)[^<]*()", rf"\g<1>{download_url}\g<2>", updated)
- content = content.replace(block, updated)
- print(f"Updated {xml_tag} channel: version={version}")
- else:
- print(f"WARNING: No {xml_tag} block in updates.xml")
-
- with open("updates.xml", "w") as f:
- f.write(content)
- PYEOF
-
- # Commit and push to current branch
+ php /tmp/moko-platform-api/cli/updates_xml_build.php --path . --version "$VERSION" --stability "$STABILITY" --sha "$SHA256" --gitea-url "$GITEA_URL" --org "$GITEA_ORG" --repo "$GITEA_REPO"
if ! git diff --quiet updates.xml 2>/dev/null; then
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]"
git add updates.xml
- git commit -m "chore: update ${STABILITY} channel ${VERSION} [skip ci]"
+ git commit -m "chore: update $STABILITY channel $VERSION [skip ci]"
git push origin HEAD 2>&1 || echo "WARNING: push failed"
fi
- name: "Sync updates.xml to all branches"
if: steps.platform.outputs.platform == 'joomla'
run: |
- CURRENT_BRANCH="${{ github.ref_name }}"
- git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
- git config --local user.name "gitea-actions[bot]"
-
- # Sync updates.xml to main and dev (whichever isn't current)
- for BRANCH in main dev; do
- [ "$BRANCH" = "$CURRENT_BRANCH" ] && continue
-
- echo "Syncing updates.xml → ${BRANCH}"
- git fetch origin "${BRANCH}" 2>/dev/null || continue
- git checkout "origin/${BRANCH}" -- . 2>/dev/null || continue
- git checkout "${CURRENT_BRANCH}" -- updates.xml
- if ! git diff --quiet updates.xml 2>/dev/null; then
- git add updates.xml
- git commit -m "chore: sync updates.xml from ${CURRENT_BRANCH} [skip ci]"
- git push origin HEAD:refs/heads/${BRANCH} 2>&1 || echo "WARNING: push to ${BRANCH} failed"
- fi
- git checkout "${CURRENT_BRANCH}" 2>/dev/null
- done
+ php /tmp/moko-platform-api/cli/updates_xml_sync.php --path . --current "${{ github.ref_name }}" --branches main,dev --version "${{ steps.meta.outputs.version }}" --token "${{ secrets.GA_TOKEN }}" --org "${GITEA_ORG}" --repo "${GITEA_REPO}" --gitea-url "${GITEA_URL}"
- name: "Delete lesser pre-release channels (cascade)"
continue-on-error: true
diff --git a/.mokogitea/workflows/repo-health.yml b/.mokogitea/workflows/repo-health.yml
index e5e1c73..d738ad7 100644
--- a/.mokogitea/workflows/repo-health.yml
+++ b/.mokogitea/workflows/repo-health.yml
@@ -7,18 +7,14 @@
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
-# INGROUP: MokoStandards.Validation
-# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API
+# INGROUP: moko-platform.Validation
+# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
# PATH: /templates/workflows/joomla/repo_health.yml.template
# VERSION: 04.06.00
# BRIEF: Enforces repository guardrails by validating release configuration, scripts governance, tooling availability, and core repository health artifacts.
# ============================================================================
-name: "Joomla: Repo Health"
-
-concurrency:
- group: repo-health-${{ github.repository }}-${{ github.ref }}
- cancel-in-progress: true
+name: "Generic: Repo Health"
defaults:
run:
@@ -288,7 +284,7 @@ jobs:
exit 0
fi
- IFS=',' read -r -a required_dirs <<< "${SCRIPTS_REQUIRED_DIRS}"
+ 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=()
@@ -392,23 +388,27 @@ jobs:
exit 0
fi
- # Source directory: src/ or htdocs/ (either is valid)
+ 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
- IFS=',' read -r -a required_artifacts <<< "${REPO_REQUIRED_ARTIFACTS}"
- IFS=',' read -r -a optional_files <<< "${REPO_OPTIONAL_FILES}"
- IFS=',' read -r -a disallowed_dirs <<< "${REPO_DISALLOWED_DIRS}"
- IFS=',' read -r -a disallowed_files <<< "${REPO_DISALLOWED_FILES}"
-
- missing_required=()
- missing_optional=()
-
for item in "${required_artifacts[@]}"; do
if printf '%s' "${item}" | grep -q '/$'; then
d="${item%/}"
@@ -450,12 +450,8 @@ jobs:
fi
done < <(git branch -r --list 'origin/dev*' | sed 's/^ *//')
- if [ "${#dev_paths[@]}" -eq 0 ]; then
- missing_required+=("dev/* branch (e.g. dev/01.00.00)")
- fi
-
- if [ "${#dev_branches[@]}" -gt 0 ]; then
- missing_required+=("invalid branch dev (must be dev/)")
+ if [ "${#dev_paths[@]}" -eq 0 ] && [ "${#dev_branches[@]}" -eq 0 ]; then
+ missing_required+=("dev or dev/* branch")
fi
content_warnings=()
@@ -481,26 +477,7 @@ jobs:
export MISSING_OPTIONAL="$(printf '%s\n' "${missing_optional[@]:-}")"
export CONTENT_WARNINGS="$(printf '%s\n' "${content_warnings[@]:-}")"
- report_json="$(python3 - <<'PY'
- import json
- import os
-
- profile = os.environ.get('PROFILE_RAW') or 'all'
-
- missing_required = os.environ.get('MISSING_REQUIRED', '').splitlines() if os.environ.get('MISSING_REQUIRED') else []
- missing_optional = os.environ.get('MISSING_OPTIONAL', '').splitlines() if os.environ.get('MISSING_OPTIONAL') else []
- content_warnings = os.environ.get('CONTENT_WARNINGS', '').splitlines() if os.environ.get('CONTENT_WARNINGS') else []
-
- out = {
- 'profile': profile,
- 'missing_required': [x for x in missing_required if x],
- 'missing_optional': [x for x in missing_optional if x],
- 'content_warnings': [x for x in content_warnings if x],
- }
-
- print(json.dumps(out, indent=2))
- PY
- )"
+ 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'
@@ -578,12 +555,14 @@ jobs:
joomla_findings+=("updates.xml missing in root (required for Joomla update server)")
fi
- 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
+ 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
{
@@ -629,43 +608,29 @@ jobs:
fi
if [ -f "${DOCS_INDEX}" ]; then
- missing_links="$(python3 - <<'PY'
- import os
- import re
-
- idx = os.environ.get('DOCS_INDEX', 'docs/docs-index.md')
- base = os.getcwd()
-
- bad = []
- pat = re.compile(r'\[[^\]]+\]\(([^)]+)\)')
-
- with open(idx, 'r', encoding='utf-8') as f:
- for line in f:
- for m in pat.findall(line):
- link = m.strip()
- if link.startswith('http://') or link.startswith('https://') or link.startswith('#') or link.startswith('mailto:'):
- continue
- if link.startswith('/'):
- rel = link.lstrip('/')
- else:
- rel = os.path.normpath(os.path.join(os.path.dirname(idx), link))
- rel = rel.split('#', 1)[0]
- rel = rel.split('?', 1)[0]
- if not rel:
- continue
- p = os.path.join(base, rel)
- if not os.path.exists(p):
- bad.append(rel)
-
- print('\n'.join(sorted(set(bad))))
- PY
- )"
+ 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:'
- while IFS= read -r l; do [ -n "${l}" ] && printf '%s\n' "- ${l}"; done <<< "${missing_links}"
+ for bl in ${missing_links}; do
+ printf '%s\n' "- ${bl}"
+ done
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
fi
@@ -764,3 +729,41 @@ jobs:
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
+
diff --git a/.mokogitea/workflows/security-audit.yml b/.mokogitea/workflows/security-audit.yml
index 789325a..714d407 100644
--- a/.mokogitea/workflows/security-audit.yml
+++ b/.mokogitea/workflows/security-audit.yml
@@ -4,8 +4,8 @@
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
-# INGROUP: MokoStandards.Security
-# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
+# INGROUP: moko-platform.Security
+# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
# PATH: /.gitea/workflows/security-audit.yml
# VERSION: 01.00.00
# BRIEF: Dependency vulnerability scanning for composer and npm packages
@@ -80,3 +80,19 @@ jobs:
-H "Priority: high" \
-d "Security audit found vulnerabilities. Review dependency updates." \
"${NTFY_URL}/${NTFY_TOPIC}" || true
+
+
+ - name: Joomla version audit
+ if: always()
+ run: |
+ if [ -f "monitoring/joomla-version-audit.php" ] && [ -n "$JOOMLA_SITES" ]; then
+ echo "$JOOMLA_SITES" > /tmp/sites.json
+ php monitoring/joomla-version-audit.php --sites /tmp/sites.json || true
+ echo "### Joomla Version Audit" >> $GITHUB_STEP_SUMMARY
+ rm -f /tmp/sites.json
+ else
+ echo "Joomla audit skipped (no script or JOOMLA_SITES_JSON not configured)"
+ fi
+ env:
+ JOOMLA_SITES: ${{ vars.JOOMLA_SITES_JSON }}
+
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 92f2bf0..6fa6c14 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -21,8 +21,8 @@
# FILE INFORMATION
DEFGROUP: MokoJoomTOS
INGROUP: plg_system_mokojoomtos
- REPO: https://github.com/mokoconsulting-tech/MokoJoomTOS
- VERSION: 04.01.00
+ REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomTOS
+ VERSION: 04.02.01
PATH: ./CHANGELOG.md
BRIEF: Version history and release notes
-->
@@ -36,6 +36,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
+### Fixed
+
+- enablePlugin() now called unconditionally in postflight() (#89)
+- Replaced Table::addIncludePath() with bootComponent()->getMVCFactory() for Joomla 5 (#90)
+- Added non-SEF URL fallback via Itemid matching (#91)
+- enablePlugin() now fires on upgrade path (#92)
+- Removed $_GET superglobal mutation (#93)
+- Added params, metadata, attribs defaults to article creation (#94)
+- Applied urldecode() to URI path before slug comparison (#95)
+- Cast Registry return to array before iterating slugs (#96)
+- Fixed MenuslugField separator `disable` to `disabled` property (#99)
+
+### Added
+
+- SEF disabled warning in MenuslugField dropdown (#97)
+- Include Children toggle for offline-accessible menu items (defaults to Yes)
+
+### Fixed (Manifest)
+
+- Hardcode description in XML manifest (language variables don't resolve during install)
+
+### Changed
+
+- Stripped legacy mokojoomtos.php to minimal stub (#101)
+- Converted script.php indentation from spaces to tabs (#102)
+- Renamed installer class to PlgSystemMokojoomtosInstallerScript (#103)
+
## [04.01.00] - 2026-05-16
### Fixed
@@ -55,7 +82,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Match against full menu route path instead of alias only (fixes nested routes like `/legal/terms-of-service`)
- Plugin pretty name updated to Joomla convention: "System - Moko Terms of Service"
-- Renamed `.mokogitea/` to `.gitea/` for standard Gitea compatibility
- MenuslugField now stores and displays full route paths (e.g., `legal/terms-of-service`)
### Removed
@@ -77,119 +103,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Added Gitea update server URL to plugin manifest (`updates.xml` on `main`)
-- Removed obsolete `src/plugins/` legacy directory
+- Fixed template chrome loading issue by changing event hook from onAfterInitialise to onAfterRoute
+- Component-only view now properly applied when site is offline
### Removed
- Legacy duplicate manifest at `src/plugins/system/mokojoomtos/mokojoomtos.xml`
-## [03.09.00] - 2026-02-28
-
-### Changed
-
-- Updated version number to 03.09.00 across all files
-- Fixed template chrome loading issue by changing event hook from onAfterInitialise to onAfterRoute
-
-### Fixed
-
-- Template chrome (header, footer, modules) no longer loads when accessing TOS page in offline mode
-- Component-only view now properly applied when site is offline
-
## [1.0.0] - 2026-01-16
### Added
-- Initial template repository structure
-- Comprehensive Makefile with 30+ build targets
-- Complete README.md with usage documentation
-- CONTRIBUTING.md with contribution guidelines
-- SECURITY.md with security policy and best practices
-- CHANGELOG.md for version tracking
-- LICENSE file (GPL-3.0-or-later)
-- EditorConfig for consistent code formatting
-- Documentation structure in `docs/` directory
-- Placeholders for component directories (`admin/`, `site/`, `media/`)
-- MokoStandards compliance:
- - File header standards with copyright and SPDX identifiers
- - Joomla coding standards configuration
- - PHPStan static analysis setup
- - Dependency security auditing
-- Makefile targets for:
- - Dependency management (install-deps, update-deps)
- - Code validation (lint, phpcs, phpstan)
- - Testing (test, test-coverage)
- - Building (build, build-assets)
- - Development (dev-install, watch-assets)
- - Release management (release, bump-version)
- - Utility commands (clean, version, help)
-- Documentation index files with MokoStandards metadata
-
-### Documentation
-
-- Comprehensive README with:
- - Quick start guide
- - Prerequisites and installation
- - Usage examples
- - Project structure overview
- - Makefile command reference
- - Standards compliance information
-- Detailed CONTRIBUTING guide with:
- - Commit message conventions
- - DCO sign-off requirements
- - Development setup instructions
- - Code review process
-- Security policy with:
- - Vulnerability reporting procedures
- - Security best practices
- - Code examples for secure development
-- Template structure for Joomla 4.x and 5.x compatibility
-
-## Version Guidelines
-
-### Version Numbering
-
-This template follows [Semantic Versioning](https://semver.org/):
-
-- **MAJOR** version for incompatible changes (e.g., 2.0.0)
-- **MINOR** version for new features (e.g., 1.1.0)
-- **PATCH** version for bug fixes (e.g., 1.0.1)
-
-### Change Categories
-
-- **Added**: New features
-- **Changed**: Changes to existing functionality
-- **Deprecated**: Soon-to-be removed features
-- **Removed**: Removed features
-- **Fixed**: Bug fixes
-- **Security**: Security fixes
-
-## Release Notes
-
-### [1.0.0] Release Notes
-
-This is the initial release of the MokoJoomTOS plugin. It provides a production-ready Joomla system plugin for offline TOS access with:
-
-- **Complete build system** via comprehensive Makefile
-- **MokoStandards compliance** out of the box
-- **Developer-friendly** workflow with automation
-- **Security-focused** with built-in best practices
-- **Well-documented** with clear usage instructions
-
-This template is ready for use in creating new Joomla components that follow organizational coding standards and best practices.
-
-### Migration Guide
-
-**For New Projects**: Simply use this template to create your new component repository.
-
-**For Existing Projects**: Review the Makefile, documentation structure, and standards compliance files to gradually adopt features that benefit your project.
+- Initial plugin release
+- Offline mode bypass for configured menu slug
+- Auto-provisioning installer (creates article, menu type, menu item)
+- Component-only rendering (`tmpl=component`)
+- Language files for en-GB and en-US
+- MokoStandards compliance
## Links
-- [Repository](https://github.com/mokoconsulting-tech/MokoJoomTOS)
-- [Issues](https://github.com/mokoconsulting-tech/MokoJoomTOS/issues)
-- [Pull Requests](https://github.com/mokoconsulting-tech/MokoJoomTOS/pulls)
-- [MokoStandards](https://github.com/mokoconsulting-tech/MokoCodingDefaults)
+- [Repository](https://git.mokoconsulting.tech/MokoConsulting/MokoJoomTOS)
+- [Issues](https://git.mokoconsulting.tech/MokoConsulting/MokoJoomTOS/issues)
+- [Releases](https://git.mokoconsulting.tech/MokoConsulting/MokoJoomTOS/releases)
-[Unreleased]: https://github.com/mokoconsulting-tech/MokoJoomTOS/compare/v03.09.00...HEAD
-[03.09.00]: https://github.com/mokoconsulting-tech/MokoJoomTOS/releases/tag/v03.09.00
-[1.0.0]: https://github.com/mokoconsulting-tech/MokoJoomTOS/releases/tag/v1.0.0
+[Unreleased]: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomTOS/compare/stable...dev
+[04.01.00]: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomTOS/releases/tag/stable
+[04.00.00]: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomTOS/compare/v03.09.00...stable
+[03.09.00]: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomTOS/releases/tag/v03.09.00
+[1.0.0]: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomTOS/releases/tag/v1.0.0
diff --git a/CLAUDE.md b/CLAUDE.md
index 3d5ba17..424c6d8 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -6,7 +6,7 @@ MokoJoomTOS is a lightweight Joomla 4.x/5.x system plugin that allows Terms of S
```
/
-├── .github/ # GitHub workflows, issue templates, copilot-instructions.md
+├── .mokogitea/ # GitHub workflows, issue templates, copilot-instructions.md
├── docs/ # Detailed documentation (currently minimal with index.md)
├── scripts/ # Build and utility scripts (validate/, package scripts)
├── src/ # Plugin source code at root level (NOT nested under plugins/)
@@ -469,6 +469,6 @@ This repository follows minimal documentation structure with essential docs in r
2. **SECURITY.md** - Security policy and vulnerability reporting procedures
3. **CODE_OF_CONDUCT.md** - Community standards and behavior expectations
4. **CHANGELOG.md** - Version history following Keep a Changelog format
-5. **.github/copilot-instructions.md** - Comprehensive guidance for GitHub Copilot (includes all Joomla patterns)
+5. **.mokogitea/copilot-instructions.md** - Comprehensive guidance for GitHub Copilot (includes all Joomla patterns)
Currently no `docs/policy/` directory exists - all policy is in root-level markdown files.
diff --git a/src/administrator/language/en-GB/plg_system_mokojoomtos.ini b/src/administrator/language/en-GB/plg_system_mokojoomtos.ini
index d26179d..cc431a8 100644
--- a/src/administrator/language/en-GB/plg_system_mokojoomtos.ini
+++ b/src/administrator/language/en-GB/plg_system_mokojoomtos.ini
@@ -11,10 +11,16 @@ PLG_SYSTEM_MOKOJOOMTOS_FIELDSET_BASIC="Basic Settings"
PLG_SYSTEM_MOKOJOOMTOS_FIELD_TOS_SLUG_LABEL="Offline-Accessible Menu Items"
PLG_SYSTEM_MOKOJOOMTOS_FIELD_TOS_SLUG_DESC="Select one or more menu items that should remain accessible when the site is in offline mode. Hold Ctrl/Cmd to select multiple items."
+PLG_SYSTEM_MOKOJOOMTOS_FIELD_INCLUDE_CHILDREN_LABEL="Include Child Menu Items"
+PLG_SYSTEM_MOKOJOOMTOS_FIELD_INCLUDE_CHILDREN_DESC="When enabled, child menu items under the selected items will also be accessible during offline mode. For example, selecting 'legal' will also allow access to 'legal/terms-of-service' and 'legal/privacy-policy'."
+
; Help
PLG_SYSTEM_MOKOJOOMTOS_HELP_LABEL="How to Use This Plugin"
PLG_SYSTEM_MOKOJOOMTOS_HELP_DESC="Step 1: Create articles for your legal pages (Terms of Service, Privacy Policy, etc.). Step 2: Create menu items pointing to those articles. Step 3: Select the menu items above (hold Ctrl/Cmd to select multiple). Step 4: When your site goes offline, visitors can still access the selected pages.
Tip: The dropdown shows the full URL path for each menu item (e.g., /legal/terms-of-service)."
+; Warnings
+PLG_SYSTEM_MOKOJOOMTOS_FIELD_SEF_WARNING="⚠ SEF URLs are disabled — path matching requires SEF. Itemid fallback is active."
+
; Errors
PLG_SYSTEM_MOKOJOOMTOS_ERROR_LOADING_MENU_ITEMS="Error loading menu items: %s"
diff --git a/src/administrator/language/en-US/plg_system_mokojoomtos.ini b/src/administrator/language/en-US/plg_system_mokojoomtos.ini
index d26179d..cc431a8 100644
--- a/src/administrator/language/en-US/plg_system_mokojoomtos.ini
+++ b/src/administrator/language/en-US/plg_system_mokojoomtos.ini
@@ -11,10 +11,16 @@ PLG_SYSTEM_MOKOJOOMTOS_FIELDSET_BASIC="Basic Settings"
PLG_SYSTEM_MOKOJOOMTOS_FIELD_TOS_SLUG_LABEL="Offline-Accessible Menu Items"
PLG_SYSTEM_MOKOJOOMTOS_FIELD_TOS_SLUG_DESC="Select one or more menu items that should remain accessible when the site is in offline mode. Hold Ctrl/Cmd to select multiple items."
+PLG_SYSTEM_MOKOJOOMTOS_FIELD_INCLUDE_CHILDREN_LABEL="Include Child Menu Items"
+PLG_SYSTEM_MOKOJOOMTOS_FIELD_INCLUDE_CHILDREN_DESC="When enabled, child menu items under the selected items will also be accessible during offline mode. For example, selecting 'legal' will also allow access to 'legal/terms-of-service' and 'legal/privacy-policy'."
+
; Help
PLG_SYSTEM_MOKOJOOMTOS_HELP_LABEL="How to Use This Plugin"
PLG_SYSTEM_MOKOJOOMTOS_HELP_DESC="Step 1: Create articles for your legal pages (Terms of Service, Privacy Policy, etc.). Step 2: Create menu items pointing to those articles. Step 3: Select the menu items above (hold Ctrl/Cmd to select multiple). Step 4: When your site goes offline, visitors can still access the selected pages.
Tip: The dropdown shows the full URL path for each menu item (e.g., /legal/terms-of-service)."
+; Warnings
+PLG_SYSTEM_MOKOJOOMTOS_FIELD_SEF_WARNING="⚠ SEF URLs are disabled — path matching requires SEF. Itemid fallback is active."
+
; Errors
PLG_SYSTEM_MOKOJOOMTOS_ERROR_LOADING_MENU_ITEMS="Error loading menu items: %s"
diff --git a/src/language/en-GB/plg_system_mokojoomtos.ini b/src/language/en-GB/plg_system_mokojoomtos.ini
index 3155c1c..7e09ab1 100644
--- a/src/language/en-GB/plg_system_mokojoomtos.ini
+++ b/src/language/en-GB/plg_system_mokojoomtos.ini
@@ -11,6 +11,9 @@ PLG_SYSTEM_MOKOJOOMTOS_FIELDSET_BASIC="Basic Settings"
PLG_SYSTEM_MOKOJOOMTOS_FIELD_TOS_SLUG_LABEL="Offline-Accessible Menu Items"
PLG_SYSTEM_MOKOJOOMTOS_FIELD_TOS_SLUG_DESC="Select one or more menu items that should remain accessible when the site is in offline mode. Hold Ctrl/Cmd to select multiple items."
+PLG_SYSTEM_MOKOJOOMTOS_FIELD_INCLUDE_CHILDREN_LABEL="Include Child Menu Items"
+PLG_SYSTEM_MOKOJOOMTOS_FIELD_INCLUDE_CHILDREN_DESC="When enabled, child menu items under the selected items will also be accessible during offline mode. For example, selecting 'legal' will also allow access to 'legal/terms-of-service' and 'legal/privacy-policy'."
+
; Help
PLG_SYSTEM_MOKOJOOMTOS_HELP_LABEL="How to Use This Plugin"
PLG_SYSTEM_MOKOJOOMTOS_HELP_DESC="Step 1: Create articles for your legal pages (Terms of Service, Privacy Policy, etc.). Step 2: Create menu items pointing to those articles. Step 3: Select the menu items above (hold Ctrl/Cmd to select multiple). Step 4: When your site goes offline, visitors can still access the selected pages.
Tip: The dropdown shows the full URL path for each menu item (e.g., /legal/terms-of-service)."
diff --git a/src/language/en-US/plg_system_mokojoomtos.ini b/src/language/en-US/plg_system_mokojoomtos.ini
index 3155c1c..7e09ab1 100644
--- a/src/language/en-US/plg_system_mokojoomtos.ini
+++ b/src/language/en-US/plg_system_mokojoomtos.ini
@@ -11,6 +11,9 @@ PLG_SYSTEM_MOKOJOOMTOS_FIELDSET_BASIC="Basic Settings"
PLG_SYSTEM_MOKOJOOMTOS_FIELD_TOS_SLUG_LABEL="Offline-Accessible Menu Items"
PLG_SYSTEM_MOKOJOOMTOS_FIELD_TOS_SLUG_DESC="Select one or more menu items that should remain accessible when the site is in offline mode. Hold Ctrl/Cmd to select multiple items."
+PLG_SYSTEM_MOKOJOOMTOS_FIELD_INCLUDE_CHILDREN_LABEL="Include Child Menu Items"
+PLG_SYSTEM_MOKOJOOMTOS_FIELD_INCLUDE_CHILDREN_DESC="When enabled, child menu items under the selected items will also be accessible during offline mode. For example, selecting 'legal' will also allow access to 'legal/terms-of-service' and 'legal/privacy-policy'."
+
; Help
PLG_SYSTEM_MOKOJOOMTOS_HELP_LABEL="How to Use This Plugin"
PLG_SYSTEM_MOKOJOOMTOS_HELP_DESC="Step 1: Create articles for your legal pages (Terms of Service, Privacy Policy, etc.). Step 2: Create menu items pointing to those articles. Step 3: Select the menu items above (hold Ctrl/Cmd to select multiple). Step 4: When your site goes offline, visitors can still access the selected pages.
Tip: The dropdown shows the full URL path for each menu item (e.g., /legal/terms-of-service)."
diff --git a/src/mokojoomtos.php b/src/mokojoomtos.php
index f7e64a0..45f95f5 100644
--- a/src/mokojoomtos.php
+++ b/src/mokojoomtos.php
@@ -9,106 +9,16 @@
defined('_JEXEC') or die;
use Joomla\CMS\Plugin\CMSPlugin;
-use Joomla\CMS\Uri\Uri;
/**
- * MokoJoomTOS Offline Mode Bypass Plugin (Legacy)
+ * MokoJoomTOS Legacy Entry Point
*
- * Allows configured menu items to remain accessible when the site
- * is in offline mode.
+ * This file is required by Joomla's plugin loader ()
+ * but is NOT executed under Joomla 5's DI container. The actual plugin logic lives
+ * in src/Extension/MokoJoomTOS.php, bootstrapped via services/provider.php.
*
* @since 1.0.0
*/
class PlgSystemMokojoomtos extends CMSPlugin
{
- /**
- * Load the language file on instantiation.
- *
- * @var boolean
- * @since 1.0.0
- */
- protected $autoloadLanguage = true;
-
- /**
- * Application object
- *
- * @var \Joomla\CMS\Application\CMSApplication
- * @since 1.0.0
- */
- protected $app;
-
- /**
- * After route event handler
- *
- * @return void
- *
- * @since 04.00.00
- */
- public function onAfterRoute()
- {
- // Only process for site application
- if (!$this->app->isClient('site'))
- {
- return;
- }
-
- // Get the global configuration
- $config = $this->app->getConfig();
-
- // Only proceed if site is offline
- if (!$config->get('offline'))
- {
- return;
- }
-
- // Get the configured slugs (stored as array for multi-select)
- $slugs = $this->params->get('tos_slug', []);
-
- // Handle legacy single-value string format
- if (is_string($slugs))
- {
- $slugs = array_filter([trim($slugs)]);
- }
-
- if (empty($slugs))
- {
- return;
- }
-
- // Get the current URI path
- $uri = Uri::getInstance();
- $path = trim($uri->getPath(), '/');
-
- // Remove the base path if present
- $base = trim(Uri::base(true), '/');
- if (!empty($base) && strpos($path, $base) === 0)
- {
- $path = trim(substr($path, strlen($base)), '/');
- }
-
- // Check if the path matches any configured slug
- foreach ($slugs as $slug)
- {
- $slug = trim($slug);
- if (empty($slug))
- {
- continue;
- }
-
- if ($path === $slug || strpos($path, $slug . '/') === 0)
- {
- // Temporarily disable offline mode for this request
- $config->set('offline', 0);
-
- // Set component-only view (no template chrome)
- $input = $this->app->input;
- $input->set('tmpl', 'component');
-
- // Also set in GET superglobal to ensure recognition
- $_GET['tmpl'] = 'component';
-
- return;
- }
- }
- }
}
diff --git a/src/mokojoomtos.xml b/src/mokojoomtos.xml
index 82b91ec..c512ce7 100644
--- a/src/mokojoomtos.xml
+++ b/src/mokojoomtos.xml
@@ -38,7 +38,7 @@
hello@mokoconsulting.techhttps://mokoconsulting.tech04.02.01
- PLG_SYSTEM_MOKOJOOMTOS_XML_DESCRIPTION
+ Allows Terms of Service to be accessible via menu slug when site is offlineJoomla\Plugin\System\MokoJoomTOS
@@ -71,6 +71,18 @@
description="PLG_SYSTEM_MOKOJOOMTOS_FIELD_TOS_SLUG_DESC"
multiple="true"
/>
+
+
+
+
+ ' . Text::_('PLG_SYSTEM_MOKOJOOMTOS_INSTALL_SUCCESS') . '
';
- }
+ /**
+ * Function called after plugin installation
+ *
+ * @param InstallerAdapter $parent Parent installer adapter
+ *
+ * @return void
+ *
+ * @since 1.0.0
+ */
+ public function install($parent)
+ {
+ echo '