Template
Compare commits
64 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b99c4e2eb7 | |||
| 4b75851412 | |||
| 6cb36784bb | |||
| a7fe9db66c | |||
| 618350dc2e | |||
| d93c69da9d | |||
| c84fd09e18 | |||
| 95ea7cee8c | |||
| 718d57bd1e | |||
| 7b86c2c16a | |||
| abe2c102fd | |||
| 44f03ddcd8 | |||
| d2e7d85e16 | |||
| a71170addf | |||
| 393677927f | |||
| 351d847619 | |||
| 9927926295 | |||
| 7ad8efe281 | |||
| e498b22b66 | |||
| be2298f6ec | |||
| 70420bec71 | |||
| 478582ba0f | |||
| 2cf42b79b7 | |||
| d46c431189 | |||
| 08a3e7b089 | |||
| d3fa17430f | |||
| de1b38213f | |||
| 10db4fe33a | |||
| dbf24e304b | |||
| b4c1065b57 | |||
| 8cd6c4626f | |||
| 757887ea11 | |||
| 7efc4731f9 | |||
| d883177b88 | |||
| 286b35fabf | |||
| da7621a7ab | |||
| 219668fd3c | |||
| dcbd33a72f | |||
| 6b6cca5761 | |||
| cd6d218e76 | |||
| c8f993c3e2 | |||
| d813100190 | |||
| cd58400dff | |||
| 3f8a5d8ec5 | |||
| 997f6c4181 | |||
| 3f0deac20e | |||
| 9964310cae | |||
| b1862ba8c7 | |||
| dba9f1c6a2 | |||
| 2ee87395c1 | |||
| 9444684322 | |||
| 90b632912b | |||
| 3d8ec08abd | |||
| db2899b3b3 | |||
| b41ed2caa5 | |||
| 66da3fa30c | |||
| 36015389a4 | |||
| f243386d8d | |||
| 48ee0ce5bb | |||
| 1b56994f16 | |||
| 51807ff944 | |||
| 5cd184a7dc | |||
| a02da1ad84 | |||
| 73628c509b |
+7
-1
@@ -180,7 +180,7 @@ __pycache__/
|
|||||||
*.egg
|
*.egg
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
.installed.cfg
|
.installed.cfg
|
||||||
MANIFEST
|
/MANIFEST
|
||||||
develop-eggs/
|
develop-eggs/
|
||||||
downloads/
|
downloads/
|
||||||
eggs/
|
eggs/
|
||||||
@@ -204,3 +204,9 @@ hypothesis/
|
|||||||
|
|
||||||
profile.ps1
|
profile.ps1
|
||||||
.mcp.json
|
.mcp.json
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Local wiki clone (not version controlled)
|
||||||
|
# ============================================================
|
||||||
|
wiki/
|
||||||
|
docs/
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ contact_links:
|
|||||||
url: https://mokoconsulting.tech/
|
url: https://mokoconsulting.tech/
|
||||||
about: Get help or ask questions through our website
|
about: Get help or ask questions through our website
|
||||||
- name: 📚 MokoStandards Documentation
|
- name: 📚 MokoStandards Documentation
|
||||||
url: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
url: https://git.mokoconsulting.tech/MokoConsulting/MokoCLI
|
||||||
about: View our coding standards and best practices
|
about: View our coding standards and best practices
|
||||||
- name: 🔒 Report a Security Vulnerability
|
- name: 🔒 Report a Security Vulnerability
|
||||||
url: https://git.mokoconsulting.tech/mokoconsulting-tech/.github-private/security/advisories/new
|
url: https://git.mokoconsulting.tech/MokoConsulting/.github-private/security/advisories/new
|
||||||
about: Report security vulnerabilities privately (for critical issues)
|
about: Report security vulnerabilities privately (for critical issues)
|
||||||
- name: 💡 Community Discussions
|
- name: 💡 Community Discussions
|
||||||
url: https://github.com/orgs/mokoconsulting-tech/discussions
|
url: https://github.com/orgs/mokoconsulting-tech/discussions
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: Gitea.Workflow
|
||||||
# INGROUP: moko-platform.Automation
|
# INGROUP: mokocli.Automation
|
||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoCLI
|
||||||
# PATH: /.gitea/workflows/branch-protection.yml
|
# PATH: /.gitea/workflows/branch-protection.yml
|
||||||
# BRIEF: Apply standardised branch protection rules to all governed repositories
|
# BRIEF: Apply standardised branch protection rules to all governed repositories
|
||||||
#
|
#
|
||||||
@@ -62,7 +62,7 @@ jobs:
|
|||||||
API="${GITEA_URL}/api/v1"
|
API="${GITEA_URL}/api/v1"
|
||||||
|
|
||||||
# Platform/standards/infra repos to exclude
|
# Platform/standards/infra repos to exclude
|
||||||
EXCLUDE="gitea-org-config org-profile gitea-private .mokogitea-private MokoStandards moko-platform MokoTesting"
|
EXCLUDE="gitea-org-config org-profile gitea-private .mokogitea-private MokoStandards mokocli MokoTesting"
|
||||||
EXCLUDE="$EXCLUDE MokoStandards-Template-Client MokoStandards-Template-Dolibarr MokoStandards-Template-Generic MokoStandards-Template-Joomla MokoDoliProjTemplate"
|
EXCLUDE="$EXCLUDE MokoStandards-Template-Client MokoStandards-Template-Dolibarr MokoStandards-Template-Generic MokoStandards-Template-Joomla MokoDoliProjTemplate"
|
||||||
|
|
||||||
if [ -n "${{ inputs.repos }}" ]; then
|
if [ -n "${{ inputs.repos }}" ]; then
|
||||||
@@ -124,7 +124,8 @@ jobs:
|
|||||||
"rule_name": "main",
|
"rule_name": "main",
|
||||||
"enable_push": true,
|
"enable_push": true,
|
||||||
"enable_push_whitelist": true,
|
"enable_push_whitelist": true,
|
||||||
"push_whitelist_usernames": ["jmiller"],
|
"push_whitelist_usernames": ["jmiller", "moko-deploy"],
|
||||||
|
"push_whitelist_actions_user": true,
|
||||||
"enable_force_push": false,
|
"enable_force_push": false,
|
||||||
"enable_force_push_allowlist": false,
|
"enable_force_push_allowlist": false,
|
||||||
"force_push_allowlist_usernames": [],
|
"force_push_allowlist_usernames": [],
|
||||||
@@ -140,10 +141,12 @@ jobs:
|
|||||||
"rule_name": "dev",
|
"rule_name": "dev",
|
||||||
"enable_push": true,
|
"enable_push": true,
|
||||||
"enable_push_whitelist": true,
|
"enable_push_whitelist": true,
|
||||||
"push_whitelist_usernames": ["jmiller"],
|
"push_whitelist_usernames": ["jmiller", "moko-deploy"],
|
||||||
"enable_force_push": false,
|
"push_whitelist_actions_user": true,
|
||||||
"enable_force_push_allowlist": false,
|
"enable_force_push": true,
|
||||||
"force_push_allowlist_usernames": [],
|
"enable_force_push_allowlist": true,
|
||||||
|
"force_push_allowlist_usernames": ["jmiller", "moko-deploy"],
|
||||||
|
"force_push_allowlist_actions_user": true,
|
||||||
"enable_merge_whitelist": false,
|
"enable_merge_whitelist": false,
|
||||||
"required_approvals": 0,
|
"required_approvals": 0,
|
||||||
"block_on_rejected_reviews": false,
|
"block_on_rejected_reviews": false,
|
||||||
@@ -154,10 +157,12 @@ jobs:
|
|||||||
"rule_name": "rc",
|
"rule_name": "rc",
|
||||||
"enable_push": true,
|
"enable_push": true,
|
||||||
"enable_push_whitelist": true,
|
"enable_push_whitelist": true,
|
||||||
"push_whitelist_usernames": ["jmiller"],
|
"push_whitelist_usernames": ["jmiller", "moko-deploy"],
|
||||||
"enable_force_push": false,
|
"push_whitelist_actions_user": true,
|
||||||
"enable_force_push_allowlist": false,
|
"enable_force_push": true,
|
||||||
"force_push_allowlist_usernames": [],
|
"enable_force_push_allowlist": true,
|
||||||
|
"force_push_allowlist_usernames": ["jmiller", "moko-deploy"],
|
||||||
|
"force_push_allowlist_actions_user": true,
|
||||||
"enable_merge_whitelist": false,
|
"enable_merge_whitelist": false,
|
||||||
"required_approvals": 0,
|
"required_approvals": 0,
|
||||||
"block_on_rejected_reviews": false,
|
"block_on_rejected_reviews": false,
|
||||||
@@ -168,7 +173,8 @@ jobs:
|
|||||||
"rule_name": "beta",
|
"rule_name": "beta",
|
||||||
"enable_push": true,
|
"enable_push": true,
|
||||||
"enable_push_whitelist": true,
|
"enable_push_whitelist": true,
|
||||||
"push_whitelist_usernames": ["jmiller"],
|
"push_whitelist_usernames": ["jmiller", "moko-deploy"],
|
||||||
|
"push_whitelist_actions_user": true,
|
||||||
"enable_force_push": false,
|
"enable_force_push": false,
|
||||||
"enable_force_push_allowlist": false,
|
"enable_force_push_allowlist": false,
|
||||||
"force_push_allowlist_usernames": [],
|
"force_push_allowlist_usernames": [],
|
||||||
@@ -182,7 +188,8 @@ jobs:
|
|||||||
"rule_name": "alpha",
|
"rule_name": "alpha",
|
||||||
"enable_push": true,
|
"enable_push": true,
|
||||||
"enable_push_whitelist": true,
|
"enable_push_whitelist": true,
|
||||||
"push_whitelist_usernames": ["jmiller"],
|
"push_whitelist_usernames": ["jmiller", "moko-deploy"],
|
||||||
|
"push_whitelist_actions_user": true,
|
||||||
"enable_force_push": false,
|
"enable_force_push": false,
|
||||||
"enable_force_push_allowlist": false,
|
"enable_force_push_allowlist": false,
|
||||||
"force_push_allowlist_usernames": [],
|
"force_push_allowlist_usernames": [],
|
||||||
|
|||||||
@@ -1,66 +1,66 @@
|
|||||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
#
|
#
|
||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: Gitea.Workflow
|
||||||
# INGROUP: mokocli.Release
|
# INGROUP: mokocli.Release
|
||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoCLI
|
||||||
# PATH: /.mokogitea/workflows/auto-bump.yml
|
# PATH: /.mokogitea/workflows/auto-bump.yml
|
||||||
# VERSION: 09.02.00
|
# VERSION: 09.02.00
|
||||||
# BRIEF: Auto patch-bump version on every push to dev (skips merge commits)
|
# BRIEF: Auto patch-bump version on every push to dev (skips merge commits)
|
||||||
|
|
||||||
name: "Universal: Auto Version Bump"
|
name: "Universal: Auto Version Bump"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- dev
|
- dev
|
||||||
- rc
|
- rc
|
||||||
- 'feature/**'
|
- 'feature/**'
|
||||||
- 'patch/**'
|
- 'patch/**'
|
||||||
|
|
||||||
env:
|
env:
|
||||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
bump:
|
bump:
|
||||||
name: Version Bump
|
name: Version Bump
|
||||||
runs-on: release
|
runs-on: release
|
||||||
if: >-
|
if: >-
|
||||||
!contains(github.event.head_commit.message, '[skip ci]') &&
|
!contains(github.event.head_commit.message, '[skip ci]') &&
|
||||||
!contains(github.event.head_commit.message, '[skip bump]') &&
|
!contains(github.event.head_commit.message, '[skip bump]') &&
|
||||||
!startsWith(github.event.head_commit.message, 'Merge pull request')
|
!startsWith(github.event.head_commit.message, 'Merge pull request')
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
|
||||||
- name: Setup mokocli tools
|
- name: Setup mokocli tools
|
||||||
run: |
|
run: |
|
||||||
if ! command -v composer &> /dev/null; then
|
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
|
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
|
fi
|
||||||
if [ -d "/opt/mokocli/cli" ]; then
|
if [ -d "/opt/mokocli/cli" ]; then
|
||||||
echo "MOKO_CLI=/opt/mokocli/cli" >> "$GITHUB_ENV"
|
echo "MOKO_CLI=/opt/mokocli/cli" >> "$GITHUB_ENV"
|
||||||
else
|
else
|
||||||
git clone --depth 1 --branch main --quiet \
|
git clone --depth 1 --branch main --quiet \
|
||||||
"https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/mokocli.git" \
|
"https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/MokoCLI.git" \
|
||||||
/tmp/mokocli
|
/tmp/mokocli
|
||||||
cd /tmp/mokocli && composer install --no-dev --no-interaction --quiet
|
cd /tmp/mokocli && composer install --no-dev --no-interaction --quiet
|
||||||
echo "MOKO_CLI=/tmp/mokocli/cli" >> "$GITHUB_ENV"
|
echo "MOKO_CLI=/tmp/mokocli/cli" >> "$GITHUB_ENV"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Bump version
|
- name: Bump version
|
||||||
run: |
|
run: |
|
||||||
php ${MOKO_CLI}/version_auto_bump.php \
|
php ${MOKO_CLI}/version_auto_bump.php \
|
||||||
--path . --branch "${GITHUB_REF_NAME}" \
|
--path . --branch "${GITHUB_REF_NAME}" \
|
||||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||||
--repo-url "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
--repo-url "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||||
|
|||||||
@@ -5,9 +5,9 @@
|
|||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: Gitea.Workflow
|
||||||
# INGROUP: mokocli.Release
|
# INGROUP: mokocli.Release
|
||||||
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokocli
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoCLI
|
||||||
# PATH: /templates/workflows/universal/auto-release.yml.template
|
# PATH: /templates/workflows/universal/auto-release.yml.template
|
||||||
# VERSION: 05.00.00
|
# VERSION: 05.01.00
|
||||||
# BRIEF: Universal build & release � detects platform from manifest.xml
|
# BRIEF: Universal build & release � detects platform from manifest.xml
|
||||||
#
|
#
|
||||||
# +=======================================================================+
|
# +=======================================================================+
|
||||||
@@ -27,7 +27,7 @@ name: "Universal: Build & Release"
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [opened, closed]
|
types: [opened, synchronize, closed]
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
@@ -52,7 +52,7 @@ on:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
|
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
|
||||||
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
|
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
|
||||||
|
|
||||||
@@ -64,9 +64,14 @@ jobs:
|
|||||||
promote-rc:
|
promote-rc:
|
||||||
name: Promote to RC
|
name: Promote to RC
|
||||||
runs-on: release
|
runs-on: release
|
||||||
|
# Skip on template repos (Template-*) — they scaffold other repos and do not release.
|
||||||
if: >-
|
if: >-
|
||||||
(github.event.action == 'opened' && github.event.pull_request.merged != true) ||
|
!startsWith(github.event.repository.name, 'Template-') &&
|
||||||
(github.event_name == 'workflow_dispatch' && inputs.action == 'promote-rc')
|
(
|
||||||
|
(github.event.action == 'opened' && github.event.pull_request.merged != true) ||
|
||||||
|
(github.event.action == 'synchronize' && github.event.pull_request.merged != true) ||
|
||||||
|
(github.event_name == 'workflow_dispatch' && inputs.action == 'promote-rc')
|
||||||
|
)
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
@@ -74,6 +79,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
- name: Setup mokocli tools
|
- name: Setup mokocli tools
|
||||||
env:
|
env:
|
||||||
@@ -89,7 +95,7 @@ jobs:
|
|||||||
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
|
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
|
fi
|
||||||
rm -rf /tmp/mokocli
|
rm -rf /tmp/mokocli
|
||||||
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git
|
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoCLI.git
|
||||||
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli
|
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli
|
||||||
cd /tmp/mokocli
|
cd /tmp/mokocli
|
||||||
composer install --no-dev --no-interaction --quiet
|
composer install --no-dev --no-interaction --quiet
|
||||||
@@ -101,7 +107,7 @@ jobs:
|
|||||||
php ${MOKO_CLI}/branch_rename.php \
|
php ${MOKO_CLI}/branch_rename.php \
|
||||||
--from "${{ github.event.pull_request.head.ref || 'dev' }}" --to rc \
|
--from "${{ github.event.pull_request.head.ref || 'dev' }}" --to rc \
|
||||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||||
--api-base "${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" \
|
--api-base "${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" \
|
||||||
--pr "${{ github.event.pull_request.number }}"
|
--pr "${{ github.event.pull_request.number }}"
|
||||||
|
|
||||||
- name: Checkout rc and configure git
|
- name: Checkout rc and configure git
|
||||||
@@ -120,7 +126,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Update RC release notes from CHANGELOG.md
|
- name: Update RC release notes from CHANGELOG.md
|
||||||
run: |
|
run: |
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||||
|
|
||||||
# Extract [Unreleased] section from changelog
|
# Extract [Unreleased] section from changelog
|
||||||
@@ -162,9 +168,13 @@ jobs:
|
|||||||
release:
|
release:
|
||||||
name: Build & Release Pipeline
|
name: Build & Release Pipeline
|
||||||
runs-on: release
|
runs-on: release
|
||||||
|
# Skip on template repos (Template-*) — they scaffold other repos and do not release.
|
||||||
if: >-
|
if: >-
|
||||||
github.event.pull_request.merged == true ||
|
!startsWith(github.event.repository.name, 'Template-') &&
|
||||||
(github.event_name == 'workflow_dispatch' && inputs.action != 'promote-rc')
|
(
|
||||||
|
github.event.pull_request.merged == true ||
|
||||||
|
(github.event_name == 'workflow_dispatch' && inputs.action != 'promote-rc')
|
||||||
|
)
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
@@ -172,6 +182,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
- name: Configure git for bot pushes
|
- name: Configure git for bot pushes
|
||||||
run: |
|
run: |
|
||||||
@@ -207,7 +218,7 @@ jobs:
|
|||||||
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
|
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
|
fi
|
||||||
rm -rf /tmp/mokocli
|
rm -rf /tmp/mokocli
|
||||||
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git
|
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoCLI.git
|
||||||
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli
|
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli
|
||||||
cd /tmp/mokocli
|
cd /tmp/mokocli
|
||||||
composer install --no-dev --no-interaction --quiet
|
composer install --no-dev --no-interaction --quiet
|
||||||
@@ -268,7 +279,7 @@ jobs:
|
|||||||
!startsWith(steps.platform.outputs.platform, 'joomla')
|
!startsWith(steps.platform.outputs.platform, 'joomla')
|
||||||
run: |
|
run: |
|
||||||
VERSION="${{ steps.version.outputs.version }}"
|
VERSION="${{ steps.version.outputs.version }}"
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||||
SEMVER_TAG="v${VERSION}"
|
SEMVER_TAG="v${VERSION}"
|
||||||
|
|
||||||
@@ -293,7 +304,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Update release notes and promote changelog
|
- name: Update release notes and promote changelog
|
||||||
run: |
|
run: |
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||||
|
|
||||||
# Get the stable release info (version and ID)
|
# Get the stable release info (version and ID)
|
||||||
@@ -362,7 +373,7 @@ jobs:
|
|||||||
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
||||||
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
||||||
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
|
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
php ${MOKO_CLI}/release_mirror.php \
|
php ${MOKO_CLI}/release_mirror.php \
|
||||||
--version "$VERSION" --tag "$RELEASE_TAG" \
|
--version "$VERSION" --tag "$RELEASE_TAG" \
|
||||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
||||||
@@ -387,11 +398,11 @@ jobs:
|
|||||||
&& echo "main branch pushed to GitHub mirror" \
|
&& echo "main branch pushed to GitHub mirror" \
|
||||||
|| echo "WARNING: GitHub mirror push failed"
|
|| echo "WARNING: GitHub mirror push failed"
|
||||||
|
|
||||||
- name: "Step 11: Delete rc branch and recreate dev from main"
|
- name: "Step 11: Delete rc branch (dev reset moved to cascade-dev.yml)"
|
||||||
if: steps.version.outputs.skip != 'true'
|
if: steps.version.outputs.skip != 'true'
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||||
|
|
||||||
# Delete rc branch (ephemeral — created by promote-rc)
|
# Delete rc branch (ephemeral — created by promote-rc)
|
||||||
@@ -399,23 +410,15 @@ jobs:
|
|||||||
"${API_BASE}/branches/rc" 2>/dev/null \
|
"${API_BASE}/branches/rc" 2>/dev/null \
|
||||||
&& echo "Deleted rc branch" || echo "rc branch not found"
|
&& echo "Deleted rc branch" || echo "rc branch not found"
|
||||||
|
|
||||||
# Delete dev branch
|
# dev is reset from main by the dedicated "Cascade Main -> Dev" workflow
|
||||||
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
|
# (cascade-dev.yml), which runs after this release completes.
|
||||||
"${API_BASE}/branches/dev" 2>/dev/null && echo "Deleted dev branch"
|
echo "rc cleaned; dev reset handled by cascade-dev.yml" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
# 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 "Pre-release branches cleaned, dev reset from main" >> $GITHUB_STEP_SUMMARY
|
|
||||||
|
|
||||||
- name: "Step 12: Create version branch from main"
|
- name: "Step 12: Create version branch from main"
|
||||||
if: steps.version.outputs.skip != 'true'
|
if: steps.version.outputs.skip != 'true'
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||||
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
||||||
BRANCH_NAME="version/${VERSION}"
|
BRANCH_NAME="version/${VERSION}"
|
||||||
@@ -436,7 +439,7 @@ jobs:
|
|||||||
if: steps.version.outputs.skip != 'true'
|
if: steps.version.outputs.skip != 'true'
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
php ${MOKO_CLI}/version_reset_dev.php \
|
php ${MOKO_CLI}/version_reset_dev.php \
|
||||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "${API_BASE}" \
|
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "${API_BASE}" \
|
||||||
--branch dev --path . 2>&1 || true
|
--branch dev --path . 2>&1 || true
|
||||||
@@ -462,5 +465,5 @@ jobs:
|
|||||||
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
|
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "| Branch | \`${{ steps.version.outputs.branch }}\` |" >> $GITHUB_STEP_SUMMARY
|
echo "| Branch | \`${{ steps.version.outputs.branch }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "| Tag | \`${{ steps.version.outputs.tag }}\` |" >> $GITHUB_STEP_SUMMARY
|
echo "| Tag | \`${{ steps.version.outputs.tag }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "| Release | [View](${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY
|
echo "| Release | [View](${MOKOGITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: Gitea.Workflow
|
||||||
# INGROUP: MokoStandards.Universal
|
# INGROUP: MokoStandards.Universal
|
||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoCLI
|
||||||
# PATH: /.mokogitea/workflows/branch-cleanup.yml
|
# PATH: /.mokogitea/workflows/branch-cleanup.yml
|
||||||
# VERSION: 01.00.00
|
# VERSION: 01.00.00
|
||||||
# BRIEF: Delete feature branches after PR merge
|
# BRIEF: Delete feature branches after PR merge
|
||||||
@@ -33,7 +33,8 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
BRANCH="${{ github.event.pull_request.head.ref }}"
|
BRANCH="${{ github.event.pull_request.head.ref }}"
|
||||||
API="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}/api/v1/repos/${{ github.repository }}/branches"
|
API="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}/api/v1/repos/${{ github.repository }}/branches"
|
||||||
ENCODED=$(php -r "echo rawurlencode('${BRANCH}');")
|
# URL-encode the branch name's slashes (no PHP dependency on the runner)
|
||||||
|
ENCODED=$(printf '%s' "${BRANCH}" | sed 's|/|%2F|g')
|
||||||
|
|
||||||
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X DELETE \
|
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X DELETE \
|
||||||
-H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \
|
-H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||||
|
|||||||
@@ -1,10 +1,65 @@
|
|||||||
# DISABLED — auto-release Step 11 recreates dev from main after every release.
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
# Cascade-dev is redundant and causes version conflicts when both main and dev
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
# have different version numbers in templateDetails.xml / manifest.xml.
|
# FILE INFORMATION
|
||||||
name: "Cascade Main → Dev (DISABLED)"
|
# DEFGROUP: Gitea.Workflow
|
||||||
on: workflow_dispatch
|
# INGROUP: mokocli.Release
|
||||||
|
# BRIEF: Reset dev to main after each release (cascade). Moved out of auto-release Step 11.
|
||||||
|
#
|
||||||
|
# +========================================================================+
|
||||||
|
# | CASCADE MAIN -> DEV |
|
||||||
|
# +========================================================================+
|
||||||
|
# | dev mirrors main and is reset on every release. |
|
||||||
|
# | delete+recreate cannot run against a protected branch, so this |
|
||||||
|
# | force-pushes main -> dev. The automation identity is force-push |
|
||||||
|
# | allowlisted on dev via branch-protection.yml. |
|
||||||
|
# | Runs AFTER the release workflow completes, so dev picks up the |
|
||||||
|
# | fully version-bumped + changelog-promoted main (not the pre-bump |
|
||||||
|
# | state). Force-push (not merge) => no version-file conflicts. |
|
||||||
|
# +========================================================================+
|
||||||
|
|
||||||
|
name: "Cascade Main -> Dev"
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_run:
|
||||||
|
workflows: ["Universal: Build & Release"]
|
||||||
|
types: [completed]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: cascade-dev-${{ github.repository }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
noop:
|
cascade:
|
||||||
|
name: Reset dev to main
|
||||||
|
if: >-
|
||||||
|
!startsWith(github.event.repository.name, 'Template-') &&
|
||||||
|
(github.event_name == 'workflow_dispatch' ||
|
||||||
|
github.event.workflow_run.conclusion == 'success')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- run: echo "Cascade disabled — auto-release handles dev recreation"
|
- name: Force dev to main
|
||||||
|
env:
|
||||||
|
TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
|
SERVER: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
|
REPO: ${{ github.repository }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
git init -q sync && cd sync
|
||||||
|
git config user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||||
|
git config user.name "gitea-actions[bot]"
|
||||||
|
git remote add origin "https://x-access-token:${TOKEN}@${SERVER#https://}/${REPO}.git"
|
||||||
|
git fetch -q --depth=1 origin main
|
||||||
|
|
||||||
|
if git ls-remote --exit-code --heads origin dev >/dev/null 2>&1; then
|
||||||
|
# dev exists -> force it to match main (dev is a mirror of main)
|
||||||
|
git push --force origin FETCH_HEAD:refs/heads/dev
|
||||||
|
echo "dev reset to main" >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
else
|
||||||
|
# dev missing (e.g. renamed to rc mid-cycle) -> recreate from main
|
||||||
|
git push origin FETCH_HEAD:refs/heads/dev
|
||||||
|
echo "dev created from main" >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
fi
|
||||||
|
|||||||
@@ -13,6 +13,12 @@
|
|||||||
name: "Generic: Project CI"
|
name: "Generic: Project CI"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- dev
|
||||||
|
- dev/**
|
||||||
|
- rc/**
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
@@ -26,6 +32,8 @@ jobs:
|
|||||||
lint:
|
lint:
|
||||||
name: Lint & Validate
|
name: Lint & Validate
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
# Skip on template repos (Template-*) — they hold placeholder scaffolding, not buildable source.
|
||||||
|
if: ${{ !startsWith(github.event.repository.name, 'Template-') }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@@ -124,6 +132,9 @@ jobs:
|
|||||||
name: Tests
|
name: Tests
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: lint
|
needs: lint
|
||||||
|
# Run only when lint succeeded; always() forces evaluation so a skipped
|
||||||
|
# lint (e.g. template repos) skips this job cleanly instead of hanging.
|
||||||
|
if: ${{ always() && needs.lint.result == 'success' }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
#
|
||||||
|
# FILE INFORMATION
|
||||||
|
# DEFGROUP: Gitea.Workflow
|
||||||
|
# INGROUP: mokocli.Universal
|
||||||
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoCLI
|
||||||
|
# PATH: /.mokogitea/workflows/ci-issue-reporter.yml
|
||||||
|
# VERSION: 01.00.00
|
||||||
|
# BRIEF: Reusable workflow — creates/updates a Gitea issue when a CI gate fails.
|
||||||
|
# Clones MokoCLI and runs cli/ci_issue_reporter.sh.
|
||||||
|
|
||||||
|
name: "Universal: CI Issue Reporter"
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
gate:
|
||||||
|
description: "CI gate name (e.g. PR Validation, Repository Health)"
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
details:
|
||||||
|
description: "Human-readable failure description"
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
severity:
|
||||||
|
description: "error or warning"
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
default: "error"
|
||||||
|
workflow:
|
||||||
|
description: "Workflow name for the issue title"
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
default: ""
|
||||||
|
secrets:
|
||||||
|
MOKOGITEA_TOKEN:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
env:
|
||||||
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
report:
|
||||||
|
name: "Report: ${{ inputs.gate }}"
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Clone MokoCLI
|
||||||
|
env:
|
||||||
|
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
|
run: |
|
||||||
|
MOKOGITEA_URL="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}"
|
||||||
|
git clone --depth 1 --filter=blob:none --sparse "${MOKOGITEA_URL}/MokoConsulting/MokoCLI.git" /tmp/mokocli
|
||||||
|
cd /tmp/mokocli && git sparse-checkout set cli/ci_issue_reporter.sh
|
||||||
|
|
||||||
|
- name: Report CI failure
|
||||||
|
env:
|
||||||
|
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
|
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
|
run: |
|
||||||
|
chmod +x /tmp/mokocli/cli/ci_issue_reporter.sh
|
||||||
|
/tmp/mokocli/cli/ci_issue_reporter.sh \
|
||||||
|
--gate "${{ inputs.gate }}" \
|
||||||
|
--details "${{ inputs.details }}" \
|
||||||
|
--severity "${{ inputs.severity }}" \
|
||||||
|
--workflow "${{ inputs.workflow }}"
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -21,7 +21,7 @@ permissions:
|
|||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
cleanup:
|
cleanup:
|
||||||
@@ -33,30 +33,30 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
token: ${{ secrets.GA_TOKEN }}
|
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
|
|
||||||
- name: Delete merged branches
|
- name: Delete merged branches
|
||||||
env:
|
env:
|
||||||
GA_TOKEN: ${{ secrets.GA_TOKEN }}
|
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
echo "=== Merged Branch Cleanup ==="
|
echo "=== Merged Branch Cleanup ==="
|
||||||
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
API="${MOKOGITEA_URL}/api/v1/repos/${{ github.repository }}"
|
||||||
|
|
||||||
# List branches via API
|
# List branches via API
|
||||||
BRANCHES=$(curl -sS -H "Authorization: token ${GA_TOKEN}" \
|
BRANCHES=$(curl -sS -H "Authorization: token ${MOKOGITEA_TOKEN}" \
|
||||||
"${API}/branches?limit=50" | jq -r '.[].name')
|
"${API}/branches?limit=50" | jq -r '.[].name')
|
||||||
|
|
||||||
DELETED=0
|
DELETED=0
|
||||||
for BRANCH in $BRANCHES; do
|
for BRANCH in $BRANCHES; do
|
||||||
# Skip protected branches
|
# Skip protected branches
|
||||||
case "$BRANCH" in
|
case "$BRANCH" in
|
||||||
main|master|develop|release/*|hotfix/*) continue ;;
|
main|master|dev|develop|rc|beta|alpha|release|release/*|production|stable|staging|hotfix/*|version/*) continue ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
# Check if branch is merged into main
|
# Check if branch is merged into main
|
||||||
if git merge-base --is-ancestor "origin/${BRANCH}" origin/main 2>/dev/null; then
|
if git merge-base --is-ancestor "origin/${BRANCH}" origin/main 2>/dev/null; then
|
||||||
echo " Deleting merged branch: ${BRANCH}"
|
echo " Deleting merged branch: ${BRANCH}"
|
||||||
curl -sS -X DELETE -H "Authorization: token ${GA_TOKEN}" \
|
curl -sS -X DELETE -H "Authorization: token ${MOKOGITEA_TOKEN}" \
|
||||||
"${API}/branches/${BRANCH}" 2>/dev/null || true
|
"${API}/branches/${BRANCH}" 2>/dev/null || true
|
||||||
DELETED=$((DELETED + 1))
|
DELETED=$((DELETED + 1))
|
||||||
fi
|
fi
|
||||||
@@ -66,20 +66,20 @@ jobs:
|
|||||||
|
|
||||||
- name: Clean old workflow runs
|
- name: Clean old workflow runs
|
||||||
env:
|
env:
|
||||||
GA_TOKEN: ${{ secrets.GA_TOKEN }}
|
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
echo "=== Workflow Run Cleanup ==="
|
echo "=== Workflow Run Cleanup ==="
|
||||||
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
API="${MOKOGITEA_URL}/api/v1/repos/${{ github.repository }}"
|
||||||
CUTOFF=$(date -d "30 days ago" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -v-30d +%Y-%m-%dT%H:%M:%SZ)
|
CUTOFF=$(date -d "30 days ago" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -v-30d +%Y-%m-%dT%H:%M:%SZ)
|
||||||
|
|
||||||
# Get old completed runs
|
# Get old completed runs
|
||||||
RUNS=$(curl -sS -H "Authorization: token ${GA_TOKEN}" \
|
RUNS=$(curl -sS -H "Authorization: token ${MOKOGITEA_TOKEN}" \
|
||||||
"${API}/actions/runs?status=completed&limit=50" | \
|
"${API}/actions/runs?status=completed&limit=50" | \
|
||||||
jq -r ".workflow_runs[] | select(.created_at < \"${CUTOFF}\") | .id" 2>/dev/null)
|
jq -r ".workflow_runs[] | select(.created_at < \"${CUTOFF}\") | .id" 2>/dev/null)
|
||||||
|
|
||||||
DELETED=0
|
DELETED=0
|
||||||
for RUN_ID in $RUNS; do
|
for RUN_ID in $RUNS; do
|
||||||
curl -sS -X DELETE -H "Authorization: token ${GA_TOKEN}" \
|
curl -sS -X DELETE -H "Authorization: token ${MOKOGITEA_TOKEN}" \
|
||||||
"${API}/actions/runs/${RUN_ID}" 2>/dev/null || true
|
"${API}/actions/runs/${RUN_ID}" 2>/dev/null || true
|
||||||
DELETED=$((DELETED + 1))
|
DELETED=$((DELETED + 1))
|
||||||
done
|
done
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: Gitea.Workflow
|
||||||
# INGROUP: MokoStandards.Security
|
# INGROUP: MokoStandards.Security
|
||||||
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
|
||||||
# PATH: /templates/workflows/gitleaks.yml.template
|
# PATH: /templates/workflows/gitleaks.yml.template
|
||||||
# VERSION: 01.00.00
|
# VERSION: 01.00.00
|
||||||
# BRIEF: Secret scanning — detect leaked credentials, API keys, and tokens
|
# BRIEF: Secret scanning — detect leaked credentials, API keys, and tokens
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ permissions:
|
|||||||
issues: write
|
issues: write
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
create-branch:
|
create-branch:
|
||||||
@@ -28,8 +28,8 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Create branch and comment
|
- name: Create branch and comment
|
||||||
run: |
|
run: |
|
||||||
TOKEN="${{ secrets.GA_TOKEN }}"
|
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||||
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
API="${MOKOGITEA_URL}/api/v1/repos/${{ github.repository }}"
|
||||||
ISSUE_NUM="${{ github.event.issue.number }}"
|
ISSUE_NUM="${{ github.event.issue.number }}"
|
||||||
ISSUE_TITLE="${{ github.event.issue.title }}"
|
ISSUE_TITLE="${{ github.event.issue.title }}"
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ jobs:
|
|||||||
echo "Created branch: ${BRANCH}"
|
echo "Created branch: ${BRANCH}"
|
||||||
|
|
||||||
# Comment on issue with branch link
|
# Comment on issue with branch link
|
||||||
REPO_URL="${GITEA_URL}/${{ github.repository }}"
|
REPO_URL="${MOKOGITEA_URL}/${{ github.repository }}"
|
||||||
BODY="Branch created: [\`${BRANCH}\`](${REPO_URL}/src/branch/${BRANCH})\n\n\`\`\`bash\ngit fetch origin\ngit checkout ${BRANCH}\n\`\`\`"
|
BODY="Branch created: [\`${BRANCH}\`](${REPO_URL}/src/branch/${BRANCH})\n\n\`\`\`bash\ngit fetch origin\ngit checkout ${BRANCH}\n\`\`\`"
|
||||||
|
|
||||||
curl -sf -X POST \
|
curl -sf -X POST \
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ name: "Universal: Notifications"
|
|||||||
on:
|
on:
|
||||||
workflow_run:
|
workflow_run:
|
||||||
workflows:
|
workflows:
|
||||||
- "Joomla Build & Release"
|
- "Universal: Build & Release"
|
||||||
- "Joomla Extension CI"
|
- "Joomla: Extension CI"
|
||||||
- "Deploy"
|
- "Generic: Project CI"
|
||||||
types:
|
types:
|
||||||
- completed
|
- completed
|
||||||
|
|
||||||
|
|||||||
+527
-534
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@
|
|||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: Gitea.Workflow
|
||||||
# INGROUP: mokocli.Validation
|
# INGROUP: mokocli.Validation
|
||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoCLI
|
||||||
# PATH: /templates/workflows/joomla/pr-metadata-check.yml.template
|
# PATH: /templates/workflows/joomla/pr-metadata-check.yml.template
|
||||||
# VERSION: 01.00.00
|
# VERSION: 01.00.00
|
||||||
# BRIEF: Validate MokoGitea metadata matches Joomla extension manifest on PRs
|
# BRIEF: Validate MokoGitea metadata matches Joomla extension manifest on PRs
|
||||||
@@ -20,7 +20,7 @@ permissions:
|
|||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
|
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
|
||||||
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
|
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
|
||||||
|
|
||||||
@@ -28,6 +28,8 @@ jobs:
|
|||||||
validate-metadata:
|
validate-metadata:
|
||||||
name: "Validate Joomla Metadata"
|
name: "Validate Joomla Metadata"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
# Skip on template repos (Template-*) — they have no real extension manifest to validate.
|
||||||
|
if: ${{ !startsWith(github.event.repository.name, 'Template-') }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@@ -47,7 +49,7 @@ jobs:
|
|||||||
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
|
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
|
fi
|
||||||
rm -rf /tmp/mokocli
|
rm -rf /tmp/mokocli
|
||||||
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git
|
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoCLI.git
|
||||||
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli
|
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli
|
||||||
cd /tmp/mokocli && composer install --no-dev --no-interaction --quiet
|
cd /tmp/mokocli && composer install --no-dev --no-interaction --quiet
|
||||||
echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV
|
echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV
|
||||||
@@ -55,14 +57,14 @@ jobs:
|
|||||||
|
|
||||||
- name: Validate metadata against Joomla manifest
|
- name: Validate metadata against Joomla manifest
|
||||||
env:
|
env:
|
||||||
GITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
php ${MOKO_CLI}/joomla_metadata_validate.php \
|
php ${MOKO_CLI}/joomla_metadata_validate.php \
|
||||||
--path . \
|
--path . \
|
||||||
--token "${GITEA_TOKEN}" \
|
--token "${MOKOGITEA_TOKEN}" \
|
||||||
--org "${GITEA_ORG}" \
|
--org "${GITEA_ORG}" \
|
||||||
--repo "${GITEA_REPO}" \
|
--repo "${GITEA_REPO}" \
|
||||||
--api-base "${GITEA_URL}/api/v1" \
|
--api-base "${MOKOGITEA_URL}/api/v1" \
|
||||||
--ci
|
--ci
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
|
|||||||
@@ -5,9 +5,9 @@
|
|||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: Gitea.Workflow
|
||||||
# INGROUP: mokocli.Release
|
# INGROUP: mokocli.Release
|
||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoCLI
|
||||||
# PATH: /templates/workflows/universal/pre-release.yml.template
|
# PATH: /templates/workflows/universal/pre-release.yml.template
|
||||||
# VERSION: 05.01.00
|
# VERSION: 05.02.00
|
||||||
# BRIEF: Auto pre-release on push to dev/alpha/beta/rc branches
|
# BRIEF: Auto pre-release on push to dev/alpha/beta/rc branches
|
||||||
|
|
||||||
name: "Universal: Pre-Release"
|
name: "Universal: Pre-Release"
|
||||||
@@ -20,6 +20,7 @@ on:
|
|||||||
- 'patch/**'
|
- 'patch/**'
|
||||||
- 'hotfix/**'
|
- 'hotfix/**'
|
||||||
- 'bugfix/**'
|
- 'bugfix/**'
|
||||||
|
- 'chore/**'
|
||||||
- alpha
|
- alpha
|
||||||
- beta
|
- beta
|
||||||
- rc
|
- rc
|
||||||
@@ -47,9 +48,13 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
name: "Build Pre-Release (${{ inputs.stability || github.ref_name }})"
|
name: "Build Pre-Release (${{ inputs.stability || github.ref_name }})"
|
||||||
runs-on: release
|
runs-on: release
|
||||||
|
# Skip on template repos (Template-*) — they scaffold other repos and do not release.
|
||||||
if: >-
|
if: >-
|
||||||
github.event_name == 'workflow_dispatch' ||
|
!startsWith(github.event.repository.name, 'Template-') &&
|
||||||
github.event_name == 'push'
|
(
|
||||||
|
github.event_name == 'workflow_dispatch' ||
|
||||||
|
github.event_name == 'push'
|
||||||
|
)
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@@ -58,6 +63,11 @@ jobs:
|
|||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
ref: ${{ github.ref_name }}
|
ref: ${{ github.ref_name }}
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Update submodules to main
|
||||||
|
run: |
|
||||||
|
git submodule foreach --quiet 'git checkout main && git pull --quiet origin main' 2>/dev/null || true
|
||||||
|
|
||||||
- name: Setup mokocli tools
|
- name: Setup mokocli tools
|
||||||
env:
|
env:
|
||||||
@@ -74,7 +84,7 @@ jobs:
|
|||||||
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
|
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
|
fi
|
||||||
rm -rf /tmp/mokocli
|
rm -rf /tmp/mokocli
|
||||||
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git
|
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoCLI.git
|
||||||
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli
|
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli
|
||||||
cd /tmp/mokocli && composer install --no-dev --no-interaction --quiet
|
cd /tmp/mokocli && composer install --no-dev --no-interaction --quiet
|
||||||
echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV
|
echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV
|
||||||
@@ -87,8 +97,20 @@ jobs:
|
|||||||
php ${MOKO_CLI}/platform_detect.php --path . --github-output 2>/dev/null || true
|
php ${MOKO_CLI}/platform_detect.php --path . --github-output 2>/dev/null || true
|
||||||
php ${MOKO_CLI}/manifest_read.php --path . --github-output
|
php ${MOKO_CLI}/manifest_read.php --path . --github-output
|
||||||
|
|
||||||
|
- name: Check platform eligibility (Joomla only)
|
||||||
|
id: eligibility
|
||||||
|
run: |
|
||||||
|
PLATFORM="${{ steps.platform.outputs.platform }}"
|
||||||
|
if [[ "$PLATFORM" == joomla* ]] || [[ "$PLATFORM" == "joomla" ]]; then
|
||||||
|
echo "proceed=true" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "proceed=false" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "::notice::Platform '$PLATFORM' — non-Joomla, skipping pre-release auto-bump"
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Resolve metadata and bump version
|
- name: Resolve metadata and bump version
|
||||||
id: meta
|
id: meta
|
||||||
|
if: steps.eligibility.outputs.proceed == 'true'
|
||||||
run: |
|
run: |
|
||||||
# Auto-detect stability from branch name on push, or use input on dispatch
|
# Auto-detect stability from branch name on push, or use input on dispatch
|
||||||
if [ "${{ github.event_name }}" = "push" ]; then
|
if [ "${{ github.event_name }}" = "push" ]; then
|
||||||
@@ -165,6 +187,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Create release
|
- name: Create release
|
||||||
id: release
|
id: release
|
||||||
|
if: steps.eligibility.outputs.proceed == 'true'
|
||||||
run: |
|
run: |
|
||||||
TAG="${{ steps.meta.outputs.tag }}"
|
TAG="${{ steps.meta.outputs.tag }}"
|
||||||
VERSION="${{ steps.meta.outputs.version }}"
|
VERSION="${{ steps.meta.outputs.version }}"
|
||||||
@@ -175,6 +198,7 @@ jobs:
|
|||||||
--repo "${GITEA_REPO}" --branch "${{ github.ref_name }}" --prerelease
|
--repo "${GITEA_REPO}" --branch "${{ github.ref_name }}" --prerelease
|
||||||
|
|
||||||
- name: Update release notes from CHANGELOG.md
|
- name: Update release notes from CHANGELOG.md
|
||||||
|
if: steps.eligibility.outputs.proceed == 'true'
|
||||||
run: |
|
run: |
|
||||||
TAG="${{ steps.meta.outputs.tag }}"
|
TAG="${{ steps.meta.outputs.tag }}"
|
||||||
VERSION="${{ steps.meta.outputs.version }}"
|
VERSION="${{ steps.meta.outputs.version }}"
|
||||||
@@ -211,6 +235,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build package and upload
|
- name: Build package and upload
|
||||||
id: package
|
id: package
|
||||||
|
if: steps.eligibility.outputs.proceed == 'true'
|
||||||
run: |
|
run: |
|
||||||
VERSION="${{ steps.meta.outputs.version }}"
|
VERSION="${{ steps.meta.outputs.version }}"
|
||||||
TAG="${{ steps.meta.outputs.tag }}"
|
TAG="${{ steps.meta.outputs.tag }}"
|
||||||
@@ -224,6 +249,7 @@ jobs:
|
|||||||
# No need to build, commit, or sync updates.xml from workflows
|
# No need to build, commit, or sync updates.xml from workflows
|
||||||
|
|
||||||
- name: "Delete lesser pre-release channels (cascade)"
|
- name: "Delete lesser pre-release channels (cascade)"
|
||||||
|
if: steps.eligibility.outputs.proceed == 'true'
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: Gitea.Workflow
|
||||||
# INGROUP: mokocli.Universal
|
# INGROUP: mokocli.Universal
|
||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoCLI
|
||||||
# PATH: /.mokogitea/workflows/rc-revert.yml
|
# PATH: /.mokogitea/workflows/rc-revert.yml
|
||||||
# VERSION: 09.23.00
|
# VERSION: 09.23.00
|
||||||
# BRIEF: Rename rc/ branch back to dev/ when PR is closed without merge
|
# BRIEF: Rename rc/ branch back to dev/ when PR is closed without merge
|
||||||
@@ -29,12 +29,20 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Rename branch
|
- name: Rename branch
|
||||||
|
env:
|
||||||
|
BRANCH: ${{ github.event.pull_request.head.ref }}
|
||||||
|
REPO: ${{ github.repository }}
|
||||||
|
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
|
TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
BRANCH="${{ github.event.pull_request.head.ref }}"
|
set -euo pipefail
|
||||||
|
# BRANCH is attacker-controlled (PR head ref). Strict allowlist before ANY use.
|
||||||
|
if ! printf '%s' "$BRANCH" | grep -Eq '^rc/[A-Za-z0-9._/-]+$'; then
|
||||||
|
echo "::error::Refusing unsafe branch name: $BRANCH"; exit 1
|
||||||
|
fi
|
||||||
SUFFIX="${BRANCH#rc/}"
|
SUFFIX="${BRANCH#rc/}"
|
||||||
DEV_BRANCH="dev/${SUFFIX}"
|
DEV_BRANCH="dev/${SUFFIX}"
|
||||||
API="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}/api/v1/repos/${{ github.repository }}/branches"
|
API="${GITEA_URL}/api/v1/repos/${REPO}/branches"
|
||||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
|
||||||
|
|
||||||
# Create dev/ branch from rc/ branch
|
# Create dev/ branch from rc/ branch
|
||||||
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X POST \
|
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X POST \
|
||||||
@@ -42,25 +50,22 @@ jobs:
|
|||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d "{\"new_branch_name\": \"${DEV_BRANCH}\", \"old_branch_name\": \"${BRANCH}\"}" \
|
-d "{\"new_branch_name\": \"${DEV_BRANCH}\", \"old_branch_name\": \"${BRANCH}\"}" \
|
||||||
"${API}" 2>/dev/null || true)
|
"${API}" 2>/dev/null || true)
|
||||||
|
|
||||||
if [ "$STATUS" = "201" ]; then
|
if [ "$STATUS" = "201" ]; then
|
||||||
echo "Created branch: ${DEV_BRANCH}" >> $GITHUB_STEP_SUMMARY
|
echo "Created branch: ${DEV_BRANCH}" >> "$GITHUB_STEP_SUMMARY"
|
||||||
else
|
else
|
||||||
echo "::error::Failed to create ${DEV_BRANCH} from ${BRANCH} (HTTP ${STATUS})"
|
echo "::error::Failed to create ${DEV_BRANCH} from ${BRANCH} (HTTP ${STATUS})"; exit 1
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Delete rc/ branch
|
# Read BRANCH from the environment inside PHP (getenv, no string interpolation -> no PHP injection)
|
||||||
ENCODED=$(php -r "echo rawurlencode('${BRANCH}');")
|
ENCODED=$(php -r 'echo rawurlencode(getenv("BRANCH"));')
|
||||||
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X DELETE \
|
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X DELETE \
|
||||||
-H "Authorization: token ${TOKEN}" \
|
-H "Authorization: token ${TOKEN}" \
|
||||||
"${API}/${ENCODED}" 2>/dev/null || true)
|
"${API}/${ENCODED}" 2>/dev/null || true)
|
||||||
|
|
||||||
if [ "$STATUS" = "204" ]; then
|
if [ "$STATUS" = "204" ]; then
|
||||||
echo "Deleted branch: ${BRANCH}" >> $GITHUB_STEP_SUMMARY
|
echo "Deleted branch: ${BRANCH}" >> "$GITHUB_STEP_SUMMARY"
|
||||||
else
|
else
|
||||||
echo "::warning::Failed to delete ${BRANCH} (HTTP ${STATUS})"
|
echo "::warning::Failed to delete ${BRANCH} (HTTP ${STATUS})"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "### RC Reverted" >> $GITHUB_STEP_SUMMARY
|
echo "### RC Reverted" >> "$GITHUB_STEP_SUMMARY"
|
||||||
echo "${BRANCH} → ${DEV_BRANCH}" >> $GITHUB_STEP_SUMMARY
|
echo "${BRANCH} → ${DEV_BRANCH}" >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,82 +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/MokoStandards
|
|
||||||
# PATH: /.gitea/workflows/security-audit.yml
|
|
||||||
# VERSION: 01.00.00
|
|
||||||
# BRIEF: Dependency vulnerability scanning for composer and npm packages
|
|
||||||
|
|
||||||
name: "Universal: Security Audit"
|
|
||||||
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: '0 6 * * 1' # Weekly on Monday at 06:00 UTC
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
paths:
|
|
||||||
- 'composer.json'
|
|
||||||
- 'composer.lock'
|
|
||||||
- 'package.json'
|
|
||||||
- 'package-lock.json'
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
env:
|
|
||||||
NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }}
|
|
||||||
NTFY_TOPIC: ${{ vars.NTFY_TOPIC || 'gitea-security' }}
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
audit:
|
|
||||||
name: Dependency Audit
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Composer audit
|
|
||||||
if: hashFiles('composer.lock') != ''
|
|
||||||
run: |
|
|
||||||
echo "=== Composer Security Audit ==="
|
|
||||||
if ! command -v composer &> /dev/null; then
|
|
||||||
sudo apt-get update -qq
|
|
||||||
sudo apt-get install -y -qq php-cli composer >/dev/null 2>&1
|
|
||||||
fi
|
|
||||||
composer audit --format=plain 2>&1 | tee /tmp/composer-audit.txt
|
|
||||||
RESULT=$?
|
|
||||||
if [ $RESULT -ne 0 ]; then
|
|
||||||
echo "::warning::Composer vulnerabilities found"
|
|
||||||
echo "composer_vulnerable=true" >> "$GITHUB_ENV"
|
|
||||||
else
|
|
||||||
echo "No known vulnerabilities in composer dependencies"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: NPM audit
|
|
||||||
if: hashFiles('package-lock.json') != ''
|
|
||||||
run: |
|
|
||||||
echo "=== NPM Security Audit ==="
|
|
||||||
npm audit --production 2>&1 | tee /tmp/npm-audit.txt || true
|
|
||||||
if npm audit --production 2>&1 | grep -q "found 0 vulnerabilities"; then
|
|
||||||
echo "No known vulnerabilities in npm dependencies"
|
|
||||||
else
|
|
||||||
echo "::warning::NPM vulnerabilities found"
|
|
||||||
echo "npm_vulnerable=true" >> "$GITHUB_ENV"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Notify on vulnerabilities
|
|
||||||
if: env.composer_vulnerable == 'true' || env.npm_vulnerable == 'true'
|
|
||||||
run: |
|
|
||||||
REPO="${{ github.event.repository.name }}"
|
|
||||||
curl -sS \
|
|
||||||
-H "Title: ${REPO} has vulnerable dependencies" \
|
|
||||||
-H "Tags: lock,warning" \
|
|
||||||
-H "Priority: high" \
|
|
||||||
-d "Security audit found vulnerabilities. Review dependency updates." \
|
|
||||||
"${NTFY_URL}/${NTFY_TOPIC}" || true
|
|
||||||
@@ -48,7 +48,7 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.MOKOGITEA_TOKEN || secrets.GA_TOKEN || github.token }}
|
token: ${{ secrets.MOKOGITEA_TOKEN || github.token }}
|
||||||
ref: ${{ inputs.branch || github.ref }}
|
ref: ${{ inputs.branch || github.ref }}
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: Gitea.Workflow
|
||||||
# INGROUP: mokocli.Universal
|
# INGROUP: mokocli.Universal
|
||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoCLI
|
||||||
# PATH: /.mokogitea/workflows/workflow-sync-trigger.yml
|
# PATH: /.mokogitea/workflows/workflow-sync-trigger.yml
|
||||||
# VERSION: 01.01.00
|
# VERSION: 01.01.00
|
||||||
# BRIEF: Trigger workflow sync to live repos when a PR is merged to main
|
# BRIEF: Trigger workflow sync to live repos when a PR is merged to main
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
name: "Universal: Workflow Sync Trigger"
|
name: "Universal: Workflow Sync Trigger"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [closed]
|
types: [closed]
|
||||||
branches:
|
branches:
|
||||||
@@ -26,8 +27,10 @@ jobs:
|
|||||||
name: Sync workflows to live repos
|
name: Sync workflows to live repos
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: >-
|
if: >-
|
||||||
github.event.pull_request.merged == true &&
|
startsWith(github.event.repository.name, 'Template-') &&
|
||||||
!contains(github.event.pull_request.title, '[skip sync]')
|
(github.event_name == 'workflow_dispatch' ||
|
||||||
|
(github.event.pull_request.merged == true &&
|
||||||
|
!contains(github.event.pull_request.title, '[skip sync]')))
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Determine platform from repo name
|
- name: Determine platform from repo name
|
||||||
@@ -45,19 +48,18 @@ jobs:
|
|||||||
echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT"
|
echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT"
|
||||||
echo "Platform: ${PLATFORM:-all}"
|
echo "Platform: ${PLATFORM:-all}"
|
||||||
|
|
||||||
- name: Setup PHP
|
|
||||||
run: |
|
|
||||||
if ! command -v php &> /dev/null; then
|
|
||||||
sudo apt-get update -qq
|
|
||||||
sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Clone mokocli
|
- name: Clone mokocli
|
||||||
env:
|
env:
|
||||||
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
GITEA_URL="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}"
|
MOKOGITEA_URL="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}"
|
||||||
git clone --depth 1 "${GITEA_URL}/MokoConsulting/mokocli.git" /tmp/mokocli
|
git clone --depth 1 "${MOKOGITEA_URL}/MokoConsulting/MokoCLI.git" /tmp/mokocli
|
||||||
|
|
||||||
|
- name: Install PHP
|
||||||
|
run: |
|
||||||
|
if ! command -v php &> /dev/null; then
|
||||||
|
apt-get update -qq && apt-get install -y -qq php-cli php-json php-curl > /dev/null 2>&1
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -8,4 +8,5 @@
|
|||||||
- HCL definition files removed -- Template repos are now the canonical source
|
- HCL definition files removed -- Template repos are now the canonical source
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
- CI: gate build/CI/release/version workflows so they skip template repos; smart-gate the sync workflows to run only on Template-* repos; normalize mokocli/MokoCLI refs and drop the stale mokoconsulting-tech org slug.
|
||||||
- `branch-cleanup.yml`: auto-delete merged feature branches after PR merge
|
- `branch-cleanup.yml`: auto-delete merged feature branches after PR merge
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ This file provides guidance to Claude Code when working with this repository.
|
|||||||
| **Default branch** | main |
|
| **Default branch** | main |
|
||||||
| **License** | GPL-3.0-or-later |
|
| **License** | GPL-3.0-or-later |
|
||||||
| **Wiki** | [Template-Joomla Wiki](https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla/wiki) |
|
| **Wiki** | [Template-Joomla Wiki](https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla/wiki) |
|
||||||
| **Standards** | [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home) |
|
| **Standards** | [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/MokoCLI/wiki/Home) |
|
||||||
|
|
||||||
## Common Commands
|
## Common Commands
|
||||||
|
|
||||||
@@ -44,4 +44,4 @@ This is a project template. Files are copied when creating new repos.
|
|||||||
- **Branch strategy**: develop on `dev`, merge to `main` for release
|
- **Branch strategy**: develop on `dev`, merge to `main` for release
|
||||||
- **Minification**: handled at build time (CI) and runtime (MokoMinifyHelper for Joomla templates)
|
- **Minification**: handled at build time (CI) and runtime (MokoMinifyHelper for Joomla templates)
|
||||||
- **Wiki**: documentation lives in the Gitea wiki, not in `docs/` files
|
- **Wiki**: documentation lives in the Gitea wiki, not in `docs/` files
|
||||||
- **Standards**: this repo follows [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home)
|
- **Standards**: this repo follows [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/MokoCLI/wiki/Home)
|
||||||
|
|||||||
@@ -1,70 +1,77 @@
|
|||||||
# Template-Joomla
|
# Template-Joomla
|
||||||
|
|
||||||
Unified scaffolding templates for all Joomla extension types.
|
Unified scaffolding templates for all Joomla extension types — component, template, module, plugin, package, and library.
|
||||||
|
|
||||||
## Structure
|
## Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
.gitea/workflows/ ← Standard 10-workflow CI/CD suite
|
.mokogitea/workflows/ ← CI/CD workflow suite (lint, validate, release)
|
||||||
types/
|
samples/
|
||||||
plugin/ ← Joomla plugin scaffold
|
manifest/ ← Reference manifest XML for each extension type
|
||||||
template/ ← Joomla site template scaffold (full src/)
|
component_sample.xml
|
||||||
module/ ← Joomla module scaffold
|
template_sample.xml
|
||||||
component/ ← Joomla component scaffold
|
module_sample.xml
|
||||||
package/ ← Joomla package scaffold
|
plugin_sample.xml
|
||||||
library/ ← Joomla library scaffold
|
package_sample.xml
|
||||||
|
library_sample.xml
|
||||||
|
script/ ← Reference install/update script (script.php) templates
|
||||||
|
script_component.php
|
||||||
|
script_module.php
|
||||||
|
script_plugin.php
|
||||||
|
script_package.php
|
||||||
|
source/ ← Your extension source lives here (CI scans this dir)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
1. Create a new repo from this template (or clone manually)
|
1. Create a new repo from this template (or clone manually).
|
||||||
2. Copy the relevant `types/<type>/` content to your project root
|
2. Build your extension under `source/`.
|
||||||
3. Customize the manifest XML, namespace, and source files
|
3. Copy the matching manifest from `samples/manifest/` to `source/`, rename it, and
|
||||||
4. Workflows are already configured — push to trigger CI
|
replace the placeholder name, namespace, and version.
|
||||||
|
4. If your extension needs install/update logic, copy the matching
|
||||||
|
`samples/script/` file and replace the `{REPONAME}`/`{PACKAGENAME}` placeholders.
|
||||||
|
5. Push — the workflows validate the manifest, source, SQL, language keys, and build.
|
||||||
|
|
||||||
## Shared Files
|
## Samples
|
||||||
|
|
||||||
The root contains files shared by all extension types:
|
`samples/` holds read-only reference material — do not build against it directly.
|
||||||
- `.gitea/workflows/` — CI/CD (auto-release, ci-joomla, pr-check, etc.)
|
|
||||||
- `.editorconfig`, `.gitignore`, `.gitattributes` — editor/git config
|
|
||||||
- `composer.json`, `phpstan.neon`, `Makefile` — build tooling
|
|
||||||
- `updates.xml` — Joomla update server manifest template
|
|
||||||
- `LICENSE`, `SECURITY.md`, `CONTRIBUTING.md` — standard docs
|
|
||||||
|
|
||||||
## Workflow Suite (10 workflows)
|
- **`manifest/`** — a well-formed `<extension>` manifest for each of the six
|
||||||
|
Joomla extension types, showing the expected structure, namespace declaration,
|
||||||
|
media/SQL wiring, and update-server hookup.
|
||||||
|
- **`script/`** — the standard `script.php` install/uninstall/update handler
|
||||||
|
pattern shared across Moko extension repos. Copy, then replace `{REPONAME}`
|
||||||
|
(UPPERCASE) and `{PACKAGENAME}` (e.g. `com_myextension`).
|
||||||
|
|
||||||
| Workflow | Purpose |
|
## CI/CD
|
||||||
|----------|---------|
|
|
||||||
| `auto-release.yml` | Stable release on PR merge to main |
|
|
||||||
| `pre-release.yml` | Manual dev/alpha/beta/rc builds |
|
|
||||||
| `ci-joomla.yml` | PHP lint, PHPStan, coding standards |
|
|
||||||
| `pr-check.yml` | PR gate: manifest + build validation |
|
|
||||||
| `deploy-manual.yml` | Manual SFTP deploy |
|
|
||||||
| `repo-health.yml` | Structure compliance checks |
|
|
||||||
| `update-server.yml` | updates.xml validation |
|
|
||||||
| `security-audit.yml` | Dependency vulnerability scanning |
|
|
||||||
| `notify.yml` | ntfy notifications |
|
|
||||||
| `cleanup.yml` | Merged branch + old run cleanup |
|
|
||||||
|
|
||||||
## Version Policy
|
`.mokogitea/workflows/ci-joomla.yml` validates every push and PR against `source/`:
|
||||||
|
|
||||||
- **Stable** (PR merge to main): Minor bump (`03.00.07` -> `03.01.00`)
|
| Check | What it verifies |
|
||||||
- **Pre-release** (manual): Patch bump (`03.00.07` -> `03.00.08`)
|
|-------|------------------|
|
||||||
|
| Manifest | Well-formed XML, required elements, version |
|
||||||
|
| SQL | `#__` prefixes, `IF [NOT] EXISTS`, `ENGINE=InnoDB`, `utf8mb4`, install/uninstall pairing, version-named update files |
|
||||||
|
| Language keys | Keys used in code are defined; orphan (unused) keys flagged |
|
||||||
|
| PHP CodeSniffer | Joomla coding standard (falls back to PSR-12) |
|
||||||
|
| Security | No direct superglobals, raw SQL in `setQuery()`, `eval()`, or missing `_JEXEC` guard |
|
||||||
|
| updates.xml | Well-formed, required `<update>` children, `<targetplatform>` |
|
||||||
|
| Assets | `joomla.asset.json` is valid and its URIs exist |
|
||||||
|
| MVC naming | View/Controller/Model/Table classes follow suffix conventions |
|
||||||
|
| Router | Components ship a Router implementing the router interface |
|
||||||
|
| ACL | `access.xml` action keys are defined in language files |
|
||||||
|
| Webservices | Webservices plugins register API routes |
|
||||||
|
| ZIP dry-run | Every file/folder the manifest references actually exists |
|
||||||
|
| JS/CSS | Syntax + balanced braces (ESLint/Stylelint if configured) |
|
||||||
|
|
||||||
## Release Cascade
|
## Standards
|
||||||
|
|
||||||
Higher releases auto-delete lower pre-release channels:
|
This repo follows [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/MokoCLI/wiki/Home).
|
||||||
- stable -> all | rc -> beta,alpha,dev | beta -> alpha,dev | alpha -> dev
|
Documentation lives in the [Template-Joomla Wiki](https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla/wiki).
|
||||||
|
|
||||||
## Platform Type
|
|
||||||
|
|
||||||
`.mokostandards` platform: `joomla-template` or `waas-component`
|
|
||||||
|
|
||||||
## Canonical Source
|
|
||||||
|
|
||||||
This repo is the **single source of truth** for Joomla workflows and scaffolding.
|
|
||||||
Sync to production repos by copying `.gitea/workflows/` from here.
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
GPL-3.0-or-later - Moko Consulting
|
GPL-3.0-or-later — Moko Consulting
|
||||||
|
|
||||||
|
## Template CI Notes
|
||||||
|
|
||||||
|
Build/CI/release/version workflows are guarded to skip template repos (Template-*) and only run in repos created from them.
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<extension type="component" method="upgrade">
|
||||||
|
<name>Moko Component</name>
|
||||||
|
<author>Moko Consulting</author>
|
||||||
|
<creationDate>July 2026</creationDate>
|
||||||
|
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
|
||||||
|
<license>GNU General Public License version 3 or later; see LICENSE</license>
|
||||||
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
<description>A premium core business engine component engineered by Moko Consulting for Joomla 6 platform solutions.</description>
|
||||||
|
|
||||||
|
<namespace path="src">MokoConsulting\Component\MyExtension</namespace>
|
||||||
|
|
||||||
|
<files folder="site">
|
||||||
|
<folder>src</folder>
|
||||||
|
<folder>tmpl</folder>
|
||||||
|
</files>
|
||||||
|
|
||||||
|
<media destination="com_myextension" folder="media">
|
||||||
|
<filename>joomla.asset.json</filename>
|
||||||
|
<folder>css</folder>
|
||||||
|
<folder>js</folder>
|
||||||
|
</media>
|
||||||
|
|
||||||
|
<administration>
|
||||||
|
<menu link="option=com_myextension">Moko Component</menu>
|
||||||
|
<files folder="admin">
|
||||||
|
<folder>services</folder>
|
||||||
|
<folder>src</folder>
|
||||||
|
<folder>tmpl</folder>
|
||||||
|
<filename>myextension.xml</filename>
|
||||||
|
</files>
|
||||||
|
|
||||||
|
<languages folder="admin">
|
||||||
|
<language tag="en-GB">language/en-GB/com_myextension.ini</language>
|
||||||
|
<language tag="en-GB">language/en-GB/com_myextension.sys.ini</language>
|
||||||
|
<language tag="en-US">language/en-US/com_myextension.ini</language>
|
||||||
|
<language tag="en-US">language/en-US/com_myextension.sys.ini</language>
|
||||||
|
</languages>
|
||||||
|
</administration>
|
||||||
|
|
||||||
|
<updateservers>
|
||||||
|
<server type="extension" priority="1" name="Moko Consulting Updates" dlid="id">
|
||||||
|
https://update.mokoconsulting.tech/com_myextension.xml
|
||||||
|
</server>
|
||||||
|
</updateservers>
|
||||||
|
</extension>
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<extension type="library" method="upgrade">
|
||||||
|
<name>Moko Shared Core Library</name>
|
||||||
|
<libraryname>mokolibrary</libraryname>
|
||||||
|
<author>Moko Consulting</author>
|
||||||
|
<creationDate>July 2026</creationDate>
|
||||||
|
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
|
||||||
|
<license>GNU General Public License version 3 or later; see LICENSE</license>
|
||||||
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
<description>Centralized helper framework and base API abstraction wrappers used across all active Moko Consulting extension matrices.</description>
|
||||||
|
|
||||||
|
<namespace path="src">MokoConsulting\Library\MokoLibrary</namespace>
|
||||||
|
|
||||||
|
<files>
|
||||||
|
<folder>src</folder>
|
||||||
|
</files>
|
||||||
|
|
||||||
|
<languages>
|
||||||
|
<language tag="en-GB">language/en-GB/lib_mokolibrary.sys.ini</language>
|
||||||
|
<language tag="en-US">language/en-US/lib_mokolibrary.sys.ini</language>
|
||||||
|
</languages>
|
||||||
|
|
||||||
|
<updateservers>
|
||||||
|
<server type="extension" priority="1" name="Moko Consulting Updates" dlid="id">
|
||||||
|
https://update.mokoconsulting.tech/lib_mokolibrary.xml
|
||||||
|
</server>
|
||||||
|
</updateservers>
|
||||||
|
</extension>
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<extension type="module" client="site" method="upgrade">
|
||||||
|
<name>Moko Dynamic Content Showcase</name>
|
||||||
|
<author>Moko Consulting</author>
|
||||||
|
<creationDate>July 2026</creationDate>
|
||||||
|
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
|
||||||
|
<license>GNU General Public License version 3 or later; see LICENSE</license>
|
||||||
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
<description>A high-performance asynchronous frontend display module designed to cleanly inject dynamic layout matrices into module zones.</description>
|
||||||
|
|
||||||
|
<namespace path="src">MokoConsulting\Module\MokoModule</namespace>
|
||||||
|
|
||||||
|
<files>
|
||||||
|
<folder module="mod_mokomodule">services</folder>
|
||||||
|
<folder>src</folder>
|
||||||
|
<folder>tmpl</folder>
|
||||||
|
<filename>mod_mokomodule.xml</filename>
|
||||||
|
</files>
|
||||||
|
|
||||||
|
<languages>
|
||||||
|
<language tag="en-GB">language/en-GB/mod_mokomodule.ini</language>
|
||||||
|
<language tag="en-GB">language/en-GB/mod_mokomodule.sys.ini</language>
|
||||||
|
<language tag="en-US">language/en-US/mod_mokomodule.ini</language>
|
||||||
|
<language tag="en-US">language/en-US/mod_mokomodule.sys.ini</language>
|
||||||
|
</languages>
|
||||||
|
|
||||||
|
<config>
|
||||||
|
<fields name="params">
|
||||||
|
<fieldset name="basic">
|
||||||
|
<field name="layout" type="modulelayout" label="Select Layout File" />
|
||||||
|
</fieldset>
|
||||||
|
</fields>
|
||||||
|
</config>
|
||||||
|
|
||||||
|
<updateservers>
|
||||||
|
<server type="extension" priority="1" name="Moko Consulting Updates" dlid="id">
|
||||||
|
https://update.mokoconsulting.tech/mod_mokomodule.xml
|
||||||
|
</server>
|
||||||
|
</updateservers>
|
||||||
|
</extension>
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<extension type="package" method="upgrade">
|
||||||
|
<name>Moko Full Suite Package</name>
|
||||||
|
<packagename>mokopackage</packagename>
|
||||||
|
<author>Moko Consulting</author>
|
||||||
|
<creationDate>July 2026</creationDate>
|
||||||
|
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
|
||||||
|
<license>GNU General Public License version 3 or later; see LICENSE</license>
|
||||||
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
<description>All-in-one distribution bundle wrapping the Moko Component, Dynamic Showcase Module, and System Interceptor Plugin into a single installation step.</description>
|
||||||
|
|
||||||
|
<languages>
|
||||||
|
<language tag="en-GB">en-GB.pkg_mokopackage.sys.ini</language>
|
||||||
|
<language tag="en-US">en-US.pkg_mokopackage.sys.ini</language>
|
||||||
|
</languages>
|
||||||
|
|
||||||
|
<files folder="constituents">
|
||||||
|
<file type="component" id="com_myextension">com_myextension.zip</file>
|
||||||
|
<file type="module" id="mod_mokomodule" client="site">mod_mokomodule.zip</file>
|
||||||
|
<file type="plugin" id="mokoplugin" group="system">plg_system_mokoplugin.zip</file>
|
||||||
|
</files>
|
||||||
|
|
||||||
|
<updateservers>
|
||||||
|
<server type="collection" priority="1" name="Moko Consulting Updates" dlid="id">
|
||||||
|
https://update.mokoconsulting.tech/pkg_mokopackage.xml
|
||||||
|
</server>
|
||||||
|
</updateservers>
|
||||||
|
|
||||||
|
<blockChildUninstall>1</blockChildUninstall>
|
||||||
|
</extension>
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<extension type="plugin" group="system" method="upgrade">
|
||||||
|
<name>System - Moko Automation Plugin</name>
|
||||||
|
<author>Moko Consulting</author>
|
||||||
|
<creationDate>July 2026</creationDate>
|
||||||
|
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
|
||||||
|
<license>GNU General Public License version 3 or later; see LICENSE</license>
|
||||||
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
<description>Advanced system-level interceptor framework developed by Moko Consulting for request optimizations in Joomla 6.</description>
|
||||||
|
|
||||||
|
<namespace path="src">MokoConsulting\Plugin\System\MokoPlugin</namespace>
|
||||||
|
|
||||||
|
<files>
|
||||||
|
<folder plugin="mokoplugin">services</folder>
|
||||||
|
<folder>src</folder>
|
||||||
|
</files>
|
||||||
|
|
||||||
|
<languages>
|
||||||
|
<language tag="en-GB">language/en-GB/plg_system_mokoplugin.ini</language>
|
||||||
|
<language tag="en-GB">language/en-GB/plg_system_mokoplugin.sys.ini</language>
|
||||||
|
<language tag="en-US">language/en-US/plg_system_mokoplugin.ini</language>
|
||||||
|
<language tag="en-US">language/en-US/plg_system_mokoplugin.sys.ini</language>
|
||||||
|
</languages>
|
||||||
|
|
||||||
|
<config>
|
||||||
|
<fields name="params">
|
||||||
|
<fieldset name="basic">
|
||||||
|
<field name="example_param" type="text" label="API Endpoint Parameter" />
|
||||||
|
</fieldset>
|
||||||
|
</fields>
|
||||||
|
</config>
|
||||||
|
|
||||||
|
<updateservers>
|
||||||
|
<server type="extension" priority="1" name="Moko Consulting Updates" dlid="id">
|
||||||
|
https://update.mokoconsulting.tech/plg_system_mokoplugin.xml
|
||||||
|
</server>
|
||||||
|
</updateservers>
|
||||||
|
</extension>
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<extension type="template" client="site" method="upgrade">
|
||||||
|
<name>tpl_mokotemplate</name>
|
||||||
|
<author>Moko Consulting</author>
|
||||||
|
<creationDate>July 2026</creationDate>
|
||||||
|
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
|
||||||
|
<license>GNU General Public License version 3 or later; see LICENSE</license>
|
||||||
|
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||||
|
<authorUrl>https://mokoconsulting.tech</authorUrl>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
<description>Moko Corporate Template - A modern, high-performance responsive frontend design system for Joomla 6.</description>
|
||||||
|
|
||||||
|
<files>
|
||||||
|
<filename>templateDetails.xml</filename>
|
||||||
|
<filename>index.php</filename>
|
||||||
|
<filename>template_thumbnail.png</filename>
|
||||||
|
<filename>template_preview.png</filename>
|
||||||
|
<folder>css</folder>
|
||||||
|
<folder>js</folder>
|
||||||
|
<folder>images</folder>
|
||||||
|
<folder>html</folder>
|
||||||
|
<folder>language</folder>
|
||||||
|
</files>
|
||||||
|
|
||||||
|
<positions>
|
||||||
|
<position>topbar</position>
|
||||||
|
<position>header</position>
|
||||||
|
<position>menu</position>
|
||||||
|
<position>breadcrumbs</position>
|
||||||
|
<position>sidebar-left</position>
|
||||||
|
<position>sidebar-right</position>
|
||||||
|
<position>main-top</position>
|
||||||
|
<position>main-bottom</position>
|
||||||
|
<position>footer</position>
|
||||||
|
</positions>
|
||||||
|
|
||||||
|
<languages folder="language">
|
||||||
|
<language tag="en-GB">en-GB/tpl_mokotemplate.ini</language>
|
||||||
|
<language tag="en-GB">en-GB/tpl_mokotemplate.sys.ini</language>
|
||||||
|
<language tag="en-US">en-US/tpl_mokotemplate.ini</language>
|
||||||
|
<language tag="en-US">en-US/tpl_mokotemplate.sys.ini</language>
|
||||||
|
</languages>
|
||||||
|
|
||||||
|
<config>
|
||||||
|
<fields name="params">
|
||||||
|
<fieldset name="basic" label="Basic Parameters">
|
||||||
|
<field name="logo" type="media" label="Select Corporate Logo" />
|
||||||
|
</fieldset>
|
||||||
|
</fields>
|
||||||
|
</config>
|
||||||
|
|
||||||
|
<updateservers>
|
||||||
|
<server type="extension" priority="1" name="Moko Consulting Updates" dlid="id">
|
||||||
|
https://update.mokoconsulting.tech/tpl_mokotemplate.xml
|
||||||
|
</server>
|
||||||
|
</updateservers>
|
||||||
|
</extension>
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* ============================================================================
|
||||||
|
* INSTRUCTIONS FOR USE:
|
||||||
|
* 1. This file template serves as the standard structure for all script files
|
||||||
|
* across the extension repositories. Copy and implement this pattern
|
||||||
|
* universally in every script file to maintain consistency.
|
||||||
|
* 2. Replace '{REPONAME}' with the actual name of the repo (UPPERCASE).
|
||||||
|
* Example: "MOKOSUITEBACKUP"
|
||||||
|
* 3. Replace '{PACKAGENAME}' with the Joomla extension element name (lowercase).
|
||||||
|
* Example: "pkg_mokosuitebackup", "com_mokosuitebackup", "mod_mokosuitebackup"
|
||||||
|
* ============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package Joomla.Installer
|
||||||
|
* @subpackage {PACKAGENAME}
|
||||||
|
*/
|
||||||
|
|
||||||
|
use Joomla\CMS\Factory;
|
||||||
|
use Joomla\CMS\Log\Log;
|
||||||
|
|
||||||
|
defined('_JEXEC') || die;
|
||||||
|
|
||||||
|
class ComponentInstallerScript
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string The saved download key cached during preflight to survive updates
|
||||||
|
*/
|
||||||
|
private $savedDownloadKey = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preflight hook runs before any installation/update/uninstallation action.
|
||||||
|
*/
|
||||||
|
public function preflight($type, $parent): bool
|
||||||
|
{
|
||||||
|
if ($type === 'update') {
|
||||||
|
$this->backupDownloadKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Postflight hook runs after any installation/update/uninstallation action.
|
||||||
|
*/
|
||||||
|
public function postflight($type, $parent): void
|
||||||
|
{
|
||||||
|
if ($type === 'install') {
|
||||||
|
$this->installSuccessful();
|
||||||
|
$this->warnMissingLicenseKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($type === 'update') {
|
||||||
|
$this->restoreDownloadKey();
|
||||||
|
$this->installSuccessful();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function install($parent): void {}
|
||||||
|
public function update($parent): void {}
|
||||||
|
public function uninstall($parent): void {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache the existing download key from the update sites table before update runs.
|
||||||
|
*/
|
||||||
|
private function backupDownloadKey(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$db = Factory::getDbo();
|
||||||
|
$query = $db->getQuery(true)
|
||||||
|
->select($db->quoteName('us.extra_query'))
|
||||||
|
->from($db->quoteName('#__update_sites', 'us'))
|
||||||
|
->join(
|
||||||
|
'INNER',
|
||||||
|
$db->quoteName('#__update_sites_extensions', 'use')
|
||||||
|
. ' ON ' . $db->quoteName('use.update_site_id') . ' = ' . $db->quoteName('us.update_site_id')
|
||||||
|
)
|
||||||
|
->join(
|
||||||
|
'INNER',
|
||||||
|
$db->quoteName('#__extensions', 'e')
|
||||||
|
. ' ON ' . $db->quoteName('e.extension_id') . ' = ' . $db->quoteName('use.extension_id')
|
||||||
|
)
|
||||||
|
->where($db->quoteName('e.element') . ' = ' . $db->quote('{PACKAGENAME}'))
|
||||||
|
->where($db->quoteName('e.type') . ' = ' . $db->quote('component'))
|
||||||
|
->setLimit(1);
|
||||||
|
|
||||||
|
$db->setQuery($query);
|
||||||
|
$extraQuery = (string) $db->loadResult();
|
||||||
|
|
||||||
|
if (!empty($extraQuery)) {
|
||||||
|
parse_str($extraQuery, $output);
|
||||||
|
$this->savedDownloadKey = $output['dlid'] ?? $extraQuery;
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::add('{REPONAME}: Could not backup download key: ' . $e->getMessage(), Log::WARNING, 'jerror');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restore the download key to the (possibly new) update site record.
|
||||||
|
*/
|
||||||
|
private function restoreDownloadKey(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$db = Factory::getDbo();
|
||||||
|
$query = $db->getQuery(true)
|
||||||
|
->select($db->quoteName('us.update_site_id'))
|
||||||
|
->from($db->quoteName('#__update_sites', 'us'))
|
||||||
|
->join(
|
||||||
|
'INNER',
|
||||||
|
$db->quoteName('#__update_sites_extensions', 'use')
|
||||||
|
. ' ON ' . $db->quoteName('use.update_site_id') . ' = ' . $db->quoteName('us.update_site_id')
|
||||||
|
)
|
||||||
|
->join(
|
||||||
|
'INNER',
|
||||||
|
$db->quoteName('#__extensions', 'e')
|
||||||
|
. ' ON ' . $db->quoteName('e.extension_id') . ' = ' . $db->quoteName('use.extension_id')
|
||||||
|
)
|
||||||
|
->where($db->quoteName('e.element') . ' = ' . $db->quote('{PACKAGENAME}'))
|
||||||
|
->where($db->quoteName('e.type') . ' = ' . $db->quote('component'))
|
||||||
|
->setLimit(1);
|
||||||
|
|
||||||
|
$db->setQuery($query);
|
||||||
|
$updateSiteId = (int) $db->loadResult();
|
||||||
|
|
||||||
|
if ($updateSiteId > 0 && !empty($this->savedDownloadKey)) {
|
||||||
|
$query = $db->getQuery(true)
|
||||||
|
->update($db->quoteName('#__update_sites'))
|
||||||
|
->set($db->quoteName('extra_query') . ' = ' . $db->quote('dlid=' . $this->savedDownloadKey))
|
||||||
|
->where($db->quoteName('update_site_id') . ' = ' . $updateSiteId);
|
||||||
|
|
||||||
|
$db->setQuery($query);
|
||||||
|
$db->execute();
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::add('{REPONAME}: Could not restore download key: ' . $e->getMessage(), Log::WARNING, 'jerror');
|
||||||
|
|
||||||
|
Factory::getApplication()->enqueueMessage(
|
||||||
|
'<h4>{REPONAME}</h4>'
|
||||||
|
. '<p>Your download/license key could not be preserved during the update.</p>'
|
||||||
|
. '<p>Please re-enter it in the <a class="btn btn-sm btn-warning ms-2" href="index.php?option=com_installer&view=updatesites&filter[search]={PACKAGENAME}">Update Sites</a> manager to continue receiving updates.</p>',
|
||||||
|
'warning'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show post-install license key prompt
|
||||||
|
*/
|
||||||
|
private function warnMissingLicenseKey(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
Factory::getApplication()->enqueueMessage(
|
||||||
|
'<h4>{REPONAME} License Key Required</h4>'
|
||||||
|
. '<p>A download/license key (DLID) is required to receive updates.</p>'
|
||||||
|
. '<p>Enter your key in the <a class="btn btn-sm btn-warning ms-2" href="index.php?option=com_installer&view=updatesites&filter[search]={PACKAGENAME}">Update Sites</a> manager '
|
||||||
|
. 'or contact <a class="btn btn-sm btn-warning ms-2" href="https://mokoconsulting.tech/support" target="_blank" rel="noopener">Moko Consulting Support</a> to obtain one.</p>',
|
||||||
|
'warning'
|
||||||
|
);
|
||||||
|
} catch (\Exception $e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show install successful prompt
|
||||||
|
*/
|
||||||
|
private function installSuccessful(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
Factory::getApplication()->enqueueMessage(
|
||||||
|
'<h4>{REPONAME} installed successfully!</h4>',
|
||||||
|
'info'
|
||||||
|
);
|
||||||
|
} catch (\Exception $e) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* ============================================================================
|
||||||
|
* INSTRUCTIONS FOR USE:
|
||||||
|
* 1. This file template serves as the standard structure for all script files
|
||||||
|
* across the extension repositories. Copy and implement this pattern
|
||||||
|
* universally in every script file to maintain consistency.
|
||||||
|
* 2. Replace '{REPONAME}' with the actual name of the repo (UPPERCASE).
|
||||||
|
* Example: "MOKOSUITEBACKUP"
|
||||||
|
* 3. Replace '{PACKAGENAME}' with the Joomla extension element name (lowercase).
|
||||||
|
* Example: "pkg_mokosuitebackup", "com_mokosuitebackup", "mod_mokosuitebackup"
|
||||||
|
* ============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package Joomla.Installer
|
||||||
|
* @subpackage {PACKAGENAME}
|
||||||
|
*/
|
||||||
|
|
||||||
|
use Joomla\CMS\Factory;
|
||||||
|
use Joomla\CMS\Log\Log;
|
||||||
|
|
||||||
|
defined('_JEXEC') || die;
|
||||||
|
|
||||||
|
class ModuleInstallerScript
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string The saved download key cached during preflight to survive updates
|
||||||
|
*/
|
||||||
|
private $savedDownloadKey = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preflight hook runs before any installation/update/uninstallation action.
|
||||||
|
*/
|
||||||
|
public function preflight($type, $parent): bool
|
||||||
|
{
|
||||||
|
if ($type === 'update') {
|
||||||
|
$this->backupDownloadKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Postflight hook runs after any installation/update/uninstallation action.
|
||||||
|
*/
|
||||||
|
public function postflight($type, $parent): void
|
||||||
|
{
|
||||||
|
if ($type === 'install') {
|
||||||
|
$this->installSuccessful();
|
||||||
|
$this->warnMissingLicenseKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($type === 'update') {
|
||||||
|
$this->restoreDownloadKey();
|
||||||
|
$this->installSuccessful();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function install($parent): void {}
|
||||||
|
public function update($parent): void {}
|
||||||
|
public function uninstall($parent): void {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache the existing download key from the update sites table before update runs.
|
||||||
|
*/
|
||||||
|
private function backupDownloadKey(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$db = Factory::getDbo();
|
||||||
|
$query = $db->getQuery(true)
|
||||||
|
->select($db->quoteName('us.extra_query'))
|
||||||
|
->from($db->quoteName('#__update_sites', 'us'))
|
||||||
|
->join(
|
||||||
|
'INNER',
|
||||||
|
$db->quoteName('#__update_sites_extensions', 'use')
|
||||||
|
. ' ON ' . $db->quoteName('use.update_site_id') . ' = ' . $db->quoteName('us.update_site_id')
|
||||||
|
)
|
||||||
|
->join(
|
||||||
|
'INNER',
|
||||||
|
$db->quoteName('#__extensions', 'e')
|
||||||
|
. ' ON ' . $db->quoteName('e.extension_id') . ' = ' . $db->quoteName('use.extension_id')
|
||||||
|
)
|
||||||
|
->where($db->quoteName('e.element') . ' = ' . $db->quote('{PACKAGENAME}'))
|
||||||
|
->where($db->quoteName('e.type') . ' = ' . $db->quote('module'))
|
||||||
|
->setLimit(1);
|
||||||
|
|
||||||
|
$db->setQuery($query);
|
||||||
|
$extraQuery = (string) $db->loadResult();
|
||||||
|
|
||||||
|
if (!empty($extraQuery)) {
|
||||||
|
parse_str($extraQuery, $output);
|
||||||
|
$this->savedDownloadKey = $output['dlid'] ?? $extraQuery;
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::add('{REPONAME}: Could not backup download key: ' . $e->getMessage(), Log::WARNING, 'jerror');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restore the download key to the (possibly new) update site record.
|
||||||
|
*/
|
||||||
|
private function restoreDownloadKey(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$db = Factory::getDbo();
|
||||||
|
$query = $db->getQuery(true)
|
||||||
|
->select($db->quoteName('us.update_site_id'))
|
||||||
|
->from($db->quoteName('#__update_sites', 'us'))
|
||||||
|
->join(
|
||||||
|
'INNER',
|
||||||
|
$db->quoteName('#__update_sites_extensions', 'use')
|
||||||
|
. ' ON ' . $db->quoteName('use.update_site_id') . ' = ' . $db->quoteName('us.update_site_id')
|
||||||
|
)
|
||||||
|
->join(
|
||||||
|
'INNER',
|
||||||
|
$db->quoteName('#__extensions', 'e')
|
||||||
|
. ' ON ' . $db->quoteName('e.extension_id') . ' = ' . $db->quoteName('use.extension_id')
|
||||||
|
)
|
||||||
|
->where($db->quoteName('e.element') . ' = ' . $db->quote('{PACKAGENAME}'))
|
||||||
|
->where($db->quoteName('e.type') . ' = ' . $db->quote('module'))
|
||||||
|
->setLimit(1);
|
||||||
|
|
||||||
|
$db->setQuery($query);
|
||||||
|
$updateSiteId = (int) $db->loadResult();
|
||||||
|
|
||||||
|
if ($updateSiteId > 0 && !empty($this->savedDownloadKey)) {
|
||||||
|
$query = $db->getQuery(true)
|
||||||
|
->update($db->quoteName('#__update_sites'))
|
||||||
|
->set($db->quoteName('extra_query') . ' = ' . $db->quote('dlid=' . $this->savedDownloadKey))
|
||||||
|
->where($db->quoteName('update_site_id') . ' = ' . $updateSiteId);
|
||||||
|
|
||||||
|
$db->setQuery($query);
|
||||||
|
$db->execute();
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::add('{REPONAME}: Could not restore download key: ' . $e->getMessage(), Log::WARNING, 'jerror');
|
||||||
|
|
||||||
|
Factory::getApplication()->enqueueMessage(
|
||||||
|
'<h4>{REPONAME}</h4>'
|
||||||
|
. '<p>Your download/license key could not be preserved during the update.</p>'
|
||||||
|
. '<p>Please re-enter it in the <a class="btn btn-sm btn-warning ms-2" href="index.php?option=com_installer&view=updatesites&filter[search]={PACKAGENAME}">Update Sites</a> manager to continue receiving updates.</p>',
|
||||||
|
'warning'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show post-install license key prompt
|
||||||
|
*/
|
||||||
|
private function warnMissingLicenseKey(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
Factory::getApplication()->enqueueMessage(
|
||||||
|
'<h4>{REPONAME} License Key Required</h4>'
|
||||||
|
. '<p>A download/license key (DLID) is required to receive updates.</p>'
|
||||||
|
. '<p>Enter your key in the <a class="btn btn-sm btn-warning ms-2" href="index.php?option=com_installer&view=updatesites&filter[search]={PACKAGENAME}">Update Sites</a> manager '
|
||||||
|
. 'or contact <a class="btn btn-sm btn-warning ms-2" href="https://mokoconsulting.tech/support" target="_blank" rel="noopener">Moko Consulting Support</a> to obtain one.</p>',
|
||||||
|
'warning'
|
||||||
|
);
|
||||||
|
} catch (\Exception $e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show install successful prompt
|
||||||
|
*/
|
||||||
|
private function installSuccessful(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
Factory::getApplication()->enqueueMessage(
|
||||||
|
'<h4>{REPONAME} installed successfully!</h4>',
|
||||||
|
'info'
|
||||||
|
);
|
||||||
|
} catch (\Exception $e) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* ============================================================================
|
||||||
|
* INSTRUCTIONS FOR USE:
|
||||||
|
* 1. This file template serves as the standard structure for all script files
|
||||||
|
* across the extension repositories. Copy and implement this pattern
|
||||||
|
* universally in every script file to maintain consistency.
|
||||||
|
* 2. Replace '{REPONAME}' with the actual name of the repo (UPPERCASE).
|
||||||
|
* Example: "MOKOSUITEBACKUP"
|
||||||
|
* 3. Replace '{PACKAGENAME}' with the Joomla extension element name (lowercase).
|
||||||
|
* Example: "pkg_mokosuitebackup", "com_mokosuitebackup", "mod_mokosuitebackup"
|
||||||
|
* ============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package Joomla.Installer
|
||||||
|
* @subpackage {PACKAGENAME}
|
||||||
|
*/
|
||||||
|
|
||||||
|
use Joomla\CMS\Factory;
|
||||||
|
use Joomla\CMS\Log\Log;
|
||||||
|
|
||||||
|
defined('_JEXEC') || die;
|
||||||
|
|
||||||
|
class PackageInstallerScript
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string The saved download key cached during preflight to survive updates
|
||||||
|
*/
|
||||||
|
private $savedDownloadKey = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preflight hook runs before any installation/update/uninstallation action.
|
||||||
|
*/
|
||||||
|
public function preflight($type, $parent): bool
|
||||||
|
{
|
||||||
|
if ($type === 'update') {
|
||||||
|
$this->backupDownloadKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Postflight hook runs after any installation/update/uninstallation action.
|
||||||
|
*/
|
||||||
|
public function postflight($type, $parent): void
|
||||||
|
{
|
||||||
|
if ($type === 'install') {
|
||||||
|
$this->installSuccessful();
|
||||||
|
$this->warnMissingLicenseKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($type === 'update') {
|
||||||
|
$this->restoreDownloadKey();
|
||||||
|
$this->installSuccessful();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function install($parent): void {}
|
||||||
|
public function update($parent): void {}
|
||||||
|
public function uninstall($parent): void {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache the existing download key from the update sites table before update runs.
|
||||||
|
*/
|
||||||
|
private function backupDownloadKey(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$db = Factory::getDbo();
|
||||||
|
$query = $db->getQuery(true)
|
||||||
|
->select($db->quoteName('us.extra_query'))
|
||||||
|
->from($db->quoteName('#__update_sites', 'us'))
|
||||||
|
->join(
|
||||||
|
'INNER',
|
||||||
|
$db->quoteName('#__update_sites_extensions', 'use')
|
||||||
|
. ' ON ' . $db->quoteName('use.update_site_id') . ' = ' . $db->quoteName('us.update_site_id')
|
||||||
|
)
|
||||||
|
->join(
|
||||||
|
'INNER',
|
||||||
|
$db->quoteName('#__extensions', 'e')
|
||||||
|
. ' ON ' . $db->quoteName('e.extension_id') . ' = ' . $db->quoteName('use.extension_id')
|
||||||
|
)
|
||||||
|
->where($db->quoteName('e.element') . ' = ' . $db->quote('{PACKAGENAME}'))
|
||||||
|
->where($db->quoteName('e.type') . ' = ' . $db->quote('package'))
|
||||||
|
->setLimit(1);
|
||||||
|
|
||||||
|
$db->setQuery($query);
|
||||||
|
$extraQuery = (string) $db->loadResult();
|
||||||
|
|
||||||
|
if (!empty($extraQuery)) {
|
||||||
|
parse_str($extraQuery, $output);
|
||||||
|
$this->savedDownloadKey = $output['dlid'] ?? $extraQuery;
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::add('{REPONAME}: Could not backup download key: ' . $e->getMessage(), Log::WARNING, 'jerror');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restore the download key to the (possibly new) update site record.
|
||||||
|
*/
|
||||||
|
private function restoreDownloadKey(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$db = Factory::getDbo();
|
||||||
|
$query = $db->getQuery(true)
|
||||||
|
->select($db->quoteName('us.update_site_id'))
|
||||||
|
->from($db->quoteName('#__update_sites', 'us'))
|
||||||
|
->join(
|
||||||
|
'INNER',
|
||||||
|
$db->quoteName('#__update_sites_extensions', 'use')
|
||||||
|
. ' ON ' . $db->quoteName('use.update_site_id') . ' = ' . $db->quoteName('us.update_site_id')
|
||||||
|
)
|
||||||
|
->join(
|
||||||
|
'INNER',
|
||||||
|
$db->quoteName('#__extensions', 'e')
|
||||||
|
. ' ON ' . $db->quoteName('e.extension_id') . ' = ' . $db->quoteName('use.extension_id')
|
||||||
|
)
|
||||||
|
->where($db->quoteName('e.element') . ' = ' . $db->quote('{PACKAGENAME}'))
|
||||||
|
->where($db->quoteName('e.type') . ' = ' . $db->quote('package'))
|
||||||
|
->setLimit(1);
|
||||||
|
|
||||||
|
$db->setQuery($query);
|
||||||
|
$updateSiteId = (int) $db->loadResult();
|
||||||
|
|
||||||
|
if ($updateSiteId > 0 && !empty($this->savedDownloadKey)) {
|
||||||
|
$query = $db->getQuery(true)
|
||||||
|
->update($db->quoteName('#__update_sites'))
|
||||||
|
->set($db->quoteName('extra_query') . ' = ' . $db->quote('dlid=' . $this->savedDownloadKey))
|
||||||
|
->where($db->quoteName('update_site_id') . ' = ' . $updateSiteId);
|
||||||
|
|
||||||
|
$db->setQuery($query);
|
||||||
|
$db->execute();
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::add('{REPONAME}: Could not restore download key: ' . $e->getMessage(), Log::WARNING, 'jerror');
|
||||||
|
|
||||||
|
Factory::getApplication()->enqueueMessage(
|
||||||
|
'<h4>{REPONAME}</h4>'
|
||||||
|
. '<p>Your download/license key could not be preserved during the update.</p>'
|
||||||
|
. '<p>Please re-enter it in the <a class="btn btn-sm btn-warning ms-2" href="index.php?option=com_installer&view=updatesites&filter[search]={PACKAGENAME}">Update Sites</a> manager to continue receiving updates.</p>',
|
||||||
|
'warning'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show post-install license key prompt
|
||||||
|
*/
|
||||||
|
private function warnMissingLicenseKey(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
Factory::getApplication()->enqueueMessage(
|
||||||
|
'<h4>{REPONAME} License Key Required</h4>'
|
||||||
|
. '<p>A download/license key (DLID) is required to receive updates.</p>'
|
||||||
|
. '<p>Enter your key in the <a class="btn btn-sm btn-warning ms-2" href="index.php?option=com_installer&view=updatesites&filter[search]={PACKAGENAME}">Update Sites</a> manager '
|
||||||
|
. 'or contact <a class="btn btn-sm btn-warning ms-2" href="https://mokoconsulting.tech/support" target="_blank" rel="noopener">Moko Consulting Support</a> to obtain one.</p>',
|
||||||
|
'warning'
|
||||||
|
);
|
||||||
|
} catch (\Exception $e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show install successful prompt
|
||||||
|
*/
|
||||||
|
private function installSuccessful(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
Factory::getApplication()->enqueueMessage(
|
||||||
|
'<h4>{REPONAME} installed successfully!</h4>',
|
||||||
|
'info'
|
||||||
|
);
|
||||||
|
} catch (\Exception $e) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* ============================================================================
|
||||||
|
* INSTRUCTIONS FOR USE:
|
||||||
|
* 1. This file template serves as the standard structure for all script files
|
||||||
|
* across the extension repositories. Copy and implement this pattern
|
||||||
|
* universally in every script file to maintain consistency.
|
||||||
|
* 2. Replace '{REPONAME}' with the actual name of the repo (UPPERCASE).
|
||||||
|
* Example: "MOKOSUITEBACKUP"
|
||||||
|
* 3. Replace '{PACKAGENAME}' with the Joomla extension element name (lowercase).
|
||||||
|
* Example: "pkg_mokosuitebackup", "com_mokosuitebackup", "mod_mokosuitebackup"
|
||||||
|
* ============================================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @package Joomla.Installer
|
||||||
|
* @subpackage {PACKAGENAME}
|
||||||
|
*/
|
||||||
|
|
||||||
|
use Joomla\CMS\Factory;
|
||||||
|
use Joomla\CMS\Log\Log;
|
||||||
|
|
||||||
|
defined('_JEXEC') || die;
|
||||||
|
|
||||||
|
class PluginInstallerScript
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string The saved download key cached during preflight to survive updates
|
||||||
|
*/
|
||||||
|
private $savedDownloadKey = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preflight hook runs before any installation/update/uninstallation action.
|
||||||
|
*/
|
||||||
|
public function preflight($type, $parent): bool
|
||||||
|
{
|
||||||
|
if ($type === 'update') {
|
||||||
|
$this->backupDownloadKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Postflight hook runs after any installation/update/uninstallation action.
|
||||||
|
*/
|
||||||
|
public function postflight($type, $parent): void
|
||||||
|
{
|
||||||
|
if ($type === 'install') {
|
||||||
|
$this->installSuccessful();
|
||||||
|
$this->warnMissingLicenseKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($type === 'update') {
|
||||||
|
$this->restoreDownloadKey();
|
||||||
|
$this->installSuccessful();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function install($parent): void {}
|
||||||
|
public function update($parent): void {}
|
||||||
|
public function uninstall($parent): void {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache the existing download key from the update sites table before update runs.
|
||||||
|
*/
|
||||||
|
private function backupDownloadKey(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$db = Factory::getDbo();
|
||||||
|
$query = $db->getQuery(true)
|
||||||
|
->select($db->quoteName('us.extra_query'))
|
||||||
|
->from($db->quoteName('#__update_sites', 'us'))
|
||||||
|
->join(
|
||||||
|
'INNER',
|
||||||
|
$db->quoteName('#__update_sites_extensions', 'use')
|
||||||
|
. ' ON ' . $db->quoteName('use.update_site_id') . ' = ' . $db->quoteName('us.update_site_id')
|
||||||
|
)
|
||||||
|
->join(
|
||||||
|
'INNER',
|
||||||
|
$db->quoteName('#__extensions', 'e')
|
||||||
|
. ' ON ' . $db->quoteName('e.extension_id') . ' = ' . $db->quoteName('use.extension_id')
|
||||||
|
)
|
||||||
|
->where($db->quoteName('e.element') . ' = ' . $db->quote('{PACKAGENAME}'))
|
||||||
|
->where($db->quoteName('e.type') . ' = ' . $db->quote('plugin'))
|
||||||
|
->setLimit(1);
|
||||||
|
|
||||||
|
$db->setQuery($query);
|
||||||
|
$extraQuery = (string) $db->loadResult();
|
||||||
|
|
||||||
|
if (!empty($extraQuery)) {
|
||||||
|
parse_str($extraQuery, $output);
|
||||||
|
$this->savedDownloadKey = $output['dlid'] ?? $extraQuery;
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::add('{REPONAME}: Could not backup download key: ' . $e->getMessage(), Log::WARNING, 'jerror');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restore the download key to the (possibly new) update site record.
|
||||||
|
*/
|
||||||
|
private function restoreDownloadKey(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$db = Factory::getDbo();
|
||||||
|
$query = $db->getQuery(true)
|
||||||
|
->select($db->quoteName('us.update_site_id'))
|
||||||
|
->from($db->quoteName('#__update_sites', 'us'))
|
||||||
|
->join(
|
||||||
|
'INNER',
|
||||||
|
$db->quoteName('#__update_sites_extensions', 'use')
|
||||||
|
. ' ON ' . $db->quoteName('use.update_site_id') . ' = ' . $db->quoteName('us.update_site_id')
|
||||||
|
)
|
||||||
|
->join(
|
||||||
|
'INNER',
|
||||||
|
$db->quoteName('#__extensions', 'e')
|
||||||
|
. ' ON ' . $db->quoteName('e.extension_id') . ' = ' . $db->quoteName('use.extension_id')
|
||||||
|
)
|
||||||
|
->where($db->quoteName('e.element') . ' = ' . $db->quote('{PACKAGENAME}'))
|
||||||
|
->where($db->quoteName('e.type') . ' = ' . $db->quote('plugin'))
|
||||||
|
->setLimit(1);
|
||||||
|
|
||||||
|
$db->setQuery($query);
|
||||||
|
$updateSiteId = (int) $db->loadResult();
|
||||||
|
|
||||||
|
if ($updateSiteId > 0 && !empty($this->savedDownloadKey)) {
|
||||||
|
$query = $db->getQuery(true)
|
||||||
|
->update($db->quoteName('#__update_sites'))
|
||||||
|
->set($db->quoteName('extra_query') . ' = ' . $db->quote('dlid=' . $this->savedDownloadKey))
|
||||||
|
->where($db->quoteName('update_site_id') . ' = ' . $updateSiteId);
|
||||||
|
|
||||||
|
$db->setQuery($query);
|
||||||
|
$db->execute();
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::add('{REPONAME}: Could not restore download key: ' . $e->getMessage(), Log::WARNING, 'jerror');
|
||||||
|
|
||||||
|
Factory::getApplication()->enqueueMessage(
|
||||||
|
'<h4>{REPONAME}</h4>'
|
||||||
|
. '<p>Your download/license key could not be preserved during the update.</p>'
|
||||||
|
. '<p>Please re-enter it in the <a class="btn btn-sm btn-warning ms-2" href="index.php?option=com_installer&view=updatesites&filter[search]={PACKAGENAME}">Update Sites</a> manager to continue receiving updates.</p>',
|
||||||
|
'warning'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show post-install license key prompt
|
||||||
|
*/
|
||||||
|
private function warnMissingLicenseKey(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
Factory::getApplication()->enqueueMessage(
|
||||||
|
'<h4>{REPONAME} License Key Required</h4>'
|
||||||
|
. '<p>A download/license key (DLID) is required to receive updates.</p>'
|
||||||
|
. '<p>Enter your key in the <a class="btn btn-sm btn-warning ms-2" href="index.php?option=com_installer&view=updatesites&filter[search]={PACKAGENAME}">Update Sites</a> manager '
|
||||||
|
. 'or contact <a class="btn btn-sm btn-warning ms-2" href="https://mokoconsulting.tech/support" target="_blank" rel="noopener">Moko Consulting Support</a> to obtain one.</p>',
|
||||||
|
'warning'
|
||||||
|
);
|
||||||
|
} catch (\Exception $e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show install successful prompt
|
||||||
|
*/
|
||||||
|
private function installSuccessful(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
Factory::getApplication()->enqueueMessage(
|
||||||
|
'<h4>{REPONAME} installed successfully!</h4>',
|
||||||
|
'info'
|
||||||
|
);
|
||||||
|
} catch (\Exception $e) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# Component Scaffold
|
|
||||||
|
|
||||||
Copy this directory as the starting point for a new Joomla component.
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# Library Scaffold
|
|
||||||
|
|
||||||
Copy this directory as the starting point for a new Joomla library.
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# Module Scaffold
|
|
||||||
|
|
||||||
Copy this directory as the starting point for a new Joomla module.
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# Package Scaffold
|
|
||||||
|
|
||||||
Copy this directory as the starting point for a new Joomla package.
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# Plugin Scaffold
|
|
||||||
|
|
||||||
Copy this directory as the starting point for a new Joomla plugin.
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
# Template Scaffold
|
|
||||||
|
|
||||||
Copy this directory as the starting point for a new Joomla site template.
|
|
||||||
Includes: index.php, error.php, offline.php, templateDetails.xml, css/, js/, fonts/, images/, html/
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
/**
|
|
||||||
* Custom CSS for yourtemplate
|
|
||||||
*
|
|
||||||
* This file is for user customizations.
|
|
||||||
* It will not be overwritten during template updates.
|
|
||||||
*
|
|
||||||
* @package Joomla.Site
|
|
||||||
* @subpackage Templates.yourtemplate
|
|
||||||
* @copyright Copyright (C) 2026. All rights reserved.
|
|
||||||
* @license GNU General Public License version 2 or later
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* Add your custom styles below this line */
|
|
||||||
@@ -1,591 +0,0 @@
|
|||||||
/**
|
|
||||||
* Template CSS for yourtemplate
|
|
||||||
*
|
|
||||||
* This file contains all template styles in one place for simplicity.
|
|
||||||
* For larger projects, consider splitting into modular files:
|
|
||||||
* - base.css (resets, typography, base elements)
|
|
||||||
* - layout.css (grid, containers, responsive)
|
|
||||||
* - components.css (buttons, forms, alerts)
|
|
||||||
* - utilities.css (helper classes)
|
|
||||||
*
|
|
||||||
* Alternatively, use CSS preprocessing (Sass, Less, PostCSS) to organize
|
|
||||||
* code into partial files that compile into a single output file.
|
|
||||||
*
|
|
||||||
* @package Joomla.Site
|
|
||||||
* @subpackage Templates.yourtemplate
|
|
||||||
* @copyright Copyright (C) 2026. All rights reserved.
|
|
||||||
* @license GNU General Public License version 2 or later
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* ==========================================================================
|
|
||||||
CSS Custom Properties (Variables)
|
|
||||||
========================================================================== */
|
|
||||||
|
|
||||||
:root {
|
|
||||||
/* Colors */
|
|
||||||
--color-primary: #0066cc;
|
|
||||||
--color-primary-dark: #0052a3;
|
|
||||||
--color-text: #333;
|
|
||||||
--color-text-light: #666;
|
|
||||||
--color-background: #fff;
|
|
||||||
--color-background-light: #f9f9f9;
|
|
||||||
--color-background-gray: #f5f5f5;
|
|
||||||
--color-border: #e0e0e0;
|
|
||||||
--color-error: #d9534f;
|
|
||||||
--color-success: #5cb85c;
|
|
||||||
--color-warning: #f0ad4e;
|
|
||||||
--color-info: #5bc0de;
|
|
||||||
|
|
||||||
/* Typography */
|
|
||||||
--font-family-base: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
||||||
--font-size-base: 1rem;
|
|
||||||
--line-height-base: 1.6;
|
|
||||||
|
|
||||||
/* Spacing */
|
|
||||||
--spacing-xs: 0.25rem;
|
|
||||||
--spacing-sm: 0.5rem;
|
|
||||||
--spacing-md: 1rem;
|
|
||||||
--spacing-lg: 1.5rem;
|
|
||||||
--spacing-xl: 3rem;
|
|
||||||
|
|
||||||
/* Layout */
|
|
||||||
--container-max-width: 1200px;
|
|
||||||
--border-radius: 4px;
|
|
||||||
--box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ==========================================================================
|
|
||||||
Base Styles
|
|
||||||
========================================================================== */
|
|
||||||
|
|
||||||
* {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
html {
|
|
||||||
font-size: 16px;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
font-family: var(--font-family-base);
|
|
||||||
font-size: var(--font-size-base);
|
|
||||||
line-height: var(--line-height-base);
|
|
||||||
color: var(--color-text);
|
|
||||||
background-color: var(--color-background);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ==========================================================================
|
|
||||||
Layout Containers
|
|
||||||
========================================================================== */
|
|
||||||
|
|
||||||
.container {
|
|
||||||
width: 100%;
|
|
||||||
max-width: var(--container-max-width);
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 0 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container-fluid {
|
|
||||||
width: 100%;
|
|
||||||
padding: 0 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.row {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
margin: 0 -15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
[class*="col-"] {
|
|
||||||
padding: 0 15px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ==========================================================================
|
|
||||||
Grid System
|
|
||||||
========================================================================== */
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
.col-md-3 {
|
|
||||||
width: 25%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.col-md-6 {
|
|
||||||
width: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.col-md-9 {
|
|
||||||
width: 75%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.col-md-12 {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ==========================================================================
|
|
||||||
Header Styles
|
|
||||||
========================================================================== */
|
|
||||||
|
|
||||||
.site-header {
|
|
||||||
background: var(--color-background);
|
|
||||||
border-bottom: 1px solid var(--color-border);
|
|
||||||
padding: 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.site-header.sticky-header {
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
z-index: 1000;
|
|
||||||
box-shadow: var(--box-shadow);
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-inner {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.site-branding {
|
|
||||||
flex: 0 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.site-logo {
|
|
||||||
max-height: 60px;
|
|
||||||
width: auto;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.site-title {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 1.75rem;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.site-title a {
|
|
||||||
color: var(--color-text);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.site-title a:hover {
|
|
||||||
color: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.site-description {
|
|
||||||
margin-top: 5px;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
color: var(--color-text-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-modules {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ==========================================================================
|
|
||||||
Top Bar
|
|
||||||
========================================================================== */
|
|
||||||
|
|
||||||
.top-bar {
|
|
||||||
background: var(--color-background-gray);
|
|
||||||
padding: 10px 0;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ==========================================================================
|
|
||||||
Navigation Styles
|
|
||||||
========================================================================== */
|
|
||||||
|
|
||||||
.site-navigation {
|
|
||||||
background: var(--color-primary);
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.site-navigation ul {
|
|
||||||
list-style: none;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.site-navigation li {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.site-navigation a {
|
|
||||||
display: block;
|
|
||||||
padding: 15px 20px;
|
|
||||||
color: #fff;
|
|
||||||
text-decoration: none;
|
|
||||||
transition: background-color 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.site-navigation a:hover,
|
|
||||||
.site-navigation a:focus {
|
|
||||||
background-color: rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.site-navigation .active > a {
|
|
||||||
background-color: rgba(0, 0, 0, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ==========================================================================
|
|
||||||
Banner Area
|
|
||||||
========================================================================== */
|
|
||||||
|
|
||||||
.banner-area {
|
|
||||||
padding: 40px 0;
|
|
||||||
background: var(--color-background-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ==========================================================================
|
|
||||||
Main Content Area
|
|
||||||
========================================================================== */
|
|
||||||
|
|
||||||
.site-main {
|
|
||||||
padding: 40px 0;
|
|
||||||
min-height: 400px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-area {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-content {
|
|
||||||
margin: 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-top,
|
|
||||||
.main-bottom {
|
|
||||||
margin: 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ==========================================================================
|
|
||||||
Sidebar Styles
|
|
||||||
========================================================================== */
|
|
||||||
|
|
||||||
.sidebar {
|
|
||||||
padding: 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar .module {
|
|
||||||
margin-bottom: 30px;
|
|
||||||
padding: 20px;
|
|
||||||
background: var(--color-background-light);
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar .module h3 {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
font-size: 1.25rem;
|
|
||||||
border-bottom: 2px solid var(--color-primary);
|
|
||||||
padding-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ==========================================================================
|
|
||||||
Footer Styles
|
|
||||||
========================================================================== */
|
|
||||||
|
|
||||||
.site-footer {
|
|
||||||
background: var(--color-text);
|
|
||||||
color: var(--color-background);
|
|
||||||
padding: 40px 0 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.site-footer a {
|
|
||||||
color: var(--color-background);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.site-footer a:hover {
|
|
||||||
color: var(--color-primary);
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ==========================================================================
|
|
||||||
Error Page Styles
|
|
||||||
========================================================================== */
|
|
||||||
|
|
||||||
.site-error {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
min-height: 100vh;
|
|
||||||
background: var(--color-background-gray);
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-container {
|
|
||||||
max-width: 600px;
|
|
||||||
padding: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-content {
|
|
||||||
background: var(--color-background);
|
|
||||||
padding: 40px;
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
box-shadow: var(--box-shadow);
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-logo {
|
|
||||||
max-height: 80px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-message h2 {
|
|
||||||
color: var(--color-error);
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-actions {
|
|
||||||
margin-top: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-technical {
|
|
||||||
margin-top: 30px;
|
|
||||||
padding: 20px;
|
|
||||||
background: var(--color-background-light);
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
text-align: left;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ==========================================================================
|
|
||||||
Offline Page Styles
|
|
||||||
========================================================================== */
|
|
||||||
|
|
||||||
.site-offline {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
min-height: 100vh;
|
|
||||||
background: var(--color-background-gray);
|
|
||||||
}
|
|
||||||
|
|
||||||
.offline-container {
|
|
||||||
max-width: 500px;
|
|
||||||
padding: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.offline-content {
|
|
||||||
background: var(--color-background);
|
|
||||||
padding: 40px;
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
box-shadow: var(--box-shadow);
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.offline-logo {
|
|
||||||
max-height: 80px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ==========================================================================
|
|
||||||
Buttons
|
|
||||||
========================================================================== */
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 10px 20px;
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 400;
|
|
||||||
text-align: center;
|
|
||||||
text-decoration: none;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary {
|
|
||||||
background-color: var(--color-primary);
|
|
||||||
border-color: var(--color-primary);
|
|
||||||
color: var(--color-background);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary:hover {
|
|
||||||
background-color: var(--color-primary-dark);
|
|
||||||
border-color: var(--color-primary-dark);
|
|
||||||
color: var(--color-background);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ==========================================================================
|
|
||||||
Messages
|
|
||||||
========================================================================== */
|
|
||||||
|
|
||||||
.alert {
|
|
||||||
padding: 15px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-success {
|
|
||||||
background-color: #d4edda;
|
|
||||||
border-color: #c3e6cb;
|
|
||||||
color: #155724;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-info {
|
|
||||||
background-color: #d1ecf1;
|
|
||||||
border-color: #bee5eb;
|
|
||||||
color: #0c5460;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-warning {
|
|
||||||
background-color: #fff3cd;
|
|
||||||
border-color: #ffeaa7;
|
|
||||||
color: #856404;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-error,
|
|
||||||
.alert-danger {
|
|
||||||
background-color: #f8d7da;
|
|
||||||
border-color: #f5c6cb;
|
|
||||||
color: #721c24;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ==========================================================================
|
|
||||||
Typography
|
|
||||||
========================================================================== */
|
|
||||||
|
|
||||||
h1, h2, h3, h4, h5, h6 {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
font-weight: 600;
|
|
||||||
line-height: 1.2;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 { font-size: 2.5rem; }
|
|
||||||
h2 { font-size: 2rem; }
|
|
||||||
h3 { font-size: 1.75rem; }
|
|
||||||
h4 { font-size: 1.5rem; }
|
|
||||||
h5 { font-size: 1.25rem; }
|
|
||||||
h6 { font-size: 1rem; }
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: var(--color-primary);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover {
|
|
||||||
color: var(--color-primary-dark);
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ==========================================================================
|
|
||||||
Responsive Styles
|
|
||||||
========================================================================== */
|
|
||||||
|
|
||||||
@media (max-width: 767px) {
|
|
||||||
.header-inner {
|
|
||||||
flex-direction: column;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-modules {
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Mobile navigation styling
|
|
||||||
* Note: This provides basic stacking for mobile. For a toggle menu,
|
|
||||||
* add a .mobile-menu-toggle button and implement show/hide functionality.
|
|
||||||
* See template.js for the toggle handler.
|
|
||||||
*/
|
|
||||||
.site-navigation ul {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.site-navigation a {
|
|
||||||
padding: 12px 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Mobile menu toggle button (add to your template HTML) */
|
|
||||||
.mobile-menu-toggle {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 10px;
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Mobile menu active state */
|
|
||||||
.site-navigation.mobile-menu-active {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.row {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
[class*="col-"] {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ==========================================================================
|
|
||||||
Accessibility
|
|
||||||
========================================================================== */
|
|
||||||
|
|
||||||
.sr-only {
|
|
||||||
position: absolute;
|
|
||||||
width: 1px;
|
|
||||||
height: 1px;
|
|
||||||
padding: 0;
|
|
||||||
margin: -1px;
|
|
||||||
overflow: hidden;
|
|
||||||
clip: rect(0, 0, 0, 0);
|
|
||||||
white-space: nowrap;
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:focus,
|
|
||||||
button:focus {
|
|
||||||
outline: 2px solid var(--color-primary);
|
|
||||||
outline-offset: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ==========================================================================
|
|
||||||
Utilities
|
|
||||||
========================================================================== */
|
|
||||||
|
|
||||||
.text-center {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-right {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-left {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mt-0 { margin-top: 0; }
|
|
||||||
.mt-1 { margin-top: 0.25rem; }
|
|
||||||
.mt-2 { margin-top: 0.5rem; }
|
|
||||||
.mt-3 { margin-top: 1rem; }
|
|
||||||
.mt-4 { margin-top: 1.5rem; }
|
|
||||||
.mt-5 { margin-top: 3rem; }
|
|
||||||
|
|
||||||
.mb-0 { margin-bottom: 0; }
|
|
||||||
.mb-1 { margin-bottom: 0.25rem; }
|
|
||||||
.mb-2 { margin-bottom: 0.5rem; }
|
|
||||||
.mb-3 { margin-bottom: 1rem; }
|
|
||||||
.mb-4 { margin-bottom: 1.5rem; }
|
|
||||||
.mb-5 { margin-bottom: 3rem; }
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* @package Joomla.Site
|
|
||||||
* @subpackage Templates.yourtemplate
|
|
||||||
*
|
|
||||||
* @copyright Copyright (C) 2026. All rights reserved.
|
|
||||||
* @license GNU General Public License version 2 or later
|
|
||||||
*/
|
|
||||||
|
|
||||||
defined('_JEXEC') or die;
|
|
||||||
|
|
||||||
use Joomla\CMS\Factory;
|
|
||||||
use Joomla\CMS\Language\Text;
|
|
||||||
use Joomla\CMS\Uri\Uri;
|
|
||||||
|
|
||||||
/** @var Joomla\CMS\Document\ErrorDocument $this */
|
|
||||||
|
|
||||||
$app = Factory::getApplication();
|
|
||||||
$wa = $this->getWebAssetManager();
|
|
||||||
|
|
||||||
// Template params
|
|
||||||
$logo = $this->params->get('logo');
|
|
||||||
$sitename = htmlspecialchars($app->get('sitename'), ENT_QUOTES, 'UTF-8');
|
|
||||||
|
|
||||||
// Add template CSS
|
|
||||||
$wa->registerAndUseStyle(
|
|
||||||
'template.yourtemplate',
|
|
||||||
'templates/' . $this->template . '/css/template.css',
|
|
||||||
[],
|
|
||||||
['version' => 'auto']
|
|
||||||
);
|
|
||||||
?>
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="<?php echo $this->language; ?>" dir="<?php echo $this->direction; ?>">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title><?php echo $this->title; ?> - <?php echo $sitename; ?></title>
|
|
||||||
<jdoc:include type="styles" />
|
|
||||||
<jdoc:include type="scripts" />
|
|
||||||
</head>
|
|
||||||
<body class="site-error">
|
|
||||||
<div class="error-container">
|
|
||||||
<div class="error-content">
|
|
||||||
<?php if ($logo) : ?>
|
|
||||||
<a href="<?php echo Uri::root(); ?>">
|
|
||||||
<img src="<?php echo Uri::root() . htmlspecialchars($logo, ENT_QUOTES, 'UTF-8'); ?>" alt="<?php echo $sitename; ?>" class="error-logo">
|
|
||||||
</a>
|
|
||||||
<?php else : ?>
|
|
||||||
<h1 class="site-title">
|
|
||||||
<a href="<?php echo Uri::root(); ?>"><?php echo $sitename; ?></a>
|
|
||||||
</h1>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<div class="error-message">
|
|
||||||
<h2><?php echo Text::_('JERROR_LAYOUT_PAGE_NOT_FOUND'); ?></h2>
|
|
||||||
<div class="error-details">
|
|
||||||
<p><strong><?php echo Text::_('JERROR_LAYOUT_ERROR_HAS_OCCURRED_WHILE_PROCESSING_YOUR_REQUEST'); ?></strong></p>
|
|
||||||
<?php if ($this->debug) : ?>
|
|
||||||
<div class="error-debug">
|
|
||||||
<p><?php echo Text::_('JERROR_LAYOUT_NOT_ABLE_TO_VISIT'); ?></p>
|
|
||||||
<ul>
|
|
||||||
<li><?php echo Text::_('JERROR_LAYOUT_AN_OUT_OF_DATE_BOOKMARK_FAVOURITE'); ?></li>
|
|
||||||
<li><?php echo Text::_('JERROR_LAYOUT_MIS_TYPED_ADDRESS'); ?></li>
|
|
||||||
<li><?php echo Text::_('JERROR_LAYOUT_SEARCH_ENGINE_OUT_OF_DATE_LISTING'); ?></li>
|
|
||||||
<li><?php echo Text::_('JERROR_LAYOUT_YOU_HAVE_NO_ACCESS_TO_THIS_PAGE'); ?></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="error-actions">
|
|
||||||
<a href="<?php echo Uri::root(); ?>" class="btn btn-primary">
|
|
||||||
<?php echo Text::_('JERROR_LAYOUT_GO_TO_THE_HOME_PAGE'); ?>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php if ($this->debug) : ?>
|
|
||||||
<div class="error-technical">
|
|
||||||
<h3><?php echo Text::_('JERROR_LAYOUT_TECHNICAL_INFORMATION'); ?></h3>
|
|
||||||
<p><?php echo $this->error->getMessage(); ?></p>
|
|
||||||
<p>
|
|
||||||
<?php if ($this->error->getCode()) : ?>
|
|
||||||
<?php echo Text::_('JERROR_LAYOUT_ERROR_CODE'); ?>: <?php echo $this->error->getCode(); ?>
|
|
||||||
<?php endif; ?>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,183 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* @package Joomla.Site
|
|
||||||
* @subpackage Templates.yourtemplate
|
|
||||||
*
|
|
||||||
* @copyright Copyright (C) 2026. All rights reserved.
|
|
||||||
* @license GNU General Public License version 2 or later
|
|
||||||
*/
|
|
||||||
|
|
||||||
defined('_JEXEC') or die;
|
|
||||||
|
|
||||||
use Joomla\CMS\Factory;
|
|
||||||
use Joomla\CMS\Uri\Uri;
|
|
||||||
|
|
||||||
/** @var Joomla\CMS\Document\HtmlDocument $this */
|
|
||||||
|
|
||||||
$app = Factory::getApplication();
|
|
||||||
$wa = $this->getWebAssetManager();
|
|
||||||
|
|
||||||
// Detecting Active Variables
|
|
||||||
$option = $app->input->getCmd('option', '');
|
|
||||||
$view = $app->input->getCmd('view', '');
|
|
||||||
$layout = $app->input->getCmd('layout', '');
|
|
||||||
$task = $app->input->getCmd('task', '');
|
|
||||||
$itemid = $app->input->getCmd('Itemid', '');
|
|
||||||
$sitename = htmlspecialchars($app->get('sitename'), ENT_QUOTES, 'UTF-8');
|
|
||||||
$menu = $app->getMenu()->getActive();
|
|
||||||
$pageclass = $menu !== null ? $menu->getParams()->get('pageclass_sfx', '') : '';
|
|
||||||
|
|
||||||
// Template params
|
|
||||||
$logo = $this->params->get('logo');
|
|
||||||
$siteTitle = $this->params->get('siteTitle', $sitename);
|
|
||||||
$siteDescription = $this->params->get('siteDescription', '');
|
|
||||||
$containerFluid = (bool) $this->params->get('containerFluid', false);
|
|
||||||
$stickyHeader = (bool) $this->params->get('stickyHeader', false);
|
|
||||||
|
|
||||||
// Add template CSS
|
|
||||||
$wa->registerAndUseStyle(
|
|
||||||
'template.yourtemplate',
|
|
||||||
'templates/' . $this->template . '/css/template.css',
|
|
||||||
[],
|
|
||||||
['version' => 'auto']
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add template JavaScript
|
|
||||||
$wa->registerAndUseScript(
|
|
||||||
'template.yourtemplate',
|
|
||||||
'templates/' . $this->template . '/js/template.js',
|
|
||||||
[],
|
|
||||||
['version' => 'auto', 'defer' => true]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add viewport meta tag for responsive design
|
|
||||||
$this->setMetaData('viewport', 'width=device-width, initial-scale=1.0');
|
|
||||||
?>
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="<?php echo $this->language; ?>" dir="<?php echo $this->direction; ?>">
|
|
||||||
<head>
|
|
||||||
<jdoc:include type="metas" />
|
|
||||||
<jdoc:include type="styles" />
|
|
||||||
<jdoc:include type="scripts" />
|
|
||||||
</head>
|
|
||||||
<body class="site <?php echo $option
|
|
||||||
. ' view-' . $view
|
|
||||||
. ($layout ? ' layout-' . $layout : ' no-layout')
|
|
||||||
. ($task ? ' task-' . $task : ' no-task')
|
|
||||||
. ($itemid ? ' itemid-' . $itemid : '')
|
|
||||||
. ($pageclass ? ' ' . $pageclass : '')
|
|
||||||
. ($this->direction === 'rtl' ? ' rtl' : '');
|
|
||||||
?>">
|
|
||||||
|
|
||||||
<?php if ($this->countModules('top')) : ?>
|
|
||||||
<div class="top-bar">
|
|
||||||
<div class="<?php echo $containerFluid ? 'container-fluid' : 'container'; ?>">
|
|
||||||
<jdoc:include type="modules" name="top" style="html5" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<header class="site-header<?php echo $stickyHeader ? ' sticky-header' : ''; ?>">
|
|
||||||
<div class="<?php echo $containerFluid ? 'container-fluid' : 'container'; ?>">
|
|
||||||
<div class="header-inner">
|
|
||||||
<div class="site-branding">
|
|
||||||
<?php if ($logo) : ?>
|
|
||||||
<a href="<?php echo Uri::root(); ?>" title="<?php echo $siteTitle; ?>">
|
|
||||||
<img src="<?php echo Uri::root() . htmlspecialchars($logo, ENT_QUOTES, 'UTF-8'); ?>" alt="<?php echo $siteTitle; ?>" class="site-logo">
|
|
||||||
</a>
|
|
||||||
<?php else : ?>
|
|
||||||
<div class="site-title">
|
|
||||||
<a href="<?php echo Uri::root(); ?>" title="<?php echo $siteTitle; ?>">
|
|
||||||
<?php echo $siteTitle; ?>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if ($siteDescription) : ?>
|
|
||||||
<div class="site-description">
|
|
||||||
<?php echo htmlspecialchars($siteDescription, ENT_QUOTES, 'UTF-8'); ?>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php if ($this->countModules('header')) : ?>
|
|
||||||
<div class="header-modules">
|
|
||||||
<jdoc:include type="modules" name="header" style="html5" />
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<?php if ($this->countModules('navigation')) : ?>
|
|
||||||
<nav class="site-navigation" role="navigation">
|
|
||||||
<div class="<?php echo $containerFluid ? 'container-fluid' : 'container'; ?>">
|
|
||||||
<jdoc:include type="modules" name="navigation" style="html5" />
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<?php if ($this->countModules('banner')) : ?>
|
|
||||||
<div class="banner-area">
|
|
||||||
<div class="<?php echo $containerFluid ? 'container-fluid' : 'container'; ?>">
|
|
||||||
<jdoc:include type="modules" name="banner" style="html5" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<main class="site-main">
|
|
||||||
<div class="<?php echo $containerFluid ? 'container-fluid' : 'container'; ?>">
|
|
||||||
<div class="row">
|
|
||||||
<?php if ($this->countModules('sidebar-left')) : ?>
|
|
||||||
<aside class="sidebar sidebar-left col-md-3">
|
|
||||||
<jdoc:include type="modules" name="sidebar-left" style="html5" />
|
|
||||||
</aside>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<div class="content-area <?php
|
|
||||||
echo $this->countModules('sidebar-left') && $this->countModules('sidebar-right') ? 'col-md-6'
|
|
||||||
: ($this->countModules('sidebar-left') || $this->countModules('sidebar-right') ? 'col-md-9'
|
|
||||||
: 'col-md-12');
|
|
||||||
?>">
|
|
||||||
<?php if ($this->countModules('main-top')) : ?>
|
|
||||||
<div class="main-top">
|
|
||||||
<jdoc:include type="modules" name="main-top" style="html5" />
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<div class="main-content">
|
|
||||||
<jdoc:include type="message" />
|
|
||||||
<jdoc:include type="component" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php if ($this->countModules('main-bottom')) : ?>
|
|
||||||
<div class="main-bottom">
|
|
||||||
<jdoc:include type="modules" name="main-bottom" style="html5" />
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<?php if ($this->countModules('sidebar-right')) : ?>
|
|
||||||
<aside class="sidebar sidebar-right col-md-3">
|
|
||||||
<jdoc:include type="modules" name="sidebar-right" style="html5" />
|
|
||||||
</aside>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<?php if ($this->countModules('footer')) : ?>
|
|
||||||
<footer class="site-footer">
|
|
||||||
<div class="<?php echo $containerFluid ? 'container-fluid' : 'container'; ?>">
|
|
||||||
<jdoc:include type="modules" name="footer" style="html5" />
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<?php if ($this->countModules('debug')) : ?>
|
|
||||||
<div class="debug-area">
|
|
||||||
<jdoc:include type="modules" name="debug" style="none" />
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
/**
|
|
||||||
* Custom JavaScript for yourtemplate
|
|
||||||
*
|
|
||||||
* This file is for user customizations.
|
|
||||||
* It will not be overwritten during template updates.
|
|
||||||
*
|
|
||||||
* @package Joomla.Site
|
|
||||||
* @subpackage Templates.yourtemplate
|
|
||||||
* @copyright Copyright (C) 2026. All rights reserved.
|
|
||||||
* @license GNU General Public License version 2 or later
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Add your custom JavaScript code below this line
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
/**
|
|
||||||
* Template JavaScript for yourtemplate
|
|
||||||
*
|
|
||||||
* @package Joomla.Site
|
|
||||||
* @subpackage Templates.yourtemplate
|
|
||||||
* @copyright Copyright (C) 2026. All rights reserved.
|
|
||||||
* @license GNU General Public License version 2 or later
|
|
||||||
*/
|
|
||||||
|
|
||||||
(function () {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize template functionality when DOM is ready
|
|
||||||
*/
|
|
||||||
function initTemplate() {
|
|
||||||
// Mobile menu toggle
|
|
||||||
initMobileMenu();
|
|
||||||
|
|
||||||
// Smooth scroll for anchor links
|
|
||||||
initSmoothScroll();
|
|
||||||
|
|
||||||
// Sticky header enhancement
|
|
||||||
initStickyHeader();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mobile menu functionality
|
|
||||||
*
|
|
||||||
* Note: This requires a mobile menu toggle button to be added to your template.
|
|
||||||
* Add the following button in your header area:
|
|
||||||
*
|
|
||||||
* <button class="mobile-menu-toggle" aria-expanded="false" aria-label="Toggle navigation">
|
|
||||||
* <span class="menu-icon"></span>
|
|
||||||
* </button>
|
|
||||||
*
|
|
||||||
* And add CSS for the toggle button and mobile menu states.
|
|
||||||
*/
|
|
||||||
function initMobileMenu() {
|
|
||||||
const mobileMenuToggle = document.querySelector('.mobile-menu-toggle');
|
|
||||||
const navigation = document.querySelector('.site-navigation');
|
|
||||||
|
|
||||||
if (mobileMenuToggle && navigation) {
|
|
||||||
mobileMenuToggle.addEventListener('click', function () {
|
|
||||||
navigation.classList.toggle('mobile-menu-active');
|
|
||||||
const isExpanded = navigation.classList.contains('mobile-menu-active');
|
|
||||||
this.setAttribute('aria-expanded', isExpanded ? 'true' : 'false');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Smooth scroll for anchor links
|
|
||||||
*/
|
|
||||||
function initSmoothScroll() {
|
|
||||||
const anchorLinks = document.querySelectorAll('a[href^="#"]');
|
|
||||||
|
|
||||||
anchorLinks.forEach(function (link) {
|
|
||||||
link.addEventListener('click', function (e) {
|
|
||||||
const targetId = this.getAttribute('href');
|
|
||||||
|
|
||||||
// Skip if it's just "#" or "#top"
|
|
||||||
if (targetId === '#' || targetId === '#top') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const targetElement = document.querySelector(targetId);
|
|
||||||
|
|
||||||
if (targetElement) {
|
|
||||||
e.preventDefault();
|
|
||||||
targetElement.scrollIntoView({
|
|
||||||
behavior: 'smooth',
|
|
||||||
block: 'start'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sticky header enhancement
|
|
||||||
*/
|
|
||||||
function initStickyHeader() {
|
|
||||||
const header = document.querySelector('.site-header.sticky-header');
|
|
||||||
|
|
||||||
if (header) {
|
|
||||||
let lastScrollTop = 0;
|
|
||||||
|
|
||||||
window.addEventListener('scroll', function () {
|
|
||||||
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
|
|
||||||
|
|
||||||
// Add/remove class based on scroll direction
|
|
||||||
if (scrollTop > lastScrollTop && scrollTop > 100) {
|
|
||||||
// Scrolling down
|
|
||||||
header.classList.add('header-hidden');
|
|
||||||
} else {
|
|
||||||
// Scrolling up
|
|
||||||
header.classList.remove('header-hidden');
|
|
||||||
}
|
|
||||||
|
|
||||||
lastScrollTop = scrollTop <= 0 ? 0 : scrollTop;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize when DOM is ready
|
|
||||||
*/
|
|
||||||
if (document.readyState === 'loading') {
|
|
||||||
document.addEventListener('DOMContentLoaded', initTemplate);
|
|
||||||
} else {
|
|
||||||
initTemplate();
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* @package Joomla.Site
|
|
||||||
* @subpackage Templates.yourtemplate
|
|
||||||
*
|
|
||||||
* @copyright Copyright (C) 2026. All rights reserved.
|
|
||||||
* @license GNU General Public License version 2 or later
|
|
||||||
*/
|
|
||||||
|
|
||||||
defined('_JEXEC') or die;
|
|
||||||
|
|
||||||
use Joomla\CMS\Factory;
|
|
||||||
use Joomla\CMS\Uri\Uri;
|
|
||||||
|
|
||||||
/** @var Joomla\CMS\Document\HtmlDocument $this */
|
|
||||||
|
|
||||||
$app = Factory::getApplication();
|
|
||||||
$wa = $this->getWebAssetManager();
|
|
||||||
|
|
||||||
// Template params
|
|
||||||
$logo = $this->params->get('logo');
|
|
||||||
$sitename = htmlspecialchars($app->get('sitename'), ENT_QUOTES, 'UTF-8');
|
|
||||||
|
|
||||||
// Add template CSS
|
|
||||||
$wa->registerAndUseStyle(
|
|
||||||
'template.yourtemplate',
|
|
||||||
'templates/' . $this->template . '/css/template.css',
|
|
||||||
[],
|
|
||||||
['version' => 'auto']
|
|
||||||
);
|
|
||||||
?>
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="<?php echo $this->language; ?>" dir="<?php echo $this->direction; ?>">
|
|
||||||
<head>
|
|
||||||
<jdoc:include type="metas" />
|
|
||||||
<jdoc:include type="styles" />
|
|
||||||
<jdoc:include type="scripts" />
|
|
||||||
</head>
|
|
||||||
<body class="site-offline">
|
|
||||||
<div class="offline-container">
|
|
||||||
<div class="offline-content">
|
|
||||||
<?php if ($logo) : ?>
|
|
||||||
<img src="<?php echo Uri::root() . htmlspecialchars($logo, ENT_QUOTES, 'UTF-8'); ?>" alt="<?php echo $sitename; ?>" class="offline-logo">
|
|
||||||
<?php else : ?>
|
|
||||||
<h1><?php echo $sitename; ?></h1>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<jdoc:include type="message" />
|
|
||||||
<jdoc:include type="component" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<extension type="template" client="site" method="upgrade">
|
|
||||||
<name>yourtemplate</name>
|
|
||||||
<creationDate>2026-04-27</creationDate>
|
|
||||||
<author>Your Name</author>
|
|
||||||
<authorEmail>your.email@example.com</authorEmail>
|
|
||||||
<authorUrl>https://www.example.com</authorUrl>
|
|
||||||
<copyright>Copyright (C) 2026. All rights reserved.</copyright>
|
|
||||||
<license>GNU General Public License version 2 or later</license>
|
|
||||||
<version>1.0.0</version>
|
|
||||||
<description>TPL_YOURTEMPLATE_XML_DESCRIPTION</description>
|
|
||||||
|
|
||||||
<files>
|
|
||||||
<filename>index.php</filename>
|
|
||||||
<filename>templateDetails.xml</filename>
|
|
||||||
<!--
|
|
||||||
IMPORTANT: Add actual PNG images before distribution
|
|
||||||
|
|
||||||
1. Replace template_thumbnail.png.txt and template_preview.png.txt with real PNG images
|
|
||||||
2. Uncomment the two lines below after adding the actual image files
|
|
||||||
3. See src/IMAGES.md for detailed instructions
|
|
||||||
|
|
||||||
Once images are added, uncomment these lines:
|
|
||||||
-->
|
|
||||||
<!-- <filename>template_thumbnail.png</filename> -->
|
|
||||||
<!-- <filename>template_preview.png</filename> -->
|
|
||||||
<filename>offline.php</filename>
|
|
||||||
<filename>error.php</filename>
|
|
||||||
<folder>css</folder>
|
|
||||||
<folder>js</folder>
|
|
||||||
<folder>images</folder>
|
|
||||||
<folder>fonts</folder>
|
|
||||||
<folder>language</folder>
|
|
||||||
<folder>html</folder>
|
|
||||||
</files>
|
|
||||||
|
|
||||||
<positions>
|
|
||||||
<position>top</position>
|
|
||||||
<position>header</position>
|
|
||||||
<position>navigation</position>
|
|
||||||
<position>banner</position>
|
|
||||||
<position>sidebar-left</position>
|
|
||||||
<position>sidebar-right</position>
|
|
||||||
<position>main-top</position>
|
|
||||||
<position>main</position>
|
|
||||||
<position>main-bottom</position>
|
|
||||||
<position>footer</position>
|
|
||||||
<position>debug</position>
|
|
||||||
</positions>
|
|
||||||
|
|
||||||
<config>
|
|
||||||
<fields name="params">
|
|
||||||
<fieldset name="advanced">
|
|
||||||
<field
|
|
||||||
name="logo"
|
|
||||||
type="media"
|
|
||||||
label="TPL_YOURTEMPLATE_LOGO_LABEL"
|
|
||||||
description="TPL_YOURTEMPLATE_LOGO_DESC"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<field
|
|
||||||
name="siteTitle"
|
|
||||||
type="text"
|
|
||||||
label="TPL_YOURTEMPLATE_SITE_TITLE_LABEL"
|
|
||||||
description="TPL_YOURTEMPLATE_SITE_TITLE_DESC"
|
|
||||||
default=""
|
|
||||||
/>
|
|
||||||
|
|
||||||
<field
|
|
||||||
name="siteDescription"
|
|
||||||
type="textarea"
|
|
||||||
label="TPL_YOURTEMPLATE_SITE_DESCRIPTION_LABEL"
|
|
||||||
description="TPL_YOURTEMPLATE_SITE_DESCRIPTION_DESC"
|
|
||||||
rows="3"
|
|
||||||
cols="30"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<field
|
|
||||||
name="containerFluid"
|
|
||||||
type="radio"
|
|
||||||
label="TPL_YOURTEMPLATE_CONTAINER_FLUID_LABEL"
|
|
||||||
description="TPL_YOURTEMPLATE_CONTAINER_FLUID_DESC"
|
|
||||||
default="0"
|
|
||||||
layout="joomla.form.field.radio.switcher"
|
|
||||||
>
|
|
||||||
<option value="0">JNO</option>
|
|
||||||
<option value="1">JYES</option>
|
|
||||||
</field>
|
|
||||||
|
|
||||||
<field
|
|
||||||
name="stickyHeader"
|
|
||||||
type="radio"
|
|
||||||
label="TPL_YOURTEMPLATE_STICKY_HEADER_LABEL"
|
|
||||||
description="TPL_YOURTEMPLATE_STICKY_HEADER_DESC"
|
|
||||||
default="0"
|
|
||||||
layout="joomla.form.field.radio.switcher"
|
|
||||||
>
|
|
||||||
<option value="0">JNO</option>
|
|
||||||
<option value="1">JYES</option>
|
|
||||||
</field>
|
|
||||||
</fieldset>
|
|
||||||
</fields>
|
|
||||||
</config>
|
|
||||||
</extension>
|
|
||||||
Reference in New Issue
Block a user