Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7f67b0cf84 | |||
| 403495824d | |||
| ca8ebdd933 | |||
| c67af5a24e | |||
| 2782acefb1 | |||
| 38fd8ed411 | |||
| b0d1e42bab |
@@ -10,7 +10,7 @@
|
|||||||
<display-name>Package - MokoJoomHero</display-name>
|
<display-name>Package - MokoJoomHero</display-name>
|
||||||
<org>MokoConsulting</org>
|
<org>MokoConsulting</org>
|
||||||
<description>A Joomla Module designed to provide a random image from a folder with content on top as a Hero.</description>
|
<description>A Joomla Module designed to provide a random image from a folder with content on top as a Hero.</description>
|
||||||
<version>01.19.00</version>
|
<version>01.08.00</version>
|
||||||
<license spdx="GPL-3.0-or-later">GNU General Public License v3</license>
|
<license spdx="GPL-3.0-or-later">GNU General Public License v3</license>
|
||||||
</identity>
|
</identity>
|
||||||
<governance>
|
<governance>
|
||||||
|
|||||||
@@ -0,0 +1,464 @@
|
|||||||
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
#
|
||||||
|
# FILE INFORMATION
|
||||||
|
# DEFGROUP: Gitea.Workflow
|
||||||
|
# INGROUP: MokoStandards.Joomla
|
||||||
|
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API
|
||||||
|
# PATH: /templates/workflows/joomla/update-server.yml.template
|
||||||
|
# VERSION: 04.06.00
|
||||||
|
# BRIEF: Update Joomla update server XML feed with stable/rc/dev entries
|
||||||
|
#
|
||||||
|
# Writes updates.xml with multiple <update> entries:
|
||||||
|
# - <tag>stable</tag> on push to main (from auto-release)
|
||||||
|
# - <tag>rc</tag> on push to rc/**
|
||||||
|
# - <tag>development</tag> on push to dev or dev/**
|
||||||
|
#
|
||||||
|
# Joomla filters by user's "Minimum Stability" setting.
|
||||||
|
|
||||||
|
name: Update Joomla Update Server XML Feed
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'dev'
|
||||||
|
- 'dev/**'
|
||||||
|
- 'alpha/**'
|
||||||
|
- 'beta/**'
|
||||||
|
- 'rc/**'
|
||||||
|
paths:
|
||||||
|
- 'src/**'
|
||||||
|
- 'htdocs/**'
|
||||||
|
pull_request:
|
||||||
|
types: [closed]
|
||||||
|
branches:
|
||||||
|
- 'dev'
|
||||||
|
- 'dev/**'
|
||||||
|
- 'alpha/**'
|
||||||
|
- 'beta/**'
|
||||||
|
- 'rc/**'
|
||||||
|
paths:
|
||||||
|
- 'src/**'
|
||||||
|
- 'htdocs/**'
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
stability:
|
||||||
|
description: 'Stability tag'
|
||||||
|
required: true
|
||||||
|
default: 'development'
|
||||||
|
type: choice
|
||||||
|
options:
|
||||||
|
- development
|
||||||
|
- alpha
|
||||||
|
- beta
|
||||||
|
- rc
|
||||||
|
- stable
|
||||||
|
|
||||||
|
env:
|
||||||
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
|
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
|
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
|
||||||
|
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
update-xml:
|
||||||
|
name: Update updates.xml
|
||||||
|
runs-on: release
|
||||||
|
if: >-
|
||||||
|
github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch' || github.event_name == 'push'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GA_TOKEN }}
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup MokoStandards tools
|
||||||
|
env:
|
||||||
|
MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN }}
|
||||||
|
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||||
|
COMPOSER_AUTH: '{"http-basic":{"git.mokoconsulting.tech":{"username":"token","password":"${{ secrets.GA_TOKEN }}"}}}'
|
||||||
|
run: |
|
||||||
|
if ! command -v composer &> /dev/null; then
|
||||||
|
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
git clone --depth 1 --branch main --quiet \
|
||||||
|
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \
|
||||||
|
/tmp/mokostandards-api 2>/dev/null || true
|
||||||
|
if [ -d "/tmp/mokostandards-api" ] && [ -f "/tmp/mokostandards-api/composer.json" ]; then
|
||||||
|
cd /tmp/mokostandards-api && composer install --no-dev --no-interaction --quiet 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Generate updates.xml entry
|
||||||
|
id: update
|
||||||
|
run: |
|
||||||
|
BRANCH="${{ github.ref_name }}"
|
||||||
|
REPO="${{ github.repository }}"
|
||||||
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
|
VERSION=$(php /tmp/mokostandards-api/cli/version_read.php --path . 2>/dev/null || echo "0.0.0")
|
||||||
|
|
||||||
|
# Auto-bump patch on all branches (dev, alpha, beta, rc)
|
||||||
|
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||||
|
git config --local user.name "gitea-actions[bot]"
|
||||||
|
BUMPED=$(php /tmp/mokostandards-api/cli/version_bump.php --path . 2>/dev/null || true)
|
||||||
|
if [ -n "$BUMPED" ]; then
|
||||||
|
VERSION=$(php /tmp/mokostandards-api/cli/version_read.php --path . 2>/dev/null || echo "$VERSION")
|
||||||
|
git add -A
|
||||||
|
git commit -m "chore(version): auto-bump patch ${VERSION} [skip ci]" \
|
||||||
|
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>" 2>/dev/null || true
|
||||||
|
git push 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Determine stability from branch or input
|
||||||
|
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||||
|
STABILITY="${{ inputs.stability }}"
|
||||||
|
elif [[ "$BRANCH" == rc/* ]]; then
|
||||||
|
STABILITY="rc"
|
||||||
|
elif [[ "$BRANCH" == beta/* ]]; then
|
||||||
|
STABILITY="beta"
|
||||||
|
elif [[ "$BRANCH" == alpha/* ]]; then
|
||||||
|
STABILITY="alpha"
|
||||||
|
elif [[ "$BRANCH" == dev/* ]] || [[ "$BRANCH" == "dev" ]]; then
|
||||||
|
STABILITY="development"
|
||||||
|
else
|
||||||
|
STABILITY="stable"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
# Parse manifest (portable — no grep -P)
|
||||||
|
MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" ! -path "./build/*" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
|
||||||
|
if [ -z "$MANIFEST" ]; then
|
||||||
|
echo "No Joomla manifest found — skipping"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Extract fields using sed (works on all runners)
|
||||||
|
EXT_NAME=$(sed -n 's/.*<name>\([^<]*\)<\/name>.*/\1/p' "$MANIFEST" | head -1)
|
||||||
|
EXT_TYPE=$(sed -n 's/.*<extension[^>]*type="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
|
||||||
|
EXT_ELEMENT=$(sed -n 's/.*<element>\([^<]*\)<\/element>.*/\1/p' "$MANIFEST" | head -1)
|
||||||
|
EXT_CLIENT=$(sed -n 's/.*<extension[^>]*client="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
|
||||||
|
EXT_FOLDER=$(sed -n 's/.*<extension[^>]*group="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
|
||||||
|
EXT_VERSION=$(sed -n 's/.*<version>\([^<]*\)<\/version>.*/\1/p' "$MANIFEST" | head -1)
|
||||||
|
TARGET_PLATFORM=$(sed -n 's/.*\(<targetplatform[^/]*\/>\).*/\1/p' "$MANIFEST" | head -1)
|
||||||
|
PHP_MINIMUM=$(sed -n 's/.*<php_minimum>\([^<]*\)<\/php_minimum>.*/\1/p' "$MANIFEST" | head -1)
|
||||||
|
|
||||||
|
# Fallbacks
|
||||||
|
[ -z "$EXT_NAME" ] && EXT_NAME="${{ github.event.repository.name }}"
|
||||||
|
[ -z "$EXT_TYPE" ] && EXT_TYPE="component"
|
||||||
|
|
||||||
|
# Derive element if not in manifest: try XML filename, then repo name
|
||||||
|
if [ -z "$EXT_ELEMENT" ]; then
|
||||||
|
EXT_ELEMENT=$(basename "$MANIFEST" .xml | tr '[:upper:]' '[:lower:]')
|
||||||
|
case "$EXT_ELEMENT" in
|
||||||
|
templatedetails|manifest|*.xml) EXT_ELEMENT=$(echo "${{ github.event.repository.name }}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Use manifest version if README version is empty
|
||||||
|
[ "$VERSION" = "0.0.0" ] && [ -n "$EXT_VERSION" ] && VERSION="$EXT_VERSION"
|
||||||
|
|
||||||
|
[ -z "$TARGET_PLATFORM" ] && TARGET_PLATFORM=$(printf '<targetplatform name="joomla" version="((5.[0-9])|(6.[0-9]))" %s>' "/")
|
||||||
|
|
||||||
|
CLIENT_TAG=""
|
||||||
|
[ -n "$EXT_CLIENT" ] && CLIENT_TAG="<client>${EXT_CLIENT}</client>"
|
||||||
|
[ -z "$CLIENT_TAG" ] && ([ "$EXT_TYPE" = "module" ] || [ "$EXT_TYPE" = "plugin" ]) && CLIENT_TAG="<client>site</client>"
|
||||||
|
|
||||||
|
FOLDER_TAG=""
|
||||||
|
[ -n "$EXT_FOLDER" ] && [ "$EXT_TYPE" = "plugin" ] && FOLDER_TAG="<folder>${EXT_FOLDER}</folder>"
|
||||||
|
|
||||||
|
PHP_TAG=""
|
||||||
|
[ -n "$PHP_MINIMUM" ] && PHP_TAG="<php_minimum>${PHP_MINIMUM}</php_minimum>"
|
||||||
|
|
||||||
|
# Version suffix for non-stable
|
||||||
|
DISPLAY_VERSION="$VERSION"
|
||||||
|
case "$STABILITY" in
|
||||||
|
development) DISPLAY_VERSION="${VERSION}-dev" ;;
|
||||||
|
alpha) DISPLAY_VERSION="${VERSION}-alpha" ;;
|
||||||
|
beta) DISPLAY_VERSION="${VERSION}-beta" ;;
|
||||||
|
rc) DISPLAY_VERSION="${VERSION}-rc" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
MAJOR=$(echo "$VERSION" | awk -F. '{print $1}')
|
||||||
|
|
||||||
|
# Each stability level has its own release tag
|
||||||
|
case "$STABILITY" in
|
||||||
|
development) RELEASE_TAG="development" ;;
|
||||||
|
alpha) RELEASE_TAG="alpha" ;;
|
||||||
|
beta) RELEASE_TAG="beta" ;;
|
||||||
|
rc) RELEASE_TAG="release-candidate" ;;
|
||||||
|
*) RELEASE_TAG="v${MAJOR}" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
PACKAGE_NAME="${EXT_ELEMENT}-${DISPLAY_VERSION}.zip"
|
||||||
|
DOWNLOAD_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/${RELEASE_TAG}/${PACKAGE_NAME}"
|
||||||
|
INFO_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
|
|
||||||
|
# -- Build install packages (ZIP + tar.gz) --------------------
|
||||||
|
SOURCE_DIR="src"
|
||||||
|
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
|
||||||
|
if [ -d "$SOURCE_DIR" ]; then
|
||||||
|
EXCLUDES=".ftpignore sftp-config* *.ppk *.pem *.key .env*"
|
||||||
|
TAR_NAME="${EXT_ELEMENT}-${DISPLAY_VERSION}.tar.gz"
|
||||||
|
|
||||||
|
cd "$SOURCE_DIR"
|
||||||
|
zip -r "/tmp/${PACKAGE_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*' .
|
||||||
|
|
||||||
|
SHA256=$(sha256sum "/tmp/${PACKAGE_NAME}" | cut -d' ' -f1)
|
||||||
|
|
||||||
|
# Ensure release exists on Gitea
|
||||||
|
RELEASE_JSON=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
||||||
|
"${API_BASE}/releases/tags/${RELEASE_TAG}" 2>/dev/null || true)
|
||||||
|
RELEASE_ID=$(echo "$RELEASE_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
|
||||||
|
|
||||||
|
if [ -z "$RELEASE_ID" ]; then
|
||||||
|
# Create release
|
||||||
|
RELEASE_JSON=$(curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
"${API_BASE}/releases" \
|
||||||
|
-d "$(python3 -c "import json; print(json.dumps({
|
||||||
|
'tag_name': '${RELEASE_TAG}',
|
||||||
|
'name': '${RELEASE_TAG} (${DISPLAY_VERSION})',
|
||||||
|
'body': '${STABILITY} release',
|
||||||
|
'prerelease': True,
|
||||||
|
'target_commitish': 'main'
|
||||||
|
}))")" 2>/dev/null || true)
|
||||||
|
RELEASE_ID=$(echo "$RELEASE_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$RELEASE_ID" ]; then
|
||||||
|
# Delete existing assets with same name before uploading
|
||||||
|
ASSETS=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
||||||
|
"${API_BASE}/releases/${RELEASE_ID}/assets" 2>/dev/null || echo "[]")
|
||||||
|
for ASSET_FILE in "$PACKAGE_NAME" "$TAR_NAME"; do
|
||||||
|
ASSET_ID=$(echo "$ASSETS" | python3 -c "
|
||||||
|
import sys,json
|
||||||
|
assets = json.load(sys.stdin)
|
||||||
|
for a in assets:
|
||||||
|
if a['name'] == '${ASSET_FILE}':
|
||||||
|
print(a['id']); break
|
||||||
|
" 2>/dev/null || true)
|
||||||
|
if [ -n "$ASSET_ID" ]; then
|
||||||
|
curl -sf -X DELETE -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
||||||
|
"${API_BASE}/releases/${RELEASE_ID}/assets/${ASSET_ID}" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Upload both formats
|
||||||
|
curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
||||||
|
-H "Content-Type: application/octet-stream" \
|
||||||
|
--data-binary @"/tmp/${PACKAGE_NAME}" \
|
||||||
|
"${API_BASE}/releases/${RELEASE_ID}/assets?name=${PACKAGE_NAME}" > /dev/null 2>&1 || true
|
||||||
|
|
||||||
|
curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
||||||
|
-H "Content-Type: application/octet-stream" \
|
||||||
|
--data-binary @"/tmp/${TAR_NAME}" \
|
||||||
|
"${API_BASE}/releases/${RELEASE_ID}/assets?name=${TAR_NAME}" > /dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Packages: ${PACKAGE_NAME} + ${TAR_NAME} (SHA: ${SHA256})" >> $GITHUB_STEP_SUMMARY
|
||||||
|
else
|
||||||
|
SHA256=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# -- Build the new entry (canonical format matching release.yml) --
|
||||||
|
NEW_ENTRY=""
|
||||||
|
NEW_ENTRY="${NEW_ENTRY} <update>\n"
|
||||||
|
NEW_ENTRY="${NEW_ENTRY} <name>${EXT_NAME}</name>\n"
|
||||||
|
NEW_ENTRY="${NEW_ENTRY} <description>${EXT_NAME} ${STABILITY} build.</description>\n"
|
||||||
|
NEW_ENTRY="${NEW_ENTRY} <element>${EXT_ELEMENT}</element>\n"
|
||||||
|
NEW_ENTRY="${NEW_ENTRY} <type>${EXT_TYPE}</type>\n"
|
||||||
|
[ -n "$CLIENT_TAG" ] && NEW_ENTRY="${NEW_ENTRY} ${CLIENT_TAG}\n"
|
||||||
|
[ -n "$FOLDER_TAG" ] && NEW_ENTRY="${NEW_ENTRY} ${FOLDER_TAG}\n"
|
||||||
|
NEW_ENTRY="${NEW_ENTRY} <version>${VERSION}</version>\n"
|
||||||
|
NEW_ENTRY="${NEW_ENTRY} <creationDate>$(date +%Y-%m-%d)</creationDate>\n"
|
||||||
|
NEW_ENTRY="${NEW_ENTRY} <infourl title='${EXT_NAME}'>https://git.mokoconsulting.tech/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${RELEASE_TAG}</infourl>\n"
|
||||||
|
NEW_ENTRY="${NEW_ENTRY} <downloads>\n"
|
||||||
|
NEW_ENTRY="${NEW_ENTRY} <downloadurl type='full' format='zip'>${DOWNLOAD_URL}</downloadurl>\n"
|
||||||
|
NEW_ENTRY="${NEW_ENTRY} </downloads>\n"
|
||||||
|
[ -n "$SHA256" ] && NEW_ENTRY="${NEW_ENTRY} <sha256>${SHA256}</sha256>\n"
|
||||||
|
NEW_ENTRY="${NEW_ENTRY} <tags><tag>${STABILITY}</tag></tags>\n"
|
||||||
|
NEW_ENTRY="${NEW_ENTRY} <maintainer>Moko Consulting</maintainer>\n"
|
||||||
|
NEW_ENTRY="${NEW_ENTRY} <maintainerurl>https://mokoconsulting.tech</maintainerurl>\n"
|
||||||
|
NEW_ENTRY="${NEW_ENTRY} <targetplatform name='joomla' version='(5|6).*'/>\n"
|
||||||
|
[ -n "$PHP_MINIMUM" ] && NEW_ENTRY="${NEW_ENTRY} <php_minimum>${PHP_MINIMUM}</php_minimum>\n"
|
||||||
|
NEW_ENTRY="${NEW_ENTRY} </update>"
|
||||||
|
|
||||||
|
# -- Write new entry to temp file --------------------------------
|
||||||
|
printf '%b' "$NEW_ENTRY" > /tmp/new_entry.xml
|
||||||
|
|
||||||
|
# -- Merge into updates.xml ----------------------------------------
|
||||||
|
# Cascade: stable→all | rc→rc+lower | beta→beta+lower | alpha→alpha+dev | dev→dev
|
||||||
|
CASCADE_MAP="stable:development,alpha,beta,rc,stable rc:development,alpha,beta,rc beta:development,alpha,beta alpha:development,alpha development:development"
|
||||||
|
TARGETS=""
|
||||||
|
for entry in $CASCADE_MAP; do
|
||||||
|
key="${entry%%:*}"
|
||||||
|
vals="${entry#*:}"
|
||||||
|
if [ "$key" = "${STABILITY}" ]; then
|
||||||
|
TARGETS="$vals"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
[ -z "$TARGETS" ] && TARGETS="${STABILITY}"
|
||||||
|
|
||||||
|
echo "Cascade: ${STABILITY} → ${TARGETS}"
|
||||||
|
|
||||||
|
# Create updates.xml if missing
|
||||||
|
if [ ! -f "updates.xml" ]; then
|
||||||
|
printf '%s\n' "<?xml version='1.0' encoding='UTF-8'?>" > updates.xml
|
||||||
|
printf '%s\n' "<!-- Copyright (C) $(date +%Y) Moko Consulting -->" >> updates.xml
|
||||||
|
printf '%s\n' "<updates>" >> updates.xml
|
||||||
|
printf '%s\n' "</updates>" >> updates.xml
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Update existing blocks or create missing ones
|
||||||
|
export PY_TARGETS="$TARGETS" PY_VERSION="$VERSION" PY_DATE="$(date +%Y-%m-%d)"
|
||||||
|
python3 << 'PYEOF'
|
||||||
|
import re, os
|
||||||
|
|
||||||
|
targets = os.environ["PY_TARGETS"].split(",")
|
||||||
|
version = os.environ["PY_VERSION"]
|
||||||
|
date = os.environ["PY_DATE"]
|
||||||
|
|
||||||
|
with open("updates.xml") as f:
|
||||||
|
content = f.read()
|
||||||
|
with open("/tmp/new_entry.xml") as f:
|
||||||
|
new_entry_template = f.read()
|
||||||
|
|
||||||
|
for tag in targets:
|
||||||
|
tag = tag.strip()
|
||||||
|
# Build entry with this tag's name
|
||||||
|
new_entry = re.sub(r"<tag>[^<]*</tag>", f"<tag>{tag}</tag>", new_entry_template)
|
||||||
|
|
||||||
|
# Try to find existing block (handles both single-line and multi-line <tags>)
|
||||||
|
block_pattern = r"(<update>(?:(?!</update>).)*?<tag>" + re.escape(tag) + r"</tag>.*?</update>)"
|
||||||
|
match = re.search(block_pattern, content, re.DOTALL)
|
||||||
|
|
||||||
|
if match:
|
||||||
|
# Update in place — replace entire block
|
||||||
|
content = content.replace(match.group(1), new_entry.strip())
|
||||||
|
print(f" UPDATED: <tag>{tag}</tag> → {version}")
|
||||||
|
else:
|
||||||
|
# Create — insert before </updates>
|
||||||
|
content = content.replace("</updates>", "\n" + new_entry.strip() + "\n\n</updates>")
|
||||||
|
print(f" CREATED: <tag>{tag}</tag> → {version}")
|
||||||
|
|
||||||
|
# Clean up excessive blank lines
|
||||||
|
content = re.sub(r"\n{3,}", "\n\n", content)
|
||||||
|
|
||||||
|
with open("updates.xml", "w") as f:
|
||||||
|
f.write(content)
|
||||||
|
PYEOF
|
||||||
|
|
||||||
|
# Commit
|
||||||
|
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||||
|
git config --local user.name "gitea-actions[bot]"
|
||||||
|
git add updates.xml
|
||||||
|
git diff --cached --quiet || {
|
||||||
|
git commit -m "chore: update updates.xml (${STABILITY}: ${DISPLAY_VERSION}) [skip ci]" \
|
||||||
|
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>"
|
||||||
|
git push
|
||||||
|
}
|
||||||
|
|
||||||
|
# -- Sync updates.xml to main (for non-main branches) ----------------------
|
||||||
|
- name: Sync updates.xml to main
|
||||||
|
if: github.ref_name != 'main'
|
||||||
|
run: |
|
||||||
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
|
GA_TOKEN="${{ secrets.GA_TOKEN }}"
|
||||||
|
|
||||||
|
FILE_SHA=$(curl -sf -H "Authorization: token ${GA_TOKEN}" \
|
||||||
|
"${API_BASE}/contents/updates.xml?ref=main" | python3 -c "import sys,json; print(json.load(sys.stdin).get('sha',''))" 2>/dev/null || true)
|
||||||
|
|
||||||
|
if [ -n "$FILE_SHA" ] && [ -f "updates.xml" ]; then
|
||||||
|
CONTENT=$(base64 -w0 updates.xml)
|
||||||
|
curl -sf -X PUT -H "Authorization: token ${GA_TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
"${API_BASE}/contents/updates.xml" \
|
||||||
|
-d "$(python3 -c "import json; print(json.dumps({
|
||||||
|
'content': '${CONTENT}',
|
||||||
|
'sha': '${FILE_SHA}',
|
||||||
|
'message': 'chore: sync updates.xml from ${STABILITY} [skip ci]',
|
||||||
|
'branch': 'main'
|
||||||
|
}))")" > /dev/null 2>&1 \
|
||||||
|
&& echo "updates.xml synced to main (${STABILITY})" >> $GITHUB_STEP_SUMMARY \
|
||||||
|
|| echo "WARNING: failed to sync updates.xml to main" >> $GITHUB_STEP_SUMMARY
|
||||||
|
else
|
||||||
|
echo "WARNING: could not get updates.xml SHA from main" >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: SFTP deploy to dev server
|
||||||
|
if: contains(github.ref, 'dev/') || github.ref == 'refs/heads/dev'
|
||||||
|
env:
|
||||||
|
DEV_HOST: ${{ vars.DEV_FTP_HOST }}
|
||||||
|
DEV_PATH: ${{ vars.DEV_FTP_PATH }}
|
||||||
|
DEV_SUFFIX: ${{ vars.DEV_FTP_SUFFIX }}
|
||||||
|
DEV_USER: ${{ vars.DEV_FTP_USERNAME }}
|
||||||
|
DEV_PORT: ${{ vars.DEV_FTP_PORT }}
|
||||||
|
DEV_KEY: ${{ secrets.DEV_FTP_KEY }}
|
||||||
|
DEV_PASS: ${{ secrets.DEV_FTP_PASSWORD }}
|
||||||
|
run: |
|
||||||
|
# -- Permission check: admin or maintain role required --------
|
||||||
|
ACTOR="${{ github.actor }}"
|
||||||
|
REPO="${{ github.repository }}"
|
||||||
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
|
|
||||||
|
PERMISSION=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
||||||
|
"${API_BASE}/collaborators/${ACTOR}/permission" 2>/dev/null | \
|
||||||
|
python3 -c "import sys,json; print(json.load(sys.stdin).get('permission','read'))" 2>/dev/null || echo "read")
|
||||||
|
case "$PERMISSION" in
|
||||||
|
admin|maintain|write) ;;
|
||||||
|
*)
|
||||||
|
echo "Deploy denied: ${ACTOR} has '${PERMISSION}' — requires admin, maintain, or write"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
[ -z "$DEV_HOST" ] || [ -z "$DEV_PATH" ] && { echo "DEV FTP not configured — skipping SFTP"; exit 0; }
|
||||||
|
|
||||||
|
SOURCE_DIR="src"
|
||||||
|
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
|
||||||
|
[ ! -d "$SOURCE_DIR" ] && exit 0
|
||||||
|
|
||||||
|
PORT="${DEV_PORT:-22}"
|
||||||
|
REMOTE="${DEV_PATH%/}"
|
||||||
|
[ -n "$DEV_SUFFIX" ] && REMOTE="${REMOTE}/${DEV_SUFFIX#/}"
|
||||||
|
|
||||||
|
printf '{"host":"%s","port":%s,"username":"%s","remotePath":"%s"' \
|
||||||
|
"$DEV_HOST" "$PORT" "$DEV_USER" "$REMOTE" > /tmp/sftp-config.json
|
||||||
|
if [ -n "$DEV_KEY" ]; then
|
||||||
|
echo "$DEV_KEY" > /tmp/deploy_key && chmod 600 /tmp/deploy_key
|
||||||
|
printf ',"privateKeyPath":"/tmp/deploy_key"}' >> /tmp/sftp-config.json
|
||||||
|
else
|
||||||
|
printf ',"password":"%s"}' "$DEV_PASS" >> /tmp/sftp-config.json
|
||||||
|
fi
|
||||||
|
|
||||||
|
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 --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json
|
||||||
|
elif [ -f "/tmp/mokostandards-api/deploy/deploy-sftp.php" ]; then
|
||||||
|
php /tmp/mokostandards-api/deploy/deploy-sftp.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json
|
||||||
|
fi
|
||||||
|
rm -f /tmp/deploy_key /tmp/sftp-config.json
|
||||||
|
echo "SFTP deploy to dev complete" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
- name: Summary
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
echo "## Joomla Update Server" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Stability | \`${STABILITY}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Version | \`${DISPLAY_VERSION}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Element | \`${EXT_ELEMENT}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Download | [ZIP](${DOWNLOAD_URL}) |" >> $GITHUB_STEP_SUMMARY
|
||||||
@@ -102,14 +102,13 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
php /tmp/moko-platform-api/cli/release_publish.php \
|
php /tmp/moko-platform-api/cli/release_publish.php \
|
||||||
--path . --stability rc --bump minor --branch rc \
|
--path . --stability rc --bump minor --branch rc \
|
||||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
--token "${{ secrets.MOKOGITEA_TOKEN }}"
|
||||||
--skip-update-stream
|
|
||||||
|
|
||||||
- name: Summary
|
- name: Summary
|
||||||
if: always()
|
if: always()
|
||||||
run: |
|
run: |
|
||||||
echo "## Promoted to Release Candidate" >> $GITHUB_STEP_SUMMARY
|
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 + lesser stream releases built, updates.xml synced" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
# ── Merged PR → Build & Release (or promote RC to stable) ────────────────────
|
# ── Merged PR → Build & Release (or promote RC to stable) ────────────────────
|
||||||
release:
|
release:
|
||||||
@@ -168,8 +167,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
php /tmp/moko-platform-api/cli/release_publish.php \
|
php /tmp/moko-platform-api/cli/release_publish.php \
|
||||||
--path . --stability stable --bump minor --branch main \
|
--path . --stability stable --bump minor --branch main \
|
||||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
--token "${{ secrets.MOKOGITEA_TOKEN }}"
|
||||||
--skip-update-stream
|
|
||||||
|
|
||||||
# -- STEP 9: Mirror to GitHub (stable only) --------------------------------
|
# -- STEP 9: Mirror to GitHub (stable only) --------------------------------
|
||||||
- name: "Step 9: Mirror release to GitHub"
|
- name: "Step 9: Mirror release to GitHub"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: Gitea.Workflow
|
||||||
# INGROUP: moko-platform.Automation
|
# INGROUP: moko-platform.Automation
|
||||||
# VERSION: 01.19.00
|
# VERSION: 01.08.00
|
||||||
# BRIEF: Auto-create feature branch when an issue is opened
|
# BRIEF: Auto-create feature branch when an issue is opened
|
||||||
|
|
||||||
name: "Universal: Issue Branch"
|
name: "Universal: Issue Branch"
|
||||||
|
|||||||
@@ -147,98 +147,6 @@ jobs:
|
|||||||
echo "PHP lint: ${ERRORS} error(s)"
|
echo "PHP lint: ${ERRORS} error(s)"
|
||||||
[ "$ERRORS" -eq 0 ] || { echo "::error::PHP syntax errors found"; exit 1; }
|
[ "$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 '<extension' {} \; 2>/dev/null | head -1)
|
|
||||||
[ -z "$MANIFEST" ] && exit 0
|
|
||||||
MANIFEST_DIR=$(dirname "$MANIFEST")
|
|
||||||
|
|
||||||
# Check scriptfile exists if declared
|
|
||||||
SCRIPTFILE=$(sed -n 's/.*<scriptfile>\([^<]*\)<\/scriptfile>.*/\1/p' "$MANIFEST" 2>/dev/null)
|
|
||||||
if [ -n "$SCRIPTFILE" ]; then
|
|
||||||
if [ ! -f "${MANIFEST_DIR}/${SCRIPTFILE}" ]; then
|
|
||||||
echo "::error::Manifest declares <scriptfile>${SCRIPTFILE}</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
|
- name: Validate platform manifest
|
||||||
run: |
|
run: |
|
||||||
PLATFORM="${{ steps.platform.outputs.platform }}"
|
PLATFORM="${{ steps.platform.outputs.platform }}"
|
||||||
@@ -256,13 +164,6 @@ jobs:
|
|||||||
for ELEMENT in name version description; do
|
for ELEMENT in name version description; do
|
||||||
grep -q "<${ELEMENT}>" "$MANIFEST" || { echo "::error::Missing <${ELEMENT}> in manifest"; exit 1; }
|
grep -q "<${ELEMENT}>" "$MANIFEST" || { echo "::error::Missing <${ELEMENT}> in manifest"; exit 1; }
|
||||||
done
|
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"
|
echo "Joomla manifest valid"
|
||||||
;;
|
;;
|
||||||
dolibarr)
|
dolibarr)
|
||||||
@@ -295,138 +196,6 @@ jobs:
|
|||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
- 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
|
- name: Check changelog has unreleased entry
|
||||||
run: |
|
run: |
|
||||||
if [ ! -f "CHANGELOG.md" ]; then
|
if [ ! -f "CHANGELOG.md" ]; then
|
||||||
|
|||||||
@@ -1,243 +0,0 @@
|
|||||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
#
|
|
||||||
# FILE INFORMATION
|
|
||||||
# DEFGROUP: Gitea.Workflow
|
|
||||||
# INGROUP: moko-platform.Release
|
|
||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/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
|
|
||||||
|
|
||||||
name: "Universal: Pre-Release"
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
types: [closed]
|
|
||||||
branches:
|
|
||||||
- dev
|
|
||||||
pull_request_target:
|
|
||||||
types: [synchronize, opened, reopened]
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
stability:
|
|
||||||
description: 'Pre-release channel'
|
|
||||||
required: true
|
|
||||||
type: choice
|
|
||||||
options:
|
|
||||||
- development
|
|
||||||
- alpha
|
|
||||||
- beta
|
|
||||||
- release-candidate
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
env:
|
|
||||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
|
||||||
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
|
|
||||||
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: "Build Pre-Release (${{ inputs.stability || 'development' }})"
|
|
||||||
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')
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
|
||||||
ref: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || '' }}
|
|
||||||
|
|
||||||
- name: Setup moko-platform tools
|
|
||||||
env:
|
|
||||||
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
|
||||||
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
|
||||||
run: |
|
|
||||||
# Use pre-installed /opt/moko-platform if available (updated by cron every 6h)
|
|
||||||
if [ -f /opt/moko-platform/cli/version_bump.php ] && [ -f /opt/moko-platform/cli/manifest_element.php ] && [ -f /opt/moko-platform/vendor/autoload.php ]; then
|
|
||||||
echo Using pre-installed /opt/moko-platform
|
|
||||||
echo MOKO_CLI=/opt/moko-platform/cli >> $GITHUB_ENV
|
|
||||||
else
|
|
||||||
echo Falling back to fresh clone
|
|
||||||
if ! command -v composer > /dev/null 2>&1; then
|
|
||||||
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer > /dev/null 2>&1
|
|
||||||
fi
|
|
||||||
rm -rf /tmp/moko-platform-api
|
|
||||||
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git
|
|
||||||
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/moko-platform-api
|
|
||||||
cd /tmp/moko-platform-api && composer install --no-dev --no-interaction --quiet
|
|
||||||
echo MOKO_CLI=/tmp/moko-platform-api/cli >> $GITHUB_ENV
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Detect platform
|
|
||||||
id: platform
|
|
||||||
run: |
|
|
||||||
php ${MOKO_CLI}/manifest_read.php --path . --github-output
|
|
||||||
|
|
||||||
- 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"
|
|
||||||
else
|
|
||||||
STABILITY="${{ inputs.stability || 'development' }}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
case "$STABILITY" in
|
|
||||||
development) SUFFIX="-dev"; TAG="development" ;;
|
|
||||||
alpha) SUFFIX="-alpha"; TAG="alpha" ;;
|
|
||||||
beta) SUFFIX="-beta"; TAG="beta" ;;
|
|
||||||
release-candidate) SUFFIX="-rc"; TAG="release-candidate" ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# Bump version via CLI: patch for dev/alpha/beta, minor for RC
|
|
||||||
case "$STABILITY" in
|
|
||||||
release-candidate) BUMP="minor" ;;
|
|
||||||
*) BUMP="patch" ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
php ${MOKO_CLI}/version_bump.php --path . $([ "$BUMP" = "minor" ] && echo "--minor") 2>/dev/null || true
|
|
||||||
|
|
||||||
# Set stability suffix and verify consistency
|
|
||||||
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null || echo "00.00.01")
|
|
||||||
VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//')
|
|
||||||
|
|
||||||
php ${MOKO_CLI}/version_set_platform.php \
|
|
||||||
--path . --version "$VERSION" --branch "${{ github.ref_name }}" --stability "$STABILITY" 2>/dev/null || true
|
|
||||||
php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true
|
|
||||||
|
|
||||||
# Ensure licensing tags (updateservers, dlid) if enabled in manifest.xml
|
|
||||||
php ${MOKO_CLI}/manifest_licensing.php --path . --fix 2>/dev/null || true
|
|
||||||
|
|
||||||
# Append suffix for output
|
|
||||||
if [ -n "$SUFFIX" ]; then
|
|
||||||
VERSION="${VERSION}${SUFFIX}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Commit version bump
|
|
||||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
|
||||||
git config --local user.name "gitea-actions[bot]"
|
|
||||||
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
|
||||||
git add -A
|
|
||||||
git diff --cached --quiet || {
|
|
||||||
git commit -m "chore(version): pre-release bump to ${VERSION} [skip ci]"
|
|
||||||
git push origin HEAD 2>&1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Auto-detect element via manifest_element.php
|
|
||||||
php ${MOKO_CLI}/manifest_element.php \
|
|
||||||
--path . --version "$VERSION" --stability "$STABILITY" \
|
|
||||||
--repo "${GITEA_REPO}" --github-output
|
|
||||||
|
|
||||||
# Read back element outputs
|
|
||||||
EXT_ELEMENT=$(grep '^ext_element=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
|
|
||||||
ZIP_NAME=$(grep '^zip_name=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
|
|
||||||
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
|
|
||||||
[ -z "$ZIP_NAME" ] && ZIP_NAME="${EXT_ELEMENT}-${VERSION}.zip"
|
|
||||||
|
|
||||||
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "suffix=${SUFFIX}" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "zip_name=${ZIP_NAME}" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "ext_element=${EXT_ELEMENT}" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
echo "=== Pre-Release: ${EXT_ELEMENT} ${VERSION}${SUFFIX} ==="
|
|
||||||
|
|
||||||
- name: Create release
|
|
||||||
id: release
|
|
||||||
run: |
|
|
||||||
TAG="${{ steps.meta.outputs.tag }}"
|
|
||||||
VERSION="${{ steps.meta.outputs.version }}"
|
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
|
||||||
php ${MOKO_CLI}/release_create.php \
|
|
||||||
--path . --version "$VERSION" --tag "$TAG" \
|
|
||||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
|
||||||
--repo "${GITEA_REPO}" --branch dev --prerelease
|
|
||||||
|
|
||||||
- name: Update release notes from CHANGELOG.md
|
|
||||||
run: |
|
|
||||||
TAG="${{ steps.meta.outputs.tag }}"
|
|
||||||
VERSION="${{ steps.meta.outputs.version }}"
|
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
|
||||||
|
|
||||||
# Extract [Unreleased] section from changelog (everything between [Unreleased] and next ## heading)
|
|
||||||
if [ -f "CHANGELOG.md" ]; then
|
|
||||||
NOTES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md)
|
|
||||||
[ -z "$NOTES" ] && NOTES="Release ${VERSION}"
|
|
||||||
else
|
|
||||||
NOTES="Release ${VERSION}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Update release body via API
|
|
||||||
RELEASE_ID=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \
|
|
||||||
"${API_BASE}/releases/tags/${TAG}" | python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
|
|
||||||
|
|
||||||
if [ -n "$RELEASE_ID" ]; then
|
|
||||||
python3 -c "
|
|
||||||
import json, urllib.request
|
|
||||||
body = open('/dev/stdin').read()
|
|
||||||
payload = json.dumps({'body': body}).encode()
|
|
||||||
req = urllib.request.Request(
|
|
||||||
'${API_BASE}/releases/${RELEASE_ID}',
|
|
||||||
data=payload, method='PATCH',
|
|
||||||
headers={
|
|
||||||
'Authorization': 'token ${{ secrets.MOKOGITEA_TOKEN }}',
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
})
|
|
||||||
urllib.request.urlopen(req)
|
|
||||||
" <<< "$NOTES"
|
|
||||||
echo "Release notes updated from CHANGELOG.md"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Build package and upload
|
|
||||||
id: package
|
|
||||||
run: |
|
|
||||||
VERSION="${{ steps.meta.outputs.version }}"
|
|
||||||
TAG="${{ steps.meta.outputs.tag }}"
|
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
|
||||||
php ${MOKO_CLI}/release_package.php \
|
|
||||||
--path . --version "$VERSION" --tag "$TAG" \
|
|
||||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
|
||||||
--repo "${GITEA_REPO}" --output /tmp || true
|
|
||||||
|
|
||||||
# updates.xml is generated dynamically by MokoGitea license server
|
|
||||||
# No need to build, commit, or sync updates.xml from workflows
|
|
||||||
|
|
||||||
- name: "Delete lesser pre-release channels (cascade)"
|
|
||||||
continue-on-error: true
|
|
||||||
run: |
|
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
|
||||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
|
||||||
|
|
||||||
php ${MOKO_CLI}/release_cascade.php \
|
|
||||||
--stability "${{ steps.meta.outputs.stability }}" \
|
|
||||||
--token "${TOKEN}" \
|
|
||||||
--api-base "${API_BASE}"
|
|
||||||
|
|
||||||
- name: Summary
|
|
||||||
if: always()
|
|
||||||
run: |
|
|
||||||
VERSION="${{ steps.meta.outputs.version }}"
|
|
||||||
STABILITY="${{ steps.meta.outputs.stability }}"
|
|
||||||
ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
|
|
||||||
SHA256="${{ steps.package.outputs.sha256_zip }}"
|
|
||||||
echo "## Pre-Release Complete" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "| Channel | ${STABILITY} |" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "| Package | \`${ZIP_NAME}\` |" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "| SHA-256 | \`${SHA256:-n/a}\` |" >> $GITHUB_STEP_SUMMARY
|
|
||||||
+32
-18
@@ -1,32 +1,46 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
## [01.19.00] --- 2026-06-04
|
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
## [01.18.00] --- 2026-06-04
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||||
|
Version format: `XX.YY.ZZ` (zero-padded semver).
|
||||||
|
|
||||||
## [01.17] - 2026-06-04
|
## [01.08.00] --- 2026-06-04
|
||||||
|
|
||||||
|
## [01.04.00] - 2026-05-30
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Download key support (`dlid`) in module manifest for update server authentication
|
- Local Video hero mode with Joomla Media Manager file picker (`localVideoFile` param)
|
||||||
- Auto-remove deprecated system plugin (`plg_system_mokojoomhero`) on install/upgrade
|
- Install script (`script.php`) creates `images/heroes/` folder on install/update
|
||||||
|
|
||||||
|
## [01.03.00] - 2026-05-30
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Configurable card fade-in delay with slide-up animation (`cardDelay` param, 0-5000ms) (#39)
|
||||||
|
- Video mute/unmute toggle button (`showMuteToggle` param) -- supports YouTube, Vimeo, and native video (#40)
|
||||||
|
|
||||||
|
## [01.02.00] - 2026-05-30
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Skip overlay background on solid colour and gradient hero modes — gradient was invisible behind 50% black overlay
|
- WebAsset registration: added `#style`/`#script` suffixes to preset dependencies (was causing `UnsatisfiedDependencyException`)
|
||||||
|
- Asset URI resolution: use `extension/filename` pattern instead of `extension/folder/filename` (Joomla auto-inserts `css/`/`js/` folders)
|
||||||
|
- iframe video cover: split CSS into `<video>` (`object-fit: cover`) and `<iframe>` (oversize + centre-crop) rules since `object-fit` doesn't apply to iframes
|
||||||
|
- Card link styling: exclude `.btn` elements from `color: inherit` so buttons retain their own styles
|
||||||
|
|
||||||
## [01.14] - 2026-06-04
|
### Added
|
||||||
|
- Module title renders inside the hero card as `<h2>`, respecting Joomla's Show Title toggle
|
||||||
|
- IntersectionObserver pauses/resumes videos when the hero scrolls out of/into the viewport (YouTube, Vimeo, and native `<video>`)
|
||||||
|
- YouTube embeds include `enablejsapi=1` for postMessage playback control
|
||||||
|
|
||||||
|
## [01.01.00] - 2026-05-30
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Update server URL to new Gitea release system
|
- Migrated all workflow and template paths from `.github/` to `.mokogitea/`
|
||||||
- Use pretty name format for update server entry
|
- Template source paths updated
|
||||||
|
- HCL definition files removed -- Template repos are now the canonical source
|
||||||
|
|
||||||
### Fixed
|
### Added
|
||||||
- Add missing `<updateservers>` section to module manifest
|
- `branch-cleanup.yml`: auto-delete merged feature branches after PR merge
|
||||||
|
|
||||||
## [01.13] - 2026-06-04
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- Restructure from package extension back to standalone site module
|
|
||||||
- Remove system plugin (`plg_system_mokojoomhero`) and package wrapper
|
|
||||||
- Simplify build target for module ZIP
|
|
||||||
|
|||||||
@@ -32,21 +32,25 @@ composer install # Install PHP dependencies
|
|||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
This is a Joomla **site module** (`mod_mokojoomhero`).
|
This is a Joomla **package** extension (`pkg_mokojoomhero`) containing two sub-extensions:
|
||||||
|
|
||||||
|
### mod_mokojoomhero (Site Module)
|
||||||
- Random hero image slideshow or background video with content overlay
|
- Random hero image slideshow or background video with content overlay
|
||||||
- Supports image folders, YouTube, Vimeo, local video, solid colour, gradient
|
- Supports image folders, YouTube, Vimeo, local video, solid colour, gradient
|
||||||
- Configurable overlay, text alignment, card animation, parallax, content animations
|
- Configurable overlay, text alignment, card animation, parallax, content animations
|
||||||
- A/B testing with weighted variations, scheduling with start/end datetime
|
- Works independently — no plugin dependency required
|
||||||
- Article content source, per-slide unique content via subform
|
|
||||||
|
### plg_system_mokojoomhero (System Plugin)
|
||||||
|
- Placeholder for future system-level features
|
||||||
|
- Auto-enabled on package install via `pkg_script.php`
|
||||||
|
- Namespace: `Joomla\Plugin\System\MokoJoomHero`
|
||||||
|
|
||||||
### Key files
|
### Key files
|
||||||
- `src/mod_mokojoomhero.xml` — module manifest
|
- `src/pkg_mokojoomhero.xml` — package manifest
|
||||||
- `src/mod_mokojoomhero.php` — module entry point
|
- `src/pkg_script.php` — auto-enables system plugin on install
|
||||||
- `src/tmpl/default.php` — template
|
- `src/packages/mod_mokojoomhero/` — hero module source
|
||||||
- `src/media/` — CSS, JS, and web asset registry
|
- `src/packages/plg_system_mokojoomhero/` — system plugin source
|
||||||
- `src/language/` — en-GB and en-US translations
|
- `updates.xml` — Joomla update server (includes legacy module entries for migration)
|
||||||
- `src/script.php` — install script (creates default image folder)
|
|
||||||
|
|
||||||
## Rules
|
## Rules
|
||||||
|
|
||||||
@@ -61,4 +65,7 @@ This is a Joomla **site module** (`mod_mokojoomhero`).
|
|||||||
## Coding Standards
|
## Coding Standards
|
||||||
|
|
||||||
- PHP 8.1+ minimum
|
- PHP 8.1+ minimum
|
||||||
|
- Joomla 5/6 DI container pattern: `services/provider.php` → Extension class
|
||||||
|
- Legacy stub `.php` file required for plugin loader but empty
|
||||||
|
- `SubscriberInterface` for event subscription (not `on*` method naming)
|
||||||
- SPDX license headers on all PHP files
|
- SPDX license headers on all PHP files
|
||||||
|
|||||||
+1
-1
@@ -14,7 +14,7 @@
|
|||||||
DEFGROUP:
|
DEFGROUP:
|
||||||
INGROUP: Project.Documentation
|
INGROUP: Project.Documentation
|
||||||
REPO:
|
REPO:
|
||||||
VERSION: 01.19.00
|
VERSION: 01.08.00
|
||||||
PATH: ./CODE_OF_CONDUCT.md
|
PATH: ./CODE_OF_CONDUCT.md
|
||||||
BRIEF: Reference + packaging repo for Moko Consulting Developer GPT Other Default
|
BRIEF: Reference + packaging repo for Moko Consulting Developer GPT Other Default
|
||||||
-->
|
-->
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
# Extension Configuration
|
# Extension Configuration
|
||||||
EXTENSION_NAME := mokojoomhero
|
EXTENSION_NAME := mokojoomhero
|
||||||
EXTENSION_TYPE := module
|
EXTENSION_TYPE := package
|
||||||
# Options: module, plugin, component, package, template
|
# Options: module, plugin, component, package, template
|
||||||
EXTENSION_VERSION := 1.0.0
|
EXTENSION_VERSION := 1.0.0
|
||||||
|
|
||||||
@@ -156,11 +156,29 @@ clean: ## Clean build artifacts
|
|||||||
|
|
||||||
.PHONY: build
|
.PHONY: build
|
||||||
build: clean ## Build extension package
|
build: clean ## Build extension package
|
||||||
@echo "$(COLOR_BLUE)Building Joomla module extension...$(COLOR_RESET)"
|
@echo "$(COLOR_BLUE)Building Joomla package extension...$(COLOR_RESET)"
|
||||||
@mkdir -p $(DIST_DIR)
|
@mkdir -p $(DIST_DIR) $(BUILD_DIR)/packages
|
||||||
@cd $(SRC_DIR) && $(ZIP) -r "$(CURDIR)/$(DIST_DIR)/mod_$(EXTENSION_NAME)-$(EXTENSION_VERSION).zip" . \
|
|
||||||
-x "*.git*" -x "*/index.html" 2>/dev/null
|
@# --- Build each sub-extension as a separate ZIP ---
|
||||||
@echo "$(COLOR_GREEN)✓ Module created: $(DIST_DIR)/mod_$(EXTENSION_NAME)-$(EXTENSION_VERSION).zip$(COLOR_RESET)"
|
@for EXT_DIR in $(SRC_DIR)/packages/*/; do \
|
||||||
|
EXT_NAME=$$(basename "$$EXT_DIR"); \
|
||||||
|
[ "$$EXT_NAME" = "index.html" ] && continue; \
|
||||||
|
echo " Packaging $$EXT_NAME..."; \
|
||||||
|
cd "$$EXT_DIR" && $(ZIP) -r "$(CURDIR)/$(BUILD_DIR)/packages/$${EXT_NAME}.zip" . \
|
||||||
|
-x "*.git*" -x "*/index.html" 2>/dev/null; \
|
||||||
|
cd "$(CURDIR)"; \
|
||||||
|
done
|
||||||
|
|
||||||
|
@# --- Build the outer package ZIP ---
|
||||||
|
@echo " Assembling pkg_$(EXTENSION_NAME)..."
|
||||||
|
@cp $(SRC_DIR)/pkg_mokojoomhero.xml $(BUILD_DIR)/pkg_mokojoomhero.xml
|
||||||
|
@cp $(SRC_DIR)/pkg_script.php $(BUILD_DIR)/pkg_script.php
|
||||||
|
@cd $(BUILD_DIR) && $(ZIP) -r "$(CURDIR)/$(DIST_DIR)/pkg_$(EXTENSION_NAME)-$(EXTENSION_VERSION).zip" \
|
||||||
|
pkg_mokojoomhero.xml pkg_script.php packages/
|
||||||
|
|
||||||
|
@echo "$(COLOR_GREEN)✓ Package created: $(DIST_DIR)/pkg_$(EXTENSION_NAME)-$(EXTENSION_VERSION).zip$(COLOR_RESET)"
|
||||||
|
@echo " Contents:"
|
||||||
|
@unzip -l "$(DIST_DIR)/pkg_$(EXTENSION_NAME)-$(EXTENSION_VERSION).zip" | tail -n +4 | head -20
|
||||||
|
|
||||||
.PHONY: package
|
.PHONY: package
|
||||||
package: build ## Alias for build
|
package: build ## Alias for build
|
||||||
|
|||||||
@@ -7,83 +7,108 @@
|
|||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
|
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
|
||||||
FILE: ./README.md
|
FILE: ./README.md
|
||||||
BRIEF: MokoJoomHero - Joomla Hero Module
|
VERSION: 01.08.00
|
||||||
|
BRIEF: MokoJoomHero - Joomla Module
|
||||||
-->
|
-->
|
||||||
|
|
||||||
# MokoJoomHero
|
# MokoJoomHero
|
||||||
|
|
||||||
A Joomla site module for random hero image slideshows, video backgrounds, solid colours, gradients, and content overlays.
|
A Joomla 5 site template developed following MokoStandards.
|
||||||
|
|
||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
MokoJoomHero is a free, open-source Joomla module that creates dynamic hero sections with multiple background modes, configurable content overlays, and advanced features like parallax scrolling, A/B testing, and scheduling. Designed for the MokoOnyx template but works with any Joomla 5/6 template.
|
MokoJoomHero is a modern, accessible Joomla 5 template by Moko Consulting. It features a prominent hero section, flexible module positions, sticky header, customisable brand colour, and responsive layout — all built on Joomla's Web Asset Manager.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
### Background Modes
|
- **Hero Section**: Dedicated `hero` module position with gradient styling
|
||||||
- **Image Slideshow** — random images from a folder with 4 transition types (crossfade, slide, fade-to-black, zoom/Ken Burns)
|
- **Flexible Layout**: 11 module positions including sidebars, topbar, banner, and footer
|
||||||
- **Video** — YouTube, Vimeo, or local video with autoplay, mute toggle, and poster image
|
- **Sticky Header**: Optional sticky navigation with smooth scroll
|
||||||
- **Solid Colour** — single colour picker
|
- **Brand Customisation**: Configurable brand colour, logo, and tagline via Template Manager
|
||||||
- **Gradient** — two-colour gradient with configurable angle
|
- **Responsive**: Mobile-first layout with CSS custom properties
|
||||||
|
- **Accessibility**: Semantic HTML5, ARIA landmarks, skip-to-content support
|
||||||
### Content & Overlay
|
- **Web Asset Manager**: Modern asset loading via `joomla.asset.json`
|
||||||
- **WYSIWYG Editor** or **Joomla Article** as content source
|
- **Error & Offline Pages**: Custom styled error and offline templates
|
||||||
- **Per-slide content** — unique heading, body, and CTA per image slide via subform
|
|
||||||
- **Card mode** — white card with shadow and fade-in delay
|
|
||||||
- **Overlay** — solid or directional gradient (dark at bottom/top/left/right)
|
|
||||||
- **Text alignment** — horizontal (left/center/right) and vertical (top/center/bottom)
|
|
||||||
- **Content animations** — fade-in, slide-up, slide-left, slide-right with configurable delay
|
|
||||||
|
|
||||||
### Advanced
|
|
||||||
- **Parallax scroll** — background moves at configurable speed (0.1–0.9)
|
|
||||||
- **A/B testing** — weighted random content variations, session-sticky per visitor
|
|
||||||
- **Scheduling** — start/end datetime with site timezone support
|
|
||||||
- **Scroll indicator** — animated chevron with smooth-scroll click handler
|
|
||||||
- **Mobile height** — separate height setting for mobile viewports
|
|
||||||
- **Reduced motion** — WCAG 2.1 AA compliant, respects `prefers-reduced-motion`
|
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- **PHP**: 8.1 or higher
|
- **PHP**: 8.1 or higher
|
||||||
- **Joomla**: 5.x or 6.x
|
- **Joomla**: 5.x (also compatible with 6.x)
|
||||||
|
- **Make**: GNU Make for build automation
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
1. Download the latest release from the [releases page](https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/releases)
|
1. Build the template package:
|
||||||
2. Upload `mod_mokojoomhero-*.zip` via **System > Install > Upload Package File**
|
```bash
|
||||||
3. Assign the module to a template position and configure
|
make build
|
||||||
|
```
|
||||||
|
|
||||||
Or build from source:
|
2. Upload the generated `tpl_mokojoomhero-1.0.0.zip` via Joomla's Extension Manager
|
||||||
|
|
||||||
```bash
|
3. Set as default template in **System → Site Template Styles**
|
||||||
make build
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
1. Go to **Content > Site Modules > MokoJoomHero**
|
```bash
|
||||||
2. Select a **Hero Mode** (Images, Video, Solid Colour, or Gradient)
|
# Display available commands
|
||||||
3. Add content via the **Hero Content** tab
|
make help
|
||||||
4. Configure overlay, text alignment, and animations in **Overlay & Text**
|
|
||||||
5. Assign to a module position and publish
|
# Validate code
|
||||||
|
make validate
|
||||||
|
|
||||||
|
# Build template package
|
||||||
|
make build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Module Positions
|
||||||
|
|
||||||
|
| Position | Purpose |
|
||||||
|
|----------|---------|
|
||||||
|
| `topbar` | Slim bar above the header (contact info, social links) |
|
||||||
|
| `banner` | Full-width banner below the header |
|
||||||
|
| `menu` | Main navigation inside the header |
|
||||||
|
| `hero` | Hero section with gradient background |
|
||||||
|
| `breadcrumbs` | Breadcrumb trail |
|
||||||
|
| `sidebar-left` | Left sidebar |
|
||||||
|
| `sidebar-right` | Right sidebar |
|
||||||
|
| `main-top` | Above the main content area |
|
||||||
|
| `main-bottom` | Below the main content area |
|
||||||
|
| `footer` | Footer area |
|
||||||
|
| `debug` | Debug output (hidden from users) |
|
||||||
|
|
||||||
|
## Template Parameters
|
||||||
|
|
||||||
|
Configure via **System → Site Template Styles → MokoJoomHero**:
|
||||||
|
|
||||||
|
- **Logo** — Upload a site logo image
|
||||||
|
- **Site Description** — Tagline displayed next to the logo
|
||||||
|
- **Brand Colour** — Primary accent colour (default: `#1a73e8`)
|
||||||
|
- **Fluid Container** — Toggle full-width vs fixed-width layout
|
||||||
|
- **Sticky Header** — Keep the header visible on scroll
|
||||||
|
- **Back to Top** — Floating scroll-to-top button
|
||||||
|
|
||||||
## Project Structure
|
## Project Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
src/
|
.
|
||||||
├── mod_mokojoomhero.php # Module entry point
|
├── docs/ # Documentation files
|
||||||
├── mod_mokojoomhero.xml # Joomla manifest
|
├── scripts/ # Build and deployment scripts
|
||||||
├── script.php # Install script
|
├── src/ # Template source code
|
||||||
├── tmpl/default.php # Template
|
│ ├── css/template.css # Main stylesheet
|
||||||
├── media/
|
│ ├── js/template.js # Main JavaScript
|
||||||
│ ├── css/mod_mokojoomhero.css
|
│ ├── images/ # Template images
|
||||||
│ ├── js/mod_mokojoomhero.js
|
│ ├── html/ # Template overrides
|
||||||
│ └── joomla.asset.json
|
│ ├── language/en-GB/ # Language files
|
||||||
└── language/
|
│ ├── templateDetails.xml # Joomla manifest
|
||||||
├── en-GB/
|
│ ├── joomla.asset.json # Web Asset Manager config
|
||||||
└── en-US/
|
│ ├── index.php # Main template
|
||||||
|
│ ├── error.php # Error page
|
||||||
|
│ ├── offline.php # Offline page
|
||||||
|
│ └── component.php # Component-only output
|
||||||
|
├── Makefile # Build automation
|
||||||
|
└── README.md # This file
|
||||||
```
|
```
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
@@ -92,7 +117,9 @@ We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guid
|
|||||||
|
|
||||||
## Versioning
|
## Versioning
|
||||||
|
|
||||||
This project uses [Semantic Versioning](https://semver.org/). See [CHANGELOG.md](CHANGELOG.md) for version history.
|
This project uses [Semantic Versioning](https://semver.org/).
|
||||||
|
|
||||||
|
See [CHANGELOG.md](CHANGELOG.md) for version history.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
@@ -104,7 +131,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
|||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
|
||||||
- **Wiki**: [MokoJoomHero Wiki](https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/wiki)
|
- **Documentation**: See the [docs/](docs/) directory
|
||||||
- **Issues**: [Issue Tracker](https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/issues)
|
|
||||||
- **Contact**: hello@mokoconsulting.tech
|
- **Contact**: hello@mokoconsulting.tech
|
||||||
- **Website**: [mokoconsulting.tech](https://mokoconsulting.tech)
|
- **Website**: [mokoconsulting.tech](https://mokoconsulting.tech)
|
||||||
|
|||||||
+1
-1
@@ -23,7 +23,7 @@ DEFGROUP: [PROJECT_NAME]
|
|||||||
INGROUP: [PROJECT_NAME].Documentation
|
INGROUP: [PROJECT_NAME].Documentation
|
||||||
REPO: [REPOSITORY_URL]
|
REPO: [REPOSITORY_URL]
|
||||||
PATH: /SECURITY.md
|
PATH: /SECURITY.md
|
||||||
VERSION: 01.19.00
|
VERSION: 01.08.00
|
||||||
BRIEF: Security vulnerability reporting and handling policy
|
BRIEF: Security vulnerability reporting and handling policy
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
|||||||
+1
-20
@@ -6,7 +6,7 @@
|
|||||||
; INGROUP: MokoJoomHero.Module
|
; INGROUP: MokoJoomHero.Module
|
||||||
; REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
|
; REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
|
||||||
; PATH: /src/language/en-GB/mod_mokojoomhero.ini
|
; PATH: /src/language/en-GB/mod_mokojoomhero.ini
|
||||||
; VERSION: 01.19.00
|
; VERSION: 01.08.00
|
||||||
; BRIEF: Language strings for MokoJoomHero module (frontend + admin form fields)
|
; BRIEF: Language strings for MokoJoomHero module (frontend + admin form fields)
|
||||||
|
|
||||||
MOD_MOKOJOOMHERO_NO_CONTENT="Add content to this module to display it over the hero image."
|
MOD_MOKOJOOMHERO_NO_CONTENT="Add content to this module to display it over the hero image."
|
||||||
@@ -90,25 +90,6 @@ MOD_MOKOJOOMHERO_PARALLAX_DESC="Background moves at a slower rate than page cont
|
|||||||
MOD_MOKOJOOMHERO_PARALLAX_SPEED_LABEL="Parallax Speed"
|
MOD_MOKOJOOMHERO_PARALLAX_SPEED_LABEL="Parallax Speed"
|
||||||
MOD_MOKOJOOMHERO_PARALLAX_SPEED_DESC="How much the background moves relative to scroll (0.1 = subtle, 0.9 = dramatic)."
|
MOD_MOKOJOOMHERO_PARALLAX_SPEED_DESC="How much the background moves relative to scroll (0.1 = subtle, 0.9 = dramatic)."
|
||||||
|
|
||||||
; A/B testing
|
|
||||||
MOD_MOKOJOOMHERO_FIELDSET_AB="A/B Testing"
|
|
||||||
MOD_MOKOJOOMHERO_AB_ENABLED_LABEL="Enable A/B Testing"
|
|
||||||
MOD_MOKOJOOMHERO_AB_ENABLED_DESC="Randomly show different content variations to visitors. Assignment is sticky per session."
|
|
||||||
MOD_MOKOJOOMHERO_AB_VARIATIONS_LABEL="Variations"
|
|
||||||
MOD_MOKOJOOMHERO_AB_VARIATIONS_DESC="Define content variations with relative weights. Higher weight = higher chance of being shown."
|
|
||||||
MOD_MOKOJOOMHERO_AB_VAR_LABEL="Label"
|
|
||||||
MOD_MOKOJOOMHERO_AB_VAR_CONTENT="Content"
|
|
||||||
MOD_MOKOJOOMHERO_AB_VAR_WEIGHT="Weight"
|
|
||||||
|
|
||||||
; Scheduling
|
|
||||||
MOD_MOKOJOOMHERO_FIELDSET_SCHEDULING="Scheduling"
|
|
||||||
MOD_MOKOJOOMHERO_SCHEDULE_ENABLED_LABEL="Enable Scheduling"
|
|
||||||
MOD_MOKOJOOMHERO_SCHEDULE_ENABLED_DESC="Only display the hero during a specific date/time range. Uses the site timezone."
|
|
||||||
MOD_MOKOJOOMHERO_SCHEDULE_START_LABEL="Start Date/Time"
|
|
||||||
MOD_MOKOJOOMHERO_SCHEDULE_START_DESC="The hero will not display before this date and time. Leave empty for no start restriction."
|
|
||||||
MOD_MOKOJOOMHERO_SCHEDULE_END_LABEL="End Date/Time"
|
|
||||||
MOD_MOKOJOOMHERO_SCHEDULE_END_DESC="The hero will not display after this date and time. Leave empty for no end restriction."
|
|
||||||
|
|
||||||
; Video poster
|
; Video poster
|
||||||
MOD_MOKOJOOMHERO_VIDEO_POSTER_LABEL="Video Poster Image"
|
MOD_MOKOJOOMHERO_VIDEO_POSTER_LABEL="Video Poster Image"
|
||||||
MOD_MOKOJOOMHERO_VIDEO_POSTER_DESC="Fallback image displayed while the video loads. Prevents a blank hero on slow connections."
|
MOD_MOKOJOOMHERO_VIDEO_POSTER_DESC="Fallback image displayed while the video loads. Prevents a blank hero on slow connections."
|
||||||
+1
-20
@@ -6,7 +6,7 @@
|
|||||||
; INGROUP: MokoJoomHero.Module
|
; INGROUP: MokoJoomHero.Module
|
||||||
; REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
|
; REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
|
||||||
; PATH: /src/language/en-GB/mod_mokojoomhero.sys.ini
|
; PATH: /src/language/en-GB/mod_mokojoomhero.sys.ini
|
||||||
; VERSION: 01.19.00
|
; VERSION: 01.08.00
|
||||||
; BRIEF: System language strings — used in admin Extension Manager and Module Manager
|
; BRIEF: System language strings — used in admin Extension Manager and Module Manager
|
||||||
|
|
||||||
MOD_MOKOJOOMHERO="Module - MokoJoomHero"
|
MOD_MOKOJOOMHERO="Module - MokoJoomHero"
|
||||||
@@ -91,25 +91,6 @@ MOD_MOKOJOOMHERO_PARALLAX_DESC="Background moves at a slower rate than page cont
|
|||||||
MOD_MOKOJOOMHERO_PARALLAX_SPEED_LABEL="Parallax Speed"
|
MOD_MOKOJOOMHERO_PARALLAX_SPEED_LABEL="Parallax Speed"
|
||||||
MOD_MOKOJOOMHERO_PARALLAX_SPEED_DESC="How much the background moves relative to scroll (0.1 = subtle, 0.9 = dramatic)."
|
MOD_MOKOJOOMHERO_PARALLAX_SPEED_DESC="How much the background moves relative to scroll (0.1 = subtle, 0.9 = dramatic)."
|
||||||
|
|
||||||
; A/B testing
|
|
||||||
MOD_MOKOJOOMHERO_FIELDSET_AB="A/B Testing"
|
|
||||||
MOD_MOKOJOOMHERO_AB_ENABLED_LABEL="Enable A/B Testing"
|
|
||||||
MOD_MOKOJOOMHERO_AB_ENABLED_DESC="Randomly show different content variations to visitors."
|
|
||||||
MOD_MOKOJOOMHERO_AB_VARIATIONS_LABEL="Variations"
|
|
||||||
MOD_MOKOJOOMHERO_AB_VARIATIONS_DESC="Define content variations with relative weights."
|
|
||||||
MOD_MOKOJOOMHERO_AB_VAR_LABEL="Label"
|
|
||||||
MOD_MOKOJOOMHERO_AB_VAR_CONTENT="Content"
|
|
||||||
MOD_MOKOJOOMHERO_AB_VAR_WEIGHT="Weight"
|
|
||||||
|
|
||||||
; Scheduling
|
|
||||||
MOD_MOKOJOOMHERO_FIELDSET_SCHEDULING="Scheduling"
|
|
||||||
MOD_MOKOJOOMHERO_SCHEDULE_ENABLED_LABEL="Enable Scheduling"
|
|
||||||
MOD_MOKOJOOMHERO_SCHEDULE_ENABLED_DESC="Only display the hero during a specific date/time range."
|
|
||||||
MOD_MOKOJOOMHERO_SCHEDULE_START_LABEL="Start Date/Time"
|
|
||||||
MOD_MOKOJOOMHERO_SCHEDULE_START_DESC="The hero will not display before this date and time."
|
|
||||||
MOD_MOKOJOOMHERO_SCHEDULE_END_LABEL="End Date/Time"
|
|
||||||
MOD_MOKOJOOMHERO_SCHEDULE_END_DESC="The hero will not display after this date and time."
|
|
||||||
|
|
||||||
; Video poster
|
; Video poster
|
||||||
MOD_MOKOJOOMHERO_VIDEO_POSTER_LABEL="Video Poster Image"
|
MOD_MOKOJOOMHERO_VIDEO_POSTER_LABEL="Video Poster Image"
|
||||||
MOD_MOKOJOOMHERO_VIDEO_POSTER_DESC="Fallback image displayed while the video loads."
|
MOD_MOKOJOOMHERO_VIDEO_POSTER_DESC="Fallback image displayed while the video loads."
|
||||||
+1
-20
@@ -6,7 +6,7 @@
|
|||||||
; INGROUP: MokoJoomHero.Module
|
; INGROUP: MokoJoomHero.Module
|
||||||
; REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
|
; REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
|
||||||
; PATH: /src/language/en-US/mod_mokojoomhero.ini
|
; PATH: /src/language/en-US/mod_mokojoomhero.ini
|
||||||
; VERSION: 01.19.00
|
; VERSION: 01.08.00
|
||||||
; BRIEF: Language strings for MokoJoomHero module (en-US, frontend + admin form fields)
|
; BRIEF: Language strings for MokoJoomHero module (en-US, frontend + admin form fields)
|
||||||
|
|
||||||
MOD_MOKOJOOMHERO_NO_CONTENT="Add content to this module to display it over the hero image."
|
MOD_MOKOJOOMHERO_NO_CONTENT="Add content to this module to display it over the hero image."
|
||||||
@@ -107,25 +107,6 @@ MOD_MOKOJOOMHERO_PARALLAX_DESC="Background moves at a slower rate than page cont
|
|||||||
MOD_MOKOJOOMHERO_PARALLAX_SPEED_LABEL="Parallax Speed"
|
MOD_MOKOJOOMHERO_PARALLAX_SPEED_LABEL="Parallax Speed"
|
||||||
MOD_MOKOJOOMHERO_PARALLAX_SPEED_DESC="How much the background moves relative to scroll (0.1 = subtle, 0.9 = dramatic)."
|
MOD_MOKOJOOMHERO_PARALLAX_SPEED_DESC="How much the background moves relative to scroll (0.1 = subtle, 0.9 = dramatic)."
|
||||||
|
|
||||||
; A/B testing
|
|
||||||
MOD_MOKOJOOMHERO_FIELDSET_AB="A/B Testing"
|
|
||||||
MOD_MOKOJOOMHERO_AB_ENABLED_LABEL="Enable A/B Testing"
|
|
||||||
MOD_MOKOJOOMHERO_AB_ENABLED_DESC="Randomly show different content variations to visitors. Assignment is sticky per session."
|
|
||||||
MOD_MOKOJOOMHERO_AB_VARIATIONS_LABEL="Variations"
|
|
||||||
MOD_MOKOJOOMHERO_AB_VARIATIONS_DESC="Define content variations with relative weights. Higher weight = higher chance of being shown."
|
|
||||||
MOD_MOKOJOOMHERO_AB_VAR_LABEL="Label"
|
|
||||||
MOD_MOKOJOOMHERO_AB_VAR_CONTENT="Content"
|
|
||||||
MOD_MOKOJOOMHERO_AB_VAR_WEIGHT="Weight"
|
|
||||||
|
|
||||||
; Scheduling
|
|
||||||
MOD_MOKOJOOMHERO_FIELDSET_SCHEDULING="Scheduling"
|
|
||||||
MOD_MOKOJOOMHERO_SCHEDULE_ENABLED_LABEL="Enable Scheduling"
|
|
||||||
MOD_MOKOJOOMHERO_SCHEDULE_ENABLED_DESC="Only display the hero during a specific date/time range. Uses the site timezone."
|
|
||||||
MOD_MOKOJOOMHERO_SCHEDULE_START_LABEL="Start Date/Time"
|
|
||||||
MOD_MOKOJOOMHERO_SCHEDULE_START_DESC="The hero will not display before this date and time. Leave empty for no start restriction."
|
|
||||||
MOD_MOKOJOOMHERO_SCHEDULE_END_LABEL="End Date/Time"
|
|
||||||
MOD_MOKOJOOMHERO_SCHEDULE_END_DESC="The hero will not display after this date and time. Leave empty for no end restriction."
|
|
||||||
|
|
||||||
; Video poster
|
; Video poster
|
||||||
MOD_MOKOJOOMHERO_VIDEO_POSTER_LABEL="Video Poster Image"
|
MOD_MOKOJOOMHERO_VIDEO_POSTER_LABEL="Video Poster Image"
|
||||||
MOD_MOKOJOOMHERO_VIDEO_POSTER_DESC="Fallback image displayed while the video loads. Prevents a blank hero on slow connections."
|
MOD_MOKOJOOMHERO_VIDEO_POSTER_DESC="Fallback image displayed while the video loads. Prevents a blank hero on slow connections."
|
||||||
+1
-20
@@ -6,7 +6,7 @@
|
|||||||
; INGROUP: MokoJoomHero.Module
|
; INGROUP: MokoJoomHero.Module
|
||||||
; REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
|
; REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
|
||||||
; PATH: /src/language/en-US/mod_mokojoomhero.sys.ini
|
; PATH: /src/language/en-US/mod_mokojoomhero.sys.ini
|
||||||
; VERSION: 01.19.00
|
; VERSION: 01.08.00
|
||||||
; BRIEF: System language strings — used in admin Extension Manager and Module Manager (en-US)
|
; BRIEF: System language strings — used in admin Extension Manager and Module Manager (en-US)
|
||||||
|
|
||||||
MOD_MOKOJOOMHERO="Module - MokoJoomHero"
|
MOD_MOKOJOOMHERO="Module - MokoJoomHero"
|
||||||
@@ -91,25 +91,6 @@ MOD_MOKOJOOMHERO_PARALLAX_DESC="Background moves at a slower rate than page cont
|
|||||||
MOD_MOKOJOOMHERO_PARALLAX_SPEED_LABEL="Parallax Speed"
|
MOD_MOKOJOOMHERO_PARALLAX_SPEED_LABEL="Parallax Speed"
|
||||||
MOD_MOKOJOOMHERO_PARALLAX_SPEED_DESC="How much the background moves relative to scroll (0.1 = subtle, 0.9 = dramatic)."
|
MOD_MOKOJOOMHERO_PARALLAX_SPEED_DESC="How much the background moves relative to scroll (0.1 = subtle, 0.9 = dramatic)."
|
||||||
|
|
||||||
; A/B testing
|
|
||||||
MOD_MOKOJOOMHERO_FIELDSET_AB="A/B Testing"
|
|
||||||
MOD_MOKOJOOMHERO_AB_ENABLED_LABEL="Enable A/B Testing"
|
|
||||||
MOD_MOKOJOOMHERO_AB_ENABLED_DESC="Randomly show different content variations to visitors."
|
|
||||||
MOD_MOKOJOOMHERO_AB_VARIATIONS_LABEL="Variations"
|
|
||||||
MOD_MOKOJOOMHERO_AB_VARIATIONS_DESC="Define content variations with relative weights."
|
|
||||||
MOD_MOKOJOOMHERO_AB_VAR_LABEL="Label"
|
|
||||||
MOD_MOKOJOOMHERO_AB_VAR_CONTENT="Content"
|
|
||||||
MOD_MOKOJOOMHERO_AB_VAR_WEIGHT="Weight"
|
|
||||||
|
|
||||||
; Scheduling
|
|
||||||
MOD_MOKOJOOMHERO_FIELDSET_SCHEDULING="Scheduling"
|
|
||||||
MOD_MOKOJOOMHERO_SCHEDULE_ENABLED_LABEL="Enable Scheduling"
|
|
||||||
MOD_MOKOJOOMHERO_SCHEDULE_ENABLED_DESC="Only display the hero during a specific date/time range."
|
|
||||||
MOD_MOKOJOOMHERO_SCHEDULE_START_LABEL="Start Date/Time"
|
|
||||||
MOD_MOKOJOOMHERO_SCHEDULE_START_DESC="The hero will not display before this date and time."
|
|
||||||
MOD_MOKOJOOMHERO_SCHEDULE_END_LABEL="End Date/Time"
|
|
||||||
MOD_MOKOJOOMHERO_SCHEDULE_END_DESC="The hero will not display after this date and time."
|
|
||||||
|
|
||||||
; Video poster
|
; Video poster
|
||||||
MOD_MOKOJOOMHERO_VIDEO_POSTER_LABEL="Video Poster Image"
|
MOD_MOKOJOOMHERO_VIDEO_POSTER_LABEL="Video Poster Image"
|
||||||
MOD_MOKOJOOMHERO_VIDEO_POSTER_DESC="Fallback image displayed while the video loads."
|
MOD_MOKOJOOMHERO_VIDEO_POSTER_DESC="Fallback image displayed while the video loads."
|
||||||
+2
-2
@@ -6,8 +6,8 @@
|
|||||||
* DEFGROUP: MokoJoomHero.Module.Assets
|
* DEFGROUP: MokoJoomHero.Module.Assets
|
||||||
* INGROUP: MokoJoomHero.Module
|
* INGROUP: MokoJoomHero.Module
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
|
||||||
* PATH: /src/media/css/mod_mokojoomhero.css
|
* PATH: /src/packages/mod_mokojoomhero/media/css/mod_mokojoomhero.css
|
||||||
* VERSION: 01.19.00
|
* VERSION: 01.08.00
|
||||||
* BRIEF: Hero module stylesheet — slideshow, video, colour/gradient, overlay, card, mute toggle, responsive
|
* BRIEF: Hero module stylesheet — slideshow, video, colour/gradient, overlay, card, mute toggle, responsive
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<html><body bgcolor="#FFFFFF"></body></html>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<html><body bgcolor="#FFFFFF"></body></html>
|
||||||
+2
-2
@@ -7,8 +7,8 @@
|
|||||||
* DEFGROUP: MokoJoomHero.Module.Assets
|
* DEFGROUP: MokoJoomHero.Module.Assets
|
||||||
* INGROUP: MokoJoomHero.Module
|
* INGROUP: MokoJoomHero.Module
|
||||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
|
||||||
* PATH: /src/media/js/mod_mokojoomhero.js
|
* PATH: /src/packages/mod_mokojoomhero/media/js/mod_mokojoomhero.js
|
||||||
* VERSION: 01.19.00
|
* VERSION: 01.08.00
|
||||||
* BRIEF: Hero module JavaScript — slideshow crossfade, video viewport control, mute toggle
|
* BRIEF: Hero module JavaScript — slideshow crossfade, video viewport control, mute toggle
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -23,73 +23,6 @@ $wa = $app->getDocument()->getWebAssetManager();
|
|||||||
$wa->getRegistry()->addExtensionRegistryFile('mod_mokojoomhero');
|
$wa->getRegistry()->addExtensionRegistryFile('mod_mokojoomhero');
|
||||||
$wa->usePreset('mod_mokojoomhero');
|
$wa->usePreset('mod_mokojoomhero');
|
||||||
|
|
||||||
// Schedule check — skip rendering if outside the configured date range
|
|
||||||
$scheduleEnabled = (bool) $params->get('scheduleEnabled', 0);
|
|
||||||
|
|
||||||
if ($scheduleEnabled) {
|
|
||||||
$now = new \DateTime('now', new \DateTimeZone($app->get('offset', 'UTC')));
|
|
||||||
$scheduleStart = $params->get('scheduleStart', '');
|
|
||||||
$scheduleEnd = $params->get('scheduleEnd', '');
|
|
||||||
|
|
||||||
if ($scheduleStart) {
|
|
||||||
$start = new \DateTime($scheduleStart, new \DateTimeZone($app->get('offset', 'UTC')));
|
|
||||||
|
|
||||||
if ($now < $start) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($scheduleEnd) {
|
|
||||||
$end = new \DateTime($scheduleEnd, new \DateTimeZone($app->get('offset', 'UTC')));
|
|
||||||
|
|
||||||
if ($now > $end) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A/B testing — weighted random variation, session-sticky per module instance
|
|
||||||
$abEnabled = (bool) $params->get('abEnabled', 0);
|
|
||||||
$abVariationContent = '';
|
|
||||||
|
|
||||||
if ($abEnabled) {
|
|
||||||
$abVariations = $params->get('abVariations', '');
|
|
||||||
$abData = is_string($abVariations) ? json_decode($abVariations, true) : (array) $abVariations;
|
|
||||||
|
|
||||||
if (is_array($abData) && count($abData) > 0) {
|
|
||||||
$session = \Joomla\CMS\Factory::getSession();
|
|
||||||
$sessionKey = 'mokojoomhero.ab.' . $module->id;
|
|
||||||
$picked = $session->get($sessionKey, null);
|
|
||||||
|
|
||||||
if ($picked === null || !isset($abData[$picked])) {
|
|
||||||
// Weighted random selection
|
|
||||||
$totalWeight = 0;
|
|
||||||
|
|
||||||
foreach ($abData as $v) {
|
|
||||||
$totalWeight += (int) (((array) $v)['weight'] ?? 50);
|
|
||||||
}
|
|
||||||
|
|
||||||
$rand = mt_rand(1, max($totalWeight, 1));
|
|
||||||
$cumulative = 0;
|
|
||||||
$picked = 0;
|
|
||||||
|
|
||||||
foreach ($abData as $i => $v) {
|
|
||||||
$cumulative += (int) (((array) $v)['weight'] ?? 50);
|
|
||||||
|
|
||||||
if ($rand <= $cumulative) {
|
|
||||||
$picked = $i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$session->set($sessionKey, $picked);
|
|
||||||
}
|
|
||||||
|
|
||||||
$variation = (array) ($abData[$picked] ?? []);
|
|
||||||
$abVariationContent = $variation['content'] ?? '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Module parameters
|
// Module parameters
|
||||||
$heroMode = $params->get('heroMode', 'images');
|
$heroMode = $params->get('heroMode', 'images');
|
||||||
$imageFolder = $params->get('imageFolder', 'images/heroes');
|
$imageFolder = $params->get('imageFolder', 'images/heroes');
|
||||||
@@ -185,11 +118,6 @@ if (!in_array($contentAnimation, $allowedContentAnimations, true)) {
|
|||||||
$parallaxSpeed = max(0.1, min(0.9, $parallaxSpeed));
|
$parallaxSpeed = max(0.1, min(0.9, $parallaxSpeed));
|
||||||
$gradientAngle = max(0, min(360, $gradientAngle));
|
$gradientAngle = max(0, min(360, $gradientAngle));
|
||||||
|
|
||||||
// Apply A/B variation content if active
|
|
||||||
if ($abEnabled && $abVariationContent) {
|
|
||||||
$heroContent = $abVariationContent;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect hero images
|
// Collect hero images
|
||||||
$heroImages = [];
|
$heroImages = [];
|
||||||
|
|
||||||
@@ -10,20 +10,20 @@
|
|||||||
DEFGROUP: MokoJoomHero.Module
|
DEFGROUP: MokoJoomHero.Module
|
||||||
INGROUP: MokoJoomHero
|
INGROUP: MokoJoomHero
|
||||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
|
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero
|
||||||
PATH: /src/mod_mokojoomhero.xml
|
PATH: /src/packages/mod_mokojoomhero/mod_mokojoomhero.xml
|
||||||
VERSION: 01.00.20
|
VERSION: 01.00.20
|
||||||
BRIEF: Joomla module manifest — random hero image with content overlay
|
BRIEF: Joomla module manifest — random hero image with content overlay
|
||||||
-->
|
-->
|
||||||
<extension type="module" client="site" method="upgrade">
|
<extension type="module" client="site" method="upgrade">
|
||||||
<name>Module - MokoJoomHero</name>
|
<name>Module - MokoJoomHero</name>
|
||||||
<creationDate>2026-06-04</creationDate>
|
<creationDate>2026-05</creationDate>
|
||||||
<author>Moko Consulting</author>
|
<author>Moko Consulting</author>
|
||||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||||
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
|
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
|
||||||
<license>GPL-3.0-or-later</license>
|
<license>GPL-3.0-or-later</license>
|
||||||
<version>01.19.00</version>
|
<version>01.08.00-rc</version>
|
||||||
<description>Random hero image slideshow, video backgrounds, solid colour/gradient, parallax, content animations, A/B testing, scheduling, and overlay with card support. Free and open source. By Moko Consulting.</description>
|
<description>Displays a random hero image slideshow or background video with content overlaid. Designed for MokoOnyx template. By Moko Consulting.</description>
|
||||||
|
|
||||||
<scriptfile>script.php</scriptfile>
|
<scriptfile>script.php</scriptfile>
|
||||||
|
|
||||||
@@ -48,10 +48,6 @@
|
|||||||
<language tag="en-US">en-US/mod_mokojoomhero.sys.ini</language>
|
<language tag="en-US">en-US/mod_mokojoomhero.sys.ini</language>
|
||||||
</languages>
|
</languages>
|
||||||
|
|
||||||
<updateservers>
|
|
||||||
<server type="extension" name="Module - MokoJoomHero">https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/updates.xml</server>
|
|
||||||
</updateservers>
|
|
||||||
|
|
||||||
<config>
|
<config>
|
||||||
<fields name="params">
|
<fields name="params">
|
||||||
<fieldset name="basic">
|
<fieldset name="basic">
|
||||||
@@ -278,89 +274,6 @@
|
|||||||
<option value="1">JYES</option>
|
<option value="1">JYES</option>
|
||||||
</field>
|
</field>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset name="abtesting"
|
|
||||||
label="MOD_MOKOJOOMHERO_FIELDSET_AB"
|
|
||||||
>
|
|
||||||
<field
|
|
||||||
name="abEnabled"
|
|
||||||
type="radio"
|
|
||||||
layout="joomla.form.field.radio.switcher"
|
|
||||||
label="MOD_MOKOJOOMHERO_AB_ENABLED_LABEL"
|
|
||||||
description="MOD_MOKOJOOMHERO_AB_ENABLED_DESC"
|
|
||||||
default="0"
|
|
||||||
>
|
|
||||||
<option value="0">JNO</option>
|
|
||||||
<option value="1">JYES</option>
|
|
||||||
</field>
|
|
||||||
<field
|
|
||||||
name="abVariations"
|
|
||||||
type="subform"
|
|
||||||
label="MOD_MOKOJOOMHERO_AB_VARIATIONS_LABEL"
|
|
||||||
description="MOD_MOKOJOOMHERO_AB_VARIATIONS_DESC"
|
|
||||||
multiple="true"
|
|
||||||
layout="joomla.form.field.subform.repeatable-table"
|
|
||||||
showon="abEnabled:1"
|
|
||||||
max="4"
|
|
||||||
>
|
|
||||||
<form>
|
|
||||||
<field
|
|
||||||
name="label"
|
|
||||||
type="text"
|
|
||||||
label="MOD_MOKOJOOMHERO_AB_VAR_LABEL"
|
|
||||||
filter="string"
|
|
||||||
default="Variation"
|
|
||||||
/>
|
|
||||||
<field
|
|
||||||
name="content"
|
|
||||||
type="textarea"
|
|
||||||
label="MOD_MOKOJOOMHERO_AB_VAR_CONTENT"
|
|
||||||
filter="safehtml"
|
|
||||||
rows="3"
|
|
||||||
/>
|
|
||||||
<field
|
|
||||||
name="weight"
|
|
||||||
type="number"
|
|
||||||
label="MOD_MOKOJOOMHERO_AB_VAR_WEIGHT"
|
|
||||||
default="50"
|
|
||||||
min="1"
|
|
||||||
max="100"
|
|
||||||
/>
|
|
||||||
</form>
|
|
||||||
</field>
|
|
||||||
</fieldset>
|
|
||||||
<fieldset name="scheduling"
|
|
||||||
label="MOD_MOKOJOOMHERO_FIELDSET_SCHEDULING"
|
|
||||||
>
|
|
||||||
<field
|
|
||||||
name="scheduleEnabled"
|
|
||||||
type="radio"
|
|
||||||
layout="joomla.form.field.radio.switcher"
|
|
||||||
label="MOD_MOKOJOOMHERO_SCHEDULE_ENABLED_LABEL"
|
|
||||||
description="MOD_MOKOJOOMHERO_SCHEDULE_ENABLED_DESC"
|
|
||||||
default="0"
|
|
||||||
>
|
|
||||||
<option value="0">JNO</option>
|
|
||||||
<option value="1">JYES</option>
|
|
||||||
</field>
|
|
||||||
<field
|
|
||||||
name="scheduleStart"
|
|
||||||
type="calendar"
|
|
||||||
label="MOD_MOKOJOOMHERO_SCHEDULE_START_LABEL"
|
|
||||||
description="MOD_MOKOJOOMHERO_SCHEDULE_START_DESC"
|
|
||||||
format="%Y-%m-%d %H:%M"
|
|
||||||
showtime="true"
|
|
||||||
showon="scheduleEnabled:1"
|
|
||||||
/>
|
|
||||||
<field
|
|
||||||
name="scheduleEnd"
|
|
||||||
type="calendar"
|
|
||||||
label="MOD_MOKOJOOMHERO_SCHEDULE_END_LABEL"
|
|
||||||
description="MOD_MOKOJOOMHERO_SCHEDULE_END_DESC"
|
|
||||||
format="%Y-%m-%d %H:%M"
|
|
||||||
showtime="true"
|
|
||||||
showon="scheduleEnabled:1"
|
|
||||||
/>
|
|
||||||
</fieldset>
|
|
||||||
<fieldset name="content"
|
<fieldset name="content"
|
||||||
label="MOD_MOKOJOOMHERO_FIELDSET_CONTENT"
|
label="MOD_MOKOJOOMHERO_FIELDSET_CONTENT"
|
||||||
>
|
>
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package Joomla.Site
|
||||||
|
* @subpackage mod_mokojoomhero
|
||||||
|
*
|
||||||
|
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||||
|
* @license GPL-3.0-or-later
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined('_JEXEC') or die;
|
||||||
|
|
||||||
|
use Joomla\CMS\Filesystem\Folder;
|
||||||
|
use Joomla\CMS\Installer\InstallerAdapter;
|
||||||
|
|
||||||
|
class Mod_MokojoomheroInstallerScript
|
||||||
|
{
|
||||||
|
public function postflight(string $type, InstallerAdapter $adapter): void
|
||||||
|
{
|
||||||
|
$heroesPath = JPATH_ROOT . '/images/heroes';
|
||||||
|
|
||||||
|
if (!is_dir($heroesPath)) {
|
||||||
|
Folder::create($heroesPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -62,10 +62,7 @@ $overlayDirections = [
|
|||||||
'gradient-right' => 'to right',
|
'gradient-right' => 'to right',
|
||||||
];
|
];
|
||||||
|
|
||||||
// Skip overlay on solid colour/gradient modes — background is already a controlled design choice
|
if ($overlayType !== 'solid' && isset($overlayDirections[$overlayType])) {
|
||||||
if ($heroMode === 'color' || $heroMode === 'gradient') {
|
|
||||||
$overlayBg = '';
|
|
||||||
} elseif ($overlayType !== 'solid' && isset($overlayDirections[$overlayType])) {
|
|
||||||
$dir = $overlayDirections[$overlayType];
|
$dir = $overlayDirections[$overlayType];
|
||||||
$overlayBg = "background: linear-gradient($dir, $rgbaTransparent, $rgbaOpaque);";
|
$overlayBg = "background: linear-gradient($dir, $rgbaTransparent, $rgbaOpaque);";
|
||||||
} else {
|
} else {
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<html><body bgcolor="#FFFFFF"></body></html>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<html><body bgcolor="#FFFFFF"></body></html>
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
; Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
; SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
; Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
; SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
PLG_SYSTEM_MOKOJOOMHERO="System - MokoJoomHero"
|
||||||
|
PLG_SYSTEM_MOKOJOOMHERO_DESCRIPTION="System plugin for MokoJoomHero"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<html><body bgcolor="#FFFFFF"></body></html>
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
; Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
; SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
; Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
; SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
PLG_SYSTEM_MOKOJOOMHERO="System - MokoJoomHero"
|
||||||
|
PLG_SYSTEM_MOKOJOOMHERO_DESCRIPTION="System plugin for MokoJoomHero"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<html><body bgcolor="#FFFFFF"></body></html>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package MokoJoomHero
|
||||||
|
* @subpackage plg_system_mokojoomhero
|
||||||
|
* @author Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||||
|
* @license GNU General Public License version 3 or later; see LICENSE
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined('_JEXEC') or die;
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
* @package MokoJoomHero
|
||||||
|
* @subpackage plg_system_mokojoomhero
|
||||||
|
* @author Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||||
|
* @license GNU General Public License version 3 or later; see LICENSE
|
||||||
|
-->
|
||||||
|
<extension type="plugin" group="system" method="upgrade">
|
||||||
|
<name>PLG_SYSTEM_MOKOJOOMHERO</name>
|
||||||
|
<version>01.08.00-rc</version>
|
||||||
|
<creationDate>2026-06-02</creationDate>
|
||||||
|
<author>Moko Consulting</author>
|
||||||
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||||
|
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
|
||||||
|
<license>GPL-3.0-or-later</license>
|
||||||
|
<description>PLG_SYSTEM_MOKOJOOMHERO_DESCRIPTION</description>
|
||||||
|
|
||||||
|
<namespace path="src">Joomla\Plugin\System\MokoJoomHero</namespace>
|
||||||
|
|
||||||
|
<files>
|
||||||
|
<filename plugin="mokojoomhero">mokojoomhero.php</filename>
|
||||||
|
<folder>services</folder>
|
||||||
|
<folder>src</folder>
|
||||||
|
</files>
|
||||||
|
|
||||||
|
<languages folder="language">
|
||||||
|
<language tag="en-GB">en-GB/plg_system_mokojoomhero.ini</language>
|
||||||
|
<language tag="en-GB">en-GB/plg_system_mokojoomhero.sys.ini</language>
|
||||||
|
<language tag="en-US">en-US/plg_system_mokojoomhero.ini</language>
|
||||||
|
<language tag="en-US">en-US/plg_system_mokojoomhero.sys.ini</language>
|
||||||
|
</languages>
|
||||||
|
</extension>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<html><body bgcolor="#FFFFFF"></body></html>
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package MokoJoomHero
|
||||||
|
* @subpackage plg_system_mokojoomhero
|
||||||
|
* @author Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||||
|
* @license GNU General Public License version 3 or later; see LICENSE
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined('_JEXEC') or die;
|
||||||
|
|
||||||
|
use Joomla\CMS\Extension\PluginInterface;
|
||||||
|
use Joomla\CMS\Factory;
|
||||||
|
use Joomla\CMS\Plugin\PluginHelper;
|
||||||
|
use Joomla\DI\Container;
|
||||||
|
use Joomla\DI\ServiceProviderInterface;
|
||||||
|
use Joomla\Event\DispatcherInterface;
|
||||||
|
use Joomla\Plugin\System\MokoJoomHero\Extension\MokoJoomHero;
|
||||||
|
|
||||||
|
return new class implements ServiceProviderInterface {
|
||||||
|
public function register(Container $container): void
|
||||||
|
{
|
||||||
|
$container->set(
|
||||||
|
PluginInterface::class,
|
||||||
|
function (Container $container) {
|
||||||
|
$plugin = new MokoJoomHero(
|
||||||
|
$container->get(DispatcherInterface::class),
|
||||||
|
(array) PluginHelper::getPlugin('system', 'mokojoomhero')
|
||||||
|
);
|
||||||
|
|
||||||
|
$plugin->setApplication(Factory::getApplication());
|
||||||
|
|
||||||
|
return $plugin;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package MokoJoomHero
|
||||||
|
* @subpackage plg_system_mokojoomhero
|
||||||
|
* @author Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||||
|
* @license GNU General Public License version 3 or later; see LICENSE
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Joomla\Plugin\System\MokoJoomHero\Extension;
|
||||||
|
|
||||||
|
defined('_JEXEC') or die;
|
||||||
|
|
||||||
|
use Joomla\CMS\Plugin\CMSPlugin;
|
||||||
|
use Joomla\Event\SubscriberInterface;
|
||||||
|
|
||||||
|
class MokoJoomHero extends CMSPlugin implements SubscriberInterface
|
||||||
|
{
|
||||||
|
public static function getSubscribedEvents(): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<html><body bgcolor="#FFFFFF"></body></html>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<html><body bgcolor="#FFFFFF"></body></html>
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
* @package MokoJoomHero
|
||||||
|
* @author Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||||
|
* @license GNU General Public License version 3 or later; see LICENSE
|
||||||
|
-->
|
||||||
|
<extension type="package" method="upgrade">
|
||||||
|
<name>Package - MokoJoomHero</name>
|
||||||
|
<packagename>mokojoomhero</packagename>
|
||||||
|
<version>01.08.00-rc</version>
|
||||||
|
<creationDate>2026-06-02</creationDate>
|
||||||
|
<author>Moko Consulting</author>
|
||||||
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||||
|
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
|
||||||
|
<license>GPL-3.0-or-later</license>
|
||||||
|
<description>Random hero image slideshow or background video with content overlay. Includes the hero module and system plugin. By Moko Consulting.</description>
|
||||||
|
|
||||||
|
<scriptfile>pkg_script.php</scriptfile>
|
||||||
|
|
||||||
|
<files folder="packages">
|
||||||
|
<file type="module" id="mod_mokojoomhero" client="site">mod_mokojoomhero.zip</file>
|
||||||
|
<file type="plugin" id="mokojoomhero" group="system">plg_system_mokojoomhero.zip</file>
|
||||||
|
</files>
|
||||||
|
|
||||||
|
<updateservers>
|
||||||
|
<server type="extension" name="MokoJoomHero Updates">https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/raw/branch/main/updates.xml</server>
|
||||||
|
</updateservers>
|
||||||
|
</extension>
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package MokoJoomHero
|
||||||
|
* @author Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||||
|
* @license GNU General Public License version 3 or later; see LICENSE
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined('_JEXEC') or die;
|
||||||
|
|
||||||
|
use Joomla\CMS\Factory;
|
||||||
|
use Joomla\CMS\Installer\InstallerAdapter;
|
||||||
|
|
||||||
|
class Pkg_MokoJoomHeroInstallerScript
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Called after install/update — only enables the system plugin on fresh install.
|
||||||
|
*
|
||||||
|
* @param string $type Action type
|
||||||
|
* @param InstallerAdapter $parent Installer adapter
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function postflight(string $type, InstallerAdapter $parent): void
|
||||||
|
{
|
||||||
|
if ($type === 'install') {
|
||||||
|
try {
|
||||||
|
$db = Factory::getDbo();
|
||||||
|
|
||||||
|
// Enable the system plugin automatically on fresh install
|
||||||
|
$query = $db->getQuery(true)
|
||||||
|
->update($db->quoteName('#__extensions'))
|
||||||
|
->set($db->quoteName('enabled') . ' = 1')
|
||||||
|
->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
|
||||||
|
->where($db->quoteName('folder') . ' = ' . $db->quote('system'))
|
||||||
|
->where($db->quoteName('element') . ' = ' . $db->quote('mokojoomhero'));
|
||||||
|
|
||||||
|
$db->setQuery($query);
|
||||||
|
$db->execute();
|
||||||
|
|
||||||
|
if ($db->getAffectedRows() === 0) {
|
||||||
|
Factory::getApplication()->enqueueMessage(
|
||||||
|
'MokoJoomHero: The system plugin could not be auto-enabled. '
|
||||||
|
. 'Please enable it manually in Extensions → Plugins.',
|
||||||
|
'warning'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Factory::getApplication()->enqueueMessage(
|
||||||
|
'MokoJoomHero: Failed to auto-enable system plugin: ' . $e->getMessage()
|
||||||
|
. ' — Please enable it manually in Extensions → Plugins.',
|
||||||
|
'warning'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @package Joomla.Site
|
|
||||||
* @subpackage mod_mokojoomhero
|
|
||||||
*
|
|
||||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
|
||||||
* @license GPL-3.0-or-later
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
defined('_JEXEC') or die;
|
|
||||||
|
|
||||||
use Joomla\CMS\Factory;
|
|
||||||
use Joomla\CMS\Filesystem\Folder;
|
|
||||||
use Joomla\CMS\Installer\InstallerAdapter;
|
|
||||||
|
|
||||||
class Mod_MokojoomheroInstallerScript
|
|
||||||
{
|
|
||||||
public function postflight(string $type, InstallerAdapter $adapter): void
|
|
||||||
{
|
|
||||||
// Create default image folder on fresh install
|
|
||||||
$heroesPath = JPATH_ROOT . '/images/heroes';
|
|
||||||
|
|
||||||
if (!is_dir($heroesPath)) {
|
|
||||||
Folder::create($heroesPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove deprecated system plugin (was part of the old package extension)
|
|
||||||
$this->removeDeprecatedPlugin();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uninstall plg_system_mokojoomhero if it exists — no longer needed.
|
|
||||||
*/
|
|
||||||
private function removeDeprecatedPlugin(): void
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$db = Factory::getDbo();
|
|
||||||
|
|
||||||
$query = $db->getQuery(true)
|
|
||||||
->select($db->quoteName('extension_id'))
|
|
||||||
->from($db->quoteName('#__extensions'))
|
|
||||||
->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
|
|
||||||
->where($db->quoteName('folder') . ' = ' . $db->quote('system'))
|
|
||||||
->where($db->quoteName('element') . ' = ' . $db->quote('mokojoomhero'));
|
|
||||||
$db->setQuery($query);
|
|
||||||
$extensionId = (int) $db->loadResult();
|
|
||||||
|
|
||||||
if ($extensionId) {
|
|
||||||
$installer = new \Joomla\CMS\Installer\Installer();
|
|
||||||
$installer->uninstall('plugin', $extensionId);
|
|
||||||
}
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
// Non-critical — plugin may already be gone
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Joomla Extension Update Server XML
|
||||||
|
See: https://docs.joomla.org/Deploying_an_Update_Server
|
||||||
|
|
||||||
|
This file is the update server manifest for mod_mokojoomhero.
|
||||||
|
The Joomla installer polls this URL to check for new versions.
|
||||||
|
|
||||||
|
The manifest in this repository must reference this file:
|
||||||
|
<updateservers>
|
||||||
|
<server type="extension" priority="1" name="MokoJoomHero Updates">
|
||||||
|
https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/raw/branch/main/updates.xml
|
||||||
|
</server>
|
||||||
|
</updateservers>
|
||||||
|
|
||||||
|
When a new release is made, run `make release` or the release workflow to
|
||||||
|
prepend a new <update> entry to this file automatically.
|
||||||
|
-->
|
||||||
|
<updates>
|
||||||
|
<update>
|
||||||
|
<name>Module - MokoJoomHero</name>
|
||||||
|
<description>MokoJoomHero — A Joomla hero image module by Moko Consulting</description>
|
||||||
|
<element>mod_mokojoomhero</element>
|
||||||
|
<type>module</type>
|
||||||
|
<client>site</client>
|
||||||
|
<version>{{VERSION}}</version>
|
||||||
|
<downloads>
|
||||||
|
<downloadurl type="full" format="zip">
|
||||||
|
https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/releases/download/v{{VERSION}}/mod_mokojoomhero.zip
|
||||||
|
</downloadurl>
|
||||||
|
</downloads>
|
||||||
|
<targetplatform name="joomla" version="(5|6).*" />
|
||||||
|
<php_minimum>8.1</php_minimum>
|
||||||
|
</update>
|
||||||
|
</updates>
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
|
<!-- Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
VERSION: 01.12.00
|
||||||
|
-->
|
||||||
|
|
||||||
|
<updates>
|
||||||
|
<update>
|
||||||
|
<name>Package - MokoJoomHero</name>
|
||||||
|
<description>Package - MokoJoomHero stable build.</description>
|
||||||
|
<element>pkg_mokojoomhero</element>
|
||||||
|
<type>package</type>
|
||||||
|
<client>site</client>
|
||||||
|
<version>01.12.00</version>
|
||||||
|
<creationDate>2026-06-04</creationDate>
|
||||||
|
<infourl title='Package - MokoJoomHero'>https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/releases/tag/stable</infourl>
|
||||||
|
<downloads>
|
||||||
|
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/releases/download/stable/pkg_mokojoomhero-01.12.00.zip</downloadurl>
|
||||||
|
</downloads>
|
||||||
|
<sha256>675e97f0f66e206b09a256152efdccd8afca979d7d68321a9931eb3d65fb3e0d</sha256>
|
||||||
|
<tags><tag>stable</tag></tags>
|
||||||
|
<changelogurl>https://git.mokoconsulting.tech/MokoConsulting/MokoJoomHero/raw/branch/main/CHANGELOG.md</changelogurl>
|
||||||
|
<maintainer>Moko Consulting</maintainer>
|
||||||
|
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||||
|
<targetplatform name="joomla" version="(5|6)\..*" />
|
||||||
|
</update>
|
||||||
|
</updates>
|
||||||
Reference in New Issue
Block a user