fix: resolve merge conflicts and pretty name resolution #7

Merged
jmiller merged 2 commits from dev into main 2026-05-10 00:15:09 +00:00
11 changed files with 163 additions and 1189 deletions
-730
View File
@@ -1,730 +0,0 @@
# 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
<<<<<<< HEAD
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN }}"}}'
=======
COMPOSER_AUTH: '{"http-basic":{"git.mokoconsulting.tech":{"username":"token","password":"${{ secrets.GA_TOKEN }}"}}}'
>>>>>>> main
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)
<<<<<<< HEAD
MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
=======
MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" ! -path "./build/*" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
>>>>>>> main
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
<<<<<<< HEAD
echo "Checking for existing release: ${RELEASE_TAG}"
REL_HTTP=$(curl -sS -o /tmp/rel_check.json -w "%{http_code}" \
-H "Authorization: token ${{ secrets.GA_TOKEN }}" \
"${API_BASE}/releases/tags/${RELEASE_TAG}" 2>&1) || true
echo "Release lookup (HTTP ${REL_HTTP}):"
cat /tmp/rel_check.json | python3 -m json.tool 2>/dev/null || cat /tmp/rel_check.json; echo
RELEASE_ID=$(python3 -c "import sys,json; print(json.load(open('/tmp/rel_check.json')).get('id',''))" 2>/dev/null || true)
if [ -z "$RELEASE_ID" ]; then
# Create release
echo "Creating new release for tag: ${RELEASE_TAG}"
CREATE_PAYLOAD=$(python3 -c "import json; print(json.dumps({
'tag_name': '${RELEASE_TAG}',
'name': '${RELEASE_TAG} (${DISPLAY_VERSION})',
'body': '${STABILITY} release',
'prerelease': True,
'target_commitish': 'main'
}))")
echo "Create payload: ${CREATE_PAYLOAD}"
CREATE_HTTP=$(curl -sS -o /tmp/rel_create.json -w "%{http_code}" \
-X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
-H "Content-Type: application/json" \
"${API_BASE}/releases" \
-d "$CREATE_PAYLOAD")
echo "Create release response (HTTP ${CREATE_HTTP}):"
cat /tmp/rel_create.json | python3 -m json.tool 2>/dev/null || cat /tmp/rel_create.json; echo
if [ "$CREATE_HTTP" -ge 400 ]; then
echo "::error::Failed to create release (HTTP ${CREATE_HTTP})"
fi
RELEASE_ID=$(python3 -c "import sys,json; print(json.load(open('/tmp/rel_create.json')).get('id',''))" 2>/dev/null || true)
fi
if [ -n "$RELEASE_ID" ]; then
echo "Release ID: ${RELEASE_ID}"
# Delete existing assets with same name before uploading
ASSETS_HTTP=$(curl -sS -o /tmp/assets.json -w "%{http_code}" \
-H "Authorization: token ${{ secrets.GA_TOKEN }}" \
"${API_BASE}/releases/${RELEASE_ID}/assets")
echo "List assets (HTTP ${ASSETS_HTTP}):"
cat /tmp/assets.json | python3 -m json.tool 2>/dev/null || cat /tmp/assets.json; echo
ASSETS=$(cat /tmp/assets.json)
[ "$ASSETS_HTTP" -ge 400 ] && ASSETS="[]"
=======
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 "[]")
>>>>>>> main
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
<<<<<<< HEAD
echo "Deleting existing asset: ${ASSET_FILE} (id=${ASSET_ID})"
DEL_HTTP=$(curl -sS -o /tmp/asset_del.json -w "%{http_code}" \
-X DELETE -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
"${API_BASE}/releases/${RELEASE_ID}/assets/${ASSET_ID}")
echo "Delete asset response (HTTP ${DEL_HTTP})"
=======
curl -sf -X DELETE -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
"${API_BASE}/releases/${RELEASE_ID}/assets/${ASSET_ID}" 2>/dev/null || true
>>>>>>> main
fi
done
# Upload both formats
<<<<<<< HEAD
echo "Uploading ${PACKAGE_NAME}..."
UP_ZIP_HTTP=$(curl -sS -o /tmp/up_zip.json -w "%{http_code}" \
-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}")
echo "Upload ZIP response (HTTP ${UP_ZIP_HTTP}):"
cat /tmp/up_zip.json | python3 -m json.tool 2>/dev/null || cat /tmp/up_zip.json; echo
[ "$UP_ZIP_HTTP" -ge 400 ] && echo "::warning::ZIP upload failed (HTTP ${UP_ZIP_HTTP})"
echo "Uploading ${TAR_NAME}..."
UP_TAR_HTTP=$(curl -sS -o /tmp/up_tar.json -w "%{http_code}" \
-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}")
echo "Upload tar.gz response (HTTP ${UP_TAR_HTTP}):"
cat /tmp/up_tar.json | python3 -m json.tool 2>/dev/null || cat /tmp/up_tar.json; echo
[ "$UP_TAR_HTTP" -ge 400 ] && echo "::warning::tar.gz upload failed (HTTP ${UP_TAR_HTTP})"
else
echo "::error::No release ID available — cannot upload packages"
=======
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
>>>>>>> main
fi
echo "Packages: ${PACKAGE_NAME} + ${TAR_NAME} (SHA: ${SHA256})" >> $GITHUB_STEP_SUMMARY
else
SHA256=""
fi
<<<<<<< HEAD
# -- 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"
=======
# -- 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"
>>>>>>> main
NEW_ENTRY="${NEW_ENTRY} </update>"
# -- Write new entry to temp file --------------------------------
printf '%b' "$NEW_ENTRY" > /tmp/new_entry.xml
<<<<<<< HEAD
# -- 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
=======
# -- Merge into updates.xml ----------------------------------------
# Cascade: stable→all | rc→rc+lower | beta→beta+lower | alpha→alpha+dev | dev→dev
>>>>>>> main
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}"
<<<<<<< HEAD
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}"
=======
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"]
>>>>>>> main
with open("updates.xml") as f:
content = f.read()
with open("/tmp/new_entry.xml") as f:
new_entry_template = f.read()
<<<<<<< HEAD
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
=======
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
>>>>>>> main
# 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: |
<<<<<<< HEAD
set -euo pipefail
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
GA_TOKEN="${{ secrets.GA_TOKEN }}"
STABILITY="${{ steps.update.outputs.stability }}"
echo "Fetching updates.xml SHA from main..."
SHA_HTTP=$(curl -sS -o /tmp/main_sha.json -w "%{http_code}" \
-H "Authorization: token ${GA_TOKEN}" \
"${API_BASE}/contents/updates.xml?ref=main") || true
echo "File SHA response (HTTP ${SHA_HTTP}):"
cat /tmp/main_sha.json | python3 -m json.tool 2>/dev/null || cat /tmp/main_sha.json; echo
FILE_SHA=$(python3 -c "import json; print(json.load(open('/tmp/main_sha.json')).get('sha',''))" 2>/dev/null || true)
if [ -n "$FILE_SHA" ] && [ -f "updates.xml" ]; then
echo "Syncing updates.xml to main (sha=${FILE_SHA})..."
CONTENT=$(base64 -w0 updates.xml)
SYNC_PAYLOAD=$(python3 -c "import json; print(json.dumps({
'content': '${CONTENT}',
'sha': '${FILE_SHA}',
'message': 'chore: sync updates.xml from ${STABILITY} [skip ci]',
'branch': 'main'
}))")
SYNC_HTTP=$(curl -sS -o /tmp/sync_main.json -w "%{http_code}" \
-X PUT -H "Authorization: token ${GA_TOKEN}" \
-H "Content-Type: application/json" \
"${API_BASE}/contents/updates.xml" \
-d "$SYNC_PAYLOAD")
echo "Sync response (HTTP ${SYNC_HTTP}):"
cat /tmp/sync_main.json | python3 -m json.tool 2>/dev/null || cat /tmp/sync_main.json; echo
if [ "$SYNC_HTTP" -ge 400 ]; then
echo "::warning::Failed to sync updates.xml to main (HTTP ${SYNC_HTTP})"
echo "WARNING: failed to sync updates.xml to main (HTTP ${SYNC_HTTP})" >> $GITHUB_STEP_SUMMARY
else
echo "updates.xml synced to main (${STABILITY})" >> $GITHUB_STEP_SUMMARY
fi
else
echo "::warning::Could not get updates.xml SHA from main (HTTP ${SHA_HTTP})"
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
PKG_NAME=$(basename "$PKG")
echo "Mirroring ${PKG_NAME}..."
MIRROR_HTTP=$(curl -sS -o /tmp/mirror_rel.json -w "%{http_code}" \
-H "Authorization: token ${{ secrets.GA_TOKEN }}" \
"${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${{ github.repository }}/releases/tags/${RELEASE_TAG}") || true
echo "Mirror release lookup (HTTP ${MIRROR_HTTP})"
_RELID=$(jq -r '.id // empty' /tmp/mirror_rel.json 2>/dev/null)
if [ -n "$_RELID" ]; then
UP_HTTP=$(curl -sS -o /tmp/mirror_up.json -w "%{http_code}" \
-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=${PKG_NAME}" \
--data-binary "@$PKG")
echo "Mirror upload ${PKG_NAME} (HTTP ${UP_HTTP})"
[ "$UP_HTTP" -ge 400 ] && echo "::warning::Mirror upload failed for ${PKG_NAME} (HTTP ${UP_HTTP})"
else
echo "::warning::No release found for mirroring (tag=${RELEASE_TAG})"
fi
fi
done
=======
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
>>>>>>> main
- 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}"
<<<<<<< HEAD
echo "Checking deploy permission for ${ACTOR}..."
PERM_HTTP=$(curl -sS -o /tmp/perm.json -w "%{http_code}" \
-H "Authorization: token ${{ secrets.GA_TOKEN }}" \
"${API_BASE}/collaborators/${ACTOR}/permission") || true
echo "Permission check (HTTP ${PERM_HTTP}):"
cat /tmp/perm.json | python3 -m json.tool 2>/dev/null || cat /tmp/perm.json; echo
PERMISSION=$(python3 -c "import json; print(json.load(open('/tmp/perm.json')).get('permission','read'))" 2>/dev/null || echo "read")
=======
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")
>>>>>>> main
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
-42
View File
@@ -1,42 +0,0 @@
# IDE
.idea/
.vscode/
*.swp
*.swo
*~
# OS
.DS_Store
Thumbs.db
desktop.ini
# Composer
/vendor/
composer.lock
# Node
/node_modules/
# Build
/build/
/dist/
*.zip
*.tar.gz
# Secrets
.env
.env.*
*.ppk
*.pem
*.key
sftp-config*
.ftpignore
# Temporary
tmp/
tmp-overrides/
profile.ps1
<<<<<<< HEAD
=======
.mcp.json
>>>>>>> main
+8 -13
View File
@@ -1,10 +1,6 @@
# Changelog
## [Unreleased]
## [01.03.00] --- 2026-05-09
## [01.02.00] --- 2026-05-09
## [Unreleased]
### Added
- Auto-create "DPCalendar Events" template category in JoomGallery on install
@@ -13,6 +9,10 @@
- Release workflow: changelog promotion, dev reset, SHA checksums
- Release workflow: extension type prefix in ZIP and release name
- Release workflow: element detection from plugin=/module= attributes
- Initial JoomGallery and DPCalendar integration
- Auto-creates gallery category per event
- Mapping table for event-to-category links
- Seed existing events on install
### Changed
- Display name updated to "Moko Gallery Calendar"
@@ -22,11 +22,6 @@
### Fixed
- ZIP filename missing element prefix
- Element detection for plugins without element tag
## [01.00.10] - 2026-05-01
### Added
- Initial JoomGallery and DPCalendar integration
- Auto-creates gallery category per event
- Mapping table for event-to-category links
- Seed existing events on install
- Release name and body: pretty name resolution from .ini language key
- Release body: duplicate version header removed
- Release body: SHA-256 checksums now display correctly
+11 -52
View File
@@ -6,42 +6,27 @@ This file is part of a Moko Consulting project.
SPDX-License-Identifier: GPL-3.0-or-later
# FILE INFORMATION
DEFGROUP: MokoJGDPC.Documentation
INGROUP: MokoJGDPC
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJGDPC
DEFGROUP: MokoGalleryCalendar.Documentation
INGROUP: MokoGalleryCalendar
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoGalleryCalendar
FILE: ./README.md
<<<<<<< HEAD
VERSION: 01.03.00
=======
VERSION: 01.04.00
>>>>>>> main
BRIEF: Documentation for MokoJGDPC plugin
VERSION: 01.01.00
BRIEF: Documentation for Moko Gallery Calendar plugin
-->
# MokoJGDPC
# Moko Gallery Calendar
<<<<<<< HEAD
> **MokoJGDPC** is a Joomla system plugin that automatically creates a JoomGallery category whenever a DPCalendar event is created. Link your events to photo galleries without manual setup.
=======
> **MokoJGDPC** is a Joomla system plugin that automatically creates a JoomGallery category whenever a DPCalendar event's start date arrives. Link your events to photo galleries without manual setup.
>>>>>>> main
> **Moko Gallery Calendar** is a Joomla system plugin that automatically creates a JoomGallery category whenever a DPCalendar event's start date arrives. Link your events to photo galleries without manual setup.
**DPCalendar + JoomGallery Bridge**
[![Version](https://img.shields.io/gitea/v/release/MokoConsulting/MokoJGDPC?gitea_url=https%3A%2F%2Fgit.mokoconsulting.tech&logo=gitea&logoColor=white&label=version)](https://git.mokoconsulting.tech/MokoConsulting/MokoJGDPC/releases/tag/stable)
[![Version](https://img.shields.io/gitea/v/release/MokoConsulting/MokoGalleryCalendar?gitea_url=https%3A%2F%2Fgit.mokoconsulting.tech&logo=gitea&logoColor=white&label=version)](https://git.mokoconsulting.tech/MokoConsulting/MokoGalleryCalendar/releases/tag/stable)
[![License](https://img.shields.io/badge/license-GPL--3.0--or--later-green.svg?logo=gnu&logoColor=white)](LICENSE)
[![Joomla](https://img.shields.io/badge/Joomla-5.x%20%7C%206.x-red.svg?logo=joomla&logoColor=white)](https://www.joomla.org)
[![PHP](https://img.shields.io/badge/PHP-8.1%2B-777BB4.svg?logo=php&logoColor=white)](https://www.php.net)
## Features
<<<<<<< HEAD
- Automatically creates a JoomGallery category when a DPCalendar event is saved
- Category title mirrors the event title for easy identification
- Configurable parent category in JoomGallery for all event galleries
- Optionally deletes the gallery category when the event is deleted
- Supports event updates (renames the gallery category when the event title changes)
=======
- **Deferred category creation** — gallery categories are created when the event date arrives, not when the event is first saved
- **Joomla Task Scheduler** — runs daily via `com_scheduler` (Joomla 4.1+); auto-registered on install
- **Frontend fallback** — if the scheduled task hasn't run in 7 days, the next site visit triggers processing
@@ -51,7 +36,7 @@ BRIEF: Documentation for MokoJGDPC plugin
- **Recurring event aware** — only the original event gets a gallery; recurring instances are skipped
- **Unique aliases** — automatically deduplicates category URL aliases
- **Seed on install** — existing DPCalendar events get mapped on first install
>>>>>>> main
- **Auto-creates template category** — creates "DPCalendar Events" parent category in JoomGallery on install
## Requirements
@@ -62,33 +47,16 @@ BRIEF: Documentation for MokoJGDPC plugin
## Installation
1. Download the latest release ZIP from the [releases page](https://git.mokoconsulting.tech/MokoConsulting/MokoJGDPC/releases/tag/stable)
1. Download the latest release ZIP from the [releases page](https://git.mokoconsulting.tech/MokoConsulting/MokoGalleryCalendar/releases/tag/stable)
2. Install via **System > Install > Extensions** in your Joomla administrator
3. Enable the plugin at **System > Manage > Plugins** (search for "MokoJGDPC")
<<<<<<< HEAD
4. Configure the parent gallery category in the plugin settings
=======
3. Enable the plugin at **System > Manage > Plugins** (search for "Moko Gallery Calendar")
4. Configure the plugin settings (see below)
5. Verify the scheduled task is registered at **System > Scheduled Tasks**
>>>>>>> main
## Configuration
| Parameter | Description | Default |
|-----------|-------------|---------|
<<<<<<< HEAD
| Parent Category | JoomGallery category under which event galleries are created | Root (1) |
| Delete on Event Remove | Remove gallery category when event is trashed/deleted | No |
| Sync Title Changes | Rename gallery category when event title changes | Yes |
## How It Works
1. A user creates or saves an event in DPCalendar
2. MokoJGDPC intercepts the `onContentAfterSave` event
3. If the content type is a DPCalendar event and no linked gallery category exists, one is created in JoomGallery
4. The link between event and gallery category is stored in the plugin's mapping table
5. When an event is deleted (if configured), the gallery category is also removed
=======
| **Parent Category** | JoomGallery category ID under which event galleries are created | `1` (root) |
| **Delete on Event Remove** | Remove gallery category when the DPCalendar event is trashed/deleted | No |
| **Sync Title Changes** | Rename gallery category when the event title changes | Yes |
@@ -146,14 +114,6 @@ The plugin creates one table:
| `event_date` | DATE | Event start date — category created when this date arrives |
| `created` | DATETIME | Row creation timestamp |
### Asset Records
Each created gallery category gets a corresponding `#__assets` row for Joomla ACL enforcement. The asset is:
- Positioned as a child of the parent category's asset (or `com_joomgallery` root)
- Populated with rules from the permissions template category (if configured)
- Linked back to the category via `asset_id`
- Cleaned up when the category is deleted
## Uninstall
Uninstalling the plugin:
@@ -162,7 +122,6 @@ Uninstalling the plugin:
- Deletes the `/tmp/mokojgdpc_lastrun` cache file
**Note:** Gallery categories and their asset records created by the plugin are **not** removed on uninstall. They become standalone JoomGallery categories.
>>>>>>> main
## License
+2 -9
View File
@@ -1,12 +1,8 @@
; Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
; SPDX-License-Identifier: GPL-3.0-or-later
<<<<<<< HEAD
; VERSION: 01.00.00
=======
; VERSION: 01.00.01
>>>>>>> main
PLG_SYSTEM_MOKOJGDPC="MokoJGDPC"
PLG_SYSTEM_MOKOJGDPC="Moko Gallery Calendar"
PLG_SYSTEM_MOKOJGDPC_DESCRIPTION="Automatically creates a JoomGallery category when a DPCalendar event is created. Links events to photo galleries."
PLG_SYSTEM_MOKOJGDPC_PARENT_CATEGORY_LABEL="Parent Gallery Category"
PLG_SYSTEM_MOKOJGDPC_PARENT_CATEGORY_DESC="JoomGallery category ID under which event galleries will be created. Default is the root category (1)."
@@ -14,12 +10,9 @@ PLG_SYSTEM_MOKOJGDPC_DELETE_ON_REMOVE_LABEL="Delete Gallery on Event Remove"
PLG_SYSTEM_MOKOJGDPC_DELETE_ON_REMOVE_DESC="When enabled, the linked JoomGallery category will be deleted when the DPCalendar event is deleted."
PLG_SYSTEM_MOKOJGDPC_SYNC_TITLE_LABEL="Sync Title Changes"
PLG_SYSTEM_MOKOJGDPC_SYNC_TITLE_DESC="When enabled, renaming a DPCalendar event will also rename its linked JoomGallery category."
<<<<<<< HEAD
=======
PLG_SYSTEM_MOKOJGDPC_DEFAULT_ACCESS_LABEL="Default Access Level"
PLG_SYSTEM_MOKOJGDPC_DEFAULT_ACCESS_DESC="Access level assigned to newly created gallery categories. Controls who can view the category."
PLG_SYSTEM_MOKOJGDPC_PERMISSIONS_TEMPLATE_LABEL="Permissions Template Category"
PLG_SYSTEM_MOKOJGDPC_PERMISSIONS_TEMPLATE_DESC="JoomGallery category ID whose permission rules will be copied to new categories. Set to 0 to inherit from parent. This controls who can create, edit, and delete images within the category."
PLG_SYSTEM_MOKOJGDPC_TASK_PROCESS_PENDING_TITLE="MokoJGDPC: Process Pending Gallery Categories"
PLG_SYSTEM_MOKOJGDPC_TASK_PROCESS_PENDING_TITLE="Moko Gallery Calendar: Process Pending Gallery Categories"
PLG_SYSTEM_MOKOJGDPC_TASK_PROCESS_PENDING_DESC="Creates JoomGallery categories for DPCalendar events whose start date has arrived. Runs daily via the Joomla Task Scheduler."
>>>>>>> main
@@ -2,10 +2,5 @@
; SPDX-License-Identifier: GPL-3.0-or-later
; VERSION: 01.00.00
<<<<<<< HEAD
PLG_SYSTEM_MOKOJGDPC="Moko Gallery Calendar"
PLG_SYSTEM_MOKOJGDPC_DESCRIPTION="Automatically creates a JoomGallery category when a DPCalendar event is created. Links events to photo galleries.<br><br><strong>After Install:</strong><br>1. Go to System → Manage → Plugins, search 'Moko Gallery Calendar'<br>2. Edit plugin parameters — set the parent JoomGallery category (auto-created as 'DPCalendar Events')<br>3. In JoomGallery → Categories, set permissions on 'DPCalendar Events' category for user access levels<br>4. New events will automatically get a photo gallery sub-category"
=======
PLG_SYSTEM_MOKOJGDPC="MokoJGDPC"
PLG_SYSTEM_MOKOJGDPC_DESCRIPTION="Automatically creates a JoomGallery category when a DPCalendar event is created. Links events to photo galleries."
>>>>>>> main
+2 -9
View File
@@ -1,12 +1,8 @@
; Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
; SPDX-License-Identifier: GPL-3.0-or-later
<<<<<<< HEAD
; VERSION: 01.00.00
=======
; VERSION: 01.00.01
>>>>>>> main
PLG_SYSTEM_MOKOJGDPC="MokoJGDPC"
PLG_SYSTEM_MOKOJGDPC="Moko Gallery Calendar"
PLG_SYSTEM_MOKOJGDPC_DESCRIPTION="Automatically creates a JoomGallery category when a DPCalendar event is created. Links events to photo galleries."
PLG_SYSTEM_MOKOJGDPC_PARENT_CATEGORY_LABEL="Parent Gallery Category"
PLG_SYSTEM_MOKOJGDPC_PARENT_CATEGORY_DESC="JoomGallery category ID under which event galleries will be created. Default is the root category (1)."
@@ -14,12 +10,9 @@ PLG_SYSTEM_MOKOJGDPC_DELETE_ON_REMOVE_LABEL="Delete Gallery on Event Remove"
PLG_SYSTEM_MOKOJGDPC_DELETE_ON_REMOVE_DESC="When enabled, the linked JoomGallery category will be deleted when the DPCalendar event is deleted."
PLG_SYSTEM_MOKOJGDPC_SYNC_TITLE_LABEL="Sync Title Changes"
PLG_SYSTEM_MOKOJGDPC_SYNC_TITLE_DESC="When enabled, renaming a DPCalendar event will also rename its linked JoomGallery category."
<<<<<<< HEAD
=======
PLG_SYSTEM_MOKOJGDPC_DEFAULT_ACCESS_LABEL="Default Access Level"
PLG_SYSTEM_MOKOJGDPC_DEFAULT_ACCESS_DESC="Access level assigned to newly created gallery categories. Controls who can view the category."
PLG_SYSTEM_MOKOJGDPC_PERMISSIONS_TEMPLATE_LABEL="Permissions Template Category"
PLG_SYSTEM_MOKOJGDPC_PERMISSIONS_TEMPLATE_DESC="JoomGallery category ID whose permission rules will be copied to new categories. Set to 0 to inherit from parent. This controls who can create, edit, and delete images within the category."
PLG_SYSTEM_MOKOJGDPC_TASK_PROCESS_PENDING_TITLE="MokoJGDPC: Process Pending Gallery Categories"
PLG_SYSTEM_MOKOJGDPC_TASK_PROCESS_PENDING_TITLE="Moko Gallery Calendar: Process Pending Gallery Categories"
PLG_SYSTEM_MOKOJGDPC_TASK_PROCESS_PENDING_DESC="Creates JoomGallery categories for DPCalendar events whose start date has arrived. Runs daily via the Joomla Task Scheduler."
>>>>>>> main
@@ -2,10 +2,5 @@
; SPDX-License-Identifier: GPL-3.0-or-later
; VERSION: 01.00.00
<<<<<<< HEAD
PLG_SYSTEM_MOKOJGDPC="Moko Gallery Calendar"
PLG_SYSTEM_MOKOJGDPC_DESCRIPTION="Automatically creates a JoomGallery category when a DPCalendar event is created. Links events to photo galleries.<br><br><strong>After Install:</strong><br>1. Go to System → Manage → Plugins, search 'Moko Gallery Calendar'<br>2. Edit plugin parameters — set the parent JoomGallery category (auto-created as 'DPCalendar Events')<br>3. In JoomGallery → Categories, set permissions on 'DPCalendar Events' category for user access levels<br>4. New events will automatically get a photo gallery sub-category"
=======
PLG_SYSTEM_MOKOJGDPC="MokoJGDPC"
PLG_SYSTEM_MOKOJGDPC_DESCRIPTION="Automatically creates a JoomGallery category when a DPCalendar event is created. Links events to photo galleries."
>>>>>>> main
+4 -21
View File
@@ -10,23 +10,13 @@ FILE INFORMATION
DEFGROUP: Joomla
INGROUP: MokoJGDPC
PATH: /src/mokojgdpc.xml
<<<<<<< HEAD
VERSION: 01.00.00
BRIEF: Plugin manifest XML file for MokoJGDPC
-->
<extension type="plugin" group="system" method="upgrade">
<name>plg_system_mokojgdpc</name>
<version>01.03.00</version>
<name>PLG_SYSTEM_MOKOJGDPC</name>
<version>01.01.00</version>
<creationDate>2026-05-09</creationDate>
=======
VERSION: 01.00.01
BRIEF: Plugin manifest XML file for MokoJGDPC
-->
<extension type="plugin" group="system" method="upgrade">
<name>Moko Gallery Calendar</name>
<version>01.03.00</version>
<creationDate>2026-05-09</creationDate>
>>>>>>> main
<author>Jonathan Miller || Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
@@ -52,8 +42,8 @@ BRIEF: Plugin manifest XML file for MokoJGDPC
<scriptfile>script.php</scriptfile>
<updateservers>
<server type="extension" priority="1" name="MokoJGDPC Update Server (Gitea)">
https://git.mokoconsulting.tech/MokoConsulting/MokoJGDPC/raw/branch/main/updates.xml
<server type="extension" priority="1" name="Moko Gallery Calendar Update Server">
https://git.mokoconsulting.tech/MokoConsulting/MokoGalleryCalendar/raw/branch/main/updates.xml
</server>
</updateservers>
@@ -90,8 +80,6 @@ BRIEF: Plugin manifest XML file for MokoJGDPC
<option value="0">JNO</option>
<option value="1">JYES</option>
</field>
<<<<<<< HEAD
=======
<field
name="default_access"
type="accesslevel"
@@ -107,12 +95,7 @@ BRIEF: Plugin manifest XML file for MokoJGDPC
label="PLG_SYSTEM_MOKOJGDPC_PERMISSIONS_TEMPLATE_LABEL"
description="PLG_SYSTEM_MOKOJGDPC_PERMISSIONS_TEMPLATE_DESC"
/>
>>>>>>> main
</fieldset>
</fields>
</config>
</extension>
<<<<<<< HEAD
<!-- dev release trigger -->
=======
>>>>>>> main
+120 -287
View File
@@ -8,7 +8,7 @@
* FILE INFORMATION
* DEFGROUP: MokoJGDPC.System
* INGROUP: MokoJGDPC
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoJGDPC
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoGalleryCalendar
* PATH: /src/script.php
* VERSION: 01.00.00
* BRIEF: Install/update/uninstall script — creates mapping table, seeds existing events
@@ -24,39 +24,21 @@ use Joomla\CMS\Plugin\PluginHelper;
class plg_system_mokojgdpc_InstallerScript
{
<<<<<<< HEAD
/**
* Runs on install — creates table then seeds categories for existing events.
*/
=======
>>>>>>> main
public function install(InstallerAdapter $adapter): bool
{
if (!$this->createTable()) {
return false;
}
<<<<<<< HEAD
$this->createTemplateCategory();
$this->seedExistingEvents();
=======
$this->seedExistingEvents();
$this->ensureScheduledTask();
>>>>>>> main
return true;
}
public function update(InstallerAdapter $adapter): bool
{
<<<<<<< HEAD
return $this->createTable();
}
/**
* Runs on uninstall — drops the mapping table.
*/
=======
if (!$this->createTable()) {
return false;
}
@@ -67,13 +49,10 @@ class plg_system_mokojgdpc_InstallerScript
return true;
}
>>>>>>> main
public function uninstall(InstallerAdapter $adapter): bool
{
$db = Factory::getContainer()->get('DatabaseDriver');
<<<<<<< HEAD
=======
// Remove scheduled task
try {
$query = $db->getQuery(true)
@@ -85,7 +64,6 @@ class plg_system_mokojgdpc_InstallerScript
// Table may not exist on older Joomla
}
>>>>>>> main
try {
$db->setQuery('DROP TABLE IF EXISTS ' . $db->quoteName('#__mokojgdpc_map'));
$db->execute();
@@ -93,15 +71,6 @@ class plg_system_mokojgdpc_InstallerScript
Log::add('MokoJGDPC: Failed to drop mapping table: ' . $e->getMessage(), Log::WARNING, 'jerror');
}
<<<<<<< HEAD
return true;
}
/**
* Find all existing DPCalendar events and create JoomGallery categories for them.
* Skips events that already have a mapping row.
*/
=======
// Clean up throttle cache file
$tmpPath = Factory::getApplication()->get('tmp_path', sys_get_temp_dir());
$cacheFile = $tmpPath . '/mokojgdpc_lastrun';
@@ -113,17 +82,12 @@ class plg_system_mokojgdpc_InstallerScript
return true;
}
>>>>>>> main
private function seedExistingEvents(): void
{
$db = Factory::getContainer()->get('DatabaseDriver');
$tables = $db->getTableList();
$prefix = $db->getPrefix();
<<<<<<< HEAD
// Bail if either DPCalendar or JoomGallery is not installed
=======
>>>>>>> main
if (
!\in_array($prefix . 'dpcalendar_events', $tables, true)
|| !\in_array($prefix . 'joomgallery_categories', $tables, true)
@@ -133,15 +97,10 @@ class plg_system_mokojgdpc_InstallerScript
return;
}
<<<<<<< HEAD
// Read parent category from plugin params (defaults to root = 1)
$parentId = 1;
=======
// Read plugin params
$parentId = 1;
$access = 1;
$rules = '{}';
>>>>>>> main
$plugin = PluginHelper::getPlugin('system', 'mokojgdpc');
if ($plugin && !empty($plugin->params)) {
@@ -150,13 +109,6 @@ class plg_system_mokojgdpc_InstallerScript
if (isset($pluginParams->parent_category)) {
$parentId = (int) $pluginParams->parent_category;
}
<<<<<<< HEAD
}
// Fetch all published DPCalendar events not yet mapped
$query = $db->getQuery(true)
->select([$db->quoteName('e.id'), $db->quoteName('e.title')])
=======
if (isset($pluginParams->default_access)) {
$access = (int) $pluginParams->default_access;
@@ -176,19 +128,14 @@ class plg_system_mokojgdpc_InstallerScript
$db->quoteName('e.title'),
$db->quoteName('e.start_date'),
])
>>>>>>> main
->from($db->quoteName('#__dpcalendar_events', 'e'))
->leftJoin(
$db->quoteName('#__mokojgdpc_map', 'm')
. ' ON ' . $db->quoteName('m.event_id') . ' = ' . $db->quoteName('e.id')
)
->where($db->quoteName('m.id') . ' IS NULL')
<<<<<<< HEAD
->where($db->quoteName('e.state') . ' = 1');
=======
->where($db->quoteName('e.state') . ' = 1')
->where('(' . $db->quoteName('e.original_id') . ' = 0 OR ' . $db->quoteName('e.original_id') . ' IS NULL)');
>>>>>>> main
$db->setQuery($query);
$events = $db->loadObjectList();
@@ -199,10 +146,6 @@ class plg_system_mokojgdpc_InstallerScript
return;
}
<<<<<<< HEAD
// Get parent node for nested set insertion
=======
>>>>>>> main
$query = $db->getQuery(true)
->select([$db->quoteName('lft'), $db->quoteName('rgt'), $db->quoteName('level')])
->from($db->quoteName('#__joomgallery_categories'))
@@ -219,216 +162,6 @@ class plg_system_mokojgdpc_InstallerScript
}
$seeded = 0;
<<<<<<< HEAD
$now = (new \DateTime('now', new \DateTimeZone('UTC')))->format('Y-m-d H:i:s');
foreach ($events as $event) {
$title = trim($event->title);
$alias = OutputFilter::stringURLSafe($title);
if (empty($alias)) {
$alias = 'event-gallery-' . $event->id;
}
// Re-read parent rgt each iteration (shifted by previous inserts)
$query = $db->getQuery(true)
->select([$db->quoteName('rgt'), $db->quoteName('level')])
->from($db->quoteName('#__joomgallery_categories'))
->where($db->quoteName('id') . ' = :pid')
->bind(':pid', $parentId, \Joomla\Database\ParameterType::INTEGER);
$db->setQuery($query);
$parentNow = $db->loadObject();
if (!$parentNow) {
continue;
}
$newLft = (int) $parentNow->rgt;
$newRgt = $newLft + 1;
$newLevel = (int) $parentNow->level + 1;
$db->transactionStart();
try {
// Shift rgt
$query = $db->getQuery(true)
->update($db->quoteName('#__joomgallery_categories'))
->set($db->quoteName('rgt') . ' = ' . $db->quoteName('rgt') . ' + 2')
->where($db->quoteName('rgt') . ' >= :rgt')
->bind(':rgt', $newLft, \Joomla\Database\ParameterType::INTEGER);
$db->setQuery($query);
$db->execute();
// Shift lft
$query = $db->getQuery(true)
->update($db->quoteName('#__joomgallery_categories'))
->set($db->quoteName('lft') . ' = ' . $db->quoteName('lft') . ' + 2')
->where($db->quoteName('lft') . ' > :lft')
->bind(':lft', $newLft, \Joomla\Database\ParameterType::INTEGER);
$db->setQuery($query);
$db->execute();
// Insert category
$category = (object) [
'parent_id' => $parentId,
'lft' => $newLft,
'rgt' => $newRgt,
'level' => $newLevel,
'title' => $title,
'alias' => $alias,
'description' => '',
'published' => 1,
'access' => 1,
'language' => '*',
'created_by' => 0,
'created_time' => $now,
'modified_time' => $now,
];
$db->insertObject('#__joomgallery_categories', $category, 'id');
$newCatId = (int) $category->id;
// Insert mapping
$map = (object) [
'event_id' => (int) $event->id,
'category_id' => $newCatId,
];
$db->insertObject('#__mokojgdpc_map', $map);
$db->transactionCommit();
$seeded++;
} catch (\Exception $e) {
$db->transactionRollback();
Log::add('MokoJGDPC: Seed failed for event #' . $event->id . ': ' . $e->getMessage(), Log::WARNING, 'plg_system_mokojgdpc');
}
}
Log::add('MokoJGDPC: Seeded ' . $seeded . ' gallery categories from existing events', Log::INFO, 'plg_system_mokojgdpc');
}
/**
* Create a "DPCalendar Events" template category in JoomGallery and set it as the default parent.
*/
private function createTemplateCategory(): void
{
$db = Factory::getContainer()->get('DatabaseDriver');
$tables = $db->getTableList();
$prefix = $db->getPrefix();
if (!\in_array($prefix . 'joomgallery_categories', $tables, true)) {
Log::add('MokoGalleryCalendar: JoomGallery not installed — skipping template category', Log::INFO, 'plg_system_mokojgdpc');
return;
}
// Check if template category already exists
$query = $db->getQuery(true)
->select('id')
->from($db->quoteName('#__joomgallery_categories'))
->where($db->quoteName('alias') . ' = ' . $db->quote('dpcalendar-events'));
$db->setQuery($query);
$existingId = $db->loadResult();
if ($existingId) {
$this->setPluginParentCategory((int) $existingId);
return;
}
// Get root category (id=1) for nested set insertion
$rootId = 1;
$query = $db->getQuery(true)
->select([$db->quoteName('rgt'), $db->quoteName('level')])
->from($db->quoteName('#__joomgallery_categories'))
->where($db->quoteName('id') . ' = :rootId')
->bind(':rootId', $rootId, \Joomla\Database\ParameterType::INTEGER);
$db->setQuery($query);
$root = $db->loadObject();
if (!$root) {
Log::add('MokoGalleryCalendar: Root category not found', Log::WARNING, 'plg_system_mokojgdpc');
return;
}
$newLft = (int) $root->rgt;
$newRgt = $newLft + 1;
$newLevel = (int) $root->level + 1;
$now = (new \DateTime('now', new \DateTimeZone('UTC')))->format('Y-m-d H:i:s');
$db->transactionStart();
try {
// Shift rgt
$query = $db->getQuery(true)
->update($db->quoteName('#__joomgallery_categories'))
->set($db->quoteName('rgt') . ' = ' . $db->quoteName('rgt') . ' + 2')
->where($db->quoteName('rgt') . ' >= :rgt')
->bind(':rgt', $newLft, \Joomla\Database\ParameterType::INTEGER);
$db->setQuery($query)->execute();
// Shift lft
$query = $db->getQuery(true)
->update($db->quoteName('#__joomgallery_categories'))
->set($db->quoteName('lft') . ' = ' . $db->quoteName('lft') . ' + 2')
->where($db->quoteName('lft') . ' > :lft')
->bind(':lft', $newLft, \Joomla\Database\ParameterType::INTEGER);
$db->setQuery($query)->execute();
// Insert template category
$category = (object) [
'parent_id' => $rootId,
'lft' => $newLft,
'rgt' => $newRgt,
'level' => $newLevel,
'title' => 'DPCalendar Events',
'alias' => 'dpcalendar-events',
'description' => 'Auto-created by MokoGalleryCalendar. Event photo galleries are created as sub-categories here.',
'published' => 1,
'access' => 1,
'language' => '*',
'created_by' => 0,
'created_time' => $now,
'modified_time' => $now,
];
$db->insertObject('#__joomgallery_categories', $category, 'id');
$db->transactionCommit();
$newId = (int) $category->id;
$this->setPluginParentCategory($newId);
Log::add('MokoGalleryCalendar: Created template category "DPCalendar Events" (ID: ' . $newId . ')', Log::INFO, 'plg_system_mokojgdpc');
} catch (\Exception $e) {
$db->transactionRollback();
Log::add('MokoGalleryCalendar: Failed to create template category: ' . $e->getMessage(), Log::WARNING, 'plg_system_mokojgdpc');
}
}
/**
* Set the parent_category param on the plugin to the given category ID.
*/
private function setPluginParentCategory(int $categoryId): void
{
$db = Factory::getContainer()->get('DatabaseDriver');
$query = $db->getQuery(true)
->select('params')
->from($db->quoteName('#__extensions'))
->where($db->quoteName('element') . ' = ' . $db->quote('mokojgdpc'))
->where($db->quoteName('folder') . ' = ' . $db->quote('system'));
$db->setQuery($query);
$params = json_decode($db->loadResult() ?: '{}', true);
$params['parent_category'] = $categoryId;
$query = $db->getQuery(true)
->update($db->quoteName('#__extensions'))
->set($db->quoteName('params') . ' = ' . $db->quote(json_encode($params)))
->where($db->quoteName('element') . ' = ' . $db->quote('mokojgdpc'))
->where($db->quoteName('folder') . ' = ' . $db->quote('system'));
$db->setQuery($query)->execute();
}
/**
* Create the event-to-category mapping table if it does not exist.
*/
=======
$today = (new \DateTime('now', new \DateTimeZone('UTC')))->format('Y-m-d');
$now = (new \DateTime('now', new \DateTimeZone('UTC')))->format('Y-m-d H:i:s');
@@ -522,6 +255,122 @@ class plg_system_mokojgdpc_InstallerScript
Log::add('MokoJGDPC: Seeded ' . $seeded . ' mappings from existing events', Log::INFO, 'plg_system_mokojgdpc');
}
/**
* Create a "DPCalendar Events" template category in JoomGallery and set it as the default parent.
*/
private function createTemplateCategory(): void
{
$db = Factory::getContainer()->get('DatabaseDriver');
$tables = $db->getTableList();
$prefix = $db->getPrefix();
if (!\in_array($prefix . 'joomgallery_categories', $tables, true)) {
Log::add('MokoGalleryCalendar: JoomGallery not installed — skipping template category', Log::INFO, 'plg_system_mokojgdpc');
return;
}
// Check if template category already exists
$query = $db->getQuery(true)
->select('id')
->from($db->quoteName('#__joomgallery_categories'))
->where($db->quoteName('alias') . ' = ' . $db->quote('dpcalendar-events'));
$db->setQuery($query);
$existingId = $db->loadResult();
if ($existingId) {
$this->setPluginParentCategory((int) $existingId);
return;
}
// Get root category (id=1) for nested set insertion
$rootId = 1;
$query = $db->getQuery(true)
->select([$db->quoteName('rgt'), $db->quoteName('level')])
->from($db->quoteName('#__joomgallery_categories'))
->where($db->quoteName('id') . ' = :rootId')
->bind(':rootId', $rootId, \Joomla\Database\ParameterType::INTEGER);
$db->setQuery($query);
$root = $db->loadObject();
if (!$root) {
Log::add('MokoGalleryCalendar: Root category not found', Log::WARNING, 'plg_system_mokojgdpc');
return;
}
$newLft = (int) $root->rgt;
$newRgt = $newLft + 1;
$newLevel = (int) $root->level + 1;
$now = (new \DateTime('now', new \DateTimeZone('UTC')))->format('Y-m-d H:i:s');
$db->transactionStart();
try {
$query = $db->getQuery(true)
->update($db->quoteName('#__joomgallery_categories'))
->set($db->quoteName('rgt') . ' = ' . $db->quoteName('rgt') . ' + 2')
->where($db->quoteName('rgt') . ' >= :rgt')
->bind(':rgt', $newLft, \Joomla\Database\ParameterType::INTEGER);
$db->setQuery($query)->execute();
$query = $db->getQuery(true)
->update($db->quoteName('#__joomgallery_categories'))
->set($db->quoteName('lft') . ' = ' . $db->quoteName('lft') . ' + 2')
->where($db->quoteName('lft') . ' > :lft')
->bind(':lft', $newLft, \Joomla\Database\ParameterType::INTEGER);
$db->setQuery($query)->execute();
$category = (object) [
'parent_id' => $rootId,
'lft' => $newLft,
'rgt' => $newRgt,
'level' => $newLevel,
'title' => 'DPCalendar Events',
'alias' => 'dpcalendar-events',
'description' => 'Auto-created by Moko Gallery Calendar. Event photo galleries are created as sub-categories here.',
'published' => 1,
'access' => 1,
'language' => '*',
'created_by' => 0,
'created_time' => $now,
'modified_time' => $now,
];
$db->insertObject('#__joomgallery_categories', $category, 'id');
$db->transactionCommit();
$newId = (int) $category->id;
$this->setPluginParentCategory($newId);
Log::add('MokoGalleryCalendar: Created template category "DPCalendar Events" (ID: ' . $newId . ')', Log::INFO, 'plg_system_mokojgdpc');
} catch (\Exception $e) {
$db->transactionRollback();
Log::add('MokoGalleryCalendar: Failed to create template category: ' . $e->getMessage(), Log::WARNING, 'plg_system_mokojgdpc');
}
}
/**
* Set the parent_category param on the plugin to the given category ID.
*/
private function setPluginParentCategory(int $categoryId): void
{
$db = Factory::getContainer()->get('DatabaseDriver');
$query = $db->getQuery(true)
->select('params')
->from($db->quoteName('#__extensions'))
->where($db->quoteName('element') . ' = ' . $db->quote('mokojgdpc'))
->where($db->quoteName('folder') . ' = ' . $db->quote('system'));
$db->setQuery($query);
$params = json_decode($db->loadResult() ?: '{}', true);
$params['parent_category'] = $categoryId;
$query = $db->getQuery(true)
->update($db->quoteName('#__extensions'))
->set($db->quoteName('params') . ' = ' . $db->quote(json_encode($params)))
->where($db->quoteName('element') . ' = ' . $db->quote('mokojgdpc'))
->where($db->quoteName('folder') . ' = ' . $db->quote('system'));
$db->setQuery($query)->execute();
}
private function uniqueAlias($db, string $title, int $eventId): string
{
$alias = OutputFilter::stringURLSafe($title);
@@ -582,14 +431,12 @@ class plg_system_mokojgdpc_InstallerScript
}
/**
* Register a daily scheduled task with Joomla's Task Scheduler (com_scheduler).
* Idempotent — skips if the task already exists.
* Register a daily scheduled task with Joomla's Task Scheduler.
*/
private function ensureScheduledTask(): void
{
$db = Factory::getContainer()->get('DatabaseDriver');
// Check if #__scheduler_tasks table exists (Joomla 4.1+)
$tables = $db->getTableList();
$prefix = $db->getPrefix();
@@ -601,7 +448,6 @@ class plg_system_mokojgdpc_InstallerScript
$taskType = 'plg_system_mokojgdpc.process_pending';
// Check if task already registered
$query = $db->getQuery(true)
->select('COUNT(*)')
->from($db->quoteName('#__scheduler_tasks'))
@@ -614,11 +460,10 @@ class plg_system_mokojgdpc_InstallerScript
return;
}
// Insert a daily task — runs at 2:00 AM server time
$now = (new \DateTime('now', new \DateTimeZone('UTC')))->format('Y-m-d H:i:s');
$task = (object) [
'title' => 'MokoJGDPC: Process pending gallery categories',
'title' => 'Moko Gallery Calendar: Process pending gallery categories',
'type' => $taskType,
'execution_rules' => json_encode([
'rule-type' => 'interval-days',
@@ -629,7 +474,7 @@ class plg_system_mokojgdpc_InstallerScript
'state' => 1,
'cli_exclusive' => 0,
'params' => '{}',
'note' => 'Auto-registered by MokoJGDPC plugin. Creates JoomGallery categories for DPCalendar events whose date has arrived.',
'note' => 'Auto-registered by Moko Gallery Calendar plugin.',
'created' => $now,
'next_execution' => $now,
'times_executed' => 0,
@@ -646,7 +491,6 @@ class plg_system_mokojgdpc_InstallerScript
}
}
>>>>>>> main
private function createTable(): bool
{
$db = Factory::getContainer()->get('DatabaseDriver');
@@ -654,13 +498,6 @@ class plg_system_mokojgdpc_InstallerScript
$query = 'CREATE TABLE IF NOT EXISTS ' . $db->quoteName('#__mokojgdpc_map') . ' ('
. $db->quoteName('id') . ' INT UNSIGNED NOT NULL AUTO_INCREMENT, '
. $db->quoteName('event_id') . ' INT UNSIGNED NOT NULL COMMENT \'DPCalendar event ID\', '
<<<<<<< HEAD
. $db->quoteName('category_id') . ' INT UNSIGNED NOT NULL COMMENT \'JoomGallery category ID\', '
. $db->quoteName('created') . ' DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, '
. 'PRIMARY KEY (' . $db->quoteName('id') . '), '
. 'UNIQUE KEY ' . $db->quoteName('idx_event') . ' (' . $db->quoteName('event_id') . '), '
. 'KEY ' . $db->quoteName('idx_category') . ' (' . $db->quoteName('category_id') . ')'
=======
. $db->quoteName('category_id') . ' INT UNSIGNED NOT NULL DEFAULT 0 COMMENT \'JoomGallery category ID (0 = pending)\', '
. $db->quoteName('event_date') . ' DATE NULL DEFAULT NULL COMMENT \'Event start date — category created when this date arrives\', '
. $db->quoteName('created') . ' DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, '
@@ -668,7 +505,6 @@ class plg_system_mokojgdpc_InstallerScript
. 'UNIQUE KEY ' . $db->quoteName('idx_event') . ' (' . $db->quoteName('event_id') . '), '
. 'KEY ' . $db->quoteName('idx_category') . ' (' . $db->quoteName('category_id') . '), '
. 'KEY ' . $db->quoteName('idx_pending') . ' (' . $db->quoteName('category_id') . ', ' . $db->quoteName('event_date') . ')'
>>>>>>> main
. ') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci';
try {
@@ -682,8 +518,6 @@ class plg_system_mokojgdpc_InstallerScript
return true;
}
<<<<<<< HEAD
=======
private function migrateSchema(): void
{
@@ -713,5 +547,4 @@ class plg_system_mokojgdpc_InstallerScript
}
}
}
>>>>>>> main
}
+16 -16
View File
@@ -1,7 +1,7 @@
<?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.03.00
VERSION: 01.01.00
-->
<updates>
@@ -10,15 +10,15 @@
<description>plg_system_mokojgdpc update</description>
<element>mokojgdpc</element>
<type>plugin</type>
<version>01.03.00</version>
<version>01.01.00</version>
<client>site</client>
<folder>system</folder>
<tags><tag>development</tag></tags>
<infourl title="plg_system_mokojgdpc">https://git.mokoconsulting.tech/MokoConsulting/MokoGalleryCalendar/releases/tag/stable</infourl>
<downloads>
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoGalleryCalendar/releases/download/stable/plg_system_mokojgdpc-01.03.00.zip</downloadurl>
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoGalleryCalendar/releases/download/stable/mokojgdpc-01.01.00.zip</downloadurl>
</downloads>
<sha256>c1173a65cda03345d313484457062871c0eb39039cf2844830c8221aad126e82</sha256>
<sha256>ae7f485ad9469c5bb231ace49393f0d5dee4b0250d1beca165d5c77620edc0f0</sha256>
<targetplatform name="joomla" version="((5.[0-9])|(6.[0-9]))" />
<maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
@@ -28,15 +28,15 @@
<description>plg_system_mokojgdpc update</description>
<element>mokojgdpc</element>
<type>plugin</type>
<version>01.03.00</version>
<version>01.01.00</version>
<client>site</client>
<folder>system</folder>
<tags><tag>alpha</tag></tags>
<infourl title="plg_system_mokojgdpc">https://git.mokoconsulting.tech/MokoConsulting/MokoGalleryCalendar/releases/tag/stable</infourl>
<downloads>
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoGalleryCalendar/releases/download/stable/plg_system_mokojgdpc-01.03.00.zip</downloadurl>
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoGalleryCalendar/releases/download/stable/mokojgdpc-01.01.00.zip</downloadurl>
</downloads>
<sha256>c1173a65cda03345d313484457062871c0eb39039cf2844830c8221aad126e82</sha256>
<sha256>ae7f485ad9469c5bb231ace49393f0d5dee4b0250d1beca165d5c77620edc0f0</sha256>
<targetplatform name="joomla" version="((5.[0-9])|(6.[0-9]))" />
<maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
@@ -46,15 +46,15 @@
<description>plg_system_mokojgdpc update</description>
<element>mokojgdpc</element>
<type>plugin</type>
<version>01.03.00</version>
<version>01.01.00</version>
<client>site</client>
<folder>system</folder>
<tags><tag>beta</tag></tags>
<infourl title="plg_system_mokojgdpc">https://git.mokoconsulting.tech/MokoConsulting/MokoGalleryCalendar/releases/tag/stable</infourl>
<downloads>
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoGalleryCalendar/releases/download/stable/plg_system_mokojgdpc-01.03.00.zip</downloadurl>
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoGalleryCalendar/releases/download/stable/mokojgdpc-01.01.00.zip</downloadurl>
</downloads>
<sha256>c1173a65cda03345d313484457062871c0eb39039cf2844830c8221aad126e82</sha256>
<sha256>ae7f485ad9469c5bb231ace49393f0d5dee4b0250d1beca165d5c77620edc0f0</sha256>
<targetplatform name="joomla" version="((5.[0-9])|(6.[0-9]))" />
<maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
@@ -64,15 +64,15 @@
<description>plg_system_mokojgdpc update</description>
<element>mokojgdpc</element>
<type>plugin</type>
<version>01.03.00</version>
<version>01.01.00</version>
<client>site</client>
<folder>system</folder>
<tags><tag>rc</tag></tags>
<infourl title="plg_system_mokojgdpc">https://git.mokoconsulting.tech/MokoConsulting/MokoGalleryCalendar/releases/tag/stable</infourl>
<downloads>
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoGalleryCalendar/releases/download/stable/plg_system_mokojgdpc-01.03.00.zip</downloadurl>
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoGalleryCalendar/releases/download/stable/mokojgdpc-01.01.00.zip</downloadurl>
</downloads>
<sha256>c1173a65cda03345d313484457062871c0eb39039cf2844830c8221aad126e82</sha256>
<sha256>ae7f485ad9469c5bb231ace49393f0d5dee4b0250d1beca165d5c77620edc0f0</sha256>
<targetplatform name="joomla" version="((5.[0-9])|(6.[0-9]))" />
<maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
@@ -82,15 +82,15 @@
<description>plg_system_mokojgdpc update</description>
<element>mokojgdpc</element>
<type>plugin</type>
<version>01.03.00</version>
<version>01.01.00</version>
<client>site</client>
<folder>system</folder>
<tags><tag>stable</tag></tags>
<infourl title="plg_system_mokojgdpc">https://git.mokoconsulting.tech/MokoConsulting/MokoGalleryCalendar/releases/tag/stable</infourl>
<downloads>
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoGalleryCalendar/releases/download/stable/plg_system_mokojgdpc-01.03.00.zip</downloadurl>
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoGalleryCalendar/releases/download/stable/mokojgdpc-01.01.00.zip</downloadurl>
</downloads>
<sha256>c1173a65cda03345d313484457062871c0eb39039cf2844830c8221aad126e82</sha256>
<sha256>ae7f485ad9469c5bb231ace49393f0d5dee4b0250d1beca165d5c77620edc0f0</sha256>
<targetplatform name="joomla" version="((5.[0-9])|(6.[0-9]))" />
<maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl>