# Conflicts: # updates.xml
This commit is contained in:
@@ -148,6 +148,9 @@ jobs:
|
||||
if match:
|
||||
block = match.group(1)
|
||||
updated = block.replace(old, new)
|
||||
import datetime
|
||||
today = datetime.date.today().isoformat()
|
||||
updated = re.sub(r"<creationDate>[^<]*</creationDate>", f"<creationDate>{today}</creationDate>", updated)
|
||||
content = content.replace(block, updated)
|
||||
with open("updates.xml", "w") as f:
|
||||
f.write(content)
|
||||
|
||||
@@ -0,0 +1,495 @@
|
||||
# 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: '{"github-oauth":{"github.com":"${{ secrets.GH_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 2 -name "*.xml" -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 -----------------------------------------
|
||||
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})</description>\n"
|
||||
NEW_ENTRY="${NEW_ENTRY} <element>${EXT_ELEMENT}</element>\n"
|
||||
NEW_ENTRY="${NEW_ENTRY} <type>${EXT_TYPE}</type>\n"
|
||||
NEW_ENTRY="${NEW_ENTRY} <version>${DISPLAY_VERSION}</version>\n"
|
||||
NEW_ENTRY="${NEW_ENTRY} <creationDate>$(date +%Y-%m-%d)</creationDate>\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} <tags>\n"
|
||||
NEW_ENTRY="${NEW_ENTRY} <tag>${STABILITY}</tag>\n"
|
||||
NEW_ENTRY="${NEW_ENTRY} </tags>\n"
|
||||
NEW_ENTRY="${NEW_ENTRY} <infourl title=\"${EXT_NAME}\">${INFO_URL}</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} ${TARGET_PLATFORM}\n"
|
||||
[ -n "$PHP_TAG" ] && NEW_ENTRY="${NEW_ENTRY} ${PHP_TAG}\n"
|
||||
NEW_ENTRY="${NEW_ENTRY} <maintainer>Moko Consulting</maintainer>\n"
|
||||
NEW_ENTRY="${NEW_ENTRY} <maintainerurl>https://mokoconsulting.tech</maintainerurl>\n"
|
||||
NEW_ENTRY="${NEW_ENTRY} </update>"
|
||||
|
||||
# -- Write new entry to temp file --------------------------------
|
||||
printf '%b' "$NEW_ENTRY" > /tmp/new_entry.xml
|
||||
|
||||
# -- Merge into updates.xml (only update this stability channel) -
|
||||
# Cascading update: each stability level updates itself and all lower levels
|
||||
# stable → all | rc → rc,beta,alpha,dev | beta → beta,alpha,dev | 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}"
|
||||
|
||||
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 <hello@mokoconsulting.tech>" >> updates.xml
|
||||
printf '%s\n' " SPDX-License-Identifier: GPL-3.0-or-later" >> updates.xml
|
||||
printf '%s\n' " VERSION: ${VERSION}" >> updates.xml
|
||||
printf '%s\n' " -->" >> updates.xml
|
||||
printf '%s\n' "" >> updates.xml
|
||||
printf '%s\n' '<updates>' >> updates.xml
|
||||
cat /tmp/new_entry.xml >> updates.xml
|
||||
printf '\n%s\n' '</updates>' >> updates.xml
|
||||
else
|
||||
# Replace each cascading channel with the new entry (different tag)
|
||||
export PY_TARGETS="$TARGETS"
|
||||
python3 << PYEOF
|
||||
import re, os
|
||||
targets = os.environ["PY_TARGETS"].split(",")
|
||||
stability = "${STABILITY}"
|
||||
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
|
||||
new_entry = re.sub(r"<tag>[^<]*</tag>", f"<tag>{tag}</tag>", new_entry_template)
|
||||
# Remove existing entry for this tag
|
||||
pattern = r" <update>.*?<tag>" + re.escape(tag) + r"</tag>.*?</update>\n?"
|
||||
content = re.sub(pattern, "", content, flags=re.DOTALL)
|
||||
# Insert before </updates>
|
||||
content = content.replace("</updates>", new_entry + "\n</updates>")
|
||||
content = re.sub(r"\n{3,}", "\n\n", content)
|
||||
with open("updates.xml", "w") as f:
|
||||
f.write(content)
|
||||
PYEOF
|
||||
if [ $? -ne 0 ]; then
|
||||
# Fallback: rebuild keeping other stability entries
|
||||
{
|
||||
printf '%s\n' "<?xml version='1.0' encoding='UTF-8'?>"
|
||||
printf '%s\n' "<!-- Copyright (C) $(date +%Y) Moko Consulting <hello@mokoconsulting.tech>"
|
||||
printf '%s\n' " SPDX-License-Identifier: GPL-3.0-or-later"
|
||||
printf '%s\n' " VERSION: ${VERSION}"
|
||||
printf '%s\n' " -->"
|
||||
printf '%s\n' ""
|
||||
printf '%s\n' '<updates>'
|
||||
for TAG in stable rc development; do
|
||||
[ "$TAG" = "${STABILITY}" ] && continue
|
||||
if grep -q "<tag>${TAG}</tag>" updates.xml 2>/dev/null; then
|
||||
sed -n "/<update>/,/<\/update>/{ /<tag>${TAG}<\/tag>/p; }" updates.xml
|
||||
fi
|
||||
done
|
||||
cat /tmp/new_entry.xml
|
||||
printf '\n%s\n' '</updates>'
|
||||
} > /tmp/updates_new.xml
|
||||
mv /tmp/updates_new.xml updates.xml
|
||||
fi
|
||||
fi
|
||||
|
||||
# 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
|
||||
|
||||
# -- Mirror to GitHub (stable and rc only) --------------------------------
|
||||
- name: Mirror release to GitHub
|
||||
if: >-
|
||||
(steps.update.outputs.stability == 'stable' || steps.update.outputs.stability == 'rc') &&
|
||||
secrets.GH_TOKEN != ''
|
||||
continue-on-error: true
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
run: |
|
||||
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
|
||||
STABILITY="${{ steps.update.outputs.stability }}"
|
||||
echo "GitHub mirror sync for ${STABILITY} — ${GH_REPO}" >> $GITHUB_STEP_SUMMARY
|
||||
# Mirror packages if they exist
|
||||
for PKG in /tmp/*.zip /tmp/*.tar.gz; do
|
||||
if [ -f "$PKG" ]; then
|
||||
_RELID=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${{ github.repository }}/releases/tags/${RELEASE_TAG}" 2>/dev/null | jq -r ".id // empty")
|
||||
[ -n "$_RELID" ] && curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" -H "Content-Type: application/octet-stream" "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${{ github.repository }}/releases/${_RELID}/assets?name=$(basename $PKG)" --data-binary "@$PKG" > /dev/null 2>&1 || true
|
||||
fi
|
||||
done
|
||||
|
||||
- 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
|
||||
@@ -330,6 +330,8 @@ jobs:
|
||||
INFO_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/stable"
|
||||
|
||||
# -- Build update entry for a given stability tag
|
||||
CREATION_DATE=$(date +%Y-%m-%d)
|
||||
|
||||
build_entry() {
|
||||
local TAG_NAME="$1"
|
||||
printf '%s\n' ' <update>'
|
||||
@@ -338,6 +340,7 @@ jobs:
|
||||
printf '%s\n' " <element>${EXT_ELEMENT}</element>"
|
||||
printf '%s\n' " <type>${EXT_TYPE}</type>"
|
||||
printf '%s\n' " <version>${VERSION}</version>"
|
||||
printf '%s\n' " <creationDate>${CREATION_DATE}</creationDate>"
|
||||
[ -n "$CLIENT_TAG" ] && printf '%s\n' " ${CLIENT_TAG}"
|
||||
[ -n "$FOLDER_TAG" ] && printf '%s\n' " ${FOLDER_TAG}"
|
||||
printf '%s\n' " <tags><tag>${TAG_NAME}</tag></tags>"
|
||||
|
||||
@@ -278,6 +278,7 @@ jobs:
|
||||
NEW_ENTRY="${NEW_ENTRY} <element>${EXT_ELEMENT}</element>\n"
|
||||
NEW_ENTRY="${NEW_ENTRY} <type>${EXT_TYPE}</type>\n"
|
||||
NEW_ENTRY="${NEW_ENTRY} <version>${DISPLAY_VERSION}</version>\n"
|
||||
NEW_ENTRY="${NEW_ENTRY} <creationDate>$(date +%Y-%m-%d)</creationDate>\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} <tags>\n"
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
INGROUP: MokoOnyx.Documentation
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
|
||||
FILE: ./README.md
|
||||
VERSION: 01.00.19
|
||||
VERSION: 01.00.25
|
||||
BRIEF: Documentation for MokoOnyx template
|
||||
-->
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
2026-04-26
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: MokoOnyx.Override
|
||||
* INGROUP: MokoOnyx
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
|
||||
* PATH: /html/com_joomgallery/category/default.php
|
||||
* VERSION: 01.00.00
|
||||
* BRIEF: Category view override — password gate then loads default_cat sub-layout
|
||||
*/
|
||||
|
||||
// No direct access
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Router\Route;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
|
||||
// Import CSS & JS
|
||||
$wa = $this->document->getWebAssetManager();
|
||||
$wa->useStyle('com_joomgallery.site');
|
||||
$wa->useStyle('com_joomgallery.jg-icon-font');
|
||||
?>
|
||||
|
||||
<?php if ($this->item->pw_protected) : ?>
|
||||
<div class="com-joomgallery-category--locked">
|
||||
<form action="<?php echo Route::_('index.php?task=category.unlock&catid=' . $this->item->id); ?>" method="post" class="row g-3 align-items-end" autocomplete="off">
|
||||
<div class="col-12">
|
||||
<h3><i class="jg-icon-lock me-2" aria-hidden="true"></i><?php echo Text::_('COM_JOOMGALLERY_CATEGORY_PASSWORD_PROTECTED'); ?></h3>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<label for="jg_password" class="form-label"><?php echo Text::_('JGLOBAL_PASSWORD'); ?></label>
|
||||
<input type="password" name="password" id="jg_password" class="form-control" required />
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button type="submit" class="btn btn-primary" id="jg_unlock_button">
|
||||
<i class="jg-icon-unlock me-1" aria-hidden="true"></i><?php echo Text::_('COM_JOOMGALLERY_CATEGORY_BUTTON_UNLOCK'); ?>
|
||||
</button>
|
||||
</div>
|
||||
<?php echo HTMLHelper::_('form.token'); ?>
|
||||
</form>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<?php echo $this->loadTemplate('cat'); ?>
|
||||
<?php endif; ?>
|
||||
@@ -0,0 +1,219 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: MokoOnyx.Override
|
||||
* INGROUP: MokoOnyx
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
|
||||
* PATH: /html/com_joomgallery/category/default_cat.php
|
||||
* VERSION: 01.00.00
|
||||
* BRIEF: Category sub-layout — subcategories grid + images grid with pagination
|
||||
*/
|
||||
|
||||
// No direct access
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Router\Route;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Session\Session;
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Layout\LayoutHelper;
|
||||
use Joomgallery\Component\Joomgallery\Administrator\Helper\JoomHelper;
|
||||
|
||||
// Image params
|
||||
$image_type = $this->params['configs']->get('jg_category_view_type_image', 'thumbnail', 'STRING');
|
||||
$gallery_class = $this->params['configs']->get('jg_category_view_class', 'masonry', 'STRING');
|
||||
$num_columns = $this->params['configs']->get('jg_category_view_num_columns', 3, 'INT');
|
||||
$image_class = $this->params['configs']->get('jg_category_view_image_class', 0, 'INT');
|
||||
$justified_height = $this->params['configs']->get('jg_category_view_justified_height', 200, 'INT');
|
||||
$justified_gap = $this->params['configs']->get('jg_category_view_justified_gap', 5, 'INT');
|
||||
$image_link = $this->params['configs']->get('jg_category_view_image_link', 'defaultview', 'STRING');
|
||||
$lightbox_image = $this->params['configs']->get('jg_category_view_lightbox_image', 'detail', 'STRING');
|
||||
$pagination_type = $this->params['configs']->get('jg_category_view_pagination', 0, 'INT');
|
||||
$show_subcategories = $this->params['configs']->get('jg_category_view_subcategories', 1, 'INT');
|
||||
$subcategory_type_image = $this->params['configs']->get('jg_category_view_type_subcategory_image', 'thumbnail', 'STRING');
|
||||
$num_columns_subcats = $this->params['configs']->get('jg_category_view_subcategories_num_columns', 3, 'INT');
|
||||
|
||||
// Import CSS & JS
|
||||
$wa = $this->document->getWebAssetManager();
|
||||
|
||||
if ($gallery_class == 'masonry') {
|
||||
$wa->useScript('com_joomgallery.masonry');
|
||||
}
|
||||
|
||||
if ($gallery_class == 'justified') {
|
||||
$wa->useScript('com_joomgallery.justified');
|
||||
$wa->addInlineStyle('.jg-images[class*=" justified-"] .jg-image-caption-hover { right: ' . $justified_gap . 'px; }');
|
||||
}
|
||||
|
||||
$lightbox = false;
|
||||
if ($image_link == 'lightgallery') {
|
||||
$lightbox = true;
|
||||
$wa->useScript('com_joomgallery.lightgallery');
|
||||
$wa->useScript('com_joomgallery.lg-thumbnail');
|
||||
$wa->useStyle('com_joomgallery.lightgallery-bundle');
|
||||
}
|
||||
|
||||
// Initialise the grid script
|
||||
$iniJS = 'window.joomGrid = {';
|
||||
$iniJS .= ' itemid: ' . $this->item->id . ',';
|
||||
$iniJS .= ' pagination: ' . $pagination_type . ',';
|
||||
$iniJS .= ' layout: "' . $gallery_class . '",';
|
||||
$iniJS .= ' num_columns: ' . $num_columns . ',';
|
||||
$iniJS .= ' lightbox: ' . ($lightbox ? 'true' : 'false') . ',';
|
||||
$iniJS .= ' justified: {height: ' . $justified_height . ', gap: ' . $justified_gap . '}';
|
||||
$iniJS .= '};';
|
||||
|
||||
$wa->addInlineScript($iniJS, ['position' => 'before'], [], ['com_joomgallery.joomgrid']);
|
||||
$wa->useScript('com_joomgallery.joomgrid');
|
||||
|
||||
// Access check
|
||||
$canEdit = $this->getAcl()->checkACL('edit', 'com_joomgallery.category', $this->item->id, $this->item->parent_id, true);
|
||||
$canAdd = $this->getAcl()->checkACL('add', 'com_joomgallery.image', 0, $this->item->id, true);
|
||||
$canDelete = $this->getAcl()->checkACL('delete', 'com_joomgallery.category', $this->item->id, $this->item->parent_id, true);
|
||||
$canCheckin = $this->getAcl()->checkACL('editstate', 'com_joomgallery.category', $this->item->id, $this->item->parent_id, true) || $this->item->checked_out == Factory::getUser()->id;
|
||||
$returnURL = base64_encode(JoomHelper::getViewRoute('category', $this->item->id, $this->item->parent_id, $this->item->language, $this->getLayout()));
|
||||
|
||||
$hasSubcats = $show_subcategories && !empty($this->item->children->items) && count($this->item->children->items) > 0;
|
||||
$hasImages = !empty($this->item->images->items) && count($this->item->images->items) > 0;
|
||||
?>
|
||||
|
||||
<div class="com-joomgallery-category" itemscope itemtype="https://schema.org/ImageGallery">
|
||||
<?php // Page heading ?>
|
||||
<?php if ($this->params['menu']->get('show_page_heading')) : ?>
|
||||
<div class="page-header">
|
||||
<h1><?php echo $this->escape($this->params['menu']->get('page_heading')); ?></h1>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php // Category title ?>
|
||||
<h2 itemprop="name"><?php echo $this->escape($this->item->title); ?></h2>
|
||||
|
||||
<?php // Category description ?>
|
||||
<?php if (!empty($this->item->description)) : ?>
|
||||
<div class="com-joomgallery-category__description mb-3" itemprop="description">
|
||||
<?php echo $this->item->description; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php // Admin buttons ?>
|
||||
<?php if ($canEdit || $canCheckin || $canAdd || $canDelete) : ?>
|
||||
<div class="com-joomgallery-category__actions btn-toolbar mb-3" role="toolbar" aria-label="<?php echo Text::_('JTOOLBAR'); ?>">
|
||||
<?php if ($canCheckin && $this->item->checked_out > 0) : ?>
|
||||
<a class="btn btn-outline-secondary btn-sm me-2" href="<?php echo Route::_('index.php?option=com_joomgallery&task=category.checkin&id=' . $this->item->id . '&return=' . $returnURL . '&' . Session::getFormToken() . '=1'); ?>">
|
||||
<i class="jg-icon-checkin me-1" aria-hidden="true"></i><?php echo Text::_('JLIB_HTML_CHECKIN'); ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($canEdit) : ?>
|
||||
<a class="btn btn-outline-primary btn-sm me-2<?php echo ($this->item->checked_out > 0) ? ' disabled' : ''; ?>" href="<?php echo Route::_('index.php?option=com_joomgallery&task=category.edit&id=' . $this->item->id . '&return=' . $returnURL); ?>">
|
||||
<i class="jg-icon-edit me-1" aria-hidden="true"></i><?php echo Text::_('JGLOBAL_EDIT'); ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($canAdd) : ?>
|
||||
<a class="btn btn-outline-success btn-sm me-2<?php echo ($this->item->checked_out > 0) ? ' disabled' : ''; ?>" href="<?php echo Route::_('index.php?option=com_joomgallery&task=image.add&catid=' . $this->item->id . '&return=' . $returnURL); ?>">
|
||||
<i class="jg-icon-upload me-1" aria-hidden="true"></i><?php echo Text::_('COM_JOOMGALLERY_IMG_UPLOAD_IMAGE'); ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($canDelete) : ?>
|
||||
<a class="btn btn-outline-danger btn-sm<?php echo ($this->item->checked_out > 0) ? ' disabled' : ''; ?>" href="#deleteCatModal" role="button" data-bs-toggle="modal">
|
||||
<i class="jg-icon-delete me-1" aria-hidden="true"></i><?php echo Text::_('JACTION_DELETE'); ?>
|
||||
</a>
|
||||
<?php echo HTMLHelper::_(
|
||||
'bootstrap.renderModal',
|
||||
'deleteCatModal',
|
||||
[
|
||||
'title' => Text::_('JACTION_DELETE'),
|
||||
'modalWidth' => '50',
|
||||
'bodyHeight' => '100',
|
||||
'footer' => '<button class="btn btn-secondary" data-bs-dismiss="modal">' . Text::_('JCANCEL') . '</button>'
|
||||
. '<a href="' . Route::_('index.php?option=com_joomgallery&task=category.remove&id=' . $this->item->id . '&return=' . $returnURL . '&' . Session::getFormToken() . '=1', false, 2) . '" class="btn btn-danger">' . Text::_('JACTION_DELETE') . '</a>',
|
||||
],
|
||||
Text::_('COM_JOOMGALLERY_COMMON_ALERT_SURE_DELETE_SELECTED_ITEM')
|
||||
); ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php // Subcategories ?>
|
||||
<?php if ($hasSubcats) : ?>
|
||||
<section class="com-joomgallery-category__subcategories mb-4" aria-label="<?php echo Text::_('COM_JOOMGALLERY_SUBCATEGORIES'); ?>">
|
||||
<h3><?php echo Text::_('COM_JOOMGALLERY_SUBCATEGORIES'); ?></h3>
|
||||
<?php
|
||||
$catsData = [
|
||||
'id' => (int) $this->item->id,
|
||||
'items' => $this->item->children->items,
|
||||
'num_columns' => (int) $num_columns_subcats,
|
||||
'image_type' => $subcategory_type_image,
|
||||
];
|
||||
echo LayoutHelper::render('joomgallery.grids.categories', $catsData);
|
||||
?>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php // Images ?>
|
||||
<?php if ($hasImages) : ?>
|
||||
<section class="com-joomgallery-category__images" aria-label="<?php echo Text::_('COM_JOOMGALLERY_IMAGES'); ?>">
|
||||
<?php if ($hasSubcats) : ?>
|
||||
<h3><?php echo Text::_('COM_JOOMGALLERY_IMAGES'); ?></h3>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
$imgsData = [
|
||||
'id' => (int) $this->item->id,
|
||||
'layout' => $gallery_class,
|
||||
'items' => $this->item->images->items,
|
||||
'num_columns' => (int) $num_columns,
|
||||
'caption_align' => 'center',
|
||||
'image_class' => $image_class,
|
||||
'image_type' => $image_type,
|
||||
'lightbox_type' => $lightbox_image,
|
||||
'image_link' => $image_link,
|
||||
'image_title' => false,
|
||||
'title_link' => 'defaultview',
|
||||
'image_desc' => false,
|
||||
'image_date' => false,
|
||||
'image_author' => false,
|
||||
'image_tags' => false,
|
||||
];
|
||||
echo LayoutHelper::render('joomgallery.grids.images', $imgsData);
|
||||
?>
|
||||
|
||||
<?php // Pagination ?>
|
||||
<nav class="mt-4" aria-label="<?php echo Text::_('JLIB_HTML_PAGINATION'); ?>">
|
||||
<?php echo $this->item->images->pagination->getListFooter(); ?>
|
||||
</nav>
|
||||
</section>
|
||||
<?php elseif (!$hasSubcats) : ?>
|
||||
<div class="alert alert-info" role="alert">
|
||||
<p class="mb-0"><?php echo Text::_('COM_JOOMGALLERY_GALLERY_NO_IMAGES'); ?></p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php // Back to parent category ?>
|
||||
<?php if ($this->item->parent_id > 0 && $this->item->parent_id != 1) : ?>
|
||||
<div class="mt-4">
|
||||
<a class="btn btn-outline-secondary" href="<?php echo Route::_('index.php?option=com_joomgallery&view=category&id=' . (int) $this->item->parent_id); ?>">
|
||||
<i class="jg-icon-arrow-left-alt me-1" aria-hidden="true"></i><?php echo Text::_('COM_JOOMGALLERY_BACK'); ?>
|
||||
</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<script>
|
||||
if (window.joomGrid.layout != 'justified') {
|
||||
document.querySelectorAll('.' + window.joomGrid.imgclass).forEach(function(image) {
|
||||
image.addEventListener('load', function() {
|
||||
this.closest('.' + window.joomGrid.imgboxclass).classList.add('loaded');
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
@@ -0,0 +1 @@
|
||||
<!DOCTYPE html><title></title>
|
||||
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: MokoOnyx.Override
|
||||
* INGROUP: MokoOnyx
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
|
||||
* PATH: /html/com_joomgallery/gallery/default.php
|
||||
* VERSION: 01.00.00
|
||||
* BRIEF: Gallery view override — main image grid with masonry/justified layout
|
||||
*/
|
||||
|
||||
// No direct access
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Router\Route;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Layout\LayoutHelper;
|
||||
|
||||
// Image params
|
||||
$image_type = $this->params['configs']->get('jg_gallery_view_type_image', 'thumbnail', 'STRING');
|
||||
$gallery_class = $this->params['configs']->get('jg_gallery_view_class', 'masonry', 'STRING');
|
||||
$num_columns = $this->params['configs']->get('jg_gallery_view_num_columns', 3, 'INT');
|
||||
$image_class = $this->params['configs']->get('jg_gallery_view_image_class', 0, 'INT');
|
||||
$justified_height = $this->params['configs']->get('jg_gallery_view_justified_height', 200, 'INT');
|
||||
$justified_gap = $this->params['configs']->get('jg_gallery_view_justified_gap', 5, 'INT');
|
||||
$image_link = $this->params['configs']->get('jg_gallery_view_image_link', 'defaultview', 'STRING');
|
||||
$lightbox_image = $this->params['configs']->get('jg_category_view_lightbox_image', 'detail', 'STRING');
|
||||
$browse_categories_link = $this->params['configs']->get('jg_gallery_view_browse_categories_link', 1, 'INT');
|
||||
|
||||
// Import CSS & JS
|
||||
$wa = $this->document->getWebAssetManager();
|
||||
$wa->useStyle('com_joomgallery.site');
|
||||
$wa->useStyle('com_joomgallery.jg-icon-font');
|
||||
|
||||
if ($gallery_class == 'masonry') {
|
||||
$wa->useScript('com_joomgallery.masonry');
|
||||
}
|
||||
|
||||
if ($gallery_class == 'justified') {
|
||||
$wa->useScript('com_joomgallery.justified');
|
||||
$wa->addInlineStyle('.jg-images[class*=" justified-"] .jg-image-caption-hover { right: ' . $justified_gap . 'px; }');
|
||||
}
|
||||
|
||||
$lightbox = false;
|
||||
if ($image_link == 'lightgallery') {
|
||||
$lightbox = true;
|
||||
$wa->useScript('com_joomgallery.lightgallery');
|
||||
$wa->useScript('com_joomgallery.lg-thumbnail');
|
||||
$wa->useStyle('com_joomgallery.lightgallery-bundle');
|
||||
}
|
||||
|
||||
// Initialise the grid script
|
||||
$iniJS = 'window.joomGrid = {';
|
||||
$iniJS .= ' itemid: ' . $this->item->id . ',';
|
||||
$iniJS .= ' pagination: 0,';
|
||||
$iniJS .= ' layout: "' . $gallery_class . '",';
|
||||
$iniJS .= ' num_columns: ' . $num_columns . ',';
|
||||
$iniJS .= ' lightbox: ' . ($lightbox ? 'true' : 'false') . ',';
|
||||
$iniJS .= ' justified: {height: ' . $justified_height . ', gap: ' . $justified_gap . '}';
|
||||
$iniJS .= '};';
|
||||
|
||||
$wa->addInlineScript($iniJS, ['position' => 'before'], [], ['com_joomgallery.joomgrid']);
|
||||
$wa->useScript('com_joomgallery.joomgrid');
|
||||
?>
|
||||
|
||||
<div class="com-joomgallery-gallery" itemscope itemtype="https://schema.org/ImageGallery">
|
||||
<?php if ($this->params['menu']->get('show_page_heading')) : ?>
|
||||
<div class="page-header">
|
||||
<h1><?php echo $this->escape($this->params['menu']->get('page_heading')); ?></h1>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php // Browse categories link (top) ?>
|
||||
<?php if ($browse_categories_link == '1') : ?>
|
||||
<div class="text-center mb-4">
|
||||
<a class="btn btn-outline-primary" href="<?php echo Route::_('index.php?option=com_joomgallery&view=category&id=1'); ?>">
|
||||
<i class="jg-icon-folder me-1" aria-hidden="true"></i><?php echo Text::_('COM_JOOMGALLERY_GALLERY_VIEW_BROWSE_CATEGORIES'); ?>
|
||||
</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (count($this->item->images->items) == 0) : ?>
|
||||
<div class="alert alert-info" role="alert">
|
||||
<p class="mb-0"><?php echo Text::_('COM_JOOMGALLERY_GALLERY_NO_IMAGES'); ?></p>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<?php
|
||||
$imgsData = [
|
||||
'id' => (int) $this->item->id,
|
||||
'layout' => $gallery_class,
|
||||
'items' => $this->item->images->items,
|
||||
'num_columns' => (int) $num_columns,
|
||||
'caption_align' => 'center',
|
||||
'image_class' => $image_class,
|
||||
'image_type' => $image_type,
|
||||
'lightbox_type' => $lightbox_image,
|
||||
'image_link' => $image_link,
|
||||
'image_title' => false,
|
||||
'title_link' => 'defaultview',
|
||||
'image_desc' => false,
|
||||
'image_date' => false,
|
||||
'image_author' => false,
|
||||
'image_tags' => false,
|
||||
];
|
||||
?>
|
||||
<?php echo LayoutHelper::render('joomgallery.grids.images', $imgsData); ?>
|
||||
|
||||
<?php // Pagination ?>
|
||||
<nav class="mt-4" aria-label="<?php echo Text::_('JLIB_HTML_PAGINATION'); ?>">
|
||||
<?php echo $this->item->images->pagination->getListFooter(); ?>
|
||||
</nav>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php // Browse categories link (bottom) ?>
|
||||
<?php if ($browse_categories_link == '2') : ?>
|
||||
<div class="text-center mt-4">
|
||||
<a class="btn btn-outline-primary" href="<?php echo Route::_('index.php?option=com_joomgallery&view=category&id=1'); ?>">
|
||||
<i class="jg-icon-folder me-1" aria-hidden="true"></i><?php echo Text::_('COM_JOOMGALLERY_GALLERY_VIEW_BROWSE_CATEGORIES'); ?>
|
||||
</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<script>
|
||||
if (window.joomGrid.layout != 'justified') {
|
||||
document.querySelectorAll('.' + window.joomGrid.imgclass).forEach(function(image) {
|
||||
image.addEventListener('load', function() {
|
||||
this.closest('.' + window.joomGrid.imgboxclass).classList.add('loaded');
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
@@ -0,0 +1 @@
|
||||
<!DOCTYPE html><title></title>
|
||||
@@ -0,0 +1,249 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: MokoOnyx.Override
|
||||
* INGROUP: MokoOnyx
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx
|
||||
* PATH: /html/com_joomgallery/image/default.php
|
||||
* VERSION: 01.00.00
|
||||
* BRIEF: Image detail view override — single image with metadata, tags, custom fields
|
||||
*/
|
||||
|
||||
// No direct access
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Router\Route;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Session\Session;
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Layout\FileLayout;
|
||||
use Joomla\CMS\User\UserFactoryInterface;
|
||||
use Joomla\Component\Fields\Administrator\Helper\FieldsHelper;
|
||||
use Joomgallery\Component\Joomgallery\Administrator\Helper\JoomHelper;
|
||||
|
||||
// Image params
|
||||
$image_type = $this->params['configs']->get('jg_detail_view_type_image', 'detail', 'STRING');
|
||||
$show_title = $this->params['configs']->get('jg_detail_view_show_title', 0, 'INT');
|
||||
$show_category = $this->params['configs']->get('jg_detail_view_show_category', 0, 'INT');
|
||||
$show_description = $this->params['configs']->get('jg_detail_view_show_description', 0, 'INT');
|
||||
$show_imgdate = $this->params['configs']->get('jg_detail_view_show_imgdate', 0, 'INT');
|
||||
$show_imgauthor = $this->params['configs']->get('jg_detail_view_show_imgauthor', 0, 'INT');
|
||||
$show_created_by = $this->params['configs']->get('jg_detail_view_show_created_by', 0, 'INT');
|
||||
$show_votes = $this->params['configs']->get('jg_detail_view_show_votes', 0, 'INT');
|
||||
$show_rating = $this->params['configs']->get('jg_detail_view_show_rating', 0, 'INT');
|
||||
$show_hits = $this->params['configs']->get('jg_detail_view_show_hits', 0, 'INT');
|
||||
$show_downloads = $this->params['configs']->get('jg_detail_view_show_downloads', 0, 'INT');
|
||||
$show_tags = $this->params['configs']->get('jg_detail_view_show_tags', 0, 'INT');
|
||||
$show_metadata = $this->params['configs']->get('jg_detail_view_show_metadata', 0, 'INT');
|
||||
|
||||
// Import CSS & JS
|
||||
$wa = $this->document->getWebAssetManager();
|
||||
$wa->useStyle('com_joomgallery.site');
|
||||
$wa->useStyle('com_joomgallery.jg-icon-font');
|
||||
|
||||
// Access check
|
||||
$canEdit = $this->getAcl()->checkACL('edit', 'com_joomgallery.image', $this->item->id, $this->item->catid, true);
|
||||
$canDelete = $this->getAcl()->checkACL('delete', 'com_joomgallery.image', $this->item->id, $this->item->catid, true);
|
||||
$canCheckin = $this->getAcl()->checkACL('editstate', 'com_joomgallery.image', $this->item->id, $this->item->catid, true) || $this->item->checked_out == Factory::getUser()->id;
|
||||
$returnURL = base64_encode(JoomHelper::getViewRoute('image', $this->item->id, $this->item->catid, $this->item->language, $this->getLayout()));
|
||||
|
||||
// Tags
|
||||
$tagLayout = new FileLayout('joomgallery.content.tags');
|
||||
$tags = $tagLayout->render($this->item->tags);
|
||||
|
||||
// Metadata
|
||||
$metadataLayout = new FileLayout('joomgallery.content.metadata');
|
||||
$metadata = $metadataLayout->render($this->item->imgmetadata);
|
||||
|
||||
// Custom Fields
|
||||
$fields = FieldsHelper::getFields('com_joomgallery.image', $this->item);
|
||||
|
||||
// Check if we have any info rows to show
|
||||
$hasInfo = $show_category || $show_imgdate || $show_imgauthor || $show_created_by
|
||||
|| $show_votes || $show_rating || $show_hits || $show_downloads
|
||||
|| $show_tags || $show_metadata || count($fields) > 0;
|
||||
?>
|
||||
|
||||
<div class="com-joomgallery-image" itemscope itemtype="https://schema.org/ImageObject">
|
||||
<?php if ($show_title) : ?>
|
||||
<h2 itemprop="name"><?php echo $this->escape($this->item->title); ?></h2>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php // Back to category ?>
|
||||
<a class="btn btn-outline-primary btn-sm mb-3" href="<?php echo Route::_('index.php?option=com_joomgallery&view=category&id=' . (int) $this->item->catid); ?>">
|
||||
<i class="jg-icon-arrow-left-alt me-1" aria-hidden="true"></i><?php echo Text::_('COM_JOOMGALLERY_IMAGE_BACK_TO_CATEGORY') . ' ' . $this->escape($this->item->cattitle); ?>
|
||||
</a>
|
||||
|
||||
<?php // Admin buttons ?>
|
||||
<?php if ($canEdit || $canCheckin || $canDelete) : ?>
|
||||
<div class="com-joomgallery-image__actions btn-toolbar mb-3" role="toolbar" aria-label="<?php echo Text::_('JTOOLBAR'); ?>">
|
||||
<?php if ($canCheckin && $this->item->checked_out > 0) : ?>
|
||||
<a class="btn btn-outline-secondary btn-sm me-2" href="<?php echo Route::_('index.php?option=com_joomgallery&task=image.checkin&id=' . $this->item->id . '&return=' . $returnURL . '&' . Session::getFormToken() . '=1'); ?>">
|
||||
<i class="jg-icon-checkin me-1" aria-hidden="true"></i><?php echo Text::_('JLIB_HTML_CHECKIN'); ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($canEdit) : ?>
|
||||
<a class="btn btn-outline-primary btn-sm me-2<?php echo ($this->item->checked_out > 0) ? ' disabled' : ''; ?>" href="<?php echo Route::_('index.php?option=com_joomgallery&task=image.edit&id=' . $this->item->id . '&return=' . $returnURL); ?>">
|
||||
<i class="jg-icon-edit me-1" aria-hidden="true"></i><?php echo Text::_('JGLOBAL_EDIT'); ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($canDelete) : ?>
|
||||
<a class="btn btn-outline-danger btn-sm<?php echo ($this->item->checked_out > 0) ? ' disabled' : ''; ?>" href="#deleteImgModal" role="button" data-bs-toggle="modal">
|
||||
<i class="jg-icon-delete me-1" aria-hidden="true"></i><?php echo Text::_('JACTION_DELETE'); ?>
|
||||
</a>
|
||||
<?php echo HTMLHelper::_(
|
||||
'bootstrap.renderModal',
|
||||
'deleteImgModal',
|
||||
[
|
||||
'title' => Text::_('JACTION_DELETE'),
|
||||
'modalWidth' => '50',
|
||||
'bodyHeight' => '100',
|
||||
'footer' => '<button class="btn btn-secondary" data-bs-dismiss="modal">' . Text::_('JCANCEL') . '</button>'
|
||||
. '<a href="' . Route::_('index.php?option=com_joomgallery&task=image.remove&id=' . $this->item->id . '&return=' . $returnURL . '&' . Session::getFormToken() . '=1', false, 2) . '" class="btn btn-danger">' . Text::_('COM_JOOMGALLERY_COMMON_DELETE_IMAGE_TIPCAPTION') . '</a>',
|
||||
],
|
||||
Text::_('COM_JOOMGALLERY_COMMON_ALERT_SURE_DELETE_SELECTED_ITEM')
|
||||
); ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php // Image ?>
|
||||
<figure class="figure com-joomgallery-image__figure text-center w-100 mb-4">
|
||||
<div id="jg-loader"></div>
|
||||
<img
|
||||
src="<?php echo JoomHelper::getImg($this->item, $image_type); ?>"
|
||||
class="figure-img img-fluid rounded"
|
||||
alt="<?php echo $this->escape($this->item->title); ?>"
|
||||
style="width:auto;"
|
||||
itemprop="contentUrl"
|
||||
loading="lazy"
|
||||
/>
|
||||
<?php if ($show_description && !empty($this->item->description)) : ?>
|
||||
<figcaption class="figure-caption" itemprop="description"><?php echo $this->item->description; ?></figcaption>
|
||||
<?php endif; ?>
|
||||
</figure>
|
||||
|
||||
<?php // Image info table ?>
|
||||
<?php if ($hasInfo) : ?>
|
||||
<div class="com-joomgallery-image__info">
|
||||
<h3><?php echo Text::_('COM_JOOMGALLERY_IMAGE_INFO'); ?></h3>
|
||||
<table class="table table-striped">
|
||||
<tbody>
|
||||
<?php if ($show_category) : ?>
|
||||
<tr>
|
||||
<th scope="row"><?php echo Text::_('JCATEGORY'); ?></th>
|
||||
<td>
|
||||
<a href="<?php echo Route::_('index.php?option=com_joomgallery&view=category&id=' . (int) $this->item->catid); ?>">
|
||||
<?php echo $this->escape($this->item->cattitle); ?>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($show_imgdate) : ?>
|
||||
<tr>
|
||||
<th scope="row"><?php echo Text::_('COM_JOOMGALLERY_DATE'); ?></th>
|
||||
<td>
|
||||
<time datetime="<?php echo HTMLHelper::_('date', $this->item->date, 'c'); ?>" itemprop="dateCreated">
|
||||
<?php echo HTMLHelper::_('date', $this->item->date, Text::_('DATE_FORMAT_LC4')); ?>
|
||||
</time>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($show_imgauthor) : ?>
|
||||
<tr>
|
||||
<th scope="row"><?php echo Text::_('JAUTHOR'); ?></th>
|
||||
<td itemprop="author"><?php echo $this->escape($this->item->author); ?></td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($show_created_by) : ?>
|
||||
<?php $user = Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById($this->item->created_by); ?>
|
||||
<tr>
|
||||
<th scope="row"><?php echo Text::_('COM_JOOMGALLERY_OWNER'); ?></th>
|
||||
<td><?php echo $this->escape($user->name); ?></td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($show_votes) : ?>
|
||||
<tr>
|
||||
<th scope="row"><?php echo Text::_('COM_JOOMGALLERY_VOTES'); ?></th>
|
||||
<td><?php echo $this->escape($this->item->votes); ?></td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($show_rating) : ?>
|
||||
<tr>
|
||||
<th scope="row"><?php echo Text::_('COM_JOOMGALLERY_IMAGE_RATING'); ?></th>
|
||||
<td><?php echo $this->escape($this->item->rating); ?></td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($show_hits) : ?>
|
||||
<tr>
|
||||
<th scope="row"><?php echo Text::_('JGLOBAL_HITS'); ?></th>
|
||||
<td><?php echo (int) $this->item->hits; ?></td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($show_downloads) : ?>
|
||||
<tr>
|
||||
<th scope="row"><?php echo Text::_('COM_JOOMGALLERY_DOWNLOADS'); ?></th>
|
||||
<td><?php echo (int) $this->item->downloads; ?></td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($show_tags) : ?>
|
||||
<tr>
|
||||
<th scope="row"><?php echo Text::_('COM_JOOMGALLERY_TAGS'); ?></th>
|
||||
<td><?php echo $tags; ?></td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($show_metadata) : ?>
|
||||
<tr>
|
||||
<th scope="row"><?php echo Text::_('COM_JOOMGALLERY_IMGMETADATA'); ?></th>
|
||||
<td><?php echo $metadata; ?></td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php // Custom fields ?>
|
||||
<?php if (count($fields) > 0) : ?>
|
||||
<tr>
|
||||
<th scope="row" colspan="2"><strong><?php echo Text::_('JGLOBAL_FIELDS'); ?></strong></th>
|
||||
</tr>
|
||||
<?php foreach ($fields as $field) : ?>
|
||||
<?php if ($this->component->getAccess()->checkViewLevel($field->access) && $field->params->get('display') > 0) : ?>
|
||||
<tr class="<?php echo $this->escape($field->params->get('render_class')); ?>">
|
||||
<?php if ($field->params->get('showlabel', true)) : ?>
|
||||
<th scope="row" class="<?php echo $this->escape($field->params->get('label_render_class')); ?>"><?php echo $this->escape($field->title); ?></th>
|
||||
<?php else : ?>
|
||||
<th scope="row"></th>
|
||||
<?php endif; ?>
|
||||
<td class="<?php echo $this->escape($field->params->get('value_render_class')); ?>"><?php echo $field->value; ?></td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<script>
|
||||
window.onload = function() {
|
||||
var el = document.querySelector('#jg-loader');
|
||||
if (el) el.classList.add('hidden');
|
||||
};
|
||||
</script>
|
||||
</div>
|
||||
@@ -0,0 +1 @@
|
||||
<!DOCTYPE html><title></title>
|
||||
@@ -39,9 +39,9 @@
|
||||
</server>
|
||||
</updateservers>
|
||||
<name>MokoOnyx</name>
|
||||
<version>01.00.18</version>
|
||||
<version>01.00.25</version>
|
||||
<scriptfile>script.php</scriptfile>
|
||||
<creationDate>2026-04-15</creationDate>
|
||||
<creationDate>2026-04-26</creationDate>
|
||||
<author>Jonathan Miller || Moko Consulting</author>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<copyright>(C)GNU General Public License Version 3 - 2026 Moko Consulting</copyright>
|
||||
@@ -376,3 +376,4 @@
|
||||
</fields>
|
||||
</config>
|
||||
</extension>
|
||||
<!-- dev release 01.00.26 -->
|
||||
|
||||
+19
-20
@@ -7,6 +7,25 @@
|
||||
<updates>
|
||||
|
||||
<!-- 1. DEVELOPMENT -->
|
||||
<update>
|
||||
<name>MokoOnyx</name>
|
||||
<description>MokoOnyx development build — unstable.</description>
|
||||
<element>mokoonyx</element>
|
||||
<type>template</type>
|
||||
<client>site</client>
|
||||
<version>01.00.26</version>
|
||||
<creationDate>2026-04-26</creationDate>
|
||||
<infourl title='MokoOnyx Dev'>https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/tag/development</infourl>
|
||||
<downloads>
|
||||
<downloadurl type='full' format='zip'>https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/development/mokoonyx-01.00.26-dev.zip</downloadurl>
|
||||
</downloads>
|
||||
<sha256></sha256>
|
||||
<tags><tag>development</tag></tags>
|
||||
<maintainer>Moko Consulting</maintainer>
|
||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||
<targetplatform name='joomla' version='(5|6).*'/>
|
||||
<php_minimum>8.1</php_minimum>
|
||||
</update>
|
||||
|
||||
<!-- 2. ALPHA -->
|
||||
<update>
|
||||
@@ -92,24 +111,4 @@
|
||||
<php_minimum>8.1</php_minimum>
|
||||
</update>
|
||||
|
||||
<update>
|
||||
<name>MokoOnyx</name>
|
||||
<description>MokoOnyx (development)</description>
|
||||
<element>mokoonyx</element>
|
||||
<type>template</type>
|
||||
<version>01.00.26-dev</version>
|
||||
<creationDate>2026-04-26</creationDate>
|
||||
<client>site</client>
|
||||
<tags>
|
||||
<tag>development</tag>
|
||||
</tags>
|
||||
<infourl title="MokoOnyx">https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx</infourl>
|
||||
<downloads>
|
||||
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoOnyx/releases/download/development/mokoonyx-01.00.26-dev.zip</downloadurl>
|
||||
</downloads>
|
||||
<sha256>c78a762256fc326662ffa87574246f2f4cbb9009391b93de9ef5eed9f1152107</sha256>
|
||||
<targetplatform name="joomla" version="((5.[0-9])|(6.[0-9]))" />
|
||||
<maintainer>Moko Consulting</maintainer>
|
||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||
</update>
|
||||
</updates>
|
||||
|
||||
Reference in New Issue
Block a user