Compare commits
158 Commits
alpha
..
version/02
| Author | SHA1 | Date | |
|---|---|---|---|
| 7bd40e3b48 | |||
| 756bde66d7 | |||
| cd318b2e85 | |||
| 63baf02135 | |||
| 22926b6d49 | |||
| 49372da0ac | |||
| 1d2350f5ae | |||
| 28330d5daa | |||
| 4705e2c416 | |||
| 116b39d980 | |||
| c69c1fac10 | |||
| 721e18fbae | |||
| cbf5f0558e | |||
| 5bb789532c | |||
| 8e216dd0dd | |||
| 0caeb62668 | |||
| 1962899c01 | |||
| a44363ba28 | |||
| e8018ebdd4 | |||
| c0747ae8b4 | |||
| 29e96919eb | |||
| 079b199f24 | |||
| d2902f6737 | |||
| d3ab78309b | |||
| ad60cfd56c | |||
| 238f9dba27 | |||
| 3869bd8a76 | |||
| e4ed99eca4 | |||
| cb1011f1dc | |||
| 21ac242019 | |||
| 984ae9f606 | |||
| 695a3acaf1 | |||
| 8b891160ef | |||
| 408fe0259b | |||
| 6ec115eab1 | |||
| 30f3a6a02e | |||
| 18be26e02e | |||
| bb2c88f485 | |||
| 81abd3557e | |||
| eb9df5496a | |||
| 9ed7c763ff | |||
| 9e87eb9bec | |||
| c9271e4977 | |||
| ebb1f5f4c9 | |||
| 793b52600f | |||
| adcf95e82e | |||
| 9091ba950b | |||
| 916e385345 | |||
| 87d4a9ec8a | |||
| 7a1b5d63aa | |||
| d83619377f | |||
| 32ba67378b | |||
| be07a35788 | |||
| 97fe1b99dd | |||
| e21103fb8c | |||
| 7d38f0aa91 | |||
| fb2e97a8ed | |||
| 8ab92c0f24 | |||
| f9b6177a78 | |||
| 800876bd71 | |||
| 43d7e8c973 | |||
| 3d7b7845b2 | |||
| 81ad188b69 | |||
| 3bd725b82e | |||
| ac0f9cb3b7 | |||
| f403fd6a53 | |||
| 3d8808d241 | |||
| 1b5c4a8f33 | |||
| 7691739085 | |||
| 750c61812d | |||
| b7b5d78929 | |||
| 35d83d7a03 | |||
| 3c4f135f56 | |||
| a5ecebe782 | |||
| bc321218a0 | |||
| 737c2e2a77 | |||
| 34f1c95d68 | |||
| 6337622bad | |||
| d96020323e | |||
| 8cb34f227e | |||
| 3573fa9c76 | |||
| e79e190361 | |||
| 29568997d6 | |||
| 6e98afe1ea | |||
| dec27343c2 | |||
| 7e95020f23 | |||
| 34c0bb97ee | |||
| 3e18f25ce1 | |||
| a2a8342c72 | |||
| ac8b6ed0cd | |||
| f5f0146ae4 | |||
| 2b684672f4 | |||
| 6c4bb7c0ba | |||
| 539ea699e6 | |||
| 82a00fc30a | |||
| 3cbe9d9c8f | |||
| d1ad2b390e | |||
| 84fd1e29c2 | |||
| e94fa5e4ac | |||
| ab8759c84e | |||
| 6cd6e2819c | |||
| c8b0009ce4 | |||
| e38d75271e | |||
| ac5b4ef3ac | |||
| d367496858 | |||
| bde7ac083d | |||
| cbcaf39b8f | |||
| f742fbd90b | |||
| 3b0c71bcd7 | |||
| 13572dd8c4 | |||
| 83039c55a8 | |||
| 77452f4c11 | |||
| d2e9852fbf | |||
| 4324309465 | |||
| 7561fb9e1e | |||
| e48b10c868 | |||
| 633af3d885 | |||
| 21f958ae56 | |||
| da2ec86c61 | |||
| 7200b8c94e | |||
| ece9195d9f | |||
| ec960ae8ca | |||
| c3ba382fda | |||
| 30d33c2275 | |||
| 4e52463fee | |||
| d73c45089c | |||
| d25c42e0a1 | |||
| cdfc404f48 | |||
| 33018f9892 | |||
| 1379e48129 | |||
| 4bbf9aedd8 | |||
| 8bb86c1ac4 | |||
| 9afb2bd4d6 | |||
| 7b21df1b6a | |||
| 5867ee7917 | |||
| f8a9d6e425 | |||
| 88ad87217c | |||
| 648f7c76cf | |||
| 1caadcea88 | |||
| c8e36dead9 | |||
| 0068debb9b | |||
| 0aa728ace6 | |||
| 545e70a65f | |||
| 858b498a20 | |||
| aef378fa77 | |||
| 4dc7f34f8d | |||
| 0f08288b0e | |||
| cc5fd83288 | |||
| 766cb58d47 | |||
| 49f1faf191 | |||
| d13135d479 | |||
| d24be9e8d6 | |||
| 336af78c06 | |||
| a933bafe2a | |||
| 9bda29eae7 | |||
| c0e2612a2b | |||
| e22a8a4d0a | |||
| d1f94d297f |
@@ -1,59 +0,0 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(cd:*)",
|
||||
"mcp__joomla-api__joomla_plugins_list",
|
||||
"mcp__gitea-moko__get_repository_tree",
|
||||
"mcp__joomla-api__joomla_api_request",
|
||||
"Bash(curl:*)",
|
||||
"mcp__gitea-moko__search_repos",
|
||||
"Bash(TOKEN=\"29367101e6edf28375e0a03cef30a7dfc7eb2186\"\ncurl -sS -H \"Authorization: token $TOKEN\" \"https://git.mokoconsulting.tech/api/v1/repos/MokoConsulting/MokoOnyx/contents/.gitea/workflows/release.yml?ref=main\" | head -200)",
|
||||
"mcp__gitea-moko__list_releases",
|
||||
"mcp__gitea-moko__delete_release",
|
||||
"mcp__gitea-moko__get_latest_release",
|
||||
"mcp__gitea-moko__actions_run_read",
|
||||
"Read(//a/MokoStandards-API/.gitea/**)",
|
||||
"Read(//a/MokoStandards-API/**)",
|
||||
"Read(//a/MokoStandards-Template-Joomla-Template/.gitea/workflows/**)",
|
||||
"Read(//a/MokoStandards-Template-Joomla-Module/.gitea/workflows/**)",
|
||||
"Read(//a/MokoStandards-Template-Joomla-Component/.gitea/workflows/**)",
|
||||
"Read(//a/MokoStandards-Template-Joomla-Package/.gitea/workflows/**)",
|
||||
"Read(//a/MokoStandards-Template-Joomla-Library/.gitea/workflows/**)",
|
||||
"Read(//a/MokoJGDPC/.gitea/workflows/**)",
|
||||
"Read(//a/MokoCassiopeia/.gitea/workflows/**)",
|
||||
"Read(//a/MokoJoomHero/.gitea/workflows/**)",
|
||||
"Read(//a/MokoJoomTOS/.gitea/workflows/**)",
|
||||
"Read(//a/MokoWaaS/.gitea/workflows/**)",
|
||||
"Read(//a/MokoWaaSAnnounce/.gitea/workflows/**)",
|
||||
"Bash(REPOS=\"MokoDPCalendarAPI MokoJGDPC MokoCassiopeia MokoJoomHero MokoJoomTOS MokoWaaS MokoWaaSAnnounce\"\n\nfor REPO in $REPOS; do\n echo \"=== $REPO ===\"\n cd \"A:/$REPO\"\n git pull --rebase origin main 2>&1 | tail -1\n rm -f .gitea/workflows/*.yml\n cp A:/MokoOnyx/.gitea/workflows/*.yml .gitea/workflows/\n git add .gitea/workflows/\n if git diff --cached --quiet; then\n echo \" No changes\"\n else\n git commit -m \"feat: expand workflow suite \\(10 workflows from MokoOnyx\\)\n\nCo-Authored-By: Claude Opus 4.6 \\(1M context\\) <noreply@anthropic.com>\"\n git push origin main 2>&1 | tail -1\n fi\n echo \"\"\ndone)",
|
||||
"mcp__gitea-moko__actions_config_read",
|
||||
"mcp__gitea-moko__actions_config_write",
|
||||
"Bash(REPOS=\"MokoDPCalendarAPI MokoJGDPC MokoCassiopeia MokoJoomHero MokoJoomTOS MokoWaaS MokoWaaSAnnounce MokoStandards-Template-Joomla-Plugin MokoStandards-Template-Joomla-Template MokoStandards-Template-Joomla-Module MokoStandards-Template-Joomla-Component MokoStandards-Template-Joomla-Package MokoStandards-Template-Joomla-Library MokoStandards-API\"\n\nfor REPO in $REPOS; do\n echo \"=== $REPO ===\"\n cd \"A:/$REPO\"\n git pull --rebase origin main 2>&1 | tail -1\n cp A:/MokoOnyx/.gitea/workflows/pre-release.yml .gitea/workflows/pre-release.yml\n git add .gitea/workflows/pre-release.yml\n if git diff --cached --quiet; then\n echo \" No changes\"\n else\n git commit -m \"feat: add pre-release workflow for manual dev/alpha/beta/rc builds\n\nCo-Authored-By: Claude Opus 4.6 \\(1M context\\) <noreply@anthropic.com>\"\n git push origin main 2>&1 | tail -1\n fi\n echo \"\"\ndone)",
|
||||
"Bash(tail -1 rm .gitea/workflows/deploy.yml git add .gitea/workflows/deploy.yml git commit -m \"chore: remove auto-deploy workflow \\(deploy is manual only\\):*)",
|
||||
"mcp__gitea-moko__pull_request_write",
|
||||
"mcp__gitea-moko__actions_run_write",
|
||||
"Bash(REPOS=\"MokoDPCalendarAPI MokoJGDPC MokoCassiopeia MokoJoomHero MokoJoomTOS MokoWaaS MokoWaaSAnnounce MokoStandards-Template-Joomla-Plugin MokoStandards-Template-Joomla-Template MokoStandards-Template-Joomla-Module MokoStandards-Template-Joomla-Component MokoStandards-Template-Joomla-Package MokoStandards-Template-Joomla-Library MokoStandards-API\" __NEW_LINE_0c05a93337c7dba4__ for REPO in $REPOS)",
|
||||
"Bash(do cd:*)",
|
||||
"Bash(then echo \"$REPO: no changes\" else git commit -m \"fix: add patch version bump to pre-release workflow:*)",
|
||||
"Bash(for REPO in client-clarksvillefurs client-kiddieland)",
|
||||
"Bash(do echo:*)",
|
||||
"Read(//a/MokoStandards-Template-Client/.gitea/workflows/**)",
|
||||
"Bash(then echo \" No changes\" else git commit -m \"feat: add standard client workflow suite + media sync:*)",
|
||||
"Bash(for:*)",
|
||||
"mcp__gitea-moko__create_repo",
|
||||
"Bash(REPOS=\"MokoDPCalendarAPI MokoJGDPC MokoCassiopeia MokoJoomHero MokoJoomTOS MokoWaaS MokoWaaSAnnounce MokoStandards-Template-Joomla\" __NEW_LINE_ef3af20e9774ebe9__ for REPO in $REPOS)",
|
||||
"Bash(then echo \"$REPO: no changes\" else git commit -m \"fix: version bump logic — stable=minor, pre-release=patch:*)",
|
||||
"Bash(then continue fi git commit -m \"fix: version bump logic — pre-release=patch with rollover:*)",
|
||||
"Bash(REPOS=\"MokoDPCalendarAPI MokoJGDPC MokoCassiopeia MokoJoomHero MokoJoomTOS MokoWaaS MokoWaaSAnnounce MokoStandards-Template-Joomla\" __NEW_LINE_a82c045e0ae69651__ for REPO in $REPOS)",
|
||||
"Bash(then continue:*)",
|
||||
"Bash(fi git commit -m \"fix: stable release = major version bump \\(XX+1.00.00\\):*)",
|
||||
"mcp__gitea-moko__get_release",
|
||||
"Bash(ALL_REPOS=\"MokoDPCalendarAPI MokoJGDPC MokoCassiopeia MokoJoomHero MokoJoomTOS MokoWaaS MokoWaaSAnnounce MokoStandards-Template-Joomla\" __NEW_LINE_fbd28532f2d35636__ for REPO in $ALL_REPOS)",
|
||||
"Bash(fi git commit -m \"fix: include element name in stable release title:*)"
|
||||
]
|
||||
},
|
||||
"enableAllProjectMcpServers": true,
|
||||
"enabledMcpjsonServers": [
|
||||
"joomla-api"
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
MokoStandards Repository Manifest
|
||||
Auto-generated by MokoStandards bulk sync.
|
||||
Manual edits to <governance> and <last-synced> may be overwritten.
|
||||
See: docs/standards/mokostandards-file-spec.md
|
||||
-->
|
||||
<mokostandards xmlns="https://standards.mokoconsulting.tech/mokostandards/1.0" schema-version="1.0">
|
||||
<identity>
|
||||
<name>MokoDPCalendarAPI</name>
|
||||
<org>MokoConsulting</org>
|
||||
<description>Joomla Web Services plugin exposing DPCalendar events, calendars, and locations via REST API</description>
|
||||
<license spdx="GPL-3.0-or-later">GNU General Public License v3</license>
|
||||
</identity>
|
||||
<governance>
|
||||
<platform>waas-component</platform>
|
||||
<standards-version>04.07.00</standards-version>
|
||||
<standards-source>https://git.mokoconsulting.tech/MokoConsulting/MokoStandards</standards-source>
|
||||
<last-synced>2026-05-02T23:06:16+00:00</last-synced>
|
||||
</governance>
|
||||
<build>
|
||||
<language>PHP</language>
|
||||
<package-type>joomla-extension</package-type>
|
||||
<entry-point>src/mokodpcalendarapi.xml</entry-point>
|
||||
</build>
|
||||
<scripts>
|
||||
<script name="package" phase="build">
|
||||
<command>make package</command>
|
||||
<description>Package via make</description>
|
||||
<runner>make</runner>
|
||||
</script>
|
||||
<script name="clean" phase="build">
|
||||
<command>make clean</command>
|
||||
<description>Clean via make</description>
|
||||
<runner>make</runner>
|
||||
</script>
|
||||
</scripts>
|
||||
</mokostandards>
|
||||
+25
-192
@@ -117,8 +117,8 @@ jobs:
|
||||
echo "Version: $VERSION (patch — platform version + badges only)"
|
||||
fi
|
||||
|
||||
# -- STEP 1b: Bump minor version (stable = minor bump, reset patch) ------
|
||||
- name: "Step 1b: Bump minor version for stable release"
|
||||
# -- STEP 1b: Bump major version (stable = major bump, reset minor+patch) -
|
||||
- name: "Step 1b: Bump major version for stable release"
|
||||
if: steps.version.outputs.skip != 'true'
|
||||
id: bump
|
||||
run: |
|
||||
@@ -126,19 +126,14 @@ jobs:
|
||||
[ -z "$CURRENT" ] && { echo "skip=true" >> "$GITHUB_OUTPUT"; exit 0; }
|
||||
|
||||
MAJOR=$((10#$(echo "$CURRENT" | cut -d. -f1)))
|
||||
MINOR=$((10#$(echo "$CURRENT" | cut -d. -f2)))
|
||||
|
||||
# Minor bump, reset patch. Rollover if minor > 99
|
||||
MINOR=$((MINOR + 1))
|
||||
if [ $MINOR -gt 99 ]; then
|
||||
MINOR=0
|
||||
MAJOR=$((MAJOR + 1))
|
||||
fi
|
||||
# Major bump, reset minor and patch
|
||||
MAJOR=$((MAJOR + 1))
|
||||
|
||||
VERSION=$(printf "%02d.%02d.00" $MAJOR $MINOR)
|
||||
VERSION=$(printf "%02d.00.00" $MAJOR)
|
||||
TODAY=$(date +%Y-%m-%d)
|
||||
|
||||
echo "Stable bump: ${CURRENT} → ${VERSION} (minor)"
|
||||
echo "Stable bump: ${CURRENT} → ${VERSION} (major)"
|
||||
|
||||
# Update README.md
|
||||
sed -i "s/VERSION:[[:space:]]*${CURRENT}/VERSION: ${VERSION}/" README.md
|
||||
@@ -151,22 +146,13 @@ jobs:
|
||||
sed -i "s|<creationDate>[^<]*</creationDate>|<creationDate>${TODAY}</creationDate>|" "$MANIFEST"
|
||||
fi
|
||||
|
||||
# Promote [Unreleased] section in CHANGELOG.md to new version
|
||||
if [ -f "CHANGELOG.md" ] && grep -qi "Unreleased" CHANGELOG.md; then
|
||||
sed -i "s|## \[Unreleased\]|## [${VERSION}] --- ${TODAY}|" CHANGELOG.md
|
||||
sed -i "s|## Unreleased|## [${VERSION}] --- ${TODAY}|" CHANGELOG.md
|
||||
sed -i "2i ## [Unreleased]" CHANGELOG.md
|
||||
sed -i "3i \\ " CHANGELOG.md
|
||||
echo "CHANGELOG promoted to [${VERSION}]"
|
||||
fi
|
||||
|
||||
# Commit and push
|
||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||
git config --local user.name "gitea-actions[bot]"
|
||||
git remote set-url origin "https://jmiller:${{ secrets.GA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||
git add -A
|
||||
git diff --cached --quiet || {
|
||||
git commit -m "chore(version): bump ${CURRENT} → ${VERSION} [skip ci]"
|
||||
git commit -m "chore(version): bump ${CURRENT} → ${VERSION} (major) [skip ci]"
|
||||
git push origin HEAD:main 2>&1
|
||||
}
|
||||
|
||||
@@ -320,7 +306,6 @@ jobs:
|
||||
|
||||
# -- STEP 5: Write updates.xml (Joomla update server) ---------------------
|
||||
- name: "Step 5: Write updates.xml"
|
||||
id: updates
|
||||
if: >-
|
||||
steps.version.outputs.skip != 'true' &&
|
||||
steps.check.outputs.already_released != 'true'
|
||||
@@ -344,44 +329,20 @@ jobs:
|
||||
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)
|
||||
|
||||
# If EXT_NAME is a language key (e.g. PLG_SYSTEM_MOKOJGDPC), resolve from .ini
|
||||
if echo "$EXT_NAME" | grep -qE '^[A-Z_]+$'; then
|
||||
INI_NAME=$(find . -name "*.sys.ini" -path "*/en-GB/*" -exec grep -h "^${EXT_NAME}=" {} \; 2>/dev/null | head -1 | cut -d'"' -f2)
|
||||
[ -z "$INI_NAME" ] && INI_NAME=$(find . -name "*.sys.ini" -exec grep -h "^${EXT_NAME}=" {} \; 2>/dev/null | head -1 | cut -d'"' -f2)
|
||||
[ -n "$INI_NAME" ] && EXT_NAME="$INI_NAME"
|
||||
fi
|
||||
|
||||
# Fallbacks
|
||||
[ -z "$EXT_NAME" ] && EXT_NAME="${{ github.event.repository.name }}"
|
||||
[ -z "$EXT_TYPE" ] && EXT_TYPE="component"
|
||||
|
||||
# Derive element if not in manifest:
|
||||
# 1. plugin="xxx" attribute (plugins)
|
||||
# 2. module="xxx" attribute (modules)
|
||||
# 3. XML filename (components, packages)
|
||||
# 4. Repo name fallback (templates, anything else)
|
||||
# 1. Try XML filename (e.g. mokowaas.xml → mokowaas)
|
||||
# 2. Fall back to repo name (lowercased)
|
||||
if [ -z "$EXT_ELEMENT" ]; then
|
||||
EXT_ELEMENT=$(sed -n 's/.*plugin="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
|
||||
fi
|
||||
if [ -z "$EXT_ELEMENT" ]; then
|
||||
EXT_ELEMENT=$(sed -n 's/.*module="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
|
||||
fi
|
||||
if [ -z "$EXT_ELEMENT" ]; then
|
||||
FNAME=$(basename "$MANIFEST" .xml | tr '[:upper:]' '[:lower:]')
|
||||
EXT_ELEMENT=$(basename "$MANIFEST" .xml | tr '[:upper:]' '[:lower:]')
|
||||
# If filename is generic (templateDetails, manifest), use repo name
|
||||
case "$FNAME" in
|
||||
templatedetails|manifest) EXT_ELEMENT=$(echo "${{ github.event.repository.name }}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') ;;
|
||||
*) EXT_ELEMENT="$FNAME" ;;
|
||||
case "$EXT_ELEMENT" in
|
||||
templatedetails|manifest|*.xml) EXT_ELEMENT=$(echo "${{ github.event.repository.name }}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') ;;
|
||||
esac
|
||||
fi
|
||||
# Final fallback
|
||||
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${{ github.event.repository.name }}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
|
||||
|
||||
# Save for Steps 7, 8, 8b
|
||||
echo "ext_element=${EXT_ELEMENT}" >> "$GITHUB_OUTPUT"
|
||||
echo "ext_name=${EXT_NAME}" >> "$GITHUB_OUTPUT"
|
||||
echo "ext_type=${EXT_TYPE}" >> "$GITHUB_OUTPUT"
|
||||
echo "ext_folder=${EXT_FOLDER}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Build client tag: plugins and frontend modules need <client>site</client>
|
||||
CLIENT_TAG=""
|
||||
@@ -408,18 +369,7 @@ jobs:
|
||||
PHP_TAG="<php_minimum>${PHP_MINIMUM}</php_minimum>"
|
||||
fi
|
||||
|
||||
# Build TYPE_PREFIX for download URL
|
||||
TYPE_PREFIX=""
|
||||
case "${EXT_TYPE}" in
|
||||
plugin) TYPE_PREFIX="plg_${EXT_FOLDER}_" ;;
|
||||
module) TYPE_PREFIX="mod_" ;;
|
||||
component) TYPE_PREFIX="com_" ;;
|
||||
template) TYPE_PREFIX="tpl_" ;;
|
||||
library) TYPE_PREFIX="lib_" ;;
|
||||
package) TYPE_PREFIX="pkg_" ;;
|
||||
esac
|
||||
|
||||
DOWNLOAD_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/stable/${TYPE_PREFIX}${EXT_ELEMENT}-${VERSION}.zip"
|
||||
DOWNLOAD_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/stable/${EXT_ELEMENT}-${VERSION}.zip"
|
||||
INFO_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/stable"
|
||||
|
||||
# -- Build update entry for a given stability tag
|
||||
@@ -514,32 +464,21 @@ jobs:
|
||||
MAJOR="${{ steps.version.outputs.major }}"
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
|
||||
# Reuse metadata from Step 5 (single source of truth)
|
||||
EXT_ELEMENT="${{ steps.updates.outputs.ext_element }}"
|
||||
EXT_NAME="${{ steps.updates.outputs.ext_name }}"
|
||||
EXT_TYPE="${{ steps.updates.outputs.ext_type }}"
|
||||
EXT_FOLDER="${{ steps.updates.outputs.ext_folder }}"
|
||||
|
||||
# Fallbacks if Step 5 was skipped
|
||||
if [ -z "$EXT_ELEMENT" ]; then
|
||||
# Auto-detect extension element for release naming
|
||||
MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
|
||||
EXT_ELEMENT=""
|
||||
if [ -n "$MANIFEST" ]; then
|
||||
EXT_ELEMENT=$(sed -n 's/.*<element>\([^<]*\)<\/element>.*/\1/p' "$MANIFEST" 2>/dev/null | head -1)
|
||||
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(basename "$MANIFEST" .xml | tr '[:upper:]' '[:lower:]')
|
||||
case "$EXT_ELEMENT" in templatedetails|manifest) EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') ;; esac
|
||||
else
|
||||
EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
|
||||
fi
|
||||
[ -z "$EXT_NAME" ] && EXT_NAME="${GITEA_REPO}"
|
||||
|
||||
NOTES=$(php /tmp/mokostandards-api/cli/release_notes.php --path . --version "$VERSION" 2>/dev/null)
|
||||
[ -z "$NOTES" ] && NOTES="Release ${VERSION}"
|
||||
|
||||
# Build release name: "Pretty Name VERSION (type_element-VERSION)"
|
||||
TYPE_PREFIX=""
|
||||
case "${EXT_TYPE}" in
|
||||
plugin) TYPE_PREFIX="plg_${EXT_FOLDER}_" ;;
|
||||
module) TYPE_PREFIX="mod_" ;;
|
||||
component) TYPE_PREFIX="com_" ;;
|
||||
template) TYPE_PREFIX="tpl_" ;;
|
||||
library) TYPE_PREFIX="lib_" ;;
|
||||
package) TYPE_PREFIX="pkg_" ;;
|
||||
esac
|
||||
RELEASE_NAME="${EXT_NAME} ${VERSION} (${TYPE_PREFIX}${EXT_ELEMENT}-${VERSION})"
|
||||
RELEASE_NAME="${EXT_ELEMENT} ${VERSION} (stable)"
|
||||
|
||||
# Delete existing release if present (overwrite, not append)
|
||||
EXISTING=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
||||
@@ -589,28 +528,9 @@ jobs:
|
||||
MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1 || true)
|
||||
[ -z "$MANIFEST" ] && exit 0
|
||||
|
||||
# Reuse element from Step 5, with same fallback chain
|
||||
EXT_ELEMENT="${{ steps.updates.outputs.ext_element }}"
|
||||
if [ -z "$EXT_ELEMENT" ]; then
|
||||
EXT_ELEMENT=$(sed -n 's/.*<element>\([^<]*\)<\/element>.*/\1/p' "$MANIFEST" 2>/dev/null | head -1)
|
||||
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(sed -n 's/.*plugin="\([^"]*\)".*/\1/p' "$MANIFEST" 2>/dev/null | head -1)
|
||||
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(basename "$MANIFEST" .xml | tr '[:upper:]' '[:lower:]')
|
||||
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
|
||||
fi
|
||||
# ZIP name: type_folder_element-VERSION (e.g. plg_system_mokojgdpc-01.01.00.zip)
|
||||
EXT_TYPE=$(sed -n 's/.*<extension[^>]*type="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
|
||||
EXT_FOLDER=$(sed -n 's/.*<extension[^>]*group="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
|
||||
TYPE_PREFIX=""
|
||||
case "${EXT_TYPE}" in
|
||||
plugin) TYPE_PREFIX="plg_${EXT_FOLDER}_" ;;
|
||||
module) TYPE_PREFIX="mod_" ;;
|
||||
component) TYPE_PREFIX="com_" ;;
|
||||
template) TYPE_PREFIX="tpl_" ;;
|
||||
library) TYPE_PREFIX="lib_" ;;
|
||||
package) TYPE_PREFIX="pkg_" ;;
|
||||
esac
|
||||
ZIP_NAME="${TYPE_PREFIX}${EXT_ELEMENT}-${VERSION}.zip"
|
||||
TAR_NAME="${TYPE_PREFIX}${EXT_ELEMENT}-${VERSION}.tar.gz"
|
||||
EXT_ELEMENT=$(sed -n 's/.*<element>\([^<]*\)<\/element>.*/\1/p' "$MANIFEST" 2>/dev/null | head -1 || basename "$MANIFEST" .xml)
|
||||
ZIP_NAME="${EXT_ELEMENT}-${VERSION}.zip"
|
||||
TAR_NAME="${EXT_ELEMENT}-${VERSION}.tar.gz"
|
||||
|
||||
# -- Build install packages from src/ ----------------------------
|
||||
SOURCE_DIR="src"
|
||||
@@ -750,73 +670,6 @@ jobs:
|
||||
echo "| Release | \`${RELEASE_TAG}\` | |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Download | [${ZIP_NAME}](${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/${RELEASE_TAG}/${ZIP_NAME}) |" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# -- STEP 8b: Update release description with changelog + SHA ----------------
|
||||
- name: "Step 8b: Update release body with changelog and SHA"
|
||||
if: steps.version.outputs.skip != 'true'
|
||||
run: |
|
||||
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
||||
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
EXT_ELEMENT="${{ steps.updates.outputs.ext_element }}"
|
||||
EXT_TYPE="${{ steps.updates.outputs.ext_type }}"
|
||||
EXT_FOLDER="${{ steps.updates.outputs.ext_folder }}"
|
||||
|
||||
# Build TYPE_PREFIX to match Step 8's ZIP naming
|
||||
TYPE_PREFIX=""
|
||||
case "${EXT_TYPE}" in
|
||||
plugin) TYPE_PREFIX="plg_${EXT_FOLDER}_" ;;
|
||||
module) TYPE_PREFIX="mod_" ;;
|
||||
component) TYPE_PREFIX="com_" ;;
|
||||
template) TYPE_PREFIX="tpl_" ;;
|
||||
library) TYPE_PREFIX="lib_" ;;
|
||||
package) TYPE_PREFIX="pkg_" ;;
|
||||
esac
|
||||
ZIP_NAME="${TYPE_PREFIX}${EXT_ELEMENT}-${VERSION}.zip"
|
||||
TAR_NAME="${TYPE_PREFIX}${EXT_ELEMENT}-${VERSION}.tar.gz"
|
||||
|
||||
# Get SHA from the built files
|
||||
SHA256_ZIP=""
|
||||
[ -f "/tmp/${ZIP_NAME}" ] && SHA256_ZIP=$(sha256sum "/tmp/${ZIP_NAME}" | cut -d' ' -f1)
|
||||
SHA256_TAR=""
|
||||
[ -f "/tmp/${TAR_NAME}" ] && SHA256_TAR=$(sha256sum "/tmp/${TAR_NAME}" | cut -d' ' -f1)
|
||||
|
||||
# Extract latest changelog entry (strip the ## header to avoid duplicate)
|
||||
CHANGELOG=""
|
||||
if [ -f "CHANGELOG.md" ]; then
|
||||
CHANGELOG=$(sed -n "/^## \[*${VERSION}/,/^## \[*[0-9]/p" CHANGELOG.md | sed '$d' | sed '1d')
|
||||
[ -z "$CHANGELOG" ] && CHANGELOG=$(sed -n '/^## /,/^## /p' CHANGELOG.md | sed '$d' | sed '1d' | head -30)
|
||||
fi
|
||||
|
||||
# Build release body (single header, no duplicate from changelog)
|
||||
BODY="## ${VERSION} ($(date +%Y-%m-%d))\n\n"
|
||||
if [ -n "$CHANGELOG" ]; then
|
||||
BODY="${BODY}${CHANGELOG}\n\n"
|
||||
fi
|
||||
BODY="${BODY}---\n\n### Checksums\n\n"
|
||||
BODY="${BODY}| File | SHA-256 |\n|------|--------|\n"
|
||||
[ -n "$SHA256_ZIP" ] && BODY="${BODY}| \`${ZIP_NAME}\` | \`${SHA256_ZIP}\` |\n"
|
||||
[ -n "$SHA256_TAR" ] && BODY="${BODY}| \`${TAR_NAME}\` | \`${SHA256_TAR}\` |\n"
|
||||
|
||||
# Get release ID and update body
|
||||
RELEASE_ID=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
|
||||
"${API_BASE}/releases/tags/${RELEASE_TAG}" 2>/dev/null | \
|
||||
python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
|
||||
|
||||
if [ -n "$RELEASE_ID" ] && [ "$RELEASE_ID" != "None" ]; then
|
||||
python3 -c "
|
||||
import json, urllib.request
|
||||
body = '''$(printf '%b' "$BODY")'''
|
||||
data = json.dumps({'body': body}).encode()
|
||||
req = urllib.request.Request(
|
||||
'${API_BASE}/releases/${RELEASE_ID}',
|
||||
data=data,
|
||||
headers={'Authorization': 'token ${{ secrets.GA_TOKEN }}', 'Content-Type': 'application/json'},
|
||||
method='PATCH'
|
||||
)
|
||||
urllib.request.urlopen(req)
|
||||
" 2>/dev/null && echo "Release body updated with changelog + SHA" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
# -- STEP 9: Mirror to GitHub (stable only) --------------------------------
|
||||
- name: "Step 9: Mirror release to GitHub"
|
||||
if: >-
|
||||
@@ -906,26 +759,6 @@ jobs:
|
||||
done
|
||||
echo "Cleaned up ${DELETED} pre-release channel(s)" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# -- STEP 11: Reset dev branch from main ------------------------------------
|
||||
- name: "Step 11: Delete and recreate dev branch from main"
|
||||
if: steps.version.outputs.skip != 'true'
|
||||
continue-on-error: true
|
||||
run: |
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
TOKEN="${{ secrets.GA_TOKEN }}"
|
||||
|
||||
# Delete dev branch
|
||||
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
|
||||
"${API_BASE}/branches/dev" 2>/dev/null && echo "Deleted dev branch"
|
||||
|
||||
# Recreate dev from main (now includes version bump + changelog promotion)
|
||||
curl -sf -X POST -H "Authorization: token ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
"${API_BASE}/branches" \
|
||||
-d '{"new_branch_name":"dev","old_branch_name":"main"}' 2>/dev/null && echo "Recreated dev from main"
|
||||
|
||||
echo "Dev branch reset from main (keeps dev ahead after release)" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# -- Summary --------------------------------------------------------------
|
||||
- name: Pipeline Summary
|
||||
if: always()
|
||||
|
||||
@@ -1,213 +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.Maintenance
|
||||
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API
|
||||
# PATH: /templates/workflows/cascade-dev.yml.template
|
||||
# VERSION: 02.00.00
|
||||
# BRIEF: Forward-merge main → all open branches after every push to main
|
||||
#
|
||||
# +========================================================================+
|
||||
# | CASCADE MAIN → ALL BRANCHES |
|
||||
# +========================================================================+
|
||||
# | |
|
||||
# | Triggers on every push to main (PR merges, bot commits, etc.) |
|
||||
# | |
|
||||
# | 1. List all branches matching: dev, rc/*, beta/*, alpha/* |
|
||||
# | 2. For each: create PR (main → branch), auto-merge if clean |
|
||||
# | 3. On conflict: leave PR open for manual resolution |
|
||||
# | |
|
||||
# +========================================================================+
|
||||
|
||||
name: Cascade Main → Dev
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
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
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
cascade:
|
||||
name: Cascade main → branches
|
||||
runs-on: ubuntu-latest
|
||||
if: >-
|
||||
!contains(github.event.head_commit.message, '[skip ci]') &&
|
||||
!contains(github.event.head_commit.message, '[skip cascade]')
|
||||
|
||||
steps:
|
||||
- name: Discover target branches
|
||||
id: branches
|
||||
env:
|
||||
GA_TOKEN: ${{ secrets.GA_TOKEN }}
|
||||
run: |
|
||||
API="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
|
||||
# Fetch all branches (paginated)
|
||||
PAGE=1
|
||||
ALL_BRANCHES=""
|
||||
while true; do
|
||||
BATCH=$(curl -sS \
|
||||
-H "Authorization: token ${GA_TOKEN}" \
|
||||
"${API}/branches?page=${PAGE}&limit=50" \
|
||||
| jq -r '.[].name // empty')
|
||||
[ -z "$BATCH" ] && break
|
||||
ALL_BRANCHES="$ALL_BRANCHES $BATCH"
|
||||
PAGE=$((PAGE + 1))
|
||||
done
|
||||
|
||||
# Filter to cascade targets: dev, dev/*, rc/*, beta/*, alpha/*
|
||||
TARGETS=""
|
||||
for BRANCH in $ALL_BRANCHES; do
|
||||
case "$BRANCH" in
|
||||
dev|dev/*|rc/*|beta/*|alpha/*)
|
||||
TARGETS="$TARGETS $BRANCH"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
TARGETS=$(echo "$TARGETS" | xargs) # trim whitespace
|
||||
|
||||
if [ -z "$TARGETS" ]; then
|
||||
echo "targets=" >> "$GITHUB_OUTPUT"
|
||||
echo "ℹ️ No cascade target branches found"
|
||||
else
|
||||
echo "targets=$TARGETS" >> "$GITHUB_OUTPUT"
|
||||
COUNT=$(echo "$TARGETS" | wc -w)
|
||||
echo "📋 Found ${COUNT} target branch(es): ${TARGETS}"
|
||||
fi
|
||||
|
||||
- name: Cascade to all target branches
|
||||
if: steps.branches.outputs.targets != ''
|
||||
env:
|
||||
GA_TOKEN: ${{ secrets.GA_TOKEN }}
|
||||
run: |
|
||||
API="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
SHORT_SHA="${GITHUB_SHA:0:7}"
|
||||
TARGETS="${{ steps.branches.outputs.targets }}"
|
||||
|
||||
SUCCESS=0
|
||||
CONFLICTS=0
|
||||
SKIPPED=0
|
||||
FAILED=0
|
||||
|
||||
for BRANCH in $TARGETS; do
|
||||
echo ""
|
||||
echo "═══ main → ${BRANCH} ═══"
|
||||
|
||||
# Check if branch is already up to date
|
||||
ENCODED_BRANCH=$(echo "$BRANCH" | sed 's|/|%2F|g')
|
||||
RESPONSE=$(curl -sS \
|
||||
-H "Authorization: token ${GA_TOKEN}" \
|
||||
"${API}/compare/${ENCODED_BRANCH}...main")
|
||||
|
||||
AHEAD=$(echo "$RESPONSE" | jq '.total_commits // 0')
|
||||
|
||||
if [ "$AHEAD" -eq 0 ]; then
|
||||
echo " ✅ Already up to date"
|
||||
SKIPPED=$((SKIPPED + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
echo " ℹ️ main is ${AHEAD} commit(s) ahead"
|
||||
|
||||
# Check for existing cascade PR
|
||||
EXISTING=$(curl -sS \
|
||||
-H "Authorization: token ${GA_TOKEN}" \
|
||||
"${API}/pulls?state=open&head=${GITEA_ORG}:main&base=${ENCODED_BRANCH}&limit=1")
|
||||
|
||||
EXISTING_COUNT=$(echo "$EXISTING" | jq 'length')
|
||||
PR_NUMBER=""
|
||||
|
||||
if [ "$EXISTING_COUNT" -gt 0 ]; then
|
||||
PR_NUMBER=$(echo "$EXISTING" | jq -r '.[0].number')
|
||||
echo " ℹ️ Reusing existing PR #${PR_NUMBER}"
|
||||
else
|
||||
# Create cascade PR
|
||||
PR_RESPONSE=$(curl -sS -w "\n%{http_code}" \
|
||||
-X POST \
|
||||
-H "Authorization: token ${GA_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"title\": \"chore: cascade main → ${BRANCH} (${SHORT_SHA}) [skip ci]\",
|
||||
\"body\": \"## Automatic cascade\\n\\nForward-merging \`main\` (${SHORT_SHA}) into \`${BRANCH}\`.\\n\\nIf conflicts exist, resolve manually and merge.\\n\\n> Auto-created by **Cascade Main → Dev**.\",
|
||||
\"head\": \"main\",
|
||||
\"base\": \"${BRANCH}\"
|
||||
}" \
|
||||
"${API}/pulls")
|
||||
|
||||
HTTP_CODE=$(echo "$PR_RESPONSE" | tail -1)
|
||||
BODY=$(echo "$PR_RESPONSE" | sed '$d')
|
||||
PR_NUMBER=$(echo "$BODY" | jq -r '.number // empty')
|
||||
|
||||
if [ "$HTTP_CODE" != "201" ] || [ -z "$PR_NUMBER" ]; then
|
||||
MSG=$(echo "$BODY" | jq -r '.message // .' 2>/dev/null | head -1)
|
||||
echo " ❌ Failed to create PR (HTTP ${HTTP_CODE}): ${MSG}"
|
||||
FAILED=$((FAILED + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
echo " ✅ Created PR #${PR_NUMBER}"
|
||||
fi
|
||||
|
||||
# Try auto-merge
|
||||
PR_DATA=$(curl -sS \
|
||||
-H "Authorization: token ${GA_TOKEN}" \
|
||||
"${API}/pulls/${PR_NUMBER}")
|
||||
|
||||
MERGEABLE=$(echo "$PR_DATA" | jq -r '.mergeable // false')
|
||||
|
||||
if [ "$MERGEABLE" != "true" ]; then
|
||||
echo " ⚠️ Conflicts — PR #${PR_NUMBER} left open"
|
||||
CONFLICTS=$((CONFLICTS + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
MERGE_RESPONSE=$(curl -sS -w "\n%{http_code}" \
|
||||
-X POST \
|
||||
-H "Authorization: token ${GA_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"Do\": \"merge\",
|
||||
\"merge_message_field\": \"chore: cascade main → ${BRANCH} [skip ci]\",
|
||||
\"delete_branch_after_merge\": false
|
||||
}" \
|
||||
"${API}/pulls/${PR_NUMBER}/merge")
|
||||
|
||||
MERGE_HTTP=$(echo "$MERGE_RESPONSE" | tail -1)
|
||||
|
||||
if [ "$MERGE_HTTP" = "200" ] || [ "$MERGE_HTTP" = "204" ]; then
|
||||
echo " ✅ Merged — ${BRANCH} is in sync"
|
||||
SUCCESS=$((SUCCESS + 1))
|
||||
else
|
||||
MERGE_BODY=$(echo "$MERGE_RESPONSE" | sed '$d')
|
||||
echo " ⚠️ Merge failed (HTTP ${MERGE_HTTP}) — PR #${PR_NUMBER} left open"
|
||||
CONFLICTS=$((CONFLICTS + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
# Summary
|
||||
echo ""
|
||||
echo "════════════════════════════════════════"
|
||||
echo " ✅ Merged: ${SUCCESS}"
|
||||
echo " ⚠️ Conflicts: ${CONFLICTS}"
|
||||
echo " ⏭️ Up to date: ${SKIPPED}"
|
||||
echo " ❌ Failed: ${FAILED}"
|
||||
echo "════════════════════════════════════════"
|
||||
|
||||
if [ "$FAILED" -gt 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
@@ -375,76 +375,3 @@ jobs:
|
||||
else
|
||||
echo "No phpunit.xml found — skipping tests." >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
static-analysis:
|
||||
name: PHPStan Analysis
|
||||
runs-on: ubuntu-latest
|
||||
needs: lint-and-validate
|
||||
continue-on-error: true
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Setup PHP
|
||||
run: php -v && composer --version
|
||||
|
||||
- name: Install dependencies
|
||||
env:
|
||||
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GA_TOKEN || github.token }}"}}'
|
||||
run: |
|
||||
if [ -f "composer.json" ]; then
|
||||
composer install --no-interaction --prefer-dist --optimize-autoloader
|
||||
fi
|
||||
|
||||
- name: Install PHPStan
|
||||
run: |
|
||||
if ! command -v vendor/bin/phpstan &> /dev/null; then
|
||||
composer require --dev phpstan/phpstan --no-interaction 2>/dev/null || \
|
||||
composer global require phpstan/phpstan --no-interaction
|
||||
fi
|
||||
|
||||
- name: Run PHPStan
|
||||
run: |
|
||||
echo "### PHPStan Static Analysis" >> $GITHUB_STEP_SUMMARY
|
||||
PHPSTAN="vendor/bin/phpstan"
|
||||
if [ ! -f "$PHPSTAN" ]; then
|
||||
PHPSTAN=$(composer global config bin-dir --absolute 2>/dev/null)/phpstan
|
||||
fi
|
||||
|
||||
# Determine source directory
|
||||
SRC_DIR=""
|
||||
for DIR in src/ htdocs/ lib/; do
|
||||
if [ -d "$DIR" ]; then
|
||||
SRC_DIR="$DIR"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$SRC_DIR" ]; then
|
||||
echo "No source directory found (src/, htdocs/, lib/) — skipping." >> $GITHUB_STEP_SUMMARY
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Use repo phpstan.neon if present, otherwise use baseline config
|
||||
ARGS="analyse ${SRC_DIR} --memory-limit=512M --no-progress --error-format=table"
|
||||
if [ -f "phpstan.neon" ] || [ -f "phpstan.neon.dist" ]; then
|
||||
echo "Using project PHPStan config." >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
ARGS="$ARGS --level=3"
|
||||
echo "No phpstan.neon found — using level 3 (type inference)." >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
$PHPSTAN $ARGS 2>&1 | tee /tmp/phpstan-output.txt
|
||||
EXIT=${PIPESTATUS[0]}
|
||||
|
||||
if [ $EXIT -eq 0 ]; then
|
||||
echo "**No errors found.**" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
ERRORS=$(grep -c "ERROR" /tmp/phpstan-output.txt 2>/dev/null || echo "some")
|
||||
echo "**${ERRORS} error(s) found.** Review output above." >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
tail -30 /tmp/phpstan-output.txt >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
exit $EXIT
|
||||
|
||||
@@ -1,96 +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.Security
|
||||
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API
|
||||
# PATH: /templates/workflows/gitleaks.yml.template
|
||||
# VERSION: 01.00.00
|
||||
# BRIEF: Secret scanning — detect leaked credentials, API keys, and tokens
|
||||
#
|
||||
# +========================================================================+
|
||||
# | SECRET SCANNING |
|
||||
# +========================================================================+
|
||||
# | |
|
||||
# | Scans commits for leaked secrets using Gitleaks. |
|
||||
# | |
|
||||
# | - PR scan: only new commits in the PR |
|
||||
# | - Scheduled: full repo scan weekly |
|
||||
# | - Alerts via ntfy on findings |
|
||||
# | |
|
||||
# +========================================================================+
|
||||
|
||||
name: Secret Scanning
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- 'dev/**'
|
||||
schedule:
|
||||
- cron: '0 5 * * 1' # Weekly Monday 05:00 UTC
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }}
|
||||
NTFY_TOPIC: ${{ vars.NTFY_TOPIC || 'gitea-security' }}
|
||||
|
||||
jobs:
|
||||
gitleaks:
|
||||
name: Gitleaks Secret Scan
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install Gitleaks
|
||||
run: |
|
||||
GITLEAKS_VERSION="8.21.2"
|
||||
curl -sSL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \
|
||||
| tar -xz -C /usr/local/bin gitleaks
|
||||
gitleaks version
|
||||
|
||||
- name: Scan for secrets
|
||||
id: scan
|
||||
run: |
|
||||
echo "### Secret Scanning" >> $GITHUB_STEP_SUMMARY
|
||||
ARGS="--source . --verbose --report-format json --report-path /tmp/gitleaks-report.json"
|
||||
|
||||
if [ "${{ github.event_name }}" = "pull_request" ]; then
|
||||
# Scan only PR commits
|
||||
ARGS="$ARGS --log-opts=${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }}"
|
||||
echo "Scanning PR commits only" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "Full repository scan" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
if gitleaks detect $ARGS 2>&1; then
|
||||
echo "result=clean" >> "$GITHUB_OUTPUT"
|
||||
echo "**No secrets detected.**" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "result=found" >> "$GITHUB_OUTPUT"
|
||||
FINDINGS=$(jq length /tmp/gitleaks-report.json 2>/dev/null || echo "unknown")
|
||||
echo "**${FINDINGS} potential secret(s) detected.**" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Review the findings and rotate any exposed credentials immediately." >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Notify on findings
|
||||
if: failure() && steps.scan.outputs.result == 'found'
|
||||
run: |
|
||||
REPO="${{ github.event.repository.name }}"
|
||||
curl -sS \
|
||||
-H "Title: ${REPO} — secrets detected in code" \
|
||||
-H "Tags: rotating_light,key" \
|
||||
-H "Priority: urgent" \
|
||||
-d "Gitleaks found potential secrets. Review and rotate credentials immediately." \
|
||||
"${NTFY_URL}/${NTFY_TOPIC}" || true
|
||||
@@ -18,7 +18,6 @@ on:
|
||||
- "Joomla Build & Release"
|
||||
- "Joomla Extension CI"
|
||||
- "Deploy"
|
||||
- "Cascade Main → Dev"
|
||||
types:
|
||||
- completed
|
||||
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# Enforces branch merge policy:
|
||||
# feature/* → dev only
|
||||
# fix/* → dev only
|
||||
# hotfix/* → dev or main (emergency)
|
||||
# dev → main only
|
||||
# alpha/* → dev only
|
||||
# beta/* → dev only
|
||||
# rc/* → main only
|
||||
|
||||
name: Branch Policy Check
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, edited]
|
||||
|
||||
jobs:
|
||||
check-target:
|
||||
name: Verify merge target
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check branch policy
|
||||
run: |
|
||||
HEAD="${{ github.head_ref }}"
|
||||
BASE="${{ github.base_ref }}"
|
||||
|
||||
echo "PR: ${HEAD} → ${BASE}"
|
||||
|
||||
ALLOWED=true
|
||||
REASON=""
|
||||
|
||||
case "$HEAD" in
|
||||
feature/*|feat/*)
|
||||
if [ "$BASE" != "dev" ]; then
|
||||
ALLOWED=false
|
||||
REASON="Feature branches must target 'dev', not '${BASE}'"
|
||||
fi
|
||||
;;
|
||||
fix/*|bugfix/*)
|
||||
if [ "$BASE" != "dev" ]; then
|
||||
ALLOWED=false
|
||||
REASON="Fix branches must target 'dev', not '${BASE}'"
|
||||
fi
|
||||
;;
|
||||
hotfix/*)
|
||||
if [ "$BASE" != "dev" ] && [ "$BASE" != "main" ]; then
|
||||
ALLOWED=false
|
||||
REASON="Hotfix branches can only target 'dev' or 'main', not '${BASE}'"
|
||||
fi
|
||||
;;
|
||||
alpha/*|beta/*)
|
||||
if [ "$BASE" != "dev" ]; then
|
||||
ALLOWED=false
|
||||
REASON="Pre-release branches must target 'dev', not '${BASE}'"
|
||||
fi
|
||||
;;
|
||||
rc/*)
|
||||
if [ "$BASE" != "main" ]; then
|
||||
ALLOWED=false
|
||||
REASON="Release candidate branches must target 'main', not '${BASE}'"
|
||||
fi
|
||||
;;
|
||||
dev)
|
||||
if [ "$BASE" != "main" ]; then
|
||||
ALLOWED=false
|
||||
REASON="Dev branch can only merge into 'main', not '${BASE}'"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "$ALLOWED" = false ]; then
|
||||
echo "::error::${REASON}"
|
||||
echo ""
|
||||
echo "## Branch Policy Violation" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "${REASON}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### Allowed merge paths:" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- \`feature/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- \`fix/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- \`hotfix/*\` → \`dev\` or \`main\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- \`dev\` → \`main\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- \`rc/*\` → \`main\`" >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Branch policy: OK (${HEAD} → ${BASE})"
|
||||
echo "## Branch Policy: Passed" >> $GITHUB_STEP_SUMMARY
|
||||
@@ -278,7 +278,7 @@ jobs:
|
||||
f.write(content)
|
||||
PYEOF
|
||||
|
||||
# Commit and push to current branch
|
||||
# Commit and push
|
||||
if ! git diff --quiet updates.xml 2>/dev/null; then
|
||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||
git config --local user.name "gitea-actions[bot]"
|
||||
@@ -287,28 +287,6 @@ jobs:
|
||||
git push origin HEAD 2>&1 || echo "WARNING: push failed"
|
||||
fi
|
||||
|
||||
- name: "Sync updates.xml to all branches"
|
||||
run: |
|
||||
CURRENT_BRANCH="${{ github.ref_name }}"
|
||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||
git config --local user.name "gitea-actions[bot]"
|
||||
|
||||
# Sync updates.xml to main and dev (whichever isn't current)
|
||||
for BRANCH in main dev; do
|
||||
[ "$BRANCH" = "$CURRENT_BRANCH" ] && continue
|
||||
|
||||
echo "Syncing updates.xml → ${BRANCH}"
|
||||
git fetch origin "${BRANCH}" 2>/dev/null || continue
|
||||
git checkout "origin/${BRANCH}" -- . 2>/dev/null || continue
|
||||
git checkout "${CURRENT_BRANCH}" -- updates.xml
|
||||
if ! git diff --quiet updates.xml 2>/dev/null; then
|
||||
git add updates.xml
|
||||
git commit -m "chore: sync updates.xml from ${CURRENT_BRANCH} [skip ci]"
|
||||
git push origin HEAD:refs/heads/${BRANCH} 2>&1 || echo "WARNING: push to ${BRANCH} failed"
|
||||
fi
|
||||
git checkout "${CURRENT_BRANCH}" 2>/dev/null
|
||||
done
|
||||
|
||||
- name: "Delete lesser pre-release channels (cascade)"
|
||||
continue-on-error: true
|
||||
run: |
|
||||
|
||||
@@ -64,7 +64,7 @@ env:
|
||||
# File / directory variables
|
||||
DOCS_INDEX: docs/docs-index.md
|
||||
SCRIPT_DIR: scripts
|
||||
WORKFLOWS_DIR: .gitea/workflows
|
||||
WORKFLOWS_DIR: .github/workflows
|
||||
SHELLCHECK_PATTERN: '*.sh'
|
||||
SPDX_FILE_GLOBS: '*.sh,*.php,*.js,*.ts,*.css,*.xml,*.yml,*.yaml'
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
@@ -1,193 +0,0 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: moko-platform.Workflows
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
# PATH: /templates/workflows/sync-roadmap-wiki.yml.template
|
||||
# VERSION: 04.06.00
|
||||
# BRIEF: Syncs project board state to a Roadmap wiki page
|
||||
|
||||
name: Sync Roadmap to Wiki
|
||||
|
||||
on:
|
||||
# Run when project issues change
|
||||
issues:
|
||||
types: [opened, closed, reopened, labeled, unlabeled, milestoned, demilestoned]
|
||||
|
||||
# Run on milestone changes
|
||||
milestone:
|
||||
types: [created, closed, opened, edited, deleted]
|
||||
|
||||
# Manual trigger
|
||||
workflow_dispatch:
|
||||
|
||||
# Weekly refresh to catch any drift
|
||||
schedule:
|
||||
- cron: '0 6 * * 1'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
issues: read
|
||||
|
||||
jobs:
|
||||
sync-roadmap:
|
||||
name: Generate Roadmap Wiki
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Generate Roadmap from Projects
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.GA_TOKEN }}
|
||||
GITEA_URL: ${{ github.server_url }}
|
||||
REPO_OWNER: ${{ github.repository_owner }}
|
||||
REPO_NAME: ${{ github.event.repository.name }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
API="${GITEA_URL}/api/v1"
|
||||
AUTH="Authorization: token ${GITEA_TOKEN}"
|
||||
REPO="${REPO_OWNER}/${REPO_NAME}"
|
||||
|
||||
# Fetch milestones (open + closed)
|
||||
MILESTONES_OPEN=$(curl -sf -H "$AUTH" "${API}/repos/${REPO}/milestones?state=open&limit=50" || echo "[]")
|
||||
MILESTONES_CLOSED=$(curl -sf -H "$AUTH" "${API}/repos/${REPO}/milestones?state=closed&limit=50" || echo "[]")
|
||||
|
||||
# Fetch all open issues
|
||||
ISSUES_OPEN=$(curl -sf -H "$AUTH" "${API}/repos/${REPO}/issues?state=open&type=issues&limit=50" || echo "[]")
|
||||
ISSUES_CLOSED=$(curl -sf -H "$AUTH" "${API}/repos/${REPO}/issues?state=closed&type=issues&limit=50&sort=updated&direction=desc" || echo "[]")
|
||||
|
||||
# Fetch labels for categorization
|
||||
LABELS=$(curl -sf -H "$AUTH" "${API}/repos/${REPO}/labels?limit=50" || echo "[]")
|
||||
|
||||
# Build the roadmap markdown
|
||||
cat > /tmp/roadmap.md << 'HEADER'
|
||||
# Roadmap
|
||||
|
||||
> Auto-generated from project milestones and issues.
|
||||
> Last updated: TIMESTAMP
|
||||
|
||||
HEADER
|
||||
sed -i "s|TIMESTAMP|$(date -u '+%Y-%m-%d %H:%M UTC')|" /tmp/roadmap.md
|
||||
|
||||
# --- Active Milestones ---
|
||||
echo "## Active Milestones" >> /tmp/roadmap.md
|
||||
echo "" >> /tmp/roadmap.md
|
||||
|
||||
MILESTONE_COUNT=$(echo "$MILESTONES_OPEN" | jq 'length')
|
||||
if [ "$MILESTONE_COUNT" -eq 0 ]; then
|
||||
echo "_No active milestones._" >> /tmp/roadmap.md
|
||||
echo "" >> /tmp/roadmap.md
|
||||
else
|
||||
echo "$MILESTONES_OPEN" | jq -r '.[] | @base64' | while read -r ms; do
|
||||
_jq() { echo "$ms" | base64 -d | jq -r "$1"; }
|
||||
TITLE=$(_jq '.title')
|
||||
DESC=$(_jq '.description // ""')
|
||||
DUE=$(_jq '.due_on // ""')
|
||||
OPEN=$(_jq '.open_issues')
|
||||
CLOSED=$(_jq '.closed_issues')
|
||||
TOTAL=$((OPEN + CLOSED))
|
||||
|
||||
if [ "$TOTAL" -gt 0 ]; then
|
||||
PCT=$((CLOSED * 100 / TOTAL))
|
||||
else
|
||||
PCT=0
|
||||
fi
|
||||
|
||||
echo "### ${TITLE}" >> /tmp/roadmap.md
|
||||
if [ -n "$DUE" ] && [ "$DUE" != "null" ] && [ "$DUE" != "0001-01-01T00:00:00Z" ]; then
|
||||
DUE_FMT=$(date -d "$DUE" '+%B %d, %Y' 2>/dev/null || echo "$DUE")
|
||||
echo "**Due:** ${DUE_FMT}" >> /tmp/roadmap.md
|
||||
fi
|
||||
if [ -n "$DESC" ] && [ "$DESC" != "null" ]; then
|
||||
echo "" >> /tmp/roadmap.md
|
||||
echo "$DESC" >> /tmp/roadmap.md
|
||||
fi
|
||||
echo "" >> /tmp/roadmap.md
|
||||
echo "**Progress:** ${CLOSED}/${TOTAL} (${PCT}%)" >> /tmp/roadmap.md
|
||||
echo "" >> /tmp/roadmap.md
|
||||
|
||||
# List issues in this milestone
|
||||
MS_ID=$(_jq '.id')
|
||||
MS_ISSUES=$(echo "$ISSUES_OPEN" | jq --arg id "$MS_ID" '[.[] | select(.milestone.id == ($id | tonumber))]')
|
||||
MS_DONE=$(echo "$ISSUES_CLOSED" | jq --arg id "$MS_ID" '[.[] | select(.milestone.id == ($id | tonumber))]')
|
||||
|
||||
if [ "$(echo "$MS_DONE" | jq 'length')" -gt 0 ]; then
|
||||
echo "$MS_DONE" | jq -r '.[] | "- [x] " + .title + " (#" + (.number | tostring) + ")"' >> /tmp/roadmap.md
|
||||
fi
|
||||
if [ "$(echo "$MS_ISSUES" | jq 'length')" -gt 0 ]; then
|
||||
echo "$MS_ISSUES" | jq -r '.[] | "- [ ] " + .title + " (#" + (.number | tostring) + ")"' >> /tmp/roadmap.md
|
||||
fi
|
||||
echo "" >> /tmp/roadmap.md
|
||||
done
|
||||
fi
|
||||
|
||||
# --- Backlog (issues without milestones) ---
|
||||
BACKLOG=$(echo "$ISSUES_OPEN" | jq '[.[] | select(.milestone == null)]')
|
||||
BACKLOG_COUNT=$(echo "$BACKLOG" | jq 'length')
|
||||
|
||||
if [ "$BACKLOG_COUNT" -gt 0 ]; then
|
||||
echo "## Backlog" >> /tmp/roadmap.md
|
||||
echo "" >> /tmp/roadmap.md
|
||||
echo "_Issues not yet assigned to a milestone._" >> /tmp/roadmap.md
|
||||
echo "" >> /tmp/roadmap.md
|
||||
|
||||
# Group by label if possible
|
||||
echo "$BACKLOG" | jq -r '.[] | "- [ ] " + .title + " (#" + (.number | tostring) + ")" + (if (.labels | length) > 0 then " `" + (.labels | map(.name) | join("`, `")) + "`" else "" end)' >> /tmp/roadmap.md
|
||||
echo "" >> /tmp/roadmap.md
|
||||
fi
|
||||
|
||||
# --- Completed Milestones ---
|
||||
CLOSED_COUNT=$(echo "$MILESTONES_CLOSED" | jq 'length')
|
||||
if [ "$CLOSED_COUNT" -gt 0 ]; then
|
||||
echo "## Completed" >> /tmp/roadmap.md
|
||||
echo "" >> /tmp/roadmap.md
|
||||
echo "$MILESTONES_CLOSED" | jq -r '.[] | "- ~~" + .title + "~~ ✓ (" + (.closed_issues | tostring) + " issues)"' >> /tmp/roadmap.md
|
||||
echo "" >> /tmp/roadmap.md
|
||||
fi
|
||||
|
||||
echo "---" >> /tmp/roadmap.md
|
||||
echo "_Generated by [sync-roadmap-wiki](${GITEA_URL}/${REPO}/actions) workflow._" >> /tmp/roadmap.md
|
||||
|
||||
echo "=== Generated Roadmap ==="
|
||||
cat /tmp/roadmap.md
|
||||
|
||||
- name: Push Roadmap to Wiki
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.GA_TOKEN }}
|
||||
GITEA_URL: ${{ github.server_url }}
|
||||
REPO_OWNER: ${{ github.repository_owner }}
|
||||
REPO_NAME: ${{ github.event.repository.name }}
|
||||
run: |
|
||||
API="${GITEA_URL}/api/v1"
|
||||
AUTH="Authorization: token ${GITEA_TOKEN}"
|
||||
REPO="${REPO_OWNER}/${REPO_NAME}"
|
||||
|
||||
CONTENT_B64=$(base64 -w0 /tmp/roadmap.md)
|
||||
|
||||
# Check if Roadmap wiki page exists
|
||||
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -H "$AUTH" "${API}/repos/${REPO}/wiki/page/Roadmap" || echo "404")
|
||||
|
||||
if [ "$STATUS" = "200" ]; then
|
||||
# Update existing page
|
||||
curl -sf -X PATCH -H "$AUTH" -H "Content-Type: application/json" \
|
||||
"${API}/repos/${REPO}/wiki/page/Roadmap" \
|
||||
-d "{\"title\": \"Roadmap\", \"content_base64\": \"${CONTENT_B64}\", \"message\": \"chore: sync roadmap from project board\"}" \
|
||||
&& echo "Roadmap wiki page updated"
|
||||
else
|
||||
# Create new page
|
||||
curl -sf -X POST -H "$AUTH" -H "Content-Type: application/json" \
|
||||
"${API}/repos/${REPO}/wiki/new" \
|
||||
-d "{\"title\": \"Roadmap\", \"content_base64\": \"${CONTENT_B64}\", \"message\": \"chore: create roadmap from project board\"}" \
|
||||
&& echo "Roadmap wiki page created"
|
||||
fi
|
||||
|
||||
- name: Summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "## Roadmap Sync" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Roadmap wiki page synced from milestones and issues." >> $GITHUB_STEP_SUMMARY
|
||||
echo "View it at: ${{ github.server_url }}/${{ github.repository }}/wiki/Roadmap" >> $GITHUB_STEP_SUMMARY
|
||||
@@ -12,30 +12,6 @@ All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
|
||||
## [03.01.00] — 2026-05-09
|
||||
|
||||
### Added
|
||||
- Bookings API: list, get via `/v1/dpcalendar/bookings`
|
||||
- Tickets API: list, get via `/v1/dpcalendar/tickets`
|
||||
- ICS/iCal export: single event and full calendar export
|
||||
- Recurring event occurrence expansion with RRULE support
|
||||
- Bulk event creation via `POST /v1/dpcalendar/events/bulk`
|
||||
- Calendar-level iCal export via `/v1/dpcalendar/calendars/{id}/ical`
|
||||
- CORS support with preflight handling
|
||||
- ETag caching for conditional GET requests
|
||||
- Location expansion on events via `?expand=locations`
|
||||
- Advanced filtering: date ranges, featured, access level, language
|
||||
- Sorting and field selection on all list endpoints
|
||||
- Search across event titles and descriptions
|
||||
- Pagination with total count metadata
|
||||
|
||||
### Changed
|
||||
- Version aligned across manifest and source files
|
||||
- Production-ready error handling with proper HTTP status codes
|
||||
|
||||
### Fixed
|
||||
- Version header consistency between manifest.xml and PHP source
|
||||
|
||||
## [01.00.00] — 2026-04-26
|
||||
|
||||
### Added
|
||||
|
||||
@@ -1,269 +1,94 @@
|
||||
<!-- 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: MokoDPCalendarAPI.Documentation
|
||||
INGROUP: MokoDPCalendarAPI
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI
|
||||
VERSION: 03.00.00
|
||||
PATH: ./README.md
|
||||
BRIEF: Joomla Web Services plugin for DPCalendar
|
||||
-->
|
||||
|
||||
[](https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/tag/stable)
|
||||
[](LICENSE)
|
||||
[](https://www.php.net)
|
||||
|
||||
# MokoDPCalendarAPI
|
||||
|
||||
Joomla Web Services plugin exposing **18 REST endpoints** for DPCalendar events, calendars, locations, bookings, and tickets.
|
||||
A Joomla 5/6 Web Services plugin that exposes DPCalendar events, calendars, and locations through the Joomla REST API (`/api/index.php/v1`).
|
||||
|
||||
    
|
||||
Enables AI assistants (via joomla-api-mcp) and external integrations to create, read, update, and delete DPCalendar content programmatically.
|
||||
|
||||
---
|
||||
## Table of Contents
|
||||
|
||||
## Features
|
||||
- [Background](#background)
|
||||
- [Install](#install)
|
||||
- [API Endpoints](#api-endpoints)
|
||||
- [Contributing](#contributing)
|
||||
- [License](#license)
|
||||
|
||||
- **18 REST endpoints** across 5 resources (events, calendars, locations, bookings, tickets)
|
||||
- **CRUD operations** for events including bulk creation
|
||||
- **iCal export** for events and calendars (`.ics` format)
|
||||
- **Recurring event expansion** -- RRULE processing with EXDATE support
|
||||
- **Pagination** with `limit`/`offset` (max 100 per page)
|
||||
- **Sorting** by 6 fields with ascending/descending order
|
||||
- **Filtering** by date range, category, search term, featured, access level, language
|
||||
- **Field selection** -- request only the fields you need
|
||||
- **Location expansion** -- inline location data with events via `expand=locations`
|
||||
- **ETag caching** with HTTP 304 Not Modified support
|
||||
- **CORS headers** for cross-origin requests
|
||||
## Background
|
||||
|
||||
---
|
||||
DPCalendar does not ship with a Web Services plugin. This plugin fills that gap by registering REST API routes for:
|
||||
|
||||
## Requirements
|
||||
- **Events** — CRUD with date filtering, category scoping, and recurrence support
|
||||
- **Calendars** — List and manage calendar categories
|
||||
- **Locations** — List and manage event locations
|
||||
|
||||
| Requirement | Version |
|
||||
|---|---|
|
||||
| **PHP** | 8.1+ |
|
||||
| **Joomla** | 5.x or 6.x |
|
||||
| **DPCalendar** | Required (component must be installed) |
|
||||
## Install
|
||||
|
||||
---
|
||||
1. Download the latest release ZIP
|
||||
2. **System > Install > Extensions** in Joomla admin
|
||||
3. Upload and install the ZIP
|
||||
4. **System > Manage > Plugins** — enable **Web Services - DPCalendar**
|
||||
|
||||
## Installation
|
||||
## API Endpoints
|
||||
|
||||
1. Download the latest release package from [Releases](https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases)
|
||||
2. In Joomla Admin, go to **System > Install > Extensions**
|
||||
3. Upload the `.zip` package
|
||||
4. Navigate to **System > Plugins** and search for `"Web Services - DPCalendar"`
|
||||
5. Enable the plugin
|
||||
All endpoints require `Authorization: Bearer <token>`.
|
||||
|
||||
See the [INSTALLATION](https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/wiki/INSTALLATION) wiki page for detailed instructions.
|
||||
### Events
|
||||
|
||||
---
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| GET | `/v1/dpcalendar/events` | List events |
|
||||
| GET | `/v1/dpcalendar/events/{id}` | Get event |
|
||||
| POST | `/v1/dpcalendar/events` | Create event |
|
||||
| PATCH | `/v1/dpcalendar/events/{id}` | Update event |
|
||||
| DELETE | `/v1/dpcalendar/events/{id}` | Delete event |
|
||||
|
||||
## Authentication
|
||||
### Calendars
|
||||
|
||||
All API requests require a Joomla API token passed via the `Authorization` header:
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| GET | `/v1/dpcalendar/calendars` | List calendars |
|
||||
| GET | `/v1/dpcalendar/calendars/{id}` | Get calendar |
|
||||
|
||||
```
|
||||
Authorization: Bearer <your-joomla-api-token>
|
||||
```
|
||||
### Locations
|
||||
|
||||
Generate a token in **Joomla Admin > Users > Manage > [User] > Joomla API Token tab**.
|
||||
|
||||
---
|
||||
|
||||
## Endpoints
|
||||
|
||||
Base path: `/api/index.php/v1`
|
||||
|
||||
### Events (8 endpoints)
|
||||
|
||||
| Method | Path | Description |
|
||||
|---|---|---|
|
||||
| `GET` | `/dpcalendar/events` | List events with filtering, sorting, pagination |
|
||||
| `GET` | `/dpcalendar/events/{id}` | Get a single event by ID |
|
||||
| `POST` | `/dpcalendar/events` | Create a new event |
|
||||
| `POST` | `/dpcalendar/events/bulk` | Bulk create multiple events |
|
||||
| `PATCH` | `/dpcalendar/events/{id}` | Update an existing event |
|
||||
| `DELETE` | `/dpcalendar/events/{id}` | Trash an event (soft delete) |
|
||||
| `GET` | `/dpcalendar/events/{id}/ical` | Export event as iCal (.ics) |
|
||||
| `GET` | `/dpcalendar/events/{id}/occurrences` | List occurrences of a recurring event |
|
||||
|
||||
### Calendars (3 endpoints)
|
||||
|
||||
| Method | Path | Description |
|
||||
|---|---|---|
|
||||
| `GET` | `/dpcalendar/calendars` | List all calendars |
|
||||
| `GET` | `/dpcalendar/calendars/{id}` | Get a single calendar by ID |
|
||||
| `GET` | `/dpcalendar/calendars/{id}/ical` | Export calendar as iCal (.ics) |
|
||||
|
||||
### Locations (2 endpoints)
|
||||
|
||||
| Method | Path | Description |
|
||||
|---|---|---|
|
||||
| `GET` | `/dpcalendar/locations` | List all locations |
|
||||
| `GET` | `/dpcalendar/locations/{id}` | Get a single location by ID |
|
||||
|
||||
### Bookings (2 endpoints)
|
||||
|
||||
| Method | Path | Description |
|
||||
|---|---|---|
|
||||
| `GET` | `/dpcalendar/bookings` | List all bookings |
|
||||
| `GET` | `/dpcalendar/bookings/{id}` | Get a single booking (with tickets) |
|
||||
|
||||
### Tickets (2 endpoints)
|
||||
|
||||
| Method | Path | Description |
|
||||
|---|---|---|
|
||||
| `GET` | `/dpcalendar/tickets` | List all tickets |
|
||||
| `GET` | `/dpcalendar/tickets/{id}` | Get a single ticket |
|
||||
|
||||
---
|
||||
|
||||
## Query Parameters
|
||||
|
||||
### Pagination
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `page[limit]` | integer | 20 | Results per page (max 100) |
|
||||
| `page[offset]` | integer | 0 | Number of results to skip |
|
||||
|
||||
### Sorting
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|---|---|---|
|
||||
| `sort` | string | Sort field. Prefix with `-` for descending. |
|
||||
|
||||
**Supported sort fields:** `id`, `title`, `start_date`, `end_date`, `catid`, `created`
|
||||
|
||||
Example: `?sort=-start_date` (newest first)
|
||||
|
||||
### Filtering
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|---|---|---|
|
||||
| `filter[search]` | string | Search events by title or description |
|
||||
| `filter[start_date]` | string | Events starting on or after this date (ISO 8601) |
|
||||
| `filter[end_date]` | string | Events ending on or before this date (ISO 8601) |
|
||||
| `filter[catid]` | integer | Filter by calendar/category ID |
|
||||
| `filter[featured]` | integer | `1` = featured only, `0` = non-featured only |
|
||||
| `filter[access]` | integer | Filter by Joomla access level |
|
||||
| `filter[language]` | string | Filter by language tag (e.g., `en-GB`) |
|
||||
|
||||
### Field Selection
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|---|---|---|
|
||||
| `fields[events]` | string | Comma-separated list of fields to return |
|
||||
|
||||
Example: `?fields[events]=id,title,start_date,end_date`
|
||||
|
||||
### Expansion
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|---|---|---|
|
||||
| `expand` | string | Include related data. Supported: `locations` |
|
||||
|
||||
---
|
||||
|
||||
## Examples
|
||||
|
||||
### List upcoming events
|
||||
|
||||
```bash
|
||||
curl -s \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Accept: application/vnd.api+json" \
|
||||
"https://example.com/api/index.php/v1/dpcalendar/events?filter[start_date]=2026-01-01&sort=start_date&page[limit]=10"
|
||||
```
|
||||
|
||||
### Get a single event
|
||||
|
||||
```bash
|
||||
curl -s \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Accept: application/vnd.api+json" \
|
||||
"https://example.com/api/index.php/v1/dpcalendar/events/42"
|
||||
```
|
||||
|
||||
### Create an event
|
||||
|
||||
```bash
|
||||
curl -s -X POST \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Accept: application/vnd.api+json" \
|
||||
-d '{
|
||||
"title": "Monthly Meetup",
|
||||
"catid": 8,
|
||||
"start_date": "2026-06-15 18:00:00",
|
||||
"end_date": "2026-06-15 20:00:00",
|
||||
"description": "<p>Join us for the monthly meetup!</p>"
|
||||
}' \
|
||||
"https://example.com/api/index.php/v1/dpcalendar/events"
|
||||
```
|
||||
|
||||
### Bulk create events
|
||||
|
||||
```bash
|
||||
curl -s -X POST \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Accept: application/vnd.api+json" \
|
||||
-d '[
|
||||
{"title": "Event A", "catid": 8, "start_date": "2026-07-01 10:00:00", "end_date": "2026-07-01 12:00:00"},
|
||||
{"title": "Event B", "catid": 8, "start_date": "2026-07-02 10:00:00", "end_date": "2026-07-02 12:00:00"}
|
||||
]' \
|
||||
"https://example.com/api/index.php/v1/dpcalendar/events/bulk"
|
||||
```
|
||||
|
||||
### Export calendar as iCal
|
||||
|
||||
```bash
|
||||
curl -s \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Accept: text/calendar" \
|
||||
"https://example.com/api/index.php/v1/dpcalendar/calendars/8/ical"
|
||||
```
|
||||
|
||||
### Get recurring event occurrences
|
||||
|
||||
```bash
|
||||
curl -s \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Accept: application/vnd.api+json" \
|
||||
"https://example.com/api/index.php/v1/dpcalendar/events/42/occurrences"
|
||||
```
|
||||
|
||||
### List events with location expansion
|
||||
|
||||
```bash
|
||||
curl -s \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Accept: application/vnd.api+json" \
|
||||
"https://example.com/api/index.php/v1/dpcalendar/events?expand=locations"
|
||||
```
|
||||
|
||||
### ETag caching (conditional request)
|
||||
|
||||
```bash
|
||||
# First request -- note the ETag in response headers
|
||||
curl -sI \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Accept: application/vnd.api+json" \
|
||||
"https://example.com/api/index.php/v1/dpcalendar/events"
|
||||
|
||||
# Subsequent request -- returns 304 if unchanged
|
||||
curl -s \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Accept: application/vnd.api+json" \
|
||||
-H 'If-None-Match: "etag-value-from-previous-response"' \
|
||||
"https://example.com/api/index.php/v1/dpcalendar/events"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Documentation
|
||||
|
||||
Full documentation is available on the [Wiki](https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/wiki), including:
|
||||
|
||||
| Page | Description |
|
||||
|---|---|
|
||||
| [Home](https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/wiki/Home) | Overview and quick reference |
|
||||
| [INSTALLATION](https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/wiki/INSTALLATION) | Installation guide for Joomla 5.x/6.x |
|
||||
| [API-Reference](https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/wiki/API-Reference) | Complete endpoint documentation |
|
||||
|
||||
---
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| GET | `/v1/dpcalendar/locations` | List locations |
|
||||
| GET | `/v1/dpcalendar/locations/{id}` | Get location |
|
||||
| POST | `/v1/dpcalendar/locations` | Create location |
|
||||
| PATCH | `/v1/dpcalendar/locations/{id}` | Update location |
|
||||
| DELETE | `/v1/dpcalendar/locations/{id}` | Delete location |
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the GNU General Public License v3.0 or later -- see the [LICENSE](LICENSE) file.
|
||||
GPL-3.0-or-later — see [LICENSE](LICENSE).
|
||||
|
||||
---
|
||||
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
*[Moko Consulting](https://mokoconsulting.tech) -- [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home)*
|
||||
## Maintainers
|
||||
|
||||
[@jmiller](https://git.mokoconsulting.tech/jmiller)
|
||||
|
||||
## Revision History
|
||||
|
||||
| Date | Version | Author | Notes |
|
||||
| --- | --- | --- | --- |
|
||||
| 2026-04-26 | 1.0.0 | jmiller | Initial Web Services plugin for DPCalendar |
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
# Installation
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Joomla 5.x or 6.x
|
||||
- DPCalendar 9.x+ installed and enabled
|
||||
- PHP 8.1+
|
||||
|
||||
## Install from Release
|
||||
|
||||
1. Download the latest ZIP from Releases
|
||||
2. In Joomla admin: **System > Install > Extensions**
|
||||
3. Upload and install the ZIP
|
||||
4. Go to **System > Manage > Plugins**
|
||||
5. Search for "DPCalendar" and enable **Web Services - DPCalendar**
|
||||
|
||||
## Install from Source
|
||||
|
||||
```sh
|
||||
git clone https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI.git
|
||||
cd MokoDPCalendarAPI
|
||||
```
|
||||
|
||||
Install the `src/` directory as a Joomla extension via Install from Folder or symlink.
|
||||
|
||||
## Verify
|
||||
|
||||
```sh
|
||||
curl -s https://your-site.com/api/index.php/v1/dpcalendar/events \
|
||||
-H "Authorization: Bearer YOUR_API_TOKEN" \
|
||||
-H "Accept: application/vnd.api+json"
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Resource not found"
|
||||
- Ensure the plugin is enabled in Plugins manager
|
||||
- Verify DPCalendar component is installed
|
||||
|
||||
### Empty responses
|
||||
- Check API user has access to the calendars
|
||||
@@ -3,17 +3,17 @@
|
||||
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI
|
||||
VERSION: 03.01.00
|
||||
VERSION: 01.00.00
|
||||
-->
|
||||
<extension type="plugin" group="webservices" method="upgrade">
|
||||
<name>Moko Web Services - DPCalendar API</name>
|
||||
<name>Web Services - DPCalendar API</name>
|
||||
<author>Moko Consulting</author>
|
||||
<creationDate>2026-05-10</creationDate>
|
||||
<creationDate>2026-05-04</creationDate>
|
||||
<copyright>Copyright (C) 2026 Moko Consulting</copyright>
|
||||
<license>GPL-3.0-or-later</license>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||
<version>03.02.00</version>
|
||||
<version>03.00.00</version>
|
||||
<description>Exposes DPCalendar events, calendars, and locations via the Joomla Web Services API</description>
|
||||
<namespace path="src">Moko\Plugin\WebServices\MokoDPCalendarAPI</namespace>
|
||||
<files>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
*
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI
|
||||
* PATH: /src/src/Extension/MokoDPCalendarAPI.php
|
||||
* VERSION: 03.01.00
|
||||
* VERSION: 02.00.00
|
||||
* BRIEF: Plugin class — registers and handles DPCalendar API routes
|
||||
*/
|
||||
|
||||
@@ -46,11 +46,9 @@ final class MokoDPCalendarAPI extends CMSPlugin
|
||||
$router->addRoute(new Route(['GET'], 'v1/dpcalendar/locations', 'locations.displayList', [], $defaults));
|
||||
$router->addRoute(new Route(['GET'], 'v1/dpcalendar/locations/:id', 'locations.displayItem', ['id' => '(\d+)'], $defaults));
|
||||
|
||||
// Bookings + Tickets
|
||||
// Bookings
|
||||
$router->addRoute(new Route(['GET'], 'v1/dpcalendar/bookings', 'bookings.displayList', [], $defaults));
|
||||
$router->addRoute(new Route(['GET'], 'v1/dpcalendar/bookings/:id', 'bookings.displayItem', ['id' => '(\d+)'], $defaults));
|
||||
$router->addRoute(new Route(['GET'], 'v1/dpcalendar/tickets', 'tickets.displayList', [], $defaults));
|
||||
$router->addRoute(new Route(['GET'], 'v1/dpcalendar/tickets/:id', 'tickets.displayItem', ['id' => '(\d+)'], $defaults));
|
||||
|
||||
// ICS export (calendar-level)
|
||||
$router->addRoute(new Route(['GET'], 'v1/dpcalendar/calendars/:id/ical', 'calendars.ical', ['id' => '(\d+)'], $defaults));
|
||||
@@ -64,7 +62,7 @@ final class MokoDPCalendarAPI extends CMSPlugin
|
||||
$controller = $input->get('controller', '', 'string');
|
||||
$task = $input->get('task', '', 'string');
|
||||
|
||||
$validControllers = ['events', 'calendars', 'locations', 'bookings', 'tickets'];
|
||||
$validControllers = ['events', 'calendars', 'locations', 'bookings'];
|
||||
if (!in_array($controller, $validControllers, true)) {
|
||||
return;
|
||||
}
|
||||
@@ -72,14 +70,6 @@ final class MokoDPCalendarAPI extends CMSPlugin
|
||||
$id = $input->getInt('id', 0);
|
||||
$method = $app->input->getMethod();
|
||||
|
||||
// Handle CORS preflight
|
||||
if ($method === 'OPTIONS') {
|
||||
$this->setCorsHeaders($app);
|
||||
$app->setHeader('Content-Length', '0', true);
|
||||
$app->sendHeaders();
|
||||
$app->close();
|
||||
}
|
||||
|
||||
try {
|
||||
$data = match (true) {
|
||||
// Events
|
||||
@@ -105,16 +95,11 @@ final class MokoDPCalendarAPI extends CMSPlugin
|
||||
$controller === 'bookings' && $method === 'GET' && !$id => $this->listBookings($input),
|
||||
$controller === 'bookings' && $method === 'GET' && $id > 0 => $this->getBooking($id),
|
||||
|
||||
// Tickets
|
||||
$controller === 'tickets' && $method === 'GET' && !$id => $this->listTickets($input),
|
||||
$controller === 'tickets' && $method === 'GET' && $id > 0 => $this->getTicket($id),
|
||||
|
||||
default => throw new \RuntimeException('Method not allowed', 405),
|
||||
};
|
||||
|
||||
// ICS responses bypass JSON
|
||||
if (is_string($data)) {
|
||||
$this->setCorsHeaders($app);
|
||||
$app->setHeader('Content-Type', 'text/calendar; charset=utf-8', true);
|
||||
$app->setHeader('Content-Disposition', 'attachment; filename="export.ics"', true);
|
||||
$app->sendHeaders();
|
||||
@@ -131,41 +116,16 @@ final class MokoDPCalendarAPI extends CMSPlugin
|
||||
|
||||
// ── Response helpers ─────────────────────────────────────────────
|
||||
|
||||
private function setCorsHeaders($app): void
|
||||
{
|
||||
$app->setHeader('Access-Control-Allow-Origin', '*', true);
|
||||
$app->setHeader('Access-Control-Allow-Methods', 'GET, POST, PATCH, DELETE, OPTIONS', true);
|
||||
$app->setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Joomla-Token', true);
|
||||
$app->setHeader('Access-Control-Max-Age', '86400', true);
|
||||
}
|
||||
|
||||
private function sendResponse($app, array $data): void
|
||||
{
|
||||
$this->setCorsHeaders($app);
|
||||
$app->setHeader('Content-Type', 'application/vnd.api+json', true);
|
||||
|
||||
// ETag for caching
|
||||
$json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||
$etag = '"' . md5($json) . '"';
|
||||
$app->setHeader('ETag', $etag, true);
|
||||
$app->setHeader('Cache-Control', 'private, max-age=60', true);
|
||||
|
||||
// Return 304 if client has current version
|
||||
$ifNoneMatch = $_SERVER['HTTP_IF_NONE_MATCH'] ?? '';
|
||||
if ($ifNoneMatch === $etag) {
|
||||
http_response_code(304);
|
||||
$app->sendHeaders();
|
||||
$app->close();
|
||||
}
|
||||
|
||||
$app->sendHeaders();
|
||||
echo $json;
|
||||
echo json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||
$app->close();
|
||||
}
|
||||
|
||||
private function sendError($app, int $code, string $message): void
|
||||
{
|
||||
$this->setCorsHeaders($app);
|
||||
http_response_code($code);
|
||||
$app->setHeader('Content-Type', 'application/vnd.api+json', true);
|
||||
$app->sendHeaders();
|
||||
@@ -272,18 +232,6 @@ final class MokoDPCalendarAPI extends CMSPlugin
|
||||
->bind(':featured', $featVal, ParameterType::INTEGER);
|
||||
}
|
||||
|
||||
$accessLevel = $input->getInt('access', 0);
|
||||
if ($accessLevel) {
|
||||
$query->where($db->quoteName('e.access') . ' = :access')
|
||||
->bind(':access', $accessLevel, ParameterType::INTEGER);
|
||||
}
|
||||
|
||||
$lang = $input->getString('language', '');
|
||||
if ($lang) {
|
||||
$query->where('(' . $db->quoteName('e.language') . ' = :lang OR ' . $db->quoteName('e.language') . ' = ' . $db->quote('*') . ')')
|
||||
->bind(':lang', $lang);
|
||||
}
|
||||
|
||||
// Count total
|
||||
$countQuery = clone $query;
|
||||
$countQuery->clear('select')->clear('order')->select('COUNT(*)');
|
||||
@@ -794,59 +742,6 @@ final class MokoDPCalendarAPI extends CMSPlugin
|
||||
return ['data' => $result];
|
||||
}
|
||||
|
||||
// ── Tickets ──────────────────────────────────────────────────────
|
||||
|
||||
private function listTickets($input): array
|
||||
{
|
||||
$db = $this->getDb();
|
||||
[$limit, $offset] = $this->getPagination($input);
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->select('t.id, t.uid, t.booking_id, t.event_id, t.name, t.email, t.state, t.price, t.created')
|
||||
->from($db->quoteName('#__dpcalendar_tickets', 't'))
|
||||
->order('t.created DESC');
|
||||
|
||||
$eventId = $input->getInt('event_id', 0);
|
||||
if ($eventId) {
|
||||
$query->where($db->quoteName('t.event_id') . ' = :event_id')
|
||||
->bind(':event_id', $eventId, ParameterType::INTEGER);
|
||||
}
|
||||
|
||||
$bookingId = $input->getInt('booking_id', 0);
|
||||
if ($bookingId) {
|
||||
$query->where($db->quoteName('t.booking_id') . ' = :booking_id')
|
||||
->bind(':booking_id', $bookingId, ParameterType::INTEGER);
|
||||
}
|
||||
|
||||
$countQuery = clone $query;
|
||||
$countQuery->clear('select')->clear('order')->select('COUNT(*)');
|
||||
$total = (int) $db->setQuery($countQuery)->loadResult();
|
||||
|
||||
$db->setQuery($query, $offset, $limit);
|
||||
$items = $db->loadAssocList() ?: [];
|
||||
|
||||
return [
|
||||
'data' => $items,
|
||||
'meta' => ['total-items' => $total, 'limit' => $limit, 'offset' => $offset],
|
||||
];
|
||||
}
|
||||
|
||||
private function getTicket(int $id): array
|
||||
{
|
||||
$db = $this->getDb();
|
||||
$query = $db->getQuery(true)
|
||||
->select('t.*')
|
||||
->from($db->quoteName('#__dpcalendar_tickets', 't'))
|
||||
->where($db->quoteName('t.id') . ' = :id')
|
||||
->bind(':id', $id, ParameterType::INTEGER);
|
||||
|
||||
$result = $db->setQuery($query)->loadAssoc();
|
||||
if (!$result) {
|
||||
throw new \RuntimeException('Ticket not found', 404);
|
||||
}
|
||||
return ['data' => $result];
|
||||
}
|
||||
|
||||
// ── ICS/iCal Export ──────────────────────────────────────────────
|
||||
|
||||
private function eventToIcal(int $id): string
|
||||
|
||||
+31
-31
@@ -1,96 +1,96 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<!-- Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
VERSION: 03.02.00
|
||||
VERSION: 03.00.00
|
||||
-->
|
||||
|
||||
<updates>
|
||||
<update>
|
||||
<name>Moko Web Services - DPCalendar API</name>
|
||||
<description>Moko Web Services - DPCalendar API update</description>
|
||||
<name>Web Services - DPCalendar API</name>
|
||||
<description>Web Services - DPCalendar API update</description>
|
||||
<element>mokodpcalendarapi</element>
|
||||
<type>plugin</type>
|
||||
<version>03.02.00</version>
|
||||
<version>03.00.00</version>
|
||||
<client>site</client>
|
||||
<folder>webservices</folder>
|
||||
<tags><tag>development</tag></tags>
|
||||
<infourl title="Moko Web Services - DPCalendar API">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/tag/stable</infourl>
|
||||
<infourl title="Web Services - DPCalendar API">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/tag/stable</infourl>
|
||||
<downloads>
|
||||
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/download/stable/-03.02.00.zip</downloadurl>
|
||||
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/download/stable/-03.00.00.zip</downloadurl>
|
||||
</downloads>
|
||||
<sha256>652b87468d39eb4a513b6b0aae3a563abf7662f0ee3b837b724ee3b6e19a08fb</sha256>
|
||||
<sha256>e51f981b6e417e7417884af4e9e3f2b078ab9bd4c035522797ed37c32c620665</sha256>
|
||||
<targetplatform name="joomla" version="((5.[0-9])|(6.[0-9]))" />
|
||||
<maintainer>Moko Consulting</maintainer>
|
||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||
</update>
|
||||
<update>
|
||||
<name>Moko Web Services - DPCalendar API</name>
|
||||
<description>Moko Web Services - DPCalendar API update</description>
|
||||
<name>Web Services - DPCalendar API</name>
|
||||
<description>Web Services - DPCalendar API update</description>
|
||||
<element>mokodpcalendarapi</element>
|
||||
<type>plugin</type>
|
||||
<version>03.02.00</version>
|
||||
<version>03.00.00</version>
|
||||
<client>site</client>
|
||||
<folder>webservices</folder>
|
||||
<tags><tag>alpha</tag></tags>
|
||||
<infourl title="Moko Web Services - DPCalendar API">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/tag/stable</infourl>
|
||||
<infourl title="Web Services - DPCalendar API">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/tag/stable</infourl>
|
||||
<downloads>
|
||||
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/download/stable/-03.02.00.zip</downloadurl>
|
||||
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/download/stable/-03.00.00.zip</downloadurl>
|
||||
</downloads>
|
||||
<sha256>652b87468d39eb4a513b6b0aae3a563abf7662f0ee3b837b724ee3b6e19a08fb</sha256>
|
||||
<sha256>e51f981b6e417e7417884af4e9e3f2b078ab9bd4c035522797ed37c32c620665</sha256>
|
||||
<targetplatform name="joomla" version="((5.[0-9])|(6.[0-9]))" />
|
||||
<maintainer>Moko Consulting</maintainer>
|
||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||
</update>
|
||||
<update>
|
||||
<name>Moko Web Services - DPCalendar API</name>
|
||||
<description>Moko Web Services - DPCalendar API update</description>
|
||||
<name>Web Services - DPCalendar API</name>
|
||||
<description>Web Services - DPCalendar API update</description>
|
||||
<element>mokodpcalendarapi</element>
|
||||
<type>plugin</type>
|
||||
<version>03.02.00</version>
|
||||
<version>03.00.00</version>
|
||||
<client>site</client>
|
||||
<folder>webservices</folder>
|
||||
<tags><tag>beta</tag></tags>
|
||||
<infourl title="Moko Web Services - DPCalendar API">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/tag/stable</infourl>
|
||||
<infourl title="Web Services - DPCalendar API">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/tag/stable</infourl>
|
||||
<downloads>
|
||||
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/download/stable/-03.02.00.zip</downloadurl>
|
||||
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/download/stable/-03.00.00.zip</downloadurl>
|
||||
</downloads>
|
||||
<sha256>652b87468d39eb4a513b6b0aae3a563abf7662f0ee3b837b724ee3b6e19a08fb</sha256>
|
||||
<sha256>e51f981b6e417e7417884af4e9e3f2b078ab9bd4c035522797ed37c32c620665</sha256>
|
||||
<targetplatform name="joomla" version="((5.[0-9])|(6.[0-9]))" />
|
||||
<maintainer>Moko Consulting</maintainer>
|
||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||
</update>
|
||||
<update>
|
||||
<name>Moko Web Services - DPCalendar API</name>
|
||||
<description>Moko Web Services - DPCalendar API update</description>
|
||||
<name>Web Services - DPCalendar API</name>
|
||||
<description>Web Services - DPCalendar API update</description>
|
||||
<element>mokodpcalendarapi</element>
|
||||
<type>plugin</type>
|
||||
<version>03.02.00</version>
|
||||
<version>03.00.00</version>
|
||||
<client>site</client>
|
||||
<folder>webservices</folder>
|
||||
<tags><tag>rc</tag></tags>
|
||||
<infourl title="Moko Web Services - DPCalendar API">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/tag/stable</infourl>
|
||||
<infourl title="Web Services - DPCalendar API">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/tag/stable</infourl>
|
||||
<downloads>
|
||||
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/download/stable/-03.02.00.zip</downloadurl>
|
||||
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/download/stable/-03.00.00.zip</downloadurl>
|
||||
</downloads>
|
||||
<sha256>652b87468d39eb4a513b6b0aae3a563abf7662f0ee3b837b724ee3b6e19a08fb</sha256>
|
||||
<sha256>e51f981b6e417e7417884af4e9e3f2b078ab9bd4c035522797ed37c32c620665</sha256>
|
||||
<targetplatform name="joomla" version="((5.[0-9])|(6.[0-9]))" />
|
||||
<maintainer>Moko Consulting</maintainer>
|
||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||
</update>
|
||||
<update>
|
||||
<name>Moko Web Services - DPCalendar API</name>
|
||||
<description>Moko Web Services - DPCalendar API update</description>
|
||||
<name>Web Services - DPCalendar API</name>
|
||||
<description>Web Services - DPCalendar API update</description>
|
||||
<element>mokodpcalendarapi</element>
|
||||
<type>plugin</type>
|
||||
<version>03.02.00</version>
|
||||
<version>03.00.00</version>
|
||||
<client>site</client>
|
||||
<folder>webservices</folder>
|
||||
<tags><tag>stable</tag></tags>
|
||||
<infourl title="Moko Web Services - DPCalendar API">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/tag/stable</infourl>
|
||||
<infourl title="Web Services - DPCalendar API">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/tag/stable</infourl>
|
||||
<downloads>
|
||||
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/download/stable/-03.02.00.zip</downloadurl>
|
||||
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoDPCalendarAPI/releases/download/stable/-03.00.00.zip</downloadurl>
|
||||
</downloads>
|
||||
<sha256>652b87468d39eb4a513b6b0aae3a563abf7662f0ee3b837b724ee3b6e19a08fb</sha256>
|
||||
<sha256>e51f981b6e417e7417884af4e9e3f2b078ab9bd4c035522797ed37c32c620665</sha256>
|
||||
<targetplatform name="joomla" version="((5.[0-9])|(6.[0-9]))" />
|
||||
<maintainer>Moko Consulting</maintainer>
|
||||
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||
|
||||
Reference in New Issue
Block a user